【问题标题】:Evaluating both column name and the target value within `j` expression within `data.table`在 `data.table` 中的 `j` 表达式中评估列名和目标值
【发布时间】:2017-02-04 19:05:48
【问题描述】:

考虑

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

当它是一个变量时,我们也可以在 data.table 范围内调用该列

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 

问题:我在这里想念的还有什么更好的方法吗?一些更惯用或更有效的东西


编辑

最初我在寻找一些惯用的东西,所以我认为使用get 的@GGrothendieck 简单解决方案是一个,但令人惊讶的是,所有@Richard 版本甚至都击败了 ins't 做任何事情的版本评估列名

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 与行计数器一起使用?这不可能是最佳的。为什么sum 是 0/1 标量?使用dt[,mycol:=0L];dt[get(target)==value,mycol:=1L] 可以更快地生成相同的向量(在我的计算机上)700 倍。检查dt[,table(mycol,NEWCOL5)]
  • 除了我从 Richard 的回答中学到了有趣的替换,我仍然无法处理字符向量输入上类似的 programming update by reference。类似于:a)select &lt;- c("value"); DT[JN, c("value") := list(i.value)] 和 b)select &lt;- c("value","meta"); DT[JN, c("value","meta") := list(i.value,i.meta)。尝试使用lapply(select, as.name(paste0("i.",select))),但嵌套在列表中的names 似乎没有被捕获。我可能会为此提出一个新问题。

标签: r data.table


【解决方案1】:

这是一种可能的选择。

target <- "vs"
value <- 1
dt <- as.data.table(head(mtcars))

就代码而言,它不一定更简单,但我们可以在dt 的范围之外定义一个未评估的调用cl,以便在数据表的环境中进行评估。

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

substitute() 对于较长的表达式可能是必需的。但在这种情况下,call() 会缩短代码并创建相同的cl 结果。所以cl也可以是

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 arg:gist
  • 您可以查看问题下的最后一条评论吗?也许这对你来说很简单......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-09
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多