¿Cómo extraer nombres y valores de listas?



Si vienes del mundo de Python y estás comenzando a trabajar con R para análisis de datos deportivos, una de las primeras dificultades que encontrarás es cómo manejar estructuras de datos similares a los diccionarios de Python. En R, estas estructuras se llaman listas con nombres (named lists), y trabajar con ellas requiere entender algunas funciones clave.

Esta guía te mostrará cómo extraer información de listas en R usando la notación completa de paquetes, fundamental para escribir código reproducible y profesional.


📚 Traductor: terminología Python → R

Antes de comenzar, aquí está tu "diccionario de traducción" para entender las equivalencias:

Concepto Python Equivalente R Función/Sintaxis Explicación
dict list() con nombres list(a=1, b=2) Lista con elementos nombrados
dict.keys() names() base::names(lista) Extraer nombres/llaves
dict.values() unlist() base::unlist(lista) Extraer valores (mismo tipo)
dict.items() tibble::enframe() tibble::enframe(lista) Crear pares nombre-valor
dict['key'] lista[["key"]] lista[["nombre"]] Acceso directo a elemento
list(dict.items()) tibble::tibble() tibble::tibble(names, values) Convertir a tabla

Nota sobre namespaces: siempre es recomendable agregar el prefijo de namespace (o prefijo de paquete) a todas las funciones, usando la notación paquete::funcion() para:

  • Evitar conflictos entre funciones con el mismo nombre
  • Hacer el código más claro y autodocumentado
  • Facilitar la reproducibilidad

🎯 Caso 1: extraer solo los nombres

Cuando solo necesitas las "llaves" de tu estructura (equivalente a dict.keys() en Python), usa base::names():

Sintaxis básica

# Cargar librería (aunque names() es de base, es buena práctica)
library(tibble)

# Tu lista (similar a un diccionario de Python)
mi_lista <- list(
  nombre = "Juan",
  edad = 30,
  ciudad = "Madrid",
  puntuacion = 85.5
)

# Extraer solo los nombres usando función de base R
nombres <- base::names(mi_lista)
print(nombres)
# [1] "nombre"     "edad"       "ciudad"     "puntuacion"

¿Qué devuelve?

Tipo de dato: Vector de caracteres (character vector)

Características:

  • Es un vector simple, no una lista
  • Mantiene el orden original de la lista
  • Útil para iteraciones o validaciones

Ejemplo práctico: validación de campos

# Lista con datos de un partido de fútbol
datos_partido <- list(
  match_id = 3399385,
  fecha = "2020-12-04",
  equipo_local = "Bayern Munich",
  equipo_visitante = "Borussia Dortmund",
  goles_local = 3,
  goles_visitante = 2
)

# Campos requeridos para nuestro análisis
campos_requeridos <- c("match_id", "fecha", "equipo_local", "equipo_visitante")

# Verificar que todos los campos existen
campos_disponibles <- base::names(datos_partido)
campos_faltantes <- base::setdiff(campos_requeridos, campos_disponibles)

if (base::length(campos_faltantes) == 0) {
  print("✅ Todos los campos requeridos están presentes")
} else {
  print(base::paste("❌ Faltan campos:", base::paste(campos_faltantes, collapse = ", ")))
}

🎯 Caso 2: crear pares nombre-valor con tibble::tibble()

Cuando necesitas una estructura tabular con pares nombre-valor (equivalente a pd.DataFrame(dict.items()) en pandas), tienes dos métodos principales:

Método 1: Manual con base::names() + base::unlist()

library(tibble)

mi_lista <- list(
  nombre = "Juan",
  edad = 30,
  ciudad = "Madrid",
  puntuacion = 85.5
)

# Crear tibble manualmente
pares_nv <- tibble::tibble(
  nombre = base::names(mi_lista),
  valor = base::unlist(mi_lista, use.names = FALSE)
)

print(pares_nv)
# # A tibble: 4 × 2
#   nombre     valor 
#   <chr>      <chr> 
# 1 nombre     Juan  
# 2 edad       30    
# 3 ciudad     Madrid
# 4 puntuacion 85.5

Ventajas:

  • Control total sobre el proceso
  • No requiere paquetes adicionales (además de tibble)
  • Fácil de entender para principiantes

Desventajas:

  • base::unlist() convierte todo a un solo tipo (generalmente character)
  • Pierdes información de tipos de datos originales
  • Más verboso

Método 2: Usando tibble::enframe() (Recomendado)

library(tibble)
library(tidyr)

mi_lista <- list(
  nombre = "Juan",
  edad = 30,
  ciudad = "Madrid",
  puntuacion = 85.5
)

# enframe() convierte directamente lista a tibble
pares_nv <- mi_lista |>
  tibble::enframe(name = "campo", value = "valor") |>
  tidyr::unnest(valor)

print(pares_nv)
# # A tibble: 4 × 2
#   campo      valor 
#   <chr>      <chr> 
# 1 nombre     Juan  
# 2 edad       30    
# 3 ciudad     Madrid
# 4 puntuacion 85.5

Ventajas:

  • Más idiomático y legible en R moderno
  • Maneja mejor listas anidadas
  • Se integra perfectamente con el tidyverse
  • Menos código

Desventajas:

  • Requiere dos paquetes (tibble + tidyr)
  • Puede ser menos intuitivo para principiantes

🛠️ Ejemplos prácticos con datos deportivos

Ejemplo 1: Procesar datos de un partido Individual

library(tibble)
library(dplyr)
library(tidyr)

# Datos típicos que podrías recibir de una API de fútbol
datos_partido <- list(
  match_id = 3399385,
  fecha = "2020-12-04",
  equipo_local = "Bayern Munich",
  equipo_visitante = "Borussia Dortmund",
  goles_local = 3,
  goles_visitante = 2,
  estadio = "Allianz Arena",
  asistencia = 75000
)

# Método 1: Solo extraer nombres de campos
nombres_campos <- base::names(datos_partido)
print("📋 Campos disponibles:")
print(nombres_campos)

# Método 2: Crear tabla de metadatos
tabla_metadatos <- datos_partido |>
  tibble::enframe(name = "campo", value = "valor") |>
  tidyr::unnest(valor) |>
  dplyr::mutate(
    tipo = base::class(valor),
    longitud = base::nchar(base::as.character(valor))
  )

print(tabla_metadatos)
# # A tibble: 8 × 4
#   campo             valor              tipo      longitud
#   <chr>             <chr>              <chr>        <int>
# 1 match_id          3399385            numeric          7
# 2 fecha             2020-12-04         character       10
# 3 equipo_local      Bayern Munich      character       13
# 4 equipo_visitante  Borussia Dortmund  character       17
# ...

Ejemplo 2: Procesar múltiples partidos

library(tibble)
library(purrr)
library(dplyr)

# Lista de partidos (estructura típica de API)
lista_partidos <- list(
  partido1 = list(
    match_id = 123, 
    goles = 3, 
    equipo = "Barcelona",
    fecha = "2024-01-15"
  ),
  partido2 = list(
    match_id = 124, 
    goles = 1, 
    equipo = "Real Madrid",
    fecha = "2024-01-16"
  ),
  partido3 = list(
    match_id = 125, 
    goles = 2, 
    equipo = "Atlético Madrid",
    fecha = "2024-01-17"
  )
)

# Opción 1: Extraer solo identificadores de partidos
nombres_partidos <- base::names(lista_partidos)
print("🏆 Identificadores de partidos:")
print(nombres_partidos)
# [1] "partido1" "partido2" "partido3"

# Opción 2: Crear tabla completa con todos los datos
tabla_partidos <- lista_partidos |>
  purrr::map_dfr(
    ~ tibble::tibble(
      match_id = .x$match_id,
      goles = .x$goles,
      equipo = .x$equipo,
      fecha = .x$fecha
    ),
    .id = "partido_key"
  )

print(tabla_partidos)
# # A tibble: 3 × 5
#   partido_key match_id goles equipo          fecha     
#   <chr>          <dbl> <dbl> <chr>           <chr>     
# 1 partido1         123     3 Barcelona       2024-01-15
# 2 partido2         124     1 Real Madrid     2024-01-16
# 3 partido3         125     2 Atlético Madrid 2024-01-17

# Opción 3: Extraer solo match_ids (más eficiente)
match_ids <- lista_partidos |>
  purrr::map_dbl("match_id")  # map_dbl garantiza tipo numérico

print(match_ids)
# partido1 partido2 partido3 
#      123      124      125

Ejemplo 3: Trabajar con datos anidados de API

library(tibble)
library(tidyr)
library(dplyr)
library(purrr)

# Estructura compleja típica de respuesta de API
respuesta_api <- list(
  metadata = list(
    request_id = "abc123",
    timestamp = "2024-10-22T10:30:00Z",
    endpoint = "/api/matches"
  ),
  data = list(
    match_id = 3399385,
    teams = list(
      home = "Bayern Munich",
      away = "Borussia Dortmund"
    ),
    score = list(
      home = 3,
      away = 2
    )
  )
)

# Extraer estructura de metadatos
estructura_metadata <- respuesta_api$metadata |>
  tibble::enframe(name = "campo_metadata", value = "valor_metadata") |>
  tidyr::unnest(valor_metadata)

print("📊 Metadatos de la API:")
print(estructura_metadata)

# Extraer datos del partido
datos_partido_limpio <- tibble::tibble(
  match_id = respuesta_api$data$match_id,
  equipo_local = respuesta_api$data$teams$home,
  equipo_visitante = respuesta_api$data$teams$away,
  goles_local = respuesta_api$data$score$home,
  goles_visitante = respuesta_api$data$score$away
)

print("⚽ Datos del partido:")
print(datos_partido_limpio)

⚠️ Consideraciones importantes y errores comunes

1. El Problema de base::unlist() con tipos de datos

library(tibble)

# Lista con diferentes tipos de datos
datos_mixtos <- list(
  nombre = "Leo Messi",
  edad = 37,
  activo = TRUE,
  goles_temporada = 25
)

# ❌ MAL: Usar unlist() sin precaución
valores_unlist <- base::unlist(datos_mixtos)
print(valores_unlist)
#      nombre         edad       activo goles_temporada 
# "Leo Messi"         "37"       "TRUE"           "25"
# ☝️ Todo se convirtió a character!

# ✅ BIEN: Usar enframe() + unnest() preserva tipos
valores_correcto <- datos_mixtos |>
  tibble::enframe(name = "campo", value = "valor") |>
  tidyr::unnest(valor)

print(valores_correcto)
# # A tibble: 4 × 2
#   campo           valor    
#   <chr>           <list>   
# 1 nombre          <chr [1]>
# 2 edad            <dbl [1]>
# 3 activo          <lgl [1]>
# 4 goles_temporada <dbl [1]>

2. Listas anidadas requieren estrategia diferente

library(tibble)
library(tidyr)
library(purrr)

# Lista con estructura anidada
equipo_completo <- list(
  equipo = "Barcelona",
  jugadores = list(
    delantero = "Lewandowski",
    mediocampista = "Gundogan"
  ),
  estadisticas = list(
    goles = 85,
    asistencias = 42
  )
)

# ❌ MAL: enframe() simple no maneja bien la anidación
intento_simple <- equipo_completo |>
  tibble::enframe()
# Resultado: Columna "value" sigue siendo lista

# ✅ BIEN: Necesitas unnest() más profundo o flatten
datos_aplanados <- equipo_completo |>
  purrr::list_flatten() |>  # Aplana un nivel
  tibble::enframe(name = "campo", value = "valor") |>
  tidyr::unnest(valor)

print(datos_aplanados)

3. Performance: tibble::tibble() vs base::data.frame()

library(tibble)
library(microbenchmark)

# Comparar rendimiento
mi_lista_grande <- base::as.list(base::setNames(1:10000, base::paste0("var", 1:10000)))

comparacion <- microbenchmark::microbenchmark(
  con_tibble = {
    tibble::tibble(
      nombre = base::names(mi_lista_grande),
      valor = base::unlist(mi_lista_grande, use.names = FALSE)
    )
  },
  con_dataframe = {
    base::data.frame(
      nombre = base::names(mi_lista_grande),
      valor = base::unlist(mi_lista_grande, use.names = FALSE)
    )
  },
  times = 100
)

print(comparacion)
# tibble() es generalmente 2-3x más rápido para datos grandes

4. Manejo de valores NULL

library(tibble)
library(tidyr)
library(purrr)

# Lista con algunos valores NULL (común en APIs)
datos_incompletos <- list(
  match_id = 123,
  goles_local = 3,
  goles_visitante = NULL,  # Dato faltante
  asistencia = 50000,
  arbitro = NULL  # Dato faltante
)

# ❌ MAL: unlist() elimina los NULL sin aviso
valores_unlist <- base::unlist(datos_incompletos)
print(base::length(valores_unlist))  # Solo 3 elementos (perdiste 2)

# ✅ BIEN: enframe() + unnest() con keep_empty
valores_completo <- datos_incompletos |>
  tibble::enframe(name = "campo", value = "valor") |>
  tidyr::unnest(valor, keep_empty = TRUE)

print(valores_completo)
# # A tibble: 5 × 2
#   campo            valor    
#   <chr>            <list>   
# 1 match_id         <dbl [1]>
# 2 goles_local      <dbl [1]>
# 3 goles_visitante  <NULL>   ← Se preserva
# 4 asistencia       <dbl [1]>
# 5 arbitro          <NULL>   ← Se preserva

📊 Comparación de métodos: ¿cuál usar?

Método Velocidad Preserva Tipos Maneja Anidación Complejidad Código Uso Recomendado
base::names() ⚡⚡⚡ N/A N/A Muy Simple Solo necesitas nombres
base::unlist() ⚡⚡⚡ Limitada Simple Datos homogéneos simples
tibble::tibble() manual ⚡⚡ No Moderada Control total del proceso
tibble::enframe() ⚡⚡ Simple Recomendado para la mayoría de casos
purrr::map_dfr() Alta Listas de listas complejas

🎓 Para profundizar

Si quieres dominar completamente el manejo de listas en R, estos son los conceptos avanzados a estudiar:

  1. purrr::pluck(): Extracción segura de elementos anidados
  2. purrr::keep() / purrr::discard(): Filtrado de listas
  3. purrr::list_modify(): Modificación de listas
  4. rlang::list2(): Creación dinámica de listas nombradas
  5. jsonlite::fromJSON(): Conversión JSON → listas R

Conclusión: La transición de diccionarios Python a listas R es más suave de lo que parece. La clave está en entender que tibble::enframe() es tu mejor amigo para la mayoría de casos, y siempre usar la notación paquete::funcion() para código profesional y reproducible.

¿Trabajas con APIs deportivas en R? Estos patrones te ahorrarán horas de frustración. ¡Guarda este artículo como referencia!

Comentarios

Entradas más populares de este blog

¿Cómo comparar nombres de equipos de distintas fuentes?

¿Cómo hacemos filtrado y extracción de listas anidadas utilizando `purrr`?