Быстрое назначение 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: несколько хороших рабочих ответов были предоставлены. Решение Рафаэля, вероятно, является лучшим в общем случае, хотя я отмечу, что я мог бы выжать еще несколько миллисекунд из конструкции вызова, рекомендованной Джангорецким, в обмен на довольно пугающую выглядящую вспомогательную функцию. Я отметил это как ответ, спасибо за помощь всем.

Ответы на вопрос(3)

Ваш ответ на вопрос