Bind_rows() error: "Argument 1 must have names" // Occurs after tidyverse update

Viewed 2117

After a global tidyverse update, I have noted a change of behaviour in my code and after many researches I am desperately unable to solve the issue. Basically I need to convert a list of elements (including lists) to a dataframe.
Here is a reprex:

Data

x <- list(
    col1 = list("a", "b", "c", NA),
    col2 = list(1, 2, 3, 4),
    col3 = list("value1", "value2", "value1", c("value1", "value2")))

Expected behaviour and output (before tidyverse update):

x <- data.frame((sapply(x, c)))
x <- purrr::map_df(x, function(x) sapply(x, function(x) unlist(x))) %>% as.data.frame()

> x
# col1 col2           col3
# 1    a    1         value1
# 2    b    2         value2
# 3    c    3         value1
# 4 <NA>    4 value1, value2

> str(x)
# 'data.frame': 4 obs. of  3 variables:
# $ col1: chr  "a" "b" "c" NA
# $ col2: num  1 2 3 4
# $ col3:List of 4
# ..$ : chr "value1"
# ..$ : chr "value2"
# ..$ : chr "value1"
# ..$ : chr  "value1" "value2"

Problem encountered after update

x <- data.frame((sapply(x, c)))

x <- purrr::map_df(x, function(x) sapply(x, function(x) unlist(x)))
# Error: Argument 1 must have names.
# Run `rlang::last_error()` to see where the error occurred.
# In addition: Warning message:
#     Outer names are only allowed for unnamed scalar atomic inputs 

> rlang::last_error()
# <error/rlang_error>
#     Argument 1 must have names.
# Backtrace:
# 1. purrr::map_df(x, function(x) sapply(x, function(x) unlist(x)))
# 2. dplyr::bind_rows(res, .id = .id)
# Run `rlang::last_trace()` to see the full context.

This error seems to be well known, and I have explored many options with the purrr::flatten_() family, and others found on Stackoverflow but was not able to solve.

Thank you if any help is providable!

3 Answers

The first part of your attempt gives you a list for every column irrespective of it's length.

x <- data.frame((sapply(x, c)))
str(x)

#'data.frame':  4 obs. of  3 variables:
# $ col1:List of 4
#  ..$ : chr "a"
#  ..$ : chr "b"
#  ..$ : chr "c"
#  ..$ : logi NA
# $ col2:List of 4
#  ..$ : num 1
#  ..$ : num 2
#  ..$ : num 3
#  ..$ : num 4
# $ col3:List of 4
#  ..$ : chr "value1"
#  ..$ : chr "value2"
#  ..$ : chr "value1"
#  ..$ : chr  "value1" "value2"

You can unlist the above for columns with only 1 element.

x[] <- lapply(x, function(p) if(max(lengths(p)) == 1) unlist(p) else p)
x
#  col1 col2           col3
#1    a    1         value1
#2    b    2         value2
#3    c    3         value1
#4 <NA>    4 value1, value2

str(x)
#'data.frame':  4 obs. of  3 variables:
# $ col1: chr  "a" "b" "c" NA
# $ col2: num  1 2 3 4
# $ col3:List of 4
#  ..$ : chr "value1"
#  ..$ : chr "value2"
#  ..$ : chr "value1"
#  ..$ : chr  "value1" "value2"

One option utilizing dplyr, tibble and purrr could be:

imap_dfc(x, ~ tibble(!!.y := .x)) %>%
 mutate(across(where(~ all(lengths(.) == 1)), ~ unlist(.)))

  col1   col2 col3     
  <chr> <dbl> <list>   
1 a         1 <chr [1]>
2 b         2 <chr [1]>
3 c         3 <chr [1]>
4 <NA>      4 <chr [2]>

no tidyverse solution, but it seems to work..

library( rlist )
as.data.frame( rlist::list.cbind( x ) )

#   col1 col2           col3
# 1    a    1         value1
# 2    b    2         value2
# 3    c    3         value1
# 4   NA    4 value1, value2
Related