计算每个组中条件成立的次数

Ant*_*osK 12 r dplyr data.table

我正在使用一个包含许多组(+ 2mil)的模拟数据集,其中我想计算每个组的观察总数和高于阈值(此处为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))
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

使用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 
Run Code Online (Sandbox Code Playgroud)

bbr*_*rot 1

dplyr 的问题在于 sum 函数与表达式和大量 ID/组一起使用。从 Arun 在评论中所说的,我猜 data.table 的问题是类似的。

考虑下面的代码:我将其减少到说明问题所需的最低限度。dplyr 对表达式求和时很慢,即使表达式仅涉及恒等函数,因此性能问题与大于比较运算符无关。相比之下,dplyr 对向量求和时速度很快。通过将 ID/组的数量从 100 万个减少到 10 个,可以实现更大的性能提升。

原因是混合求值(即 C++ 中的求值)仅在 sum 与向量一起使用时才有效。使用表达式作为参数,计算在 R 中完成,这会增加每个组的开销。详细信息位于链接的小插图中。从代码概况来看,开销主要来自于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))) })
Run Code Online (Sandbox Code Playgroud)

代码简介:

代码简介