Escalas: log, fechas, percentiles

r
visualizacion
ggplot2
El componente número 4 de la gramática. Escalas logarítmicas, escalas de fecha, breaks y labels personalizados, el paquete scales para formatters, y la diferencia entre limits y coord_cartesian.

¿Qué hace una escala?

En la gramática de Wilkinson, una escala controla cómo se traducen los valores de los datos a propiedades visuales. Si tu data frame tiene price en USD, la escala del eje Y decide:

  • Si los números se muestran como 1000, 1,000 o $1k.
  • Si la escala es lineal, logarítmica o de percentiles.
  • Dónde aparecen las marcas (breaks) y qué etiqueta llevan.
  • Qué rango se muestra (limits).

ggplot2 elige una escala razonable por defecto. Customizar la escala es lo que separa un gráfico funcional de uno legible al instante.

Escalas logarítmicas: cuándo y por qué

Cuando una variable abarca varios órdenes de magnitud (precios de viviendas, población de ciudades, citas de papers), una escala lineal aplasta los valores bajos contra el eje. Una escala logarítmica reparte el espacio proporcional al cambio relativo.

library(ggplot2)

# Lineal — el grueso se aplasta en el lado bajo
ggplot(diamonds, aes(carat, price)) +
  geom_point(alpha = 0.05)

# Log10 — la relación carat-price se vuelve casi lineal
ggplot(diamonds, aes(carat, price)) +
  geom_point(alpha = 0.05) +
  scale_x_log10() +
  scale_y_log10()

Pista visual: si tus puntos forman una nube curva pero al log se vuelve recta, la relación es multiplicativa, no aditiva. Eso es información sustantiva, no estética.

Variantes:

scale_x_log10()                              # log base 10
scale_x_log10(labels = scales::label_dollar())   # con formateo de moneda
scale_x_continuous(trans = "log2")           # log base 2
scale_x_sqrt()                               # raíz cuadrada (útil con conteos)

Escalas de fecha

Cuando una variable es de tipo Date o POSIXct, ggplot2 usa por defecto scale_x_date o scale_x_datetime. Para personalizar:

library(scales)

ggplot(economics, aes(date, unemploy)) +
  geom_line() +
  scale_x_date(
    date_breaks = "5 years",
    date_labels = "%Y"
  )

date_breaks controla cada cuánto aparece una marca. date_labels el formato (códigos strftime: %Y año, %m mes, %b mes abreviado, etc.).

Para fechas con locale español:

Sys.setlocale("LC_TIME", "es_ES.UTF-8")

ggplot(economics, aes(date, unemploy)) +
  geom_line() +
  scale_x_date(date_breaks = "5 years", date_labels = "%b %Y")
# "ene 2010", "jun 2015"...

Breaks y labels personalizados

Para cualquier escala continua, breaks controla dónde aparecen las marcas y labels qué texto las acompaña:

ggplot(diamonds, aes(carat, price)) +
  geom_point(alpha = 0.05) +
  scale_y_continuous(
    breaks = c(0, 5000, 10000, 15000, 20000),
    labels = c("0 €", "5k", "10k", "15k", "20k")
  )

Una opción más limpia: usar funciones de scales que generan breaks y labels coherentes:

library(scales)

scale_y_continuous(
  breaks = breaks_pretty(n = 5),
  labels = label_number(big.mark = ".", suffix = " €")
)

El paquete scales: formatters listos

scales es un paquete auxiliar (instalado automáticamente con ggplot2) que provee funciones de formateo muy útiles:

Función Ejemplo de output
label_number() 1,234.5 (genérico)
label_comma() 1,234,567
label_dollar() $1,234.50
label_percent() 12.5%
label_scientific() 1.23e+04
label_log() 10^4 (para ejes log)
label_date(format = "%b %Y") Mar 2024

Y para breaks:

  • breaks_pretty(n): divisiones “humanas”.
  • breaks_log(): divisiones logarítmicas (10, 100, 1000…).
  • breaks_width(width): divisiones de ancho fijo.

Combinación típica:

scale_y_continuous(
  breaks = breaks_pretty(n = 6),
  labels = label_number(scale = 1e-3, suffix = "k")
)

scale = 1e-3 divide los valores entre 1000 antes de mostrarlos, con sufijo "k". Útil para no llenar el eje con muchos ceros.

limits vs coord_cartesian: la trampa silenciosa

Hay dos formas de “recortar” el rango visible de un gráfico. No son equivalentes.

# Forma 1: limits en la escala
ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  scale_x_continuous(limits = c(0, 2))    # ¡filtra los datos!

# Forma 2: zoom con coord_cartesian
ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  coord_cartesian(xlim = c(0, 2))         # zoom sin filtrar

La diferencia es crítica:

  • limits en la escala → los puntos fuera del rango son eliminados antes de pasar a los siguientes pasos. Si tienes un geom_smooth después, lo calcula solo con los puntos dentro del rango.
  • coord_cartesian(xlim = ...) → los puntos siguen ahí, los cálculos siguen usando todos los datos. Solo se cambia el rango visible.

Regla práctica: para “ver una zona del gráfico”, usa coord_cartesian. Para “filtrar los datos antes de plotear”, filtra explícitamente con dplyr::filter(), no escondas el filtrado dentro de la escala.

Trampas habituales

  • limits en scale_* cuando quieres zoom. El gráfico se ve igual pero geom_smooth y otras estadísticas se han recalculado con menos datos. Diagnóstico: la línea de tendencia cambia al “ajustar el eje”. Solución: coord_cartesian para zoom.
  • Olvidar el scale_ para ejes log y aplicar log() a la columna. aes(y = log(price)) funciona, pero el eje muestra valores logarítmicos (-2, 0, 2), no los originales. Usar scale_y_log10() mantiene las etiquetas legibles (1, 10, 100).
  • Mezclar locale del SO con locale del gráfico. Si fijas Sys.setlocale("LC_TIME", "es_ES.UTF-8") y olvidas resetearlo, puede afectar a otros análisis de la misma sesión. Mejor documentarlo en el script o usar withr::with_locale() para encapsularlo.
  • breaks con valores fuera del rango de los datos. Funciona, pero deja huecos visuales. Si especificas breaks manualmente, asegúrate de que tienen sentido para el rango efectivo de los datos.

En la siguiente entrega

Has aprendido a controlar las escalas. Los datos ya están bien representados. Falta hacer que el gráfico se vea bien como pieza editorial. Eso es el séptimo componente de la gramática: el theme. Vemos cómo elegir un theme base, cómo customizar tipografía y cómo construir un theme reutilizable que dé identidad visual a tus gráficos. Es lo siguiente.