Rozvíjame vnorené stĺpce – zoznamy pomocou jazyka R (balík tidyr a funkcie z rodiny unnest)

V väčšine prípadov pri práci s odpoveďou získanou z API alebo s akýmikoľvek inými dátami, ktoré majú zložitú stromovú štruktúru, sa stretnete s formátmi JSON a XML.

Tieto formáty majú množstvo výhod: dostatočne kompaktné ukladanie dát a umožňujú vyhnúť sa nadmernému duplikovaniu informácií.

Nevýhodou týchto formátov je zložitost ich spracovania a analýzy. Nestruktúrované dáta nie je možné využiť v výpočtoch a nemožno na ich základe vytvárať vizualizácie.

Rozvíjame vnorené stĺpce – zoznamy pomocou jazyka R (balík tidyr a funkcie z rodiny unnest)

Tento článok je logickým pokračovaním publikácie "R balíček tidyr a jeho nové funkcie pivot_longer a pivot_wider". Pomôže vám previesť nestruktúrované konštrukcie dát do bežného a vhodného pre analýzu tabuľkového formátu pomocou balíčka tidyr, ktorý patrí do jadra knižnice tidyverse, a jeho funkcií z rodiny unnest_*().

Obsah

Ak vás zaujíma analýza dát, mohli by vás zaujímať moje telegram a youtube kanály. Väčšina obsahu je venovaná jazyku R.

  1. Úvod
  2. Používatelia GitHub
  3. Repozitáre Github
  4. Postavy Hry o tróny
  5. Geokódovanie pomocou Google
  6. Diskografia Sharly Gelfand
  7. Záver

Úvod

Rectangling (pozn. prekladateľa, nenašiel som adekvátne možnosti prekladu tohto pojmu, preto ho necháme ako je.) — to je proces prevádzania nestruktúrovaných dát s vnorenými poľami do dvojrozmernej tabuľky, pozostávajúcej z bežne známych riadkov a stĺpcov. V tidyr existuje niekoľko funkcií, ktoré vám pomôžu rozvinúť vnorené stĺpce-zoznamy a previesť dáta do obdĺžnikovej, tabuľkovej formy:

  • unnest_longer() vezme každý prvok zoznamu-stĺpca a vytvorí nový riadok.
  • unnest_wider() vezme každý prvok zoznamu-stĺpca a vytvorí nový stĺpec.
  • unnest_auto() automaticky určí, ktorú z funkcií je najlepšie použiť
    unnest_longer() alebo unnest_wider().
  • hoist() je podobná unnest_wider() ale vyberá len uvedené komponenty a umožňuje pracovať s viacerými úrovňami vnorenia.

Väčšinu problémov, ktoré súvisia s prevádzaním nestruktúrovaných dát s viacerými úrovňami vnorenia do dvojrozmernej tabuľky, je možné vyriešiť kombinovaním uvedených funkcií s dplyr.

Na demonštráciu týchto techník budeme používať balíček repurrrsive, ktorý poskytuje niekoľko zložitých, viacerých zoznamov získaných z web-API.

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

Používatelia GitHub

Začneme s gh_users, zoznam, ktorý obsahuje informácie o šiestich používateľoch GitHub. Na začiatok transformujeme zoznam gh_users v tibble framework.:

users <- tibble( user = gh_users ) 

To môže zdať trochu nelogické: prečo predkladať zoznam gh_users, k zložitejšej štruktúre dát? Ale dátový rámec má veľkú výhodu: spája niekoľko vektorov, takže všetko sa sleduje v jednom objekte.

Každý element objektu users predstavuje pomenovaný zoznam, v ktorom každý prvok predstavuje stĺpec.

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"

Existujú dva spôsoby, ako previesť komponenty zoznamu na stĺpce. unnest_wider() berie každý komponent a vytvára nový stĺpec:

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.…
#> # … with 23 more variables: 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

V tomto prípade sme získali tabuľku pozostávajúcu z 30 stĺpcov, a väčšina z nich nám nebude potrebná, preto môžeme namiesto unnest_wider() použiť hoist(). hoist() nám umožňuje extrahovať vybrané komponenty, používajúc rovnakú syntax, akú purrr::pluck():

užívatelia %>% 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() odstráni uvedené pomenované komponenty zo zoznamu-stĺpca user, preto ich môžete považovať za hoist() ako presun komponentov z vnútorného zoznamu dátového rámca na jeho najvyššiu úroveň.

Repozitáre Github

Zarovnanie zoznamu gh_repos začneme podobne, premeníme ho na tibble:

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

Tentokrát sú prvky user zoznamom repozitárov, ktoré patrí tomuto používateľovi. Každý repozitár je samostatným pozorovaním, a preto podľa konceptu čistých dát (pozn. tidy data) by mali byť nové riadky, preto používame unnest_longer() a nie unnest_wider():

repos % unnest_longer(repo)
repos
#> # A tibble: 176 x 1
#>    repo             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … so 166 ďalších riadkov

Teraz môžeme použiť unnest_wider() alebo 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 
#> # … so 166 ďalších riadkov

Venovať pozornosť používaniu c("owner", "login"): to nám umožňuje získať hodnotu druhej úrovne z vnútorného zoznamu ownerAlternatívny prístup spočíva v získaní celého zoznamu owner a následne pomocou funkcie unnest_wider() umiestniť každý jeho prvok do stĺpca:

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.…
#> # … s ďalšími 166 riadkami a ďalšími 11 premennými: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

Namiesto zvažovania voľby správnej funkcie unnest_longer() alebo unnest_wider() môžete použiť unnest_auto(). Táto funkcia využíva niekoľko heuristických metód na výber najvhodnejšej funkcie na transformáciu údajov a vypisuje správu o zvolenom spôsobe.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Používame `unnest_longer(repo)`; žiadny prvok nemá názvy
#> Používame `unnest_wider(repo)`; prvky majú 68 názvov spoločných
#> # A 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…  # … so 166 ďalšími riadkami a 58 ďalšími premennými: 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

Postavy Hry o tróny

got_chars má rovnakú štruktúru ako gh_users: je to súbor pomenovaných zoznamov, kde každý prvok vnútorného zoznamu opisuje určitý atribút postavy Hry o tróny. Prevod got_chars do tabuľkového formátu začíname vytvorením dátového rámca, rovnako ako v predchádzajúcich príkladoch, a potom prevedieme každý prvok do samostatného stĺpca:

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 …   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 …  # … with 20 more rows, and 7 more variables: mother <chr>, spouse <chr>,
#> #   allegiances <list>, books <list>, povBooks <list>, tvSeries <list>,
#> #   playedBy <list>

Struktura got_chars malo komplikovanejšie, ako gh_users, pretože niektoré komponenty zoznamu char sú samy osebe zoznamom, takže získame stĺpce — zoznamy:

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

Vaše ďalšie kroky závisia od cieľov analýzy. Možno budete musieť zadať do riadkov informácie o každej knihe a seriáli, v ktorých sa postava objavuje:

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

Alebo možno chcete vytvoriť tabuľku, ktorá vám umožní prepojiť postavu a dielo:

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

(Všimnite si, že sú prázdne hodnoty "" v poli title, to je spôsobené chybami, ktoré sa vyskytli pri vkladaní údajov do got_chars: skutočne postavy, pre ktoré neexistujú zodpovedajúce názvy kníh a seriálov v poli title by mali mať vektor dĺžky 0, nie vektor dĺžky 1, ktorý obsahuje prázdny reťazec.)

Môžeme prepracovať predpokladaný príklad pomocou funkcie unnest_auto(). Tento prístup je vhodný na jednorazovú analýzu, ale netreba sa naň spoliehať unnest_auto() pre pravidelné používanie. Faktom je, že ak sa vaša štruktúra údajov zmení unnest_auto() môže zmeniť vybraný mechanizmus transformácie údajov, ak pôvodne prevedený do riadkov pomocou unnest_longer(), tak pri zmene štruktúry vstupných údajov sa logika môže zmeniť na unnest_wider(), a používanie takého prístupu na trvalom základe môže viesť k nepredvídateľným chybám.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> Používa sa `unnest_wider(char)`; prvky majú 18 spoločných názvov
#> Používa sa `unnest_longer(title)`; žiadny prvok nemá názvy
#> # 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 (according to the 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             ""                                                  
#> # … s 50 ďalšími riadkami

Geokódovanie pomocou Google

