DeduciveR
DeduciveR

Reputation: 1702

Odd behaviour for converting a list of named characters to a data frame

I work with lists from APIs reasonably often, but I can't convert this simple list to a df.

Reprex data:

my_ls <- list(list(ip = "104.28.131.102", type = "ipv4", continent_code = "NA", 
continent_name = "North America", country_code = "US", country_name = "United States", 
region_code = "CA", region_name = "California", city = "San Jose", 
zip = "95122", latitude = 37.3305282592773, longitude = -121.838226318359, 
location = list(geoname_id = 5392171L, capital = "Washington D.C.", 
    languages = list(list(code = "en", name = "English", 
        native = "English")), country_flag = "https://assets.ipstack.com/flags/us.svg", 
    country_flag_emoji = "🇺🇸", country_flag_emoji_unicode = "U+1F1FA U+1F1F8", 
    calling_code = "1", is_eu = FALSE)), list(ip = "109.206.213.143", 
type = "ipv4", continent_code = "EU", continent_name = "Europe", 
country_code = "PL", country_name = "Poland", region_code = "MZ", 
region_name = "Mazovia", city = "Piastów", zip = "05-850", 
latitude = 52.2565498352051, longitude = 20.7680397033691, 
location = list(geoname_id = 762381L, capital = "Warsaw", 
    languages = list(list(code = "pl", name = "Polish", native = "Polski")), 
    country_flag = "https://assets.ipstack.com/flags/pl.svg", 
    country_flag_emoji = "🇵🇱", country_flag_emoji_unicode = "U+1F1F5 U+1F1F1", 
    calling_code = "48", is_eu = TRUE)))

I want to get this into a simple data frame with 22 cols and 2 rows.

I've tried several things, but this illustrates the problem:

library(tidyverse)

my_df <- my_ls %>% 
  map(unlist) %>%
  map(tibble)

The resulting data frame still has a list level of a character within it where I would have expected unlist to remove that into a named character vector ready for tibble to do its job, but there's still a list element in there for every tibble.

head(my_df)

    [[1]]
# A tibble: 22 × 1
   `<chr>`       
   <chr>         
 1 104.28.131.102
 2 ipv4          
 3 NA            
 4 North America 
 5 US            
 6 United States 
 7 CA            
 8 California    
 9 San Jose      
10 95122         
# … with 12 more rows

I'm stumped - I'm sure it's something simple. Thanks.

Upvotes: 0

Views: 38

Answers (1)

akrun
akrun

Reputation: 887741

The reason is that unlist returns a vector and tibble/data.frame directly applied on that vector returns a single column. Instead, the vector needs to be either transposed (t) or convert to list (as.data.frame.list in the current solution) to return as many columns as the length of the vector

library(purrr)
library(dplyr)
out <- map_dfr(my_ls, ~ unlist(.x) %>%
   as.data.frame.list())  %>%
   type.convert(as.is = TRUE)

-output

> str(out)
'data.frame':   2 obs. of  22 variables:
 $ ip                                 : chr  "104.28.131.102" "109.206.213.143"
 $ type                               : chr  "ipv4" "ipv4"
 $ continent_code                     : chr  NA "EU"
 $ continent_name                     : chr  "North America" "Europe"
 $ country_code                       : chr  "US" "PL"
 $ country_name                       : chr  "United States" "Poland"
 $ region_code                        : chr  "CA" "MZ"
 $ region_name                        : chr  "California" "Mazovia"
 $ city                               : chr  "San Jose" "Piastów"
 $ zip                                : chr  "95122" "05-850"
 $ latitude                           : num  37.3 52.3
 $ longitude                          : num  -121.8 20.8
 $ location.geoname_id                : int  5392171 762381
 $ location.capital                   : chr  "Washington D.C." "Warsaw"
 $ location.languages.code            : chr  "en" "pl"
 $ location.languages.name            : chr  "English" "Polish"
 $ location.languages.native          : chr  "English" "Polski"
 $ location.country_flag              : chr  "https://assets.ipstack.com/flags/us.svg" "https://assets.ipstack.com/flags/pl.svg"
 $ location.country_flag_emoji        : chr  "🇺🇸" "🇵🇱"
 $ location.country_flag_emoji_unicode: chr  "U+1F1FA U+1F1F8" "U+1F1F5 U+1F1F1"
 $ location.calling_code              : int  1 48
 $ location.is_eu                     : logi  FALSE TRUE

Upvotes: 1

Related Questions