在`data.table`中使用`j`表达式同时评估列名和目标值

9

考虑以下事项:

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 

这些对我来说看起来都很奇怪。(@jangorecki的悬赏引导我来到这里。)为什么要在行计数器中使用 by?那不可能是最优的。而且为什么要对一个0/1标量进行求和?同样的向量可以通过 dt[,mycol:=0L];dt[get(target)==value,mycol:=1L] 在我的电脑上快700倍。用 dt[,table(mycol,NEWCOL5)] 进行检查。 - Frank
除了从Richard的答案中学到有趣的替换方法之外,我仍然无法处理类似于字符向量输入的相似按引用编程更新。像a) select <- 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
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - jangorecki
1个回答

9
这里有一种可能的替代方案。
target <- "vs"
value <- 1
dt <- as.data.table(head(mtcars))

从代码角度来看,这并不一定更简单,但我们可以设置一个未求值的调用cl,其定义在dt范围之外,需要在数据表环境中求值。

cl <- substitute(
    x == y, 
    list(x = as.name(target), y = value)
)

substitute() 在处理更长的表达式时可能是必要的。但在这种情况下,call() 可以缩短代码并创建相同的 cl 结果。因此,cl 也可以使用call()

cl <- call("==", as.name(target), value)

现在我们可以在dt内部评估cl。 在您的示例中,这似乎运行良好。
dt[, NEWCOL := sum(eval(cl)), by = am][]
#     mpg cyl disp  hp drat    wt  qsec vs am gear carb NEWCOL
# 1: 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4      1
# 2: 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4      1
# 3: 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1      1
# 4: 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1      2
# 5: 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2      2
# 6: 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1      2

经过一分钟的思考,我不确定是否需要替换value,因此以下方法也可以。但正如David所指出的那样,第一种方法更加高效。

dt[, eval(as.name(target)) == value]
# [1] FALSE FALSE  TRUE  TRUE FALSE  TRUE

我发现这个答案非常有价值,可以为data.table的灵活使用做准备。不幸的是,在我颁发奖励之前,我需要等23小时。顺便说一下,i参数中的j参数的类似方法:gist - jangorecki
你能检查一下问题下面的最后一条评论吗?也许对你来说很简单... - jangorecki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接