Extindem coloanele înnăscute - liste folosind limbajul R (pachetul tidyr și funcțiile familiei unnest)

În cele mai multe cazuri, lucrând cu răspunsul obținut de la API sau cu alte date care au o structură complexă de tip arbore, întâlniți formatele JSON și XML.

Aceste formate au numeroase avantaje: ele stochează datele într-un mod relativ compact și permit evitarea duplicării excesive a informațiilor.

Un dezavantaj al acestor formate este complexitatea de procesare și analiză. Datele nestructurate nu pot fi utilizate în calcule și nu pot fi folosite pentru a crea vizualizări.

Extindem coloanele înnăscute - liste folosind limbajul R (pachetul tidyr și funcțiile familiei unnest)

Acest articol este o continuare logică a publicației "Pachetul R tidyr și noile sale funcții pivot_longer și pivot_wider". El vă va ajuta să transformați construcțiile de date nestructurate într-o formă tabelară familiare, potrivită pentru analiză, cu ajutorul pachetului tidyr, care face parte din nucleul bibliotecii tidyverse, și a funcțiilor sale din familia unnest_*().

Cuprins

Dacă sunteți interesat de analiza datelor, s-ar putea să vă intereseze canalele mele. telegram și youtube Marea parte a conținutului lor este dedicată limbajului R.

  1. Introducere
  2. Utilizatorii GitHub
  3. Repositoarele Github
  4. Personajele din Jocul Tronurilor
  5. Geocodare folosind Google
  6. Discografia Sharlei Gelfand
  7. Concluzie

Introducere

Rectangulare (nota traducătorului, nu am găsit variante adecvate pentru a traduce acest termen, așa că îl vom lăsa așa cum este.) — este procesul de transformare a datelor nestructurate cu array-uri imbricate într-un tabel bidimensional, format din linii și coloane familiare. În tidyr există câteva funcții care vă vor ajuta să desfășurați coloanele-listă și să aduceți datele într-o formă rectangulară, tabelară:

  • unnest_longer() preia fiecare element al coloanei-listă și creează un nou rând.
  • unnest_wider() preia fiecare element al coloanei-listă și creează o nouă coloană.
  • unnest_auto() determină automat care dintre funcții este mai bun de utilizat
    unnest_longer() sau unnest_wider().
  • hoist() se aseamănă cu unnest_wider() dar selectează doar componentele specificate și permite lucrul cu mai multe niveluri de imbricare.

Cele mai multe probleme legate de transformarea datelor nestructurate cu mai multe niveluri de imbricare într-un tabel bidimensional pot fi soluționate prin combinarea funcțiilor enumerate cu dplyr.

Pentru a demonstra aceste tehnici, vom folosi pachetul repurrrsive, care oferă mai multe liste complexe și multi-nivel, obținute de la un API web.

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

Utilizatorii GitHub

Să începem cu gh_users, o listă care conține informații despre șase utilizatori GitHub. Mai întâi, să transformăm lista gh_users în tibble cadru.:

users <- tibble(user = gh_users) 

Acest lucru pare puțin nelogic: de ce să folosești o listă gh_users, într-o structură de date mai complexă? Dar cadrul de date are un mare avantaj: reunește mai mulți vectori, astfel încât totul este urmărit într-un singur obiect.

Fiecare element al obiectului aveau adrese de email pe domeniile reprezintă o listă denumită, în care fiecare element reprezintă o coloană.

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"

Există două moduri de a transforma componentele listelor în coloane. unnest_wider() preia fiecare componentă și creează o coloană nouă:

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.…
#> # … cu 23 de variabile suplimentare: 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

În acest caz, am obținut un tabel format din 30 de coloane, iar majoritatea dintre ele nu ne vor fi necesare, așa că putem în loc de unnest_wider() folosi hoist(). hoist() ne permite să extragem componentele selectate folosind aceeași sintaxă ca și purrr::pluck():

utilizatori %>% hoist(user, 
  followers = "followers", 
  login = "login", 
  url = "html_url"
)
#> # Un 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() șterge componentele numite specificate din lista-coloană utilizator, așa că poți considera hoist() ca mutarea componentelor din lista internă a cadrului de date la nivelul său superior.

