¿Cómo puedo expandir columnas anidadas en R? Guía completa de `tidyr::unnest_wider()`
tidyr::unnest_wider() es tu herramienta ideal cuando necesitas expandir estos datos horizontalmente, convirtiendo cada elemento de la lista en una columna separada.¿Qué es tidyr::unnest_wider()?
tidyr::unnest_wider() es una función del paquete tidyr que expande columnas anidadas en múltiples columnas del tibble principal. A diferencia de tidyr::unnest() que expande en filas, unnest_wider() expande horizontalmente.
Sintaxis Básica
tibble_desanidado <- tibble_original %>%
tidyr::unnest_wider(columna_anidada)
Caso de Uso 1: Lista de Vectores Nombrados
Este es el caso más común: tienes una columna que contiene listas con elementos nombrados.
# Crear datos de ejemplo
datos_ejemplo <- tibble::tibble(
id = 1:3,
info = list(
c(nombre = "Ana", edad = "25", ciudad = "Madrid"),
c(nombre = "Luis", edad = "30", ciudad = "Barcelona"),
c(nombre = "María", edad = "28", ciudad = "Valencia")
)
)
# Ver estructura original
print(datos_ejemplo)
# # A tibble: 3 × 2
# id info
# <int> <list>
# 1 1 <chr [3]>
# 2 2 <chr [3]>
# 3 3 <chr [3]>
# Expandir horizontalmente
datos_expandidos <- datos_ejemplo %>%
tidyr::unnest_wider(info)
print(datos_expandidos)
# # A tibble: 3 × 4
# id nombre edad ciudad
# <int> <chr> <chr> <chr>
# 1 1 Ana 25 Madrid
# 2 2 Luis 30 Barcelona
# 3 3 María 28 Valencia
¿Qué Sucedió?
Cada elemento de la lista info (nombre, edad, ciudad) se convirtió en su propia columna. Las tres columnas anidadas ahora son columnas independientes del tibble.
Caso de Uso 2: Columnas con Tibbles Anidados
Cuando tu columna anidada contiene tibbles completos:
# Datos con tibbles anidados
datos_complejos <- tibble::tibble(
producto = c("Laptop", "Mouse", "Teclado"),
especificaciones = list(
tibble::tibble(marca = "Dell", precio = 1200, stock = 15),
tibble::tibble(marca = "Logitech", precio = 25, stock = 100),
tibble::tibble(marca = "Corsair", precio = 80, stock = 45)
)
)
# Ver estructura
print(datos_complejos)
# # A tibble: 3 × 2
# producto especificaciones
# <chr> <list>
# 1 Laptop <tibble [1 × 3]>
# 2 Mouse <tibble [1 × 3]>
# 3 Teclado <tibble [1 × 3]>
# Expandir
productos_expandidos <- datos_complejos %>%
tidyr::unnest_wider(especificaciones)
print(productos_expandidos)
# # A tibble: 3 × 4
# producto marca precio stock
# <chr> <chr> <dbl> <dbl>
# 1 Laptop Dell 1200 15
# 2 Mouse Logitech 25 100
# 3 Teclado Corsair 80 45
Caso de Uso 3: Listas con Diferentes Longitudes
¿Qué pasa cuando no todos los elementos tienen los mismos campos?
# Datos con estructura inconsistente
datos_inconsistentes <- tibble::tibble(
usuario = c("user1", "user2", "user3"),
datos = list(
list(edad = 25, ciudad = "Madrid", profesion = "Ingeniero"),
list(edad = 30, ciudad = "Barcelona"), # Falta 'profesion'
list(edad = 28, profesion = "Doctora") # Falta 'ciudad'
)
)
# Expandir con valores NA para elementos faltantes
usuarios_expandidos <- datos_inconsistentes %>%
tidyr::unnest_wider(datos)
print(usuarios_expandidos)
# # A tibble: 3 × 4
# usuario edad ciudad profesion
# <chr> <dbl> <chr> <chr>
# 1 user1 25 Madrid Ingeniero
# 2 user2 30 Barcelona NA
# 3 user3 28 NA Doctora
Nota importante: tidyr::unnest_wider() automáticamente rellena con NA los valores faltantes.
Parámetros Útiles de tidyr::unnest_wider()
1. names_sep: Añadir Prefijos a Columnas
# Útil cuando tienes múltiples columnas anidadas
datos_multiples <- tibble::tibble(
id = 1:2,
medicion_A = list(
list(valor = 10, unidad = "kg"),
list(valor = 15, unidad = "kg")
),
medicion_B = list(
list(valor = 20, unidad = "m"),
list(valor = 25, unidad = "m")
)
)
# Expandir con separador para evitar conflictos de nombres
datos_con_prefijo <- datos_multiples %>%
tidyr::unnest_wider(medicion_A, names_sep = "_") %>%
tidyr::unnest_wider(medicion_B, names_sep = "_")
print(datos_con_prefijo)
# # A tibble: 2 × 5
# id medicion_A_valor medicion_A_unidad medicion_B_valor medicion_B_unidad
# <int> <dbl> <chr> <dbl> <chr>
# 1 1 10 kg 20 m
# 2 2 15 kg 25 m
2. names_repair: Manejo de Nombres Duplicados
# Datos con posibles nombres duplicados
datos_duplicados <- tibble::tibble(
id = 1:2,
datos = list(
list(x = 1, y = 2, x = 3), # 'x' está duplicado
list(x = 4, y = 5, x = 6)
)
)
# Usar names_repair para resolver conflictos
datos_reparados <- datos_duplicados %>%
tidyr::unnest_wider(datos, names_repair = "unique")
print(datos_reparados)
# Los nombres duplicados se renombran automáticamente (x, x...3)
3. simplify: Control de Simplificación
# Control sobre cómo se simplifican las listas
datos_lista <- tibble::tibble(
grupo = c("A", "B"),
valores = list(
list(a = 1:3, b = 4:6),
list(a = 7:9, b = 10:12)
)
)
# Sin simplificar (mantiene como listas)
sin_simplificar <- datos_lista %>%
tidyr::unnest_wider(valores, simplify = FALSE)
# Con simplificación (intenta convertir a vectores)
con_simplificar <- datos_lista %>%
tidyr::unnest_wider(valores, simplify = TRUE)
Comparación: unnest_wider() vs unnest_longer()
Para entender mejor cuándo usar tidyr::unnest_wider(), veamos la diferencia con tidyr::unnest_longer():
# Datos de ejemplo
datos_comparacion <- tibble::tibble(
id = c(1, 2),
medidas = list(
list(altura = 170, peso = 70),
list(altura = 180, peso = 85)
)
)
# Con unnest_wider (expandir en COLUMNAS)
wider_resultado <- datos_comparacion %>%
tidyr::unnest_wider(medidas)
print(wider_resultado)
# # A tibble: 2 × 3
# id altura peso
# <dbl> <dbl> <dbl>
# 1 1 170 70
# 2 2 180 85
# Con unnest_longer (expandir en FILAS)
longer_resultado <- datos_comparacion %>%
tidyr::unnest_longer(medidas)
print(longer_resultado)
# # A tibble: 4 × 3
# id medidas medidas_id
# <dbl> <dbl> <chr>
# 1 1 170 altura
# 2 1 70 peso
# 3 2 180 altura
# 4 2 85 peso
Regla de oro:
- Usa
tidyr::unnest_wider()cuando quieras cada elemento como columna separada - Usa
tidyr::unnest_longer()cuando quieras cada elemento como fila separada
Caso Práctico: Datos de APIs JSON
Un uso común de tidyr::unnest_wider() es procesar respuestas de APIs que vienen en formato JSON:
# Simular respuesta de API
respuesta_api <- tibble::tibble(
endpoint = c("/users/1", "/users/2", "/users/3"),
respuesta = list(
list(nombre = "Ana García", email = "ana@email.com", activo = TRUE, edad = 28),
list(nombre = "Luis Pérez", email = "luis@email.com", activo = TRUE, edad = 35),
list(nombre = "María López", email = "maria@email.com", activo = FALSE, edad = 42)
)
)
# Procesar respuesta
usuarios_procesados <- respuesta_api %>%
tidyr::unnest_wider(respuesta) %>%
dplyr::mutate(
edad_grupo = dplyr::case_when(
edad < 30 ~ "Joven",
edad < 40 ~ "Adulto",
TRUE ~ "Senior"
)
) %>%
dplyr::select(nombre, email, edad, edad_grupo, activo)
print(usuarios_procesados)
# # A tibble: 3 × 5
# nombre email edad edad_grupo activo
# <chr> <chr> <dbl> <chr> <lgl>
# 1 Ana García ana@email.com 28 Joven TRUE
# 2 Luis Pérez luis@email.com 35 Adulto TRUE
# 3 María López maria@email.com 42 Senior FALSE
Combinando tidyr::unnest_wider() con dplyr
La verdadera potencia viene al combinar con otras funciones del tidyverse:
# Ejemplo completo: análisis de ventas por región
ventas_anidadas <- tibble::tibble(
region = c("Norte", "Sur", "Este"),
metricas = list(
list(ventas = 50000, clientes = 120, ticket_promedio = 416.67),
list(ventas = 35000, clientes = 85, ticket_promedio = 411.76),
list(ventas = 62000, clientes = 145, ticket_promedio = 427.59)
)
)
# Pipeline completo de análisis
analisis_ventas <- ventas_anidadas %>%
tidyr::unnest_wider(metricas) %>%
dplyr::mutate(
categoria = dplyr::case_when(
ventas > 60000 ~ "Alta",
ventas > 40000 ~ "Media",
TRUE ~ "Baja"
),
eficiencia = ventas / clientes
) %>%
dplyr::arrange(dplyr::desc(ventas)) %>%
dplyr::select(region, categoria, ventas, clientes, eficiencia)
print(analisis_ventas)
# # A tibble: 3 × 5
# region categoria ventas clientes eficiencia
# <chr> <chr> <dbl> <dbl> <dbl>
# 1 Este Alta 62000 145 428.
# 2 Norte Media 50000 120 417.
# 3 Sur Baja 35000 85 412.
Errores Comunes y Soluciones
Error 1: Columna No Es una Lista
# ❌ ERROR
datos_mal <- tibble::tibble(
id = 1:3,
valor = c(10, 20, 30) # No es una lista
)
# Esto dará error
# datos_mal %>% tidyr::unnest_wider(valor)
# ✅ SOLUCIÓN: Verificar estructura primero
print(class(datos_mal$valor)) # "numeric", no "list"
Error 2: Nombres de Columnas Conflictivos
# ❌ PROBLEMA
datos_conflicto <- tibble::tibble(
id = 1:2,
nombre = c("A", "B"),
datos = list(
list(nombre = "Ana", edad = 25), # 'nombre' ya existe
list(nombre = "Luis", edad = 30)
)
)
# ✅ SOLUCIÓN 1: Usar names_sep
solucion1 <- datos_conflicto %>%
tidyr::unnest_wider(datos, names_sep = "_")
# ✅ SOLUCIÓN 2: Renombrar antes
solucion2 <- datos_conflicto %>%
dplyr::rename(grupo = nombre) %>%
tidyr::unnest_wider(datos)
Error 3: Elementos No Nombrados
# ❌ PROBLEMA: Lista sin nombres
datos_sin_nombres <- tibble::tibble(
id = 1:2,
valores = list(
c(10, 20, 30), # Sin nombres
c(15, 25, 35)
)
)
# ✅ SOLUCIÓN: Asignar nombres primero
datos_corregidos <- datos_sin_nombres %>%
dplyr::mutate(
valores = purrr::map(valores, ~{
names(.x) <- c("valor1", "valor2", "valor3")
.x
})
) %>%
tidyr::unnest_wider(valores)
Cuándo Usar tidyr::unnest_wider()
Usa tidyr::unnest_wider() cuando:
- ✅ Tienes una columna con listas de elementos nombrados
- ✅ Quieres que cada elemento sea una columna separada
- ✅ Estás procesando respuestas de APIs JSON
- ✅ Necesitas "aplanar" estructuras de datos complejas
- ✅ Los elementos dentro de cada lista representan diferentes atributos del mismo objeto
NO uses tidyr::unnest_wider() cuando:
- ❌ Quieres expandir en filas (usa
tidyr::unnest_longer()) - ❌ Tu columna no contiene listas (no es necesario)
- ❌ Cada lista tiene longitud variable de elementos sin nombres consistentes
Resumen de Funciones Relacionadas
| Función | Propósito | Dirección de Expansión |
|---|---|---|
tidyr::unnest_wider() |
Expandir listas en columnas | Horizontal (→) |
tidyr::unnest_longer() |
Expandir listas en filas | Vertical (↓) |
tidyr::unnest() |
Expandir tibbles anidados | Vertical (↓) |
tidyr::hoist() |
Extraer elementos específicos | Selectiva |
Conclusión
tidyr::unnest_wider() es una herramienta esencial para trabajar con datos complejos y anidados en R. Su capacidad para transformar estructuras de listas en columnas separadas la hace indispensable cuando trabajas con:
- Respuestas de APIs
- Datos JSON
- Resultados de análisis complejos
- Estructuras de datos anidadas
Puntos clave para recordar:
- Expande horizontalmente (en columnas)
- Requiere que la columna sea una lista
- Los elementos deben tener nombres para crear columnas
- Automáticamente maneja valores faltantes con NA
- Se combina perfectamente con dplyr para análisis completos
Dominar tidyr::unnest_wider() te permitirá trabajar con datos complejos de manera eficiente y mantener tu código limpio y legible dentro del ecosistema tidyverse.
Para más información sobre manipulación de datos anidados, consulta la documentación oficial de tidyr: ?tidyr::unnest_wider

Comentarios
Publicar un comentario