我正在寻找一种技巧/技术来优化如下操作:
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
实际上,由于小标题的类和属性在排序后不会改变,因此看来以后没有办法利用排序。
有想法吗?
编辑:当计时实际上是用n <-1e5完成时,我错误地写了n <-5e4,我只是将其固定在编辑中。另外,这是我的规格:
> 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 :(得分:3)
这是由版本0.8.2中的回归引起的:
性能损失是非线性的,因此在第4458期中,它是500倍,在我的示例中是10000倍,在我的真实数据集中,我可能不得不等待宇宙的热死对其进行测量。 / p>
升级到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
答案 1 :(得分:1)
以下是您的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()
})
另一个变种,它删除了第一个管道,因此删除了注释中提到的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倍。