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

Combine a list of data frames into one data frame by row

提问人:JD Long 提问时间:5/18/2010 最后编辑:HenrikJD Long 更新时间:7/10/2023 访问量:345689

问:

我的代码在一个地方最终得到了一个数据帧列表,我真的想将其转换为单个大数据帧。

我从之前的一个问题中得到了一些建议,该问题试图做一些类似但更复杂的事情。

这是我开始的一个例子(为了说明这一点,这被大大简化了):

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)
列表 数据帧 R-FAQ

评论

0赞 Shane 5/18/2010
另请参阅此问题:stackoverflow.com/questions/2209258/...
37赞 Dirk is no longer here 5/18/2010
这个成语也是我以前用过的。为什么需要首字母?do.call("rbind", list)unlist
8赞 user6571411 8/20/2016
有人可以向我解释 do.call(“rbind”, list) 和 rbind(list) 之间的区别 - 为什么输出不一样?
2赞 Marjolein Fokkema 12/29/2018
@user6571411 因为 do.call() 不会一个一个地返回参数,而是使用列表来保存函数的参数。查看 https://www.stat.berkeley.edu/~s133/Docall.html

答:

195赞 Shane 5/18/2010 #1

另一种选择是使用 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

我的猜测是,除非您可以执行以下操作,否则使用将是您将找到的最快的方法:(a) 使用矩阵而不是 data.frames,以及 (b) 预分配最终矩阵并分配给它而不是增长它。do.call("rbind", ...)

编辑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 版本甚至比这更快。

评论

29赞 hadley 5/18/2010
最新版本的 plyr 中的 rbind.fill 比 do.call 和 rbind 快得多
1赞 Matt Bannert 11/29/2010
有趣。对我来说,rbind.fill 是最快的。很奇怪,do.call / rbind 没有返回相同的 TRUE,即使我找不到差异。另外两个是相等的,但 plyr 速度较慢。
0赞 baptiste 8/28/2013
I()可以在您的通话中替换data.frameldply
4赞 baptiste 8/28/2013
还有 Reshape(2)melt.list
2赞 see24 7/26/2018
bind_rows()根据 rmd 的回答,这是最快的,我认为这是最直接的。它还具有添加 id 列的功能
140赞 andrekos 8/28/2013 #2

为了完整起见,我认为这个问题的答案需要更新。“我的猜测是,使用将是你会发现的最快的方法......”2010 年 5 月及之后的一段时间可能是正确的,但在 2011 年 9 月左右,软件包版本 1.8.2 中引入了一个新函数,并注释“这与 相同,但速度要快得多”。快多少?do.call("rbind", ...)rbindlistdata.tabledo.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

评论

4赞 KarateSnowMachine 9/18/2013
非常感谢你 -- 我拔掉了我的头发,因为我的数据集太大了,无法处理一堆长而熔融的数据帧。无论如何,通过使用您的建议,我获得了令人难以置信的加速。ldplyrbindlist
12赞 andyteucher 7/16/2014
还有一个完整性:也可以解决问题。dplyr::rbind_all(listOfDataFrames)
2赞 rafa.pereira 9/14/2015
是否有等效于 but 按列附加数据帧的 ?像 cbindlist 一样的东西?rbindlist
4赞 Henrik 2/26/2018
@rafa.pereira 最近有一个功能请求: 添加函数 cbindlist
0赞 Graeme Frost 4/2/2019
我也在拔头发,因为已经在数据帧列表上运行了 18 个小时,但仍然没有完成,谢谢!!do.call()
51赞 TheVTM 4/29/2015 #3

还有。bind_rows(x, ...)dplyr

> 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

评论

1赞 user1617979 6/2/2015
从技术上讲,您不需要 as.data.frame - 它所做的一切使它完全是一个 Data.Frame,而不是一个 table_df(来自 deplyr)
116赞 mindlessgreen 7/22/2016 #4

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’

更新: 2018 年 1 月 31 日重新运行。在同一台计算机上运行。软件包的新版本。为种子爱好者添加了种子。

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’

更新:重新运行06-Aug-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’

更新:2023 年 12 月 14 日重新运行。R 和包的更新版本,但同一台计算机。还添加了 Mael 的答案和包。collapse

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),
  collapse::unlist2d(dflist),
  times=1000)

ggplot2::autoplot(mb)+ggplot2::theme_bw()
Unit: microseconds
                                    expr     min       lq       mean   median       uq      max neval
                plyr::rbind.fill(dflist)  7146.5  7514.00  8270.0111  7753.80  8112.95  23541.9  1000
                dplyr::bind_rows(dflist)  1319.7  1476.35  1772.7105  1566.60  1668.40  13891.0  1000
           data.table::rbindlist(dflist)   466.8   603.20   828.2240   661.35   713.50  85147.2  1000
         plyr::ldply(dflist, data.frame) 14537.2 15161.20 16701.7973 15556.20 16417.50 101030.9  1000
                do.call("rbind", dflist)  9345.5  9851.10 12995.3883 10408.55 16046.65 110697.6  1000
                 Reduce("rbind", dflist) 71055.8 78980.90 97294.2492 83496.15 91894.60 285057.9  1000
 purrr::map_df(dflist, dplyr::bind_rows) 15097.9 15654.65 17361.9197 16020.10 16681.60 660603.4  1000
              collapse::unlist2d(dflist)   186.7   254.80   528.7272   312.90   351.90  88946.8  1000
R version 4.3.2 (2023-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

> packageVersion("plyr")
[1] ‘1.8.9’
> packageVersion("dplyr")
[1] ‘1.1.4’
> packageVersion("data.table")
[1] ‘1.14.10’
> packageVersion("purrr")
[1] ‘1.0.2’
> packageVersion("collapse")
[1] ‘2.0.7’
> packageVersion("microbenchmark")
[1] ‘1.4.10’

评论

2赞 C8H10N4O2 10/19/2016
这是一个很好的答案。我运行了同样的东西(相同的操作系统,相同的软件包,不同的随机化,因为你没有),但在最坏情况下看到一些差异。 实际上在我的结果中具有最好的最坏情况和最佳典型情况set.seedrbindlist
14赞 Nick 5/16/2017 #5

在整洁中应该如何做:

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

评论

6赞 see24 7/24/2018
为什么要使用 if can take a list of dataframe?mapbind_rows
1赞 LMc 11/23/2023
从 purrr 1.0.0 开始,函数已被取代。*_df()
23赞 yeedle 5/17/2017 #6

这是另一种可以完成的方法(只是将其添加到答案中,因为这是一个非常有效的功能工具,经常被忽视为循环的替代品。在这种特殊情况下,它们都不比 do.call 快得多)reduce

使用基数 R:

df <- Reduce(rbind, listOfDataFrames)

或者,使用 Tidyverse:

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

评论

1赞 Nathan T Alexander 3/17/2022
我总是使用base,除非一个包真的好得多。 。感谢您提供基本解决方案
12赞 f0nzie 6/22/2017 #7

解决方案唯一缺少的是标识符列,用于了解数据来自列表中的哪个数据帧。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  
11赞 Nova 8/23/2017 #8

对于那些想要比较一些最近的答案的人来说,这是一个更新的视觉对象(我想将咕噜声与 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’
323赞 joeklieg 2/28/2018 #9

dplyr 包中使用:bind_rows()

bind_rows(list_of_dataframes, .id = "column_label")

评论

10赞 SJ9 4/30/2018
不错的解决方案。 根据列表元素名称添加唯一的行名称。.id = "column_label"
22赞 JD Long 1/12/2019
由于它是 2018 年,并且既快速又可靠,因此我将其更改为公认的答案。岁月飞逝!dplyr
0赞 JAQuent 8/29/2020
这正是我需要的!
2赞 drastega 12/22/2020
奇怪,但它不能正常工作
2赞 Maël 12/22/2022 #10

一个非常快速的选项是 C 编写的:collapse::unlist2d

library(collapse)
unlist2d(listOfDataFrames)

由于 ,另一个选项是 (取代 和 (部分) ):purrr 1.0.0list_rbindflatten_dfrmap_dfr

library(purrr)
list_rbind(listOfDataFrames, names_to = "column_label")

unlist2d比任何其他选项都快:

library(microbenchmark)
mb <- microbenchmark(
  bind_rows = dplyr::bind_rows(listOfDataFrames),
  rbindlist = data.table::rbindlist(listOfDataFrames),
  do.call = do.call("rbind", listOfDataFrames),
  list_rbind = list_rbind(listOfDataFrames),
  unlist2d = unlist2d(listOfDataFrames), times = 1000)

# Unit: microseconds
#        expr       min         lq       mean     median         uq      max neval
#   bind_rows  1590.601  2139.7010  2807.0167  2335.2510  2717.3510  51773.4  1000
#   rbindlist   613.401   890.6015  1438.0510  1012.7505  1318.3015  13893.4  1000
#     do.call 12009.201 19973.7010 25526.6986 22102.8005 25239.9510 151914.2  1000
#  list_rbind  1287.401  1781.1510  2510.0104  1970.5515  2282.3515 115803.6  1000
#    unlist2d   245.401   400.3015   937.1731   488.3005   690.5015  12683.0  1000