【问题标题】:Why does `substitute` work in multiple lines, but not in a single line?为什么 `substitute` 可以在多行中工作,但不能在单行中工作?
【发布时间】:2019-10-31 18:42:49
【问题描述】:

我试图回答 this nice question 关于为 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 

【问题讨论】:

  • 我现在没有时间(或者可能没有能力)对此进行深入研究,但似乎在data.table 调用中,您传递给substitute 的任何变量仍然是符号(例如,如果你将它包装在 rleid 或 as.integer 中,对于像我这样的菜鸟来说,警告会更清楚一些)。那是对的吗?那么,substitute 在 data.table 调用中使用时不会查看函数环境吗?另外,我对 data.table 的所有事情都很熟悉,所以如果这已经很明显了,我很抱歉。
  • 我认为这是一个很好的贡献。 Akrun 最初的建议也是关于环境的,我尝试了substitute(var, env = parent.frame(environment())) 之类的版本,也没有运气。但这似乎是正确的轨道。
  • 嘿格雷戈尔! (来自 UW STTR 的迈克尔)。我最近一直在考虑为 data.table 过程编写函数,并且已经走上了创建函数的道路,这些函数将列名的字符向量作为参数而不是不带引号的符号。通常,使用 data.table 编写函数存在一些潜在问题,其中函数可能会通过创建临时列来利用底层 data.table 对象以避免复制整个表。潜在的列名冲突和更改输入表的键。我希望看到更多关于最佳做法的讨论。

标签: r data.table non-standard-evaluation


【解决方案1】:

在substitute 的文档中,您可以阅读它如何决定替换什么, 事实上,默认情况下,它会搜索调用它的环境。 如果您在 data.table 框架内调用 substitute (即在[] 内) 它将无法找到符号,因为它们不存在于 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

【讨论】:

    【解决方案2】:

    似乎substitute 在数据表中的工作方式与人们可能期望它在其他上下文中的工作方式不同,但您可以使用 rlang 包中的 enexpr 代替 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
    

    【讨论】:

      【解决方案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 package (v0.3.0) 于 2020 年 2 月 20 日创建

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-04-09
        • 1970-01-01
        • 2016-02-28
        • 1970-01-01
        • 1970-01-01
        • 2020-03-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多