tidyr: pivot_longer y pivot_wider

r
tidyverse
Qué es tidy data, cuándo cada formato es el correcto y cómo moverte entre ellos. pivot_longer, pivot_wider y los helpers que casi nadie conoce.

¿Qué es “tidy data”?

Una tabla está en formato tidy cuando cumple tres reglas (Wickham, 2014):

  1. Cada variable tiene su propia columna.
  2. Cada observación tiene su propia fila.
  3. Cada valor tiene su propia celda.

Suena trivial. No lo es. La mayoría de los datos del mundo real vienen en formato wide: una variable repartida en varias columnas. Por ejemplo:

pais     | ventas_2022 | ventas_2023 | ventas_2024
---------|-------------|-------------|------------
España   |   1200      |   1400      |   1600
Francia  |   1800      |   2100      |   2400

Para humanos, esto se lee bien. Para análisis con dplyr + ggplot2, no. La variable real es año, pero está embebida en los nombres de columna. Hay que extraerla.

La forma tidy del mismo dato:

pais     | año  | ventas
---------|------|-------
España   | 2022 | 1200
España   | 2023 | 1400
España   | 2024 | 1600
Francia  | 2022 | 1800
Francia  | 2023 | 2100
Francia  | 2024 | 2400

Ahora cada variable es una columna. group_by(pais), group_by(año), ggplot(aes(x = año, y = ventas, color = pais)), todo trivial.

La regla práctica: wide para humanos, long para análisis.

pivot_longer(): de wide a long

Es el verbo que más usarás. Lo que hace: toma N columnas y las convierte en 2 columnas (nombre + valor) con N veces más filas.

library(tidyr)

ventas_wide |>
  pivot_longer(
    cols      = starts_with("ventas_"),
    names_to  = "año",
    values_to = "ventas"
  )

Argumentos:

  • cols: qué columnas pivotar. Acepta selectores tipo select() (starts_with, where, etc.).
  • names_to: nombre de la nueva columna que contendrá los nombres originales.
  • values_to: nombre de la nueva columna que contendrá los valores.

Si los nombres originales contenían info que quieres separar (por ejemplo ventas_2022_q1), usa names_pattern o names_sep:

ventas |>
  pivot_longer(
    cols          = starts_with("ventas_"),
    names_to      = c("año", "trimestre"),
    names_pattern = "ventas_(\\d{4})_q(\\d)",
    values_to     = "valor"
  )

Aquí cada nombre tipo ventas_2022_q3 se parsea en año = "2022" y trimestre = "3". Es uno de los superpoderes que tidyr ofrece y casi nadie usa.

Para casos con un separador fijo, la alternativa más simple:

ventas |>
  pivot_longer(
    cols      = starts_with("ventas_"),
    names_to  = c("año", "trimestre"),
    names_sep = "_",
    values_to = "valor"
  )

pivot_wider(): de long a wide

El inverso. Útil para presentación, tablas resumen, exports a Excel.

ventas_long |>
  pivot_wider(
    names_from  = año,
    values_from = ventas
  )

Argumentos:

  • names_from: de qué columna salen los nombres de las nuevas columnas.
  • values_from: de qué columna salen los valores.

Cuando una combinación (identificador, name) se repite, pivot_wider se queja y deja una lista en la celda. Para resumir al pivotar, pasa values_fn:

ventas_long |>
  pivot_wider(
    names_from  = año,
    values_from = ventas,
    values_fn   = sum
  )

Con values_fn = sum, si hay varias filas para la misma combinación, las suma. Patrón típico: agregar y pivotar en una sola operación.

El flujo típico

El patrón más común en análisis real es:

datos_wide |>
  pivot_longer(cols = starts_with("año_"), names_to = "año", values_to = "valor") |>
  mutate(año = as.integer(año)) |>
  filter(año >= 2020) |>
  group_by(pais, año) |>
  summarise(total = sum(valor, na.rm = TRUE), .groups = "drop") |>
  pivot_wider(names_from = año, values_from = total)

Léelo: “alargar para analizar, agregar, ensanchar para presentar”. Esa es la cadencia. Long es la forma de trabajo. Wide es la forma de salida.

Más allá de pivots: separate_wider_* y unite()

A veces el problema no es wide vs long sino dentro de una sola columna:

# Columna "nombre_completo" con "Apellido, Nombre"
contactos |>
  separate_wider_delim(
    cols   = nombre_completo,
    delim  = ", ",
    names  = c("apellido", "nombre")
  )

Y el inverso, unite():

contactos |>
  unite("nombre_completo", apellido, nombre, sep = ", ")

separate_wider_delim (y sus primos _position, _regex) reemplazan al antiguo separate(), que sigue funcionando pero se está soft-deprecating.

Trampas habituales

  • Pivotar todo el data frame. Si pasas cols = everything() por reflejo, te llevas las columnas identificadoras al pivot. Especifica solo las columnas que realmente son la variable repartida.
  • pivot_wider() que genera columnas con nombres no sintácticos. Si año tiene valores como "2024 (provisional)", pivot_wider crea una columna con ese nombre exacto. Para acceder después tienes que usar backticks. Limpia los valores antes de pivotar si vas a operar con ellos por nombre.
  • Olvidar values_fn en pivot_wider. Si hay duplicados de la clave conjunta, pivot_wider los embebe como listas (con un warning fácil de ignorar). Si esperabas valores planos, el bug pasa silencioso. Casi siempre quieres values_fn = sum, mean, first, etc.
  • pivot_longer con cols = -id. Funciona y es muy idiomático (“todo menos id”), pero si añades una columna identificadora nueva al dataset, se te pivota sin querer. Lista explícita > negación cuando la composición es delicada.

En la siguiente entrega

Has aprendido a reorganizar tablas entre formatos. Queda un dominio que casi todo dataset real toca y que en R es notoriamente espinoso: las fechas. lubridate es el paquete que las hace tratables, pero hay tres conceptos distintos (duration, period, interval) que conviene entender bien. Es lo siguiente.