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

标签 r performance dplyr magrittr cardinality

我认为一般来说使用 %>% 不会对速度产生明显的影响。但在这种情况下,它的运行速度要慢 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))

不带管道:

min       lq     mean   median       uq      max neval
1.691441 1.739436 1.841157 1.812778 1.880713 2.495853   100

带管道:

min       lq     mean   median       uq      max neval
6.753999 6.969573 7.167802 7.052744 7.195204 8.833322   100

为什么在这种情况下 %>% 慢得多?有更好的写法吗?

编辑:

我缩小了数据框,并将 Moody_Mudskipper 的建议纳入基准测试中。

microbenchmark(
  nopipe=dummy_data %>% group_by(id) %>% summarise(list(unique(label))),
  magrittr=dummy_data %>% group_by(id) %>% summarise(label %>% unique %>% list),
  magrittr2=dummy_data %>% group_by(id) %>% summarise_at('label', . %>% unique %>% list),
  fastpipe=dummy_data %.% group_by(., id) %.% summarise(., label %.% unique(.) %.% list(.))
)

Unit: milliseconds
      expr       min        lq      mean    median        uq      max neval
    nopipe  59.91252  70.26554  78.10511  72.79398  79.29025 214.9245   100
  magrittr 469.09573 525.80084 568.28918 558.05634 590.48409 767.4647   100
 magrittr2  84.06716  95.20952 106.28494 100.32370 110.92373 241.1296   100
  fastpipe  93.57549 103.36926 109.94614 107.55218 111.90049 162.7763   100

最佳答案

当编写依赖于以前“可忽略”的时间的单行代码时,在现实世界的完整应用程序中可能可以忽略不计的影响变得不可忽略。我怀疑如果你分析你的测试,那么大部分时间都会在 summarize 子句中,所以让我们对类似的东西进行微基准测试:

> 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

这与您的代码做了一些不同的事情,但说明了这一点。管道速度较慢。

因为管道需要将 R 的调用重组为函数求值所使用的相同调用,然后对它们求值。所以它必须要慢一些。多少取决于函数的速度。在 R 中,对 uniquelist 的调用非常快,因此这里的全部区别在于管道开销。

对这样的表达式进行分析表明,大部分时间都花在管道函数上:

                         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 .....

然后在大约第 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

而如果您只是按照 Chambers 的意图调用函数,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

因此,经常被引用的建议是,管道在命令行上是可以的,因为你的大脑以链式方式思考,但在可能对时间要求严格的函数中则不行。实际上,这种开销可能会在一次调用带有数百个数据点的 glm 时被消除,但那是另一回事了......

关于r - 对于高基数分组,为什么使用 dplyr 管道 (%>%) 比等效的非管道表达式慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35933272/

相关文章:

r - 在 R 中使用 if else 为 for 语句的每次迭代保存值

java - Android 是否禁止使用浮点类型

c# - 比较大整数列表和小整数列表的最有效方法是什么?

r - dplyr R 中的欧氏距离

r - 为什么表达式 LHS 中的 `rlang::sym` 和 `rlang::quo_name` 表现相似?

R map 绘制经度和​​纬度点

r - 列表中的模型无法预测

r - 根据外部数据表更新指定列中的值

performance - Canvas 多个文本绘制性能

r - 在 dplyr 中使用多个条件进行过滤