7

我试图回答这个关于为 data.table 对象创建非标准评估函数的好问题,并进行分组求和。Akrun 想出了一个可爱的答案,我将在这里简化:

akrun <- function(data, var, group){
 var <- substitute(var)
 group <- substitute(group)
 data[, sum(eval(var)), by = group]
}

library(data.table)
mt = as.data.table(mtcars)
akrun(mt, cyl, mpg)
#    group    V1
# 1:     6 138.2
# 2:     4 293.3
# 3:     8 211.4

我也在研究一个答案,并且得到了接近相同的答案,但substitutes 与其余的内联。我的结果是一个错误:

gregor = function(data, var, group) {
  data[, sum(eval(substitute(var))), by = substitute(group)]
} 

gregor(mt, mpg, cyl)
# Error in `[.data.table`(data, , sum(eval(substitute(var))), by = substitute(group)) : 
#  'by' or 'keyby' must evaluate to vector or list of vectors 
#  (where 'list' includes data.table and data.frame which are lists, too) 

从表面上看,我的功能是对 Akrun 的简单替代。为什么它不起作用?


请注意,这两种替换都会导致问题,如下所示:

gregor_1 = function(data, var, group) {
  var = substitute(var)
  data[,sum(eval(var)), 
       by = substitute(group)]
} 
gregor_1(mt, mpg, cyl)
# Same error as above


gregor_2 = function(data, var, group) {
  group = substitute(group)
  data[,sum(eval(substitute(var))), 
       by = group]
} 
gregor_2(mt, mpg, cyl)
# Error in eval(substitute(var)) : object 'mpg' not found 
4

3 回答 3

7

substitute的文档中,您可以阅读它如何决定替换什么,以及默认情况下它会搜索调用它的环境这一事实。如果您在框架内(即 inside )调用substitutedata.table它将[]无法找到符号,因为它们不存在于data.table评估环境中,它们位于[被调用的环境中。

您可以“反转”调用函数的顺序以获得所需的行为:

library(data.table)

foo <- function(dt, group, var) {
    eval(substitute(dt[, sum(var), by = group]))
}

foo(as.data.table(mtcars), cyl, mpg)
   cyl    V1
1:   6 138.2
2:   4 293.3
3:   8 211.4
于 2019-11-01T13:12:29.983 回答
3

data.table使用 NSE 是因为它需要by在选择是否评估它之前分析/操作参数(如果你给它一个符号,例如它不会评估它)。

结果是,如果需要评估参数,则应在正确的环境中对其进行评估,这是函数的责任。data.table在数据中评估其by参数,而不是在调用环境中。

在大多数情况下,您不会看到问题,因为如果未找到符号,则会在父环境中评估该符号,但substitute()它更敏感。

请参见下面的示例:

fun <- function(x){
  standard_eval(x)
  non_standard_eval_safe(x)
  non_standard_eval_not_safe(x)
}

standard_eval          <- function(expr) print(expr)

non_standard_eval_safe <- function(expr) {
  expr <- bquote(print(.(substitute(expr)))) # will be quote(print(x)) in our example
  eval.parent(expr)
}

non_standard_eval_not_safe <- function(expr) {
  expr <- bquote(print(.(substitute(expr))))  # will be quote(print(x)) in our example
  eval(expr)
}

standard_eval(1+1)          
#> [1] 2

non_standard_eval_safe(1+1)
#> [1] 2

non_standard_eval_not_safe(1+1)
#> [1] 2

fun(1+1)
#> [1] 2
#> [1] 2
#> Error in print(x): object 'x' not found


reprex 包于 2020-02-20 创建(v0.3.0)

于 2020-02-20T15:12:15.263 回答
3

似乎它substitute在数据表中的工作方式与人们可能期望它在其他上下文中的工作方式不同,但您可以使用enexprrlang 包代替substitute

library(data.table)
library(rlang)

gregor_rlang = function(data, var, group) {
  data[, sum(eval(enexpr(var))), by = .(group = eval(enexpr(group)))]
} 

gregor_rlang(mt, mpg, cyl)
##    group    V1
## 1:     6 138.2
## 2:     4 293.3
## 3:     8 211.4

环境

这个问题似乎与环境有关,因为这适用于我们特别指定环境substitute应该使用的地方。

gregor_pf = function(data, val, group) {
  data[, sum(eval(substitute(val, parent.env(environment())))), 
    by = c(deparse(substitute(group)))]
} 
gregor_pf(mt, mpg, cyl)
##      cyl    V1
## 1:     6 138.2
## 2:     4 293.3
## 3:     8 211.4
于 2019-10-31T20:28:30.030 回答