Asignación rápida de data.table de varias columnas por grupo desde la búsqueda

He buscado la forma canónica de hacer lo que estoy intentando, pero parece que tengo poca suerte para conseguir que algo funcione de manera rápida y elegante. En resumen, tengo una tabla grande con varias columnas de valor y quiero multiplicar cada una por un factor correspondiente de una tabla de búsqueda. No puedo entender cómo pasar dinámicamente en qué columnas quiero multiplicar por los valores de búsqueda, o cómo referirme a los valores de búsqueda en general fuera de las expresiones básicas.

Aquí está mi ejemplo, lo configuré con 3 millones de filas con 10 columnas de valor, esto no toma demasiado tiempo y es algo representativo del tamaño de los datos (esto se implementará como parte de un bucle mucho más grande, de ahí el énfasis en rendimiento). También hay una tabla de búsqueda con 6 niveles y algunos multiplicadores surtidos para nuestras columnas 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")

Solución 1: es bastante rápido, pero no puedo entender cómo hacer referencia genérica a las columnas i comoi.value_1 así que puedo pasarlos en un bucle o mejor aún aplicarlos todos a la vez.

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 

Solución 2: después de que no pude conseguir que la solución 1 fuera genérica, probé unset() enfoque basado Sin embargo, a pesar de permitirme especificar las columnas de valor objetivo en el vector de caractereswps, en realidad es mucho más lento que el anterior. Sé que lo estoy usando mal, pero no estoy seguro de cómo mejorarlo para eliminar toda la sobrecarga [.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 

Cualquier consejo sobre cómo estructurar mejor estas operaciones sería apreciado.

Editar: Estoy muy frustrado conmigo mismo por no probar esta solución obvia antes de publicar esta pregunta:

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 

que parece hacer lo que quiero con relativa velocidad. Sin embargo, todavía es 10 veces más lento que la primera solución anterior (estoy seguro debido a la repeticiónget()) así que todavía estoy abierto a consejos.

Edición 2: Reemplazarget() coneval(parse(text=col)) Parece haber hecho el truco.

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 

Edición 3: Se han proporcionado varias buenas respuestas de trabajo. La solución de Rafael es probablemente la mejor en el caso general, aunque señalaré que podría exprimir algunos milisegundos más de la construcción de llamadas recomendada por Jangorecki a cambio de una función auxiliar de aspecto bastante intimidante. Lo marqué como respondido, gracias por la ayuda de todos.

Respuestas a la pregunta(3)

Su respuesta a la pregunta