Las siguientes son pautas y ejemplos de código que ilustran las buenas prácticas en R.
Conveción de nombres en buenas prácticas en R
Nombre de archivos
Para los nombres de archivo, tratemos siempre nombres de archivo fáciles de interpretar y no usemos espacios en blanco en el nombre del archivo, por ejemplo:
# Buena práctica Calculo_de_muestra.R # Mal ejemplo Mi archivo.R
Objetos y funciones
Los nombres de sus variables sean más expresivos y explícitos, hace que su código sea más legible y explícito.
Usemos snake_case
para objetos y el caso de PascalCase
para funciones. Además, los nombres de los objetos y funciones deben ser lo más explícitos pero cortos. Un ejemplo:
# Buena práctica mi_vector <- c(1,2,3) NuestraFuncion() nuestra_funcion() ComputePower <- function (base, exponent) { return (base**exponent) } # Mala Práctica myvector <- c(1,2,3) nuestrafuncion() ComputePowerOfBaseWithExponent <- function (base, exponent) { return (base**exponent) }
Utilice el operador =
en lugar de <-
. De manera similar, en una llamada de función, definitivamente usemos «argumentos con nombre» y separemos los argumentos para que su código sea más legible. Aquí hay un ejemplo de cómo se vería una llamada con parámetros bien definidos y nombrados de la función para calcular_KSSS, y abajo sin argumentos con nombre y sin ninguna separación:
# Buena práctica input_1_KSSS_ill = calculate_KSSS( centroids = all_study_school_shapes, statistical_input = input_1, k_nearest_neighbors = 5, heat_map_title = "Local Clustering of Illness-Specific\n Absence Rates in all years during Flu Season" ) # Mala Práctica input_1_KSSS_ill <- calculate_KSSS(all_study_school_shapes, input_1, 5, "Local Clustering of Illness-Specific\n Absence Rates in all years during Flu Season")
Evitar objetos innecesarios
Evitar operadores innecesarios
R es un lenguaje interpretado, cada operador en los scripts R requiere una búsqueda de nombre cada vez que lo usa.
Los siguientes dos ejemplos de código son funcionalmente equivalentes. Sin embargo, el primer ejemplo de código requiere aproximadamente el doble de tiempo de procesamiento debido a los múltiples paréntesis.
# Buena práctica system.time({ I = 0 while (I<100000) { 10 I = I + 1 } }) user system elapse 0.055 0.000 0.055 # Mala Práctica system.time({ I = 0 while (I<100000) { ((((((((((10)))))))))) I = I + 1 } }) user system elapse 0.125 0.000 0.125
Evitar variables incrementales dentro de un bucle
Siempre asignemos previamente los objetos que se utilizarán dentro de los bucles. La ejecución de bucles en R es lenta y el crecimiento de objetos dentro de los bucles hará que el programa R sea particularmente lento. Siempre debemos intentar preasignar vectores, listas y objetos de datos a los que se accede dentro de cualquier bucle.
Consideremos los siguientes dos ejemplos de código. El primero accede y hace crecer un vector dentro del bucle for
, mientras que el segundo asigna previamente el vector y accede al vector dentro del bucle for
sin aumentar su tamaño.
# Buena práctica square_loop_noinit <- function (n) { x <- integer(n) for (i in 1:n) { x[i] <- i^2 } system.time({ square_loop_noinit(200) }) user system elapse 0.099 0.000 0.099 # Mala Práctica square_loop_noinit <- function (n) { x <- c() for (i in 1:n) { x <- c(x, i^2) } system.time({ square_loop_noinit(200) }) user system elapse 0.257 0.000 0.257
Usa vectorización en la medida de lo posible
Lo que pocas personas saben es que en R todo es un vector. En la secuencia de comandos R siempre debemos escribir código vectorizado o usar núcleos compilados preexistentes (que ya están vectorizados y optimizados) para evitar la sobrecarga del intérprete.
Consideremos los siguientes dos ejemplos de código. El segundo ejemplo logramos una aceleración de 38 veces mediante el uso de código vectorizado proporcionado por núcleos compilados.
# Buena práctica vec <- function(x) rnorm(1000) system.time({ vec() }) user system elapse 0.009 0.000 0.009 # Mala Práctica Ply <- function(x) lapply (rep(1, 1000), rnorm) system.time({ Ply() }) user system elapse 0.348 0.000 0.348
Herramientas automatizadas para estilos y flujos de trabajo de proyectos
El paquete here
es un gran paquete de R que ayuda a múltiples colaboradores a lidiar con el desorden que es trabajar con los directorios dentro de una estructura de proyecto de R. Digamos que tenemos un proyecto R en la ruta /home/oski/Some-R-Project
. Otro colaborador podría clonar el repositorio y trabajar con él en alguna otra ruta, como /home/bear/R-Code/Some-R-Project
. Tratar con directorios y rutas de trabajo explícitamente puede ser una gran molestia y, como puede imaginar, configurar una configuración con rutas requiere que esas rutas funcionen de manera flexible para todos los colaboradores de un proyecto. Aquí es donde entra el paquete here
y esta es una gran viñeta que lo describe.
- Autoformato de código: RStudio incluye una fantástica utilidad integrada (atajo de teclado: CTRL-SHIFT-A (windows) o CMD-SHIFT-A para mac) para autoformatear fragmentos de código resaltados para adaptarse a muchas de las mejores prácticas de estilo. Por lo general, hace que el código sea más legible y corrige muchas de las cosas pequeñas que quizás no tenga ganas de arreglar usted mismo. ¡Pruébelo como un «primer paso» en algún código suyo que no siga muchas de estas mejores prácticas!
- Alineador de asignaciones: Un excelente paquete R nos permite formatear de manera muy poderosa grandes fragmentos de código de asignación para que sean mucho más limpios y legibles. Siga estas instrucciones y cree un atajo de teclado de su elección (recomendación: CTRL-SHIFT-Z (windows) o CMD-SHIFT-Z para mac). Ponemos un ejemplo de cómo la alineación de asignaciones puede mejorar drásticamente la legibilidad del código:
# Buena práctica OUSD_not_found_aliases = list( "Brookfield Village Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Brookfield"), "Carl Munck Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Munck"), "Community United Elementary School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Community United"), "East Oakland PRIDE Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "East Oakland Pride"), "EnCompass Academy" = str_subset(string = OUSD_school_shapes$schnam, pattern = "EnCompass"), "Global Family School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Global"), "International Community School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "International Community"), "Madison Park Lower Campus" = "Madison Park Academy TK-5", "Manzanita Community School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Manzanita Community"), "Martin Luther King Jr Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "King"), "PLACE @ Prescott" = "Preparatory Literary Academy of Cultural Excellence", "RISE Community School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Rise Community") ) # Mala Práctica OUSD_not_found_aliases = list( "Brookfield Village Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Brookfield"), "Carl Munck Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Munck"), "Community United Elementary School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Community United"), "East Oakland PRIDE Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "East Oakland Pride"), "EnCompass Academy" = str_subset(string = OUSD_school_shapes$schnam, pattern = "EnCompass"), "Global Family School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Global"), "International Community School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "International Community"), "Madison Park Lower Campus" = "Madison Park Academy TK-5", "Manzanita Community School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Manzanita Community"), "Martin Luther King Jr Elementary" = str_subset(string = OUSD_school_shapes$schnam, pattern = "King"), "PLACE @ Prescott" = "Preparatory Literary Academy of Cultural Excellence", "RISE Community School" = str_subset(string = OUSD_school_shapes$schnam, pattern = "Rise Community") )
- StyleR: Otro paquete R genial de Tidyverse que puede ser poderoso y usarse como un primer paso en proyectos completos que necesitan refactorización. La función más útil del paquete es la función style_dir, que diseñará todos los archivos dentro de un directorio determinado. Consulte la documentación de la función y la viñeta vinculada anteriormente para obtener más detalles.
Prácticas obsoletas de Base R que se deben evitar
- Archivos
.RDS
frente a.RData
: Una de las formas más comunes de cargar y guardar datos en Base R es con las funcionesload()
ysave()
para serializar varios objetos en un archivo. Los mayores problemas con esta práctica incluyen la incapacidad de controlar los nombres de las cosas que se cargan, la confusión inherente que esto crea en la comprensión del código antiguo y la incapacidad de cargar elementos individuales de un archivo guardado. Para esto, recomendamos usar el formato RDS para guardar objetos R.saveRDS
yloadRDS
son funciones de Base R para hacer esto, pero recomendamos usarsave_rds
yload_rds
del paquetereadr
para tener mayor coherencia y las mejoras de rendimiento. cuando usaload_rds
, debe asignar lo que se está cargando a una variable (es decir,algun_nombre_descriptivo = load_rds (…)
). Luego, use la variable como lo haría. Esto hace que el código sea menos frágil y no necesitemos depender de un archivo.RData
para cargar en un nombre particular para que el código se ejecute correctamente. Si tenemos muchos objetos R relacionados que de otro modo habría guardado todos juntos usando la función desave
, el equivalente funcional conRDS
sería crear una lista (nombrada) que contuviera cada uno de estos objetos y guardarla. - CSV: Una vez más, el paquete
readr
como parte deTidvyerse
es excelente, con unread_csv()
mucho más rápido que elread.csv()
de Base R. Para CSV masivos (> 5 GB) veremos quedata.table::fread()
es el lector de CSV más rápido en cualquier lenguaje de ciencia de datos que existe. Para escribir CSV,readr::write_csv()
ydata.table::fwrite()
superan awrite.csv()
de Base R también por un margen significativo. - Feather: si usa R y Python, puede consultar el paquete
Feather
para intercambiar datos entre los dos idiomas de forma rápida.
Tidyverse
A lo largo de este documento hay referencias a Tidyverse, pero esta sección es para mostrarle explícitamente cómo transformar sus tendencias Base R a Tidyverse (o Data.Table, el competidor optimizado para el rendimiento de Tidyverse). Tidyverse se está convirtiendo rápidamente en el estándar de oro en el análisis de datos R y los paquetes y el código de ciencia de datos modernos deben usar el estilo y los paquetes de Tidyverse a menos que haya una razón importante para no hacerlo (es decir, canalizaciones de big data que se beneficiarían de las optimizaciones de rendimiento de Data.Table).
El autor del paquete ha publicado un excelente libro de texto sobre R para ciencia de datos, que se basa en gran medida en muchos paquetes de Tidyverse y puede valer la pena consultarlo.
Base R | Mejor estilo |
---|---|
_ | _ |
read.csv() | readr::read_csv() or data.table::fread() |
write.csv() | readr::write_csv() or data.table::fwrite() |
readRDS | readr::read_rds() |
saveRDS() | readr::write_rds() |
_ | _ |
data.frame() | tibble::tibble() or data.table::data.table() |
rbind() | dplyr::bind_rows() |
cbind() | dplyr::bind_cols() |
df$some_column | df %>% dplyr::pull(some_column) |
df$some_column = ... | df %>% dplyr::mutate(some_column = ...) |
df[get_rows_condition,] | df %>% dplyr::filter(get_rows_condition) |
df[,c(col1, col2)] | df %>% dplyr::select(col1, col2) |
merge(df1, df2, by = ..., all.x = ..., all.y = ...) | df1 %>% dplyr::left_join(df2, by = ...) or dplyr::full_join or dplyr::inner_join or dplyr::right_join |
_ | _ |
str() | dplyr::glimpse() |
grep(pattern, x) | stringr::str_which(string, pattern) |
gsub(pattern, replacement, x) | stringr::str_replace(string, pattern, replacement) |
ifelse(test_expression, yes, no) | if_else(condition, true, false) |
Nested: ifelse(test_expression1, yes1, ifelse(test_expression2, yes2, ifelse(test_expression3, yes3, no))) | case_when(test_expression1 ~ yes1, test_expression2 ~ yes2, test_expression3 ~ yes3, TRUE ~ no) |
proc.time() | tictoc::tic() and tictoc::toc() |
stopifnot() | assertthat::assert_that() or assertthat::see_if() or assertthat::validate_that() |
Optimización para el rendimiento
También puede darse el caso de que estemos trabajando con conjuntos de datos muy grandes. En general, definiría esto como más de 10 millones de filas. Como se describe en este documento, los 3 jugadores principales en el espacio de análisis de datos son Base R, Tidvyerse (más específicamente, dplyr
) y data.table
. Para la mayoría de las cosas, Base R es inferior tanto a dplyr
como a data.table
, con una sintaxis concisa pero menos clara y menos rápida. dplyr
está diseñado para datos medianos y pequeños, y aunque es muy rápido para el uso diario, ofrece el máximo rendimiento por la facilidad de uso y la sintaxis en comparación con data.table
.
También puede lograr un aumento de rendimiento ejecutando comandos dplyr
en data.tables
, que considero que es lo mejor de ambos mundos, dado que data.table
es un tipo especial de data.frame
y bastante fácil de convertir con as. función data.table()
. La aceleración se debe al uso que hace dplyr
del backend data.table
y, en el futuro, este acoplamiento debería volverse aún más natural.