Быстрое назначение data.table нескольких столбцов по группам из поиска
Я искал канонический способ сделать то, что я пытаюсь, но мне, кажется, не повезло, получая что-то работающее, быстрое и элегантное. Короче говоря, у меня есть большая таблица с несколькими столбцами значений, и я хочу умножить каждый на соответствующий коэффициент из таблицы поиска. Я не могу понять, как динамически передавать столбцы, которые я хочу, умноженные на значения поиска, или как ссылаться на значения поиска в целом вне базовых выражений.
Вот мой пример, я настроил его на 3 миллиона строк с 10 столбцами значений, это не займет много времени и в некоторой степени отражает размер данных (это будет реализовано как часть гораздо большего цикла, поэтому акцент сделан на по производительности). Существует также справочная таблица с 6 уровнями и некоторыми различными множителями для наших столбцов value_1: value_10.
library(data.table)
setsize <- 3000000
value_num <- 10
factors <- c("factor_a", "factor_b", "factor_c", "factor_d", "factor_e", "factor_f")
random <- data.table(replicate(10, sample(factors, size = setsize, replace = T))
, replicate(10, rnorm(setsize, mean = 700, sd = 50)))
lookup <- data.table("V1" = factors, replicate(10, seq(.90, 1.5, length.out = length(factors))))
wps <- paste("value", c(1:10), sep = "_")
names(random)[11:20] <- wps
names(lookup)[2:11] <- wps
setkeyv(random, "V1")
setkeyv(lookup, "V1")
Решение 1: Это довольно быстро, но я не могу понять, как в целом ссылаться на i-столбцы, какi.value_1
так что я могу передать их в цикле или еще лучше применить их все сразу.
f <- function() {
random[lookup, value_1 := value_1 * i.value_1, by = .EACHI]
random[lookup, value_2 := value_2 * i.value_2, by = .EACHI]
random[lookup, value_3 := value_3 * i.value_3, by = .EACHI]
random[lookup, value_4 := value_4 * i.value_4, by = .EACHI]
random[lookup, value_5 := value_5 * i.value_5, by = .EACHI]
random[lookup, value_6 := value_6 * i.value_6, by = .EACHI]
random[lookup, value_7 := value_7 * i.value_7, by = .EACHI]
random[lookup, value_8 := value_8 * i.value_8, by = .EACHI]
random[lookup, value_9 := value_9 * i.value_9, by = .EACHI]
random[lookup, value_10 := value_10 * i.value_10, by = .EACHI]
}
system.time(f())
user system elapsed
0.184 0.000 0.181
Решение 2: После того, как я не смог получить решение 1, чтобы быть универсальным, я попыталсяset()
основанный подход. Однако, несмотря на то, что позволил мне указать целевые столбцы значений в символьном вектореwps
, это на самом деле намного медленнее, чем выше. Я знаю, что я использую это неправильно, но не уверен, как улучшить его, чтобы удалить все накладные расходы [.data.table.
idx_groups <- random[,.(rowstart = min(.I), rowend = max(.I)), by = key(random)][lookup]
system.time(
for (i in 1:nrow(idx_groups)){
rows <- idx_groups[["rowstart"]][i]:idx_groups[["rowend"]][i]
for (j in wps) {
set(random, i=rows, j=j, value= random[rows][[j]] * idx_groups[[j]][i])
}
})
user system elapsed
3.940 0.024 3.967
Любые советы о том, как лучше структурировать эти операции, будут оценены.
Изменить: я очень разочарован тем, что не смог попробовать это очевидное решение, прежде чем опубликовать этот вопрос:
system.time(
for (col in wps){
random[lookup, (col) := list(get(col) * get(paste0("i.", col))), by = .EACHI, with = F]
})
user system elapsed
1.600 0.048 1.652
который, кажется, делает то, что я хочу с относительной скоростью. Однако это все еще в 10 раз медленнее, чем первое решение выше (я уверен, из-за повторногоget()
) так что я все еще открыт для совета.
Изменить 2: Заменаget()
сeval(parse(text=col))
кажется, сделал свое дело.
system.time(
for (col in wps){
random[lookup, (col) := list(eval(parse(text=col)) * eval(parse(text=paste0("i.", col)))), by = .EACHI, with = F]
})
user system elapsed
0.184 0.000 0.185
Изменить 3: несколько хороших рабочих ответов были предоставлены. Решение Рафаэля, вероятно, является лучшим в общем случае, хотя я отмечу, что я мог бы выжать еще несколько миллисекунд из конструкции вызова, рекомендованной Джангорецким, в обмен на довольно пугающую выглядящую вспомогательную функцию. Я отметил это как ответ, спасибо за помощь всем.