快速data.table分配来自查找的多个列的列

时间:2016-05-24 05:19:19

标签: r data.table

我已经搜索了我正在尝试的规范方法但我似乎没有运气得到快速和优雅的工作。简而言之,我有一个包含多个值列的大表,并希望将每个值乘以查找表中的相应因子。我无法弄清楚如何动态传递我想要的列乘以查找值,或者如何在基本表达式之外引用查询值。

这是我的例子,我设置了300万行,有10个值列,这不需要太长时间,并且有点代表数据大小(这将作为更大的循环的一部分实现,因此强调表现)。我们的value_1:value_10列还有一个包含6个级别和一些匹配乘数的查找表。

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.value_1这样的i列,所以我可以将它们传递给循环或者更好,然后立即应用它们。

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:提供了几个好的工作答案。 Rafael的解决方案在一般情况下可能是最好的,但我会注意到我可以从Jangorecki推荐的呼叫结构中挤出几毫秒来换取相当令人生畏的辅助功能。我已将其标记为已回答,感谢大家的帮助。

3 个答案:

答案 0 :(得分:4)

您还可以使用lapply

cols <- noquote(paste0("value_",1:10))

random[lookup, (cols) := lapply (cols, function(x)  get(x) * get(paste0("i.", x))), by = .EACHI ]

如果您的数据集太大而且想要查看操作的进度条,可以使用pblapply

library(pbapply)

random[lookup, (cols) := pblapply(cols, function(x)  get(x) * get(paste0("i.", x))), by = .EACHI ]

答案 1 :(得分:3)

这比文本解析/调用构造慢约2倍,但更具可读性:

random[lookup, (wps) := Map('*', mget(wps), mget(paste0('i.', wps))), by = .EACHI]

答案 2 :(得分:2)

感谢jangorecki指出他的答案here,它使用辅助函数动态构建J表达式,然后立即计算所有。它避免了解析/获取的开销,似乎是我将得到的最快的解决方案。我也喜欢手动指定被调用函数的能力(我可能需要/而不是*的某些实例)并在评估之前检查J表达式。

batch.lookup = function(x) {
  as.call(list(as.name(":="),x
               ,as.call(c(
                 list(as.name("list")),
                 sapply(x, function(x) call("*", as.name(x), as.name(paste0("i.",x))), simplify=FALSE)
               ))
  ))
}

print(batch.lookup(wps))

`:=`(c("value_1", "value_2", "value_3", "value_4", "value_5", 
"value_6", "value_7", "value_8", "value_9", "value_10"), list(value_1 = value_1 * 
    i.value_1, value_2 = value_2 * i.value_2, value_3 = value_3 * 
    i.value_3, value_4 = value_4 * i.value_4, value_5 = value_5 * 
    i.value_5, value_6 = value_6 * i.value_6, value_7 = value_7 * 
    i.value_7, value_8 = value_8 * i.value_8, value_9 = value_9 * 
    i.value_9, value_10 = value_10 * i.value_10))

system.time(
  random[lookup, eval(batch.lookup(wps)), by = .EACHI])

   user  system elapsed 
   0.14    0.04    0.18