首页 文章

R中另一个函数的非标准评估

提问于
浏览
18

以下是哈德利高级R书中的一个例子:

sample_df <- data.frame(a = 1:5, b = 5:1, c = c(5, 3, 1, 4, 1))

subset2 <- function(x, condition) {
  condition_call <- substitute(condition)
  r <- eval(condition_call, x, parent.frame())
  x[r, ]
}

scramble <- function(x) x[sample(nrow(x)), ]

subscramble <- function(x, condition) {
  scramble(subset2(x, condition))
}

subscramble(sample_df, a >= 4)
# Error in eval(expr, envir, enclos) : object 'a' not found

哈德利解释说:

你能看出问题所在吗? condition_call包含表达式条件 . 因此,当我们评估condition_call时,它还会计算条件,其值为a> = 4.但是,由于在父环境中没有名为a的对象,因此无法计算 .

据我所知,父env中没有 a ,但是 eval(condition_call, x, parent.frame()) evals在x中的conditional_call(用作环境的data.frame),由 parent.frame() 括起来 . 只要在x中有一个名为 a 的列,为什么会出现任何问题?

3 回答

  • 1

    tl;博士

    当从 subscramble() 中调用 subset2() 时, condition_call 的值是符号 condition (而不是直接调用时产生的调用 a >= 4 ) . subset()eval() 的调用首先在 envir=x (data.frame sample_df )中搜索 condition . 在那里找不到它,它接下来在 enclos=parent.frame() 中搜索它找到一个名为 condition 的对象 .

    该对象是一个promise对象,其表达式槽位为 a >= 4 ,其评估环境为 .GlobalEnv . 除非在 .GlobalEnv 中找到名为 a 的对象或在搜索路径的后面,否则对promise的评估将失败,并显示以下消息: Error in eval(expr, envir, enclos) : object 'a' not found .


    详细说明

    发现错误的一个很好的方法是在 subset2() 失败的行之前插入一个 browser() 调用 . 这样,我们可以直接和间接地(从另一个函数中)调用它,并检查它在第一种情况下成功的原因并在第二种情况下失败 .

    subset2 <- function(x, condition) {
      condition_call <- substitute(condition)
      browser()
      r <- eval(condition_call, x, parent.frame())  ## <- Point of failure
      x[r, ]
    }
    

    直接调用subset2()

    当用户直接调用 subset2() 时, condition_call <- substitute(condition)condition_call 分配给包含未评估调用 a >= 4 的"call"对象 . 此调用传递给 eval(expr, envir, enclos) ,它需要作为其第一个参数的符号,该符号计算为类 callnameexpression 的对象 . 到现在为止还挺好 .

    subset2(sample_df, a >= 4)
    ## Called from: subset2(sample_df, a >= 4)
    Browse[1]> is(condition_call)
    ## [1] "call"     "language"
    Browse[1]> condition_call
    ## a >= 4
    

    eval() 现在设置为工作,首先在 envir=x 中搜索 expr=condition_call 中包含的任何符号的值,然后在 enclos=parent.frame() 及其封闭环境中搜索(如果需要) . 在这种情况下,它在 envir=x (和 package:base 中的符号 >= )中找到符号 a 并成功完成评估 .

    Browse[1]> ls(x)
    ## [1] "a" "b" "c"
    Browse[1]> get("a", x)
    ## [1] 1 2 3 4 5
    Browse[1]> eval(condition_call, x, parent.frame())
    ## [1] FALSE FALSE FALSE  TRUE  TRUE
    

    从subscramble()内调用subset2()

    subscramble() 的主体内, subset2() 被称为: subset2(x, condition) . 充实,这个召唤真的相当于 subset2(x=x, condition=condition) . 因为它的 supplied 参数(即传递给名为 conditionformal 参数的值)是表达式 conditioncondition_call <- substitute(condition)condition_call 分配给符号对象 condition . (理解这一点对于准确理解嵌套调用如何失败非常关键 . )

    因为 eval() 很高兴有一个符号(又名"name")作为它的第一个参数,所以再一次到目前为止一直很好 .

    subscramble(sample_df, a >= 4)
    ## Called from: subset2(x, condition)
    Browse[1]> is(condition_call)
    ## [1] "name"      "language"  "refObject"
    Browse[1]> condition_call
    ## condition
    

    现在 eval() 开始寻找未解决的符号 condition . envir=x 中没有列(data.frame sample_df )匹配,因此它会移至 enclos=parent.frame() 由于相当复杂的原因,该环境最终成为对 subscramble() 的调用的评估框架 . 在那里, does 找到一个名为 condition 的对象 .

    Browse[1]> ls(x)
    ## [1] "a" "b" "c"
    Browse[1]> ls(parent.frame()) ## Aha! Here's an object named "condition"
    ## [1] "condition" "x"
    

    重要的是,事实证明,在调用 browser() 的环境上方的调用堆栈上有几个名为 condition 的对象 .

    Browse[1]> sys.calls()
    # [[1]]
    # subscramble(sample_df, a >= 4)
    # 
    # [[2]]
    # scramble(subset2(x, condition))
    # 
    # [[3]]
    # subset2(x, condition)               
    # 
    Browse[1]> sys.frames()
    # [[1]]
    # <environment: 0x0000000007166f28>   ## <- Envt in which `condition` is evaluated
    # 
    # [[2]]
    # <environment: 0x0000000007167078>
    # 
    # [[3]]
    # <environment: 0x0000000007166348>   ## <- Current environment
    
    
    ## Orient ourselves a bit more
    Browse[1]> environment()            
    # <environment: 0x0000000007166348>
    Browse[1]> parent.frame()           
    # <environment: 0x0000000007166f28>
    
    ## Both environments contain objects named 'condition'
    Browse[1]> ls(environment())
    # [1] "condition"      "condition_call" "x"             
    Browse[1]> ls(parent.frame())
    # [1] "condition" "x"
    

    要检查 eval() 找到的 condition 对象( parent.frame() 中的对象,后来证明是 subscramble() 的评估框架)需要特别注意 . 我使用了 recover()pryr::promise_info() ,如下所示 .

    该检查显示 condition 是一个承诺,其表达槽为 a >= 4 ,其环境为 .GlobalEnv . 到目前为止,我们对 a 的搜索移动了 sample_df (其中找到了 a 的值),因此对表达式槽的评估失败(除非在 .GlobalEnv 中找到名为 a 的对象或在搜索路径的其他位置) .

    Browse[1]> library(pryr) ## For is_promise() and promise_info()  
    Browse[1]> recover()
    # 
    # Enter a frame number, or 0 to exit   
    # 
    # 1: subscramble(sample_df, a >= 4)
    # 2: #2: scramble(subset2(x, condition))
    # 3: #1: subset2(x, condition)
    # 
    Selection: 1
    # Called from: top level 
    Browse[3]> is_promise(condition)
    # [1] TRUE
    Browse[3]> promise_info(condition)
    # $code
    # a >= 4
    # 
    # $env
    # <environment: R_GlobalEnv>
    # 
    # $evaled
    # [1] FALSE
    # 
    # $value
    # NULL
    # 
    Browse[3]> get("a", .GlobalEnv)
    # Error in get("a", .GlobalEnv) : object 'a' not found
    

    对于在 enclos=parent.frame() 中找到promise对象 condition 的另一个证据,可以在搜索路径的更远处指向 enclos ,以便在 condition_call 的评估期间跳过 parent.frame() . 当一个人这样做时, subscramble() 再次失败,但这次是发现 condition 本身未找到的消息 .

    ## Compare
    Browse[1]> eval(condition_call, x, parent.frame())
    # Error in eval(expr, envir, enclos) (from #4) : object 'a' not found
    
    Browse[1]> eval(condition_call, x, .GlobalEnv)
    # Error in eval(expr, envir, enclos) (from #4) : object 'condition' not found
    
  • 6

    这是一个棘手的问题,所以感谢这个问题 . 该错误与替换在参数调用时的行为方式有关 . 如果我们查看来自substitute()的帮助文本:

    通过检查解析树的每个组件进行替换,如下所示:如果它不是env中的绑定符号,则不变 . 如果它是一个promise对象,即函数的形式参数或使用delayedAssign()显式创建的,则promise的表达式槽替换该符号 .

    这意味着当你在嵌套的subset2函数中评估 condition 时, substitutecondition_call 设置为未评估的'condition'参数的promise对象 . 由于promise对象非常模糊,定义如下:http://cran.r-project.org/doc/manuals/r-release/R-lang.html#Promise-objects

    关键点是:

    Promise对象是R的懒惰评估机制的一部分 . 它们包含三个槽:值,表达式和环境 .

    访问参数时,将在存储的环境中计算存储的表达式,并返回结果

    基本上,在嵌套函数中, condition_call 设置为promise对象 condition ,而不是替换 condition 中包含的实际表达式 . 因为promise对象来自它们,所以它似乎会覆盖 eval() 的行为 - 所以无论eval()的第二个参数如何, condition_call 都是在父环境中评估参数的传递,其中没有'a' .

    您可以使用 delayedAssign() 创建promise对象并直接观察:

    delayedAssign("condition", a >= 4)
    substitute(condition)
    eval(substitute(condition), sample_df)
    

    您可以看到 substitute(condition) 不会返回 a >= 4 ,而只是 condition ,并且尝试在 sample_df 环境中对其进行评估会失败,就像在Hadley的示例中一样 .

    希望这很有用,我相信其他人可以进一步澄清 .

  • 15

    如果其他人偶然发现这个帖子,这里是Hadley的书中本节下面的任务#5的答案 . 它还包含对上述问题的可能的一般解决方案 .

    subset2 <- function(x, condition, env = parent.frame()) {
      condition_call <- substitute(condition, env)
      r <- eval(condition_call, x, env)
      x[r, ]
    }
    scramble <- function(x) x[sample(nrow(x)), ]
    subscramble <- function(x, condition) {
      scramble(subset2(x, condition))
    }
    subscramble(sample_df, a >= 3)
    

    神奇发生在 subset2 的第二行 . 在那里, substitute 收到一个明确的 env 参数 . 从 substitute 的帮助部分:“ substitute 返回(未评估的)表达式 expr 的解析树,替换 env 中绑定的任何变量 . ” env "Defaults to the current evaluation environment" . 相反,我们使用调用环境 .

    看看这样:

    debugonce(subset2)
    subscramble(sample_df, a >= 3)
    Browse[2]> substitute(condition)
    condition
    Browse[2]> substitute(condition, env)
    a >= 3
    

    我不是100%肯定这里的解释 . 我认为这只是 substitute 的工作方式 . 在 substitute 的帮助页面中:

    通过检查解析树的每个组件进行替换,如下所示:(...)如果它是一个promise对象,即函数的形式参数或使用delayedAssign()显式创建的,则promise的表达式槽替换符号 . 如果它是普通变量,则其值被替换(...) .

    在当前环境中, condition 是一个promise,因此表达式槽被填充,更重要的是,condition_call接收一个符号作为值 . 在调用环境中, condition 只是一个普通变量,因此值(表达式)被替换 .

相关问题