我的代码在一个地方以数据框列表结尾,我真的想将其转换为单个大数据框。
我从试图做类似但更复杂的事情的 earlier question 那里得到一些指示。
这是我开始的一个例子(为了说明,这被大大简化了):
listOfDataFrames <- vector(mode = "list", length = 100)
for (i in 1:100) {
listOfDataFrames[[i]] <- data.frame(a=sample(letters, 500, rep=T),
b=rnorm(500), c=rnorm(500))
}
我目前正在使用这个:
df <- do.call("rbind", listOfDataFrames)
do.call("rbind", list)
成语也是我以前使用过的。为什么需要初始 unlist
?
使用 dplyr 包中的 bind_rows()
:
bind_rows(list_of_dataframes, .id = "column_label")
另一种选择是使用 plyr 函数:
df <- ldply(listOfDataFrames, data.frame)
这比原来的慢一点:
> system.time({ df <- do.call("rbind", listOfDataFrames) })
user system elapsed
0.25 0.00 0.25
> system.time({ df2 <- ldply(listOfDataFrames, data.frame) })
user system elapsed
0.30 0.00 0.29
> identical(df, df2)
[1] TRUE
我的猜测是,使用 do.call("rbind", ...)
将是您会发现的最快方法,除非您可以执行以下操作(a)使用矩阵而不是 data.frames 和(b)预先分配最终矩阵并分配给它而不是种植它。
编辑1:
根据 Hadley 的评论,以下是 CRAN 的 rbind.fill
的最新版本:
> system.time({ df3 <- rbind.fill(listOfDataFrames) })
user system elapsed
0.24 0.00 0.23
> identical(df, df3)
[1] TRUE
这比 rbind 更容易,而且速度稍微快一些(这些时间在多次运行中保持不变)。据我了解,the version of plyr
on github 甚至比这更快。
I()
可以在您的 ldply
调用中替换 data.frame
melt.list
为了完整起见,我认为这个问题的答案需要更新。 “我的猜测是,使用 do.call("rbind", ...)
将是你会发现的最快的方法......” 2010 年 5 月和之后的一段时间可能是这样,但在 2011 年 9 月左右引入了一个新函数 rbindlist
data.table
软件包版本 1.8.2,并附注“这与 do.call("rbind",l)
相同,但速度更快”。快多少?
library(rbenchmark)
benchmark(
do.call = do.call("rbind", listOfDataFrames),
plyr_rbind.fill = plyr::rbind.fill(listOfDataFrames),
plyr_ldply = plyr::ldply(listOfDataFrames, data.frame),
data.table_rbindlist = as.data.frame(data.table::rbindlist(listOfDataFrames)),
replications = 100, order = "relative",
columns=c('test','replications', 'elapsed','relative')
)
test replications elapsed relative
4 data.table_rbindlist 100 0.11 1.000
1 do.call 100 9.39 85.364
2 plyr_rbind.fill 100 12.08 109.818
3 plyr_ldply 100 15.14 137.636
ldply
处理一堆长而熔化的数据帧,所以我要抓狂了。无论如何,通过使用您的 rbindlist
建议,我得到了令人难以置信的加速。
dplyr::rbind_all(listOfDataFrames)
也可以解决问题。
rbindlist
但按列附加数据帧的方法?类似 cbindlist 的东西?
do.call()
已经在一个数据帧列表上运行了 18 个小时,但仍然没有完成,所以我也把头发拉出来了,谢谢!!!
https://i.stack.imgur.com/UKG6w.png
代码:
library(microbenchmark)
dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
c=rep(LETTERS,10),d=rep(LETTERS,10))
}
mb <- microbenchmark(
plyr::rbind.fill(dflist),
dplyr::bind_rows(dflist),
data.table::rbindlist(dflist),
plyr::ldply(dflist,data.frame),
do.call("rbind",dflist),
times=1000)
ggplot2::autoplot(mb)
会议:
R version 3.3.0 (2016-05-03)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1
> packageVersion("plyr")
[1] ‘1.8.4’
> packageVersion("dplyr")
[1] ‘0.5.0’
> packageVersion("data.table")
[1] ‘1.9.6’
更新:2018 年 1 月 31 日重新运行。在同一台计算机上运行。新版本的软件包。为种子爱好者添加了种子。
https://i.stack.imgur.com/u3MXE.png
set.seed(21)
library(microbenchmark)
dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
c=rep(LETTERS,10),d=rep(LETTERS,10))
}
mb <- microbenchmark(
plyr::rbind.fill(dflist),
dplyr::bind_rows(dflist),
data.table::rbindlist(dflist),
plyr::ldply(dflist,data.frame),
do.call("rbind",dflist),
times=1000)
ggplot2::autoplot(mb)+theme_bw()
R version 3.4.0 (2017-04-21)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1
> packageVersion("plyr")
[1] ‘1.8.4’
> packageVersion("dplyr")
[1] ‘0.7.2’
> packageVersion("data.table")
[1] ‘1.10.4’
更新:2019 年 8 月 6 日重新运行。
https://i.stack.imgur.com/OpPoJ.png
set.seed(21)
library(microbenchmark)
dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
c=rep(LETTERS,10),d=rep(LETTERS,10))
}
mb <- microbenchmark(
plyr::rbind.fill(dflist),
dplyr::bind_rows(dflist),
data.table::rbindlist(dflist),
plyr::ldply(dflist,data.frame),
do.call("rbind",dflist),
purrr::map_df(dflist,dplyr::bind_rows),
times=1000)
ggplot2::autoplot(mb)+theme_bw()
R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.2.20.so
packageVersion("plyr")
packageVersion("dplyr")
packageVersion("data.table")
packageVersion("purrr")
>> packageVersion("plyr")
[1] ‘1.8.4’
>> packageVersion("dplyr")
[1] ‘0.8.3’
>> packageVersion("data.table")
[1] ‘1.12.2’
>> packageVersion("purrr")
[1] ‘0.3.2’
更新:2021 年 11 月 18 日重新运行。
https://i.stack.imgur.com/nsutp.png
set.seed(21)
library(microbenchmark)
dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
c=rep(LETTERS,10),d=rep(LETTERS,10))
}
mb <- microbenchmark(
plyr::rbind.fill(dflist),
dplyr::bind_rows(dflist),
data.table::rbindlist(dflist),
plyr::ldply(dflist,data.frame),
do.call("rbind",dflist),
Reduce("rbind",dflist),
purrr::map_df(dflist,dplyr::bind_rows),
times=1000)
ggplot2::autoplot(mb)+theme_bw()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19043)
>packageVersion("plyr")
[1] ‘1.8.6’
> packageVersion("dplyr")
[1] ‘1.0.7’
> packageVersion("data.table")
[1] ‘1.14.2’
> packageVersion("purrr")
[1] ‘0.3.4’
set.seed
),但在最坏情况下的性能方面发现了一些差异。 rbindlist
在我的结果中实际上有最好的最坏情况和最好的典型情况
dplyr
中还有 bind_rows(x, ...)
。
> system.time({ df.Base <- do.call("rbind", listOfDataFrames) })
user system elapsed
0.08 0.00 0.07
>
> system.time({ df.dplyr <- as.data.frame(bind_rows(listOfDataFrames)) })
user system elapsed
0.01 0.00 0.02
>
> identical(df.Base, df.dplyr)
[1] TRUE
这是可以完成的另一种方法(只需将其添加到答案中,因为 reduce
是一个非常有效的功能工具,它经常被忽视作为循环的替代品。在这种特殊情况下,这些都没有比 do.call 快得多)
使用基础 R:
df <- Reduce(rbind, listOfDataFrames)
或者,使用 tidyverse:
library(tidyverse) # or, library(dplyr); library(purrr)
df <- listOfDataFrames %>% reduce(bind_rows)
在tidyverse中应该如何做:
df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)
bind_rows
可以获取数据帧列表,为什么还要使用 map
?
唯一缺少 data.table
的解决方案是标识符列,用于了解数据来自列表中的哪个数据框。
像这样的东西:
df_id <- data.table::rbindlist(listOfDataFrames, idcol = TRUE)
idcol
参数添加一列 (.id
),用于标识列表中包含的数据框的来源。结果看起来像这样:
.id a b c
1 u -0.05315128 -1.31975849
1 b -1.00404849 1.15257952
1 y 1.17478229 -0.91043925
1 q -1.65488899 0.05846295
1 c -1.43730524 0.95245909
1 b 0.56434313 0.93813197
为那些想要比较最近的一些答案的人提供更新的视觉效果(我想比较 purrr 和 dplyr 解决方案)。基本上我结合了@TheVTM 和@rmf 的答案。
https://i.stack.imgur.com/L6qQG.png
代码:
library(microbenchmark)
library(data.table)
library(tidyverse)
dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
c=rep(LETTERS,10),d=rep(LETTERS,10))
}
mb <- microbenchmark(
dplyr::bind_rows(dflist),
data.table::rbindlist(dflist),
purrr::map_df(dflist, bind_rows),
do.call("rbind",dflist),
times=500)
ggplot2::autoplot(mb)
会话信息:
sessionInfo()
R version 3.4.1 (2017-06-30)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1
包版本:
> packageVersion("tidyverse")
[1] ‘1.1.1’
> packageVersion("data.table")
[1] ‘1.10.0’
.id = "column_label"
根据列表元素名称添加唯一的行名称。dplyr
既快速又可靠,因此我已将其更改为已接受的答案。岁月,它们飞驰而过!