3 - Gráficos estáticos con ggplot: nivel avanzado

Author

Luz Frias

¿Qué vamos a hacer?

En los capítulos 1 y 2 hemos visto cómo crear gráficos estáticos con ggplot, con diferentes geometrías (puntos, líneas, boxplots, barras, …), escalas (numéricas continuas, categóricas, temporales, …) y transformaciones estadísticas.

En este nuevo capítulo vamos a aprender a personalizar los gráficos ajustando el aspecto y a presentar datos geoespaciales sobre mapas.

1. Títulos, etiquetas y temas

Vamos a rescatar uno de los gráficos que hicimos en el anterior capítulo para personalizarlo.

library(ggplot2)
library(dplyr)

# Lectura de los datos
defunciones <- read.csv("dat/defunciones_espana.csv")

# Conversión de la fecha de tipo character a Date
defunciones <- defunciones %>%
  mutate(fecha_defuncion = as.Date(fecha_defuncion))

# Pintamos la mortalidad observada y esperada
ggplot(defunciones, aes(x = fecha_defuncion)) +
  geom_ribbon(aes(ymin = defunciones_esperadas_q01, ymax = defunciones_esperadas_q99),
              fill = "blue", alpha = 0.3) +
  geom_line(aes(y = defunciones_observadas), color = "black") +
  geom_line(aes(y = defunciones_esperadas), color = "blue") +
  scale_x_date(date_breaks = "1 month", date_labels = "%b-%y")

Si vamos a utilizar el gráfico en un informe divulgativo o una presentación, es recomendable poner un título, una leyenda, y mejorar el texto de los ejes.

Conviene tener siempre a mano la documentación de ggplot para conocer todos los elementos que podemos personalizar. Está disponible dentro del paquete lanzando ?labs o en su web.

En la documentación, vemos que podemos definir, entr otros elementos:

  • Título, subtítulo y pie mediante labs(title, subtitle, caption)
  • Nombre de cualquier elemento estético con una lista de valores con nombre. Por ejemplo, labs(x = "Nombre eje x", fill = "Nombre de la variable para el color").
ggplot(defunciones, aes(x = fecha_defuncion)) +
  geom_ribbon(aes(ymin = defunciones_esperadas_q01, ymax = defunciones_esperadas_q99),
              fill = "blue", alpha = 0.3) +
  geom_line(aes(y = defunciones_observadas), color = "black") +
  geom_line(aes(y = defunciones_esperadas), color = "blue") +
  scale_x_date(date_breaks = "1 month", date_labels = "%b-%y") +
  labs(title    = "Mortalidad en España",
       subtitle = "Defunciones observadas y esperadas entre agosto de 2019 y agosto de 2020",
       caption  = "Fuente: MoMo, Centro Nacional de Epidemiología",
       x        = "Fecha",
       y        = "Defunciones"
  )

Solo nos faltaría una leyenda para el color, y así diferenciar que las líneas negras son las defunciones observadas y la azul las esperadas. ¿Por qué ggplot no la genera automáticamente, como en otros gráficos que hemos hecho? Por ejemplo, en este:

# Lectura de los datos del CSV
usa_president <- read.csv("dat/usa_president.csv")

# Filtro por los partidos de mi interés y agrupo por partido y año
usa_president_by_year <- usa_president %>%
  filter(party %in% c("democrat", "republican", "libertarian", "green"),
         writein == FALSE) %>%
  group_by(year, party) %>%
  summarise(candidatevotes = sum(candidatevotes))

# Definición de mi paleta personalizada de colores por partido
parties_palette <- c("democrat" = "blue", "republican" = "red", "libertarian" = "gold", "green" = "darkgreen")

# Gráfico de evolución en número de votos por partido y año
ggplot(usa_president_by_year, aes(x = year, y = candidatevotes, color = party)) +
  geom_line() +
  scale_y_log10(labels = scales::comma) +
  scale_color_manual(values = parties_palette) +
  labs(
    x     = "Año",
    y     = "Votos",
    color = "Partido"
  )

La diferencia es que, en el gráfico de votos, los colores son parte del mapeo de estéticos a columnas mediante aes(...) mientras que en el gráfico de defunciones, son geometrías (geom_line) diferentes. En el primer caso, ggplot añade automáticamente la leyenda pero, en el segundo, no lo hará a no ser que lo especifiquemos. Podemos hacerlo creando una paleta manual que usemos en la escala del color e incluyéndola dentro del mapeo de aes(...). De la siguiente forma:

# Paleta
mortality_palette <- c("Observadas" = "black", "Esperadas" = "blue")

# Guardo el gráfico en la variable p para no repetir este código continuamente
p <- ggplot(defunciones, aes(x = fecha_defuncion)) +
  geom_ribbon(aes(ymin = defunciones_esperadas_q01, ymax = defunciones_esperadas_q99),
              fill = "blue", alpha = 0.3) +
  geom_line(aes(y = defunciones_observadas, color = "Observadas")) +
  geom_line(aes(y = defunciones_esperadas, color = "Esperadas")) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b-%y") +
  scale_color_manual(values = mortality_palette) +
  labs(title    = "Mortalidad en España",
       subtitle = "Defunciones observadas y esperadas entre agosto de 2019 y agosto de 2020",
       caption  = "Fuente: MoMo, Centro Nacional de Epidemiología",
       x        = "Fecha",
       y        = "Defunciones",
       color    = "Defunciones"
  )

p

Hay otros aspectos estéticos que podemos personalizar: el tipo de fuente que se usa en las diferentes partes del gráfico, su color y tamaño, el grid que aparece detrás, los márgenes, etc. Todos estos valores toman un valor por defecto, heredado de su tema (theme). Si no especificamos uno, el tema por defecto es theme_gray(), pero ggplot nos proporciona más opciones: theme_classic, theme_bw, theme_minimal, …

p + theme_minimal()

Prueba a escribir en la consola theme_ y examina con el autocompletado todas las opciones de temas que vienen con ggplot. Prueba varias de ellas sobre el gráfico.

Un tema son opciones por defecto sobre todos los elementos estéticos del gráfico, pero podemos sobrescribirlos.

p +
  theme_minimal() +
  theme(legend.position = "bottom",
        axis.text.x = element_text(angle = 90, hjust = 1),
        plot.title = element_text(size = 18, hjust = 0.5))

De nuevo, es necesario tener la documentación a mano. En theme encontramos todos los argumentos que podemos especificar para alterar aspectos del gráfico. Es imposible que nos conozcamos los argumentos de memoria, solo tenemos que tener claro que los podemos localizar rápidamente en la documentación.

2. Facets

Hemos visto que podemos añadir variables a visualizar mediante el mapeo de estéticos con aes(...): la posición con x e y, el color, el tipo de línea, el tamaño de un punto, etc.

Otra opción, muy útil cuando tenemos variables categóricas, es dividir el gráfico en varios, cada uno mostrando un subconjunto de los datos. Estos son los facets.

Para crear un facet por una variable, podemos utilizar facet_wrap(), especificando la fórmula de generación de los sub-gráficos. Esta fórmula se puede crear con ~ mi_variable_categorica.

library(palmerpenguins)

ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) + 
  geom_point() +
  facet_wrap(~ species)

Puedes controlar el número de filas y columnas que se usan mediante los parámetros nrow y ncol de facet_wrap. Consulta su ayuda y prueba a cambiarlos sobre el gráfico anterior.

En la documentación de facet_wrap, consulta para qué sirve el parámetro scales y prueba a cambiarlo en el gráfico anterior.

Para dividir el gráfico por dos variables en lugar de por una, se suele utilizar facet_grid en su lugar, y una formula del tipo mi_variable_1 ~ mi_variable_2.

# Quito las filas con el sexo nulo
tmp <- penguins %>%
  filter(!is.na(sex))

# Creo el facet por sexo y especie
ggplot(tmp, aes(x = flipper_length_mm, y = body_mass_g)) + 
  geom_point() +
  facet_grid(sex ~ species)

Normalmente, ponemos la variable que más valores tiene en columnas, y la que menos en filas. Esto es porque los gráficos suelen tener más ancho que alto, y así aprovechamos mejor el espacio.

3. Mapas

Representar gráficos con datos geolocalizados es bastante común. Este proceso suele requerir de dos pasos:

  1. Pintar el mapa base, es decir, los polígonos que forman las regiones que vamos a representar (p.e. los países del mundo, las comunidades autónomas de España, los barrios de Madrid, …)
  2. Añadir la información que queremos representar (p.e. la densidad de la población, la incidencia de una determinada enfermedad, …)

Para seguir la parte sobre mapas, necesitarás tener instalado el paquete sf.

# Instálalo si aún no lo tienes
install.packages("sf")

Mapas de polígonos

La forma más fácil de pintar un mapa es utilizando geom_polygon. Tenemos algunos mapas de ejemplo disponibles con map_data:

italy <- map_data("italy")
head(italy)
      long      lat group order        region subregion
1 11.83295 46.50011     1     1 Bolzano-Bozen      <NA>
2 11.81089 46.52784     1     2 Bolzano-Bozen      <NA>
3 11.73068 46.51890     1     3 Bolzano-Bozen      <NA>
4 11.69115 46.52257     1     4 Bolzano-Bozen      <NA>
5 11.65041 46.50721     1     5 Bolzano-Bozen      <NA>
6 11.63282 46.48045     1     6 Bolzano-Bozen      <NA>
ggplot(italy, aes(x = long, y = lat, group = group)) +
  geom_polygon(fill = "white", color = "black") +
  coord_quickmap()

Utilizamos coord_quickmap para que longitud y latitud sigan la misma escala y no aparezca deformado.

Aunque resulta muy cómodo hacerlo de esta forma, no es habitual encontrar los mapas base (los polígonos) en un formato de tabla como este, con información de latitud y longitud. Los formatos más habituales son: geoJSON, shapefiles y bases de datos (especialmente PostgreSQL con PostGIS)

Para leer estos formatos, tenemos sf::read_sf().

library(sf)

# Leo los polígonos de un geojson que contiene las provincias españolas simplificadas
provincias <- read_sf("dat/spain_provinces.geojson")
head(provincias)
Simple feature collection with 6 features and 3 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -1.8386 ymin: 3.6739 xmax: 0.7771 ymax: 4.2623
Geodetic CRS:  WGS 84
# A tibble: 6 × 4
  OBJECTID codigo nombre                                                geometry
     <int>  <int> <chr>                                            <POLYGON [°]>
1        1     20 Guipúzcoa  ((0.3515 4.0509, 0.2355 4.1197, 0.2355 4.2006, 0.2…
2        2     15 Coruña (A) ((-1.601 3.7516, -1.6469 3.7784, -1.6845 3.7593, -…
3        3     48 Vizcaya    ((0.053 4.0646, -0.01 4.1035, -0.01 4.1277, 0.0123…
4        4     31 Navarra    ((0.6115 3.6739, 0.5459 3.7058, 0.5465 3.7752, 0.3…
5        5     39 Cantabria  ((-0.1907 3.9288, -0.3244 4.0103, -0.3652 3.988, -…
6        6     33 Asturias   ((-1.0583 3.8709, -1.0583 3.9308, -1.1265 3.9792, …

Tenemos una tabla con una fila por provincia, cada una de ellas con ciertos metadatos (código INE, nombre, …) y un objeto geometría, con la definición del polígono.

Para representar el mapa base, utilizamos geom_sf.

ggplot(provincias) +
  geom_sf()

Podemos añadir etiquetas a nuestros mapas mediante geom_sf_label() o geom_sf_text().

ggplot(provincias) +
  geom_sf() +
  geom_sf_label(aes(label = nombre))

Sobre la geometría, podemos mapear otros atributos. Es muy habitual colorear las regiones según el dato que queramos representar. Para ello, primero tenemos que incorporar al mapa esta información.

Para ilustrarlo, vamos a representar la participación en las últimas elecciones españolas por provincia.

# La información de las elecciones la tenemos en:
# - elecciones_2019_provincias.csv con la información a nivel de provincia (población, censo, votos válidos, ...)
# - elecciones_2019_votos.csv con el número de votos por provincia y partido
# Para calcular la participación, solo necesitamos el primer dataset
elecciones <- read.csv("dat/elecciones_2019_provincias.csv")
head(elecciones)
  ccaa_nombre provincia_cod_ine provincia_nombre poblacion censo_electoral
1   Andalucía                 4          Almería    709340          502627
2   Andalucía                11            Cádiz   1238714         1002295
3   Andalucía                14          Córdoba    785240          648341
4   Andalucía                18          Granada    912075          755007
5   Andalucía                21           Huelva    519932          399019
6   Andalucía                23             Jaén    638099          526659
  votos_validos votos_candidaturas votos_blanco votos_nulos
1        302424             299763         2661        2990
2        616079             606858         9221        8116
3        444376             438971         5405        7399
4        482779             478251         4528        8123
5        250681             247336         3345        4648
6        365106             361745         3361        5937
# Calculamos el ratio de participación
participacion <- elecciones %>%
  mutate(ratio_participacion = round((votos_validos + votos_nulos) / censo_electoral, 3)) %>%
  select(provincia_cod_ine, ratio_participacion)

head(participacion)
  provincia_cod_ine ratio_participacion
1                 4               0.608
2                11               0.623
3                14               0.697
4                18               0.650
5                21               0.640
6                23               0.705
# Añadimos al mapa esta información
tmp <- provincias %>%
  inner_join(participacion, by = c("codigo" = "provincia_cod_ine"))

# Representamos la participación coloreando cada provincia
# Invertimos la escala con trans = "reverse" para que las intensidades
# mayores correspondan a niveles más altos de participación
ggplot(tmp) +
  geom_sf(aes(fill = ratio_participacion)) +
  scale_fill_continuous(trans = "reverse")

4. Exportación

Tenemos varias formas de guardar los gráficos que vamos generando.

ggsave() guarda el último gráfico generado.

# ggsave guarda el último gráfico generado
ggsave("mi_grafico.png")

Si estamos en una sesión interactiva con RStudio, podemos pinchar en Export en la ventana de Plots.

Y ahí podremos elegir varias opciones de exportación, como la ruta del fichero o el tamaño de la imagen.

Aunque podemos utilizar estas dos opciones para guardar los gráficos en disco, lo normal es que formen parte de un documento Rmarkdown y que lo exportemos junto a texto formateado a PDF, HTML u otros formatos. Profundizaremos sobre este tema en el capítulo dedicado a Rmarkdown.

Profundiza

Para saber más sobre los conceptos que hemos visto, puedes consultar alguna de estas referencias:

Conclusiones

Nos podemos quedar con las siguientes ideas como resumen de este tema:

  • Es importante cuidar los textos (título, descripción, pie, nombres de los ejes, …) cuando nuestros gráficos van dirigidos a un público general, o van a ser incluidos en un informe.
  • Los textos se pueden especificar con labs().
  • Los temas de ggplot son plantillas con valores por defecto como tipo y tamaño de fuente, estilo de grid de fondo, … pero estos valores los podemos sobrescribir.
  • Los facets sirven para separar en gráficos diferentes las observaciones por una o varias variables categóricas.
  • Para representar facets sobre una variable, utilizamos facet_wrap. Si es sobre dos variables, utilizamos facet_grid.
  • Para pintar gráficas con mapas, necesitamos dos cosas: el mapa base (los polígonos) y los datos a representar.
  • Para leer un mapa base en un formato habitual (geojson, shapefiles, …) podemos utilizar sf::read_sf.
  • Para pintar los polígonos de un mapa, podemos utilizar geom_sf. Lo más habitual es mapear el color de relleno fill a la propiedad que queramos representar.

Actividades

Actividad 1

Sobre el dataset diamonds, pinta:

  • El histograma del precio según los facets de la calidad del corte (cut)
  • Un gráfico de barras por claridad (clarity) según los facets de calidad del corte (cut) y color (color).

Actividad 2

Utilizando los datos de elecciones (elecciones_2019_provincias.csv y elecciones_2019_votos.csv) y el mapa base con las provincias (spain_provinces.geojson), crea los siguientes gráficos:

  1. Un mapa coloreado según la población de cada provincia (mayor intensidad = mayor población).
  2. Un mapa con el color del partido más votado en cada provincia (PP en azul, PSOE en rojo, …).
  3. Un facet de mapas, con 4, marcando el porcentaje de votos a los 4 partidos políticos (PP, PSOE, VOX, Podemos). Por ejemplo, en el facet del PSOE, se verán en un color más intenso las provincias con mayor % de votos a PSOE, y menos aquellas con menor %. Y así con cada uno de los partidos.

Actividad 3

Repasa estos gráficos que acabas de generar y añade títulos, nombres de eje, y personaliza el tema utilizado.