Anotaciones: text, label, annotate
¿Para qué anotar?
Un gráfico sin anotación es exploración. Un gráfico con anotación dirige al lector exactamente a lo que tiene que ver. Es la diferencia entre “aquí están los datos” y “este punto es el que importa”.
Las anotaciones cubren tres necesidades distintas:
- Etiquetar puntos concretos (un país en un scatter, una empresa en un ranking).
- Añadir texto fijo en una posición del gráfico (notas, etiquetas de zonas).
- Marcar valores de referencia (líneas verticales para fechas clave, horizontales para umbrales).
Cada una tiene su herramienta.
geom_text vs geom_label
Para etiquetar puntos basados en datos, hay dos geoms:
library(ggplot2)
library(palmerpenguins)
# Filtramos para evitar saturar
peng_sample <- na.omit(penguins)[1:20, ]
# geom_text: texto sin fondo
ggplot(peng_sample, aes(bill_length_mm, flipper_length_mm,
label = species)) +
geom_point() +
geom_text(nudge_y = 1, size = 3)
# geom_label: texto con fondo (recuadro)
ggplot(peng_sample, aes(bill_length_mm, flipper_length_mm,
label = species)) +
geom_point() +
geom_label(nudge_y = 1, size = 3)Cuándo cada uno:
geom_text: texto puro. Útil cuando el fondo del gráfico es claro y limpio.geom_label: texto con caja de fondo. Mejor cuando hay densidad de puntos y el texto se confundiría con los marcadores.
nudge_x / nudge_y desplazan ligeramente la etiqueta de la coordenada original para que no se superponga al punto.
El problema del solapamiento
En cuanto pasas de 5-10 etiquetas, todas se amontonan. Manualmente con nudge_* no escala. La solución estándar es ggrepel:
install.packages("ggrepel")
library(ggrepel)
ggplot(peng_sample, aes(bill_length_mm, flipper_length_mm,
label = species)) +
geom_point() +
geom_text_repel(size = 3, max.overlaps = 20)ggrepel aplica un algoritmo de repulsión: las etiquetas se desplazan automáticamente para no solaparse entre sí ni con los puntos. Es el estándar moderno, adóptalo desde el principio.
Variantes:
geom_text_repel(): sin caja.geom_label_repel(): con caja.min.segment.length = 0para que SIEMPRE aparezca la línea conectora.max.overlaps = Infsi tienes muchas etiquetas y no quieres queggrepeldescarte algunas en silencio.
annotate(): marcadores fijos
Cuando quieres añadir texto, líneas o cualquier geom en una posición fija del gráfico (no basada en datos), usa annotate(). La diferencia con un geom_*() es:
geom_text()tomadatay mapea filas a etiquetas.annotate("text", ...)añade un único elemento, sin necesidad de un data frame.
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.05) +
annotate(
geom = "text",
x = 2.5,
y = 18000,
label = "Zona premium",
color = "darkred",
size = 4
)annotate() acepta cualquier tipo de geom:
annotate("rect", xmin = 2, xmax = 3, ymin = 0, ymax = 20000,
fill = "yellow", alpha = 0.2)
annotate("segment", x = 0, xend = 5, y = 10000, yend = 10000,
color = "red", linetype = "dashed")Casi cualquier patrón de “marcar una zona del gráfico” usa annotate().
Líneas de referencia: hline, vline, abline
Para líneas que cruzan todo el gráfico, hay tres geoms dedicados:
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.05) +
geom_hline(yintercept = 10000, linetype = "dashed", color = "red") +
geom_vline(xintercept = 2.0, linetype = "dotted", color = "blue") +
geom_abline(slope = 5000, intercept = 0, color = "green")geom_hline(yintercept = ...): línea horizontal en un valor de Y.geom_vline(xintercept = ...): línea vertical en un valor de X.geom_abline(slope, intercept): línea recta con pendiente e intercepto (útil para línea x=y, líneas de tendencia teóricas).
Casos típicos:
geom_hline(yintercept = 0)para resaltar el cero en gráficos de cambio.geom_vline(xintercept = as.Date("2020-03-15"))para marcar el inicio de la pandemia en una serie temporal.geom_abline(slope = 1, intercept = 0)para la diagonal x=y en gráficos de comparación predicho vs observado.
Con leyenda
Si quieres que la línea aparezca en la leyenda, mapea el color a un valor:
ggplot(diamonds, aes(carat, price)) +
geom_point(alpha = 0.05) +
geom_hline(aes(yintercept = 10000, color = "Umbral premium"),
linetype = "dashed") +
scale_color_manual(name = NULL, values = c("Umbral premium" = "red"))Mapear color = "Umbral premium" dentro de aes crea una “variable” con un único nivel, que aparece en la leyenda con el color especificado.
Trampas habituales
geom_textcon todos los puntos. Etiquetar 500 puntos no comunica nada, solo emborrona el gráfico. Filtra los puntos a etiquetar (top N, los relevantes) antes de pasarlos al geom:geom_text(data = filter(df, ranking <= 5), ...).- Olvidar que
annotate("text", ...)no tomaaes. Si escribesannotate("text", aes(...)), falla.annotate()espera valores literales en sus argumentos, no mapeos. geom_hline(yintercept = mean(y))que da error. La funciónmean()se evalúa fuera del contexto del data frame. Solución: calcula la media antes y pásala como valor:media_y <- mean(df$y); geom_hline(yintercept = media_y).ggrepelque descarta etiquetas silenciosamente. Por defecto, si hay demasiadas etiquetas,ggrepelomite algunas y avisa con un warning. Si necesitas ver todas, subemax.overlaps = Info reduce las etiquetas pasadas.
En la siguiente entrega
Has aprendido a anotar. Con lo del bloque 2 (facetado, escalas, themes) y este tutorial, ya puedes producir gráficos con calidad de publicación. Quedan tres tutoriales del bloque 3, patchwork para combinar gráficos, color y accesibilidad y el caso completo final, que pulen los últimos detalles de presentación. El próximo es patchwork.