在 mgcv::gam() 包装器中传递偏移量参数

Pass offset argument in mgcv::gam() wrapper

我围绕 mgcv::gam() 编写了一个包装函数来直接将模型写入磁盘并做一些额外方便的事情。到目前为止,一切都很好,每个论点都被传递并且有效。除了,当我添加要传递给 mgcv::gam(offset = ) 的偏移量参数时。下面是一些示例代码。

require(mgcv)

gam1 = function(form,
                data,
                family,
                knots = NULL) {
  gam(formula = form,
      knots = knots,
      data = data,
      family = family)
}

gam1(Sepal.Length ~ s(Sepal.Width),
     data = iris,
     family = 'gaussian')

参数传递给 gam()(例如结、家庭)。作品。但是,如果我将 offset_ 添加到聚会中,它不会传递但会引发错误:

gam2 = function(form,
                data,
                family,
                knots = NULL,
                offset_ = NULL) {
  gam(formula = form,
      data = data,
      family = family,
      offset = offset_)
}

gam2(Sepal.Length ~ s(Sepal.Width),
     data = iris,
     family = 'gaussian',
     offset_ = NULL)

offset_ 未传递并抛出此错误: Error in eval(extras, data, env) : object 'offset_' not found。如果我改用 offset,它会抛出这个错误:invalid type (closure) for variable '(offset)'.

问:为什么我的包装器无法通过 offset_?我怎样才能做到运行?

错误发生在下面源代码片段的最后一行。但是我不太明白为什么。

mf$drop.unused.levels <- drop.unused.levels
mf[[1]] <- quote(stats::model.frame) ## as.name("model.frame")
pmf <- mf
mf <- eval(mf, parent.frame()) # the model frame now contains all the data 

问题是范围界定。 gam() 首先在 data 参数中查找 formulaoffset 中的变量,然后在附加到 formula 的环境中查找。通常那是创建 formula 的环境;在您的示例中,这将是全球环境。

您应该可以通过将 offset_ 变量添加到 data 的本地副本来让事情正常进行,例如

gam3 <- function(form,
                data,
                family,
                knots = NULL,
                offset_ = NULL) {
  if (is.null(offset_)) {
    gam(formula = form,
      data = data,
      family = family,
      offset = NULL)
  } else {
    data$offset_copy <- offset_
    gam(formula = form,
        data = data,
        family = family,
        offset = offset_copy)
  }
}

如果 data 有一个名为 offset_copy 的列,这将覆盖它,所以一定要使用 data.[=28= 中没有的名称]

编辑添加:@GavinSimpson 建议修改他的答案中的公式,以避免 predict() 出现问题。我建议进行与他使用的不同的修改:不对公式和偏移量进行解析,而是直接修改公式。例如,

fun <- function(form, data, offset_ = NULL, ...) {
    ## capture what was passed to offset_, unevaluated
    off <- substitute(offset_)
    if (!is.null(offset_)) { # need to add offset
        form[[3]] <- call("+", form[[3]], call("offset", off))
    }
    ## fit and return model
    gam(form, data = data, ...)
}

函数 call() 创建一个调用,因此 form[[3]] 行将公式的 RHS 替换为对旧 RHS 的 "+" 的未评估调用,以及对 offset() 包含偏移量。

这样做而不是解析的好处是它应该正确处理异常情况,例如可能会分解为多行的极长公式或偏移量,或环境很重要的公式,因为此版本保持环境不变。

@user2554330 已经解释了为什么以及如何修复它。我想完全提出一种替代方法。

一般来说,在公式中包含偏移量会更好。如果你不这样做,那么它会在 predict().

之类的事情中被忽略

如果 offset_ 不是 NULL,我建议您将偏移量添加到公式中。像这样的东西有效:

fun <- function(form, data, offset_ = NULL, ...) {
    ## capture what use passed to offset_, unevaluated
    off <- deparse(substitute(offset_))
    new_form <- form # copy as we may be modifying formula
    if (!is.null(offset_)) { # need to add offset
        form <- as.character(form) # coerce to character
        ## paste some bits of the formula back together
        new_form <- paste(form[2], form[1], form[3])
        ## add the offset
        off_form <- paste0("offset(", off, ")")
        new_form <- paste(new_form, off_form, sep = " + ")
        ## coerce to formula
        new_form <- as.formula(new_form)
    }
    ## fit and return model
    gam(new_form, data = data, ...)
}

正在使用中

N <- 50
effort <- rep(5, N)
df <- data.frame(y = runif(N), x = runif(N), z = runif(N))
fun(y ~ s(x) + s(z), offset_ = log(effort), data = df)

哪个returns:

> 
Family: gaussian 
Link function: identity 

Formula:
y ~ s(x) + s(z) + offset(log(effort))

Estimated degrees of freedom:
1 1  total = 3 

GCV score: 0.08046669```