凹时在 R 中使用 NLS 的三段式分段回归的语法
Syntax for three-piece segmented regression using NLS in R when concave
我的目标是拟合一个三段式(即两个断点)回归模型以使用传播的 predictNLS
函数进行预测,确保将节点定义为参数,但我的模型公式似乎不正确.
我已经使用 segmented
包来估计断点位置(在 NLS 中用作起始值),但我想将我的模型保留为 NLS 格式,特别是 nlsLM {minipack.lm}
因为我正在使用 NLS 将其他类型的曲线拟合到我的数据中,希望允许 NLS 优化节点值,有时使用可变权重,并且需要能够从 propagate
轻松计算 Monte Carlo 置信区间].尽管我非常接近 具有正确的公式语法,但我在断点附近没有得到 expected/required 行为。这些段应该直接在断点处相遇(没有任何跳跃),但至少在这个数据上,我在断点处得到了一个奇怪的局部最小值(见下图)。
以下是我的数据和一般过程的示例。我认为我的问题出在 NLS 公式中。
library(minpack.lm)
library(segmented)
y <- c(-3.99448113, -3.82447011, -3.65447803, -3.48447030, -3.31447855, -3.14448753, -2.97447972, -2.80448401, -2.63448380, -2.46448069, -2.29448796, -2.12448912, -1.95448783, -1.78448797, -1.61448563, -1.44448719, -1.27448469, -1.10448651, -0.93448525, -0.76448637, -0.59448626, -0.42448586, -0.25448588, -0.08448548, 0.08551417, 0.25551393, 0.42551411, 0.59551395, 0.76551389, 0.93551398)
x <- c(61586.1711, 60330.5550, 54219.9925, 50927.5381, 48402.8700, 45661.9175, 37375.6023, 33249.1248, 30808.6131, 28378.6508, 22533.3782, 13901.0882, 11716.5669, 11004.7305, 10340.3429, 9587.7994, 8736.3200, 8372.1482, 8074.3709, 7788.1847, 7499.6721, 7204.3168, 6870.8192, 6413.0828, 5523.8097, 3961.6114, 3460.0913, 2907.8614, 2016.1158, 452.8841)
df<- data.frame(x,y)
#Use Segmented to get estimates for parameters with 2 breakpoints
my.seg2 <- segmented(lm(y ~ x, data = df), seg.Z = ~ x, npsi = 2)
#extract knot, intercept, and coefficient values to use as NLS start points
my.knot1 <- my.seg2$psi[1,2]
my.knot2 <- my.seg2$psi[2,2]
my.m_2 <- slope(my.seg2)$x[1,1]
my.b1 <- my.seg2$coefficients[[1]]
my.b2 <- my.seg2$coefficients[[2]]
my.b3 <- my.seg2$coefficients[[3]]
#Fit a NLS model to ~replicate segmented model. Presumably my model formula is where the problem lies
my.model <- nlsLM(y~m*x+b+(b2*(ifelse(x>=knot1&x<=knot2,1,0)*(x-knot1))+(b3*ifelse(x>knot2,1,0)*(x-knot2-knot1))),data=df, start = c(m = my.m_2, b = my.b1, b2 = my.b2, b3 = my.b3, knot1 = my.knot1, knot2 = my.knot2))
应该看起来如何
plot(my.seg2)
看起来如何
plot(x, y)
lines(x=x, y=predict(my.model), col='black', lty = 1, lwd = 1)
我很确定我是“正确的”,但是当用直线绘制 95% 的置信区间并且预测分辨率(例如,x 点的密度)增加时,事情似乎 非常不正确.
谢谢大家的帮助。
它可能部分反映了 segmented
中的限制。 segmented
returns 没有量化相关不确定性的单个变化点值。使用 mcp
重做分析,其中 returns 贝叶斯后验,我们看到第二个变化点是双峰分布的:
library(mcp)
model = list(
y ~ 1 + x, # Intercept + slope in first segment
~ 0 + x, # Only slope changes in the next segments
~ 0 + x
)
# Fit it with a large number of samples and plot the change point posteriors
fit = mcp(model, data = data.frame(x, y), iter = 50000, adapt = 10000)
plot_pars(fit, regex_pars = "^cp*", type = "dens_overlay")
仅供参考,mcp
也可以绘制可信区间(红色虚线):
plot(fit, q_fit = TRUE)
将 g 定义为与 x 具有相同长度的分组向量,对于 X 轴的 3 个部分采用值 1、2、3,并从中创建 nls 模型。结果图看起来不错。
my.knots <- c(my.knot1, my.knot2)
g <- cut(x, c(-Inf, my.knots, Inf), label = FALSE)
fm <- nls(y ~ a[g] + b[g] * x, df, start = list(a = c(1, 1, 1), b = c(1, 1, 1)))
plot(y ~ x, df)
lines(fitted(fm) ~ x, df, col = "red")
(图后续)
约束条件
虽然上面看起来不错并且可能已经足够了,但它并不能保证线段在节点处相交。为此,我们必须施加约束,即双方在节点处相等:
a[2] + b[2] * my.knots[1] = a[1] + b[1] * my.knots[1]
a[3] + b[3] * my.knots[2] = a[2] + b[2] * my.knots[2]
所以
a[2] = a[1] + (b[1] - b[2]) * my.knots[1]
a[3] = a[2] + (b[2] - b[3]) * my.knots[2]
= a[1] + (b[1] - b[2]) * my.knots[1] + (b[2] - b[3]) * my.knots[2]
给予:
# returns a vector of the three a values
avals <- function(a1, b) unname(cumsum(c(a1, -diff(b) * my.knots)))
fm2 <- nls(y ~ avals(a1, b)[g] + b[g] * x, df, start = list(a1 = 1, b = c(1, 1, 1)))
要获得我们可以使用的三个 a 值:
co <- coef(fm2)
avals(co[1], co[-1])
求残差平方和:
deviance(fm2)
## [1] 0.193077
多项式
虽然涉及大量参数,但可以使用多项式拟合代替分段线性回归。 12 次多项式涉及 13 个参数,但残差平方和低于分段线性回归。随着残差平方和的相应增加,可以使用较低的次数。 7次多项式涉及8个参数,虽然残差平方和较高,但视觉上看起来还不错。
fm12 <- nls(y ~ cbind(1, poly(x, 12)) %*% b, df, start = list(b = rep(1, 13)))
deviance(fm12)
## [1] 0.1899218
我的目标是拟合一个三段式(即两个断点)回归模型以使用传播的 predictNLS
函数进行预测,确保将节点定义为参数,但我的模型公式似乎不正确.
我已经使用 segmented
包来估计断点位置(在 NLS 中用作起始值),但我想将我的模型保留为 NLS 格式,特别是 nlsLM {minipack.lm}
因为我正在使用 NLS 将其他类型的曲线拟合到我的数据中,希望允许 NLS 优化节点值,有时使用可变权重,并且需要能够从 propagate
轻松计算 Monte Carlo 置信区间].尽管我非常接近 具有正确的公式语法,但我在断点附近没有得到 expected/required 行为。这些段应该直接在断点处相遇(没有任何跳跃),但至少在这个数据上,我在断点处得到了一个奇怪的局部最小值(见下图)。
以下是我的数据和一般过程的示例。我认为我的问题出在 NLS 公式中。
library(minpack.lm)
library(segmented)
y <- c(-3.99448113, -3.82447011, -3.65447803, -3.48447030, -3.31447855, -3.14448753, -2.97447972, -2.80448401, -2.63448380, -2.46448069, -2.29448796, -2.12448912, -1.95448783, -1.78448797, -1.61448563, -1.44448719, -1.27448469, -1.10448651, -0.93448525, -0.76448637, -0.59448626, -0.42448586, -0.25448588, -0.08448548, 0.08551417, 0.25551393, 0.42551411, 0.59551395, 0.76551389, 0.93551398)
x <- c(61586.1711, 60330.5550, 54219.9925, 50927.5381, 48402.8700, 45661.9175, 37375.6023, 33249.1248, 30808.6131, 28378.6508, 22533.3782, 13901.0882, 11716.5669, 11004.7305, 10340.3429, 9587.7994, 8736.3200, 8372.1482, 8074.3709, 7788.1847, 7499.6721, 7204.3168, 6870.8192, 6413.0828, 5523.8097, 3961.6114, 3460.0913, 2907.8614, 2016.1158, 452.8841)
df<- data.frame(x,y)
#Use Segmented to get estimates for parameters with 2 breakpoints
my.seg2 <- segmented(lm(y ~ x, data = df), seg.Z = ~ x, npsi = 2)
#extract knot, intercept, and coefficient values to use as NLS start points
my.knot1 <- my.seg2$psi[1,2]
my.knot2 <- my.seg2$psi[2,2]
my.m_2 <- slope(my.seg2)$x[1,1]
my.b1 <- my.seg2$coefficients[[1]]
my.b2 <- my.seg2$coefficients[[2]]
my.b3 <- my.seg2$coefficients[[3]]
#Fit a NLS model to ~replicate segmented model. Presumably my model formula is where the problem lies
my.model <- nlsLM(y~m*x+b+(b2*(ifelse(x>=knot1&x<=knot2,1,0)*(x-knot1))+(b3*ifelse(x>knot2,1,0)*(x-knot2-knot1))),data=df, start = c(m = my.m_2, b = my.b1, b2 = my.b2, b3 = my.b3, knot1 = my.knot1, knot2 = my.knot2))
应该看起来如何
plot(my.seg2)
看起来如何
plot(x, y)
lines(x=x, y=predict(my.model), col='black', lty = 1, lwd = 1)
我很确定我是“正确的”,但是当用直线绘制 95% 的置信区间并且预测分辨率(例如,x 点的密度)增加时,事情似乎 非常不正确.
谢谢大家的帮助。
它可能部分反映了 segmented
中的限制。 segmented
returns 没有量化相关不确定性的单个变化点值。使用 mcp
重做分析,其中 returns 贝叶斯后验,我们看到第二个变化点是双峰分布的:
library(mcp)
model = list(
y ~ 1 + x, # Intercept + slope in first segment
~ 0 + x, # Only slope changes in the next segments
~ 0 + x
)
# Fit it with a large number of samples and plot the change point posteriors
fit = mcp(model, data = data.frame(x, y), iter = 50000, adapt = 10000)
plot_pars(fit, regex_pars = "^cp*", type = "dens_overlay")
仅供参考,mcp
也可以绘制可信区间(红色虚线):
plot(fit, q_fit = TRUE)
将 g 定义为与 x 具有相同长度的分组向量,对于 X 轴的 3 个部分采用值 1、2、3,并从中创建 nls 模型。结果图看起来不错。
my.knots <- c(my.knot1, my.knot2)
g <- cut(x, c(-Inf, my.knots, Inf), label = FALSE)
fm <- nls(y ~ a[g] + b[g] * x, df, start = list(a = c(1, 1, 1), b = c(1, 1, 1)))
plot(y ~ x, df)
lines(fitted(fm) ~ x, df, col = "red")
(图后续)
约束条件
虽然上面看起来不错并且可能已经足够了,但它并不能保证线段在节点处相交。为此,我们必须施加约束,即双方在节点处相等:
a[2] + b[2] * my.knots[1] = a[1] + b[1] * my.knots[1]
a[3] + b[3] * my.knots[2] = a[2] + b[2] * my.knots[2]
所以
a[2] = a[1] + (b[1] - b[2]) * my.knots[1]
a[3] = a[2] + (b[2] - b[3]) * my.knots[2]
= a[1] + (b[1] - b[2]) * my.knots[1] + (b[2] - b[3]) * my.knots[2]
给予:
# returns a vector of the three a values
avals <- function(a1, b) unname(cumsum(c(a1, -diff(b) * my.knots)))
fm2 <- nls(y ~ avals(a1, b)[g] + b[g] * x, df, start = list(a1 = 1, b = c(1, 1, 1)))
要获得我们可以使用的三个 a 值:
co <- coef(fm2)
avals(co[1], co[-1])
求残差平方和:
deviance(fm2)
## [1] 0.193077
多项式
虽然涉及大量参数,但可以使用多项式拟合代替分段线性回归。 12 次多项式涉及 13 个参数,但残差平方和低于分段线性回归。随着残差平方和的相应增加,可以使用较低的次数。 7次多项式涉及8个参数,虽然残差平方和较高,但视觉上看起来还不错。
fm12 <- nls(y ~ cbind(1, poly(x, 12)) %*% b, df, start = list(b = rep(1, 13)))
deviance(fm12)
## [1] 0.1899218