Capítulo 4 Gráficos - Parte II

4.1 Títulos en los ejes

Las expresiones matemáticas pueden incorporarse con el comando quote(). Las reglas que se utilizan para interpretar las expresiones se pueden encontrar escribiendo ?plotmath.

values <- seq(from = -2, to = 2, by = .01)
df <- data.frame(x = values, y = values ^ 3)
ggplot(df, aes(x, y)) + 
  geom_path() + 
  labs(y = quote(f(x) == x^3))

El tipo de letra se puede modificar con el family aesthetic que permite usar el nombre de una fuente, pero se requiere cierto cuidado. Solo hay tres tipos de letra que funcionan en todas partes: “sans” (predeterminada), “serif” o “mono”.

df <- data.frame(x = 1, y = 3:1, family = c("sans", "serif", "mono"))
ggplot(df, aes(x, y)) + 
  geom_text(aes(label = family, family = family))

4.2 Combinar distintos graficos

ggplot2 está pensado para la creación de gráficos individuales. Si bien la opción facet proporciona la posibilidad de producir varias tramas secundarias, todas ellas forman parte de la misma visualización principal, compartiendo capas, datos y escalas. Sin embargo, a menudo es necesario usar varias tramas diferentes para contar una historia. Existe una variedad de paquetes para organizar gráficos separados como por ejemplo patchwork.

p1 <- ggplot(mpg) + 
  geom_point(aes(x = displ, y = hwy))

p2 <- ggplot(mpg) + 
  geom_bar(aes(x = as.character(year), fill = drv), position = "dodge") + 
  labs(x = "year")

p3 <- ggplot(mpg) + 
  geom_density(aes(x = hwy, fill = drv), colour = NA) + 
  facet_grid(rows = vars(drv))
library(patchwork)
p1 + p2

En la primera línea de abajo se muestra como ordenar 3 graficos en una grilla especifica y luego en agregar etiquetas a cada sub-gráfico que luego se utilizan para identificarlas en el texto. ggplot2 tiene el elemento tag para realizar esto y patchwork ofrece funcionalidad para hacerlo automáticamente usando el argumento tag_levels. Puede generar niveles automáticos en caracteres latinos, números arábigos o números romanos.

p123 <- p1 | (p2 / p3)
p123 + plot_annotation(tag_levels = "I") # Numeros romanos en mayuscula

Una característica adicional es que es posible anidar para definir nuevos niveles de etiquetado:

p123[[2]] <- p123[[2]] + plot_layout(tag_level = "new")
p123 + plot_annotation(tag_levels = c("I", "a"))

Si bien gran parte de la funcionalidad del mosaico se relaciona con la alineación de gráficos en una cuadrícula, también se puede hacer inserciones, es decir, gráficos colocados encima de otro gráfico. Se utiliza la función inset_element() que sirve para marcar el gráfico dado como un recuadro que se colocará en el gráfico principal, además de registrar la ubicación deseada, etc. El uso básico es así:

p1 + inset_element(p2, left = 0.5, bottom = 0.4, right = 0.9, top = 0.95)

Una característica de las inserciones es que se comportan como tramas secundarias de mosaico estándar. Esto significa que se pueden modificar después del montaje, por ejemplo usando &:

p12 <- p1 + inset_element(p2, left = 0.5, bottom = 0.5, right = 0.9, top = 0.95)
p12 & theme_bw()

4.3 Agrandar una parte del grafico

Se puede reducir los límites de escala predeterminados para hacer zoom, pero se requiere cuidado: cuando trunca los límites de escala, algunos puntos de datos quedarán fuera de los límites establecidos y ggplot2 tiene que tomar una decisión sobre qué hacer con estos puntos de datos. El comportamiento predeterminado en ggplot2 es convertir cualquier valor de datos fuera de los límites de escala a NA. Esto significa que cambiar los límites de una escala no siempre es lo mismo que acercarse visualmente a una región del gráfico. Si su objetivo es acercar una parte de la gráfica, generalmente es mejor usar los argumentos xlim y ylim de coord_cartesian():

base <- ggplot(mpg, aes(drv, hwy)) + 
  geom_hline(yintercept = 28, colour = "red") + 
  geom_boxplot() 

base + coord_cartesian(ylim = c(10, 35)) # hace lo esperado
base + ylim(10, 35) # distorsiona el boxplot 

4.4 Escala de colores manual

Para definir colores propios se puede usar scale_fill_manual(). Esto puede ser útil si desea elegir colores que resalten una estructura de agrupación secundaria o llamar la atención sobre diferentes comparaciones.

df <- data.frame(x = c("a", "b", "c", "d"), y = c(3, 4, 1, 2))
bars <- ggplot(df, aes(x, y, fill = x)) + 
  geom_bar(stat = "identity") + 
  labs(x = NULL, y = NULL) +
  theme(legend.position = "none")
bars + 
  scale_fill_manual(
    values = c("tomato1", "tomato2", "tomato3", "tomato4")
  )

bars + 
  scale_fill_manual(
    values = c(
      "d" = "grey",
      "c" = "grey",
      "b" = "black",
      "a" = "grey"
    )
  )

4.5 Límites, epacios y etiquetas

Los límites de escala para escalas de color discretas se pueden establecer usando el argumento limits para el argumento de escala, o usando la función auxiliar lims(). Esto puede ser importante cuando la misma variable se representa en diferentes gráficos y desea asegurarse de que los colores sean consistentes en todos los gráficos. El color representa el tipo de combustible que puede ser: regular, etanol, diesel, premium o gas natural comprimido.

mpg_99 <- mpg %>% dplyr::filter(year == 1999)
mpg_08 <- mpg %>% dplyr::filter(year == 2008)

base_99 <- ggplot(mpg_99, aes(displ, hwy, colour = fl)) + geom_point() 
base_08 <- ggplot(mpg_08, aes(displ, hwy, colour = fl)) + geom_point() 

base_99
base_08

Cada trama tiene sentido por sí sola, pero la comparación visual entre las dos es difícil. Los límites de los ejes son diferentes, y debido a que en los datos de 1998 solo se representan los combustibles regulares, premium y diesel, los colores se asignan de manera inconsistente. Para asegurar un mapeo consistente para la estética del color, podemos usar lims() para establecer manualmente los límites.

base_99 + lims(colour = c("c", "d", "e", "p", "r"))
base_08 + lims(colour = c("c", "d", "e", "p", "r"))

Lo bueno de lims() es que podemos establecer los límites para múltiples estéticas a la vez. Para asegurarnos de que \(x\), \(y\) y colour usen límites consistentes, podemos hacer:

base_99 + 
  lims(
    x = c(1, 7), 
    y = c(10, 45), 
    colour = c("c", "d", "e", "p", "r")
  )

base_08 + 
  lims(
    x = c(1, 7), 
    y = c(10, 45), 
    colour = c("c", "d", "e", "p", "r")
  )

Hay dos limitaciones potenciales para estas grillas. En primer lugar, si bien establecer los límites de escala garantiza que los colores se asignen de manera idéntica en ambos gráficos, también significa que el gráfico de los datos de 1999 muestra etiquetas para los cinco tipos de combustible, a pesar de que no se usaban combustibles de etanol y gas natural comprimido. Podemos abordar esto configurando manualmente los saltos de escala, asegurándonos de que solo se muestren en la leyenda los tipos de combustible que aparecen en los datos. La segunda limitación es que las etiquetas no son particularmente útiles, lo que podemos solucionar especificándolas manualmente. Al configurar múltiples propiedades de una sola escala, puede ser más útil personalizar usando los argumentos de la función de escala en lugar de usar la función auxiliar lims():

base_99 + 
  scale_color_discrete(
    limits = c("c", "d", "e", "p", "r"), 
    breaks = c("d", "p", "r"),
    labels = c("diesel", "premium", "regular")
  )

