在每个组内计算条件为真的次数

13

我正在使用一个模拟数据集,其中包含许多组(+2百万),我想计算每个组的观测总数以及超过阈值(这里是2)的观测数量。

当我创建一个标志变量时,似乎速度更快,特别是对于dplyr来说,而对于data.table也稍微快一点。

为什么会这样?在每种情况下它是如何背后工作的?

请查看下面的示例。

模拟数据集

# create an example dataset
set.seed(318)

N = 3000000 # number of rows

dt = data.frame(id = sample(1:5000000, N, replace = T),
                value = runif(N, 0, 10))

使用dplyr

library(dplyr)

# calculate summary variables for each group
t = proc.time()
dt2 = dt %>% group_by(id) %>% summarise(N = n(),
                                        N2 = sum(value > 2))
proc.time() - t

# user  system elapsed
# 51.70    0.06   52.11


# calculate summary variables for each group after creating a flag variable
t = proc.time()
dt2 = dt %>% mutate(flag = ifelse(value > 2, 1, 0)) %>%
  group_by(id) %>% summarise(N = n(),
                             N2 = sum(flag))
proc.time() - t

# user  system elapsed
# 3.40    0.16    3.55

Using data.table

library(data.table)

# set as data table
dt2 = setDT(dt, key = "id")


# calculate summary variables for each group
t = proc.time()
dt3 = dt2[, .(N = .N,
              N2 = sum(value > 2)), by = id]
proc.time() - t

# user  system elapsed 
# 1.93    0.00    1.94 


# calculate summary variables for each group after creating a flag variable
t = proc.time()
dt3 = dt2[, flag := ifelse(value > 2, 1, 0)][, .(N = .N,
                                                 N2 = sum(flag)), by = id]
proc.time() - t

# user  system elapsed 
# 0.33    0.04    0.39 

8
data.table中,sum(var).N被进行了gforce优化,但sum(expr)还没有进行优化。添加verbose = TRUE参数,查看优化表达式的不同之处。我们将来会更好地处理这些情况。 - Arun
2
data.table 中,我怀疑一个区别是第二个实例中的数据表已经排序,因此您没有计算它。尝试在每个实例上方放置 dt2 = setDT(dt, key = "id") - lmo
1
预先键入在我的机器上的第一个data.table实例中提供了相对较小的加速,从经过时间=2.30到经过时间=1.95。 - lmo
2
@Arun 可能还起到了一种方法对所有行进行逻辑比较,而另一种方法是通过分组来进行比较的作用? - Roland
3
仅使用逻辑比较(flag := value > 2)将进一步提高速度。 - Jaap
显示剩余8条评论
1个回答

1
问题在于dplyr使用sum函数时,表达式和大量ID/组一起使用。从Arun在评论中所说的内容来看,我猜测data.table的问题也类似。
考虑下面的代码:我将其减少到最少的必要部分以说明问题。即使表达式仅涉及身份函数,dplyr在对表达式求和时也很慢,因此性能问题与大于比较运算符无关。相反,当对向量求和时,dplyr速度很快。通过将ID/组的数量从一百万减少到十个,可以获得更大的性能提升。
原因是混合评估,即在C ++中进行评估,仅适用于将sum与向量一起使用的情况。如果作为参数的是表达式,则在R中进行评估,这会为每个组添加开销。详细信息请参见链接的vignette。从代码的概要中可以看出,开销主要来自tryCatch错误处理函数。
##########################
### many different IDs ###
##########################

df <- data.frame(id = 1:1e6, value = runif(1e6))

# sum with expression as argument
system.time(df %>% group_by(id) %>% summarise(sum(identity(value))))
#    user  system elapsed
#  80.492   0.368  83.251

# sum with vector as argument
system.time(df %>% group_by(id) %>% summarise(sum(value)))
#    user  system elapsed
#   1.264   0.004   1.279


#########################
### few different IDs ###
#########################

df$id <- rep(1:10, each = 1e5)

# sum with expression as argument
system.time(df %>% group_by(id) %>% summarise(sum(identity(value))))
#    user  system elapsed
#   0.088   0.000   0.093

# sum with vector as argument
system.time(df %>% group_by(id) %>% summarise(sum(value)))
#    user  system elapsed
#   0.072   0.004   0.077


#################
### profiling ###
#################

df <- data.frame(id = 1:1e6, value = runif(1e6))

profvis::profvis({ df %>% group_by(id) %>% summarise(sum(identity(value))) })

代码概要:

Code profile


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接