在R中,rbind函数能否并行化?

34

当我坐在这里等待我的R脚本运行时,我想知道...是否有任何方法可以在R中并行操作rbind?

因为我处理大量数据时,常常需要等待这个调用的完成。

do.call("rbind", LIST)

5
plyr包中的rbind.fill声称比基础rbind快得多。也许你会在这里看到一些性能提升。如果您添加一些代表性数据样本,人们也可以提供其他解决方案,并进行基准测试以测量时间。 - Chase
3
LIST 中包含什么类型的对象(矩阵,数据框等)? - Joshua Ulrich
LIST中有哪些类型的对象?Data.frames。 - Atlas1j
6个回答

24

到目前为止,我还没有找到一种并行处理的方法。不过,针对我的数据集(其中有一个包含大约1500个数据框和总共450万行的列表),下面的代码片段似乎有所帮助:

while(length(lst) > 1) {
    idxlst <- seq(from=1, to=length(lst), by=2)

    lst <- lapply(idxlst, function(i) {
        if(i==length(lst)) { return(lst[[i]]) }

        return(rbind(lst[[i]], lst[[i+1]]))
    })
}

其中lst是列表。这种方法似乎比使用do.call(rbind, lst)甚至比使用plyr包中的rbind.fill函数快大约4倍。在每次迭代中,此代码都会将数据帧减半。


1
该死,它确实跑得更快了! - Hong Ooi

20
因为您想要将对象,所以应该使用包。它有一个名为的函数,大大增强了的功能。我不100%确定,但我敢打赌任何使用的地方都会触发复制,而则不会。 无论如何,是,所以尝试一下不会有任何损失。

编辑:

library(data.table)
system.time(dt <- rbindlist(pieces))
utilisateur     système      écoulé 
       0.12        0.00        0.13 
tables()
     NAME  NROW MB COLS                        KEY
[1,] dt   1,000 8  X1,X2,X3,X4,X5,X6,X7,X8,...    
Total: 8MB

闪电般快速...


1
哈哈,这一定是计算机历史上最快的加速!在我的端上,从几个小时缩短到不到一秒钟。 - Ben Ogorek

18

我怀疑你想通过并行化来更快地实现这个功能:除了你可能需要自己编写代码(线程一首先将项目1和2组合,而线程二将项目3和4组合等等,等它们完成后,结果就像“重新绑定”一样 - 我不认为没有非C方式可以改进这个问题),这还将涉及在线程之间复制大量数据,这通常是导致速度变慢的原因。

在 C 语言中,您可以在线程之间共享对象,因此您可以让所有线程在同一内存中写入。祝您好运 :-)

最后,顺带一提:rbinding 数据框(data.frame)的速度很慢。如果您事先知道所有数据框的结构完全相同,并且它不包含纯字符列,则可以使用我的问题的这个答案中的技巧。如果您的数据框包含字符列,我认为最好单独处理这些列(do.call(c, lapply(LIST, "[[", "myCharColName"))),然后进行其余部分的操作,之后再将它们重新组合。


7

以下是一种解决方案,它自然地扩展到rbind.fill,merge和其他数据框列表函数:

但是像我所有的答案/问题一样,请进行验证 :)

require(snowfall)
require(rbenchmark)

rbinder <- function(..., cores=NULL){
  if(is.null(cores)){
    do.call("rbind", ...)
  }else{
    sequ <- as.integer(seq(1, length(...), length.out=cores+1))
    listOLists <- paste(paste("list", seq(cores), sep=""), " = ...[",  c(1, sequ[2:cores]+1), ":", sequ[2:(cores+1)], "]", sep="", collapse=", ") 
    dfs <- eval(parse(text=paste("list(", listOLists, ")")))
    suppressMessages(sfInit(parallel=TRUE, cores))
    dfs <- sfLapply(dfs, function(x) do.call("rbind", x))
    suppressMessages(sfStop())
    do.call("rbind", dfs)   
  }
}

pieces <- lapply(seq(1000), function(.) data.frame(matrix(runif(1000), ncol=1000)))

benchmark(do.call("rbind", pieces), rbinder(pieces), rbinder(pieces, cores=4), replications = 10)

#test replications elapsed relative user.self sys.self user.child sys.child
#With intel i5 3570k    
#1     do.call("rbind", pieces)           10  116.70    6.505    115.79     0.10         NA        NA
#3 rbinder(pieces, cores = 4)           10   17.94    1.000      1.67     2.12         NA        NA
#2              rbinder(pieces)           10  116.03    6.468    115.50     0.05         NA        NA

@Arun,这是一个我已经修复过的错误,但似乎没有保存。此外,您可以轻松地将其扩展到rbindlist或其他基于列表的技术。我只是使用R的标准rbind来进行简单的概念验证。 - Xachriel
@Arun,那不是我的意思。我从未争论过我的并行化解决方案会是最快的解决方案。我只是回答了问题:“rbind可以并行化吗?”,“是的”,并提到您可以使用不同的函数来实现此方法。对于“rbindlist”解决方案,如果能通过并行化使其改进就很好了。也许不会,因为在“核心”中读取库/函数需要更长的时间,而不仅仅是绑定几万帧。但是对于100k、1M、10M呢?我现在在一个老旧的MacBook上的度假屋里,所以在周一之前我无法进行适当尝试。 - Xachriel

2

这是对 @Dominik 回答的进一步扩展。

我们可以使用 parallel 包中的 mclapply 来进一步提高速度。此外,rbind.fill 比 rbind 做得更好,因此以下是改进后的代码。 注意:这仅适用于 mac/linux。mclapply 不支持 Windows。 编辑:如果您想查看进度,请取消注释 print(i) 行,并确保从终端而非 RStudio 运行。从并行进程打印到 RStudio 会有点搞乱 RStudio。

library(parallel)
rbind.fill.parallel <- function(list){
  while(length(list) > 1) {
    idxlst <- seq(from=1, to=length(list), by=2)

    list <- mclapply(idxlst, function(i) {
      #print(i) #uncomment this if you want to see progress
      if(i==length(list)) { return(list[[i]]) }
      return(rbind.fill(list[[i]], list[[i+1]]))
    })
  }
}

1

看起来已经有很多人回答了,但如果有人遇到这个问题,这里是一个针对非data.table/data.frame-esque对象的并行rbind版本:

rbind.parallel <- function(list,ncore)
  {
  library(parallel)
  do.call.rbind<-function(x){do.call(rbind,x)}
  cl<-makeCluster(ncore)
  list.split<-split(list,rep(1:ncore,length(list)+1)[1:length(list)])
  list.join<-parLapply(cl,list.split,do.call.rbind)
  stopCluster(cl)
  list.out<-do.call(rbind,list.join)
  return(list.out)
  }

这在 sf 类型的对象上非常有效。例如,如果您使用 lapply(.,st_read) 从目录中读取一组 shapefile 的列表,则明显不能使用 rbind.fill 及其变体来连接所有要素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接