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.

Este artículo es una continuación lógica de la publicación . 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. y Estructura del proyecto del bot
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()ounnest_wider().hoist()es similar aunnest_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 , hireableEn 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/masalmonhoist() 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
#> 6En 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ásAhora 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ásPresta 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 , repoEn 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 ,
#> # homepagePersonajes 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ásSus 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ásO 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ásGeocodificació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 SpringfieldEl 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 OKTenga 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 OKAhora 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… OKPodemos 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 ,
#> # statusY 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 ,
#> # statusDe 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 ,
#> # statusTambié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 ,
#> # statusO 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.3Discografí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 rowsA 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 , ratingEl 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 , ratingComo 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 , ratingAquí 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 rowsLuego 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
