Rozbalení vnořených sloupců - seznamů pomocí jazyka R (balíček tidyr a funkce rodiny unnest)

Ve většině případů, když pracujete s odpovědí získanou z API nebo s jakýmikoli jinými daty, která mají složitou stromovou strukturu, se setkáváte s formáty JSON a XML.

Tyto formáty mají mnoho výhod: údaje ukládají poměrně kompaktně a umožňují se vyhnout nadměrnému duplicitnímu informování.

Nevýhodou těchto formátů je složitost jejich zpracování a analýzy. Nestrukturovaná data nelze použít ve výpočtech a nelze na jejich základě vytvářet vizualizace.

Rozbalení vnořených sloupců - seznamů pomocí jazyka R (balíček tidyr a funkce rodiny unnest)

Tento článek je logickým pokračováním publikace "R balíček tidyr a jeho nové funkce pivot_longer a pivot_wider". Pomůže vám přetvořit nestrukturované datové struktury do obvyklého a pro analýzu vhodného tabulkového formátu pomocí balíčku tidyr, který je součástí jádra knihovny tidyverse, a jeho funkcí rodiny unnest_*().

Obsah

Pokud se zajímáte o analýzu dat, možná vás zaujmou mé telegram a youtube kanály. Většina obsahu je věnována jazyku R.

  1. Úvod
  2. Uživatelé GitHub
  3. Republiky Github
  4. Postavy ze Hry o trůny
  5. Geokódování s pomocí Google
  6. Diskografie Šarly Gelfand
  7. Závěr

Úvod

Rectangling (pozn. překladatele, nenašel jsem adekvátní varianty překladu tohoto výrazu, proto ho necháme tak, jak je.) — je proces přetvoření nestrukturovaných dat s vnořenými poli do dvourozměrné tabulky, tvořené obvyklými řádky a sloupci. V tidyr existuje několik funkcí, které vám pomohou rozbalit vnořené sloupce-seznamy a přetvořit data do obdélníkového, tabulkového formátu:

  • unnest_longer() vezme každý prvek seznamu-sloupce a vytvoří nový řádek.
  • unnest_wider() vezme každý prvek seznamu-sloupce a vytvoří nový sloupec.
  • unnest_auto() automaticky určuje, kterou z funkcí je lepší použít
    unnest_longer() nebo unnest_wider().
  • hoist() je podobný unnest_wider() ale vybírá pouze určené komponenty a umožňuje pracovat s několika úrovněmi vnoření.

Většinu problémů spojených s převedením nestrukturovaných dat s více úrovněmi vnoření do dvourozměrné tabulky lze vyřešit kombinováním uvedených funkcí s dplyr.

Pro demonstraci těchto technik budeme používat balíček repurrrsive, který poskytuje několik složitých, vícerozměrných seznamů získaných z webového API.

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

Uživatelé GitHub

Začneme s gh_users, seznam, který obsahuje informace o šesti uživatelích GitHubu. Nejprve převedeme seznam gh_users v tibble data frame.:

users <- tibble( user = gh_users ) 

To se může zdát trochu nelogické: proč převádět seznam gh_users, na složitější datovou strukturu? Ale datový rámec má velkou výhodu: sloučí několik vektorů, takže je vše sledováno v jednom objektu.

Každý prvek objektu users je pojmenovaný seznam, kde každý prvek představuje sloupec.

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 způsoby, jak převést komponenty seznamu na sloupce. unnest_wider() vezme každou komponentu a vytvoří nový sloupec:

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.…
#> # … s 23 dalšími proměnnými: 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 případě jsme získali tabulku o 30 sloupcích a většina z nich nebude potřebná, takže můžeme místo unnest_wider() použít hoist(). hoist() nám umožňuje extrahovat vybrané komponenty, pomocí stejné syntaxe jako purrr::pluck():

uživatelé %> % hoist(user, 
  založené_komponenty = "followers", 
  přihlášení = "login", 
  url = "html_url"
)
#> # A tibble: 6 x 4
#>   založené_komponenty přihlášení   url                             uživatel          
#>                                                               
#> 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() odstraňuje uvedené pojmenované komponenty ze seznamu-sloupce uživatel, proto můžete považovat hoist() za přesun komponent z vnitřního seznamu datového rámce na jeho vrchní úroveň.

Republiky Github

Zarovnání seznamu gh_repos začínáme analogicky, převáděním ho na tibble:

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

Tentokrát prvky uživatel představují seznam repozitářů, které patří tomuto uživateli. Každý repozitář je samostatným pozorováním, takže podle konceptu tidy data (pozn. tidy data) by měly být nový řádky, proto používáme unnest_longer() a ne unnest_wider():

repos  % unnest_longer(repo)
repos
#> # A tibble: 176 x 1
#>    repo              
#>                
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … s 166 dalšími řádky

