R:c_across() 非常慢 - 有没有更快但优雅的方法?

Rob*_*sen 2 r dplyr

这个问题的根本主题实际上是关于《D&D 5E》中的积分购买系统,但在研究它时,我注意到这c_across()大大减慢了速度。

该方法

die_value <- 8L:15L
point_value <- c(0L, 1L, 2L, 3L, 4L, 5L, 7L, 9L)

determine_points <- function(n) {
    sapply(n, function(x) point_value[which(die_value == x)])
}

sum27 <- crossing(
    d1 = die_value,
    d2 = die_value,
    d3 = die_value,
    d4 = die_value,
    d5 = die_value,
    d6 = die_value
) %>%
    mutate(across(d1:d6, ~determine_points(.x), .names = "{col}_points")) %>% # see next blocks for options
Run Code Online (Sandbox Code Playgroud)

作为第一次尝试按行对d[n]_points列进行求和total_points,我尝试采用明显的rowwise %>% sum(c_across)路线

rowwise %>% mutate(total_points = sum(c_across(d1_points:d6_points)))
Run Code Online (Sandbox Code Playgroud)

然而,运行这个过程花了很长时间。相比之下,更明确的版本确实很快。

mutate(total_points = d1_points + d2_points + d3_points + d4_points + d5_points + d6_points)
Run Code Online (Sandbox Code Playgroud)

平衡速度与便利性始终是一个挑战,但这里的差异是巨大的。

有没有更好的方法来总结数据帧的某些行,同时对需要添加的列数保持一点不可知性?

akr*_*run 6

我们可以使用withrowSums代替+ 。使用,我们还可以删除如果存在的sumacrossrowwisec_acrossrowSumsNA

library(dplyr)
...
%>%
mutate(total_points = rowSums(across(d1_points:d6_points), na.rm = TRUE))
Run Code Online (Sandbox Code Playgroud)

或者在较新的版本 ( dplyr version >= 1.1.0) 中,使用pick选择列,因为我们将该函数应用于rowSums列的整个子集而不是每列本身。根据?pick

使用 pick(),您通常将函数应用于整个数据框。

使用 across(),您通常会对每一列应用一个函数。

%>%
  mutate(total_points = rowSums(pick(ends_with("_points")), na.rm = TRUE))
Run Code Online (Sandbox Code Playgroud)

如果没有 NA,还可以选择reduce使用+

library(purrr)
...
%>%
   mutate(total_points = reduce(across(1_points:d6_points), `+`))
Run Code Online (Sandbox Code Playgroud)

基准测试

> dim(sum27)
[1] 262144     12
> system.time(sum27 %>%
+ mutate(total_points = rowSums(across(d1_points:d6_points),
   na.rm = TRUE)))
   user  system elapsed 
  0.021   0.006   0.027 
# stopped the timing
> system.time(sum27 %>% rowwise %>% 
+ mutate(total_points = sum(c_across(d1_points:d6_points)))
+ )
#Timing stopped at: 122.3 0.892 124.6
> system.time(sum27 %>%
+ mutate(total_points = reduce(across(d1_points:d6_points), `+`)))
   user  system elapsed 
  0.030   0.010   0.039 
Run Code Online (Sandbox Code Playgroud)