Espandiamo colonne annidate — liste utilizzando il linguaggio R (pacchetto tidyr e funzioni della famiglia unnest)

Nella maggior parte dei casi, quando lavori con la risposta ricevuta dall'API o con qualsiasi altro dato che ha una struttura ad albero complessa, ti imbatti nei formati JSON e XML.

Questi formati hanno numerosi vantaggi: memorizzano i dati in modo abbastanza compatto e consentono di evitare eccessive duplicazioni di informazioni.

Un limite di questi formati è la difficoltà nel loro trattamento e analisi. I dati non strutturati non possono essere utilizzati nei calcoli e non è possibile costruire visualizzazioni basate su di essi.

Espandiamo colonne annidate — liste utilizzando il linguaggio R (pacchetto tidyr e funzioni della famiglia unnest)

Questo articolo è una continuazione logica della pubblicazione "Il pacchetto R tidyr e le sue nuove funzioni pivot_longer e pivot_wider". Ti aiuterà a trasformare le strutture di dati non strutturati in una forma tabellare familiare e adatta all'analisi utilizzando il pacchetto tidyr, che fa parte del nucleo della libreria tidyverse, e delle sue funzioni della famiglia unnest_*().

Contenuto

Se sei interessato all'analisi dei dati, potresti trovare interessanti i miei telegram e youtube canali. Gran parte del contenuto è dedicato al linguaggio R.

  1. Introduzione
  2. Utenti di GitHub
  3. Repository GitHub
  4. Personaggi di Game of Thrones
  5. Geocodifica con Google
  6. Discografia di Sharly Gelfand
  7. Conclusione

Introduzione

