R

时间:2015-05-31 23:24:01

标签: r

以下是哈德利高级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())在x(用作环境的数据框架)中包含了条件调用,由parent.frame()括起来。只要x中有一个名为a的列,为什么会有任何问题?

3 个答案:

答案 0 :(得分:15)

TL;博士

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

该对象是一个promise对象,其表达式槽是 a >= 4,其评估环境为.GlobalEnv。除非 名为a的对象位于.GlobalEnv或搜索的后面 路径,承诺的评估随着观察到的消息而失败 那:Error in eval(expr, envir, enclos) : object 'a' not found

详细说明

发现这里出错的一个好方法是插入一个 browser()subset2()行之前调用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_calla >= 4分配给“调用”对象 包含未评估的电话eval(expr, envir, enclos)。此调用将传入 call,需要作为第一个参数a 评估为类nameexpression或对象的对象的符号 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()

expr=condition_call现在开始工作,搜索任何符号的值 envir=x首先包含在enclos=parent.frame()中,然后(如果 需要)a及其封闭的环境。在 在这种情况下,它会在envir=x中找到符号>=(以及符号package:baseBrowse[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()

从subscramble()

中调用subset2()

subset2()的正文中,subset2(x, condition)被称为: subset2(x=x, condition=condition)。充实,这个电话真的是等同的 到condition。因为提供 参数(即传递给名为的形式参数的值 condition)是表达式condition_call <- substitute(condition)condition_callcondition分配给符号对象eval()。 (理解这一点对于准确理解嵌套调用如何失败非常关键。)

由于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=xsample_df中没有列(data.frame enclos=parent.frame()) 匹配,所以它转移到subscramble()公平 复杂的原因,那个环境最终成了评价 调用condition的框架。在那里,确实找到 一个名为Browse[1]> ls(x) ## [1] "a" "b" "c" Browse[1]> ls(parent.frame()) ## Aha! Here's an object named "condition" ## [1] "condition" "x" 的对象。

condition

重要的是,事实证明,在调用browser()的环境之上的调用堆栈上有几个名为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" 的对象。

condition

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

该检查显示a >= 4是一个承诺,其表达式为.GlobalEnv并且其中 环境是a。到目前为止,我们对sample_df的搜索进展顺利 过去a(其中找到a的值),所以评估 表达式槽失败(除非在.GlobalEnvBrowse[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 中找到名为condition的对象 在搜索路径更远的地方。)

enclos=parent.frame()

还有一个证据表明找到了承诺对象enclosparent.frame()中,可以将condition_call指向其他地方更远的地方 搜索路径,以便在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 再次失败,但这一次发出了消息 {{1}}本身未找到。

{{1}}

答案 1 :(得分:6)

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

  

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

这意味着,当您在嵌套的subset2函数中评估condition时,substitute会将condition_call设置为未评估条件的承诺对象&#39;论点。由于promise对象非常模糊,因此定义如下:http://cran.r-project.org/doc/manuals/r-release/R-lang.html#Promise-objects

关键点是:

  

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

  

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

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

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

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

您可以看到substitute(condition)没有返回a >= 4,只是condition,并且试图在sample_df的环境中评估它会失败,就像在Hadley& #39; s例子。

希望这很有帮助,我相信其他人可以进一步澄清。

答案 2 :(得分:1)

如果其他人偶然发现这个帖子,这里是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的帮助部分中:&#34; substitute返回(未评估的)表达式expr的解析树,替换env中绑定的任何变量。&#34 ; env&#34;默认为当前评估环境&#34;。相反,我们使用调用环境。

像这样检查出来:

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

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

  

通过检查解析树的每个组件来进行替换   如下:(...)如果它是一个承诺对象,即一个正式的论点   一个函数或使用delayedAssign()显式创建的   promise的表达式槽替换了符号。如果是的话   普通变量,其值被替换为(...)。

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