Nyní můžeme použít unnest_wider() nebo hoist() :

repos %> % hoist(repo, 
  přihlášení = c("owner", "login"), 
  název = "name",
  domovská stránka = "homepage",
  pozorovatelé = "watchers_count"
)
#> # A tibble: 176 x 5
#>    přihlášení   název        domovská stránka pozorovatelé 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 
#> # … s 166 dalšími řádky

Všimněte si použití c("owner", "login"): to nám umožňuje získat hodnotu druhé úrovně z vnořeného seznamu vlastníkAlternativní přístup spočívá v získání celého seznamu vlastník a poté pomocí funkce unnest_wider() vložit každý jeho prvek do sloupce:

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 166 dalšími řádky a 11 dalšími proměnnými: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

Místo abyste přemýšleli o výběru správné funkce unnest_longer() nebo unnest_wider() můžete použít unnest_auto(). Tato funkce používá několik heuristických metod k nalezení nejvhodnější funkce pro transformaci dat a vypisuje zprávu o zvoleném způsobu.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Používá `unnest_longer(repo)`; žádný prvek nemá názvy
#> Používá `unnest_wider(repo)`; prvky mají 68 společných názvů
#> # 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…  # … s 166 dalšími řádky, a 58 dalšími proměnný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 ze Hry o trůny

got_chars má identickou strukturu s gh_users: je to soubor pojmenovaných seznamů, kde každý prvek vnitřního seznamu popisuje určitou vlastnost postavy Hry o trůny. Převod got_chars do tabulkové podoby začínáme vytvořením datového rámce, stejně jako v předchozích příkladech, a poté převedeme každý prvek do samostatného sloupce:

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

Struktura got_chars je o něco složitější než gh_users, protože některé komponenty seznamu char samy o sobě jsou seznamy, a tím se vytvářejí sloupce – seznamy:

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 další kroky závisí na cílech analýzy. Možná budete muset do řádků umístit informace o každé knize a seriálu, ve kterém se postava objevuje:

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    Hra o trůny
#>  2 Theon Greyjoy    books    Bouře mečů
#>  3 Theon Greyjoy    books    Hostina pro vrabce
#>  4 Theon Greyjoy    tvSeries Řada 1         
#>  5 Theon Greyjoy    tvSeries Řada 2         
#>  6 Theon Greyjoy    tvSeries Řada 3         
#>  7 Theon Greyjoy    tvSeries Řada 4         
#>  8 Theon Greyjoy    tvSeries Řada 5         
#>  9 Theon Greyjoy    tvSeries Řada 6         
#> 10 Tyrion Lannister books    Hostina pro vrabce
#> # … s 170 dalšími řádky

Nebo možná chcete vytvořit tabulku, která vám umožní přiřadit postavu a dílo:

chars2 %>% 
  select(name, title = titles) %>% 
  unnest_longer(title)
#> # A tibble: 60 x 2
#>    name              title                                               
#>                                                                
#>  1 Theon Greyjoy     Princ z Winterfellu                                
#>  2 Theon Greyjoy     Kapitán mořské mrchy                                
#>  3 Theon Greyjoy     Lord Železných ostrovů (podle zákona zelených zemí)
#>  4 Tyrion Lannister  Činný Ruka krále (bývalý)                          
#>  5 Tyrion Lannister  Mistr mincí (bývalý)                             
#>  6 Victarion Greyjoy Lord kapitán Železné flotily                      
#>  7 Victarion Greyjoy Mistr Železného vítězství                          
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Kapitán stráže na Sunspear                        
#> 10 Chett             ""                                                  
#> # … s 50 dalšími řádky

(Všimněte si prázdných hodnot "" v poli title, to je způsobeno chybami, které byly udělány při zadávání dat v got_chars: ve skutečnosti postavy, pro které neexistují odpovídající názvy knih a seriálů, by měly mít vektor délky 0, nikoli vektor délky 1 obsahující prázdný řetězec.) title Můžeme přepracovat výše uvedený příklad pomocí funkce

. Tento přístup je vhodný pro jednorázovou analýzu, ale není dobré se na něj spoléhat unnest_auto()pro pravidelné použití. Věc se má tak, že pokud se vaše struktura dat změní unnest_auto() může se vybraná metoda transformace dat změnit; pokud původně převáděla sloupce-seznamy na řádky pomocí unnest_auto() , pak změna struktury vstupních dat může změnit logiku ve prospěch unnest_longer(), a používání takového přístupu na stálém základě může vést k neočekávaným chybám. unnest_wider(), a používání takového přístupu na trvalé základě může vést k nepředvídaným chybám.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> Použití `unnest_wider(char)`; prvky mají 18 společných názvů
#> Použití `unnest_longer(title)`; žádný prvek nemá názvy
#> # A tibble: 60 x 2
#>    name              title                                               
#>                                                                
#>  1 Theon Greyjoy     Prince z Winterfellu                                
#>  2 Theon Greyjoy     Kapitán mořské mrchy                                
#>  3 Theon Greyjoy     Pán železných ostrovů (podle zákona zelených zemí)
#>  4 Tyrion Lannister  Jednající ruka krále (bývalý)                      
#>  5 Tyrion Lannister  Mistr mincí (bývalý)                               
#>  6 Victarion Greyjoy Lord kapitán železné flotily                      
#>  7 Victarion Greyjoy Mistr železného vítěze                          
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Kapitán stráže v Sunspear                            
#> 10 Chett             ""                                                  
#> # … s 50 dalšími řádky

Geokódování s pomocí Google

Dále se podíváme na složitější strukturu dat získávaných ze služby geokódování Google. Ukládání přihlašovacích údajů do cache je v rozporu s podmínkami použití API Google Maps, proto nejprve napíšu jednoduchý wrapper k API. Ten bude založen na ukládání klíče API Google Maps do proměnné prostředí; pokud v prostředních proměnných nemáte uložený klíč pro práci s API Google Maps, kódové fragmenty uvedené v této části se neprovedou.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Klíč API Google Maps nenalezen; kódové bloky nebudou spuštěny")
}

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

Seznam, který tato funkce vrací, je poměrně slož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štěstí můžeme krok za krokem vyřešit problém převodu těchto dat do tabulkového formátu pomocí funkcí tidyr. Aby byla úloha mírně složitější a realistická, začnu geokódováním několika měst:

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

Výsledný údaj převedu na tibble, abych přidal sloupec s odpovídajícím názvem města.

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

První úroveň obsahuje komponenty status a result, které můžeme rozbalit pomocí 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šimněte si, že results je vícestupňový seznam. Většina měst má 1 prvek (reprezentující unikátní hodnotu odpovídající geokódovacímu API), ale Springfield má dva. Můžeme je vytáhnout do samostatných řádků pomocí 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

Nyní mají všechny stejné komponenty, což lze ověřit pomocí 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 najít souřadnice zeměpisné šířky a délky pro každé město rozbalením seznamu 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

A poté polohu, k čemuž je třeba rozbalit 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

Znovu, unnest_auto() zjednodušuje popsanou operaci s některými riziky, které mohou vzniknout změnou struktury vstupních dat:

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 se také jednoduše podívat na první adresu pro každé město:

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

Nebo použít hoist() pro vícestupňové ponoření, abyste přešli přímo k lat a lng.

lok %>%
  zvedni(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

Diskografie Šarly Gelfand

Na závěr se podíváme na nejkomplexnější strukturu — diskografii Šarly Gelfandové. Stejně jako v předchozích příkladech začneme konverzí seznamu na datový rámec s jedním sloupcem a poté jej rozšíříme, aby každý komponent byl v samostatném sloupci. Také převedu sloupec date_added do odpovídajícího formátu data 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
#> # … s 145 dalšími řádky

Na této úrovni jsme získali informace o tom, kdy byl každý disk přidán do diskografie Šarly, ale zároveň nevidíme žádná data o těchto discích. K tomu potřebujeme rozšířit sloupec basic_information:

discs %>% unnest_wider(basic_information)
#> Název sloupce `id` nesmí být duplikován.
#> Použijte .name_repair k určení opravy.

Bohužel dostaneme chybu, protože uvnitř seznamu basic_information existuje sloupec se stejným názvem basic_information. Když dojde k podobné chybě, lze rychle zjistit její příčinu použitím names_repair = "unique":

disky %>% unnest_wider(basic_information, names_repair = "unique")
#> Nová jména:
#> * id -> id...6
#> * id -> id...14
#> # Tibbles: 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 opakuje sloupec id, který je také uložen na vrchní úrovni, proto ho můžeme jednoduše odstranit:

disky %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # Tibbles: 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

Jako alternativu 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

Zde rychle vyextractuji jméno prvního labelu a umělce podle indexu, ponořením se do vnořeného seznamu.

Systematičtější přístup spočívá ve vytvoření samostatných tabulek pro umělce 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

Poté je můžete podle potřeby znovu připojit k původnímu datovému souboru.

Závěr

Jádro knihovny tidyverse obsahuje řadu užitečných balíčků sjednocených společnou filosofií zpracování dat.

V tomto článku jsme probrali rodinu funkcí unnest_*(), které se zaměřují na práci s extrakcí prvků z vnořených seznamů. Tento balíček obsahuje řadu dalších užitečných funkcí, které usnadňují transformaci dat podle konceptu Úhledná data.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster