5

I have a nested list of lists:

data = list(a = list(1, 2, 3), b = list("foo"), c = list("toast", "onions"))

How can I convert this into a single row of a data.frame or tibble? I would like the lists with more than one element (a and c here) to be kept as lists, and the single-element (b) to be a regular value.

Expected output is:

# A tibble: 1 x 3
  a          b     c         
  <list>     <chr> <list>    
1 <list [3]> foo   <list [2]>
D Greenwood
  • 415
  • 2
  • 11
  • 1
    `tibble::enframe(data) %>% tidyr::pivot_wider()` ? – Ronak Shah Oct 20 '20 at 09:08
  • Yes, that works for me Ronak. Thanks for your help. What would be the best way to then convert column b to a regular character column? I have tried `... %>% mutate(b = map(b, unlist))`, but this doesn't change the actual column type. – D Greenwood Oct 20 '20 at 09:15
  • [Ronak] + `%>% tidyr::unnest(b) %>% tidyr::unnest(b)` – Edo Oct 20 '20 at 09:17
  • Thanks Ronak. Also works if I change `map` to `map_chr`, i.e. `... %>% mutate(b = map_chr(b, unlist))'. – D Greenwood Oct 20 '20 at 09:18

4 Answers4

3

What about this?

> as_tibble_row(Map(function(x) ifelse(length(x)==1,unlist(x),list(x)),data))
# A tibble: 1 x 3
  a          b     c
  <list>     <chr> <list>
1 <list [3]> foo   <list [2]>
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81
1
data[] <- lapply(data, function(x) if (length(x) == 1) x[[1]] else list(x))
data.table::setDF(data)

# > str(data)
# 'data.frame': 1 obs. of  3 variables:
#  $ a:List of 1
#   ..$ :List of 3
#   .. ..$ : num 1
#   .. ..$ : num 2
#   .. ..$ : num 3
#  $ b: chr "foo"
#  $ c:List of 1
#   ..$ :List of 2
#   .. ..$ : chr "toast"
#   .. ..$ : chr "onions"
s_baldur
  • 29,441
  • 4
  • 36
  • 69
1

You can use enframe + pivot_wider

tibble::enframe(data) %>% tidyr::pivot_wider() 
#      a          b          c         
#  <list>     <list>     <list>    
#1 <list [3]> <list [1]> <list [2]>

To get length one column as vector we can add :

library(dplyr)

tibble::enframe(data) %>% 
  tidyr::pivot_wider() %>%
  summarise(across(.fns = ~if(length(unlist(.)) == 1) unlist(.) else .))

#      a          b     c         
#  <list>     <chr> <list>    
#1 <list [3]> foo   <list [2]>
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
1

An option is to create a two column dataset with aggregate from base R

aggregate(values ~ ind, stack(data), list)
akrun
  • 874,273
  • 37
  • 540
  • 662