【问题标题】:Fast data.table assign of multiple columns by group from lookup从查找中按组快速分配多列的 data.table
【发布时间】:2016-05-24 05:19:18
【问题描述】:

我一直在寻找规范的方式来做我正在尝试的事情,但我似乎没有什么运气可以让一些快速而优雅的工作。简而言之,我有一个包含多个值列的大表,并且希望将每个列乘以查找表中的相应因子。我不知道如何动态传递我想要乘以查找值的列,或者如何在基本表达式之外引用查找值。

这是我的示例,我设置了 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:用 eval(parse(text=col)) 替换 get() 似乎已经成功了。

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 推荐的调用构造中挤出几毫秒,以换取看起来相当吓人的辅助函数。我已将其标记为已回答,感谢大家的帮助。

【问题讨论】:

  • 我猜想使用mget 而不是eval(parse(...)) 应该可以达到相同的结果(但没有测试)。如果您自己回答了问题,请将正确的解决方案发布为“答案”(而不是编辑)。 THX :-)
  • 这个问题可能会有所帮助:stackoverflow.com/questions/30468455/… - 你可以从那里尝试最新的解决方案,我相信它应该是最有效的,因为它避免了来自 get 的解析和具体化字段。如果您找到比当前提供的更快/更好的解决方案,请随时自行回答。
  • R Yoda,不幸的是 mget 不像 eval(parse(...)) 那样对我有用。 Jangorecki,感谢您的链接!我认为您的最后一个解决方案是我见过的最快的解决方案,并且检查 J 表达式的能力使其对正在发生的事情更加直观。我将发布我的版本作为这个问题的答案。
  • 是在“设计时”(=当您编写 R 代码时)预定义的用于计算的列名,还是需要通过使用来支持计算中的任意数量的列一些列选择逻辑(如正则表达式和编号)?我问这个是因为它会影响可能的解决方案/答案......
  • 解决方案需要支持预定义列名的任意子集。因此,如果我们有value_1:value_10,它可能需要根据传递给函数的内容应用于所有 10 个、5 个随机或单个。但是我们会提前知道他们都叫value_1:value_10

标签: r data.table


【解决方案1】:

你也可以使用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 ]

【讨论】:

  • 这仅比调用构造慢几毫秒,并且避免了对辅助函数的需要,所以谢谢你的回答。我很好奇你为什么包含with = F,但它似乎不需要并且不会影响时间?
  • @etrippler,我很乐意提供帮助。你是对的,没有必要使用with = FHere is an explanation on when to use it
  • 在这里使用Map 更好:(cols) := Map(`*`, mget(cols), mget(icols)) where icols = paste0("i.", cols)
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    感谢 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
    

    【讨论】:

      猜你喜欢
      • 2012-12-08
      • 1970-01-01
      • 2012-07-25
      • 2016-03-17
      • 1970-01-01
      • 2012-11-13
      • 2020-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多