Уменьшите время обработки применения функции к вектору и группировки по столбцам

Я пытаюсь применить функцию к столбцам data.table и группировать по значению столбцов.

Я использую функцию lapply, но мой сценарий работает довольно медленно.

Чтобы дать некоторый контекст, я работаю со значениями вероятности:

  • Сначала я умножаю каждый набор из 5 значений вероятности для каждого «id» на случайное значение.
  • Затем я делаю следующий расчет, группируя по переменным «group_1» и «group_2»: PD_3_N=1-PROD(1-PD_2_N)

Вот воспроизводимый пример с фиктивными значениями:

###########
# Dummy data
set.seed(99)
n_col <- 4
size <- 3e6
num_group2 <- 10
vec_1 <- paste0("PD_1_N", (0:n_col))
vec_2 <- paste0("PD_2_N", (0:n_col))
vec_3 <- paste0("PD_3_N", (0:n_col))
id <- rep(seq(1, size, 1), num_group2)
group_1 <- rep(sample(seq(1, size, 1), size=size, replace=TRUE), num_group2)
group_2 <- sort(rep(seq(1, num_group2, 1), size))
factor <- runif (size*num_group2, 0.5, 4)
data <- data.table(id, group_1, group_2, factor)
data[, vec_1] <- data.table(rep(runif (size, 0, 0.5), num_group2), 
                            rep(runif (size, 0, 0.5), num_group2), 
                            rep(runif (size, 0, 0.5), num_group2), 
                            rep(runif (size, 0, 0.5), num_group2), 
                            rep(runif (size, 0, 0.5), num_group2))
###############
# lapply step 1
t <- Sys.time()
data[, (vec_2) := lapply(.SD, function(x) pmin(1, factor*x)), .SDcols=vec_1]
Sys.time() - t

###############
# lapply step 2
t <- Sys.time()
data[, (vec_3) := lapply(.SD, function(x) 1 - prod((1 - x))), 
     by=c("group_1", "group_2"), .SDcols=vec_2]
Sys.time() - t

######################
# test: 2 steps in one
t <- Sys.time()
data[, (vec_3) := lapply(.SD, function(x) 1 - prod((1 - pmin(1, factor*x)))), 
     by=c("group_1", "group_2"), .SDcols=vec_1]
Sys.time() - t
# end test
  • Шаг 1 довольно быстрый: около 1 секунды.
  • Шаг 2 довольно медленный: около 1,9 минут.

Есть ли способ сократить время обработки шага 2? Я также удивлен тем, что когда я пытаюсь объединить два шага в уникальную строку кода, на самом деле это происходит намного медленнее, около 10 минут (см. «тест: 2 шага в одном» в приведенном выше коде).

Посмотрите {collapse}.

Friede 12.07.2024 09:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
1
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Медленная часть кажется сгруппированной 1 - x. Попробуйте удалить его из сгруппированных вычислений:

t <- Sys.time()
data[, (vec_3) := lapply(.SD, function(x) 1 - x), .SDcols = vec_2]
data[, (vec_3) := lapply(.SD, prod), by = c("group_1", "group_2"), .SDcols = vec_3]
data[, (vec_3) := lapply(.SD, function(x) 1 - x), .SDcols = vec_3]
Sys.time() - t

Сгруппированный 1-х тоже медленный, вы правы! Но основную оптимизацию дает GForce. Смотрите мой комментарий в моем ответе :)

Telaroz 12.07.2024 13:25
Ответ принят как подходящий

data.table имеет оптимизированную версию prod. Авторы data.table называют это оптимизацией «GForce». Смотрите раздел «Что это за волшебство» здесь.

Проблема здесь в том, что это не сработает, если несколько операций выполняются вместе и внутри prod.

Нам нужно построить его шаг за шагом:

# First, what is inside the prod. No need for grouping
data[, (vec_3) := lapply(.SD, function(x) 1 - x), .SDcols = vec_2]

# Now, the prod alone, by groups. Now we take vec_3 in .SDcols

data[, (vec_3) := lapply(.SD, prod), 
     by = c("group_1", "group_2"), .SDcols = vec_3]

# Now, the outer operation, no need for grouping
data[, (vec_3) := lapply(.SD, function(x) 1 - x), .SDcols = vec_3]


Мои результаты сравнения обоих подходов:

> data.table::setkey(data, group_1, group_2)
> # lapply step 2
> t <- Sys.time()
> data[, (vec_3) := lapply(.SD, function(x) 1 - prod((1 - x))), 
+      by=c("group_1", "group_2"), .SDcols=vec_2]
> t <- Sys.time()
> data[, (vec_3) := lapply(.SD, function(x) 1 - prod((1 - x))), 
+      by=c("group_1", "group_2"), .SDcols=vec_2]
> Sys.time() - t
Time difference of 1.847846 mins

> # Optmized
> t <- Sys.time()
> # First, what is inside the prod. No need for grouping
> data[, (vec_3) := lapply(.SD, function(x) 1 - x), .SDcols = vec_2]
> 
> # Now, the prod alone, by groups. Now we take vec_3 in .SDcols
> 
> data[, (vec_3) := lapply(.SD, prod), 
+      by = c("group_1", "group_2"), .SDcols = vec_3]
> 
> # Now, the outer operation, no need for grouping
> data[, (vec_3) := lapply(.SD, function(x) 1 - x), .SDcols = vec_3]
> Sys.time() - t
Time difference of 4.312201 secs

Итак, 1,9 минуты против 4,31 секунды.

Посмотрите, когда GForce оптимизируется или нет, с помощью: (есть некоторые накладные расходы)

options(datatable.verbose = TRUE)

Finding groups using uniqlist on key ... 0.540s elapsed (0.390s cpu) 
Finding group sizes from the positions (can be avoided to save RAM) ... 0.110s elapsed (0.080s cpu) 
lapply optimization changed j from 'lapply(.SD, prod)' to 'list(prod(PD_3_N0), prod(PD_3_N1), prod(PD_3_N2), prod(PD_3_N3), prod(PD_3_N4))'
GForce optimized j to 'list(gprod(PD_3_N0), gprod(PD_3_N1), gprod(PD_3_N2), gprod(PD_3_N3), gprod(PD_3_N4))' (see ?GForce)
Making each group and running j (GForce TRUE) ... gforce initial population of grp took 0.028
gforce assign high and low took 0.025
gforce eval took 1.122
1.310s elapsed (0.970s cpu) 
Assigning to 30000000 row subset of 30000000 rows
RHS_list_of_columns == true

В этом примере знак + деактивирует GForce, поэтому большая часть оптимизации сохраняется.

> data[, (vec_3) := lapply(.SD, prod), .SDcols = vec_3, by = c("group_1", "group_2")]
Finding groups using uniqlist on key ... 0.510s elapsed (0.420s cpu) 
Finding group sizes from the positions (can be avoided to save RAM) ... 0.110s elapsed (0.090s cpu) 
lapply optimization changed j from 'lapply(.SD, prod)' to 'list(prod(PD_3_N0), prod(PD_3_N1), prod(PD_3_N2), prod(PD_3_N3), prod(PD_3_N4))'
GForce optimized j to 'list(gprod(PD_3_N0), gprod(PD_3_N1), gprod(PD_3_N2), gprod(PD_3_N3), gprod(PD_3_N4))' (see ?GForce)
Making each group and running j (GForce TRUE) ... gforce initial population of grp took 0.028
gforce assign high and low took 0.030
gforce eval took 1.212
1.390s elapsed (0.750s cpu) 
Assigning to 30000000 row subset of 30000000 rows
RHS_list_of_columns == true
> Sys.time() - t
Time difference of 3.031504 secs
> t <- Sys.time()
> data[, (vec_3) := lapply(.SD, function(x) +prod(x)), .SDcols = vec_3, by = c("group_1", "group_2")]
Finding groups using uniqlist on key ... 0.530s elapsed (0.480s cpu) 
Finding group sizes from the positions (can be avoided to save RAM) ... 0.110s elapsed (0.090s cpu) 
lapply optimization changed j from 'lapply(.SD, function(x) +prod(x))' to 'list(..FUN1(PD_3_N0), ..FUN1(PD_3_N1), ..FUN1(PD_3_N2), ..FUN1(PD_3_N3), ..FUN1(PD_3_N4))'
GForce is on, but not activated for this query; left j unchanged (see ?GForce)
Old mean optimization is on, left j unchanged.
Making each group and running j (GForce FALSE) ... 
  memcpy contiguous groups took 7.584s for 18964290 groups
  eval(j) took 63.648s for 18964290 calls
00:01:30 elapsed (00:01:22 cpu) 
> Sys.time() - t
Time difference of 1.514286 mins

Другие вопросы по теме