为何dplyr::slice_min()函数性能表现异常缓慢?
为什么dplyr::slice_min()提取组内最小值对应行时性能远低于排序或summarize方法?
假设有一个数据框dat,包含group变量,每组有一个或多个x观测值,且组内x无重复值。提取每组中x取最小值对应观测值的一种方法是使用dplyr::slice_min()。
我认可slice_min()能清晰表达意图,但它的性能常常异常缓慢,如下测试所示。我原以为在组内对x值排序(逻辑上比找最小值更复杂)会更慢,为何排序方法反而快这么多?甚至我用summarize()的特殊写法都快得多!
更具体地说,我希望在组数n趋于无穷大且每组观测数为O(1)时,仍能保持良好的处理性能。
测试代码
library(dplyr) library(microbenchmark) # 模拟数据。y是我们想要保留的、对应x最小值的其他变量 set.seed(1) n <- 5e3 k <- 1 + rpois(n, 1) dat <- data.frame( group = rep(1:n, k), x = rnorm(sum(k)), y = sample(letters, sum(k), replace = TRUE) ) # 获取每组内x最小的观测行 microbenchmark( slice = dat |> group_by(group) |> slice_min(x) |> ungroup(), arrange = dat |> arrange(group, x) |> filter(!duplicated(group)), summarize = dat |> group_by(group) |> summarize(i = which.min(x), across(everything(), \(v) v[i])) |> select(!i), times = 10 )
性能测试结果
# 单位:毫秒 # expr min lq mean median uq max neval # slice 556.812802 625.876500 655.172451 632.45395 646.751201 909.931001 10 # arrange 3.148302 3.209201 3.348941 3.34970 3.441501 3.663301 10 # summarize 37.503501 37.946201 53.125181 38.17705 38.911001 127.843800 10
性能差异的核心原因
slice_min()的内部损耗:slice_min()是逐组单独执行排序并提取首行,当组数极多时,每个分组的初始化、排序校验等流程开销会被不断累积。另外它还包含了处理重复值、支持多参数的通用逻辑,这些在简单场景下反而成了不必要的性能负担。arrange()+filter的全局优化:arrange()基于底层高效的C++算法做全局排序,向量化操作的效率远高于逐组处理。排序后每组最小值行自然排在组内首位,再用!duplicated(group)保留每组第一行,这个步骤也是向量化的,几乎无额外开销。当每组观测数很少时,全局排序的时间复杂度(O(M log M))远低于逐组处理的复杂度(O(K*m log m)),性能优势明显。summarize()的中间定位:
这种写法逐组调用which.min(x)找最小值索引,再提取对应行。which.min()是轻量向量操作,比逐组排序快,但仍属于逐组执行,所以性能介于前两者之间。
大组数场景的优化建议
- 优先用
arrange()+filter(!duplicated()):在每组观测数少的场景下性能碾压其他方法,代码简洁性也不错。 - 可选
data.table实现:DT[, .SD[which.min(x)], by = group]的分组处理基于底层优化,性能接近arrange方法,适合严格依赖分组逻辑的场景。 - 仅在可读性优先时用
slice_min():比如团队协作中,它的语义最清晰,但要注意大组数场景的性能问题。
内容的提问来源于stack exchange,提问作者nahp
相关产品推荐
相关产品推荐

