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

Nella maggior parte dei casi, quando si lavora con la risposta ricevuta da un'API o con qualsiasi altro dato che presenta una struttura ad albero complessa, ci si imbatte nei formati JSON e XML.

Questi formati hanno molti vantaggi: conservano i dati in modo relativamente compatto e consentono di evitare la duplicazione eccessiva delle informazioni.

Un punto negativo di questi formati è la difficoltà di elaborazione e analisi. I dati non strutturati non possono essere utilizzati per calcoli e non è possibile costruire visualizzazioni su di essi.

Sviluppiamo colonne annidate — elenchi 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". Aiuterà a trasformare le strutture di dati non strutturate in un formato tabellare familiare e utilizzabile per l'analisi grazie al pacchetto tidyr, parte del nucleo della libreria tidyverse, e alle sue funzioni della famiglia unnest_*().

Contenuto

Se siete interessati all'analisi dei dati, potrebbero interessarvi i miei telegram e youtube canali. Gran parte del contenuto è dedicato al linguaggio R.

  1. Introduzione
  2. Gli utenti di GitHub
  3. Repository GitHub
  4. Personaggi del Trono di Spade
  5. Geocodifica con Google
  6. Discografia di Sharly Gelfand
  7. Conclusione

Introduzione

Rectangolazione (nota del traduttore, non ho trovato traduzioni adeguate per questo termine, quindi lo lasciamo così com'è.) è il processo di trasformazione di dati non strutturati con array annidati in una tabella bidimensionale composta da righe e colonne familiari. Nel tidyr ci sono diverse funzioni che possono aiutarti a espandere colonne-liste annidate e trasformare i dati in una forma rettangolare 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 la migliore da usare
    unnest_longer() o unnest_wider().
  • hoist() sembra simile a unnest_wider() ma seleziona solo i componenti specificati e consente di lavorare con più livelli di annidamento.

La maggior parte dei problemi relativi alla trasformazione di dati non strutturati con più livelli di annidamento in una tabella bidimensionale possono essere risolti combinando le funzioni elencate con dplyr.

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

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

Gli utenti di GitHub

Iniziamo con gh_users, una lista che contiene informazioni su sei utenti GitHub. Innanzitutto, trasformiamo la lista gh_users in tibble frame.:

users <- tibble( user = gh_users ) 

Questo sembra un po' illogico: perché convertire una lista gh_usersin una struttura di dati più complessa? Ma i data frame hanno un grande vantaggio: uniscono più vettori, in modo che tutto sia tracciato in un solo oggetto.

Ogni elemento dell'oggetto users è una lista nominata, 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 della lista 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 serviranno, quindi possiamo invece unnest_wider() utilizzare hoist(). hoist() ci consente di estrarre 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             
#>                                                    
#> 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() rimuove i componenti nominati specificati dalla lista-colonna utente, quindi puoi considerare hoist() come uno spostamento dei componenti dall'elenco interno del data frame al suo livello superiore.

Repository GitHub

Allineamento della lista gh_repos iniziamo similmente, trasformandolo in tibble:

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

Questa volta gli elementi utente rappresentano un elenco di repository appartenenti a questo utente. Ogni repository è un'osservazione separata, quindi secondo il concetto di dati puliti (nota: tidy data) devono diventare nuove righe, motivo per cui utilizziamo unnest_longer() invece di 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 righe aggiuntive

Ora possiamo usare 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             
#>                                     
#>  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 righe aggiuntive

Si prega di notare l'uso di c("owner", "login"): questo ci consente di ottenere il valore di secondo livello da un elenco annidato proprietario. Un approccio alternativo è quello di ottenere l'intero elenco proprietario e poi utilizzare la funzione unnest_wider() per mettere 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 aggiuntive e 11 variabili in più: 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 giusta unnest_longer() o unnest_wider() puoi utilizzare unnest_auto(). Questa funzione utilizza diversi metodi euristici per determinare la funzione più adatta per la trasformazione dei dati, e fornisce un messaggio riguardante il metodo scelto.

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 nome  full_name owner private html_url description fork  url  
#>                            
#>  1 6.12e7 dopo gaborcsa…   2 4.05e7 argu… gaborcsa…   3 3.64e7 chiedi 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 righe in più e 58 variabili aggiuntive: 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 del Trono di Spade

got_chars ha una struttura identica a gh_users: è un insieme di elenchi nominati, in cui ogni elemento dell'elenco interno descrive un certo attributo del personaggio de Il Trono di Spade. La conversione got_chars in un formato tabellare inizia con la creazione di un data frame, proprio come negli esempi precedenti, e poi tradurremo ogni elemento in una colonna separata:

chars  # Un 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
#> # Un tibble: 30 x 18
#>    url      id nome  gender cultura nato  morto vivo titoli alias padre
#>                  
#>  1 http…  1022 Theo… Maschio Ironbo… In 2… ""    TRUE  <chr …   2 http…  1052 Tyri… Maschio ""      In 2… ""    TRUE  <chr …   3 http…  1074 Vict… Maschio Ironbo… In 2… ""    TRUE  <chr …   4 http…  1109 Will  Maschio ""      ""    In 2… FALSE <chr …   5 http…  1166 Areo… Maschio Norvos… In 2… ""    TRUE  <chr …   6 http…  1267 Chett Maschio ""      A H… In 2… FALSE <chr …   7 http…  1295 Cres… Maschio ""      In 2… In 2… FALSE <chr …   8 http…   130 Aria… Femmina Dornish In 2… ""    TRUE  <chr …   9 http…  1303 Daen… Femmina Valyri… In 2… ""    TRUE  <chr …  10 http…  1319 Davo… Maschio Wester… In 2… ""    TRUE  <chr …  # … con 20 righe in più e 7 variabili aggiuntive: mother , spouse ,
#> #   allegiances , books , povBooks , tvSeries ,
#> #   playedBy

Struttura got_chars un po' più complesso di gh_users, poiché alcuni componenti della lista char sono essi stessi delle liste, quindi otteniamo colonne che sono liste:

chars2 %>% select_if(is.list)
#> # Un 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 vostre azioni successive dipendono dagli obiettivi dell'analisi. Potrebbe essere necessario inserire in 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
#> # … with 170 more rows

Oppure, forse vuoi creare una tabella che ti consenta di associare un personaggio a un'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             ""                                                  
#> # … with 50 more rows

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

. Questo approccio è comodo per un'analisi una tantum, ma non è consigliabile fare affidamento su unnest_auto()per un uso regolare. Il fatto è che se la tua struttura dati cambia unnest_auto() potrebbe modificare il meccanismo di trasformazione scelto; se inizialmente espandeva colonne-liste in righe usando unnest_auto() , un cambiamento nella struttura dei dati in ingresso potrebbe alterare la logica, a favore di unnest_longer(), e un utilizzo costante di questo approccio può portare a errori imprevisti. unnest_wider()tibble(char = got_chars) %>% unnest_auto(char) %>% select(name, title = titles) %>% unnest_auto(title) #> Using `unnest_wider(char)`; elements have 18 names in common #> Using `unnest_longer(title)`; no element has names #> # 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 "" #> # … with 50 more rows

Successivamente esamineremo una struttura dati più complessa derivata dal servizio di geocodificazione di Google. La memorizzazione delle credenziali contrasta con i termini di servizio delle API di Google Maps, quindi prima scriverò un semplice wrapper per le API. Questo sarà basato sulla memorizzazione della chiave API di Google Maps in una variabile ambiente; se nella tua variabile ambiente non è memorizzata la chiave per utilizzare le API di Google Maps, i frammenti di codice presentati in questa sezione non verranno eseguiti.

Geocodifica con Google

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "") if (!has_key) { message("No Google Maps API key found; code chunks will not be run") }# 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 che restituisce questa funzione è piuttosto complessa:

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 un formato tabellare passo dopo passo utilizzando funzioni tidyr. Per rendere l'attività un po' più complessa e realistica, inizierò geocodificando alcune città:

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

Trasformerò il risultato in tibble, aggiungendo una colonna con i nomi delle città corrispondenti per comodità.

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 status e result, 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

Nota che results è una lista gerarchica. La maggior parte delle città ha 1 elemento (che rappresenta un valore unico corrispondente all'API di geocodifica), 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 simili, come possiamo verificare 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

Possiamo trovare le coordinate di latitudine e longitudine di ogni città espandendo 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…  # … with 4 more variables: viewport , place_id , types ,
#> #   status

E quindi la posizione, che richiede di 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 potenziali causati da modifiche nella struttura dei dati in ingresso:

loc %>%
  unnest_auto(json) %>%
  unnest_auto(results) %>%
  unnest_auto(results) %>%
  unnest_auto(geometry) %>%
  unnest_auto(location)
#> Utilizzando `unnest_wider(json)`; gli elementi hanno 2 nomi in comune
#> Utilizzando `unnest_longer(results)`; nessun elemento ha nomi
#> Utilizzando `unnest_wider(results)`; gli elementi hanno 5 nomi in comune
#> Utilizzando `unnest_wider(geometry)`; gli elementi hanno 4 nomi in comune
#> Utilizzando `unnest_wider(location)`; gli elementi hanno 2 nomi in comune
#> # Un tibble: 5 x 11
#>   city  address_compone… formatted_addre… bounds   lat    lng location_type
#>                            <dbl>  <dbl> <chr>
#> 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…  # … con 4 variabili aggiuntive: viewport , place_id , types ,
#> #   status

Possiamo anche semplicemente guardare il primo indirizzo di ogni città:

loc %>%
  unnest_wider(json) %>%
  hoist(results, first_result = 1) %>%
  unnest_wider(first_result) %>%
  unnest_wider(geometry) %>%
  unnest_wider(location)
#> # Un tibble: 5 x 11
#>   city  address_compone… formatted_addre… bounds   lat    lng location_type
#>                            <dbl>  <dbl> <chr>
#> 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…  # … con 4 variabili aggiuntive: viewport , place_id , types ,
#> #   status

O utilizzare hoist() per un'immersione multilivello, per andare direttamente a lat e lng.

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

Discografia di Sharly Gelfand

In conclusione, esamineremo la costruzione più complessa: la discografia di Sharla Gelfand. Come negli esempi precedenti, iniziamo convertendo la lista in un dataframe con una singola colonna e poi la espandiamo affinché ogni componente sia una colonna separata. Trasformerò anche la colonna date_added nel formato data e ora appropriato in R.

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

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 questo, dobbiamo espandere la colonna basic_information:

discs %>% unnest_wider(basic_information)
#> Il nome della colonna `id` non deve essere duplicato.
#> Usa .name_repair per specificare la riparazione.

Purtroppo otterremo un errore poiché all'interno della lista basic_information c'è una colonna con lo stesso nome basic_information. Quando si verifica un errore di questo tipo, è possibile utilizzare names_repair = "unique":

discs %>% 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
#>          <int> <dttm>              <list> <int> <list>   <int> <chr> <chr>
#>  1   354823933 2019-02-16 17:48:59 <list…  2015 <list … 7.50e6 http… Demo 
#>  2   354092601 2019-02-13 14:13:11 <list…  2013 <list … 4.49e6 http… Obse…
#>  3   354091476 2019-02-13 14:07:23 <list…  2017 <list … 9.83e6 http… I    
#>  4   351244906 2019-02-02 11:39:58 <list…  2017 <list … 9.77e6 http… Oído…
#>  5   351244801 2019-02-02 11:39:37 <list…  2015 <list … 7.24e6 http… A Ca…
#>  6   351052065 2019-02-01 20:40:53 <list…  2019 <list … 1.31e7 http… Tash…
#>  7   350315345 2019-01-29 15:48:37 <list…  2014 <list … 7.11e6 http… Demo 
#>  8   350315103 2019-01-29 15:47:22 <list…  2015 <list … 1.05e7 http… Let …
#>  9   350314507 2019-01-29 15:44:08 <list…  2017 <list … 1.13e7 ""    Sub …
#> 10   350314047 2019-01-29 15:41:35 <list…  2017 <list … 1.17e7 http… Demo 
#> # … con 145 righe aggiuntive, e 7 variabili aggiuntive: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , id...14 , rating

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

discs %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # A 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  # … with 145 more rows, and 6 more variables: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , rating

In alternativa, potremmo usare 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 in base all'indice, immergendomi nella lista nidificata.

Un approccio più sistematico è quello di 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 di nuovo al set di dati originale se necessario.

Conclusione

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

In questo articolo abbiamo esaminato la famiglia di funzioni unnest_*(), progettate per lavorare con l'estrazione di elementi da liste nidificate. Questo pacchetto contiene molte altre funzioni utili che semplificano la trasformazione dei dati secondo il concetto di Tidy Data.

Fonte: habr.com

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