Repository: tidyverse/tidyr

Issue #645: Unnest shortcut
Created: 2019-06-02T12:42:30Z
If you have a list you have to first put it in a tibble (with a dummy column name), and then you have to unnest it using that name. Could we reduce into a single step?

Duplicate issue #646: `unnest` sometimes removes rows
Created: 2019-06-03T12:21:48Z
I encountered this when disentangling JSON data. NULL elements are converted by tibble into 0x0 tibbles. When unnesting these zero dimensional tibbles the row is removed and we lose data, and I didn't expect unnest to be able to remove data.
suppressPackageStartupMessages(library(tidyverse, warn.conflicts = FALSE, quiet = TRUE))
df1 <- tibble(id = 1:3, data = list(list(a=1,b=2), NULL , list(a=3,b=4)))
df1
#> # A tibble: 3 x 2
#>      id data      
#>   <int> <list>    
#> 1     1 <list [2]>
#> 2     2 <NULL>    
#> 3     3 <list [2]>

df2  <- df1 %>% mutate(data = map(data, as_tibble))
df2
#> # A tibble: 3 x 2
#>      id data            
#>   <int> <list>          
#> 1     1 <tibble [1 x 2]>
#> 2     2 <tibble [0 x 0]>
#> 3     3 <tibble [1 x 2]>

unnest(df2, data)
#> # A tibble: 2 x 3
#>      id     a     b
#>   <int> <dbl> <dbl>
#> 1     1     1     2
#> 2     3     3     4
Created on 2019-06-03 by the reprex package (v0.2.1)

I can see how it makes sense and `n` rows in the data to `unnest` give `n` rows in the output data, but on the other hand I believe you rarely wants to remove rows by using unnest (you'd typically use `filter` in a previous step).
In my case I'm using unnest to expand the table horizontally and I expect the number of rows to stay constant. This is my workaround, which is not obvious :
suppressPackageStartupMessages(library(tidyverse, warn.conflicts = FALSE, quiet = TRUE))
df1 <- tibble(id = 1:3, data = list(list(a=1,b=2), NULL , list(a=3,b=4)))
df1
#> # A tibble: 3 x 2
#>      id data      
#>   <int> <list>    
#> 1     1 <list [2]>
#> 2     2 <NULL>    
#> 3     3 <list [2]>

df2  <- df1 %>% mutate(data = map_if(data, is.null, ~tibble(.rows = 1), .else = as_tibble))
df2
#> # A tibble: 3 x 2
#>      id data                
#>   <int> <list>              
#> 1     1 <tibble [1 x 2]>    
#> 2     2 <tibble [1 x 0]>
#> 3     3 <tibble [1 x 2]>

unnest(df2, data)
#> # A tibble: 3 x 3
#>      id     a     b
#>   <int> <dbl> <dbl>
#> 1     1     1     2
#> 2     2    NA    NA
#> 3     3     3     4
Created on 2019-06-03 by the reprex package (v0.2.1)
I don't know if the default behavior should change or it would justify an extra parameter, but maybe a warning when unnest destroys data ? possibly with a pointer to a workaround. It wasn't so obvious to debug.
EDIT :
A new verb dedicated to unnest horizontally only, working directly from lists or atomic vectors might be a good alternative, I use a lot this conversion from list to tibble and it's somewhat awkward.
it might help provide more elegant solutions to use cases such as:
https://stackoverflow.com/questions/51813445/how-to-spread-a-list-column/51823029#51823029
https://stackoverflow.com/questions/46251040/flatten-column-as-argument/46346344#46346344
https://stackoverflow.com/questions/50919060/use-merge-with-one-data-frame-in-r/50919280#50919280

Closing comment (2019-06-03T14:27:30Z): It's unrelated to #645. It's more like #637 in another guise. Minimal reprex:
library(tidyr)
df1 <- tibble(
  id = 1:3,
  data = list(
    tibble(a = 1, b = 2), 
    tibble(), 
    tibble(a = 3, b = 4)
  )
)

df1 %>% unnest(data)
#> # A tibble: 2 x 3
#>      id     a     b
#>   <int> <dbl> <dbl>
#> 1     1     1     2
#> 2     3     3     4

df1 %>% unnest(data, keep_empty = TRUE)
#> # A tibble: 2 x 3
#>      id     a     b
#>   <int> <dbl> <dbl>
#> 1     1     1     2
#> 2     3     3     4
Created on 2019-06-03 by the reprex package (v0.2.1.9000)