Udrulning af indlejrede kolonner — lister ved hjælp af R (tidyr-pakken og unnest-familien af funktioner)

I de fleste tilfælde, når du arbejder med svar fra API'et eller med andre data, der har en kompleks træstruktur, vil du støde på formaterne JSON og XML.

Disse formater har mange fordele: de opbevarer data ret kompakt og undgår unødig duplication af information.

Ulempen ved disse formater er kompleksiteten i deres behandling og analyse. Ustrukturerede data kan ikke bruges i beregninger og kan ikke danne grundlag for visualisering.

Udrulning af indlejrede kolonner — lister ved hjælp af R (tidyr-pakken og unnest-familien af funktioner)

Denne artikel er en logisk fortsættelse af publikationen "R-pakken tidyr og dens nye funktioner pivot_longer og pivot_wider". Den vil hjælpe dig med at omdanne ustrukturerede datakonfigurationer til et velkendt, og analytisk egnet tabel format med hjælp fra pakken tidyr, som er en del af kernen i biblioteket tidyverse, og dets funktioner i familien unnest_*().

Indhold

Hvis du er interesseret i dataanalyse, kan mine telegram og youtube kanaler være interessante. Størstedelen af indholdet er dedikeret til R-sprog.

  1. Introduktion
  2. GitHub-brugere
  3. GitHub-repositories
  4. Karakterer fra Game of Thrones
  5. Geokodning med Google
  6. Diskografi for Sharly Gelfand
  7. Konklusion

Introduktion

Rectangling (oversætterens note, jeg fandt ikke passende oversættelsesmuligheder for dette udtryk, så vi lader det være som det er.) — er processen med at bringe ustrukturerede data med indlejrede arrays ind i en todimensional tabel, der består af de velkendte rækker og kolonner. I tidyr er der flere funktioner, der kan hjælpe dig med at udvide indlejrede liste-kolonner og omdanne data til en rektangulær, tabel-lignende form:

  • unnest_longer() tager hvert element fra liste-kolonnen og skaber en ny række.
  • unnest_wider() tager hvert element fra liste-kolonnen og skaber en ny kolonne.
  • unnest_auto() bestemmer automatisk, hvilken af funktionerne der er bedst at bruge
    unnest_longer() eller unnest_wider().
  • hoist() ligner unnest_wider() men vælger kun de specificerede komponenter og tillader arbejde med flere niveauer af indlejring.

De fleste problemer relateret til at bringe ustrukturerede data med flere niveauer af indlejring ind i en todimensional tabel kan løses ved at kombinere de nævnte funktioner med dplyr.

For at demonstrere disse teknikker vil vi bruge pakken repurrrsive, som giver flere komplekse, multilevel lister, der er opnået fra web-API'er.

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

GitHub-brugere

Lad os begynde med gh_users, en liste, der indeholder information om seks GitHub-brugere. Først vil vi omdanne listen gh_users i tibble frame.:

users <- tibble( user = gh_users ) 

Det virker lidt ulogisk: hvorfor lave en liste gh_users, til en mere kompleks datakonstruktion? Men dataframen har en stor fordel: den samler flere vektorer, så alt holdes i ét objekt.

Hvert element i objektet brugere er en navngivet liste, hvor hvert element repræsenterer en kolonne.

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"

Der er to måder at omdanne listekomponenter til kolonner på. unnest_wider() tager hver komponent og opretter en ny kolonne:

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.… #
# … med 23 flere variable: 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

I dette tilfælde fik vi en tabel bestående af 30 kolonner, og de fleste af dem har vi ikke brug for, så vi kan i stedet unnest_wider() bruges hoist(). hoist() giver os mulighed for at udtrække valgte komponenter ved hjælp af den samme syntax som purrr::pluck():

brugere %>% hoist(user, 
  followers = "followers", 
  login = "login", 
  url = "html_url"
)
#> # En 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() fjerner de angivne navngivne komponenter fra listekolonnen user, så du kan betragte hoist() som en flytning af komponenter fra den indre liste i dataframen til dets øverste niveau.

GitHub-repositories

Udfladning af listen gh_repos starter vi på lignende måde ved at omdanne den til tibble:

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

Denne gang repræsenterer elementerne user en liste med repositoryer, der tilhører denne bruger. Hver repository er en separat observation, så i henhold til konceptet med pæne data (bemærk. pæne data) bør de blive nye rækker, hvilket betyder, at vi bruger unnest_longer() i stedet for unnest_wider():

repos <- repos %>% unnest_longer(repo)
repos
#> # En 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]>
#> # … med 166 flere rækker

Nu kan vi bruge unnest_wider() eller hoist() :

repos %>% hoist(repo, 
  login = c("owner", "login"), 
  name = "name",
  homepage = "homepage",
  watchers = "watchers_count"
)
#> # En 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]>
#> # … med 166 flere rækker

Vær opmærksom på brugen af c("owner", "login"): det tillader os at få værdien i det andet niveau fra den indre liste ownerEn alternativ tilgang er at få hele listen owner og derefter med hjælp af funktionen unnest_wider() placere hvert element i en kolonne:

repos %>% 
  hoist(repo, owner = "owner") %>% 
  unnest_wider(owner)
#> # En 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.…
#> # … med 166 flere rækker og 11 flere variabler: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

I stedet for at overveje valget af den rigtige funktion unnest_longer() eller unnest_wider() kan du bruge unnest_auto(). Denne funktion anvender flere heuristiske metoder til at finde den mest passende funktion til datatransformationen og giver en meddelelse om den valgte tilgang.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Bruger `unnest_longer(repo)`; ingen element har navne
#> Bruger `unnest_wider(repo)`; elementer har 68 navne i fællesskab
#> # En tibble: 176 x 67
#>        id navn  fulde_navn ejer privat html_url beskrivelse fork  url  
#>                            
#>  1 6.12e7 efter 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…  # … med 166 flere rækker, og 58 flere variabler: 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 , størrelse , stargazers_count ,
#> #   watchers_count , sprog , har_issues ,
#> #   har_downloads , har_wiki , har_sider ,
#> #   forks_count , open_issues_count , forks ,
#> #   open_issues , watchers , default_branch ,
#> #   hjemmeside

Karakterer fra Game of Thrones

got_chars har en identisk struktur med gh_users: det er en samling af navngivne lister, hvor hvert element i den indre liste beskriver en bestemt egenskab ved en karakter i Game of Thrones. At bringe got_chars til tabelformat starter vi med at oprette et datasæt, ligesom i de tidligere nævnte eksempler, og derefter vil vi konvertere hvert element til en separat kolonne:

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

Struktur got_chars mere komplekse end gh_users, da nogle af komponenterne i listen char i sig selv er lister, hvilket resulterer i, at vi får kolonner — lister:

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

Dine videre handlinger afhænger af analyseformålene. Måske er du nødt til at placere information i rækkerne for hver bog og tv-serie, hvori karakteren optræder:

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

Eller måske vil du oprette en tabel, der muliggør, at du kan matche karakteren med værket:

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

(Bemærk de tomme værdier "" i feltet title, det skyldes fejl begået ved dataindtastningen i got_chars: i virkeligheden bør karakterer, for hvilke der ikke er tilsvarende titler på bøger og serier i feltet title have en vektor på længde 0, ikke en vektor på længde 1, der indeholder en tom streng.)

Vi kan omskrive det ovenstående eksempel ved hjælp af funktionen unnest_auto(). Denne tilgang er praktisk til engangsanalysen, men man skal ikke stole på unnest_auto() til brug på regelmæssig basis. Problemet er, at hvis din datastruktur ændrer sig unnest_auto() kan det ændre den valgte dataomstillingsmekanisme, hvis den oprindeligt udvidede listekolonner til rækker ved hjælp af unnest_longer(), så kan logikken ændres til fordel for unnest_wider(), og brugen af en sådan tilgang på permanent basis kan føre til uventede fejl.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> Bruger `unnest_wider(char)`; elementer har 18 navne til fælles
#> Bruger `unnest_longer(title)`; intet element har navne
#> # En tibble: 60 x 2
#>    navn              titel                                               
#>                                                                
#>  1 Theon Greyjoy     Prins af Winterfell                                
#>  2 Theon Greyjoy     Kaptajn af Sea Bitch                                
#>  3 Theon Greyjoy     Herre af Jernøerne (ifølge loven fra de grønne lande)
#>  4 Tyrion Lannister  Handlende for Kongen (tidligere)                    
#>  5 Tyrion Lannister  Kasserer (tidligere)                             
#>  6 Victarion Greyjoy Herre Kaptajn af Jernflåden                      
#>  7 Victarion Greyjoy Kasserer af Jernsejren                          
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Kaptajn af Vagten i Sunspear                    
#> 10 Chett             ""                                                  
#> # … med 50 flere rækker

Geokodning med Google

Næste vil vi se på en mere kompleks datamodel, der modtages fra Google Geocoding-tjenesten. Caching af legitimationsoplysninger er i strid med betingelserne for brug af Google Maps API, så jeg vil først skrive en simpel wrapper til API'et. Den er baseret på at gemme Google Maps API-nøglen i en miljøvariabel; hvis du ikke har gemt nøglen til brug med Google Maps API i miljøvariablerne, vil kodefragmenterne præsenteret i dette afsnit ikke blive kørt.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Ingen Google Maps API-nøgle fundet; kodefragmenter vil ikke blive kørt")
}

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

Listen, som denne funktion returnerer, er ret kompleks:

houston  Liste over 2
#>  $ results:Liste over 1
#>   ..$ :Liste over 5
#>   .. ..$ address_components:Liste over 4
#>   .. .. ..$ :Liste over 3
#>   .. .. .. ..$ long_name : chr "Houston"
#>   .. .. .. ..$ short_name: chr "Houston"
#>   .. .. .. ..$ types     :Liste over 2
#>   .. .. .. .. ..$ : chr "locality"
#>   .. .. .. .. ..$ : chr "political"
#>   .. .. ..$ :Liste over 3
#>   .. .. .. ..$ long_name : chr "Harris County"
#>   .. .. .. ..$ short_name: chr "Harris County"
#>   .. .. .. ..$ types     :Liste over 2
#>   .. .. .. .. ..$ : chr "administrative_area_level_2"
#>   .. .. .. .. ..$ : chr "political"
#>   .. .. ..$ :Liste over 3
#>   .. .. .. ..$ long_name : chr "Texas"
#>   .. .. .. ..$ short_name: chr "TX"
#>   .. .. .. ..$ types     :Liste over 2
#>   .. .. .. .. ..$ : chr "administrative_area_level_1"
#>   .. .. .. .. ..$ : chr "political"
#>   .. .. ..$ :Liste over 3
#>   .. .. .. ..$ long_name : chr "United States"
#>   .. .. .. ..$ short_name: chr "US"
#>   .. .. .. ..$ types     :Liste over 2
#>   .. .. .. .. ..$ : chr "country"
#>   .. .. .. .. ..$ : chr "political"
#>   .. ..$ formatted_address : chr "Houston, TX, USA"
#>   .. ..$ geometry          :Liste over 4
#>   .. .. ..$ bounds       :Liste over 2
#>   .. .. .. ..$ northeast:Liste over 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ southwest:Liste over 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. .. ..$ location     :Liste over 2
#>   .. .. .. ..$ lat: num 29.8
#>   .. .. .. ..$ lng: num -95.4
#>   .. .. ..$ location_type: chr "APPROXIMATE"
#>   .. .. ..$ viewport     :Liste over 2
#>   .. .. .. ..$ northeast:Liste over 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ southwest:Liste over 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. ..$ place_id          : chr "ChIJAYWNSLS4QIYROwVl894CDco"
#>   .. ..$ types             :Liste over 2
#>   .. .. ..$ : chr "locality"
#>   .. .. ..$ : chr "political"
#>  $ status : chr "OK"

Heldigvis kan vi trin for trin løse problemet med at konvertere disse data til tabelformat ved hjælp af funktioner tidyr. For at gøre opgaven lidt mere kompleks og realistisk, vil jeg starte med geokodning af flere byer:

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

Det opnåede resultat vil jeg konvertere til tibble, og for nemheds skyld vil jeg tilføje en kolonne med det tilsvarende bynavn.

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

Det første niveau indeholder komponenterne status og resultat, som vi kan udvide ved hjælp af unnest_wider() :

loc %>%
  unnest_wider(json)
#> # En tibble: 5 x 3
#>   city        results    status
#>                
#> 1 Houston      OK    
#> 2 LA           OK    
#> 3 New York     OK    
#> 4 Chicago      OK    
#> 5 Springfield  OK

Bemærk venligst, at results er en hierarkisk liste. De fleste byer har 1 element (som repræsenterer en unik værdi, der svarer til API-geokodning), men Springfield har to. Vi kan udtrække dem i separate rækker ved hjælp af 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

Nu har de alle de samme komponenter, hvilket kan verificeres ved hjælp af unnest_wider():

loc %>%
  unnest_wider(json) %>% 
  unnest_longer(results) %>% 
  unnest_wider(results)
#> # A tibble: 5 x 7
#>   city   address_componen… formatted_addre… geometry place_id  types status
#>                                       
#> 1 Houst…         Houston, TX, USA <named … ChIJAYWN…  OK    
#> 2 LA             Los Angeles, CA… <named … ChIJE9on…  OK    
#> 3 New Y…         New York, NY, U… <named … ChIJOwg_…  OK    
#> 4 Chica…         Chicago, IL, USA <named … ChIJ7cv0…  OK    
#> 5 Sprin…         Springfield, MO… <named … ChIJP5jI…  OK

Vi kan finde bredde- og længdegrader for hver by ved at udvide listen 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…  # … med 4 yderligere variabler: viewport , place_id , types ,
#> #   status

Og derefter beliggenheden, hvilket kræver at udvide location:

loc %>%
  unnest_wider(json) %>%
  unnest_longer(results) %>%
  unnest_wider(results) %>%
  unnest_wider(geometry) %>%
  unnest_wider(location)
#> # En 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…  # … med 4 flere variabler: viewport , place_id , types ,
#> #   status

Igen, unnest_auto() forenkler den beskrevne operation med visse risici, der kan opstå ved ændringer i strukturen af de indkommende data:

loc %>%
  unnest_auto(json) %>%
  unnest_auto(results) %>%
  unnest_auto(results) %>%
  unnest_auto(geometry) %>%
  unnest_auto(location)
#> Bruger `unnest_wider(json)`; elementer har 2 navne, der er fælles
#> Bruger `unnest_longer(results)`; ingen elementer har navne
#> Bruger `unnest_wider(results)`; elementer har 5 navne, der er fælles
#> Bruger `unnest_wider(geometry)`; elementer har 4 navne, der er fælles
#> Bruger `unnest_wider(location)`; elementer har 2 navne, der er fælles
#> # En 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…  # … med 4 flere variabler: viewport , place_id , types ,
#> #   status

Vi kan også blot se på den første adresse for hver by:

loc %>%
  unnest_wider(json) %>%
  hoist(results, first_result = 1) %>%
  unnest_wider(first_result) %>%
  unnest_wider(geometry) %>%
  unnest_wider(location)
#> # En 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…  # … med 4 flere variabler: viewport , place_id , types ,
#> #   status

Eller bruge hoist() til multi-niveau dykning for at gå direkte til lat og lng.

loc %>%
  hoist(json,
    lat = list("results", 1, "geometry", "location", "lat"),
    lng = list("results", 1, "geometry", "location", "lng")
  )
#> # En 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

Diskografi for Sharly Gelfand

Til slut vil vi se på den mest komplekse struktur — diskografien for Sharly Gelfand. Som i de tidligere nævnte eksempler starter vi med at konvertere en liste til en data frame med én kolonne, og derefter vil vi udvide den, så hver komponent er i sin egen kolonne. Jeg vil også omdanne kolonnen date_added til det relevante dato- og tidsformat i R.

discs % 
  unnest_wider(disc) %>% 
  mutate(date_added = as.POSIXct(strptime(date_added, "%Y-%m-%dT%H:%M:%S"))) 
discs
#> # En 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
#> # … med 145 flere rækker

På dette niveau har vi fået information om, hvornår hver disk blev tilføjet til Sharlys diskografi, men vi ser stadig ingen data om disse diske. For at få disse oplysninger skal vi udvide kolonnen basic_information:

discs %>% unnest_wider(basic_information)
#> Kolonnenavn `id` må ikke dupliceres.
#> Brug .name_repair til at angive reparation.

Desværre vil vi få en fejl, da der inde i listen basic_information er en kolonne med samme navn basic_information. Når en sådan fejl opstår, kan man hurtigt finde årsagen ved at bruge names_repair = "unique":

plader %>% unnest_wider(basic_information, names_repair = "unique")
#> Nye navne:
#> * id -> id...6
#> * id -> id...14
#> # En 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  # … med 145 flere rækker, og 7 flere variabler: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , id...14 , rating

Problemet er, at basic_information gentager kolonnen id, som også findes på det øverste niveau, så vi kan blot fjerne den:

plader %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # En 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  # … med 145 flere rækker, og 6 flere variabler: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , rating

Alternativt kunne vi have brugt hoist():

discs %>% 
  hoist(basic_information,
    title = "title",
    year = "year",
    label = list("labels", 1, "name"),
    artist = list("artists", 1, "name")
  )
#> # En 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…
#> # … med 145 flere rækker, og 3 flere variabler: basic_information ,
#> #   id , rating

Her henter jeg hurtigt navnet på den første label og kunstner ved at dykke ned i den indlejrede liste.

En mere systematisk tilgang indebærer at oprette separate tabeller for kunstneren og labelen:

discs %>% 
  hoist(basic_information, artist = "artists") %>% 
  select(disc_id = id, artist) %>% 
  unnest_longer(artist) %>% 
  unnest_wider(artist)
#> # En 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
#> # … med 157 flere rækker

discs %>% 
  hoist(basic_information, format = "formats") %>% 
  select(disc_id = id, format) %>% 
  unnest_longer(format) %>% 
  unnest_wider(format) %>% 
  unnest_longer(descriptions)
#> # En 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    
#> # … med 270 flere rækker

Derefter kan du knytte dem tilbage til det oprindelige datasæt efter behov.

Konklusion

I kernen af biblioteket tidyverse er der mange nyttige pakker samlet under en fælles filosofi for databehandling.

I denne artikel har vi gennemgået familien af funktioner unnest_*(), der er designet til at arbejde med udtrækning af elementer fra indlejrede lister. Denne pakke indeholder mange andre nyttige funktioner, der forenkler datatransformationer i overensstemmelse med konceptet Tidy Data.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster