Développons des colonnes imbriquées — des listes avec le langage R (paquet tidyr et fonctions de la famille unnest)

Dans la plupart des cas, lorsque vous travaillez avec la réponse obtenue d'une API ou avec d'autres données ayant une structure arborescente complexe, vous êtes confronté aux formats JSON et XML.

Ces formats présentent de nombreux avantages : ils stockent les données de manière assez compacte et permettent d'éviter une duplication excessive de l'information.

Le principal inconvénient de ces formats est la complexité de leur traitement et de leur analyse. Les données non structurées ne peuvent pas être utilisées dans des calculs et ne peuvent pas servir à créer des visualisations.

Développons des colonnes imbriquées — des listes avec le langage R (paquet tidyr et fonctions de la famille unnest)

Cet article est une suite logique de la publication "Le paquet R tidyr et ses nouvelles fonctions pivot_longer et pivot_wider". Il vous aidera à transformer des constructions de données non structurées en un format tabulaire familier et approprié pour l'analyse à l'aide du paquet tidyr, qui fait partie intégrante de la bibliothèque tidyverse, et des fonctions de la famille unnest_*().

Contenu

Si vous vous intéressez à l'analyse de données, mes telegram et youtube canaux pourraient vous intéresser. La majeure partie du contenu y est consacrée au langage R.

  1. Introduction
  2. Utilisateurs de GitHub
  3. Dépôts GitHub
  4. Personnages de Game of Thrones
  5. Géocodage avec Google
  6. Discographie de Sharla Gelfand
  7. Conclusion

Introduction

Rectangulation (note du traducteur, je n'ai pas trouvé d'équivalent adéquat pour ce terme, donc nous le laisserons tel quel.) — c'est le processus de conversion de données non structurées avec des tableaux imbriqués en un tableau bidimensionnel composé de lignes et de colonnes familières. Dans tidyr il existe plusieurs fonctions qui vous aideront à développer des colonnes de listes imbriquées et à convertir les données en une forme rectangulaire, tabulaire :

  • unnest_longer() prend chaque élément de la colonne-liste et crée une nouvelle ligne.
  • unnest_wider() prend chaque élément de la colonne-liste et crée une nouvelle colonne.
  • unnest_auto() détermine automatiquement quelle fonction utiliser.
    unnest_longer() ou unnest_wider().
  • hoist() est similaire à unnest_wider() mais ne sélectionne que les composants spécifiés et permet de travailler avec plusieurs niveaux d'imbrication.

La plupart des problèmes liés à la conversion de données non structurées avec plusieurs niveaux d'imbrication en un tableau bidimensionnel peuvent être résolus en combinant les fonctions énumérées avec dplyr.

Pour démontrer ces techniques, nous allons utiliser le paquet repurrrsive, qui fournit plusieurs listes complexes et hiérarchiques issues d'une API web.

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

Utilisateurs de GitHub

Commençons par gh_users, une liste contenant des informations sur six utilisateurs de GitHub. Pour commencer, transformons la liste gh_users dans tibble frame:

users <-   tibble( user = gh_users ) 

Cela peut sembler un peu illogique : pourquoi faire référence à une liste, gh_userspour une structure de données plus complexe ? Mais le DataFrame a un grand avantage : il regroupe plusieurs vecteurs, de sorte que tout est suivi dans un seul objet.

Chaque élément de l'objet users est une liste nommée où chaque élément représente une colonne.

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"

Il existe deux façons de transformer les composants de la liste en colonnes. unnest_wider() prend chaque composant et crée une nouvelle colonne :

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.…
#> # … avec 23 variables supplémentaires : 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

Dans ce cas, nous obtenons un tableau composé de 30 colonnes, et la plupart d'entre elles ne nous seront pas nécessaires, donc nous pouvons à la place unnest_wider() d'utiliser hoist(). hoist() nous permet d'extraire les composants sélectionnés en utilisant la même syntaxe que purrr::pluck():

utilisateurs %> % 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() supprime les composants nommés spécifiés de la liste-colonne user, donc vous pouvez considérer hoist() comme le déplacement de composants de la liste interne du cadre de données vers son niveau supérieur.

Dépôts GitHub

Alignement de la liste gh_repos nous commençons de manière similaire, en le transformant en tibble:

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

Cette fois, les éléments user représentent une liste de dépôts appartenant à cet utilisateur. Chaque dépôt est une observation distincte, donc selon le concept de données organisées (note : tidy data) ils doivent devenir de nouvelles lignes, c'est pourquoi nous utilisons unnest_longer() et non pas unnest_wider():

repos  % unnest_longer(repo)
repos
#> # A tibble: 176 x 1
#>    repo             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … avec 166 autres lignes

Maintenant, nous pouvons utiliser unnest_wider() ou 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 
#> # … avec 166 autres lignes

Faites attention à l'utilisation de c("owner", "login"): cela nous permet d'obtenir la valeur de deuxième niveau à partir de la liste imbriquée propriétaire. Une approche alternative consiste à obtenir toute la liste propriétaire et ensuite, à l'aide de la fonction unnest_wider() placer chacun de ses éléments dans une colonne :

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.…
#> # … avec 166 autres lignes, et 11 autres variables : following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

Au lieu de réfléchir au choix de la fonction appropriée unnest_longer() ou unnest_wider() vous pouvez utiliser unnest_auto(). Cette fonction utilise plusieurs méthodes heuristiques pour sélectionner la fonction la plus appropriée pour transformer les données et affiche un message sur la méthode choisie.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Utilisation de `unnest_longer(repo)` ; aucun élément n'a de noms
#> Utilisation de `unnest_wider(repo)` ; les éléments ont 68 noms en commun
#> # Un tibble : 176 x 67
#>        id nom  nom_complet propriétaire privé url_html description fork  url  
#>                                        
#>  1 6.12e7 après gaborcsa…   2 4.05e7 argu… gaborcsa…   3 3.64e7 demander gaborcsa…   4 3.49e7 base… gaborcsa…   5 6.16e7 citer… gaborcsa…   6 3.39e7 clis… gaborcsa…   7 3.72e7 cmak… gaborcsa…   8 6.80e7 cmark gaborcsa…   9 6.32e7 cond… gaborcsa… <nam… FAUX   https:…         VRAI http…
#> 10 2.43e7 cray… gaborcsa…  # … avec 166 autres lignes, et 58 autres variables : 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

Personnages de Game of Thrones

caractères_got a une structure identique à gh_users: il s'agit d'un ensemble de listes nommées, où chaque élément de la liste interne décrit un certain attribut d'un personnage de Game of Thrones. La transformation caractères_got en format tabulaire commence par la création d'un cadre de données, tout comme dans les exemples précédents, puis nous diviserons chaque élément en colonnes séparées :

chars  # Un tibble : 30 x 1
#>    char             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … avec 20 autres lignes

chars2 % unnest_wider(char)
chars2
#> # Un 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 …  # … avec 20 autres lignes, et 7 autres variables : mother , spouse ,
#> #   allegiances , books , povBooks , tvSeries ,
#> #   playedBy

Structure caractères_got un peu plus compliqué que gh_users, car certains composants de la liste char eux-mêmes étant des listes, ce qui nous donne des colonnes — listes :

chars2 %>% select_if(is.list)
#> # Un tibble : 30 x 7
#>    titles    aliases    allegiances books     povBooks  tvSeries  playedBy 
#>                                  
#>  1          
#>  2         
#>  3          
#>  4            
#>  5          
#>  6            
#>  7            
#>  8          
#>  9         
#> 10          
#> # … avec 20 autres lignes

Vos prochaines actions dépendent des objectifs de l'analyse. Il peut être nécessaire d'inscrire dans les lignes des informations sur chaque livre et chaque série dans laquelle le personnage apparaît :

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
#> # … avec 170 autres lignes

Ou peut-être voulez-vous créer un tableau qui vous permettra d'associer un personnage à une œuvre :

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 (par la loi des terres vertes)
#>  4 Tyrion Lannister  Acting Hand of the King (ancien)                     
#>  5 Tyrion Lannister  Master of Coin (ancien)                              
#>  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             ""                                                  
#> # … avec 50 autres lignes

(Veuillez noter les valeurs vides, "" dans le champ title, cela est dû à des erreurs survenues lors de la saisie des données dans caractères_got: en réalité, les personnages pour lesquels il n'y a pas de titres de livres et de séries correspondants dans le champ title devraient avoir un vecteur de longueur 0, et non un vecteur de longueur 1 contenant une chaîne vide.)

Nous pouvons réécrire l'exemple ci-dessus en utilisant la fonction unnest_auto(). Cette approche est appropriée pour une analyse ponctuelle, mais il n'est pas conseillé de compter sur unnest_auto() pour une utilisation régulière. En effet, si votre structure de données change, unnest_auto() peut modifier le mécanisme de transformation des données choisi si à l'origine il dépliait les colonnes-liste en lignes en utilisant unnest_longer(), alors en cas de changement de la structure des données d'entrée, la logique peut être modifiée en faveur de unnest_wider(), et l'utilisation de cette approche de manière permanente peut entraîner des erreurs imprévues.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> En utilisant `unnest_wider(char)` ; les éléments ont 18 noms en commun
#> En utilisant `unnest_longer(title)` ; aucun élément n'a de noms
#> # Un tibble : 60 x 2
#>    name              title                                               
#>                                                                
#>  1 Theon Greyjoy     Prince de Winterfell                                
#>  2 Theon Greyjoy     Capitaine de Sea Bitch                             
#>  3 Theon Greyjoy     Seigneur des Îles de Fer (par la loi des terres vertes)
#>  4 Tyrion Lannister  Main du Roi par intérim (ancien)                   
#>  5 Tyrion Lannister  Maître des Finances (ancien)                       
#>  6 Victarion Greyjoy Commandant de la Flotte de Fer                      
#>  7 Victarion Greyjoy Maître de l'Iron Victory                           
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Capitaine de la Garde à Sunspear                   
#> 10 Chett             ""                                                  
#> # … avec 50 lignes supplémentaires

Géocodage avec Google

Nous allons maintenant examiner une structure de données plus complexe provenant du service de géocodage de Google. Le cache des informations d'identification va à l'encontre des conditions d'utilisation de l'API Google Maps, donc je vais d'abord écrire une simple enveloppe pour l'API. Celle-ci est basée sur le stockage de la clé API Google Maps dans une variable d'environnement ; si vous n'avez pas enregistré de clé pour utiliser l'API Google Maps dans vos variables d'environnement, les morceaux de code présentés dans cette section ne s'exécuteront pas.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Aucune clé API Google Maps trouvée ; les morceaux de code ne seront pas exécutés")
}

# https://developers.google.com/maps/documentation/geocoding
geocode <- function(address, api_key = Sys.getenv("GOOGLE_MAPS_API_KEY")) {
  url <- "https://maps.googleapis.com/maps/api/geocode/json"
  url <- paste0(url, "?address=", URLencode(address), "&key=", api_key)

  jsonlite::read_json(url)
}

La liste renvoyée par cette fonction est assez complexe :

houston  Liste de 2
#>  $ résultats: Liste de 1
#>   ..$ : Liste de 5
#>   .. ..$ composants_adresse: Liste de 4
#>   .. .. ..$ : Liste de 3
#>   .. .. .. ..$ nom_long : chr "Houston"
#>   .. .. .. ..$ nom_court: chr "Houston"
#>   .. .. .. ..$ types     : Liste de 2
#>   .. .. .. .. ..$ : chr "localité"
#>   .. .. .. .. ..$ : chr "politique"
#>   .. .. ..$ : Liste de 3
#>   .. .. .. ..$ nom_long : chr "Comté de Harris"
#>   .. .. .. ..$ nom_court: chr "Comté de Harris"
#>   .. .. .. ..$ types     : Liste de 2
#>   .. .. .. .. ..$ : chr "niveau_de_zone_administratif_2"
#>   .. .. .. .. ..$ : chr "politique"
#>   .. .. ..$ : Liste de 3
#>   .. .. .. ..$ nom_long : chr "Texas"
#>   .. .. .. ..$ nom_court: chr "TX"
#>   .. .. .. ..$ types     : Liste de 2
#>   .. .. .. .. ..$ : chr "niveau_de_zone_administratif_1"
#>   .. .. .. .. ..$ : chr "politique"
#>   .. .. ..$ : Liste de 3
#>   .. .. .. ..$ nom_long : chr "États-Unis"
#>   .. .. .. ..$ nom_court: chr "US"
#>   .. .. .. ..$ types     : Liste de 2
#>   .. .. .. .. ..$ : chr "pays"
#>   .. .. .. .. ..$ : chr "politique"
#>   .. ..$ adresse_formatée : chr "Houston, TX, USA"
#>   .. ..$ géométrie          : Liste de 4
#>   .. .. ..$ limites       : Liste de 2
#>   .. .. .. ..$ nord-est: Liste de 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ sud-ouest: Liste de 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. .. ..$ emplacement     : Liste de 2
#>   .. .. .. ..$ lat: num 29.8
#>   .. .. .. ..$ lng: num -95.4
#>   .. .. ..$ type_emplacement: chr "APPROXIMATIF"
#>   .. .. ..$ fenêtre_affichage     : Liste de 2
#>   .. .. .. ..$ nord-est: Liste de 2
#>   .. .. .. .. ..$ lat: num 30.1
#>   .. .. .. .. ..$ lng: num -95
#>   .. .. .. ..$ sud-ouest: Liste de 2
#>   .. .. .. .. ..$ lat: num 29.5
#>   .. .. .. .. ..$ lng: num -95.8
#>   .. ..$ id_lieu          : chr "ChIJAYWNSLS4QIYROwVl894CDco"
#>   .. ..$ types             : Liste de 2
#>   .. .. ..$ : chr "localité"
#>   .. .. ..$ : chr "politique"
#>  $ statut : chr "OK"

Heureusement, nous pouvons résoudre ce problème de conversion de ces données en format tabulaire étape par étape grâce à des fonctions tidyr. Pour rendre la tâche un peu plus complexe et réaliste, je vais commencer par géocoder plusieurs villes :

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

Le résultat obtenu sera transformé en tibble, pour plus de commodité, j'ajouterai une colonne portant le nom de la ville correspondante.

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

Le premier niveau contient des composants statut et result, que nous pouvons déplier à l'aide de unnest_wider() :

loc %>%
  unnest_wider(json)
#> # Un tibble: 5 x 3
#>   ville        résultats    statut
#>                
#> 1 Houston      OK    
#> 2 LA           OK    
#> 3 New York     OK    
#> 4 Chicago      OK    
#> 5 Springfield  OK

Veuillez noter que résultats est une liste à plusieurs niveaux. La plupart des villes ont 1 élément (représentant une valeur unique associée à l'API de géocodage), mais Springfield en a deux. Nous pouvons les extraire en lignes séparées à l'aide de 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

Maintenant, tous ont des composants identiques, ce que nous pouvons vérifier avec 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

Nous pouvons trouver les coordonnées de latitude et de longitude de chaque ville en développant la liste 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

Et ensuite la localisation, pour cela il est nécessaire de développer 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

Encore une fois, unnest_auto() simplifie l'opération décrite avec certains risques qui peuvent être causés par des changements dans la structure des données d'entrée :

loc %>%
  unnest_auto(json) %>%
  unnest_auto(results) %>%
  unnest_auto(results) %>%
  unnest_auto(geometry) %>%
  unnest_auto(location)
#>% Utilisation de `unnest_wider(json)` ; les éléments ont 2 noms en commun
#>% Utilisation de `unnest_longer(results)` ; aucun élément n'a de noms
#>% Utilisation de `unnest_wider(results)` ; les éléments ont 5 noms en commun
#>% Utilisation de `unnest_wider(geometry)` ; les éléments ont 4 noms en commun
#>% Utilisation de `unnest_wider(location)` ; les éléments ont 2 noms en commun
#>% # 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… % # … avec 4 autres variables : viewport , place_id , types ,
#>% #   statut

Nous pouvons également simplement examiner la première adresse pour chaque ville :

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… % # … avec 4 autres variables : viewport , place_id , types ,
#>% #   statut

Ou utiliser hoist() pour une immersion multi-niveaux, afin de passer directement à lat et lng.

loc %>%
  hoist(json,
    lat = list("results", 1, "geometry", "location", "lat"),
    lng = list("results", 1, "geometry", "location", "lng")
  )
#> # Un tibble: 5 x 4
#>   city          lat    lng json            
#>                       
#> 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

Discographie de Sharla Gelfand

Pour conclure, nous allons examiner la structure la plus complexe - la discographie de Sharly Gel'fand. Comme dans les exemples précédents, nous commencerons par convertir la liste en un cadre de données avec une seule colonne, puis nous l'étendrons pour que chaque composant soit dans une colonne séparée. Je vais également transformer la colonne date_added au format date et heure approprié dans R.

discs % 
  unnest_wider(disc) %>% 
  mutate(date_added = as.POSIXct(strptime(date_added, "%Y-%m-%dT%H:%M:%S"))) 
discs
#> # Un tibble: 155 x 5
#>    instance_id date_added          basic_information       id rating
#>                                          
#>  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
#> # … avec 145 lignes de plus

À ce niveau, nous avons obtenu des informations sur la date à laquelle chaque disque a été ajouté à la discographie de Sharly, mais nous ne voyons pas encore de données sur ces disques. Pour cela, nous devons élargir la colonne basic_information:

discs %>% unnest_wider(basic_information)
#> Le nom de la colonne `id` ne doit pas être dupliqué.
#> Utilisez .name_repair pour spécifier la réparation.

Malheureusement, nous obtiendrons une erreur, car à l'intérieur de la liste basic_information il y a une colonne du même nom. basic_informationLorsqu'une telle erreur se produit, pour identifier rapidement sa cause, vous pouvez utiliser names_repair = "unique":

discs %>% unnest_wider(basic_information, names_repair = "unique")
#> Nouvelles noms :
#> * id -> id...6
#> * id -> id...14
#> # Un 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  # … avec 145 lignes supplémentaires, et 7 variables de plus : formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , id...14 , rating

Le problème est que basic_information répète la colonne id qui est également stockée au niveau supérieur, donc nous pouvons simplement l'éliminer :

discs %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # Un tibble : 155 x 14
#>    instance_id date_added          labels  year artists     id thumb title
#>                                
#>  1   354823933 2019-02-16 17:48:59 <list…  2015   2   354092601 2019-02-13 14:13:11 <list…  2013   3   354091476 2019-02-13 14:07:23 <list…  2017   4   351244906 2019-02-02 11:39:58 <list…  2017   5   351244801 2019-02-02 11:39:37 <list…  2015   6   351052065 2019-02-01 20:40:53 <list…  2019   7   350315345 2019-01-29 15:48:37 <list…  2014   8   350315103 2019-01-29 15:47:22 <list…  2015   9   350314507 2019-01-29 15:44:08 <list…  2017  10   350314047 2019-01-29 15:41:35 <list…  2017  # … avec 145 lignes supplémentaires, et 6 variables de plus : formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , rating

En alternative, nous pourrions utiliser 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…
#> # … avec 145 autres lignes, et 3 autres variables : basic_information ,
#> #   id , rating

Ici, j'extrais rapidement le nom du premier label et de l'artiste par indice, en plongeant dans une liste imbriquée.

Une approche plus systématique consiste à créer des tables distinctes pour l'artiste et le 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
#> # … avec 157 autres lignes

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    
#> # … avec 270 autres lignes

Ensuite, vous pouvez les rejoindre à nouveau avec l'ensemble de données d'origine si nécessaire.

Conclusion

Au cœur de la bibliothèque tidyverse se trouvent de nombreux packages utiles unis par une philosophie commune de traitement des données.

Dans cet article, nous avons examiné la famille de fonctions unnest_*(), qui sont destinées à travailler avec l'extraction d'éléments à partir de listes imbriquées. Ce package contient de nombreuses autres fonctions utiles qui facilitent la transformation des données selon le concept de Données Propre.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster