Skip to content

Taller IFIBYNE (M3.A)

Lo que sige es el .rmd de la guía.

--- 
title: "[ATR] Taller IFIBYNE (M3.A)" author: "Nicolás Méndez" date: "2019/8/13"
output:
    pdf_document:
      toc: true
      toc_depth: 3
      number_sections: true
urlcolor: blue

Esta guía fue hecha en Rstudio con Rmarkdown.

Tip del módulo: Ctrl + Shift + C (atajo para comentar y descomentar una o varias líneas).

# Opciones globales knitr::opts_chunk\$set(echo = TRUE) knitr::opts_chunk\$set(out.width='100%', dpi=300) knitr::opts_chunk\$set(fig.align='center', dev = "png", results = "hold") options(max.print=100) library("gridExtra")

Gráficos usando ggplot2

La idea del módulo 3 es que salgan haciendo gráficos con ggplot2, un paquete del mundillo tidy.

En esta primera parte les propongo aprender sobre la forma general de escribir gráficos en ggplot.

En la parte B y C hay ejemplos y ejercicios para los tipos de gráficos más comunes.

Preparar entorno

  1. Cargar la librería de ggplot. 2. Explorar el data frame "mtcars", un dataset de ejemplo, incluído en el entorno de R por defecto.
library(tidyverse)
# Incluye ggplot2

# Usaremos datos de ejemplo tablas precargadas: head(mtcars)

mtcars es una tabla de eficiencias de motores en Millas Por Galon.

Usaremos dos de sus variables: cyl (cantidad de cilindros) y mpg (eficiencia).

Para ver otros, ejecutar: library(help = "datasets").

Flashback

  • ¿Qué columnas tiene el data frame mtcars?

    Podés usar: colnames() names() View()

  • ¿Qué tipo de datos contiene cada columna?

    str(mtcars)

    lapply(mtcars, class)

    glimpse(mtcars)

  • Resumir la variable mpg agrupando por cyl usando la media:

    En base R es: aggregate(mpg ~ cyl, mtcars, FUN = mean)

    O en tidycosas: mtcars %>% group_by(cyl) %>% summarise(mpg = mean(mpg))

Anatomía de ggplot

Conceptualmente, hay tres partes que deben estar para poder graficar:

  1. La geometría del gráfico. 2. Los datos que queremos graficar. 3. Mateadatos sobre cómo queremos representar los datos en ese gráfico.

Ahora vamos a ver cómo entregar toda esta información a ggplot.

La Base

La función ggplot() es la base, todos los gráficos hechos con esta librería empiezan llamando a esa función.

# ggplot() es la base del gráfico ggplot()

Ahora especificamos el primer ítem de la lista anterior: la geometría del gráfico.

# Las capas de geometría y otras opciones se añaden con "+"
# Para armar un gráfico de dispersión, añadiríamos geom_point() ggplot() + geom_point()

# Un gráfico de dos capas se vería así 
ggplot() +
    geom_point() +   # Una capa de dispersión
    geom_contour()   # Una capa de curvas de contorno

Esta es la estructura fundamental de cualquier gráfico hecho con ggplot.

Entonces, si quisiéramos graficar puntos y una curva de regresión por encima, deberíamos escribir algo así:

# Primero la base ggplot() +
    geom_point() +  # Luego la capa de dispersión
    geom_smooth()   # Y por encima la capa de la curva

¿Qué otras geometrías hay?

  • Escribir geom_ en la consola de Rstudio y explorar las opciones que aparecen para autocompletar.

    La referencia para cada geometría y función de ggplot2 está acá. Además hay varios sitios para buscar tipos de gráficos en R, como esta colección.

  • En esta guía vamos a usar principalmente geom_point y geom_line.

El Contenido

ggplot() y las geometrías como geom_point(), toman dos argumentos necesarios:

  • data: una tabla de datos limpios y bien estructurados (datos "largos", de gather()).
  • mapping: la relación entre datos y geometría (llamados aesthetics, o aes).

Estas dos partes son las que nos faltaban, y ya casi podemos armar un gráfico simple.

data

Los datos se pasan al argumento data de ggplot:

```{r, eval = FALSE} ggplot(data = mtcars) + # Base del gráfico

geom_point()            # Primera y única capa
Pero como todavía no le dijimos como usar esos datos, ggplot devuelve un error, pidiendo "aesthetics".

#### `aes()`

La función `aes()` se usa para pasar la relación entre los datos y la geometría al argumento `mapping` de ggplot.

Esta función nos ayuda a decirle a ggplot cúales de las variables en nuestra tabla queremos usar en los ejes y formas o colores de los elementos del gráfico.

En la geometría de dispersón, `geom_point`, debemos pasar una variable al parámetro `x` y otra al parámetro `y` dentro de `aes()`.

```{r, out.width = '40%', fig.width=3, fig.height=3}
# Un gráfico de dispersión
ggplot(data = mtcars, mapping = aes(x = cyl, y = mpg)) +
    geom_point()

Deconstruir el código

Identificar las partes en el ejemplo anterior

data = mtcars

  • Los datos están en el data frame mtcars.

mapping = aes(x = cyl, y = mpg)

  • La columna cyl va en el eje horizontal (x)
  • La columna mpg en el eje vertical (y)

geom_point()

  • Hacer un gráfico de dispersión.
  • Como no dimos argumentos para esta geometría, hereda por defecto la data y el mapping de ggplot().

Bonus: otra forma de escribir lo mismo

mapping y data pueden especificarse en ggplot y/o en una geometría como geom_point().

En este caso ayuda leer ?ggplot. Según la documentación, la idea es que cada geom tenga la flexibilidad de usar data y/o mapping diferentes a los que pusimos en ggplot() al principio, o en otros geoms.

Lo siguente es equivalente al ejemplo anterior.

```{r, eval = FALSE} # Data y mapping se pueden pasar a cada geom por separado ggplot() +

geom_point(data = mtcars, mapping = aes(x = cyl, y = mpg))
Y la forma más prolija de escribir este gráfico es la siguiente:

```{r, eval = FALSE} # Si los parámetros están en orden, no es necesario nombrarlos ggplot(mtcars, aes(cyl, mpg)) +

    geom_point()

Los "adornos"

Hay varias opciones que afectan diferentes elementos de la visualización.

  1. Todo lo relevante al título del gráfico o de los ejes, la leyenda, etc.

    (ejemplos acá). 2. Los límites, escalas y transformaciones de los ejes (log, sqrt, etc.) se pueden cambiar después de definir la geometría

    (ver ejemplos).

  2. Las opciones que afectan el aspecto (colores, tamaños, estilos de línea, puntos o texto) se pueden explorar ejecutando browseVignettes("ggplot2").

    En muchos casos, estos elemntos del gráfico pueden asociarse a una variable en aes().

    Si incluyéramos aes(color = mpg) en el llamado a geom_point(), los puntos de esa geometría se colorearían según el valor de mpg.

    La escala de colores que se usaría se puede cambiar. Por ejemplo, añadiendo scale_color_viridis_c() en el siguiente ejemplo.

    Prueben ejecutarlo para ver qué sale :)

```{r, eval = FALSE} # Escala de colores por defecto ggplot(mtcars, aes(cyl, mpg)) +

geom_point(aes(color = mpg))  # mapeamos el valor de "mpg" al color del punto

Escala de colores tipo "viridis" ggplot(mtcars, aes(cyl, mpg)) +

geom_point(aes(color = mpg)) +
scale_color_viridis_c()
### Ejercicio 1

En este ejemplo hay varias líneas comentadas a ignorar (por ahora).

#### Consignas

* Ejecutar el código como está y observar el resultado. * Identificar las 3 partes principales de cada plot: **data**, **aes** y **geom**. * ¿Funcionó bien `geom_line()` en el gráfico del medio?

    ¿Y en el gráfico a la derecha? ¿Qué cambió?

```{r capas1, eval=FALSE}
# Izquierda: Un gráfico de dispersión
ggplot(data = mtcars, mapping = aes(cyl, mpg)) + # Base del gráfico
    geom_point()                                     # Primera y única capa

# Centro: Un gráfico de 2 capas
ggplot(data = mtcars, mapping = aes(cyl, mpg)) +
    #geom_bar(aes(group = cyl), stat = "summary", alpha = .5) +
    geom_point() + 
    geom_line() +
    xlab("# Cilindros")                    # Así se modifica el axis label

# Derecha: el mismo gráfico, con otra estadística para

geom_line() ggplot(mtcars, aes(cyl, mpg)) +
    #geom_boxplot(aes(group = cyl), fill = "#00000000") + 
    geom_point() + 
    #stat_summary(geom = "line", fun.y = "mean") +
    geom_line(stat = "summary", fun.y = "mean")

Resultado esperado

p0 <- ggplot(data = mtcars, mapping = aes(cyl, mpg)) +

    geom_point()

p1 <- ggplot(data = mtcars, mapping = aes(cyl, mpg)) +

    geom_point() + 
    geom_line() +
    xlab("# Cilindros")

p2 <- ggplot(mtcars, aes(cyl, mpg)) +

    geom_point() + 
    geom_line(stat = "summary", fun.y = "mean")

# <https://stackoverflow.com/questions/1249548/side-by-side-plots-with-ggplot2> grid.arrange(p0, p1, p2, ncol = 3)

Ejercicio 2

Jugar un poco con el ejemplo anterior.

Consignas

  • Comentar o descomentar las capas de a una y ver qué pasa.

    Quitar o agregar el #, manualmente o bien con el atajo: Ctrl + Shift + C.

  • Cambiar el orden de las capas.

    ¿Cual es la capa superior? ¿La primera o la última en el código?

  • Modificar el color de los elementos en el gráfico:

  • Colorear las líneas con rojo escribiendo color = "red" dentro de geom_line().

  • Agregar un aes a geom_point() para colorear los puntos según la cantidad de cilindros:

    geom_point(aes(color = cyl))

    • Agregar un aes a geom_point() para dar forma a los puntos según la cantidad de cilindros:

    geom_point(aes(shape = cyl))

    ¿Con qué error nos encontramos?

Resultado Si hicieron el último punto, se encontraron con esto:

"Error: A continuous variable can not be mapped to shape"

  • ¿Que tipo de escala es shape?

  • ¿Que tipo de variable era cyl?

El error aparece cuando pedimos que una variable contínua sea traducida a una escala únicamente discreta.

Arreglemos eso discretizando la cantidad de cilindros poniendo a cyl dentro de factor(). Voilà!

ggplot(mtcars, aes(factor(cyl), mpg)) + # Ahora 5 y 7 no se incluyen en el eje x.

    geom_point(aes(shape = factor(cyl)))    #  Arreglamos el error y apareció una leyenda.
ggplot(mtcars, aes(factor(cyl), mpg)) + # Ahora 5 y 7 no se incluyen en el eje x.

    geom_point(aes(shape = factor(cyl)))  + #  Arreglamos el error y apareció una leyenda.
    theme_minimal()

Buenas prácticas

Sabemos que la cantidad de cilindros en un motor no es una variable contínua, así que deberíamos haberla tratado de forma discreta desde el principio.

Para eso era necesario hacer la conversión al inicio del script: mtcars <- mutate(mtcars, cyl = factor(cyl))

De esta forma, cyl es un factor en el data frame, y no es necesario aclararlo en ggplot().

mtcars2 <- mutate(mtcars, cyl = factor(cyl))

ggplot(mtcars2, aes(cyl, mpg)) + # Ahora 5 y 7 no se incluyen en el eje x.

    geom_point(aes(shape = factor(cyl)))  # Ya no se produce el error.

Además, apareció una leyenda para las escalas no representadas en los ejes del gráfico.

Bonus: discretizar cyl también afecta la escala de color

El efecto de convertir cyl a factor en aes(color = cyl) es que la escala de color también cambia.

Pasa de una escala de color contínua (que por defecto va de negro a azul) a una escala de color discreta (y linda) que permite diferenciar claramente las categorías.

p6 <- ggplot(mtcars, aes(cyl, mpg)) +

    geom_point(aes(color = cyl), show.legend = F) +
    ggtitle("Sin factor(cyl)")

p7 <- ggplot(mtcars, aes(cyl, mpg)) +

    geom_point(aes(color = factor(cyl)), show.legend = F) +
    ggtitle("factor(cyl)\nsolo en aes(color)")

p8 <- ggplot(mtcars2, aes(cyl, mpg)) +

    geom_point(aes(color = cyl), show.legend = F) +
    ggtitle("factor(cyl)\n en dataframe")

library(gridExtra) grid.arrange(p6, p7, p8, ncol = 3)

La escala de color puede ser discreta o contínua, y en ese caso ggplot se adapta al tipo de datos la variable.

R y confusión: ¿el código que viste en otro lado es diferente pero hace lo mismo?

Si. En R suele haber varias formas de hacer exactamente lo mismo.

Algunos "criterios" para elegir como escribir el ggplot (o cualquier cosa):

  1. Claridad (que puedas entender más adelante)
  2. Elegancia (escribir poco)
  3. Eficiencia (que ande, y en lo posible rápido)
  4. Consistencia (hacerlo siempre de la misma manera)
# Poner los elementos compartidos en ggplot() es más corto que escribirlos siempre por separado

# Más elegante ggplot(mtcars, aes(cyl, mpg)) +

    geom_point() + 
    geom_line(stat = "summary", fun.y = "mean")

# No elegante ggplot() +

    geom_point(data = mtcars, aes(cyl, mpg)) + 
    geom_line(data = mtcars, aes(cyl, mpg), stat = "summary", fun.y = "mean")

# No consistente ggplot(mtcars, aes(cyl, mpg)) +

    geom_point() + 
    stat_summary(fun.y = "mean", geom = "line")
p3 <- ggplot(data = mtcars, mapping = aes(cyl, mpg)) +

    geom_point() + 
    geom_line(stat = "summary", fun.y = "mean")

p4 <- ggplot() +

    geom_point(data = mtcars, aes(cyl, mpg)) + 
    geom_line(data = mtcars, aes(cyl, mpg), stat = "summary", fun.y = "mean")

p5 <- ggplot(mtcars, aes(cyl, mpg)) +

    geom_point() + 
    stat_summary(geom = "line", fun.y = "mean")

grid.arrange(p3, p4, p5, ncol = 3)

Bonus: aclaraciones sobre stat_summary

En el último gráfico, stat_summary reemplaza a geom_point.

Detrás de escena, cada geom tiene por defecto una transformación estadística (o stat) que se aplica sobre los datos.

En el caso de geom_line es "identity", que no tiene efecto. Por lo tanto todos los puntos aparecían conectados.

Al especificar geom_line(stat = "summary", fun.y = "mean"), cambiamos la transformación para obtener la media por cada grupo.

También es posible hacer el camino inverso, llamando a la transforamción estadística stat_summary primero, y especificar la geometría dentro: stat_summary(fun.y = "mean", geom = "line").