按行绑定未命名向量的未命名列表的 Tidyverse 方法 - do.call(rbind,x) 等效

Ian*_*ell 34 r dplyr data.table purrr tidyverse

我经常发现一些问题,人们不知何故以命名字符向量的未命名列表结束,他们想将它们逐行绑定到data.frame. 下面是一个例子:

library(magrittr)
data <- cbind(LETTERS[1:3],1:3,4:6,7:9,c(12,15,18)) %>%
  split(1:3) %>% unname
data
#[[1]]
#[1] "A"  "1"  "4"  "7"  "12"
#
#[[2]]
#[1] "B"  "2"  "5"  "8"  "15"
#
#[[3]]
#[1] "C"  "3"  "6"  "9"  "18"
Run Code Online (Sandbox Code Playgroud)

一种典型的方法是使用do.call基础 R。

do.call(rbind, data) %>% as.data.frame
#  V1 V2 V3 V4 V5
#1  A  1  4  7 12
#2  B  2  5  8 15
#3  C  3  6  9 18
Run Code Online (Sandbox Code Playgroud)

也许一种效率较低的方法是Reduce从基础 R 开始。

Reduce(rbind,data, init = NULL) %>% as.data.frame
#  V1 V2 V3 V4 V5
#1  A  1  4  7 12
#2  B  2  5  8 15
#3  C  3  6  9 18
Run Code Online (Sandbox Code Playgroud)

但是,当我们考虑更现代的包(例如dplyr或 )时data.table,可能会立即想到的一些方法不起作用,因为向量未命名或不是列表。

library(dplyr)
bind_rows(data)
#Error: Argument 1 must have names
Run Code Online (Sandbox Code Playgroud)
library(data.table)
rbindlist(data)
#Error in rbindlist(data) : 
#  Item 1 of input is not a data.frame, data.table or list
Run Code Online (Sandbox Code Playgroud)

一种方法可能是set_names在向量上。

library(purrr)
map_df(data, ~set_names(.x, seq_along(.x)))
# A tibble: 3 x 5
#  `1`   `2`   `3`   `4`   `5`  
#  <chr> <chr> <chr> <chr> <chr>
#1 A     1     4     7     12   
#2 B     2     5     8     15   
#3 C     3     6     9     18  
Run Code Online (Sandbox Code Playgroud)

但是,这似乎比实际需要的步骤多。

因此,我的问题是什么是有效tidyversedata.table方法结合的未命名的列表无名特征向量成data.frame逐行?

tmf*_*mnk 15

不完全确定效率,但使用purrr和的紧凑选项tibble可能是:

map_dfc(purrr::transpose(data), ~ unlist(tibble(.)))

  V1    V2    V3    V4    V5   
  <chr> <chr> <chr> <chr> <chr>
1 A     1     4     7     12   
2 B     2     5     8     15   
3 C     3     6     9     18  
Run Code Online (Sandbox Code Playgroud)


mar*_*kus 11

编辑

使用@sindri_baldur的方法:https : //stackoverflow.com/a/61660119/8583393


一种方式data.table,类似于@tmfmnk 显示的

library(data.table)
as.data.table(transpose(data))
#   V1 V2 V3 V4 V5
#1:  A  1  4  7 12
#2:  B  2  5  8 15
#3:  C  3  6  9 18
Run Code Online (Sandbox Code Playgroud)


sin*_*dur 10

library(data.table)
setDF(transpose(data))

  V1 V2 V3 V4 V5
1  A  1  4  7 12
2  B  2  5  8 15
3  C  3  6  9 18
Run Code Online (Sandbox Code Playgroud)

  • 我刚刚用其他一些方法运行了基准测试。这在速度方面碾压了其他所有解决方案,并且是第一个真正击败“base::rbind()”解决方案的解决方案。 (4认同)
  • @dww 是的,但是 `setDF()` 与 `as.data.table()` / `as.data.frame()` 不同。 (3认同)

Ada*_*dam 9

这看起来相当紧凑。我相信这就是力量bind_rows()fromdplyrmap_df()in purrr,所以应该相当有效。

library(vctrs)

vec_rbind(!!!data)
Run Code Online (Sandbox Code Playgroud)

这给出了一个 data.frame。

  ...1 ...2 ...3 ...4 ...5
1    A    1    4    7   12
2    B    2    5    8   15
3    C    3    6    9   18
Run Code Online (Sandbox Code Playgroud)

一些基准

这似乎是.name_repair在内部tidyverse的方法是一个严重的瓶颈。我采用了一些相当简单的选项,这些选项似乎也从其他帖子中运行得最快(感谢 H 1 和 sindri_baldur)。

microbenchmark(vctrs = vec_rbind(!!!data),
               dt = rbindlist(lapply(data, as.list)),
               map = map_df(data, as_tibble_row, .name_repair = "unique"),
               base = as.data.frame(do.call(rbind, data)))
Run Code Online (Sandbox Code Playgroud)

基准 1

但是如果你首先命名向量(但不一定是列表元素),你会得到一个不同的故事。

data2 <- modify(data, ~set_names(.x, seq(.x)))

microbenchmark(vctrs = vec_rbind(!!!data2),
               dt = rbindlist(lapply(data2, as.list)),
               map = map_df(data2, as_tibble_row),
               base = as.data.frame(do.call(rbind, data2)))
Run Code Online (Sandbox Code Playgroud)

基准 2

事实上,您可以将命名向量的时间包含在vec_rbind()解决方案中而不是其他的中,并且仍然可以看到相当高的性能。

microbenchmark(vctrs = vec_rbind(!!!modify(data, ~set_names(.x, seq(.x)))),
               dt = setDF(transpose(data)),
               map = map_df(data2, as_tibble_row),
               base = as.data.frame(do.call(rbind, data)))
Run Code Online (Sandbox Code Playgroud)

最终基准

物有所值。


akr*_*run 6

一个选项 unnest_wider

library(tibble)
library(tidyr)
library(stringr)
tibble(col = data) %>%
    unnest_wider(c(col), names_repair = ~ str_c('value', seq_along(.)))
# A tibble: 3 x 5
#  value1 value2 value3 value4 value5
#  <chr>  <chr>  <chr>  <chr>  <chr> 
#1 A      1      4      7      12    
#2 B      2      5      8      15    
#3 C      3      6      9      18    
Run Code Online (Sandbox Code Playgroud)


jan*_*cki 5

我的方法是将这些列表条目转换为预期类型

rbindlist(lapply(data, as.list))
#       V1     V2     V3     V4     V5
#   <char> <char> <char> <char> <char>
#1:      A      1      4      7     12
#2:      B      2      5      8     15
#3:      C      3      6      9     18
Run Code Online (Sandbox Code Playgroud)

如果您希望将数据类型从字符向量调整为适当的类型,那么lapply在这里也可以提供帮助。Firstlapply为每一行调用,secondlapply为每一列调用。

rbindlist(lapply(data, as.list))[, lapply(.SD, type.convert)]
       V1    V2    V3    V4    V5
   <fctr> <int> <int> <int> <int>
1:      A     1     4     7    12
2:      B     2     5     8    15
3:      C     3     6     9    18
Run Code Online (Sandbox Code Playgroud)