Caso completo: del EDA al gráfico publicable

r
visualizacion
ggplot2
Un análisis real con ggplot2 de principio a fin: del gráfico exploratorio rápido a la figura editorial publicable. Aplica los 10 tutoriales anteriores sobre los datos de la Ruta 1 (NYC flights).

El cierre de la ruta

Has completado 10 tutoriales sobre ggplot2. Cada uno te enseñó una pieza. Esta entrega es distinta: no introduce conceptos nuevos. Toma un gráfico de EDA y lo lleva paso a paso hasta una figura editorial publicable.

Reusamos el dataset del caso completo de la Ruta 1: puntualidad de aerolíneas en NYC durante 2013. Si no has hecho la Ruta 1, el pipeline que genera los datos está incluido aquí mismo.

Punto de partida: el EDA

library(tidyverse)
library(lubridate)
library(nycflights13)
library(scales)

# Pipeline del caso completo de la Ruta 1
puntualidad <- flights |>
  filter(!is.na(dep_delay)) |>
  mutate(fecha = make_date(year, month, day)) |>
  left_join(airlines, by = "carrier") |>
  rename(aerolinea = name) |>
  group_by(aerolinea, month) |>
  summarise(
    n_vuelos        = n(),
    mediana_retraso = median(dep_delay, na.rm = TRUE),
    pct_a_tiempo    = mean(dep_delay <= 0, na.rm = TRUE),
    .groups = "drop"
  ) |>
  filter(n_vuelos >= 100)

El gráfico exploratorio inicial, útil para EDA, no para publicación:

ggplot(puntualidad, aes(x = month, y = mediana_retraso, color = aerolinea)) +
  geom_line() +
  geom_point()

Funciona para explorar. Para publicar no: las etiquetas son crípticas, los meses son números sin contexto, los colores son el default. Vamos a pulirlo en cinco pasos.

Refinamiento 1: escalas con formato humano

El eje X muestra 1, 2, 3, .... Más útiles son los meses abreviados:

ggplot(puntualidad, aes(x = month, y = mediana_retraso, color = aerolinea)) +
  geom_line(linewidth = 0.6) +
  geom_point(size = 1.8) +
  scale_x_continuous(
    breaks = 1:12,
    labels = month.abb
  ) +
  scale_y_continuous(
    labels = label_number(suffix = " min")
  )

Cambios:

  • breaks = 1:12 fuerza una marca por mes.
  • labels = month.abb muestra Jan, Feb, … (constante interna de R).
  • label_number(suffix = " min") añade unidades al eje Y.

Pequeño detalle, gran legibilidad.

Refinamiento 2: theme editorial

Aplicamos un theme custom (similar al theme_rmori que vimos en Themes):

theme_editorial <- function(base_size = 12) {
  theme_minimal(base_size = base_size) +
  theme(
    plot.title       = element_text(size = rel(1.5), face = "plain"),
    plot.subtitle    = element_text(size = rel(1.0), color = "grey40",
                                    margin = margin(b = 12)),
    plot.caption     = element_text(size = rel(0.75), color = "grey50",
                                    hjust = 0, margin = margin(t = 12)),
    axis.title       = element_text(size = rel(0.9), color = "grey30"),
    axis.text        = element_text(size = rel(0.85), color = "grey50"),
    panel.grid.minor = element_blank(),
    legend.position  = "bottom",
    legend.title     = element_blank()
  )
}

(Si tienes showtext instalado y quieres tipografía editorial real, añade base_family = "Newsreader" o lo que prefieras.)

Aplicamos:

ggplot(puntualidad, aes(x = month, y = mediana_retraso, color = aerolinea)) +
  geom_line(linewidth = 0.6) +
  geom_point(size = 1.8) +
  scale_x_continuous(breaks = 1:12, labels = month.abb) +
  scale_y_continuous(labels = label_number(suffix = " min")) +
  theme_editorial() +
  labs(
    title    = "Puntualidad de aerolíneas en NYC",
    subtitle = "Mediana de retraso de salida por aerolínea y mes (2013)",
    x        = NULL,
    y        = NULL,
    caption  = "Fuente: dataset nycflights13"
  )

x = NULL y y = NULL quitan los títulos de ejes, son redundantes con el subtítulo y los labels.

Refinamiento 3: anotaciones que dirigen

Un gráfico editorial señala al lector lo que importa. El hallazgo claro: los meses de verano (junio-agosto) concentran los peores retrasos. Lo anotamos:

ggplot(puntualidad, aes(x = month, y = mediana_retraso, color = aerolinea)) +
  annotate("rect", xmin = 5.5, xmax = 8.5, ymin = -Inf, ymax = Inf,
           fill = "grey", alpha = 0.15) +
  annotate("text", x = 7, y = max(puntualidad$mediana_retraso) * 0.95,
           label = "Temporada alta", color = "grey30",
           size = 3.2, fontface = "italic") +
  geom_line(linewidth = 0.6) +
  geom_point(size = 1.8) +
  # ...resto igual

annotate("rect", ...) dibuja una franja gris cubriendo los meses de verano. annotate("text", ...) la etiqueta. Ahora el lector ve la zona antes de mirar las líneas, y las líneas se interpretan en ese contexto.

Refinamiento 4: paleta accesible

El default de colores de ggplot2 no es accesible para daltonismo. Cambiamos a viridis_d:

# ...código anterior +
scale_color_viridis_d(option = "viridis", end = 0.9)

end = 0.9 evita el amarillo más claro al final de la paleta, que se ve mal sobre fondo blanco.

Testamos con colorspace::deutan():

library(colorspace)
p <- # el gráfico completo refinado
deutan(p)

Si las líneas siguen siendo distinguibles, la paleta es válida.

Refinamiento 5: composición con patchwork

A veces una sola figura no basta. La pregunta original tenía dos partes: “qué aerolíneas son más puntuales” (nivel anual) y “cómo varía por mes” (patrón estacional). Una composición de dos paneles cuenta mejor la historia:

library(patchwork)
library(forcats)

# Panel A: ranking general (qué nivel)
p_ranking <- puntualidad |>
  group_by(aerolinea) |>
  summarise(mediana_anual = median(mediana_retraso), .groups = "drop") |>
  arrange(mediana_anual) |>
  mutate(aerolinea = fct_inorder(aerolinea)) |>
  ggplot(aes(mediana_anual, aerolinea, fill = aerolinea)) +
  geom_col() +
  scale_fill_viridis_d(option = "viridis", end = 0.9, guide = "none") +
  scale_x_continuous(labels = label_number(suffix = " min")) +
  theme_editorial() +
  labs(x = NULL, y = NULL, subtitle = "Mediana anual")

# Panel B: serie temporal (qué patrón)
p_temporal <- # el gráfico refinado de los pasos anteriores

# Composición
(p_ranking | p_temporal) +
  plot_layout(widths = c(1, 2)) +
  plot_annotation(
    title    = "Puntualidad de aerolíneas en NYC, 2013",
    subtitle = "Ranking anual y patrón estacional por aerolínea",
    caption  = "Fuente: dataset nycflights13",
    theme    = theme_editorial()
  )

Dos paneles, dos preguntas, una figura. Los ratios c(1, 2) dan más espacio al panel temporal porque tiene más densidad de información.

Exportar con ggsave

El último paso: guardar la figura final para incluir en el informe, paper o presentación.

ggsave(
  filename = "output/puntualidad-nyc-2013.svg",
  plot     = figura_final,
  width    = 10,
  height   = 6,
  units    = "in",
  dpi      = 300
)

Convenciones que importan:

  • Formato vectorial (.svg o .pdf) si la figura va a paper o web. Escala sin pérdida de calidad.
  • Formato raster (.png) si va a presentación con muchas figuras o si el SVG resulta pesado.
  • width y height explícitos: ggsave por defecto usa el tamaño de la ventana de RStudio, que cambia entre sesiones y entre máquinas.
  • DPI 300 para impresión, 96 para web. Solo afecta a raster.

Lo que has hecho

En este caso completo has aplicado los diez tutoriales anteriores sobre un único gráfico:

  1. La gramática, el patrón ggplot() + ... que se mantiene como capas.
  2. aes(): mapeo de month, mediana_retraso, aerolinea a x/y/color.
  3. Geoms básicos, geom_line + geom_point combinados.
  4. Distribuciones, implícitamente, al elegir mediana en vez de media.
  5. Facetado, no aplicado aquí. Lo natural si hubiéramos comparado por aeropuerto de origen.
  6. Escalas, formatos numéricos, breaks de meses.
  7. Themes, un theme custom reutilizable.
  8. Anotaciones, la franja “Temporada alta”.
  9. Patchwork, la composición de ranking + serie temporal.
  10. Color accesible, viridis_d con test de daltonismo.

Y has terminado con un export profesional con ggsave.

¿Quieres ir más a fondo?

El libro ggplot2 para visualización publicable (en preparación) desarrolla esta misma materia con:

  • Un caso real más largo (informe sectorial real, no datos académicos).
  • Plantillas de theme listas para personalizar.
  • Paleta corporativa con tests de accesibilidad.
  • Capítulo de troubleshooting con las veinte trampas que más cuestan en exports.

Has terminado la Ruta 2

Felicidades. Si quieres seguir aprendiendo R, las rutas que naturalmente continúan son:

  • Estadística aplicada con R: para llevar tus análisis del describir al inferir.
  • Reproducibilidad con Quarto: para que tus análisis sean publicables y replicables.

Ambas están en el listado de Rutas.