【问题标题】:Optimize `dplyr` group_by / summarize优化`dplyr` group_by / summarise
【发布时间】:2019-11-27 14:00:18
【问题描述】:

我正在寻找一种技巧/技术来优化如下操作:

library(dplyr)

n <- 1e5

d <- tibble(x=sample(800, n, TRUE),
            y=sample(2000, n, TRUE) %>% as.Date(origin='1970-01-01'),
            z=sample(5, n, TRUE),
            val=runif(n))

system.time({
  y_dp <- d %>%
    group_by(x, y) %>%
    summarize(w = val[which.max(z)])
})
#     user   system  elapsed 
# 1014.918    9.760 1027.845 

这是非常普通的 - 按 2 个变量分组,根据另外 2 个变量计算每个组的标量汇总。

data.table 能够以大约 10000 倍的效率处理这种大小的数据:

library(data.table)
system.time({
  y_dt <- data.table(d, key=c("x", "y")) %>%
    `[`(, .(w=val[which.max(z)]), by=list(x, y)) %>%
    as_tibble()
})
#    user  system elapsed 
#   0.109   0.003   0.112 

all.equal(y_dt, y_dp)
# TRUE

它大概可以通过基于键的索引(在这种情况下是排序),然后线性迭代结构来实现; dplyr 可能必须为每个组合 (x, y) 在结构中构造单独的索引。

(x, y) 预排序也无助于dplyr 的情况,因为它似乎没有“记住”数据是按其分组依据排序的:

system.time({
  y3 <- d %>%
    arrange(x, y) %>%
    group_by(x, y) %>%
    summarize(w = val[which.max(z)])
})
#     user   system  elapsed 
# 1048.983   13.616 1070.929 

确实,由于 tibble 的类和属性在排序后不会改变,似乎没有办法在之后利用排序。

想法?

编辑:我错误地写了 n

> sessionInfo()
R version 3.6.0 (2019-04-26)
Platform: x86_64-apple-darwin17.7.0 (64-bit)
Running under: macOS High Sierra 10.13.6

Matrix products: default
BLAS/LAPACK: /usr/local/Cellar/openblas/0.3.6_1/lib/libopenblasp-r0.3.6.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] dplyr_0.8.2

loaded via a namespace (and not attached):
 [1] tidyselect_0.2.5 compiler_3.6.0   magrittr_1.5     assertthat_0.2.1
 [5] R6_2.4.0         pillar_1.4.2     glue_1.3.1       tibble_2.1.3    
 [9] crayon_1.3.4     Rcpp_1.0.1       pkgconfig_2.0.2  rlang_0.4.0     
[13] purrr_0.3.2     

【问题讨论】:

  • 对我来说,只需 0.209 0.022 0.231summarize 更改为 summarise。此外,您是否也加载了plyr 库。我使用set.seed(24) 创建了一个可重现的示例
  • 是的,我在第一个示例中得到了 1.22 已过
  • 啊哈-我在私人图书馆中安装了dplyr_0.8.3,现在我也得到了0.447 0.050 0.500。这是相当戏剧性的! 0.8.3 的发行说明说“在版本 0.8.2 (#4458) 中引入了固定的性能回归”。
  • github.com/tidyverse/dplyr/issues/4458 - 除非其他人愿意,否则我可以将其写成答案。
  • 仅供参考,计划对 data.table 中的分组 var[which.max(x)] 进行进一步优化:github.com/Rdatatable/data.table/issues/523 我想它适用于有序因子,不确定。

标签: r optimization dplyr data.table


【解决方案1】:

这是由版本 0.8.2 中的回归引起的:

https://github.com/tidyverse/dplyr/issues/4458

性能损失是非线性的,所以在上面的 issue #4458 中它是 500x,在我的示例中是 10000x,在我的真实数据集中,我可能不得不等待宇宙的热寂来测量它。

升级到dplyr 0.8.3 为我解决了这个问题:

> install.packages('dplyr')
# Installing package into ‘/private/tmp/lib’
# ...

n <- 1e5

d <- tibble(x=sample(800, n, TRUE),
            y=sample(2000, n, TRUE) %>% as.Date(origin='1970-01-01'),
            z=sample(5, n, TRUE),
            val=runif(n))
system.time({
  y_dp <- d %>%
    group_by(x, y) %>%
    summarize(w = val[which.max(z)])
})
#   user  system elapsed 
#  0.447   0.050   0.500 

【讨论】:

  • 实际上,我发现它实际上并不能解决我的真实数据集上的问题,这有点复杂 - 我将把它写成 dplyr 的新错误报告,因为关于我在此处发布的问题,问题已解决。
【解决方案2】:

以下是您的data.table 代码的可能更易读的版本。

您可以使用来自 magittr 的 . 符号对 DT 进行管道传输。 另一个细节是您可以使用.(x, y) 而不是list(x,y)。 排序与聚合中的data.table 无关,但它与连接有关。

library(data.table)

system.time({
  y_dt <- data.table(d) %>% 
    .[, .(w = val[which.max(z)]), .(x,y)]
    as_tibble()
})

删除第一个管道的另一个变体,因此删除了 cmets 中提到的 magittr .

system.time({
  y_dt <- as.data.table(d)[, .(w = val[which.max(z)]), .(x,y)] %>%
    as_tibble()
})

请注意,我使用 as.data.table(d) 而不是 setDT,因为这会通过引用更改 d

基准测试:

       user  system elapsed 
dplyr  2.643   0.000   2.642
DT     0.158   0.000   0.092

在这个例子中,data.table 似乎仍然比 dplyr 快 28 倍。

【讨论】:

  • 谢谢,我喜欢它作为一种通用技术,但是. 在这个例子中开始有很多不同的含义,它也会变得相当混乱。在这种情况下,magrittr 点(.[])和data.table 点(.()),只有命名空间优先规则(或函数解析?)阻止它使用magrittr .[...] 调用中的点。
  • 至于排序 - 按已排序的变量进行分组不应该比未排序的变量更有效吗?
  • 我没有意识到. 的多种用途,但如果它在DT 内,它代表一个列表。你也可以传递一个字符向量或list(),但如果我觉得更冗长的话。据我了解,排序与聚合无关,因为它在内部进行排序。
  • 设置一个键将使连接更快,但对我来说,这很少值得我花时间(编程)时间。如果您使用的是 100GB DT,这是有道理的。即使DT 代码没有经过优化,在 99% 的情况下它对于大多数用途来说都是快速的。至少这是我的经验。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 2018-12-24
  • 2022-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多