do.call 不适用于“+”作为 "what" 和 3 个以上元素的列表

do.call doesn't work with "+" as "what" and a list of 3+ elements

我可以使用 do.call 对两个向量进行元素求和:

do.call(what="+", args =list(c(0,0,1), c(1,2,3))
>[1] 1 2 4

但是,如果我想用三个向量的列表调用同一个运算符,它会失败:

do.call(what = "+", args = list(c(0,0,1), c(1,2,3), c(9,1,2)))
>Error in `+`(c(0, 0, 1), c(1, 2, 3), c(9, 1, 2)): operator needs one or two arguments

我可以用Reduce

Reduce(f = "+", x = list(c(0,0,1), c(1,2,3), c(9,1,2)))
>[1] 10  3  6

但我知道与 do.call 相比,Reduce 操作产生的开销,并且在我的真实应用程序中这是不能容忍的,因为我不需要对 3 元素列表求和,而是 10^4 元素长向量的 10^5 元素列表。

UPD:Reduce 原来是最快的方法,毕竟...

lst <- list(1:10000, 10001:20000, 20001:30000)
lst2 <- lst[rep(seq.int(length(lst)), 1000)]
microbenchmark::microbenchmark(colSums(do.call(rbind, lst2)),
                            vapply(transpose(lst2), sum, 0),
                            Reduce(f = "+", x = lst2))

    Unit: milliseconds
                           expr      min       lq     mean   median       uq       max neval cld
   colSums(do.call(rbind, lst2)) 153.5086 194.9139 222.7954 198.1952 201.8152  915.6354   100  b 
 vapply(transpose(lst2), sum, 0) 398.9424 537.3834 732.4747 781.7255 813.7376 1538.4301   100   c
       Reduce(f = "+", x = lst2) 101.5618 105.5864 139.8651 108.1204 112.7861 2567.1793   100 a  

您可以使用:

colSums(do.call(rbind, lst))
#[1] 10  3  6

或类似的:

rowSums(do.call(cbind, lst))

其中 lst 是:

lst <- list(c(0,0,1), c(1,2,3), c(9, 1, 2))

随着您的列表变大,您可能会发现这开始变快:

# careful if you use the tidyverse that purrr does not mask transpose
library(data.table) 

lst <- list(c(0,0,1), c(1,2,3), c(9, 1, 2))

vapply(transpose(lst), sum, 0)
# [1] 10  3  6

我拿了几个答案来比较速度,好像是你想要的。

# make the list a bit bigger...
lst2 <- lst[rep(seq.int(length(lst)), 1000)]

microbenchmark::microbenchmark(Reduce(`+`, lst2),
                               colSums(do.call(rbind, lst2)),
                               vapply(transpose(lst2), sum, 0),
                               eval(str2lang(paste0(lst2,collapse = "+"))))
)

Unit: microseconds
                                         expr     min       lq      mean   median       uq     max neval
                            Reduce(`+`, lst2)   954.9  1088.10  1341.271  1191.05  1389.00  6923.2   100
                colSums(do.call(rbind, lst2))   402.2   474.80   761.473   538.85   843.75  7079.7   100
              vapply(transpose(lst2), sum, 0)    81.9    91.85   110.455   103.90   119.00   330.4   100
 eval(str2lang(paste0(lst2, collapse = "+"))) 17489.2 18466.65 20767.888 19572.25 20809.80 57770.4   100

虽然这里有更长的向量,但您的用例也是如此。此基准测试需要一两分钟才能完成 运行。请注意单位现在以毫秒为单位。我认为这将取决于列表的长度。

lst <- list(1:10000, 10001:20000, 20001:30000)
lst2 <- lst[rep(seq.int(length(lst)), 1000)]

microbenchmark::microbenchmark(colSums(do.call(rbind, lst2)),
                               vapply(transpose(lst2), sum, 0))
)

Unit: milliseconds
                            expr      min       lq     mean   median       uq      max neval
   colSums(do.call(rbind, lst2)) 141.7147 146.6305 188.5108 163.4915 228.7852 270.5679   100
 vapply(transpose(lst2), sum, 0) 261.8630 335.6093 348.6241 341.6958 348.6404 495.0994   100

另一个基本 R 解决方法

rowSums(as.data.frame(lst)

eval(str2lang(paste0(lst,collapse = "+")))

这给出了

[1] 10  3  6

数据

lst <- list(c(0,0,1), c(1,2,3), c(9, 1, 2))