Sin embargo, no hay nada que impida usar lims() para controlar los límites estéticos de la posición, mientras se usa scale_colour_discrete() para ejercer un control más detallado sobre la estética del color:

base_99 + 
  lims(x = c(1, 7), y = c(10, 45)) +
  scale_color_discrete(
    limits = c("c", "d", "e", "p", "r"), 
    breaks = c("d", "p", "r"),
    labels = c("diesel", "premium", "regular")
  )

base_08 + 
  lims(x = c(1, 7), y = c(10, 45)) +
  scale_color_discrete(
    limits = c("c", "d", "e", "p", "r"), 
    labels = c("compressed", "diesel", "ethanol", "premium", "regular")
  )

4.6 Leyendas

Las leyendas para escalas de colores discretas se pueden personalizar usando el argumento de guía para la función de escala o con la función auxiliar guides(), la leyenda predeterminada muestra claves individuales en una tabla, que se puede personalizar usando guide_legend(). Las opciones más útiles son:

  • nrow o ncol que especifican las dimensiones de la tabla. byrow controla cómo se llena la tabla: FALSE la llena por columna (el valor predeterminado), TRUE la llena por fila.
base <- ggplot(mpg, aes(drv, fill = factor(cyl))) + geom_bar() 

base
base + guides(fill = guide_legend(ncol = 2))
base + guides(fill = guide_legend(ncol = 2, byrow = TRUE))

  • override.aes: es útil cuando desea que los elementos de la leyenda se muestren de forma diferente a las geoms del gráfico. Esto suele ser necesario cuando ha utilizado transparencia o tamaño para lidiar con superposiciones.
base <- ggplot(mpg, aes(displ, hwy, colour = drv)) +
  geom_point(size = 4, alpha = .2, stroke = 0)

base + guides(colour = guide_legend())
base + guides(colour = guide_legend(override.aes = list(alpha = 1)))

4.7 Posición de la leyenda

Una serie de configuraciones que afectan la visualización general de las leyendas se controlan a través del sistema theme(). La posición y la justificación de las leyendas están controladas por la configuración del legend.position, que toma los valores “right”, “left”, “top”, “bottom”, or “none” (sin leyenda).

toy <- data.frame(
  const = 1, 
  up = 1:4,
  txt = letters[1:4], 
  big = (1:4)*1000,
  log = c(2, 5, 10, 2000)
)

base <- ggplot(toy, aes(up, up)) + 
  geom_point(aes(colour = txt), size = 3) + 
  xlab(NULL) + 
  ylab(NULL)

base + theme(legend.position = "left")
base + theme(legend.position = "right") # the default 
base + theme(legend.position = "bottom")
base + theme(legend.position = "none")

Alternativamente, si hay mucho espacio en blanco en el gráfico, se puede colocar la leyenda dentro del gráfico. Configurando legend.position con un vector numérico de longitud dos. Los números representan una ubicación relativa en el área del panel: c(0, 1) es la esquina superior izquierda y c(1, 0) es la esquina inferior derecha. Se define a qué esquina de la leyenda se refiere legend.position con legend.justification, que se especifica de manera similar. Colocar la leyenda exactamente donde lo desea requiere mucho ensayo y error.

base <- ggplot(toy, aes(up, up)) + 
  geom_point(aes(colour = txt), size = 3)

base + 
  theme(
    legend.position = c(0, 1), 
    legend.justification = c(0, 1)
  )

base + 
  theme(
    legend.position = c(0.5, 0.5), 
    legend.justification = c(0.5, 0.5)
  )

base + 
  theme(
    legend.position = c(1, 0), 
    legend.justification = c(1, 0)
  )

4.8 Unir leyendas

La union de leyendas ocurre con bastante frecuencia cuando se usa ggplot2. Por ejemplo, si ha asignado color tanto a puntos como a líneas, la leyenda mostrarán tanto puntos como líneas.

De forma predeterminada, una capa solo aparecerá si la estética correspondiente se asigna a una variable con aes(). Puede anular si una capa aparece o no en la leyenda con show.legend: FALSE para evitar que una capa aparezca en la leyenda; TRUE lo obliga a aparecer que puede ser útil junto con el siguiente truco para resaltar los puntos:

ggplot(toy, aes(up, up)) + 
  geom_point(size = 4, colour = "grey20") +
  geom_point(aes(colour = txt), size = 2) 

ggplot(toy, aes(up, up)) + 
  geom_point(size = 4, colour = "grey20", show.legend = TRUE) +
  geom_point(aes(colour = txt), size = 2) 

4.9 Separar leyendas

Dividir una leyenda es una tarea menos común. En general, no es recomendable asignar una estética (ej. color) a varias variables, por lo que, de forma predeterminada, ggplot2 no permite “dividir” la estética del color en varias escalas con leyendas separadas. Sin embargo, hay excepciones a esta regla general, y es posible anular este comportamiento usando el paquete ggnewscale.6 El comando ggnewscale::new_scale_colour() es una instrucción para inicializar una nueva escala de color: comandos de escala y guía que aparecen arriba, el comando new_scale_colour() se aplicará a la primera escala de color, y los comandos que aparecen a continuación se aplicarán a la segunda escala de color.

Para ilustrar esto, el gráfico de la izquierda usa geom_point() para mostrar un marcador grande para cada marca de vehículo en los datos de mpg, con una escala de un solo color que corresponde al año. A la derecha, se superpone una segunda capa geom_point() en el gráfico usando pequeños marcadores: esta capa está asociada con una escala de color diferente, que se usa para indicar si el vehículo tiene un motor de 4 cilindros.

base <- ggplot(mpg, aes(displ, hwy)) + 
  geom_point(aes(colour = factor(year)), size = 5) + 
  scale_colour_brewer("year", type = "qual", palette = 5) 

base
base + 
  ggnewscale::new_scale_colour() + 
  geom_point(aes(colour = cyl == 4), size = 1, fill = NA) + 
  scale_colour_manual("4 cylinder", values = c("grey60", "black"))

4.10 Agrupar y desagrupar

El facet es una alternativa al uso de la estética (como el color, la forma o el tamaño) para diferenciar grupos. Ambas técnicas tienen fortalezas y debilidades, basadas en las posiciones relativas de los subconjuntos. Con facet, cada grupo está separado en su propio panel y no hay superposición entre los grupos. Esto es bueno si los grupos se superponen mucho, pero hace que las pequeñas diferencias sean más difíciles de ver. Cuando se usa la aesthetics para diferenciar grupos, los grupos están muy juntos y pueden superponerse, pero las pequeñas diferencias son más fáciles de ver.

df <- data.frame(
  x = rnorm(120, c(0, 2, 4)),
  y = rnorm(120, c(1, 2, 1)),
  z = letters[1:3]
)

ggplot(df, aes(x, y)) + 
  geom_point(aes(colour = z))

ggplot(df, aes(x, y)) + 
  geom_point() + 
  facet_wrap(~z)

Las comparaciones entre facetas a menudo se benefician de una anotación bien pensada. Por ejemplo, en este caso se podría mostrar la media de cada grupo en cada panel.

df_sum <- df %>% 
  group_by(z) %>% 
  summarise(x = mean(x), y = mean(y)) %>%
  rename(z2 = z)
ggplot(df, aes(x, y)) + 
  geom_point() + 
  geom_point(data = df_sum, aes(colour = z2), size = 4) + 
  facet_wrap(~z)

Otra técnica útil es poner todos los datos en el fondo de cada panel:

df2 <- dplyr::select(df, -z)

ggplot(df, aes(x, y)) + 
  geom_point(data = df2, colour = "grey70") +
  geom_point(aes(colour = z)) + 
  facet_wrap(~z)

