估算命令如何在R中的公式中找到变量名?

时间:2019-06-21 13:27:22

标签: r nls

我想在用户定义的函数上使用R的nls()函数来估计大量模型。由于许多变量在我的规范中是固定的,因此我想在函数中进行预设置,但是我无法正确理解R如何在公式所包含的函数中寻找变量。

我看过哈德利·威克姆(Hadley Wickham)的高级R书中有关元编程的部分,但是并没有启发我。这是一个使用mtcars数据集的简化示例:

我尝试为在各个规范中固定的变量设置默认值:

expo <- function(x, theta, weight = wt) {
  x*weight^theta
}

我也尝试过仅使用固定变量的列名作为函数内部的变量

expo <- function(x, theta) {
  x*wt^theta
}

如果我只是想计算函数,这两种方法都可以使用

attach(mtcars)
expo(qsec, 1)
detach()

例如,如果我尝试在估算例程中使用expo()函数

nls(mpg ~ phi + expo(qsec, theta),
    data = mtcars,
    start = c('phi' = -2, 'theta' = 1))

失败,并显示消息Error in expo(qsec, theta) : object 'wt' not found。注释中提到的一种可能性是,将数据集mtcars传递给expo()作为参数。但是,由于我只会在对expo()的调用中调用nls(),其中数据集已经是一个参数,如果能找到避免这种重复的方法,我会很高兴。

在正确定义或调用expo()之后,我的最终目标是能够执行以下操作:

depvars <- c('qsec', 'drat', 'dist')
lapply <- (depvars, function(x) {
    formula <- as.formula(paste0('mpg ~ phi + expo(', x, ', theta)'))
    nls(formula,
        data = mtcars,
        start = c('phi' = -2, 'theta' = 1))
}

2 个答案:

答案 0 :(得分:2)

棘手的事情是R的词法作用域在封闭环境中进行搜索, 这在通话过程中可能会造成混乱,因为呼叫者环境每个都有封闭的环境,事情很快就会变得混乱。

我将使用rlang包调试此方案。

首先,如果您在全局环境中定义了expo, 那就是它的封闭环境:

expo <- function(x, theta) {
  x*wt^theta
}

rlang::get_env(expo)
# <environment: R_GlobalEnv>

因此,当您调用它时,R将首先在函数调用中搜索变量 (不是来电者!) 环境, 然后在封闭环境(此处为全局环境)中。

我不知道nls到底是做什么的, 但是我会假设它会根据您提供的data创建一个环境,并在那里计算公式。 但是,似乎它创建的环境仅包含可以在公式中明确看到的变量, 我发现的东西:

expo <- function(x, theta) {
  cat("caller: ")
  print(ls(rlang::caller_env()))
  cat("enclosing: ")
  print(ls(rlang::env_parent(rlang::current_env())))
}

nls(mpg ~ phi + expo(qsec, theta),
    data = mtcars,
    start = c('phi' = -2, 'theta' = 1))
# caller: [1] "mpg"   "phi"   "qsec"  "theta"
# enclosing: [1] "expo"    
# Error ...

我们可以看到,expo caller 环境包含我们可以在公式中识别的变量, 并且其封闭环境仅包含expo的定义 (全球环境)。 不幸的是,这意味着您甚至无法在eval.parent内使用expo之类的东西, 因为该环境不会包含data中的所有变量。

如果您仍然想解决它, 您可以在调用expo之前用数据修改nls的封闭环境, 像这样:

expo <- function(x, theta) {
  x*wt^theta
}

environment(expo) <- list2env(as.list(mtcars))

nls(mpg ~ phi + expo(qsec, theta),
    data = mtcars,
    start = c('phi' = -2, 'theta' = 1))
# Error ... number of iterations exceeded maximum of 50

答案 1 :(得分:2)

我接受了Alexis的回答,因为它回答了我的原始问题。不过,我想我会分享我采用的解决方案,以防万一有人觉得有用。

正如Alexis所说,该解决方案需要涉及修改expo()的封闭环境。我的方法不是每次都手动执行此操作(可能在每次调用expo()之后将其更改回原始环境),而是结合了NelsonGon的建议,即expo()的环境包含正确的变量的要求我在某个时候将数据集作为参数输入。为此,我创建了一个函数工厂make_expo(),该函数工厂设置了必需的变量并返回了expo(),以便变量自动位于expo()的封闭环境中:

make_expo <- function(df, vars = c('wt')) {
  wt <- df[[vars[1]]]
  function(x, theta) {
    x + wt^theta
  }
}

expo <- make_expo(mtcars)

nls(mpg ~ phi + expo(qsec, theta),
    data = mtcars,
    start = c('phi' = 1, theta = 1))
# Error ... number of iterations exceeded maximum of 50

我认为这有两个优点。首先,它更加健壮,因为您无需记住设置expo()的环境,因此在定义expo()时会自动进行设置。不过,make_expo()是灵活的-我可以设置默认值,也可以输入不同的数据集。其次,它使参数expo()的要求降低到我实际上期望在对expo()的不同调用中会有所不同的参数,从而提高了理解力

令我惊讶的是,公式创建了一个环境,可以在其中查找名称,该名称仅包含在公式中显式命名的变量,而不包含传递给nls()的数据集中的其他变量,但是您去了