对于高基数分组,为什么使用dplyr管道(%>%)比等效的非管道表达式慢?

log*_*thy 16 performance r cardinality dplyr magrittr

我认为一般来说使用%>%不会对速度产生明显影响.但在这种情况下,它运行速度慢了4倍.

library(dplyr)
library(microbenchmark)

set.seed(0)
dummy_data <- dplyr::data_frame(
  id=floor(runif(10000, 1, 10000))
  , label=floor(runif(10000, 1, 4))
)

microbenchmark(dummy_data %>% group_by(id) %>% summarise(list(unique(label))))
microbenchmark(dummy_data %>% group_by(id) %>% summarise(label %>% unique %>% list))
Run Code Online (Sandbox Code Playgroud)

没有管道:

min       lq     mean   median       uq      max neval
1.691441 1.739436 1.841157 1.812778 1.880713 2.495853   100
Run Code Online (Sandbox Code Playgroud)

带管:

min       lq     mean   median       uq      max neval
6.753999 6.969573 7.167802 7.052744 7.195204 8.833322   100
Run Code Online (Sandbox Code Playgroud)

为什么%>%在这种情况下会这么慢?有没有更好的方法来写这个?

Spa*_*man 32

在编写与以前"可忽略的"时间相关的单线时,在现实世界的完整应用中可能产生的微不足道的影响变得不可忽视.我怀疑如果你对你的测试进行了剖析,那么大部分时间都在这个summarize子句中,所以让microbenchmark类似于那样:

> set.seed(99);z=sample(10000,4,TRUE)
> microbenchmark(z %>% unique %>% list, list(unique(z)))
Unit: microseconds
                  expr     min      lq      mean   median      uq     max neval
 z %>% unique %>% list 142.617 144.433 148.06515 145.0265 145.969 297.735   100
       list(unique(z))   9.289   9.988  10.85705  10.5820  11.804  12.642   100
Run Code Online (Sandbox Code Playgroud)

这与您的代码有所不同,但说明了重点.管道比较慢.

因为管道需要将R的调用重组为功能评估正在使用的相同的调用,然后对它们进行评估.因此,要慢一些.多少取决于功能的快速程度.在R中调用unique并且list相当快,所以这里的全部差异是管道开销.

像这样的分析表达式表明我大部分时间花在管道函数上:

                         total.time total.pct self.time self.pct
"microbenchmark"              16.84     98.71      1.22     7.15
"%>%"                         15.50     90.86      1.22     7.15
"eval"                         5.72     33.53      1.18     6.92
"split_chain"                  5.60     32.83      1.92    11.25
"lapply"                       5.00     29.31      0.62     3.63
"FUN"                          4.30     25.21      0.24     1.41
 ..... stuff .....
Run Code Online (Sandbox Code Playgroud)

然后在大约第15位的某处,真正的工作完成:

"as.list"                      1.40      8.13      0.66     3.83
"unique"                       1.38      8.01      0.88     5.11
"rev"                          1.26      7.32      0.90     5.23
Run Code Online (Sandbox Code Playgroud)

然而,如果你只是将这些函数称为钱伯斯的意图,那么R直接归结为它:

                         total.time total.pct self.time self.pct
"microbenchmark"               2.30     96.64      1.04    43.70
"unique"                       1.12     47.06      0.38    15.97
"unique.default"               0.74     31.09      0.64    26.89
"is.factor"                    0.10      4.20      0.10     4.20
Run Code Online (Sandbox Code Playgroud)

因此经常引用的建议是,管道在你的大脑认为是连锁的命令行上是可以的,但不是在可能是时间关键的功能中.在实践中,这个开销可能会在一次调用glm几百个数据点时被消除,但这是另一个故事....

  • FWIW,`library(pipeR); z%>>%unique%>>%list`执行相同的操作,比`magrittr`版本快4倍,但仍然比纯基本版本慢. (7认同)
  • 从功能包中,`Compose`也更快`库(功能); 微基准(MAG = Z%>%独特%>%列表,base =列表(唯一的(Z)),有趣=撰写(唯一的,列表)(Z))`(静止6X作为碱如慢,虽然). (5认同)