在循环中将数据框的行与另一个数据框绑定的有效方法是什么?

3
我知道在循环中将元素添加到向量或列表的最节省内存的方法是要在分配索引之前预先分配该向量/列表。
问题:
在循环中,rbinding data.frames的最节省内存(和时间)的方法是什么?(注意:最终,我想知道在循环中绑定大量大型数据框的最有效、合理的方法)
目前我所知道的是:
我们可以使用标准的rbind,例如:
output <- data.frame(a=c(), b=c())

for(i in 1:1000) { 
  temp <- data.frame(a=c(i), b=c(i))
  output <- rbind(output, temp)
}

或者 bind_rows():(这应该会更快)
library(dplyr)
output <- data.frame(a=c(), b=c())

for(i in 1:1000) { 
  temp <- data.frame(a=c(i), b=c(i))
  output <- bind_rows(output, temp)
}

我不确定其中哪个更有效率(例如对于长时间/大规模操作),也不知道是否有其他更有效的替代方案/最佳实践?


1
两者都不适用于大型操作。不要在循环内绑定 - 它会快速创建不必要的数据副本,成为真正的瓶颈。将数据以列表形式返回并进行一次绑定。 - Ritchie Sacramento
@H1 我猜明智的步骤是:i) 预分配列表,然后ii)将数据框分配给列表索引,iii)最后使用bind_rows(output_list)绑定所有内容? - stevec
1个回答

4

正如OP在评论中建议的那样,最好的方法是创建一个大列表,然后在最后绑定所有内容。这使用lapply()而不是显式循环,接着使用do.call(rbind, tmp)

n = 1000
tmp = lapply(seq_len(n), function(i) data.frame(a = i, b = i))
output = do.call(rbind, tmp)
## or 
output = dplyr::bind_rows(tmp)
## or
output = data.table::rbindlist(tmp)

现在,如果我们针对这个特定的例子并要求循环,我们也可以使用一些替代方案。例如,我们知道每次迭代都会产生一个整数,而不是增加数据框列表。因此,我们可以简单地预先分配整数向量,这也容易转换为
n = 1000L
a = b = integer(n)
for (i in seq_len(n)) {
  a[i] = b[i] = i
}
data.frame(a = a, b = b)

## or with Rcpp:
rcpp_new_loop = Rcpp::cppFunction(code = 
'DataFrame rcpp_new_loop(int n) {
   IntegerVector a(n);
   IntegerVector b(n);

   for (int i = 0; i < n; i++) {
     a(i) = b(i) = i + 1;
   }
   return(DataFrame::create(Named("a") = a, _["b"] = b));
 } 
')

同样地,data.frame调用时存在很多开销。 dplyr::bind_rows()data.table::rbindlist()默认将lists转换为data.frame类型的结果:
tmp = lapply(seq_len(n), function(i) list(a = i, b = i))

##data.table
output = rbindlist(tmp)
setDF(output)

##dplyr
output = bind_rows(tmp)
as.data.frame(output)

性能: Rcpp是最快的方法,但使用data.table::rbindlistdplyr::bind_rows与列表一起使用是一个相当简单的方法。

### n = 1,000 
# A tibble: 9 x 13
  expression            min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
  <bch:expr>       <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
1 OP               378.18ms 379.92ms      2.63    15.7MB     2.63     2     2      760ms
2 do_call          254.76ms 254.89ms      3.92   220.7KB     5.88     2     3      510ms
3 bind_rows_df     196.69ms 202.48ms      4.94    16.9KB     3.29     3     2      607ms
4 dt_df            179.41ms 184.76ms      4.52    32.8KB     3.01     3     2      664ms
5 bind_rows_list     2.74ms   2.81ms    321.      16.9KB     3.98   161     2      502ms
6 new_loop           2.56ms   2.63ms    342.      17.6KB     4.00   171     2      500ms
7 dt_list            1.33ms   1.35ms    525.      32.8KB     3.99   263     2      501ms
8 new_loop_fx(n)    270.2us  280.5us   2188.      11.8KB     4.00  1094     2      500ms
9 rcpp_new_loop(n)  217.4us  228.3us   3872.      10.4KB     4.00  1936     2      500ms

### n = 10,000
# A tibble: 9 x 13
  expression            min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
  <bch:expr>       <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
1 OP                  5.69s    5.69s     0.176    1.51GB     5.80     1    33      5.69s
2 do_call             2.67s    2.67s     0.374     2.2MB     3.74     1    10      2.67s
3 bind_rows_df        1.92s    1.92s     0.520  157.52KB     4.16     1     8      1.92s
4 dt_df               2.25s    2.25s     0.444  243.77KB     4.44     1    10      2.25s
5 bind_rows_list    30.73ms  34.57ms    28.5    157.75KB     3.81    15     2   525.49ms
6 new_loop           3.64ms   3.79ms   238.     123.07KB     3.99   119     2   500.85ms
7 dt_list           14.68ms  17.98ms    49.8    243.77KB     5.98    25     3      502ms
8 new_loop_fx(n)      1.2ms   1.24ms   691.     117.28KB     7.99   346     4   500.55ms
9 rcpp_new_loop(n)  299.5us  313.3us  2818.      80.66KB     4.00  1409     2   499.96ms

##code to reproduce::
library(data.table)
library(dplyr)

n = 1000L

new_loop_fx = function(n){
  a = b = integer(n)
  for (i in seq_len(n)) {
    a[i] = b[i] = i
  }
  data.frame(a = a, b = b)
}

rcpp_new_loop = Rcpp::cppFunction(code = 
'DataFrame rcpp_new_loop(int n) {
   IntegerVector a(n);
   IntegerVector b(n);

   for (int i = 0; i < n; i++) {
     a(i) = b(i) = i + 1;
   }
   return(DataFrame::create(Named("a") = a, _["b"] = b));
 } 
')

bench::mark(
  OP = {
    output <- data.frame(a=c(), b=c())

    for(i in seq_len(n)) { 
      temp <- data.frame(a=i, b=i)
      output <- rbind(output, temp)
    }
    output
  }
  ,
  do_call = {
    tmp = lapply(seq_len(n), function(i) data.frame(a = i, b = i))
    output = do.call(rbind, tmp)
  }
  , 
  bind_rows_df = {
    tmp = lapply(seq_len(n), function(i) data.frame(a = i, b = i))
    output = bind_rows(tmp)
    as.data.frame(output)
  }
  ,
  dt_df = {
    tmp = lapply(seq_len(n), function(i) data.frame(a = i, b = i))
    output = rbindlist(tmp)
    setDF(output)
  }
  , 
  bind_rows_list = {
    tmp = lapply(seq_len(n), function(i) list(a = i, b = i))
    output = bind_rows(tmp)
    as.data.frame(output)
  }
  ,
  new_loop = {
    a = b = integer(n)
    for (i in seq_len(n)){
      a[i] = b[i] = i
    }
    data.frame(a = a, b = b)
  }
  ,
  dt_list = {
    tmp = lapply(seq_len(n), function(i) list(a = i, b = i))
    output = rbindlist(tmp)
    setDF(output)
  }

  ,
  new_loop_fx(n),
  rcpp_new_loop(n)
)

1
加一个 data.table 的解决方案怎么样? :) - chinsoon12
2
@chinsoon12 谢谢你提醒我。我的初步研究发现,bind_rowsrbindlist在很大程度上是等效的 - 开始影响时间的垃圾回收有很多。 - Cole

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