考虑以下事项:
target <- "vs"
value <- 1
library(data.table)
dt <- as.data.table(head(mtcars))
我正在尝试将列名和数值作为变量传递到data.table环境中的j表达式中,这相当于:
dt[, vs == 1]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
如果变量只包含值,则它可以正常工作。
dt[, vs == value]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
我们还可以在变量是数据表的范围内调用列。
dt[, target, with = FALSE]
# vs
# 1: 0
# 2: 0
# 3: 1
# 4: 1
# 5: 0
# 6: 1
但我不知道如何简单地将两者结合起来
注意: 我很清楚我可以简单地执行以下操作:
dt[[target]] == value
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
但我需要在数据表范围内进行操作,以便可以通过引用修改其他列,类似于以下内容:
dt[, NEWCOL := sum(vs == 1), by = am]
当列名和值都是变量时,以下是我的尝试:
dt[, target == value, with = FALSE]
# Null data.table (0 rows and 0 cols)
dt[, target == value]
# [1] FALSE
dt[, (target) == value]
# [1] FALSE
dt[, .(target == value)]
# V1
# 1: FALSE
dt[, eval(target) == value]
# [1] FALSE
dt[target %in% value]
## Empty data.table (0 rows) of 11 cols: mpg,cyl,disp,hp,drat,wt...
最终我想到了:
dt[, .SD[[target]] == value]
# [1] FALSE FALSE TRUE TRUE FALSE TRUE
但这种方法非常低效,这里有一个简单的基准测试。
set.seed(123)
n <- 1e6
dt <- data.table(vs = sample(1L:30L, n, replace = TRUE), am = seq_len(n))
system.time(dt[, NEWCOL := sum(.SD[[target]] == value), by = am])
# user system elapsed
# 13.00 0.02 13.12
system.time(dt[, NEWCOL2 := sum(vs == value), by = am])
# user system elapsed
# 0.82 0.00 0.83
问题: 我是否遗漏了更好的做法?有没有更符合惯用语或更有效的方法?
编辑
最初,我在寻找符合惯用语的方法,所以我认为@GGrothendieck使用get
的简单解决方案是最好的,但令人惊讶的是,所有@Richard的版本都比那个没有对列名进行任何评估的版本更好。
set.seed(123)
n <- 1e7
dt <- data.table(vs = sample(1L:30L, n, replace = TRUE), am = seq_len(n))
cl <- substitute(
x == y,
list(x = as.name(target), y = value)
)
cl2 <- call("==", as.name(target), value)
system.time(dt[, NEWCOL := sum(vs == value), by = am])
# user system elapsed
# 0.83 0.00 0.82
system.time(dt[, NEWCOL1 := sum(.SD[[target]] == value), by = am])
# user system elapsed
# 8.97 0.00 8.97
system.time(dt[, NEWCOL2 := sum(get(target) == value), by = am])
# user system elapsed
# 2.35 0.00 2.37
system.time(dt[, NEWCOL3 := sum(eval(cl)), by = am])
# user system elapsed
# 0.69 0.02 0.71
system.time(dt[, NEWCOL4 := sum(eval(cl2)), by = am])
# user system elapsed
# 0.76 0.00 0.77
system.time(dt[, NEWCOL5 := sum(eval(as.name(target)) == value), by = am])
# user system elapsed
# 0.78 0.00 0.78
by
?那不可能是最优的。而且为什么要对一个0/1标量进行求和?同样的向量可以通过dt[,mycol:=0L];dt[get(target)==value,mycol:=1L]
在我的电脑上快700倍。用dt[,table(mycol,NEWCOL5)]
进行检查。 - Frankselect <- c("value"); DT[JN, c("value") := list(i.value)]
和 b)select <- c("value","meta"); DT[JN, c("value","meta") := list(i.value,i.meta)
这样的东西。尝试使用lapply(select, as.name(paste0("i.",select)))
,但列表中嵌套的名称似乎无法被捕获。我可能会为此发起一个新问题。 - jangorecki