按行将数据帧列表合并为一个数据帧

我有代码,在一个地方以一个数据帧列表结束,我真的想转换成一个单一的大数据帧。

我从早些时候的问题中得到了一些指针,它试图做一些类似的事情,但更复杂。

下面是我开始的一个例子(为了说明,这是非常简化的):

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)
307662 次浏览

另一个选择是使用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更简单,并且稍微快一些(这些计时在多次运行中都有效)。据我所知,github上的plyr版本甚至比这个还要快。

为了完整起见,我认为这个问题的答案需要更新。“我猜使用do.call("rbind", ...)将是最快的方法,你会发现…”2010年5月和之后的一段时间可能是这样,但在2011年9月左右,在data.table包1.8.2版本中引入了一个新函数rbindlist,并注释说“这与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

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

bind-plot

代码:

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’

< p > 更新: 重新运行31 - 1月- 2018。在同一台计算机上运行。包的新版本。

enter image description here

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’

更新: Rerun 06-08-2019。

enter image description here

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日。

enter image description here

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’

在tidyverse中应该如何做:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)

这里有另一种方法可以做到这一点(只要将它添加到答案中,因为reduce是一个非常有效的函数工具,它经常被忽视为循环的替代品。在这个特定的情况下,这两种方法都没有比do.call快得多)

使用底数R:

df <- Reduce(rbind, listOfDataFrames)

或者,用tidyverse来形容:

library(tidyverse) # or, library(dplyr); library(purrr)
df <- listOfDataFrames %>% reduce(bind_rows)

带有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的答案。

enter image description here

代码:

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’

使用dplyr包中的bind_rows():

bind_rows(list_of_dataframes, .id = "column_label")