Ďalej preskúmame zložitejšiu štruktúru dát získaných zo služby geokódovania Google. Ukladanie údajov do cache je v rozpore s podmienkami používania API Google máp, preto najprv napíšem jednoduchý obalok pre API. Ten bude založený na ukladaní kľúča API Google máp v premenných prostredia; ak v premenných prostredia nemáte uložený kľúč na prácu s API Google Maps, úryvky kódu uvedené v tejto časti nebudú vykonané.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Žiadny kľúč Google Maps API nenájdený; úryvky kódu sa nebudú vykonávať")
}

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

Zoznam, ktorý táto funkcia vracia, je dosť zložitý:

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"

Našťastie, môžeme krok za krokom vyriešiť problém s konverziou týchto údajov na tabuľkový formát pomocou funkcií tidyr. Aby bola úloha o niečo zložitejšia a realistickejšia, začnem geokódovaním niekoľkých miest:

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

Získaný výsledok prevediem na tibble, pre pohodlie pridám stĺpec s príslušným názvom mesta.

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

Prvá úroveň obsahuje komponenty status a result, ktoré môžeme rozbaliť pomocou 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

Všimnite si, že results je viacúrovňový zoznam. Väčšina miest má 1 prvok (predstavujúci jedinečnú hodnotu zodpovedajúcu API geokódovania), ale Springfield má dva. Môžeme ich extrahovať do samostatných riadkov pomocou 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

Teraz majú všetky rovnaké komponenty, čo môžeme overiť pomocou 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

Môžeme nájsť súradnice šírky a dĺžky každého mesta rozbalením zoznamu geometria:

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

A potom lokalizácia, na čo je potrebné rozbalenie 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

Opäť unnest_auto() uľahčuje popísanú operáciu s niektorými rizikami, ktoré môžu byť spôsobené zmenou štruktúry prichádzajúcich údajov:

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

Môžeme si tiež jednoducho pozrieť prvú adresu pre každé mesto:

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

Alebo použiť hoist() na viacúrovňovú analýzu, aby ste prešli priamo k lat a 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

Diskografia Sharly Gelfand

Na záver sa pozrieme na najzložitejšiu konštrukciu — diskografiu Šarly Gelfand. Tak ako vo vyššie uvedených príkladoch, začneme konverziou zoznamu na dátový rámec s jedným stĺpcom, a potom ho rozšírime, aby každý komponent bol v samostatnom stĺpci. Taktiež prevediem stĺpec date_added na zodpovedajúci formát dátumu a času v 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

Na tejto úrovni sme získali informácie o tom, kedy bol každý disk pridaný do diskografie Šarly, ale nevidíme žiadne údaje o týchto diskoch. Preto musíme rozšíriť stĺpec basic_information:

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

Bohužiaľ, dostaneme chybu, pretože v rámci zoznamu basic_information je stĺpec so zhodným názvom basic_information. Pri takejto chybe, aby sme rýchlo určili jej príčinu, môžeme použiť names_repair = "unique":

disky %>% unnest_wider(basic_information, names_repair = "unique")
#> Nové názvy:
#> * id -> id...6
#> * id -> id...14
#> # 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  # … s 145 dalšími řádky a 7 dalšími proměnnými: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , id...14 , rating

Problém je v tom, že basic_information opakujeme sloupec id, který je také uložen na nejvyšší úrovni, takže ho můžeme jednoduše odstranit:

disky %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # 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  # … s 145 dalšími řádky a 6 dalšími proměnnými: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , rating

Alternativně bychom mohli použít 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

Tu rýchlo vyberiem meno prvého labelu a umelca podľa indexu, pričom sa ponorím do vnoreného zoznamu.

Systematickejší prístup spočíva v vytvorení samostatných tabuliek pre umelca a label:

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

Potom ich môžete podľa potreby znovu pripojiť k pôvodnému súboru údajov.

Záver

Jadro knižnice tidyverse zahŕňa množstvo užitočných balíkov spojení spoločnou filozofiou spracovania údajov.

V tomto článku sme preskúmali rodinu funkcií unnest_*(), ktoré sú zamerané na prácu s extrakciou prvkov z vnorených zoznamov. Tento balík obsahuje množstvo ďalších užitočných funkcií, ktoré uľahčujú transformáciu údajov podľa konceptu Tidy Data.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster