Я пытаюсь применить функцию к столбцам data.table и группировать по значению столбцов.
Я использую функцию lapply, но мой сценарий работает довольно медленно.
Чтобы дать некоторый контекст, я работаю со значениями вероятности:
Вот воспроизводимый пример с фиктивными значениями:
###########
# 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
Есть ли способ сократить время обработки шага 2? Я также удивлен тем, что когда я пытаюсь объединить два шага в уникальную строку кода, на самом деле это происходит намного медленнее, около 10 минут (см. «тест: 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. Смотрите мой комментарий в моем ответе :)
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
Посмотрите
{collapse}
.