Repositoarele Github

Alinierea listei gh_repos începem similar, transformându-l în tibble:

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

De data aceasta, elementele utilizator reprezintă o listă de repozitorii care aparțin acestui utilizator. Fiecare repozitoriu este o observație separată, așa că, conform conceptului de date curate (notă: tidy data) ele trebuie să devină noi rânduri, motiv pentru care folosim unnest_longer() și nu unnest_wider():

repos <- repos %>% unnest_longer(repo)
repos
#> # Un 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]>
#> # … cu 166 de rânduri suplimentare

Acum putem folosi unnest_wider() sau hoist() :

repos %>% hoist(repo, 
  login = c("owner", "login"), 
  name = "name",
  homepage = "homepage",
  watchers = "watchers_count"
)
#> # Un 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]>
#> # … cu 166 de rânduri suplimentare

Atenție la utilizarea c("owner", "login"): acest lucru ne permite să obținem valoarea de nivelul doi din lista încrustată ownerO abordare alternativă constă în a obține întreaga listă owner și apoi, folosind o funcție unnest_wider() să plasăm fiecare element într-o coloană:

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.…
#> # … cu 166 de rânduri în plus și 11 variabile mai multe: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

În loc să reflectați asupra alegerii funcției potrivite unnest_longer() sau unnest_wider() puteți utiliza unnest_auto(). Această funcție folosește mai multe metode heuristice pentru a determina cea mai potrivită funcție de transformare a datelor și afișează un mesaj despre metoda aleasă.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Utilizând `unnest_longer(repo)`; nici un element nu are denumiri
#> Utilizând `unnest_wider(repo)`; elementele au 68 de denumiri 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…  # … cu 166 de rânduri suplimentare și 58 de variabile suplimentare: 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

Personajele din Jocul Tronurilor

got_chars are identice ca structură cu gh_users: este un set de liste numite, unde fiecare element din lista interioară descrie un anumit atribut al unui personaj din Game of Thrones. Convertirea got_chars în format tabular începe cu crearea unui cadru de date, exact ca în exemplele date anterior, după care vom transforma fiecare element într-o coloană separată:

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 , spouse ,
#> #   allegiances , books , povBooks , tvSeries ,
#> #   playedBy

Structura got_chars mai complicat decât gh_users, deoarece unele componente ale listei char sunt ele însele liste, rezultatul este că obținem coloane — 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          
#> # … with 20 more rows

Acțiunile tale ulterioare depind de obiectivele analizei. Este posibil să fie necesar să introduci informații în rânduri despre fiecare carte și serial în care apare personajul:

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

Sau, poate vrei să creezi un tabel care să îți permită să corelezi personajul cu 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

(Reține că există valori goale "" în câmpul titlul, aceasta este cauzată de erori comise în momentul introducerii datelor în got_chars: de fapt, personajele pentru care nu există titluri corespunzătoare de cărți și seriale în câmpul titlul ar trebui să aibă un vector de lungime 0, nu un vector de lungime 1 care conține un șir gol.)

Putem rescrie exemplul de mai sus folosind funcția unnest_auto(). Această abordare este convenabilă pentru o analiză unică, dar nu trebuie să ne bazăm pe unnest_auto() pentru utilizare regulată. Problema este că, dacă structura datelor tale se schimbă unnest_auto() s-ar putea schimba mecanismul selectat de transformare a datelor; dacă inițial acesta desfăcea coloanele-liste în rânduri folosind unnest_longer(), atunci când structura datelor de intrare se schimbă, logica ar putea fi modificată în favoarea unnest_wider(), iar utilizarea unei astfel de abordări pe termen lung poate duce la erori neașteptate.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
# > Folosind `unnest_wider(char)`; elementele au 18 nume comune
# > Folosind `unnest_longer(title)`; niciun element nu are nume
# > # Un tibble: 60 x 2
# >    name              title                                               
# >                                                                
# >  1 Theon Greyjoy     Prințul de Winterfell                               
# >  2 Theon Greyjoy     Căpitanul Bărcilor de Mare                        
# >  3 Theon Greyjoy     Lord al Insulelor de Fier (prin legea pământurilor verzi)
# >  4 Tyrion Lannister  Mâna Regelui în exercițiu (fost)                    
# >  5 Tyrion Lannister  Stăpânul Monedei (fost)                             
# >  6 Victarion Greyjoy Lord Căpitan al Flotei de Fier                    
# >  7 Victarion Greyjoy Stăpânul Victoriei de Fier                          
# >  8 Will              ""                                                  
# >  9 Areo Hotah        Căpitanul Pazei la Sunspear                        
# > 10 Chett             ""                                                  
# > # … cu 50 de rânduri mai multe

Geocodare folosind Google

În continuare, vom explora o structură de date mai complexă obținută de la serviciul de geocodare Google. Stocarea cheilor de autentificare contravin termenilor de utilizare a API-ului Google Maps, așa că mai întâi voi scrie un simplu wrapper pentru API. Care se bazează pe stocarea cheii API Google Maps într-o variabilă de mediu; dacă nu aveți salvat în variabilele de mediu cheia pentru utilizarea API-ului Google Maps, fragmentele de cod prezentate în această secțiune nu vor fi executate.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Nu s-a găsit cheia API Google Maps; bucățile de cod nu vor fi rulate")
}

# 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)
}

Lista pe care o returnează această funcție este destul de complexă:

houston  Lista de 2
#>  $ results:Lista de 1
#>   ..$ :Lista de 5
#>   .. ..$ address_components:Lista de 4
#>   .. .. ..$ :Lista de 3
#>   .. .. .. ..$ long_name : chr "Houston"
#>   .. .. .. ..$ short_name: chr "Houston"
#>   .. .. .. ..$ types     :Lista de 2
#>   .. .. .. .. ..$ : chr "localitate"
#>   .. .. .. .. ..$ : chr "politic"
#>   .. .. ..$ :Lista de 3
#>   .. .. .. ..$ long_name : chr "Harris County"
#>   .. .. .. ..$ short_name: chr "Harris County"
#>   .. .. .. ..$ types     :Lista de 2
#>   .. .. .. .. ..$ : chr "nivel_administrativ_2"
#>   .. .. .. .. ..$ : chr "politic"
#>   .. .. ..$ :Lista de 3
#>   .. .. .. ..$ long_name : chr "Texas"
#>   .. .. .. ..$ short_name: chr "TX"
#>   .. .. .. ..$ types     :Lista de 2
#>   .. .. .. .. ..$ : chr "nivel_administrativ_1"
#>   .. .. .. .. ..$ : chr "politic"
#>   .. .. ..$ :Lista de 3
#>   .. .. .. ..$ long_name : chr "Statele Unite"
#>   .. .. .. ..$ short_name: chr "US"
#>   .. .. .. ..$ types     :Lista de 2
#>   .. .. .. .. ..$ : chr "țară"
#>   .. .. .. .. ..$ : chr "politic"
#>   .. ..$ formatted_address : chr "Houston, TX, SUA"
#>   .. ..$ geometry          :Lista de 4
#>   .. .. ..$ bounds       :Lista de 2
#>   .. .. .. ..$ northeast:Lista de 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ southwest:Lista de 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. .. ..$ location     :Lista de 2
#>   .. .. .. ..$ lat: num 29.8
#>   .. .. .. ..$ lng: num -95.4
#>   .. .. ..$ location_type: chr "APROXIMAT"
#>   .. .. ..$ viewport     :Lista de 2
#>   .. .. .. ..$ northeast:Lista de 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ southwest:Lista de 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. ..$ place_id          : chr "ChIJAYWNSLS4QIYROwVl894CDco"
#>   .. ..$ types             :Lista de 2
#>   .. .. ..$ : chr "localitate"
#>   .. .. ..$ : chr "politic"
#>  $ status : chr "OK"

Din fericire, putem rezolva pas cu pas problema transformării acestor date într-un format tabular folosind funcții tidyr. Pentru a face sarcina puțin mai complexă și mai realistă, voi începe cu geocodarea câtorva orașe:

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

Rezultatul obținut îl voi transforma în tibble, pentru comoditate, voi adăuga o coloană cu numele corespunzător al orașului.

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

Primul nivel conține componente status și rezultat, pe care le putem desfășura folosind 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

Rețineți că results este un listă ierarhică. Cele mai multe orașe au 1 element (reprezentând o valoare unică corespunzătoare API-ului de geocodare), dar Springfield are două. Le putem extrage în rânduri separate folosind 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

Acum toate au aceleași componente, ceea ce poate fi verificat folosind 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

Putem găsi coordonatele de latitudine și longitudine pentru fiecare oraș prin desfășurarea listei 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…  # … cu 4 variabile mai: viewport , place_id , types ,
#> #   status

Și apoi locația, pentru care trebuie desfășurată 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

Din nou, unnest_auto() simplifică operațiunea descrisă cu unele riscuri care pot fi cauzate de schimbarea structurii datelor de intrare:

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

Putem, de asemenea, să ne uităm la prima adresă pentru fiecare oraș:

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

Sau folosi hoist() pentru o aprofundare ierarhică, pentru a ajunge direct la lat și 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 Sharlei Gelfand

În final, vom analiza cea mai complexă construcție - discografia Sharly Gelfand. La fel ca în exemplele anterioare, începem prin a converti lista într-un cadru de date cu o singură coloană, iar apoi îl vom extinde, astfel încât fiecare component să fie într-o coloană separată. De asemenea, voi transforma coloana date_added în formatul corespunzător de dată și oră în 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
#> # … cu 145 de rânduri suplimentare

La acest nivel, am obținut informații despre când fiecare disc a fost adăugat în discografia Sharly, dar nu vedem date despre aceste discuri. Pentru aceasta, trebuie să extindem coloana basic_information:

discs %>% unnest_wider(basic_information)
#> Numele coloanei `id` nu trebuie să fie duplicat.
#> Folosiți .name_repair pentru a specifica reparația.

Din păcate, vom primi o eroare, deoarece în interiorul listei basic_information există o coloană cu același nume. basic_informationCând apare o astfel de eroare, pentru a determina rapid cauza acesteia, se poate folosi names_repair = "unique":

discuri %>% unnest_wider(basic_information, names_repair = "unique")
#> Nume noi:
#> * id -> id...6
#> * id -> id...14
#> # Un tibble: 155 x 15
#>    instance_id data_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  # … cu 145 de rânduri suplimentare și 7 variabile mai: formate ,
#> #   imagine_copertă , url_resursă , id_master ,
#> #   url_master , id...14 , rating

Problema este că basic_information repetă coloana id care este, de asemenea, stocată la nivel înalt, deci putem să o ștergem pur și simplu:

discuri %>% 
  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  # … cu 145 de rânduri suplimentare și 6 variabile mai: formate ,
#> #   imagine_copertă , url_resursă , id_master ,
#> #   url_master , rating

Ca alternativă, am putea folosi hoist():

discuri %>%
  hoist(basic_information,
    title = "titlu",
    year = "an",
    label = list("etichete", 1, "nume"),
    artist = list("artiști", 1, "nume")
  )
#> # 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…
#> # … cu 145 mai multe rânduri, și 3 mai multe variabile: basic_information ,
#> #   id , rating

Aici extrag rapid numele primei etichete și al artistului după index, adâncindu-mă în lista învelită.

O abordare mai sistematică constă în crearea unor tabele separate pentru artist și etichetă:

discuri %>%
  hoist(basic_information, artist = "artiști") %>%
  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
#> # … cu 157 mai multe rânduri

discuri %>%
  hoist(basic_information, format = "formate") %>%
  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 Numărul     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    
#> # … cu 270 mai multe rânduri

Apoi, le poți alătura înapoi la setul de date original după cum este necesar.

Concluzie

În nucleul bibliotecii tidyverse se află numeroase pachete utile unite de o filosofie comună de procesare a datelor.

În acest articol am analizat familia de funcții unnest_*(), care se concentrează pe extragerea elementelor din listele încadrați. Acest pachet conține numeroase alte funcții utile, care simplifică transformarea datelor conform conceptului de Date Ordinate.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster