Themes y customización tipográfica

r
visualizacion
ggplot2
El séptimo componente de la gramática: el aspecto no relacionado con los datos. Themes built-in, theme() y los elementos, tipografía editorial con showtext y cómo crear un theme reutilizable.

¿Qué es un theme?

En la gramática de Wilkinson, el theme es la capa que controla todo lo que no depende de los datos: tipografía, fondo, leyendas, márgenes, colores del eje, grosor de las líneas no-datos.

Datos y theme son ortogonales. Puedes cambiar el theme entero sin tocar el resto del gráfico. Es lo que permite tener un “estilo de la casa”, tu marca visual, tus tipos, tu paleta de neutrales, aplicado consistentemente a treinta gráficos distintos.

Themes built-in

ggplot2 viene con varios themes pre-hechos:

library(ggplot2)
p <- ggplot(diamonds, aes(carat, price)) + geom_point(alpha = 0.1)

p + theme_grey()      # default — fondo gris, líneas blancas
p + theme_bw()        # fondo blanco, ejes negros, gridlines grises
p + theme_minimal()   # como bw pero sin marco
p + theme_classic()   # ejes negros estilo libro, sin grid
p + theme_void()      # sin nada (solo el gráfico, útil para mapas)
p + theme_dark()      # fondo oscuro

Recomendación práctica: theme_minimal() para uso editorial moderno. theme_classic() para algo más austero, estilo libro científico. theme_void() cuando los ejes son redundantes (mapas, infografías).

Para que se aplique globalmente sin escribirlo en cada gráfico:

theme_set(theme_minimal())

A partir de esa línea, todos los ggplot() siguientes usan theme_minimal por defecto.

theme() y los tres tipos de elementos

Para customizar más allá del preset, usa theme(). Cada componente del gráfico es un elemento que se ajusta con una de estas tres funciones:

Función Para qué elementos
element_text() Cualquier texto (títulos, ejes, leyendas)
element_line() Líneas (ejes, gridlines)
element_rect() Rectángulos (fondo del panel, fondo total)

Ejemplo completo:

ggplot(diamonds, aes(carat, price)) +
  geom_point(alpha = 0.1) +
  theme_minimal() +
  theme(
    plot.title       = element_text(size = 16, face = "bold"),
    axis.title       = element_text(size = 12, color = "grey20"),
    axis.text        = element_text(size = 10),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey90"),
    plot.background  = element_rect(fill = "white", color = NA)
  ) +
  labs(title = "Precio vs quilates")

La estructura: <nombre_del_elemento> = element_<tipo>(<parámetros>).

Los nombres siguen el patrón <dónde>.<qué>:

  • plot.*: del gráfico entero (título, subtítulo, caption, background).
  • panel.*: del área del gráfico (background, grid lines).
  • axis.*: de los ejes (text, title, ticks, line).
  • legend.*: de las leyendas (text, title, position, key).
  • strip.*: de los títulos de los facets.

Para “no mostrar” un elemento: element_blank().

Tipografía editorial: showtext

ggplot2 por defecto usa la fuente de sistema. Si quieres tipografía editorial (serif para títulos, sans humanista para texto), showtext es la solución más robusta.

install.packages("showtext")
library(showtext)

# Cargar fuentes desde Google Fonts
font_add_google("Newsreader", "newsreader")
font_add_google("Inter Tight", "inter")

# Activar showtext (necesario para que las fuentes se rendericen)
showtext_auto()

# Usar en el theme
ggplot(diamonds, aes(carat, price)) +
  geom_point(alpha = 0.1) +
  theme_minimal(base_family = "inter") +
  theme(
    plot.title    = element_text(family = "newsreader", size = 18, face = "plain"),
    plot.subtitle = element_text(family = "inter", size = 11, color = "grey40")
  ) +
  labs(
    title    = "Precio vs quilates",
    subtitle = "Dataset diamonds (ggplot2)"
  )

base_family en el theme aplica la fuente a todo el texto base. El theme() posterior la sobrescribe para elementos específicos.

Importante: showtext_auto() debe estar activo cuando renderizas. Si exportas con ggsave() y las fuentes salen como las del sistema en lugar de las cargadas, has olvidado activar showtext_auto() o estás en un dispositivo gráfico que no lo soporta. Casi siempre la solución es añadir al script:

showtext_opts(dpi = 300)   # asegura buena resolución al exportar

Construir un theme reutilizable

El paso que separa scripts ad-hoc de un sistema visual: define tu theme una vez, úsalo en todos tus gráficos.

theme_rmori <- function(base_size = 12) {
  theme_minimal(base_size = base_size, base_family = "inter") +
  theme(
    plot.title       = element_text(family = "newsreader", size = rel(1.5),
                                    face = "plain", color = "#2A2A2A"),
    plot.subtitle    = element_text(size = rel(1.0), color = "#6B6B6B",
                                    margin = margin(b = 12)),
    plot.caption     = element_text(size = rel(0.75), color = "#6B6B6B",
                                    hjust = 0, margin = margin(t = 12)),
    axis.title       = element_text(size = rel(0.9), color = "#3D3D3D"),
    axis.text        = element_text(size = rel(0.85), color = "#6B6B6B"),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "#E5E1DA", linewidth = 0.3),
    plot.background  = element_rect(fill = "#FAF9F6", color = NA),
    legend.position  = "bottom"
  )
}

Uso:

ggplot(diamonds, aes(carat, price)) +
  geom_point(alpha = 0.1, color = "#5F8575") +
  theme_rmori() +
  labs(
    title    = "Precio vs quilates",
    subtitle = "Relación log-lineal en diamantes",
    caption  = "Fuente: dataset diamonds (ggplot2)"
  )

Ahora tienes identidad visual. Cualquier gráfico nuevo que añadas con theme_rmori() aplicará el mismo lenguaje. Cuando quieras evolucionarlo, cambias una función y todos los gráficos se actualizan.

Trampas habituales

  • Olvidar showtext_auto() al exportar con ggsave(). Las fuentes salen genéricas y no entiendes por qué. Solución: showtext_auto() antes de ggsave() o configurar showtext_opts(dpi = ...).
  • element_blank() vs no especificar nada. Si no defines un elemento en theme(), se hereda del theme base. Si quieres eliminarlo expresamente, element_blank(). No es lo mismo.
  • theme() antes que theme_minimal(). El orden importa: si pones theme(panel.grid = ...) antes de theme_minimal(), el preset sobrescribe tu customización. Convención: theme_minimal() primero, theme(...) después.
  • Tamaños absolutos en lugar de rel(). Si pones size = 14 para el título y luego cambias base_size = 16, el título no escala. Usar size = rel(1.5) mantiene proporciones cuando varías el tamaño base.

En la siguiente entrega

Has aprendido a controlar la apariencia. Queda una pieza para que tus gráficos cuenten una historia: anotar. Etiquetas, líneas de referencia, marcadores destacados, todo lo que dirige la atención del lector al hallazgo. Vemos geom_text, annotate() y ggrepel en el siguiente tutorial.