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.

  1. 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!
  2. 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")
)
  1. 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

  1. Archivos .RDS frente a .RData: Una de las formas más comunes de cargar y guardar datos en Base R es con las funciones load() y save() 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 y loadRDS son funciones de Base R para hacer esto, pero recomendamos usar save_rds y load_rds del paquete readr para tener mayor coherencia y las mejoras de rendimiento. cuando usa load_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 de save, el equivalente funcional con RDS sería crear una lista (nombrada) que contuviera cada uno de estos objetos y guardarla.
  2. CSV: Una vez más, el paquete readr como parte de Tidvyerse es excelente, con un read_csv() mucho más rápido que el read.csv() de Base R. Para CSV masivos (> 5 GB) veremos que data.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() y data.table::fwrite() superan a write.csv() de Base R también por un margen significativo.
  3. 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 RMejor estilo
__
read.csv()readr::read_csv() or data.table::fread()
write.csv()readr::write_csv() or data.table::fwrite()
readRDSreadr::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_columndf %>% 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.

Deja un comentario

Tu dirección de correo electrónico no será publicada.