Skip to content

Módulo 5B - Scripting y Buenas prácticas

Conceptos de programación en R

Lo que sige es el .rmd del curso.

---
title: "\[ATR\] Taller IFIBYNE (M5B)" author: "Nicolás Méndez" date: "13/7/2019" output:
    pdf_document:
      toc: true
      toc_depth: 2
      number_sections: true
---

```{r setup, include=FALSE, message=FALSE} # Opciones globales knitr::opts_chunk$set(echo = TRUE) knitr::opts_chunk$set(out.width='90%', dpi=200) options(max.print=100) library("gridExtra") library("tidyverse") options(tinytex.verbose = TRUE)

Tip del módulo: abusar de los atajos es bueno, ver \[atajos I\](<https://appsilon.com/r-studio-shortcuts-and-tips/>) y \[atajos II\](<https://appsilon.com/r-studio-shortcuts-and-tips-part-2/>).

# Scripting (y mejores prácticas de trabajo en R)

## Organizar el código

Como decía antes, es recontra recomendable que la carpeta donde estemos trabajando esté ordenada, por ejemplo, de la siguiente manera:

* Una carpeta "R" con todos los scripts. \* Una carpeta "data" con todos los datos. \* Una carpeta "results" con el output de nuestros análisis.

Lectura recomendada \[acá\](<https://www.r-bloggers.com/structuring-r-projects/>).

### 123 Ultraprolijo

Hay cosas ultraprolijas para las intenciones de esta guía, pero acá hay una lista de ellas:

* Usar un sistema de control de cambios\[\^git\]. \* Guardar las versiones de los paquetes que usamos en un experimento\[\^packrat\] es la respuesta a:

    ¿Qué hacer para reproducir un análisis antiguo si los paquetes que usaste ya no existen?

Lectura recomendada \[acá\](<https://www.r-bloggers.com/%F0%9F%93%81-project-oriented-workflow/>).

\[\^git\]: git para control de cambios.

\[\^packrat\]: packrat para gestionar paquetes.

### Bloques: `{}`

Si queremos que varias líneas de código se ejecuten como una sola, las ponemos entre corchetes.

Es una forma de emprolijar cosas como escribir \[funciones\](#funciones).

```{r, eval = F} # Si nos paramos en esta línea con el cursor y tocamos Ctrl + Enter {

    # Todo lo que está entre las llaves se ejecuta junto
    print("Hola")
    print("Mundo")

}

# También se usa en funciones, "if" y "for" mi_funcion <- function(argumento1){

    # Para indicar que todo lo que ocurre acá es el cuerpo
    print("ATR")
    return("atodoerre")

}

# Sin el "{}" solo se ejecutaría el código después de la función if(FALSE)

    print("No imprime")
    print("Pero esto imprime igual :V")  # En R la indentación no importa

# También se puede usar para asignaciones mi_variable <- {

    print("Hola, esto no formará parte de la variable")
    5  # Esto tampoco
    7  # Pero esto sí, porque solo lo último importa

}

``` Los bloques también son convenientes en el IDE, para ocultarlos (con la pequeña flechita que aparece a su altura sobre la izquierda).

### Pipes: `%>%`

Un operador para pasar el output de una función al input de otra. Como funciones conectadas por caños.

Si queremos aplicar una serie de funciones a una tabla, y nos interesa solo el final, podemos hacer tres cosas:

1. Anidar funciones 2. Usar objetos intermedios 3. Usar el pipe `%>%`

Considere:

```{r, eval = F} # Opcion 1 tabla <- gather(filter(read_csv("ejemplos/iris.csv"), Sepal.Length >= 0.1), "key", "value", -Species)

# Opcion 2 tabla <- read_csv("ejemplos/iris.csv") tabla_filtrada <- filter(tabla, Sepal.Length >= 0.1) tabla_filtrada_larga <- gather(tabla_filtrada, "key", "value", -Species)

# Opción 3 tabla <- read_csv("ejemplos/iris.csv") %>%

    filter(Sepal.Length >= 0.1) %>%
    gather( "key", "value", -Species)

En el ejemplo anterior, cuál manera de escribir el mismo código tiene más:

  1. Claridad (que puedas entender más adelante)
  2. Elegancia (escribir poco)
  3. Eficiencia (que ande, y en lo posible rápido)

En una de las tres opciones:

  • El flujo de información es evidente (claridad) * Solo hacemos una asignación (elegancia) * No definimos objetos que qudan como basura (eficiencia)

No está (tan) mal usar objetos intermedios (2), es más desprolijo, pero eviten anidar funciones (1).

Al principio es normal usar la segunda, pero quien no conoce %>% a cualquier santo le reza (3).

Leer ?magrittr::`%>%`.

Importar código: source()

Cuando definimos nuestras propias funciones, es buena práctica ponerlas en otro/s archivo/s (diferenes al que usamos para trabajar).

source() sirve para cargar ese código, que está escrito en otro archivo.

Cuando querramos usar las funciones definidas en otros scripts, las cargamos con:

```{r, eval = F} # Falla, no definido. sumar_cosas(1, 2, multiplicar_cosas(2, 4))

source("R/algunas_funciones_utiles.R") source("R/otras_funciones_utiles.R")

multiplicar_cosas(2, 4) %>% # Con un pipe es más claro :)

sumar_cosas(1, 2)

```

Ejercicios:

  • Ejecutar el código anterior.

  • Examinar la definicón de las funciones que importamos.

    Para esto, escribir su nombre sin paréntesis en la consola y apretando Enter.

  • Examinar la definición de read.csv

    ¿En qué función está basada?

Las funciones, en general, son el tema que siguen.

Por qué y cuándo escribir funciones

Todas las funciones que usamos hasta ahora están hechas de código, que típicamente podemos inspeccionar escribiendo el nombre de la función en la consola.

Por ejemplo, podemos ver que read.csv es un envoltorio de read.table con opciones configuradas por defecto.

Vale preguntarse por qué existe si es solo un caso particular de otra función.

La respuesta es que, gracias a read.csv, amenudo nos ahorramos escribir esto:

  read.table(header = TRUE, sep = ",", quote = "\"", dec = ".", fill = TRUE, comment.char = "")

La parte de "amenudo" nos dice cuando y la parte de "nos ahorramos escribir" nos dice por qué.

Concepto de función

  • Es un conjunto de instrucciones, * que procesa variables de entrada (input) y devuelve valores de salida (output), * para cumplir con una tarea.

Para definir una función en R, escribimos:

```{r, eval = F} mi_funcion <- function(input1, input2, input3, ...){ # Acá nombramos los argumentos de la función (input)

# Hacer algo con los parámetros
resultado <- sum(input1, input2, input3)

# Decir algo lindo
print("Algo lindo :)")

# Devolver un resultado
return(resultado)

}

Si ahora queremos inspeccionar la definicón, ejecutamos solo el nombre de la función mi_funcion

Si queremos usarla, le ponemos los paréntesis, como a cualquier otra mi_funcion(1, 2, 3) sum(1, 2, 3) ```

Donde:

  • mi_funcion es el nombre que damos a la función que queremos. * input1, input2, input3, ... son los argumentos de la función, separados por coma. * Entre {} está el código de la función. * return() indica el fin de la función y el valor que se devuelve. * "..." es una forma genérica de referirse a *los demás argumentos que se pasen a la función*.

¿Es esta una función que vale la pena? Pensemos...

  • ¿Voy a usar la función *amenudo*? * ¿*Acorta* el código?

Sin usar funciones, son dos líneas:

{r, eval=F} print("Algo lindo :)") sum(1, 2, 3)

Usando funciones, once líneas:

```{r, eval =F} mi_funcion_que_suma <- function(input1, input2, input3, ...){

# Hacer algo con los parámetros

resultado <- sum(input1, input2, input3)

# Decir algo lindo
print("Algo lindo :)")

return(resultado)

} mi_funcion_que_suma(1, 2, 3) ```

Para que valga la pena, tengo que necesitar esta función varias veces.

Otro criterio para decidir escribir una función es que sea útil para otras personas.

Buena práctica Es posible inspeccionar el "interior" de una función mientras se ejecuta usando algunas funciones de R:

  • debug(), undebug(), y debugonce()
  • browser() y recover()
  • trace()

Vean que pasa si usamos mal la función, y luego qué pasa si intentamos debuguearla.

En el segundo caso, debería aparecer el modo debug de Rstudio. Don't panic!

```{r, eval = FALSE} mi_funcion_que_suma(1, "2", 3) # Va a fallar, porque sum() no suma caracteres.

debugonce(mi_funcion_que_suma)

mi_funcion_que_suma(1, "2", 3) ```

A veces no sabemos dónde y por qué falla nuestro código, o queremos inspeccionar lo que pasa dentro de una función.

Por qué y cuándo escribir objetos

El usuario normal de R no va a necesitar escribirlos, pero puede usarlos todo el tiempo.

R es un lenguaje orientado a objetos. Cuando vean S3 o S4 en algún lado, están lidiando con ellos.

- Los componentes de un objeto se pueden invocar con @: objeto@componente

- Los métodos de un objeto se pueden ver con methods(): methods(objeto)

Probablemente sea buena idea escribir objetos cuando estés desarrollando un paquete.

Por qué y cuándo escribir paquetes

Digamos que con el tiempo o por placer desarrollaste varias funciones para tus análisis de rutina.

Y que cada vez que las querés usar, las tenés que copiar o sourcear de algún otro script.

- Quizás debas armarte un paquete [para vos](https://hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch/).

Si compartir ese código con el resto de tu laboratorio ayudaría a tus compas.

O si lo que armaste fuera relevante para gente fuera del labo.

- Es hora de armar un paquete.

Lectura recomendada, [acá](http://r-pkgs.had.co.nz/intro.html).

Salvar el entorno de trabajo

Salvar tu código cada vez que puedas es un buen reflejo (Ctrl + S).

Pero las variables y objetos que cargamos se pierden:

- Si cerramos Rstudio - Si se corta la luz - Si querés volver a algún momento específico del script - Si estás con datos muy grandes y tardan mucho en cargarse desde un csv.

Para estos casos, podemos guardar el "workspace" en archivos que sirvan para restaurarlo:

  1. Al cerrar Rstudio, nos pregunta si queremos guardar la imagen en ".Rdata"

    La sesión se puede restaurar desde ahí, cuando volvamos a abir el script.

  2. Dentro de un script, con save(), load() & attach() podemos guardar el entorno en archivos.

    Una o varias veces a lo largo del script, para ir guardando los avances.

Si queremos guardar UN objeto en particular, usaríamos:

  1. saveRDS() & loadRDS()

Otra forma de dejar lo que estábamos haciendo para después, es usar [R Projects](#rprojects) (seguir leyendo).

Organizar el espacio de trabajo

Hay varias formas de trabajar en Rstudio. Lo más recomendado es empezar creando un R project y trabajar con scripts.

Cuando estemos cómodos con ese formato y querramos armar documentos o informes lindos sin salir de Rstudio, usar Rmarkdown es buena idea.

Es recontra recomendable que la carpeta donde estemos trabajando esté ordenada, por ejemplo, de la siguiente manera:

  • Una carpeta "R" con todos los scripts. * Una carpeta "data" con todos los datos. * Una carpeta "results" con el output de nuestros análisis.

    U otras según las particularidades de tu uso de R.

Scripts

Son los típicos archivos con extensión ".R"

  • La forma más elemental de trabajar es escribiendo el código en scripts.

  • El código en un script puede importarse en otro script usando source(), y esto es muy útil para no terminar trabajando con un archivo enorme, cada vez más difícil de navegar, comprender, y editar.

Rmarkdown

Lectura recomendada [acá](https://bookdown.org/yihui/rmarkdown/notebook.html).

  • Son una buena herramienta para generar documentos o informes como el que están leyendo.

  • El informe se puede generar a partir de un script, con la función rmarkdown::render().

  • También se pueden desarrollar de forma interactiva dentro de Rstudio, en un archivo con extensión "rmd" (yo prefiero esto).

    "File -> New File -> R Notebook / R Markdown".

    Este tipo de archivo contiene cachos de código en R y texto en formato markdown 1.

    Apretando el botón "Knit" (Ctrl + Shift + K) se puede generar un archivo PDF como el que están leyendo.

R Projects

  • Los "R projects" con extensión ".Rpoj" representan un espacio de trabajo.

    La idea es tener un ambiente organizado y contenido en una carpeta, que podamos dejar y volver a abrir más tarde tal como estaba.

  • En este espacio de trabajo puede haber tanto *scripts* como *rmarkdown*, y todo lo relevante al trabajo.

    Cada vez que abran un .Rproj, se abrirán también los archivos que tenían abiertos la última vez que lo cerraron (y las variables, si las guardaron en un .Rdata antes de cerrar).

Ejemplo - Abrir ejemplos

  1. Ir a la carpeta de ejemplos, que viene con el material para este módulo.

    Entren a las carpetas, encontrarán en cada una un .Rproj y una carpeta "R" con un script.

  2. Ignoren los scripts. Abran un .Rproj y después abran el otro.

    Noten que los scripts y las variables que yo estaba usando, se reestablecieron y son específicas de cada proyecto.

    Esta es la idea principal de un proyecto en Rstudio: aislar espacios de trabajo y retomarlos sin complicaciones.

Ejercicio - Crear un proyecto

Podés seguir las instrucciones disponibles [acá](https://support.rstudio.com/hc/en-us/articles/200526207-Using-Projects), y si no anda internet:

  1. Usando el menú de Rstudio, crear un proyecto en un nuevo directorio.

    Ir a "Archivo -> Nuevo proyecto" y seguir las instrucciones.

    Nombrarlo "ejemplo.Rproj".

    Rstudio debería abrir el proyecto nuevo.

  2. Crear un script yendo a "Archivo -> Nuevo archivo -> R Script" (Ctrl + Shift + N).

    Insertar el siguiente código:

```{r, eval = F} library(ggplot2)

Armar dataset y tomar una muestra x <- rnorm(100, mean = 100, sd = 10)

Histogramas qplot(x, bins = 10, xlim = c(50,150)) ```

  1. Guardar el script en una carpeta "R" dentro del proyecto (Ctrl + S) y ejecutar el código.

  2. Al cerrar Rstudio, guardar la "imagen" del espacio de trabajo (en un .Rdata).

    En la carpeta del proyecto ahora hay dos archivos normales:

    • un archivo .Rproj
    • un archivo .R en la carpeta R

    También hay archivos ocultos que guardan más información sobre el espacio de trabajo.

  3. Ahora abran el Rproj.

    Deberían ver que se abrió el script que estaban editando antes.

    Además, las variables que habíamos cargado al ejecutar el script siguen ahí.

Cuando solamente están editando UN script, usar proyectos no hace la diferencia.

Pero, a medida que hagamos más y más análisis, esta organización es cada vez más importante.

Los proyectos de Rstudio simplifican la división de tu trabajo en múltiples contextos; cada uno con su propia carpeta, espacios de trabajo, historia y archivos.

Cuando algo no anda

Leer la documentación (offline) La causa principal de ver errores en vez de resultados es no leer la documentación.

debuguear (offline) ¿Dónde está *tu* error?

Seguro a alguien le pasó (online/offline)

Pedir ayuda a la gente del instituto :)

Hay muchos blogs y sitios de Q&A para problemas de R.

Es muy improbable que alguien no haya tenido el mismo error que vos.

Es improbable que ese alguien no haya preguntado lo mismo en internet.

Pedir ayuda (online)

Pero si aprenden a pedir ayuda bien, alguien estará encantad@ de responder su pregunta en internet.

En este caso, "bien" es algo [precisamente definido](https://sindominio.net/ayuda/preguntas-inteligentes.html) en [varios idiomas](http://www.catb.org/~esr/faqs/smart-questions.html).

Para evitar el Infierno de R, estos pasos generales ayudan:

  1. Ya pasaste por la documentación. 2. Ya hiciste una búsqueda rápida en internet. 3. Ya hiciste una consulta rápida con algún compañero/a.

Si eso no resuelve tu problema:

  1. Hacer una pregunta en internet (en stackoverflow, típicamente).

Cuando las cosas andan Más temible que el error es su ausencia.

Un programa de computadora siempre da un resultado, por lo tanto, *siempre* hay que revisar el input y el output de nuestros scripts.

Minimizar dolores de cabeza:

  • Probar nuestro código con fragmentos revisados del dataset, diseñados para poner a prueba cada parte del análisis.
  Debería devolver output que podamos inspeccionar y validar "manualmente".

* Intentar romper el código: si le pasamos input evidentemente incorrecto, el script debería fallar.

  Si no lo hace deberíamos tener (mucho) miedo.

* ¿Alguna más?

Finalmente, hay un principio de la computación que dice:

"*garbarge in, garbage out*"

Quizás el código es bueno, pero si el input no está bien no hay como aRReglarlo (?).

The R Inferno, un libro acogedor.

Los 9 círculos infernales de R [y cómo evitarlos](https://www.burns-stat.com/pages/Tutor/R_inferno.pdf).

Ese libro es una colección de pequeñas dificultades inesperadas (o inentendibles) y cómo evitarlas.

Algunas secciones de este módulo fueron sacadas de ahí.

Es una buena lectura para codear mejor, y es relativamente graciosa para quienes ya pasaron alguna noche con R.

“If you are using R and you think you’re in hell, this is a map for you. A book about trouble spots, oddities, traps, glitches in R.”

Respecto a hacer buenas preguntas en internet, dice:

Sometimes background on why you are asking the question is relevant.

Consider the question:

"*Is there a way to order pizza with R?*"

The answer is, of course:

"*Of course there is a way to order pizza. This \*is\* R*"


  1. Pero también pueden incluir y ejecutar código Python, SQL, BASH, C, JS, entre otros.