Desdobrando colunas aninhadas — listas usando a linguagem R (pacote tidyr e funções da família unnest)

Na maioria dos casos, ao lidar com a resposta recebida da API ou com quaisquer outros dados que tenham uma estrutura hierárquica complexa, você se depara com os formatos JSON e XML.

Esses formatos têm muitas vantagens: eles armazenam os dados de forma bastante compacta e evitam a duplicação excessiva de informações.

Um ponto negativo desses formatos é a complexidade do seu processamento e análise. Dados não estruturados não podem ser usados em cálculos e não podem ser visualizados.

Desdobrando colunas aninhadas — listas usando a linguagem R (pacote tidyr e funções da família unnest)

Este artigo é uma continuação lógica da publicação "O pacote R tidyr e suas novas funções pivot_longer e pivot_wider". Ele ajudará você a transformar estruturas de dados não estruturadas em uma forma tabular familiar e utilizável para análise, usando o pacote tidyr, que faz parte do núcleo da biblioteca tidyverse, e suas funções da família unnest_*().

Conteúdo

youtube telegram e canais. A maior parte do conteúdo é dedicada à linguagem R. O processo de construção do bot

  1. Introdução
  2. Usuários do GitHub
  3. Repositórios do Github
  4. Personagens de Game of Thrones
  5. Geocodificação com o Google
  6. Discografia de Sharly Gelfand
  7. Conclusão

Introdução

Retangulamento (nota do tradutor: não encontrei uma tradução adequada para esse termo, então vamos deixá-lo como está.) — é o processo de transformar dados não estruturados com arrays aninhados em uma tabela bidimensional, composta por linhas e colunas familiares. No tidyr existem várias funções que ajudarão você a expandir colunas-listas aninhadas e trazer os dados para uma forma retangular e tabular:

  • unnest_longer() pega cada elemento da lista-coluna e cria uma nova linha.
  • unnest_wider() pega cada elemento da lista-coluna e cria uma nova coluna.
  • unnest_auto() determina automaticamente qual das funções usar
    unnest_longer() ou unnest_wider().
  • hoist() é semelhante a unnest_wider() mas seleciona apenas os componentes especificados e permite trabalhar com vários níveis de aninhamento.

A maioria dos problemas relacionados à transformação de dados não estruturados com múltiplos níveis de aninhamento em uma tabela bidimensional pode ser resolvida combinando as funções listadas com dplyr.

Para demonstrar essas técnicas, utilizaremos o pacote repurrrsive, que fornece várias listas complexas e multilayer, obtidas de uma API da web.

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

Usuários do GitHub

Vamos começar com gh_users, uma lista que contém informações sobre seis usuários do GitHub. Primeiro, vamos transformar a lista gh_users em tibble em um data frame:

users <- tibble(user = gh_users) 

Isso parece um pouco ilógico: por que trazer uma lista gh_users, para uma estrutura de dados mais complexa? Mas o data frame tem uma grande vantagem: ele combina vários vetores, de modo que tudo é rastreado em um único objeto.

Cada elemento do objeto tinham endereços de e-mail em domínios representa uma lista nomeada, onde cada elemento representa uma coluna.

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"

Existem duas maneiras de transformar componentes da lista em colunas. unnest_wider() pega cada componente e cria uma nova coluna:

users %>% unnest_wider(user)
#> # A tibble: 6 x 30
#>   login     id avatar_url gravatar_id url   html_url followers_url
#>   <chr>  <int> <chr>      <chr>       <chr> <chr>    <chr>        
#> 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.…
#> # … com mais 23 variáveis: following_url <chr>, gists_url <chr>,
#> #   starred_url <chr>, subscriptions_url <chr>, organizations_url <chr>,
#> #   repos_url <chr>, events_url <chr>, received_events_url <chr>,
#> #   type <chr>, site_admin <lgl>, name <chr>, company <chr>, blog <chr>,
#> #   location <chr>, email <chr>, public_repos <int>, public_gists <int>,
#> #   followers <int>, following <int>, created_at <chr>, updated_at <chr>,
#> #   bio <chr>, hireable <lgl>

Neste caso, obtemos uma tabela composta por 30 colunas, e a maioria delas não será necessária, portanto podemos usar em vez de unnest_wider() utilizados hoist(). hoist() nos permite extrair componentes selecionados, usando a mesma sintaxe que purrr::pluck():

usuários %> hoist(user, 
  seguidores = "followers", 
  login = "login", 
  url = "html_url"
)
#> # Um tibble: 6 x 4
#>   seguidores login       url                            usuário             
#>                                                    
#> 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() remove os componentes nomeados especificados da lista-coluna user, portanto você pode considerar hoist() como a movimentação de componentes da lista interna do data frame para seu nível superior.

Repositórios do Github

Alinhamento da lista gh_repos começamos de maneira semelhante, convertendo-o em tibble:

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

Desta vez, os elementos user representam uma lista de repositórios pertencentes a este usuário. Cada repositório é uma observação separada, portanto, de acordo com o conceito de dados organizados (nota: dados organizados) eles devem se tornar novas linhas, e por isso usamos unnest_longer() em vez de unnest_wider():

repos % unnest_longer(repo)
repos
#> # Um tibble: 176 x 1
#>    repo             
#>               
#>  1 
#>  2 
#>  3 
#>  4 
#>  5 
#>  6 
#>  7 
#>  8 
#>  9 
#> 10 
#> # … com 166 linhas adicionais

Agora podemos usar unnest_wider() ou hoist() :

repos %> hoist(repo, 
  login = c("owner", "login"), 
  nome = "name",
  homepage = "homepage",
  watchers = "watchers_count"
)
#> # Um tibble: 176 x 5
#>    login       nome        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 
#> # … com 166 linhas adicionais

Preste atenção no uso de c("owner", "login"): isso nos permite obter o valor de segundo nível da lista aninhada ownerUma abordagem alternativa consiste em obter toda a lista owner e, em seguida, usar a função unnest_wider() para colocar cada um de seus elementos em uma coluna:

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.…
#> # … com 166 linhas a mais e 11 variáveis adicionais: following_url ,
#> #   gists_url , starred_url , subscriptions_url ,
#> #   organizations_url , repos_url , events_url ,
#> #   received_events_url , type , site_admin , repo

Em vez de pensar sobre qual função escolher unnest_longer() ou unnest_wider() você pode usar unnest_auto(). Esta função utiliza vários métodos heurísticos para encontrar a função mais adequada para a transformação dos dados e fornece uma mensagem sobre a abordagem escolhida.

tibble(repo = gh_repos) %>% 
  unnest_auto(repo) %>% 
  unnest_auto(repo)
#> Usando `unnest_longer(repo)`; nenhum elemento tem nomes
#> Usando `unnest_wider(repo)`; os elementos têm 68 nomes em comum
#> # Um tibble: 176 x 67
#>        id nome  nome_completo proprietário privado url_html descrição fork  url  
#>                                       
#>  1 6.12e7 depois gaborcsa…   2 4.05e7 argu… gaborcsa…   3 3.64e7 pergunte 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… FALSO   https:/…         VERDADEIRO  http…
#> 10 2.43e7 cray… gaborcsa…  # … com 166 linhas a mais, e 58 variáveis a mais: 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

Personagens de Game of Thrones

got_chars tem uma estrutura idêntica a gh_users: é um conjunto de listas nomeadas, onde cada elemento da lista interna descreve um determinado atributo de um personagem de Game of Thrones. A conversão got_chars para uma forma tabular começa com a criação de um dataframe, assim como nos exemplos anteriores, e depois traduzimos cada elemento em uma coluna separada:

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… Masculino Ironbo… Em 2… ""    TRUE  <chr … <chr [… ""    
#>  2 http…  1052 Tyri… Masculino ""      Em 2… ""    TRUE  <chr … <chr [… ""    
#>  3 http…  1074 Vict… Masculino Ironbo… Em 2… ""    TRUE  <chr … <chr [… ""    
#>  4 http…  1109 Will  Masculino ""      ""    Em 2… FALSE <chr … <chr [… ""    
#>  5 http…  1166 Areo… Masculino Norvos… Em 2… ""    TRUE  <chr … <chr [… ""    
#>  6 http…  1267 Chett Masculino ""      At H… Em 2… FALSE <chr … <chr [… ""    
#>  7 http…  1295 Cres… Masculino ""      Em 2… Em 2… FALSE <chr … <chr [… ""    
#>  8 http…   130 Aria… Feminino Dornish Em 2… ""    TRUE  <chr … <chr [… ""    
#>  9 http…  1303 Daen… Feminino Valyri… Em 2… ""    TRUE  <chr … <chr [… ""    
#> 10 http…  1319 Davo… Masculino Wester… Em 2… ""    TRUE  <chr … <chr [… ""    
#> # … with 20 more rows, and 7 more variables: mother <chr>, spouse <chr>,
#> #   allegiances <list>, books <list>, povBooks <list>, tvSeries <list>,
#> #   playedBy <list>

Estrutura got_chars um pouco mais complicado do que gh_users, pois alguns componentes da lista char são eles mesmos listas, resultando em colunas — listas:

chars2 %>% select_if(is.list)
#> # A tibble: 30 x 7
#>    titles    aliases    allegiances books     povBooks  tvSeries  playedBy 
#>                                  
#>  1 <chr [3]> <chr [4]>  <chr [1]>   <chr [3]> <chr [2]> <chr [6]> <chr [1]>
#>  2 <chr [2]> <chr [11]> <chr [1]>   <chr [2]> <chr [4]> <chr [6]> <chr [1]>
#>  3 <chr [2]> <chr [1]>  <chr [1]>   <chr [3]> <chr [2]> <chr [1]> <chr [1]>
#>  4 <chr [1]> <chr [1]>  <???>       <chr [1]> <chr [1]> <chr [1]> <chr [1]>
#>  5 <chr [1]> <chr [1]>  <chr [1]>   <chr [3]> <chr [2]> <chr [2]> <chr [1]>
#>  6 <chr [1]> <chr [1]>  <???>       <chr [2]> <chr [1]> <chr [1]> <chr [1]>
#>  7 <chr [1]> <chr [1]>  <???>       <chr [2]> <chr [1]> <chr [1]> <chr [1]>
#>  8 <chr [1]> <chr [1]>  <chr [1]>   <chr [4]> <chr [1]> <chr [1]> <chr [1]>
#>  9 <chr [5]> <chr [11]> <chr [1]>   <chr [1]> <chr [4]> <chr [6]> <chr [1]>
#> 10 <chr [4]> <chr [5]>  <chr [2]>   <chr [1]> <chr [3]> <chr [5]> <chr [1]>
#> # … with 20 more rows

Suas próximas ações dependem dos objetivos da análise. Pode ser necessário inserir informações em linhas sobre cada livro e série em que o personagem aparece:

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
#> # … com mais 170 linhas

Ou talvez você queira criar uma tabela que permita corresponder o personagem à obra:

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             ""                                                  
#> # … com mais 50 linhas

(Observe os valores em branco, "" no campo o título, isso se deve a erros cometidos na entrada de dados em got_chars: na verdade, os personagens para os quais não há títulos correspondentes de livros e séries no campo o título devem ter um vetor de comprimento 0, e não um vetor de comprimento 1 contendo uma string vazia.)

Podemos reescrever o exemplo acima usando a função unnest_auto(). Essa abordagem é conveniente para análises pontuais, mas não deve ser usada de forma regular. unnest_auto() O problema é que se a sua estrutura de dados mudar, unnest_auto() pode alterar o mecanismo de transformação de dados escolhido. Se inicialmente ele expandiu as colunas-listas em linhas usando unnest_longer(), então, ao mudar a estrutura dos dados de entrada, a lógica pode ser alterada em favor de unnest_wider(), e o uso de tal abordagem de forma contínua pode levar a erros inesperados.

tibble(char = got_chars) %>% 
  unnest_auto(char) %>% 
  select(name, title = titles) %>% 
  unnest_auto(title)
#> Usando `unnest_wider(char)`; elementos têm 18 nomes em comum
#> Usando `unnest_longer(title)`; nenhum elemento possui nomes
#> # Um tibble: 60 x 2
#>    name              title                                               
#>                                                                
#>  1 Theon Greyjoy     Príncipe de Winterfell                                
#>  2 Theon Greyjoy     Capitão da Sea Bitch                                
#>  3 Theon Greyjoy     Senhor das Ilhas de Ferro (por lei das terras verdes)
#>  4 Tyrion Lannister  Mão do Rei (anterior)                             
#>  5 Tyrion Lannister  Mestre do Dinheiro (anterior)                             
#>  6 Victarion Greyjoy Senhor Capitão da Frota de Ferro                      
#>  7 Victarion Greyjoy Mestre da Vitória de Ferro                          
#>  8 Will              ""                                                  
#>  9 Areo Hotah        Capitão da Guarda em Sunspear                    
#> 10 Chett             ""                                                  
#> # … com 50 linhas a mais

Geocodificação com o Google

A seguir, examinaremos uma estrutura de dados mais complexa obtida do serviço de geocodificação do Google. O armazenamento em cache das credenciais contradiz os termos de uso da API do Google Maps, portanto, primeiro escrevei uma simples interface para a API. Que é baseada no armazenamento da chave da API do Google Maps em uma variável de ambiente; se você não tiver a chave salva nas variáveis de ambiente para trabalhar com a API do Google Maps, os trechos de código apresentados nesta seção não serão executados.

has_key <- !identical(Sys.getenv("GOOGLE_MAPS_API_KEY"), "")
if (!has_key) {
  message("Nenhuma chave da API do Google Maps encontrada; os trechos de código não serão executados")
}

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

A lista retornada por esta função é bastante complexa:

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

Felizmente, podemos resolver passo a passo o problema de converter esses dados em formato tabular usando funções tidyr. Para tornar a tarefa um pouco mais complexa e realista, começarei geocodificando algumas cidades:

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

O resultado obtido será transformado em tibble, para facilitar, adicionarei uma coluna com o nome correspondente da cidade.

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

O primeiro nível contém os componentes status e result, que podemos expandir usando 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

Observe que results é uma lista multilayer. A maioria das cidades tem 1 elemento (representando um valor único correspondente à API de geocodificação), mas Springfield tem dois. Podemos extraí-los em linhas separadas usando 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

Agora todos eles têm os mesmos componentes, o que pode ser verificado com 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

Podemos encontrar as coordenadas de latitude e longitude de cada cidade expandindo a lista 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…  # … com 4 variáveis a mais: viewport , place_id , types ,
#> #   status

E então a localização, para isso é necessário expandir localização:

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

Novamente, unnest_auto() simplifica a operação descrita com alguns riscos que podem ser causados pela mudança na estrutura dos dados de entrada:

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

Podemos também simplesmente olhar para o primeiro endereço de cada cidade:

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

Ou usar hoist() para imersão em camadas, a fim de ir diretamente para lat e lng.

loc %>%
  hoist(json,
    lat = list("results", 1, "geometry", "location", "lat"),
    lng = list("results", 1, "geometry", "location", "lng")
  )
# > # A tibble: 5 x 4
# >   city          lat    lng json            
# >                       
# > 1 Houston      29.8  -95.4 
# > 2 LA           34.1 -118.  
# > 3 New York     40.7  -74.0 
# > 4 Chicago      41.9  -87.6 
# > 5 Springfield  37.2  -93.3

Discografia de Sharly Gelfand

No final, vamos analisar a estrutura mais complexa — a discografia de Sharla Gelfand. Assim como nos exemplos anteriores, começamos convertendo a lista em um dataframe com uma coluna, e então ampliaremos, para que cada componente fique em uma coluna separada. Também vou converter a coluna date_added para o formato correspondente de data e hora no R.

discs % 
  unnest_wider(disc) %>% 
  mutate(date_added = as.POSIXct(strptime(date_added, "%Y-%m-%dT%H:%M:%S"))) 
discs
# > # A tibble: 155 x 5
# >    instance_id date_added          basic_information       id rating
# >                                          
# >  1   354823933 2019-02-16 17:48:59   7496378      0
# >  2   354092601 2019-02-13 14:13:11   4490852      0
# >  3   354091476 2019-02-13 14:07:23   9827276      0
# >  4   351244906 2019-02-02 11:39:58   9769203      0
# >  5   351244801 2019-02-02 11:39:37   7237138      0
# >  6   351052065 2019-02-01 20:40:53  13117042      0
# >  7   350315345 2019-01-29 15:48:37   7113575      0
# >  8   350315103 2019-01-29 15:47:22  10540713      0
# >  9   350314507 2019-01-29 15:44:08  11260950      0
# > 10   350314047 2019-01-29 15:41:35  11726853      0
# > # … with 145 more rows

Neste nível, conseguimos informações sobre quando cada disco foi adicionado à discografia da Sharla, mas ainda não vemos dados sobre esses discos. Para isso, precisamos expandir a coluna basic_information:

discs %>% unnest_wider(basic_information)
# > O nome da coluna `id` não pode ser duplicado.
# > Use .name_repair para especificar a correção.

Infelizmente, receberemos um erro, pois dentro da lista basic_information existe uma coluna homônima basic_information. Ao ocorrer esse tipo de erro, para identificar rapidamente a causa, podemos usar names_repair = "unique":

discs %>% unnest_wider(basic_information, names_repair = "unique")
#> Novos nomes:
#> * id -> id...6
#> * id -> id...14
#> # Um 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  # … com 145 mais linhas e 7 mais variáveis: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , id...14 , rating

O problema é que basic_information repete a coluna id que também está armazenada no nível superior, então podemos simplesmente removê-la:

discs %>% 
  select(-id) %>% 
  unnest_wider(basic_information)
#> # Um 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  # … com 145 mais linhas e 6 mais variáveis: formats ,
#> #   cover_image , resource_url , master_id ,
#> #   master_url , rating

Como alternativa, poderíamos usar 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…
#> # … com 145 linhas a mais e 3 variáveis a mais: basic_information ,
#> #   id , rating

Aqui, extraio rapidamente o nome do primeiro rótulo e do artista pelo índice, mergulhando em uma lista aninhada.

Uma abordagem mais sistemática envolve a criação de tabelas separadas para o artista e o rótulo:

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
#> # … com 157 linhas a mais

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    
#> # … com 270 linhas a mais

Depois, você pode juntá-los novamente ao conjunto de dados original conforme necessário.

Conclusão

A biblioteca central tidyverse inclui uma variedade de pacotes úteis que compartilham uma filosofia comum de processamento de dados.

Neste artigo, examinamos uma família de funções unnest_*(), que são voltadas para a extração de elementos de listas aninhadas. Este pacote contém diversas outras funções úteis que simplificam a transformação de dados de acordo com o conceito de Dados Organizados.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster