【发布时间】: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