将数据帧列表转换为一个数据帧


336

我有一个代码,它在一处最终以一个数据帧列表结尾,我真的想将其转换为一个大数据帧。

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

这是我开始的示例(为说明起见,已大大简化了该示例):

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)

也看到了这个问题:stackoverflow.com/questions/2209258/...
巴蒂尔

27
这个do.call("rbind", list)习语也是我以前使用过的。为什么需要初始字母unlist
德克·埃德比布特

5
有人可以向我解释do.call(“ rbind”,list)和rbind(list)之间的区别-为什么输出不一样?
user6571411

1
@ user6571411因为do.call()不会一一返回参数,而是使用列表来保存函数的参数。参见https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema 18/12/29

Answers:


130

使用dplyr包中的bind_rows():

bind_rows(list_of_dataframes, .id = "column_label")

5
不错的解决方案。.id = "column_label"根据列表元素名称添加唯一的行名称。
江西波

10
由于是2018年,dplyr既快速又可靠,可以使用,所以我将其更改为可接受的答案。岁月,他们飞逝!
JD Long

186

另一种选择是使用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的评论,这是rbind.fillCRAN 的最新版本:

> system.time({ df3 <- rbind.fill(listOfDataFrames) })
   user  system elapsed 
   0.24    0.00    0.23 
> identical(df, df3)
[1] TRUE

这比rbind更容易,并且速度稍快(这些时间在多次运行中保持不变)。据我了解,github上的版本plyr比这更快。


28
最新版本的plyr中的rbind.fill比do.call和rbind快得多
hadley 2010年

1
有趣。对我来说,rbind.fill是最快的。足够奇怪的是,即使我可以找到区别,do.call / rbind也没有返回相同的TRUE。其他两个相等,但是plyr慢一些。
马特·班纳特

I()可以代替data.frame您的ldply电话
baptiste 2013年

4
还有melt.listreshape(2)
baptiste 2013年

do.call(function(...) rbind(..., make.row.names=F), df)如果您不希望自动生成的唯一行名,则很有用。
smci

111

为了完整起见,我认为该问题的答案需要更新。“我的猜测是,使用do.call("rbind", ...)它将是最快的方法……”这可能在2010年5月及以后是正确的,但在2011年9月左右rbindlistdata.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

3
非常感谢您-我要拔头发了,因为我的数据集太大了,无法ldply读取一堆冗长而复杂的数据帧。无论如何,我通过您的rbindlist建议获得了令人难以置信的加速。
KarateSnowMachine

11
为了完整性,还有一个:dplyr::rbind_all(listOfDataFrames)同样可以解决问题。
andyteucher 2014年

2
是否有一个等效项,rbindlist但按列追加数据帧?像cbindlist这样的东西?
rafa.pereira 2015年

2
@ rafa.pereira最近有一个功能请求: 添加功能cbindlist
Henrik

我也在拔头发,因为do.call()已经在数据帧列表上运行了18个小时,但还没有完成,谢谢!!!
Graeme Frost

74

绑定图

码:

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日。在同一台计算机上运行。软件包的新版本。为种子爱好者添加了种子。

在此处输入图片说明

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

在此处输入图片说明

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

2
这是一个很好的答案。我运行了相同的东西(相同的操作系统,相同的软件包,不同的随机化,因为您没有这样做set.seed),但是看到了最坏情况下的性能差异。 rbindlist在我的结果中实际上具有最好的最坏情况和最典型的情况
C8H10N4O2

48

也有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

从技术上讲,您不需要as.data.frame-只需这样做就可以使其成为一个data.frame,而不是table_df(来自deplyr)
user1617979 2015年

14

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

使用基数R:

df <- Reduce(rbind, listOfDataFrames)

或者,使用tidyverse:

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

11

tidyverse中应如何做:

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

3
为什么要使用mapif bind_rows可以获取数据帧列表?
参阅

9

想要比较一些最近答案的人的最新视觉效果(我想将purrr与dplyr解决方案进行比较)。基本上,我结合了@TheVTM和@rmf的答案。

在此处输入图片说明

码:

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

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  
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.