Rectangling (nota del traduttore, non ho trovato opzioni di traduzione adeguate per questo termine, quindi lo lasciamo così com'è.) è il processo di trasformazione dei dati non strutturati con array nidificati in una tabella bidimensionale composta da righe e colonne a noi familiari. In tidyr ci sono diverse funzioni che ti aiuteranno a espandere le colonne di lista nidificate e trasformare i dati in una forma rettangolare e tabellare:

  • unnest_longer() prende ogni elemento della lista-colonna e crea una nuova riga.
  • unnest_wider() prende ogni elemento della lista-colonna e crea una nuova colonna.
  • unnest_auto() determina automaticamente quale delle funzioni sia meglio utilizzare
    unnest_longer() o unnest_wider().
  • hoist() è simile a unnest_wider() ma seleziona solo i componenti specificati e consente di lavorare con più livelli di nidificazione.

La maggior parte dei problemi legati alla trasformazione dei dati non strutturati con più livelli di nidificazione in una tabella bidimensionale può essere risolta combinando le funzioni elencate con dplyr.

Per dimostrare queste tecniche, utilizzeremo il pacchetto repurrrsive, che fornisce diversi elenchi complessi e multilevel ottenuti da un'API web.

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

Utenti di GitHub

Iniziamo con gh_users, un elenco che contiene informazioni su sei utenti di GitHub. Per iniziare, trasformeremo l'elenco gh_users in tibble frame.:

users <-   tibble( user = gh_users ) 

Sembra un po' illogico: perché riportare un elenco gh_users, in una struttura dati più complessa? Ma il data frame ha un grande vantaggio: combina più vettori, in modo che tutto sia tracciato in un unico oggetto.

Ogni elemento dell'oggetto users è un elenco nominato, in cui ogni elemento rappresenta una colonna.

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"

Ci sono due modi per trasformare i componenti dell'elenco in colonne. unnest_wider() prende ogni componente e crea una nuova colonna:

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 variabili in più: 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

In questo caso abbiamo ottenuto una tabella composta da 30 colonne, e la maggior parte di esse non ci servirà, quindi possiamo invece unnest_wider() di utilizzare hoist(). hoist() ci consente di estrarre i componenti selezionati, utilizzando la stessa sintassi di purrr::pluck():

utenti %>% hoist(user, 
  followers = "followers", 
  login = "login", 
  url = "html_url"
)
#> # A tibble: 6 x 4
#>   followers login       url                            user             
#>       <int> <chr>       <chr>                          <list>           
#> 1       303 gaborcsardi https://github.com/gaborcsardi <named list [27]>
#> 2       780 jennybc     https://github.com/jennybc     <named list [27]>
#> 3      3958 jtleek      https://github.com/jtleek      <named list [27]>
#> 4       115 juliasilge  https://github.com/juliasilge  <named list [27]>
#> 5       213 leeper      https://github.com/leeper      <named list [27]>
#> 6        34 masalmon    https://github.com/masalmon    <named list [27]>

hoist() rimuove i componenti denominati indicati dalla colonna elenco user, quindi puoi considerare hoist() come spostare i componenti dalla lista interna del frame dati al suo livello superiore.

Repository GitHub

Allineamento della lista gh_repos iniziamo in modo simile, trasformandola in tibble:

repos <- tibble(repo = gh_repos)
repos
#> # A tibble: 6 x 1
#>   repo       
#>   <list>     
#> 1 <list [30]>
#> 2 <list [30]>
#> 3 <list [30]>
#> 4 <list [26]>
#> 5 <list [30]>
#> 6 <list [30]>

Questa volta gli elementi user rappresentano un elenco di repository appartenenti a questo utente. Ogni repository è un'osservazione separata, quindi secondo il concetto di dati ordinati (- tidy data -) dovrebbero diventare nuove righe, motivo per cui utilizziamo unnest_longer() e non unnest_wider():

repos <- repos %>% unnest_longer(repo)
repos
#> # A tibble: 176 x 1
#>    repo             
#>    <list>           
#>  1 <named list [68]>
#>  2 <named list [68]>
#>  3 <named list [68]>
#>  4 <named list [68]>
#>  5 <named list [68]>
#>  6 <named list [68]>
#>  7 <named list [68]>
#>  8 <named list [68]>
#>  9 <named list [68]>
#> 10 <named list [68]>
#> # … con altre 166 righe

Ora possiamo utilizzare unnest_wider() o hoist() :

repos %>% hoist(repo, 
  login = c("owner", "login"), 
  name = "name",
  homepage = "homepage",
  watchers = "watchers_count"
)
#> # A tibble: 176 x 5
#>    login       name        homepage watchers repo             
#>    <chr>       <chr>       <chr>       <int> <list>           
#>  1 gaborcsardi after       <NA>            5 <named list [65]>
#>  2 gaborcsardi argufy      <NA>           19 <named list [65]>
#>  3 gaborcsardi ask         <NA>            5 <named list [65]>
#>  4 gaborcsardi baseimports <NA>            0 <named list [65]>
#>  5 gaborcsardi citest      <NA>            0 <named list [65]>
#>  6 gaborcsardi clisymbols  ""             18 <named list [65]>
#>  7 gaborcsardi cmaker      <NA>            0 <named list [65]>
#>  8 gaborcsardi cmark       <NA>            0 <named list [65]>
#>  9 gaborcsardi conditions  <NA>            0 <named list [65]>
#> 10 gaborcsardi crayon      <NA>           52 <named list [65]>
#> # … con altre 166 righe

Si prega di notare l'uso di c("owner", "login"): questo ci consente di ottenere il valore di secondo livello da una lista annidata proprietario. Un approccio alternativo consiste nel ottenere l'intero elenco proprietario e poi utilizzare la funzione unnest_wider() per inserire ciascun elemento in una colonna:

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 righe in più e 11 variabili aggiuntive: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

Invece di riflettere sulla scelta della funzione corretta unnest_longer() o unnest_wider() puoi utilizzare unnest_auto(). Questa funzione utilizza diversi metodi euristici per trovare la funzione più adatta per trasformare i dati e restituisce un messaggio sul metodo selezionato.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Utilizzando `unnest_longer(repo)`; nessun elemento ha nomi
#> Utilizzando `unnest_wider(repo)`; gli elementi hanno 68 nomi in comune
#> # Un tibble: 176 x 67
#>        id name  full_name owner private html_url description fork  url  
#>                            
#>  1 6.12e7 after gaborcsa…   2 4.05e7 argu… gaborcsa…   3 3.64e7 ask   gaborcsa…   4 3.49e7 base… gaborcsa…   5 6.16e7 cite… 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 righe in più e 58 variabili in più: 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

Personaggi di Game of Thrones

got_chars ha una struttura identica a gh_users: è un insieme di elenchi nominati, dove ogni elemento dell'elenco interno descrive un certo attributo del personaggio di Game of Thrones. La trasformazione got_chars in formato tabellare inizia con la creazione di un dataframe, proprio come negli esempi precedenti, e poi trasformeremo ogni elemento in una colonna separata:

chars  # A tibble: 30 x 1
#>    char             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … con 20 righe in più

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 …   2 http…  1052 Tyri… Male   ""      In 2… ""    TRUE  <chr …   3 http…  1074 Vict… Male   Ironbo… In 2… ""    TRUE  <chr …   4 http…  1109 Will  Male   ""      ""    In 2… FALSE <chr …   5 http…  1166 Areo… Male   Norvos… In 2… ""    TRUE  <chr …   6 http…  1267 Chett Male   ""      At H… In 2… FALSE <chr …   7 http…  1295 Cres… Male   ""      In 2… In 2… FALSE <chr …   8 http…   130 Aria… Female Dornish In 2… ""    TRUE  <chr …   9 http…  1303 Daen… Female Valyri… In 2… ""    TRUE  <chr …  10 http…  1319 Davo… Male   Wester… In 2… ""    TRUE  <chr …  # … con 20 righe in più, e 7 variabili in più: madre , coniuge ,
#> #   alleanze , libri , povBooks , tvSeries ,
#> #   interpretatoDa

Struttura got_chars un po' più difficile rispetto a gh_users, in quanto alcuni componenti della lista char in sé stessi sono liste, di conseguenza otteniamo colonne - liste:

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 righe in più

Le tue azioni successive dipendono dagli obiettivi dell'analisi. Potresti dover inserire nelle righe le informazioni su ciascun libro e serie in cui appare il personaggio:

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 righe in più

Oppure, forse, desideri creare una tabella che ti permetta di mettere in relazione il personaggio e l'opera:

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 righe in più

(Nota, ci sono valori vuoti "" nel campo title, ciò è dovuto a errori commessi durante l'inserimento dei dati in got_chars: in realtà i personaggi per i quali non ci sono titoli di libri e serie corrispondenti nel campo title dovrebbero avere un vettore di lunghezza 0, e non un vettore di lunghezza 1 contenente una stringa vuota.)

Possiamo riscrivere l'esempio sopra utilizzando la funzione unnest_auto(). Questo approccio è utile per un'analisi una tantum, ma non bisogna fare affidamento su unnest_auto() per un utilizzo regolare. Il fatto è che se la tua struttura dati cambia unnest_auto() potrebbe cambiare il meccanismo scelto per la trasformazione dei dati; se inizialmente si adattava per espandere le colonne elenco in righe usando unnest_longer(), allora con il cambiamento della struttura dei dati in ingresso, la logica potrebbe essere modificata a favore di unnest_wider(), e utilizzare tale approccio in modo permanente potrebbe portare a errori imprevisti.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> Utilizzando `unnest_wider(char)`; gli elementi condividono 18 nomi
#> Utilizzando `unnest_longer(title)`; nessun elemento ha nomi
#> # Un tibble: 60 x 2
#>    name              title                                               
#>                                                                
#>  1 Theon Greyjoy     Principe di Winterfell                              
#>  2 Theon Greyjoy     Capitano della Sea Bitch                           
#>  3 Theon Greyjoy     Signore delle Isole di Ferro (per legge delle terre verdi)
#>  4 Tyrion Lannister  Mano del Re ad interim (ex)                          
#>  5 Tyrion Lannister  Maestro delle Finanze (ex)                          
#>  6 Victarion Greyjoy Capitano del Ferro della Flotta                       
#>  7 Victarion Greyjoy Maestro della Vittoria di Ferro                      
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Capitano della Guardia a Sunspear                   
#> 10 Chett             ""                                                  
#> # … con 50 righe in più

Geocodifica con Google

Successivamente, esamineremo una struttura dati più complessa, ottenuta dal servizio di geocodifica di Google. La memorizzazione nella cache delle credenziali è contraria ai termini di utilizzo dell'API di Google Maps, quindi inizierò a scrivere un semplice wrapper per l'API. Basato sul mantenimento della chiave API di Google Maps in una variabile d'ambiente; se nella vostra variabile d'ambiente non è presente la chiave per utilizzare l'API di Google Maps, i frammenti di codice presentati in questa sezione non verranno eseguiti.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Nessuna chiave API di Google Maps trovata; i blocchi di codice non verranno eseguiti")
}

# 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 restituita da questa funzione è piuttosto complessa:

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"

Fortunatamente, possiamo risolvere il problema della conversione di questi dati in formato tabellare passo dopo passo utilizzando le funzioni tidyr. Per rendere l'attività un po' più complessa e realistica, inizierò con la geocodifica di alcune città:

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

Il risultato ottenuto lo trasformerò in tibble, per comodità aggiungerò una colonna con il nome corrispondente della città.

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

Il primo livello contiene i componenti stato e risultato, che possiamo espandere utilizzando 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

Si prega di notare che results è un elenco multilivello. La maggior parte delle città ha 1 elemento (che rappresenta un valore unico corrispondente all'API di geocoding), ma Springfield ne ha due. Possiamo estrarli in righe separate 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

Ora tutti hanno componenti identici, il che può essere confermato usando 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…  2 LA             Los Angeles, CA… <named … ChIJE9on…  3 New Y…         New York, NY, U… <named … ChIJOwg_…  4 Chica…         Chicago, IL, USA <named … ChIJ7cv0…  5 Sprin…         Springfield, MO… <named … ChIJP5jI… <lis… OK

Possiamo trovare le coordinate di latitudine e longitudine di ogni città espandendo l'elenco 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 variabili in più: viewport , place_id , types ,
#> #   status

E poi la posizione, per cui è necessario espandere location:

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

Ancora una volta, unnest_auto() semplifica l'operazione descritta con alcuni rischi che possono derivare dalla modifica della struttura dei dati in ingresso:

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

Possiamo anche semplicemente dare un'occhiata al primo indirizzo per ciascuna città:

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

Oppure utilizzare hoist() per un'immersione multilivello, per accedere direttamente a lat e 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

Discografia di Sharly Gelfand

In conclusione, esamineremo la struttura più complessa: la discografia di Sharla Gelfand. Come negli esempi precedenti, iniziamo convertendo l'elenco in un dataframe con una singola colonna, quindi lo estendiamo in modo che ogni componente sia una colonna separata. Trasformerò anche la colonna date_added nel formato data e ora appropriato in 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 questo livello abbiamo ottenuto informazioni su quando ogni disco è stato aggiunto alla discografia di Sharla, ma non vediamo alcun dato su questi dischi. Per farlo, dobbiamo espandere la colonna basic_information:

discs %>% unnest_wider(basic_information)
#> Column name `id` must not be duplicated.
#> Use .name_repair to specify repair.

Purtroppo riceveremo un errore, poiché all'interno dell'elenco basic_information c'è una colonna con lo stesso nome basic_information. Quando si verifica un errore del genere, per determinare rapidamente la causa si può utilizzare names_repair = "unique":

dischi %>% unnest_wider(basic_information, names_repair = "unique")
#> Nuovi nomi:
#> * id -> id...6
#> * id -> id...14
#> # Un tibble: 155 x 15
#>    instance_id date_added          labels  year artists id...6 thumb title
#>                                
#>  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 righe aggiuntive e 7 variabili in più: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , id...14 , rating

Il problema è che basic_information ripete la colonna id che è già presente a livello superiore, pertanto possiamo semplicemente eliminarla:

dischi %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # Un tibble: 155 x 14
#>    instance_id date_added          labels  year artists     id thumb title
#>                                
#>  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 righe aggiuntive e 6 variabili in più: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , rating

In alternativa, potremmo utilizzare 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

Qui estraggo rapidamente il nome della prima etichetta e dell'artista per indice, immergendomi in una lista annidata.

Un approccio più sistematico consiste nel creare tabelle separate per l'artista e l'etichetta:

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

Poi puoi unirli nuovamente al set di dati originale se necessario.

Conclusione

Nel nucleo della libreria tidyverse ci sono molti pacchetti utili uniti da una filosofia comune di elaborazione dei dati.

In questo articolo abbiamo esaminato la famiglia di funzioni unnest_*(), che sono destinate a lavorare con l'estrazione di elementi da elenchi annidati. Questo pacchetto contiene molte altre funzioni utili che semplificano la trasformazione dei dati secondo il concetto di Dati Ordinati.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster