do.call не работает с "+" вместо "что" и списком из 3+ элементов - PullRequest
6 голосов
/ 05 августа 2020

Я могу использовать 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

, но я знаю о накладных расходах, генерируемых операцией Reduce по сравнению с do.call, а в моем РЕАЛЬНОМ приложении это недопустимо , поскольку мне нужно суммировать не трехэлементные списки, а скорее список из 10 ^ 5 элементов из 10 ^ 4-элементных векторов.

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  

Ответы [ 3 ]

6 голосов
/ 05 августа 2020

По мере того, как ваш список становится больше, вы можете обнаружить, что это начинает ускоряться:

# 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
6 голосов
/ 05 августа 2020

Вы можете использовать:

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))
1 голос
/ 05 августа 2020

Другой базовый обходной путь R

rowSums(as.data.frame(lst)

или

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

, что дает

[1] 10  3  6

Data

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