Подсчитывая, сколько раз условие истинно в каждой группе

Я использую смоделированный набор данных со многими группами (+ 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))

Использование 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

Использование 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 

Ответ 1

Проблема с dplyr заключается в том, что функция sum используется с выражением и большим количеством идентификаторов/групп. Из того, что Арун говорит в комментариях, я думаю, проблема с data.table похоже.

Рассмотрим приведенный ниже код: я уменьшил его до минимума, необходимого для иллюстрации проблемы. dplyr медленнее при суммировании выражения, даже если выражение включает только функцию идентификации, поэтому проблемы с производительностью не имеют ничего общего с оператором сравнения, чем оператор сравнения. Напротив, dplyr является быстрым при суммировании вектора. Еще большее увеличение производительности достигается за счет сокращения количества идентификаторов/групп от миллиона до десяти.

Причина в том, что гибридная оценка, т.е. оценка на С++, работает только в том случае, если сумма используется с вектором. С выражением в качестве аргумента оценка выполняется в 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))) })

Код профиля:

Профиль кода