4.11 Leyenda manual

El ejemplo de abajo muestra como definir la estética de distintas series de tiempo de manera individual. Notar que el eje en común se declara como argumento en aes() del comando ggplot(), luego se utiliza un geom_line() para cada serie con sus propiedades y finalmente los colores y la leyenda se define con scale_color_manual().

library(scales)
nyse1 = as_tibble(NYSE %>% tail(., 36))
nyse1$date = as.Date(nyse1$date)
nyse1$DJ_return =nyse1$DJ_return*30        # Solo por una cuestión de escala para este ejemplo

ggplot(nyse1, aes(x=date)) +
  geom_line(aes(y = DJ_return, color = 'DJ_return')) +
  geom_line(aes(y = log_volume, color = 'log_volume'), linetype = 'dashed') +
  theme_minimal() +
  theme(axis.text=element_text(size=12), axis.text.x = element_text(size = 10, angle = 90), legend.position="bottom") +
  labs(title=paste('NYSE Dow Jones'), x = '', y = '%', colour = '') +
  scale_y_continuous(labels = comma_format(big.mark = '.', decimal.mark = ',')) +  
  scale_x_date(date_breaks = '3 days', date_labels = '%Y-%m-%d') +
  scale_color_manual(values = c('DJ_return' = 'darkblue', 'log_volume' = 'darkred'),
                     labels = c('Retorno', 'Volumen (en LOGs)')) +
  NULL

Similar al anterior pero definiendo el orden de la leyenda con breaks dado que por default ggplot utiliza un orden alfabético.

y_obs = c(39.8, 41.9, 45.0, 49.2, 50.6, 52.6, 55.1, 56.2, 57.3, 57.8, 55.0, 50.9, 45.6, 46.5, 48.7, 51.3, 57.7, 58.7, rep(NA, 4))
y_f = c(rep(NA, 17), 58.7, 65.08, 71.43, 74.11, 88.13)
y_fh = c(rep(NA, 17), 58.7, 69.52, 76.76, 78.85, 91.58)
y_fl = c(rep(NA, 17), 58.7, 60.64, 66.10, 69.38, 84.68)
date = 2007:2028

data = tibble(
  date = date,
  y_obs = y_obs,
  y_f = y_f,
  y_fh = y_fh,
  y_fl = y_fl,
)

cols = c('y_obs' = 'darkblue', 'y_f' = 'darkred')
labs = c('Observado', 'Estimado')
ggplot(data, aes(x=date)) +
  geom_line(aes(y = y_obs,  color = 'y_obs')) +
  geom_line(aes(y = y_f,    color = 'y_f')) +
  geom_ribbon(aes(ymin = y_fl, ymax = y_fh), color = 'darkred', fill = 'darkred', alpha = 0.2, linewidth = 0.1) +
  theme_minimal() +
  theme(axis.text=element_text(size=14), axis.text.x = element_text(size = 12), 
        legend.position="bottom", legend.text=element_text(size=14)) +
  labs(title='Consumo', x = '', y = '', colour = '') +
  scale_color_manual(values = cols,
                     labels = labs,
                     breaks = c('y_obs', 'y_f')) +
  NULL

4.12 Estadísticas

Una transformación estadística, o stat, transforma los datos, normalmente resumiéndolos de alguna manera. Por ejemplo, una estadística útil es el smoother, que calcula la media suavizada de \(y\), condicional en \(x\). Algunas alternativas son:

  • stat_bin(): geom_bar(), geom_freqpoly(), geom_histogram()

  • stat_bin2d(): geom_bin2d()

  • stat_bindot(): geom_dotplot()

  • stat_binhex(): geom_hex()

  • stat_boxplot(): geom_boxplot()

  • stat_contour(): geom_contour()

  • stat_quantile(): geom_quantile()

  • stat_smooth(): geom_smooth()

  • stat_sum(): geom_count()

ggplot(mpg, aes(trans, cty)) + 
  geom_point() + 
  stat_summary(geom = "point", fun = "mean", colour = "darkred", size = 4)

