【问题标题】:Pass offset argument in mgcv::gam() wrapper在 mgcv::gam() 包装器中传递偏移量参数
【发布时间】:2021-04-13 09:27:54
【问题描述】:

我在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_?我怎样才能让它运行?

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

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 

【问题讨论】:

    标签: r scope gam mgcv


    【解决方案1】:

    问题在于范围界定。 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 中不存在的名称。

    编辑添加:@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() 的调用并包含偏移量。

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

    【讨论】:

    • 在您的代码示例中,df 应该是倒数第二行的data。 @GavinSimpson 的回答也一样。编辑队列已满。
    • 是的,这比进行公式操作更简洁
    【解决方案2】:

    @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)
    

    返回:

    > 
    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```
    

    【讨论】:

    • 感谢补充,公式中也可以设置偏移量。我现在直接将它添加到公式中,完全省略参数offset_
    • 我很确定这是一个不同的公式。这添加了一个线性预测器log(effort),当您想要(对于此链接)将y 替换为y - log(effort) 时。对于其他链接,它更复杂。但是,包括 offset(log(effort)) 应该做你想做的事。
    • 我也建议你不要去解析和重新解析。通常这是可行的,但在一些奇怪的情况下它会失败,例如如果原始公式从其环境而不是数据中获取了一些变量,或者如果公式或偏移量是一个非常长的表达式,并且被分解为几行。我将为此添加演示代码。
    • @user2554330 不正确的公式只是我很傻 - 我会更新以将其包装在 offset( ) 中。我认为重新分析,问题是将更多的东西分成多行 - 上面的代码即使在全局环境中也能找到变量。使用offset 参数路径的问题在于,在拟合后对模型进行操作时需要更加小心。然而,这对 OP 来说可能不是问题。我正在考虑通过 tidyeval 工具来做到这一点,因为我认为这些工具在拆分长表达式时没有同样的问题。
    猜你喜欢
    • 2016-01-07
    • 1970-01-01
    • 2020-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-24
    相关资源
    最近更新 更多