Search code examples
rtidyrtibble

Unnest a list column directly into several columns


Can I unnest a list column directly into n columns?

The list can be assumed to regular, with all elements being of equal length.

If instead of a list column I would have a character vector, I could tidyr::separate. I can tidyr::unnest, but we need another helper variable to be able to tidyr::spread. Am I missing an obvious method?

Example data:

library(tibble)

df1 <- data_frame(
  gr = c('a', 'b', 'c'),
  values = list(1:2, 3:4, 5:6)
)
# A tibble: 3 x 2
  gr    values   
  <chr> <list>   
1 a     <int [2]>
2 b     <int [2]>
3 c     <int [2]>

Goal:

df2 <- data_frame(
  gr = c('a', 'b', 'c'),
  V1 = c(1, 3, 5),
  V2 = c(2, 4, 6)
)
# A tibble: 3 x 3
  gr       V1    V2
  <chr> <dbl> <dbl>
1 a        1.    2.
2 b        3.    4.
3 c        5.    6.

Current method:

unnest(df1) %>% 
  group_by(gr) %>% 
  mutate(r = paste0('V', row_number())) %>% 
  spread(r, values)

Solution

  • with tidyr 1.0.0 you can do :

    library(tidyr)
    df1 <- tibble(
      gr = c('a', 'b', 'c'),
      values = list(1:2, 3:4, 5:6)
    )
    
    unnest_wider(df1, values)
    #> New names:
    #> * `` -> ...1
    #> * `` -> ...2
    #> New names:
    #> * `` -> ...1
    #> * `` -> ...2
    #> New names:
    #> * `` -> ...1
    #> * `` -> ...2
    #> # A tibble: 3 x 3
    #>   gr     ...1  ...2
    #>   <chr> <int> <int>
    #> 1 a         1     2
    #> 2 b         3     4
    #> 3 c         5     6
    

    Created on 2019-09-14 by the reprex package (v0.3.0)

    The output is verbose here because the elements that were unnested horizontally (the vector elements) were not named, and unnest_wider doesn't want to guess silently.

    We can name them beforehand to avoid it :

    df1 %>%
      dplyr::mutate(values = purrr::map(values, setNames, c("V1","V2"))) %>%
      unnest_wider(values)
    #> # A tibble: 3 x 3
    #>   gr       V1    V2
    #>   <chr> <int> <int>
    #> 1 a         1     2
    #> 2 b         3     4
    #> 3 c         5     6
    

    Or just use suppressMessages() or purrr::quietly()