rbind.data.frame的性能表现

9

我有一个数据框列表,我确定它们都至少包含一行数据(事实上,有些只包含一行数据,而其他一些包含给定数量的行数据),并且它们都具有相同的列(名称和类型)。如果有影响的话,我还确定在行中没有任何NA值。

可以通过以下方式模拟这种情况:

#create one row
onerowdfr<-do.call(data.frame, c(list(), rnorm(100) , lapply(sample(letters[1:2], 100, replace=TRUE), function(x){factor(x, levels=letters[1:2])})))
colnames(onerowdfr)<-c(paste("cnt", 1:100, sep=""), paste("cat", 1:100, sep=""))
#reuse it in a list
someParts<-lapply(rbinom(200, 1, 14/200)*6+1, function(reps){onerowdfr[rep(1, reps),]})

我已经设置了参数(随机化)以便它们近似于我的真实情况。

现在,我想将所有这些数据框合并成一个数据框。我认为使用rbind可以解决问题,像这样:

system.time(
result<-do.call(rbind, someParts)
)

现在,在我的系统上(这并不特别慢),并且使用上述设置,这是系统时间输出的结果:

   user  system elapsed 
   5.61    0.00    5.62

将254(在我的情况下)行的200个变量进行rbind,需要近6秒钟?肯定有一种方法可以提高这里的性能吧?在我的代码中,我经常需要做类似的事情(它是多重插补的一种形式),所以我需要尽可能快地完成。


在我的工作中,我使用了Dominik在这里提供的技术(https://dev59.com/GWw05IYBdhLWcg3weBpF#8071176)来合并一系列数据框,这种方法比do.call更快,而且随着数据量的增加速度越来越快。当我使用字符而不是因子读取原始列表数据时,发现性能甚至更好。使用rbind花费了很多时间匹配;我猜测这是为了检查要添加的因子水平。 - ARobertson
6个回答

15

您能否仅使用数值变量构建矩阵,并在最后将其转换为因子?相对于因子矩阵,rbind 在数值矩阵上的运行速度更快。

在我的系统中,使用数据框:

> system.time(result<-do.call(rbind, someParts))
   user  system elapsed 
  2.628   0.000   2.636 

使用仅包含数字矩阵的列表构建:

onerowdfr2 <- matrix(as.numeric(onerowdfr), nrow=1)
someParts2<-lapply(rbinom(200, 1, 14/200)*6+1, 
                   function(reps){onerowdfr2[rep(1, reps),]})

使用 rbind 会导致速度更快的结果。

> system.time(result2<-do.call(rbind, someParts2))
   user  system elapsed 
  0.001   0.000   0.001

编辑:这里有另一种可能性;它只是逐个组合每一列。

> system.time({
+   n <- 1:ncol(someParts[[1]])
+   names(n) <- names(someParts[[1]])
+   result <- as.data.frame(lapply(n, function(i) 
+                           unlist(lapply(someParts, `[[`, i))))
+ })
   user  system elapsed 
  0.810   0.000   0.813  

但仍然比使用矩阵慢得多。

编辑2:

如果您只有数字和因子,将所有内容转换为数字(rbind)并将必要的列转换回因子并不难。这假设所有因子具有完全相同的水平。从整数转换为因子也比从数字转换更快,因此我首先强制将其转换为整数。

someParts2 <- lapply(someParts, function(x)
                     matrix(unlist(x), ncol=ncol(x)))
result<-as.data.frame(do.call(rbind, someParts2))
a <- someParts[[1]]
f <- which(sapply(a, class)=="factor")
for(i in f) {
  lev <- levels(a[[i]])
  result[[i]] <- factor(as.integer(result[[i]]), levels=seq_along(lev), labels=lev)
}

我的系统时间是:

   user  system elapsed 
   0.090    0.00    0.091 

1
@Aaron:这些数据是模拟的,OP的问题始于数据框。 - Joris Meys
@Joris:确实,这并没有回答帖子作者的具体问题(如何加快rbind.data.frame的速度?)。但是,也许有了矩阵rbinding更快的知识,他可以重写代码以避免使用数据框,或稍后转换为数据框。我很想看到真正加快rbind.data.frame速度的方法。 - Aaron left Stack Overflow
@Aaron:我想现在我会采用你的编辑(尽管我担心当我的实际数据框有更多列时)。由于我在其他地方使用了某些列是因子的事实,使用矩阵似乎不是一个选项。 - Nick Sabbe
2
如果你将[[改为.subset2(不应该这样做,因为它是内部函数),它会运行得快2倍。 - Marek
1
@Nick:很高兴你觉得有帮助。我写了一些代码来转换矩阵,就像我一开始建议的那样;请看我的第二次编辑。 - Aaron left Stack Overflow
显示剩余4条评论

5

虽然提升不算很大,但是使用plyr包中的rbind.fill代替rbind可以将运行时间缩短约10%(在我的机器上,使用样本数据集)。


5
如果您真的想更快地操作您的数据框,我建议使用包data.table和函数rbindlist()。我没有进行大规模测试,但对于我的数据集(3000个数据框,每个数据框1000行x 40列),rbindlist()仅需20秒。

3

这个速度快了约25%,但一定有更好的方法...

system.time({
  N <- do.call(sum, lapply(someParts, nrow))
  SP <- as.data.frame(lapply(someParts[[1]], function(x) rep(x,N)))
  k <- 0
  for(i in 1:length(someParts)) {
    j <- k+1
    k <- k + nrow(someParts[[i]])
    SP[j:k,] <- someParts[[i]]
  }
})

基于此,我尝试使用lapply从每个元素中获取正确的列,逐列填充数据框。这似乎仍然更快。请参见我的答案编辑。 - Aaron left Stack Overflow

1

确保将数据框绑定到数据框。当将列表绑定到数据框时,会遇到巨大的性能降级。


0
从ecospace包中,rbind_listdf每次处理100个数据框。与do.call(rbind)相比,如果您要合并数百个数据框,则似乎更具时间和内存效率。在合并总大小约为5GB的5000个数据框时,我发现峰值内存使用量减少了约25%。

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