Desplegando columnas anidadas — listas usando el lenguaje R (paquete tidyr y funciones de la familia unnest)

En la mayoría de los casos, al trabajar con la respuesta obtenida de una API o con cualquier otro dato que tenga una estructura jerárquica compleja, se encuentra con los formatos JSON y XML.

Estos formatos ofrecen varias ventajas: almacenan los datos de manera compacta y permiten evitar la duplicación excesiva de información.

Un inconveniente de estos formatos es la complejidad en su procesamiento y análisis. Los datos no estructurados no se pueden utilizar en cálculos y no se puede construir una visualización a partir de ellos.

Desplegando columnas anidadas — listas usando el lenguaje R (paquete tidyr y funciones de la familia unnest)

Este artículo es una continuación lógica de la publicación "El paquete R tidyr y sus nuevas funciones pivot_longer y pivot_wider". Ayudará a convertir las construcciones de datos no estructurados a un formato tabular familiar y adecuado para el análisis utilizando el paquete tidyr, que forma parte del núcleo de la biblioteca tidyverse, y sus funciones de la familia unnest_*().

Contenido

canales de youtube. La mayor parte del contenido está dedicado al lenguaje R. telegram y Proceso de construcción del bot Estructura del proyecto del bot

  1. Introducción
  2. Usuarios de GitHub
  3. Repositorios de Github
  4. Personajes de Juego de Tronos
  5. Geocodificación con Google
  6. Discografía de Sharla Helfand
  7. Conclusión

Introducción

Rectangulando (nota del traductor: no encontré versiones adecuadas para traducir este término, así que lo dejaremos tal cual.) — es el proceso de convertir datos no estructurados con arreglos anidados en una tabla bidimensional que consta de filas y columnas familiares. En tidyr hay varias funciones que te ayudarán a desplegar columnas en listas y transformar los datos a una forma rectangular y tabular:

  • unnest_longer() toma cada elemento de la lista-columna y crea una nueva fila.
  • unnest_wider() toma cada elemento de la lista-columna y crea una nueva columna.
  • unnest_auto() determina automáticamente cuál de las funciones es mejor utilizar.
    unnest_longer() o unnest_wider().
  • hoist() es similar a unnest_wider() pero selecciona solo los componentes especificados y permite trabajar con múltiples niveles de anidamiento.

La mayoría de los problemas relacionados con la conversión de datos no estructurados con varios niveles de anidación a una tabla bidimensional se pueden resolver combinando las funciones mencionadas con dplyr.

Para demostrar estos métodos, utilizaremos el paquete repurrrsive, que proporciona varias listas complejas y multinivel obtenidas de una API web.

library(tidyr)
library(dplyr)
library(repurrrsive)

Usuarios de GitHub

Comencemos con gh_users, una lista que contiene información sobre seis usuarios de GitHub. Primero, transformemos la lista gh_users en tibble data.frame:

users <- tibble(user = gh_users) 

Parece un poco ilógico: ¿por qué proporcionar una lista gh_users, a una estructura de datos más compleja? Pero el dataframe tiene una gran ventaja: combina varios vectores, por lo que todo se rastrea en un solo objeto.

Cada elemento del objeto usuarios es una lista nombrada donde cada elemento representa una columna.

names(users$user[[1]])
#<  [1] "login"               "id"                  "avatar_url"         
#<  [4] "gravatar_id"         "url"                 "html_url"           
#<  [7] "followers_url"       "following_url"       "gists_url"          
#< [10] "starred_url"         "subscriptions_url"   "organizations_url"  
#< [13] "repos_url"           "events_url"          "received_events_url"
#< [16] "type"                "site_admin"          "name"               
#< [19] "company"             "blog"                "location"           
#< [22] "email"               "hireable"            "bio"                
#< [25] "public_repos"        "public_gists"        "followers"          
#< [28] "following"           "created_at"          "updated_at"

Hay dos maneras de convertir componentes de la lista en columnas. unnest_wider() toma cada componente y crea una nueva columna:

users %>% unnest_wider(user)
#< # A tibble: 6 x 30
#<   login     id avatar_url gravatar_id url   html_url followers_url
#<                                
#< 1 gabo… 6.60e5 https://a… ""          http… https:/… https://api.…
#< 2 jenn… 5.99e5 https://a… ""          http… https:/… https://api.…
#< 3 jtle… 1.57e6 https://a… ""          http… https:/… https://api.…
#< 4 juli… 1.25e7 https://a… ""          http… https:/… https://api.…
#< 5 leep… 3.51e6 https://a… ""          http… https:/… https://api.…
#< 6 masa… 8.36e6 https://a… ""          http… https:/… https://api.…
#< # … con 23 variables más: following_url , gists_url ,
#< #   starred_url , subscriptions_url , organizations_url ,
#< #   repos_url , events_url , received_events_url ,
#< #   type , site_admin , name , company , blog ,
#< #   location , email , public_repos , public_gists ,
#< #   followers , following , created_at , updated_at ,
#< #   bio , hireable

En este caso, hemos obtenido una tabla de 30 columnas, y la mayoría de ellas no las necesitaremos, por lo que podemos utilizar unnest_wider() utilizar hoist(). hoist() nos permite extraer componentes seleccionados usando la misma sintaxis que purrr::pluck():

usuarios %>%, levantar(usuario, 
  seguidores = "seguidores", 
  inicio_sesion = "login", 
  url = "html_url"
)
#> # A tibble: 6 x 4
#>   seguidores inicio_sesion url                            usuario             
#>                                                    
#> 1       303 gaborcsardi https://github.com/gaborcsardi 
#> 2       780 jennybc     https://github.com/jennybc     
#> 3      3958 jtleek      https://github.com/jtleek      
#> 4       115 juliasilge  https://github.com/juliasilge  
#> 5       213 leeper      https://github.com/leeper      
#> 6        34 masalmon    https://github.com/masalmon

hoist() elimina los componentes nombrados especificados de la lista-columna usuario, por lo que puedes considerar hoist() como mover componentes de la lista interna del marco de datos a su nivel superior.

Repositorios de Github

Alineación de la lista gh_repos comenzamos de manera similar, transformándola en tibble:

repos  # A tibble: 6 x 1
#>   repo       
#>        
#> 1 
#> 2 
#> 3 
#> 4 
#> 5 
#> 6

En esta ocasión, los elementos usuario representan una lista de repositorios que pertenecen a este usuario. Cada repositorio es una observación separada, por lo que de acuerdo con el concepto de datos ordenados (nota. tidy data) deben convertirse en nuevas filas, por lo que utilizamos unnest_longer() en vez de unnest_wider():

repos % unnest_longer(repo)
repos
#> # A tibble: 176 x 1
#>    repo             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … con 166 filas más

Ahora podemos utilizar unnest_wider() o hoist() :

repos %>% levantar(repo, 
  inicio_sesion = c("propietario", "login"), 
  nombre = "name",
  pagina_principal = "homepage",
  observadores = "watchers_count"
)
#> # A tibble: 176 x 5
#>    inicio_sesion nombre      pagina_principal observadores repo             
#>                                     
#>  1 gaborcsardi after                   5 
#>  2 gaborcsardi argufy                 19 
#>  3 gaborcsardi ask                     5 
#>  4 gaborcsardi baseimports             0 
#>  5 gaborcsardi citest                  0 
#>  6 gaborcsardi clisymbols  ""             18 
#>  7 gaborcsardi cmaker                  0 
#>  8 gaborcsardi cmark                   0 
#>  9 gaborcsardi conditions              0 
#> 10 gaborcsardi crayon                 52 
#> # … con 166 filas más

Presta atención a la utilización de c("propietario", "login"): esto nos permite obtener el valor de segundo nivel de una lista anidada propietarioUn enfoque alternativo es obtener toda la lista propietario y luego utilizar la función unnest_wider() para colocar cada uno de sus elementos en una columna:

repos %>% 
  hoist(repo, owner = "owner") %>% 
  unnest_wider(owner)
#> # A tibble: 176 x 18
#>    login     id avatar_url gravatar_id url   html_url followers_url
#>                                 
#>  1 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  2 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  3 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  4 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  5 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  6 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  7 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  8 gabo… 660288 https://a… ""          http… https:… https://api.…
#>  9 gabo… 660288 https://a… ""          http… https:… https://api.…
#> 10 gabo… 660288 https://a… ""          http… https:… https://api.…
#> # … con 166 filas más, y 11 variables más: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

En lugar de reflexionar sobre la elección de la función adecuada unnest_longer() o unnest_wider() puede utilizar unnest_auto(). Esta función utiliza varios métodos heurísticos para seleccionar la función más adecuada para transformar los datos, y muestra un mensaje sobre el método seleccionado.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Usando `unnest_longer(repo)`; ningún elemento tiene nombres
#> Usando `unnest_wider(repo)`; los elementos tienen 68 nombres en común
#> # Un tibble: 176 x 67
#>        id name  full_name owner private html_url description fork  url  
#>                            
#>  1 6.12e7 después gaborcsa…   2 4.05e7 argu… gaborcsa…   3 3.64e7 pregunta gaborcsa…   4 3.49e7 base… gaborcsa…   5 6.16e7 cita… gaborcsa…   6 3.39e7 clis… gaborcsa…   7 3.72e7 cmak… gaborcsa…   8 6.80e7 cmark gaborcsa…   9 6.32e7 cond… gaborcsa… <nam… FALSE   https:/…         TRUE  http…
#> 10 2.43e7 cray… gaborcsa…  # … con 166 filas más, y 58 variables más: forks_url ,
#> #   keys_url , collaborators_url , teams_url ,
#> #   hooks_url , issue_events_url , events_url ,
#> #   assignees_url , branches_url , tags_url ,
#> #   blobs_url , git_tags_url , git_refs_url ,
#> #   trees_url , statuses_url , languages_url ,
#> #   stargazers_url , contributors_url , subscribers_url ,
#> #   subscription_url , commits_url , git_commits_url ,
#> #   comments_url , issue_comment_url , contents_url ,
#> #   compare_url , merges_url , archive_url ,
#> #   downloads_url , issues_url , pulls_url ,
#> #   milestones_url , notifications_url , labels_url ,
#> #   releases_url , deployments_url , created_at ,
#> #   updated_at , pushed_at , git_url , ssh_url ,
#> #   clone_url , svn_url , size , stargazers_count ,
#> #   watchers_count , language , has_issues ,
#> #   has_downloads , has_wiki , has_pages ,
#> #   forks_count , open_issues_count , forks ,
#> #   open_issues , watchers , default_branch ,
#> #   homepage

Personajes de Juego de Tronos

got_chars tiene una estructura idéntica a gh_users: es un conjunto de listas nombradas, donde cada elemento de la lista interna describe algún atributo del personaje de Juego de Tronos. La transformación got_chars a formato tabular comienza creando un marco de datos, al igual que en los ejemplos anteriores, y luego convertimos cada elemento en una columna separada:

chars  # A tibble: 30 x 1
#>    char             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … with 20 more rows

chars2 % unnest_wider(char)
chars2
#> # A tibble: 30 x 18
#>    url      id name  gender culture born  died  alive titles aliases father
#>     <int> <chr> <chr>  <chr>   <chr> <chr> <lgl> <list> <list>  <chr> 
#>  1 http…  1022 Theo… Male   Ironbo… In 2… ""    TRUE  <chr … <chr [… ""    
#>  2 http…  1052 Tyri… Male   ""      In 2… ""    TRUE  <chr … <chr [… ""    
#>  3 http…  1074 Vict… Male   Ironbo… In 2… ""    TRUE  <chr … <chr [… ""    
#>  4 http…  1109 Will  Male   ""      ""    In 2… FALSE <chr … <chr [… ""    
#>  5 http…  1166 Areo… Male   Norvos… In 2… ""    TRUE  <chr … <chr [… ""    
#>  6 http…  1267 Chett Male   ""      At H… In 2… FALSE <chr … <chr [… ""    
#>  7 http…  1295 Cres… Male   ""      In 2… In 2… FALSE <chr … <chr [… ""    
#>  8 http…   130 Aria… Female Dornish In 2… ""    TRUE  <chr … <chr [… ""    
#>  9 http…  1303 Daen… Female Valyri… In 2… ""    TRUE  <chr … <chr [… ""    
#> 10 http…  1319 Davo… Male   Wester… In 2… ""    TRUE  <chr … <chr [… ""    
#> # … with 20 more rows, and 7 more variables: mother <chr>, spouse <chr>,
#> #   allegiances <list>, books <list>, povBooks <list>, tvSeries <list>,
#> #   playedBy <list>

Estructura got_chars algo más complicado que gh_users, ya que algunos componentes de la lista char en sí mismos son listas, lo que resulta en columnas — listas:

chars2 %>% select_if(is.list)
#> # A tibble: 30 x 7
#>    titles    aliases    allegiances books     povBooks  tvSeries  playedBy 
#>                                  
#>  1          
#>  2         
#>  3          
#>  4          
#>  5          
#>  6          
#>  7          
#>  8          
#>  9         
#> 10          
#> # … con 20 filas más

Sus acciones posteriores dependen de los objetivos del análisis. Puede que necesite colocar en filas información sobre cada libro y serie en la que aparece el personaje:

chars2 %>% 
  select(name, books, tvSeries) %>% 
  pivot_longer(c(books, tvSeries), names_to = "media", values_to = "value") %>% 
  unnest_longer(value)
#> # A tibble: 180 x 3
#>    name             media    value            
#>                                
#>  1 Theon Greyjoy    books    A Game of Thrones
#>  2 Theon Greyjoy    books    A Storm of Swords
#>  3 Theon Greyjoy    books    A Feast for Crows
#>  4 Theon Greyjoy    tvSeries Season 1         
#>  5 Theon Greyjoy    tvSeries Season 2         
#>  6 Theon Greyjoy    tvSeries Season 3         
#>  7 Theon Greyjoy    tvSeries Season 4         
#>  8 Theon Greyjoy    tvSeries Season 5         
#>  9 Theon Greyjoy    tvSeries Season 6         
#> 10 Tyrion Lannister books    A Feast for Crows
#> # … con 170 filas más

O tal vez desee crear una tabla que le permita relacionar el personaje con la obra:

chars2 %>% 
  select(name, title = titles) %>% 
  unnest_longer(title)
#> # A tibble: 60 x 2
#>    name              title                                               
#>                                                                
#>  1 Theon Greyjoy     Prince of Winterfell                                
#>  2 Theon Greyjoy     Captain of Sea Bitch                                
#>  3 Theon Greyjoy     Lord of the Iron Islands (by law of the green lands)
#>  4 Tyrion Lannister  Acting Hand of the King (former)                    
#>  5 Tyrion Lannister  Master of Coin (former)                             
#>  6 Victarion Greyjoy Lord Captain of the Iron Fleet                      
#>  7 Victarion Greyjoy Master of the Iron Victory                          
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Captain of the Guard at Sunspear                    
#> 10 Chett             ""                                                  
#> # … con 50 filas más

(Tenga en cuenta que los valores vacíos "" en el campo title, esto se debe a errores cometidos al ingresar datos en got_chars: de hecho, los personajes que no tienen títulos relacionados con libros y series en el campo title deben tener un vector de longitud 0, no un vector de longitud 1 que contenga una cadena vacía.)

Podemos reescribir el ejemplo anterior usando la función unnest_auto(). Este enfoque es conveniente para un análisis puntual, pero no debe confiar en unnest_auto() para un uso regular. La cuestión es que si su estructura de datos cambia unnest_auto() puede cambiar el mecanismo de transformación de datos seleccionado, si originalmente desplegó columnas-listas en filas usando unnest_longer(), entonces, con un cambio en la estructura de los datos de entrada, la lógica puede cambiar en favor de unnest_wider(), y utilizar este enfoque de forma continua puede llevar a errores imprevistos.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> Usando `unnest_wider(char)`; los elementos tienen 18 nombres en común
#> Usando `unnest_longer(title)`; ningún elemento tiene nombres
#> # Un tibble: 60 x 2
#>    name              title                                                
#>                                                                 
#>  1 Theon Greyjoy     Príncipe de Invernalia                              
#>  2 Theon Greyjoy     Capitán de la Bitch del Mar                        
#>  3 Theon Greyjoy     Señor de las Islas de Hierro (por ley de las tierras verdes)
#>  4 Tyrion Lannister  Mano Actuante del Rey (anterior)                    
#>  5 Tyrion Lannister  Maestro de la Moneda (anteriormente)                
#>  6 Victarion Greyjoy Capitán Maestro de la Flota de Hierro               
#>  7 Victarion Greyjoy Maestro del Hierro Victoria                           
#>  8 Will              ""                                                 
#>  9 Areo Hotah        Capitán de la Guardia en Dorne                       
#> 10 Chett             ""                                                 
#> # … con 50 filas más

Geocodificación con Google

A continuación, analizaremos una estructura de datos más compleja obtenida del servicio de geocodificación de Google. El almacenamiento en caché de credenciales contradice los términos de uso de la API de Google Maps, por lo que primero escribiré una simple envoltura para la API. Esta se basa en almacenar la clave de la API de Google Maps en una variable de entorno; si no tiene guardada en las variables de entorno la clave para trabajar con la API de Google Maps, los fragmentos de código presentados en esta sección no se ejecutarán.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("No se encontró la clave de API de Google Maps; los fragmentos de código no se ejecutarán")
}

# https://developers.google.com/maps/documentation/geocoding
geocode <- function(address, api_key = Sys.getenv("GOOGLE_MAPS_API_KEY")) {
  url <- "https://maps.googleapis.com/maps/api/geocode/json"
  url <- paste0(url, "?address=", URLencode(address), "&key=", api_key)

  jsonlite::read_json(url)
}

La lista que devuelve esta función es bastante compleja:

houston  List of 2
#>  $ results:List of 1
#>   ..$ :List of 5
#>   .. ..$ address_components:List of 4
#>   .. .. ..$ :List of 3
#>   .. .. .. ..$ long_name : chr "Houston"
#>   .. .. .. ..$ short_name: chr "Houston"
#>   .. .. .. ..$ types     :List of 2
#>   .. .. .. .. ..$ : chr "locality"
#>   .. .. .. .. ..$ : chr "political"
#>   .. .. ..$ :List of 3
#>   .. .. .. ..$ long_name : chr "Harris County"
#>   .. .. .. ..$ short_name: chr "Harris County"
#>   .. .. .. ..$ types     :List of 2
#>   .. .. .. .. ..$ : chr "administrative_area_level_2"
#>   .. .. .. .. ..$ : chr "political"
#>   .. .. ..$ :List of 3
#>   .. .. .. ..$ long_name : chr "Texas"
#>   .. .. .. ..$ short_name: chr "TX"
#>   .. .. .. ..$ types     :List of 2
#>   .. .. .. .. ..$ : chr "administrative_area_level_1"
#>   .. .. .. .. ..$ : chr "political"
#>   .. .. ..$ :List of 3
#>   .. .. .. ..$ long_name : chr "United States"
#>   .. .. .. ..$ short_name: chr "US"
#>   .. .. .. ..$ types     :List of 2
#>   .. .. .. .. ..$ : chr "country"
#>   .. .. .. .. ..$ : chr "political"
#>   .. ..$ formatted_address : chr "Houston, TX, USA"
#>   .. ..$ geometry          :List of 4
#>   .. .. ..$ bounds       :List of 2
#>   .. .. .. ..$ northeast:List of 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ southwest:List of 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. .. ..$ location     :List of 2
#>   .. .. .. ..$ lat: num 29.8
#>   .. .. .. ..$ lng: num -95.4
#>   .. .. ..$ location_type: chr "APPROXIMATE"
#>   .. .. ..$ viewport     :List of 2
#>   .. .. .. ..$ northeast:List of 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ southwest:List of 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. ..$ place_id          : chr "ChIJAYWNSLS4QIYROwVl894CDco"
#>   .. ..$ types             :List of 2
#>   .. .. ..$ : chr "locality"
#>   .. .. ..$ : chr "political"
#>  $ status : chr "OK"

Afortunadamente, podemos resolver el problema de transformar estos datos en formato tabular paso a paso utilizando funciones. tidyrPara hacer la tarea un poco más compleja y realista, comenzaré con la geocodificación de varias ciudades:

  city <- c("Houston", "LA", "New York", "Chicago", "Springfield") city_geo <- purrr::map(city, geocode) 

Convertiré el resultado en tibble, y para mayor comodidad, añadiré una columna con el nombre de la ciudad correspondiente.

loc  # A tibble: 5 x 2
#>   city        json            
#>                    
#> 1 Houston     
#> 2 LA          
#> 3 New York    
#> 4 Chicago     
#> 5 Springfield

El primer nivel contiene componentes status y resultado, que podemos expandir usando unnest_wider() :

loc %>%
  unnest_wider(json)
#> # A tibble: 5 x 3
#>   city        results    status
#>                
#> 1 Houston      OK    
#> 2 LA           OK    
#> 3 New York     OK    
#> 4 Chicago      OK    
#> 5 Springfield  OK

Tenga en cuenta que results es una lista jerárquica. La mayoría de las ciudades tiene 1 elemento (representando un valor único correspondiente a la API de geocodificación), pero Springfield tiene dos. Podemos extraerlos en filas separadas usando unnest_longer() :

loc %>%
  unnest_wider(json) %>% 
  unnest_longer(results)
#> # A tibble: 5 x 3
#>   city        results          status
#>                      
#> 1 Houston      OK    
#> 2 LA           OK    
#> 3 New York     OK    
#> 4 Chicago      OK    
#> 5 Springfield  OK

Ahora todos ellos tienen componentes idénticos, lo cual se puede verificar con unnest_wider():

loc %>%
  unnest_wider(json) %>% 
  unnest_longer(results) %>% 
  unnest_wider(results)
#> # A tibble: 5 x 7
#>   city   address_componen… formatted_addre… geometry place_id  types status
#>                                       
#> 1 Houst…         Houston, TX, USA <named … ChIJAYWN…  OK    
#> 2 LA             Los Angeles, CA… <named … ChIJE9on…  OK    
#> 3 New Y…         New York, NY, U… <named … ChIJOwg_…  OK    
#> 4 Chica…         Chicago, IL, USA <named … ChIJ7cv0…  OK    
#> 5 Sprin…         Springfield, MO… <named … ChIJP5jI…  OK

Podemos encontrar las coordenadas de latitud y longitud de cada ciudad expandiendo la lista geometry:

loc %>%
  unnest_wider(json) %>% 
  unnest_longer(results) %>% 
  unnest_wider(results) %>% 
  unnest_wider(geometry)
#> # A tibble: 5 x 10
#>   city  address_compone… formatted_addre… bounds location location_type
#>                                       
#> 1 Hous…        Houston, TX, USA <name…  2 LA           Los Angeles, CA… <name…  3 New …        New York, NY, U… <name…  4 Chic…        Chicago, IL, USA <name…  5 Spri…        Springfield, MO… <name…  # … con 4 variables más: viewport , place_id , types ,
#> #   status

Y luego la ubicación, para lo cual es necesario expandir ubicación:

loc %>%
  unnest_wider(json) %>%
  unnest_longer(results) %>%
  unnest_wider(results) %>%
  unnest_wider(geometry) %>%
  unnest_wider(location)
#> # A tibble: 5 x 11
#>   city  address_compone… formatted_addre… bounds   lat    lng location_type
#>                                       
#> 1 Hous…        Houston, TX, USA  2 LA           Los Angeles, CA…  3 New …        New York, NY, U…  4 Chic…        Chicago, IL, USA  5 Spri…        Springfield, MO…  # … with 4 more variables: viewport , place_id , types ,
#> #   status

De nuevo, unnest_auto() simplifica la operación descrita con algunos riesgos que pueden surgir debido a cambios en la estructura de los datos entrantes:

loc %>%
  unnest_auto(json) %>%
  unnest_auto(results) %>%
  unnest_auto(results) %>%
  unnest_auto(geometry) %>%
  unnest_auto(location)
#> Using `unnest_wider(json)`; elements have 2 names in common
#> Using `unnest_longer(results)`; no element has names
#> Using `unnest_wider(results)`; elements have 5 names in common
#> Using `unnest_wider(geometry)`; elements have 4 names in common
#> Using `unnest_wider(location)`; elements have 2 names in common
#> # A tibble: 5 x 11
#>   city  address_compone… formatted_addre… bounds   lat    lng location_type
#>                                       
#> 1 Hous…        Houston, TX, USA  2 LA           Los Angeles, CA…  3 New …        New York, NY, U…  4 Chic…        Chicago, IL, USA  5 Spri…        Springfield, MO…  # … with 4 more variables: viewport , place_id , types ,
#> #   status

También podemos simplemente observar la primera dirección de cada ciudad:

loc %>%
  unnest_wider(json) %>%
  hoist(results, first_result = 1) %>%
  unnest_wider(first_result) %>%
  unnest_wider(geometry) %>%
  unnest_wider(location)
#> # A tibble: 5 x 11
#>   city  address_compone… formatted_addre… bounds   lat    lng location_type
#>                                       
#> 1 Hous…        Houston, TX, USA  2 LA           Los Angeles, CA…  3 New …        New York, NY, U…  4 Chic…        Chicago, IL, USA  5 Spri…        Springfield, MO…  # … with 4 more variables: viewport , place_id , types ,
#> #   status

O usar hoist() para la inmersión de varios niveles, para ir directamente a lat y lng.

loc %>%
  hoist(json,
    lat = list("results", 1, "geometry", "location", "lat"),
    lng = list("results", 1, "geometry", "location", "lng")
  )
#> # A tibble: 5 x 4
#>   city          lat    lng json            
#>                       
#> 1 Houston      29.8  -95.4 
#> 2 LA           34.1 -118.  
#> 3 New York     40.7  -74.0 
#> 4 Chicago      41.9  -87.6 
#> 5 Springfield  37.2  -93.3

Discografía de Sharla Helfand

Al final, vamos a ver la construcción más complicada: la discografía de Sharly Gelfand. Al igual que en los ejemplos anteriores, comenzamos convirtiendo la lista en un marco de datos con una columna y luego lo expandiremos para que cada componente sea una columna separada. También convertiré la columna date_added al formato de fecha y hora adecuado en R.

discs % 
  unnest_wider(disc) %>% 
  mutate(date_added = as.POSIXct(strptime(date_added, "%Y-%m-%dT%H:%M:%S"))) 
discs
#> # A tibble: 155 x 5
#>    instance_id date_added          basic_information       id rating
#>                                          
#>  1   354823933 2019-02-16 17:48:59   7496378      0
#>  2   354092601 2019-02-13 14:13:11   4490852      0
#>  3   354091476 2019-02-13 14:07:23   9827276      0
#>  4   351244906 2019-02-02 11:39:58   9769203      0
#>  5   351244801 2019-02-02 11:39:37   7237138      0
#>  6   351052065 2019-02-01 20:40:53  13117042      0
#>  7   350315345 2019-01-29 15:48:37   7113575      0
#>  8   350315103 2019-01-29 15:47:22  10540713      0
#>  9   350314507 2019-01-29 15:44:08  11260950      0
#> 10   350314047 2019-01-29 15:41:35  11726853      0
#> # … with 145 more rows

A este nivel hemos obtenido información sobre cuándo se añadió cada disco a la discografía de Sharly, pero no vemos ningún dato sobre esos discos. Para ello, necesitamos expandir la columna basic_information:

discs %>% unnest_wider(basic_information)
#> El nombre de la columna `id` no debe duplicarse.
#> Usa .name_repair para especificar la reparación.

Lamentablemente, obtendremos un error, ya que dentro de la lista basic_information hay una columna con el mismo nombre basic_information. Cuando ocurra un error como este, para identificar rápidamente su causa se puede utilizar names_repair = "unique":

discos %>% unnest_wider(basic_information, names_repair = "unique")
#>% Nuevos nombres:
#>% * id -> id...6
#>% * id -> id...14
#>% # Un tibble: 155 x 15
#>%    instance_id date_added          labels  year artistas id...6 thumb título
#>%                                
#>%  1   354823933 2019-02-16 17:48:59 <list…  2015 %  2   354092601 2019-02-13 14:13:11 <list…  2013 %  3   354091476 2019-02-13 14:07:23 <list…  2017 %  4   351244906 2019-02-02 11:39:58 <list…  2017 %  5   351244801 2019-02-02 11:39:37 <list…  2015 %  6   351052065 2019-02-01 20:40:53 <list…  2019 %  7   350315345 2019-01-29 15:48:37 <list…  2014 %  8   350315103 2019-01-29 15:47:22 <list…  2015 %  9   350314507 2019-01-29 15:44:08 <list…  2017 % 10   350314047 2019-01-29 15:41:35 <list…  2017 % # … con 145 filas más, y 7 variables adicionales: formats ,
#>% #   cover_image , resource_url , master_id ,
#>% #   master_url , id...14 , rating

El problema es que basic_information repite la columna id que también se almacena en el nivel superior, así que podemos simplemente eliminarlo:

discos %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#>% # Un tibble: 155 x 14
#>%    instance_id date_added          labels  year artistas     id thumb título
#>%                                
#>%  1   354823933 2019-02-16 17:48:59 <list…  2015 %  2   354092601 2019-02-13 14:13:11 <list…  2013 %  3   354091476 2019-02-13 14:07:23 <list…  2017 %  4   351244906 2019-02-02 11:39:58 <list…  2017 %  5   351244801 2019-02-02 11:39:37 <list…  2015 %  6   351052065 2019-02-01 20:40:53 <list…  2019 %  7   350315345 2019-01-29 15:48:37 <list…  2014 %  8   350315103 2019-01-29 15:47:22 <list…  2015 %  9   350314507 2019-01-29 15:44:08 <list…  2017 % 10   350314047 2019-01-29 15:41:35 <list…  2017 % # … con 145 filas más, y 6 variables adicionales: formats ,
#>% #   cover_image , resource_url , master_id ,
#>% #   master_url , rating

Como alternativa, podríamos usar hoist():

discs %>% 
  hoist(basic_information,
    title = "title",
    year = "year",
    label = list("labels", 1, "name"),
    artist = list("artists", 1, "name")
  )
#> # A tibble: 155 x 9
#>    instance_id date_added          title  year label artist
#>                             
#>  1   354823933 2019-02-16 17:48:59 Demo   2015 Tobi… Mollot
#>  2   354092601 2019-02-13 14:13:11 Obse…  2013 La V… Una B…
#>  3   354091476 2019-02-13 14:07:23 I      2017 La V… S.H.I…
#>  4   351244906 2019-02-02 11:39:58 Oído…  2017 La V… Rata …
#>  5   351244801 2019-02-02 11:39:37 A Ca…  2015 Kato… Ivy (…
#>  6   351052065 2019-02-01 20:40:53 Tash…  2019 High… Tashme
#>  7   350315345 2019-01-29 15:48:37 Demo   2014 Mind… Desgr…
#>  8   350315103 2019-01-29 15:47:22 Let …  2015 Not … Phant…
#>  9   350314507 2019-01-29 15:44:08 Sub …  2017 Not … Sub S…
#> 10   350314047 2019-01-29 15:41:35 Demo   2017 Pres… Small…
#> # … with 145 more rows, and 3 more variables: basic_information ,
#> #   id , rating

Aquí extraigo rápidamente el nombre de la primera etiqueta y el artista por su índice, sumergiéndome en la lista anidada.

Un enfoque más sistemático consiste en crear tablas separadas para el artista y la etiqueta:

discs %>% 
  hoist(basic_information, artist = "artists") %>% 
  select(disc_id = id, artist) %>% 
  unnest_longer(artist) %>% 
  unnest_wider(artist)
#> # A tibble: 167 x 8
#>     disc_id join  name        anv   tracks role  resource_url            id
#>                                    
#>  1  7496378 ""    Mollot      ""    ""     ""    https://api.discog… 4.62e6
#>  2  4490852 ""    Una Bèstia… ""    ""     ""    https://api.discog… 3.19e6
#>  3  9827276 ""    S.H.I.T. (… ""    ""     ""    https://api.discog… 2.77e6
#>  4  9769203 ""    Rata Negra  ""    ""     ""    https://api.discog… 4.28e6
#>  5  7237138 ""    Ivy (18)    ""    ""     ""    https://api.discog… 3.60e6
#>  6 13117042 ""    Tashme      ""    ""     ""    https://api.discog… 5.21e6
#>  7  7113575 ""    Desgraciad… ""    ""     ""    https://api.discog… 4.45e6
#>  8 10540713 ""    Phantom He… ""    ""     ""    https://api.discog… 4.27e6
#>  9 11260950 ""    Sub Space … ""    ""     ""    https://api.discog… 5.69e6
#> 10 11726853 ""    Small Man … ""    ""     ""    https://api.discog… 6.37e6
#> # … with 157 more rows

discs %>% 
  hoist(basic_information, format = "formats") %>% 
  select(disc_id = id, format) %>% 
  unnest_longer(format) %>% 
  unnest_wider(format) %>% 
  unnest_longer(descriptions)
#> # A tibble: 280 x 5
#>     disc_id descriptions text  name     qty  
#>                     
#>  1  7496378 Numbered     Black Cassette 1    
#>  2  4490852 LP             Vinyl    1    
#>  3  9827276 "7""          Vinyl    1    
#>  4  9827276 45 RPM         Vinyl    1    
#>  5  9827276 EP             Vinyl    1    
#>  6  9769203 LP             Vinyl    1    
#>  7  9769203 Album          Vinyl    1    
#>  8  7237138 "7""          Vinyl    1    
#>  9  7237138 45 RPM         Vinyl    1    
#> 10 13117042 "7""          Vinyl    1    
#> # … with 270 more rows

Luego puede volver a unirlos al conjunto de datos original según sea necesario.

Conclusión

En el núcleo de la biblioteca tidyverse se encuentran muchos paquetes útiles que comparten una filosofía común de procesamiento de datos.

En este artículo hemos analizado la familia de funciones unnest_*(), que se centran en la extracción de elementos de listas anidadas. Este paquete contiene muchas otras funciones útiles que simplifican la transformación de datos de acuerdo con el concepto de Datos Ordenados.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster