【问题标题】:Why does this simple function calling `lm(..., subset)` fail?为什么这个调用 lm(..., subset) 的简单函数会失败?
【发布时间】:2016-05-19 14:48:56
【问题描述】:

我正在处理一个包含对lm() 的调用的自定义函数,但由于某种原因该函数失败了。我无法理解它失败的原因。

考虑这个简单的例子:

myfun <- function(form., data., subs., ...){
    lm(form., data., subs., ...)
}

这将导致错误:

myfun(mpg ~ cyl + hp, mtcars, TRUE)
## Error in eval(expr, envir, enclos) : object 'subs.' not found

但是直接使用lm() 就可以了:

lm(mpg ~ cyl + hp, mtcars, TRUE)
## 
## Call:
## lm(formula = mpg ~ cyl + hp, data = mtcars, subset = TRUE)
## 
## Coefficients:
## (Intercept)          cyl           hp  
##    36.90833     -2.26469     -0.01912  

我尝试了调试,但仍然无法找到问题的根源。为什么自定义函数会失败?显然subs. 提供给函数...


编辑:

虽然下面建议的大多数解决方案都有助于这种简单的情况,但如果我添加一个简单的扭曲,该功能仍然会失败。例如expand.model.frame() 依赖于公式的环境,但如果我使用正常的评估解决方案会失败:

myfun <- function(form., data., subs., ...){
    fit <- lm(form., data.[ subs., ], ...)
    expand.model.frame(fit, ~ drat)
}

myfun(mpg ~ cyl + hp, mtcars, TRUE)
## Error in eval(expr, envir, enclos) : object 'data.' not found

这显然与原始问题有关,但我不知道如何。模型公式的环境是否被破坏了?

【问题讨论】:

  • 一个简短的答案可能是不要单独列出 subset 参数。只需通过... 以命名方式传递所有内容。
  • @joran 是的,这可以做到,但我正在处理我的自定义乐趣中的许多不同功能,并且通过... 将其全部传递可能不是一个可靠的解决方案。我最终可能会将一些参数传递给错误的函数。我仍然不明白为什么问题中的功能会失败......
  • 这很复杂,我一大早(对我来说)正在努力解释清楚。基本上它归结为在lm中进行的大量特殊评估。
  • @joran 谁说过 R 不是古怪的……那么围绕这个“特殊评估”的犹太方式是什么?使用... 或执行match.call(),然后匹配所需的参数,后跟do.call()(基本上是在lm 中完成的工作流程)?
  • Roland 的第一个贡献是:“干净的解决方案是使用标准评估”。区别只是程度的问题。我不确定所有这些人可以用多少不同的方式来解释公式附加了环境,以及创建公式的位置会影响查找内容的位置,因此在顶层创建的公式将附加到全局环境。并且全球环境不知道myfun 的参数。

标签: r function


【解决方案1】:

正如 cmets 中所建议的,另一种解决方案是在非交互式使用中完全避免使用 subset 参数,而是使用标准评估:

myfun <- function(form., data., subs., ...){
    lm(form., data.[ subs., ], ...)
}

现在按预期工作:

myfun(formula(mpg ~ cyl + hp), mtcars, TRUE)

但是,如果您的自定义函数随后包含像 expand.model.frame() 或类似的调用,这仍然不够,这些调用本身似乎对 subset 参数的非标准评估很敏感。为了使函数稳健并避免意外,您需要 (1) 在自定义函数中定义公式(另请参见 reformulate approach)和 (2) 将数据之前设置为 @987654328 的子集@ 调用,同时明显避免使用 subset 参数。

像这样:

myfun <- function(form., data., subs., ...){
    stopifnot(is.character(form.))
    data. <- data.[ subs., ]
    fit <- lm(as.formula(form.), data., ...)
    expand.model.frame(fit, ~ drat)
}

myfun("mpg ~ cyl + hp", mtcars, TRUE)

我尝试使用 (1) 或 (2),但仍然设法在某些函数中遇到奇怪的错误,并且只有 (1) 和 (2) 的错误似乎已经消失...

【讨论】:

    【解决方案2】:

    这个函数不起作用的原因是因为参数 subset 的评估方式:

    所有的“权重”、“子集”和“偏移量”都在同一个地方进行评估 方式作为“公式”中的变量,即首先在“数据”中,然后在 “公式”的环境。

    换句话说,lmdata 中查找名为subs. 的变量,然后在formula 的环境中查找,由于在这两个环境中都没有subs. 变量,因此会产生错误.

    【讨论】:

    • 除了在函数定义中仅使用 ... 之外,解决此特殊评估的“正确”方法是什么?
    • 没有干净的解决方案。函数myfun 必须将subs. 添加到data 和/或公式环境中,但这将覆盖这些环境中已经存在的任何同名变量。
    • @landroni 查看我对另一个答案的新评论(和改变主意)。
    • 干净的解决方案是使用标准评估,即在非交互式使用中避免 lmsubset 参数。
    • @landroni 因为你还是写了包装函数:是的。
    【解决方案3】:

    你可以这样做:

    myfun <- function(form., data., subs., ...){
        lm(as.formula(form.), data., subs., ...)
    }
    

    将其称为myfun("mpg ~ cyl + hp", mtcars, T)。这会强制在函数 myfun 的环境中创建公式,然后该函数将包含 subs.

    【讨论】:

    • @joran 也许——也许不是。提议的代码确实解决了弹出的问题,但我同意它并不理想。
    • 我已经改变了对这个答案的看法。将公式作为字符传递,然后在调用lm 时强制它强制公式的环境成为lm 调用的封闭环境,其中将包含参数form.data.subs.,所以它会被正确评估。这对我来说似乎是一个合理的解决方案。
    • ...还请注意,所有这些实际上都发生在model.frame.default
    【解决方案4】:

    基于@ErnestA 的答案,您可以修改您的函数以确保subs. 存在于公式form. 的环境中:

    myfun <- function(form., data., subs., ...){
    assign("subs.", subs., envir=environment(form.))
    lm(form., data., subs., ...)
    }
    

    ETA 为避免污染form 的环境,您可以这样创建一个新环境:

    myfun <- function(form., data., subs., ...){
    environment(form.) <- new.env(parent=environment(form.))
    assign("subs.", subs., envir=environment(form.))
    lm(form., data., subs., ...)
    }
    

    ETA 可能单独解决 lm 问题的最巧妙方法是将 form. 的环境设置为 myfun 的环境:

    myfun <- function(form., data., subs., ...){
    environment(form.) <- environment()
    lm(form., data., subs., ...)
    }
    myfun(mpg ~ cyl + hp, mtcars, TRUE)
    ## Call:
    ##   lm(formula = form., data = data., subset = subs.)
    ## 
    ## Coefficients:
    ##   (Intercept)          cyl           hp  
    ##      36.90833     -2.26469     -0.01912  
    

    转到expand.model.frame 问题,虽然subs. 是在?expand.model.frame 所说的使用环境中,但没有找到subs.。这是 expand.model.frame 中的错误吗?或者至少与文档有冲突?

    myfun <- function(form., data., subs., ...){
    environment(form.) <- environment()
    fit <- lm(form., data., subs., ...)
    print(ls(environment(formula(fit))))
    expand.model.frame(fit, ~drat )
    }
    myfun(mpg ~ cyl + hp, mtcars, TRUE)
    ## [1] "data." "fit"   "form." "subs."
    ##  Error in eval(expr, envir, enclos) : object 'subs.' not found
    

    subs. 放入父环境似乎可行。

    myfun <- function(form., data., subs., ...){
    environment(form.) <- environment()
    fit <- lm(form., data., subs., ...)
    assign("subs.", subs., envir = parent.env(environment(formula(fit))))
    expand.model.frame(fit, ~drat)
    }
    myfun(mpg ~ cyl + hp, mtcars, TRUE)
    ## mpg cyl  hp drat
    ## Mazda RX4           21.0   6 110 3.90
    ## Mazda RX4 Wag       21.0   6 110 3.90
    ## Datsun 710          22.8   4  93 3.85
    ## Hornet 4 Drive      21.4   6 110 3.08
    ## etc.
    

    但这存在污染父环境的问题,在本例中为R_GlobalEnv。除了R_GlobalEnv 作为父级之外,我无法使其工作。

    【讨论】:

    • 我不确定我是否遵循。第一种情况可能会污染什么?
    • 在对他的回答@Ernest A 的评论中正确地说“myfun 函数必须添加 subs. 到数据和/或公式的环境中,但这将覆盖任何已具有相同名称的变量存在于这些环境中”。默认情况下,form. 的环境是创建它的环境; R_GlobalEnv 如果您对 myfun 的示例调用来自命令提示符。为了避免在该环境中创建/覆盖subs.,我将form. 的环境创建为一个新的空环境,我可以在其中创建subs.。这似乎无法解决您的expand.model.frame 问题:-(
    猜你喜欢
    • 1970-01-01
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    • 2017-08-13
    • 2017-08-06
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多