ggplot(mpg, aes(trans, cty)) + 
  geom_point() + 
  geom_point(stat = "summary", fun = "mean", colour = "darkred", size = 4)

4.13 Themes

Themes permite ejercer un control preciso sobre los elementos del gráfico que no son datos. El sistema de temas no afecta la forma en que los geoms representan los datos o cómo se transforman mediante escalas. Los temas no cambian las propiedades perceptivas de la trama, pero ayudan a hacer que la trama sea estéticamente agradable o que coincida con una guía de estilo existente. Los temas le dan control sobre cosas como fuentes, marcas (ticks), paneles y fondos.

Con ggplot2 al crear el gráfico, se determina cómo se muestran los datos, luego, una vez que se ha creado, puede editar cada detalle de la representación, utilizando el sistema de temas.

El sistema de temas se compone de cuatro componentes principales:

  • Los elementos del tema especifican los elementos que no son datos que puede controlar. Por ejemplo, el elemento plot.title controla la apariencia del título de la trama; axis.ticks.x, los ticks en el eje \(x\); legend.key.height, la altura de las claves en la leyenda.

  • Cada elemento está asociado con una función, que describe las propiedades visuales del elemento. Por ejemplo, element_text() establece el tamaño de fuente, el color y el tipo de los elementos de texto como plot.title.

  • La función theme() permite anular los elementos predeterminados del tema usando funciones de elementos, como theme(plot.title = element_text(colour = "red").

  • Los temas completos, como theme_grey(), establecen todos los elementos del tema en valores diseñados para trabajar juntos en armonía.

Por ejemplo, si se busca entender la relación entre cty y hwy:

base <- ggplot(mpg, aes(cty, hwy, color = factor(cyl))) +
  geom_jitter() + 
  geom_abline(colour = "grey50", linewidth = 2)
base

El gráfico cumple su objetivo: hemos aprendido que cty y hwy están altamente correlacionados, ambos están estrechamente relacionados con cyl, y que hwy siempre es mayor que cty (y la diferencia aumenta a medida que cty aumenta). Ahora se busca compartir la trama con otros, tal vez publicándola en un documento, requiere algunos cambios. Primero, debe asegurarse de que la trama pueda mantenerse sola:

  • Mejorando los ejes y etiquetas de leyenda.

  • Agregar un título para la trama.

  • Ajustando la escala de colores.

Procedamos:

labelled <- base +
  labs(
    x = "City mileage/gallon",
    y = "Highway mileage/gallon",
    colour = "Cylinders",
    title = "Highway and city mileage are highly correlated"
  ) +
  scale_colour_brewer(type = "seq", palette = "Spectral")
labelled

Sin embargo, podría ser que la trama coincida con las pautas de estilo de la publicación:

  • El fondo debe ser blanco, no gris pálido.

  • La leyenda debe colocarse dentro de la trama si hay espacio.

  • Las líneas de cuadrícula principales deben ser de color gris pálido y las líneas de cuadrícula secundarias deben eliminarse.

  • El título de la trama debe ser texto en negrita de 12 puntos.

  • En este capítulo, aprenderá a usar el sistema de temas para realizar esos cambios, como se muestra a continuación:

styled <- labelled +
  theme_bw() + 
  theme(
    plot.title = element_text(face = "bold", size = 12),
    legend.background = element_rect(fill = "white", linewidth = 4, colour = "white"),
    legend.justification = c(0, 1),
    legend.position = c(0, 1),
    axis.ticks = element_line(colour = "grey70", linewidth = 0.2),
    panel.grid.major = element_line(colour = "grey70", linewidth = 0.2),
    panel.grid.minor = element_blank()
  )
styled


  1. Campitelli, Elio. Ggnewscale: Multiple Fill and Colour Scales in ’Ggplot2’, 2020. https://CRAN.R-project.org/package=ggnewscale.↩︎