Visualización

Paquetes de R para construir, componer y animar gráficos de calidad publicable

r
visualization
ggplot2
plotting
graphics
genomics
neuroimaging
Referencia comentada del ecosistema de visualización en R: ggplot2 como núcleo, extensiones de dominio (genómica, neuroimagen) y herramientas de composición y animación.

Sobre visualización en R

R nace en estadística y la visualización ha sido siempre uno de sus puntos fuertes. Hoy el ecosistema gravita alrededor de ggplot2 y la grammar of graphics de Leland Wilkinson: una idea estructurada según la cual un gráfico se compone de capas (datos + mapeos estéticos + geometrías + escalas + facetas + tema). Esto contrasta con el sistema base (plot(), par(), points(), lines()), imperativo, rápido, útil para diagnósticos sueltos pero costoso de mantener, y con lattice, la implementación clásica de trellis graphics de Deepayan Sarkar, hoy en uso minoritario fuera de comunidades específicas (econometría, ciertos paquetes de mixed models).

En la práctica, un usuario senior:

  • Usa base para diagnósticos rápidos dentro de la consola (plot(model), hist(), pairs()) y para gráficos que están dentro de paquetes que no quieren depender de ggplot2.
  • Construye gráficos finales con ggplot2 y elige entre el océano de extensiones (gg*) según el dominio: gggenes para genómica, ggseg para neuroimagen, ggrepel para etiquetas no superpuestas, ggtree para árboles filogenéticos, ggdist para distribuciones, ggplot2::geom_sf para mapas.
  • Compone múltiples paneles con patchwork (o cowplot si necesita alineación más exigente) en lugar de pelearse con gridExtra::grid.arrange o par(mfrow = ...).
  • Sale de ggplot2 cuando hace falta interactividad: plotly::ggplotly() para conversión rápida, htmlwidgets (leaflet, DT, dygraphs) cuando el target es HTML.
  • Anima con gganimate o, si el output es 3D / científico, considera rgl, plotly o rayshader.

Tres principios que conviene tener interiorizados:

  • Datos tidy primero. ggplot2 espera long format. Tiempo invertido en tidyr::pivot_longer() se recupera con creces frente a forzar la geometría sobre datos anchos.
  • Escalas, no aes(). Personalizar colores, formas o tamaños se hace con scale_*_manual(), scale_*_brewer(), scale_*_viridis_c(). Hardcodear estéticas dentro de aes() rompe la lógica de leyendas y mapeos.
  • Tema al final. El orden idiomático es ggplot(...) + geom_*() + scale_*() + facet_*() + labs() + theme_*(). Cambios de tema deben aplicarse al final, idealmente con un theme_set() global por proyecto.

Esta página cataloga los paquetes que estructuran la mayor parte de los flujos de visualización en R. El orden refleja jerarquía conceptual: primero el núcleo (ggplot2), después las extensiones específicas de dominio, y finalmente composición y animación.


ggplot2

ggplot2 es la implementación canónica de la grammar of graphics en R y, en la práctica, el sistema gráfico por defecto fuera del dibujo rápido en consola. Desarrollado por Hadley Wickham, hoy es parte del tidyverse y la base sobre la que se han construido cientos de extensiones específicas de dominio.

Su valor real no está en producir un gráfico concreto, sino en imponer una sintaxis composicional: si entiendes cómo encadenar capas, escalas y facetas, prácticamente cualquier visualización que veas publicada es reproducible con dos o tres líneas de código.

Cuándo usarlo

Por defecto, en cualquier visualización que vaya a aparecer en un informe, paper, dashboard o presentación. Especialmente fuerte cuando hay agrupaciones (color, fill, shape), facetas (facet_wrap, facet_grid) o múltiples geometrías superpuestas en el mismo lienzo.

Cuándo NO usarlo

  • Diagnósticos efímeros dentro de la consola. Para plot(model), hist(x), pairs(df), el sistema base es más rápido y no añade dependencia.
  • Volúmenes muy grandes (millones de puntos sin agregación). Considera ggrastr para rasterizar capas pesadas dentro de un ggplot, scattermore para scatter eficiente, o herramientas fuera de R (datashader en Python) cuando el render se vuelve inviable.
  • Gráficos interactivos por defecto. ggplot2 produce gráficos estáticos. Para interactividad, envuelve con plotly::ggplotly() o salta a plotly / htmlwidgets directamente.
  • Animaciones nativas. Usa gganimate (extensión), no es funcionalidad core.

Conceptos clave

  • Capas (geom_*). Los geom_point, geom_line, geom_col, geom_boxplot, geom_smooth son las unidades constructivas. Cada uno hereda mapeos del ggplot(...) raíz o define los suyos propios.
  • aes() mapea variables a estéticas. Los valores literales van fuera. aes(color = species) mapea la variable. color = "red" se pone como argumento del geom, no dentro de aes().
  • Escalas. Cualquier estética (color, fill, x, y, size, alpha) tiene una familia scale_<estética>_<tipo>(). Usa scale_color_viridis_d() para discreto perceptualmente uniforme. scale_color_brewer(palette = "Set2") para paletas categóricas estandarizadas.
  • Facetas. facet_wrap(~ var) para una variable. facet_grid(rows ~ cols) para dos cruzadas. scales = "free_y" cuando los rangos no son comparables.
  • Temas (theme_*). theme_minimal(), theme_bw(), theme_classic() son los más usados. theme() permite ajuste fino de cualquier elemento (element_text, element_rect, element_blank).
  • Datos largos. La gramática asume long format. Si tu gráfico requiere demasiados aes() con columnas distintas, casi siempre la solución es pivot_longer() previo.

Patrón mínimo

library(ggplot2)

ggplot(mpg, aes(x = displ, y = hwy, color = class)) +
  geom_point(alpha = 0.7, size = 2) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 0.6) +
  scale_color_viridis_d(option = "D") +
  facet_wrap(~ drv, nrow = 1) +
  labs(
    x = "Cilindrada (L)",
    y = "Consumo en carretera (mpg)",
    color = "Clase de vehículo",
    title = "Consumo vs cilindrada por tipo de tracción"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

Trampas habituales

  • aes() con literales. Poner color = "red" dentro de aes() crea una variable llamada literalmente “red” y dibuja una leyenda absurda. Los literales fuera de aes(), los mapeos dentro.
  • Orden de factor. Las leyendas y los ejes se ordenan alfabéticamente por defecto. Para controlar el orden, convierte la variable a factor con levels = ... explícitos, o usa forcats::fct_reorder().
  • group = cuando no hay color. Si dibujas múltiples líneas sin mapear color/linetype, ggplot2 las une todas en una sola. Añade aes(group = id) para separarlas visualmente sin que aparezca leyenda.
  • scale_*_continuous vs scale_*_discrete. No son intercambiables. Deben coincidir con el tipo de la variable mapeada. Confundirlos es la causa más frecuente del error “Discrete value supplied to continuous scale”.

Enlaces

Relacionados en esta página


patchwork

patchwork es el paquete de composición de gráficos ggplot2 en layouts arbitrarios desarrollado por Thomas Lin Pedersen. Resuelve, con una sintaxis algebraica notablemente legible, el problema clásico de combinar varios paneles en una figura única, algo que históricamente se hacía con gridExtra::grid.arrange o cowplot::plot_grid, ambos funcionales pero verbosos.

Hoy es la opción por defecto en el tidyverse y la primera elección para figuras compuestas de paper.

Cuándo usarlo

  • Figuras compuestas para publicación con dos o más paneles relacionados (p. ej. PCA + heatmap + barplot).
  • Cuando necesitas alineación automática de ejes y leyendas entre paneles.
  • Anotación global (un único título, una única leyenda compartida).

Cuándo NO usarlo

  • facet_wrap / facet_grid ya resuelven el caso. Si los paneles muestran la misma geometría sobre subconjuntos de los datos, las facetas de ggplot2 son superiores: mismas escalas, mismo gráfico, código más corto.
  • Alineación pixel-perfect muy exigente entre paneles de orígenes muy distintos: cowplot::plot_grid ofrece control más fino sobre alineación de bordes. gridExtra da acceso directo a grobs si necesitas manipulación manual.
  • Composición con gráficos no-ggplot. patchwork funciona con ggplots. Mezclar lattice, base o grid requiere cowplot o conversión manual con gridGraphics.

Conceptos clave

  • Operadores. + añade en el siguiente espacio libre. | coloca en horizontal. / apila vertical. () agrupa subexpresiones. (p1 | p2) / p3 produce dos arriba, uno abajo.
  • plot_layout() controla ncol, nrow, anchos relativos (widths = c(2, 1)), alturas, y guides (guides = "collect" consolida leyendas duplicadas).
  • plot_annotation() añade título, subtítulo, caption y etiquetas automáticas de panel (tag_levels = "A" produce A, B, C, D).
  • & theme(...) aplica un tema a todos los subplots simultáneamente. & scale_color_viridis_d() propaga una escala común.

Patrón mínimo

library(ggplot2)
library(patchwork)

p1 <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + theme_minimal()
p2 <- ggplot(mtcars, aes(factor(cyl), mpg)) + geom_boxplot() + theme_minimal()
p3 <- ggplot(mtcars, aes(hp)) + geom_histogram(bins = 15) + theme_minimal()

(p1 | p2) / p3 +
  plot_layout(heights = c(2, 1)) +
  plot_annotation(
    title    = "Análisis exploratorio de mtcars",
    tag_levels = "A"
  ) &
  theme(plot.tag = element_text(face = "bold"))

Trampas habituales

  • Leyendas duplicadas. Sin guides = "collect" en plot_layout(), cada panel muestra su propia leyenda aunque sea idéntica. Cuando el mapeo es el mismo, consolidar siempre.
  • & vs + para temas. + aplica el tema solo al último plot añadido. & lo propaga a todos los subplots de la composición. Confundirlos da resultados estéticos inconsistentes.
  • Tamaños relativos olvidados. Por defecto todos los paneles tienen el mismo peso. Para paneles asimétricos usa plot_layout(widths = ..., heights = ...).

Enlaces

Relacionados en esta página

  • ggplot2, patchwork solo compone ggplots.

gganimate

gganimate extiende ggplot2 con transiciones temporales y animación. Reemplaza el flujo histórico de generar frames manualmente y unirlos con ImageMagick, ofreciendo en su lugar verbos declarativos (transition_*, ease_aes, view_*) que se componen como cualquier otra capa de ggplot.

Está desarrollado por Thomas Lin Pedersen, igual que patchwork. Las animaciones se renderizan a GIF (gifski) o vídeo (av, ffmpeg) según el renderer configurado.

Cuándo usarlo

  • Series temporales o secuencias donde la dimensión tiempo (o cualquier variable ordinal) cuenta una historia que un facetado no transmite igual de bien.
  • Presentaciones y comunicación divulgativa: el movimiento captura la atención cuando el público no está dispuesto a leer un grid de paneles estáticos.
  • Demostración de procesos iterativos (convergencia de un algoritmo, evolución de una simulación).

Cuándo NO usarlo

  • Artículo científico estático. Las revistas siguen siendo PDF. Una animación no es citable como figura. Usa facet_wrap(~ year) y deja la animación para la companion website.
  • Datos sin orden natural. Animar transiciones entre categorías sin un eje temporal o secuencial suele ser más confuso que informativo.
  • Volúmenes grandes. Renderizar cientos de frames con miles de puntos cada uno es lento y produce GIFs enormes. Considera muestreo o agregación previa.
  • Output interactivo. Para gráficos donde el usuario controla qué se muestra, plotly o shiny son la herramienta correcta, gganimate produce vídeo, no interactividad.

Conceptos clave

  • transition_* define qué variable orquesta la animación: transition_time(year) para tiempo continuo, transition_states(group) para saltos discretos, transition_reveal(t) para acumulación incremental.
  • ease_aes() controla la interpolación entre estados ("linear", "cubic-in-out", "quadratic-out"). Cambia drásticamente la percepción del movimiento.
  • view_follow() hace que los ejes “persigan” los datos en cada frame. Útil cuando los rangos cambian mucho.
  • shadow_* deja rastro de estados anteriores: shadow_mark() (puntos previos persistentes), shadow_trail() (cola que se desvanece), shadow_wake() (estela suavizada).
  • anim_save() persiste la animación. animate(plot, renderer = gifski_renderer()) controla resolución, FPS y duración.

Patrón mínimo

library(ggplot2)
library(gganimate)
library(gapminder)

p <- ggplot(gapminder,
            aes(gdpPercap, lifeExp,
                size = pop, color = continent)) +
  geom_point(alpha = 0.7) +
  scale_x_log10() +
  scale_size(range = c(2, 12), guide = "none") +
  scale_color_viridis_d() +
  labs(
    title = "Año: {frame_time}",
    x = "PIB per cápita (log)", y = "Esperanza de vida"
  ) +
  theme_minimal() +
  transition_time(year) +
  ease_aes("cubic-in-out")

animate(p, nframes = 100, fps = 20, width = 800, height = 500,
        renderer = gifski_renderer())
anim_save("gapminder.gif")

Trampas habituales

  • {frame_time} vs {closest_state}. transition_time() usa {frame_time}. transition_states() usa {closest_state}. Confundir las plantillas de string en title deja el título vacío.
  • Tiempo de render. El render es lineal en frames × geoms. Empieza con nframes = 20 para iterar diseño. Solo después sube a nframes = 100+ para versión final.
  • Falta de gifski o av. Sin un renderer instalado, animate() falla con error opaco. install.packages("gifski") resuelve el caso por defecto.

Enlaces

Relacionados en esta página

  • ggplot2, gganimate solo anima ggplots.

gggenes

gggenes es una extensión de ggplot2 para dibujar mapas de genes, flechas que representan la posición, tamaño y orientación de ORFs sobre una pista lineal. Desarrollado por David Wilkins, se ha convertido en el estándar de facto cuando hay que mostrar operones, regiones sintenicas, plásmidos o clústeres biosintéticos en R.

Su valor es operar dentro de la gramática de ggplot2: las flechas son una geom más (geom_gene_arrow) que se compone con el resto del lenguaje (facetas, escalas, temas) en lugar de ser un sistema gráfico aparte.

Cuándo usarlo

  • Operones, clústeres biosintéticos (BGCs), regiones plasmídicas, elementos móviles representados como flechas con coordenadas y orientación.
  • Comparación de sintenia entre genomas (facet_wrap(~ molecule) con make_alignment_dummies()).
  • Anotación de mutaciones, dominios o subregiones sobre una pista de genes con geom_subgene_arrow().

Cuándo NO usarlo

  • Visualización genómica multi-pista compleja (genes + cobertura + variantes + conservación apilados). Ahí entran Gviz, karyoploteR o ggbio, diseñados para esa lógica de tracks.
  • Vistas circulares (cromosomas bacterianos, plásmidos circulares estilo SnapGene). Usa gggenomes, circlize o BioCircos.
  • Conexiones de sintenia entre múltiples genomas con bloques homólogos. gggenomes (del mismo autor, hereda de gggenes) está diseñado específicamente para eso.

Conceptos clave

  • geom_gene_arrow() dibuja la flecha. Mapeos típicos: xmin, xmax, y (molécula/contig), fill (familia funcional), forward (TRUE si en cadena +).
  • geom_gene_label() etiqueta cada flecha. Usa la fuente especificada y se recorta si no cabe.
  • make_alignment_dummies() crea puntos invisibles que alinean genes homólogos entre facetas, pieza clave para comparar operones entre cepas.
  • theme_genes() ajusta el tema base (sin grid de y, eje y discreto limpio) para que el resultado parezca un mapa genómico, no un scatter.
  • geom_subgene_arrow() dibuja subregiones (dominios, mutaciones) dentro de una flecha de gen, útil para anotación fina.

Patrón mínimo

library(ggplot2)
library(gggenes)

ggplot(example_genes,
       aes(xmin = start, xmax = end, y = molecule,
           fill = gene, forward = orientation)) +
  geom_gene_arrow(arrowhead_height = unit(3, "mm"),
                  arrowhead_width  = unit(2, "mm")) +
  geom_gene_label(aes(label = gene), align = "left") +
  facet_wrap(~ molecule, scales = "free", ncol = 1) +
  scale_fill_brewer(palette = "Set3") +
  theme_genes()

Trampas habituales

  • Orientación. forward = TRUE/FALSE controla la dirección de la flecha. Si tu columna codifica la cadena como "+"/"-", conviértela antes (orientation = strand == "+").
  • Facetas sin alineación. Si los genes homólogos no caen en la misma x entre facetas, la comparación visual no funciona. make_alignment_dummies() resuelve esto al precio de generar filas auxiliares.
  • Etiquetas que se solapan. Cuando los genes son cortos y muchos, geom_gene_label() recorta agresivamente. Para regiones densas, considera dejar etiquetas fuera y mapear fill a familia funcional con leyenda.

Enlaces

Relacionados en esta página

  • ggplot2, gggenes hereda toda su gramática.

ggseg

ggseg es una extensión de ggplot2 para visualizar segmentaciones anatómicas del cerebro sobre atlas estándar (Desikan-Killiany, Destrieux, aseg, glasser). Desarrollado por Athanasia Mowinckel y Didac Vidal-Piñeiro, su nicho es traducir vectores de valores por región (espesor cortical, volumen, t-stat, índice de conectividad) en mapas cerebrales reproducibles dentro de la gramática de ggplot2.

El paquete principal contiene los atlas más comunes. Familias relacionadas (ggsegExtra, ggseg3d, ggsegAtlas) extienden el catálogo de atlas y añaden vistas 3D.

Cuándo usarlo

  • Resultados de morfometría (FreeSurfer, FSL) por región anatómica que necesitan visualización rápida en figuras 2D estándar (lateral / medial, ambos hemisferios).
  • Comparación entre grupos o tratamientos con un valor por región y atlas (t-stat, Cohen’s d, diferencia de medias).
  • Cualquier output ROI-based donde la geometría real del cerebro aporta más que una tabla.

Cuándo NO usarlo

  • Mapas voxel-wise (estadísticos por voxel, no por región). Eso es trabajo de freesurferformats + visores nativos (Freeview, FSLeyes) o paquetes como oro.nifti / neurobase.
  • Conectividad y grafos cerebrales complejos (matrices de conectividad, circular plots). Usa circlize, brainGraph o herramientas dedicadas (BrainNet Viewer).
  • Renders 3D interactivos rotables. Aunque ggseg3d existe, para análisis 3D serio (proyecciones, vistas inflated) lo natural es exportar a FreeSurfer/PySurfer.
  • Atlas no incluidos. Si tu parcelación es custom o muy específica (Schaefer 400, Yeo 17-network, AAL3), revisa primero ggsegExtra antes de asumir que está disponible.

Conceptos clave

  • El paquete contiene polígonos por región, no imágenes. Esto permite usar geom_brain() como una geom de ggplot, con aes(fill = mi_metrica) mapeas valores a regiones de forma estándar.
  • Datos en formato largo. Cada fila es una región × hemisferio × valor. La columna region (o label, según atlas) debe coincidir con los nombres del atlas, los mismatches silenciosos son la fuente principal de figuras vacías.
  • Atlas disponibles. dk (Desikan-Killiany), aseg (subcortical), destrieux, glasser, yeo7/yeo17. Cada uno expone su propio data frame con polígonos.
  • position = "stacked" vs position = "dispersed". Controla si los hemisferios se apilan o se separan en la figura.

Patrón mínimo

library(ggplot2)
library(ggseg)

# data.frame con una fila por región y un valor a mapear
df <- tibble::tibble(
  region = c("superior frontal", "precuneus", "insula"),
  value  = c(0.6, -0.3, 0.8)
)

ggplot(df) +
  geom_brain(atlas = dk, aes(fill = value),
             position = position_brain(hemi ~ side)) +
  scale_fill_gradient2(low = "blue", mid = "white", high = "red",
                       midpoint = 0, na.value = "grey90") +
  theme_void() +
  labs(fill = "Efecto (z)")

Trampas habituales

  • Nombres de región que no casan con el atlas. ggseg no produce error si un valor no encuentra región, simplemente no se pinta. Verifica con dk$data$region |> unique() antes de asumir que la figura está mal renderizada.
  • Hemisferios y vistas. position_brain(hemi ~ side) separa hemisferios (izquierda/derecha) y vistas (lateral/medial). Sin position_brain, la disposición por defecto puede mezclarlos.
  • Conflictos con scales discretas. Si value es categórico, usa scale_fill_manual(). Si es continuo, scale_fill_gradient*(). Confundirlos hace que las regiones queden grises.

Enlaces

Relacionados en esta página

  • ggplot2, base sobre la que opera.

Recursos auxiliares

Pequeñas referencias prácticas que recurren constantemente al trabajar con cualquiera de los paquetes anteriores.

Paletas de colores

Elegir paletas no es decorativo: una paleta mal escogida puede invertir la interpretación de un gradiente o dejar daltónicos sin información. Recomendaciones:

  • Continuas perceptualmente uniformes: viridis, magma, cividis (scale_*_viridis_c()). Diseñadas explícitamente para ser correctas en escala de grises y daltonismo.
  • Continuas divergentes (valores con signo, centro en 0): RColorBrewer::brewer.pal(11, "RdBu") o scale_*_gradient2(midpoint = 0).
  • Categóricas: scale_*_brewer(palette = "Set2") para hasta 8 categorías. Más allá, conviene replantear el agrupamiento.
  • Referencia visual de paletas R: r-charts.com/es/colores.

Símbolos de punto (pch) en base R

Para gráficos en sistema base (no ggplot), pch controla la forma del marcador. Valores típicos: 0 (cuadrado), 1 (círculo), 2 (triángulo), 16 (círculo relleno), 17 (triángulo relleno), 19 (círculo relleno sólido), 21-25 (formas con fill + color separados).

Referencia visual con todos los códigos: sthda.com, pch symbols.

En ggplot2 el equivalente es la estética shape y se controla con scale_shape_manual(values = c(16, 17, 15)).