Caso completo: del EDA al gráfico publicable
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:12fuerza una marca por mes.labels = month.abbmuestraJan,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 igualannotate("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 (
.svgo.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. widthyheightexplícitos:ggsavepor 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:
- La gramática, el patrón
ggplot() + ...que se mantiene como capas. aes(): mapeo demonth,mediana_retraso,aerolineaa x/y/color.- Geoms básicos,
geom_line + geom_pointcombinados. - Distribuciones, implícitamente, al elegir mediana en vez de media.
- Facetado, no aplicado aquí. Lo natural si hubiéramos comparado por aeropuerto de origen.
- Escalas, formatos numéricos, breaks de meses.
- Themes, un theme custom reutilizable.
- Anotaciones, la franja “Temporada alta”.
- Patchwork, la composición de ranking + serie temporal.
- Color accesible,
viridis_dcon 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.