计算并绘制广义非线性模型的95%置信区间。

5
我已经使用R包nlme和其中的gnls()函数构建了几个广义非线性最小二乘模型(指数衰减)。我之所以不仅使用基本的nls()函数构建非线性最小二乘模型,是因为我想能够对异方差进行建模以避免转换。我的模型大致如下:
model <- gnls(Response ~ C * exp(k * Explanatory1) + A,
              start = list(C = c(C1,C1), k = c(k1,k1), A = c(A1,A1)),
              params = list(C ~ Explanatory2, k ~ Explanatory2, 
                            A ~ Explanatory2),
              weights = varPower(), 
              data = Data)

与简单的 nls() 模型相比,关键区别在于 weights 参数,它通过解释变量使异方差建模成为可能。与 gnls() 的线性等效物是广义最小二乘法,使用 nlmegls() 函数运行。
现在我想在 R 中计算置信区间,并将其与我的模型拟合一起绘制在 ggplot()ggplot2 包)中。对于 gls() 对象,我会这样做:
NewData <- data.frame(Explanatory1 = c(...), Explanatory2 = c(...)) 
NewData$fit <- predict(model, newdata = NewData)

到目前为止,一切都很顺利,我得到了我的模型适配。

modmat <-  model.matrix(formula(model)[-2], NewData)
int <- diag(modmat %*% vcov(model) %*% t(modmat))
NewData$lo <- with(NewData, fit - 1.96*sqrt(int))
NewData$hi <- with(NewData, fit + 1.96*sqrt(int))

这部分无法使用gnls()工作,因此我无法获得上限和下限的模型预测。
由于这似乎不适用于gnls()对象,我已经查阅了教科书以及之前提出的问题,但没有一个符合我的需求。我找到的唯一类似的问题是如何在r中计算非线性最小二乘的置信区间?。在顶部答案中,建议使用investr::predFit()或使用drc::drm()构建模型,然后使用常规的predict()函数。但这些解决方案都不能帮助我处理gnls()
我的当前最佳解决方案是使用confint()函数计算三个参数(C、k、A)的95%置信区间,然后编写两个分别使用Cmin、kmin和Amin以及Cmax、kmax和Amax的上限和下限置信度的函数。然后我使用这些函数来预测值,然后使用ggplot()绘制图形。但是,我对结果并不完全满意,也不确定这种方法是否最优。

以下是一个最小可重现示例,为简单起见忽略了第二个分类解释变量:

# generate data
set.seed(10)
x <-  rep(1:100,2)
r <- rnorm(x, mean = 10, sd = sqrt(x^-1.3))
y <- exp(-0.05*x) + r
df <-  data.frame(x = x, y = y)

# find starting values
m <- nls(y ~ SSasymp(x, A, C, logk))
summary(m) # A = 9.98071, C = 10.85413, logk = -3.14108
plot(m) # clear heteroskedasticity

# fit generalised nonlinear least squares
require(nlme)
mgnls <- gnls(y ~ C * exp(k * x) + A, 
              start = list(C = 10.85413, k = -exp(-3.14108), A = 9.98071),
              weights = varExp(),
              data = df)
plot(mgnls) # more homogenous

# plot predicted values 
df$fit <- predict(mgnls)
require(ggplot2)
ggplot(df) +
  geom_point(aes(x, y)) +
  geom_line(aes(x, fit)) +
  theme_minimal()

编辑以下Ben Bolker的答案

标准的非参数自助法解决方案应用于第二个模拟数据集,该数据集更接近我的原始数据,并包括第二个分类解释变量:

# generate data
set.seed(2)
x <- rep(sample(1:100, 9), 12)
set.seed(15)
r <- rnorm(x, mean = 0, sd = 200*x^-0.8)
y <- c(200, 300) * exp(c(-0.08, -0.05)*x) + c(120, 100) + r
df <-  data.frame(x = x, y = y, 
                  group = rep(letters[1:2], length.out = length(x)))

# find starting values
m <- nls(y ~ SSasymp(x, A, C, logk))
summary(m) # A = 108.9860, C = 356.6851, k = -2.9356
plot(m) # clear heteroskedasticity

# fit generalised nonlinear least squares
require(nlme)
mgnls <- gnls(y ~ C * exp(k * x) + A, 
              start = list(C = c(356.6851,356.6851), 
                           k = c(-exp(-2.9356),-exp(-2.9356)), 
                           A = c(108.9860,108.9860)),
              params = list(C ~ group, k ~ group, A ~ group),
              weights = varExp(),
              data = df)
plot(mgnls) # more homogenous

# calculate predicted values 
new <- data.frame(x = c(1:100, 1:100),
                  group = rep(letters[1:2], each = 100))
new$fit <- predict(mgnls, newdata = new)

# calculate bootstrap confidence intervals
bootfun <- function(newdata) {
  start <- coef(mgnls)
  dfboot <- df[sample(nrow(df), size = nrow(df), replace = TRUE),]
  bootfit <- try(update(mgnls,
                        start = start,
                        data = dfboot),
                 silent = TRUE)
  if(inherits(bootfit, "try-error")) return(rep(NA, nrow(newdata)))
  predict(bootfit, newdata)
}

set.seed(10)
bmat <- replicate(500, bootfun(new))
new$lwr <- apply(bmat, 1, quantile, 0.025, na.rm = TRUE)
new$upr <- apply(bmat, 1, quantile, 0.975, na.rm = TRUE)

# plot data and predictions
require(ggplot2)
ggplot() +
  geom_point(data = df, aes(x, y, colour = group)) +
  geom_ribbon(data = new, aes(x = x, ymin = lwr, ymax = upr, fill = group), 
              alpha = 0.3) +
  geom_line(data = new, aes(x, fit, colour = group)) +
  theme_minimal()

enter image description here

这是生成的图表,看起来很整洁!


在这种情况下,引导法或增量法都是合理的方法。这两种方法都在此处进行了说明(https://dev59.com/u8Dqa4cB1Zd3GeqPUwtA#66809664),用于不同的非线性模型...如果您想发布一个[mcve],我可以尝试展示如何应用到您的问题上。 - Ben Bolker
@BenBolker 感谢您的有益回复。我已添加所需的最小可重现示例,并非常感谢您能说明您建议的解决方案。 - Luka Seamus Wright
看起来很棒。然而,在“#计算预测值”之后的代码中,我遇到了一个错误。我的系统显示“'contrasts.arg'参数必须被命名”。 - TimothyEbert
1个回答

6

我实现了一种引导方案。最开始,我采用标准的非参数引导方法来重新抽样 观察值 ,但是这会产生看起来可疑宽度的95%置信区间——我 认为 这是因为这种形式的引导未能保持x分布的平衡(例如通过重新抽样,您可能得不到小x值的观测数据)。 (也有可能是我的代码有漏洞。)

第二次尝试时,我切换到了重新抽样初始拟合的 残差 并将其加到预测值中;这是一个相当标准的方法,例如在引导时间序列中使用(尽管我忽略了残差自相关的可能性,这需要 块引导)。

这是基本的引导重采样器。

df$res <- df$y-df$fit
bootfun <- function(newdata=df, perturb=0, boot_res=FALSE) {
    start <- coef(mgnls)
    ## if we start exactly from the previously fitted coefficients we end
    ## up getting all-identical answers? Not sure what's going on here, but
    ## we can fix it by perturbing the starting conditions slightly
    if (perturb>0) {
        start <- start * runif(length(start), 1-perturb, 1+perturb)
    }
    if (!boot_res) {
        ## bootstrap raw data
        dfboot <- df[sample(nrow(df),size=nrow(df), replace=TRUE),]
    } else {
        ## bootstrap residuals
        dfboot <- transform(df,
                            y=fit+sample(res, size=nrow(df), replace=TRUE))
    }
    bootfit <- try(update(mgnls,
                      start = start,
                      data=dfboot),
                   silent=TRUE)
    if (inherits(bootfit, "try-error")) return(rep(NA,nrow(newdata)))
    predict(bootfit,newdata=newdata)
}

set.seed(101)
bmat <- replicate(500,bootfun(perturb=0.1,boot_res=TRUE))   ## resample residuals
bmat2 <- replicate(500,bootfun(perturb=0.1,boot_res=FALSE)) ## resample observations
## construct envelopes (pointwise percentile bootstrap CIs)
df$lwr <- apply(bmat, 1, quantile, 0.025, na.rm=TRUE)
df$upr <- apply(bmat, 1, quantile, 0.975, na.rm=TRUE)
df$lwr2 <- apply(bmat2, 1, quantile, 0.025, na.rm=TRUE)
df$upr2 <- apply(bmat2, 1, quantile, 0.975, na.rm=TRUE)

现在开始画图:

ggplot(df, aes(x,y)) +
    geom_point() +
    geom_ribbon(aes(ymin=lwr, ymax=upr), colour=NA, alpha=0.3) +
    geom_ribbon(aes(ymin=lwr2, ymax=upr2), fill="red", colour=NA, alpha=0.3) +
    geom_line(aes(y=fit)) +
    theme_minimal()

粉红色/浅红色区域是观测水平的自助法置信区间(可疑);灰色区域是残差自助法置信区间。

带有自助法置信区间曲线

尝试使用 Delta 方法也不错,但是 (1) 它比自助法做出了更强的假设和逼近,而且 (2) 我时间不够了。


感谢您花时间说明您的解决方案!您遇到的问题可能与我最初模拟的数据有关,因为在新生成的数据集上,您的解决方案在不扰动或残留重采样的情况下运行良好(请参见我的编辑)。我认为您的残差方法在新生成的数据上也不会起作用?当我将标准的非参数自助法应用于我的数据时,我注意到一个置信区间中只有一个组中存在凸起,尽管两个组的数据分布相似(请参见部分图)。这是正常现象还是我做错了什么? - Luka Seamus Wright

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接