【问题标题】:piecewise function fitting with nls() in R在 R 中使用 nls() 进行分段函数拟合
【发布时间】:2013-04-08 08:09:11
【问题描述】:

我正在尝试将两部分的线拟合到数据中。

以下是一些示例数据:

x<-c(0.00101959664756622, 0.001929220749155, 0.00165657261751726, 
0.00182514724375389, 0.00161532360585458, 0.00126991061099209, 
0.00149545009309177, 0.000816386510029308, 0.00164402569283353, 
0.00128029006251656, 0.00206892841921455, 0.00132378793976235, 
0.000953143467154676, 0.00272964503695939, 0.00169743839571702, 
0.00286411493120396, 0.0016464862337286, 0.00155672067449593, 
0.000878271561566836, 0.00195872573138819, 0.00255412836538339, 
0.00126212428137799, 0.00106206607962734, 0.00169140916371657, 
0.000858015581562961, 0.00191955159274793, 0.00243104345247067, 
0.000871042201994687, 0.00229814264111745, 0.00226756341241083)

y<-c(1.31893118849162, 0.105150790530179, 0.412732029152914, 0.25589805483046, 
0.467147868109498, 0.983984462069833, 0.640007862668818, 1.51429617241365, 
0.439777145282391, 0.925550163462951, -0.0555942758921906, 0.870117027565708, 
1.38032147826294, -0.96757052387814, 0.346370836378525, -1.08032147826294, 
0.426215616848312, 0.55151485221263, 1.41306889485598, 0.0803478641720901, 
-0.86654892295057, 1.00422341998656, 1.26214517662281, 0.359512373951839, 
1.4835398594013, 0.154967053938309, -0.680501679226447, 1.44740598234453, 
-0.512732029152914, -0.359512373951839)

我希望能够定义最合适的两部分线(显示手绘示例)

然后我定义了一个分段函数,它应该找到一个两部分的线性函数。定义是基于两条线的梯度和它们相互的截距,应该完全定义线。

# A=gradient of first line segment
# B=gradient of second line segment
# Cx=inflection point x coord
# Cy=inflexion point y coord 

out_model <- nls(y ~ I(x <= Cx)*Cy-A*(Cx-x)+I(x > Cx)*Cy+B*(x), 
                  data = data.frame(x,y), 
                  start = c(A=-500,B=-500,Cx=0.0001,Cy=-1.5) )

但是我得到了错误:

nls(y ~ I(x Cx) * Cy + B * 中的错误: 奇异梯度

我从Finding a curve to match data得到了基本方法

任何想法我哪里出错了?

【问题讨论】:

  • 我还不知道为什么这不起作用,但我尝试了多种不同的方法来将此函数与数据相匹配,但都没有奏效 - 使用 nls() 或 @ 987654327@。在每种情况下,我都得到了一个奇异矩阵。所以,我可以确认这是一个棘手的问题。
  • 简短的回答是您的数据不支持您的模型。 IE。单行就足够了。不需要分段线。
  • 使用R包mcp,你可以用mcp(list(y ~ x, ~ x), data.frame(x, y))推断出变化点。在此处查看替代软件包列表:lindeloev.github.io/mcp/articles/packages.html

标签: r piecewise


【解决方案1】:

我没有优雅的答案,但我有一个的答案。

(请参阅下面的编辑以获得更优雅的答案)

如果Cx 足够小以至于没有数据点可以容纳ACy,或者如果Cx 足够大以至于没有数据点可以容纳BCy到,QR 分解矩阵将是奇异的,因为将有许多不同的值 CxACyCxBCy 分别将同样适合数据。

我通过阻止安装 Cx 来测试这一点。如果我在(比如说)Cx = mean(x) 处修复 Cxnls() 可以毫无困难地解决问题:

nls(y ~ ifelse(x < mean(x),ya+A*x,yb+B*x), 
               data = data.frame(x,y), 
               start = c(A=-1000,B=-1000,ya=3,yb=0))

...给出:

Nonlinear regression model
  model:  y ~ ifelse(x < mean(x), ya + A * x, yb + B * x) 
   data:  data.frame(x, y) 
        A         B        ya        yb 
-1325.537 -1335.918     2.628     2.652 
 residual sum-of-squares: 0.06614

Number of iterations to convergence: 1 
Achieved convergence tolerance: 2.294e-08 

这让我想到,如果我将Cx 转换为永远不会超出[min(x),max(x)] 的范围,那可能会解决问题。事实上,我希望至少有三个数据点可用于拟合“A”线和“B”线中的每一个,因此 Cx 必须介于x 的第三低和第三高值之间.使用 atan() 函数和适当的算术让我将范围 [-inf,+inf] 映射到 [0,1],所以我得到了代码:

trans <- function(x) 0.5+atan(x)/pi
xs <- sort(x)
xlo <- xs[3]
xhi <- xs[length(xs)-2]
nls(y ~ ifelse(x < xlo+(xhi-xlo)*trans(f),ya+A*x,yb+B*x), 
               data = data.frame(x,y), 
               start = c(A=-1000,B=-1000,ya=3,yb=0,f=0))

不幸的是,我仍然从这段代码中得到singular gradient matrix at initial parameters 错误,所以问题仍然是过度参数化的。正如@Henrik 所建议的那样,双线性和单线性拟合之间的差异对于这些数据来说并不是很大。

不过,我仍然可以得到双线性拟合的答案。因为nls() 解决了Cx 固定时的问题,所以我现在可以通过简单地使用optimize() 进行一维最小化来找到Cx 的值,它可以最小化残差标准误差。不是一个特别优雅的解决方案,但总比没有好:

xs <- sort(x)
xlo <- xs[3]
xhi <- xs[length(xs)-2]
nn <- function(f) nls(y ~ ifelse(x < xlo+(xhi-xlo)*f,ya+A*x,yb+B*x), 
               data = data.frame(x,y), 
               start = c(A=-1000,B=-1000,ya=3,yb=0))
ssr <- function(f) sum(residuals(nn(f))^2)
f = optimize(ssr,interval=c(0,1))
print (f$minimum)
print (nn(f$minimum))
summary(nn(f$minimum))

... 给出以下输出:

[1] 0.8541683
Nonlinear regression model
  model:  y ~ ifelse(x < xlo + (xhi - xlo) * f, ya + A * x, yb + B * x) 
   data:  data.frame(x, y) 
        A         B        ya        yb 
-1317.215  -872.002     2.620     1.407 
 residual sum-of-squares: 0.0414

Number of iterations to convergence: 1 
Achieved convergence tolerance: 2.913e-08 

Formula: y ~ ifelse(x < xlo + (xhi - xlo) * f, ya + A * x, yb + B * x)

Parameters:
     Estimate Std. Error t value Pr(>|t|)    
A  -1.317e+03  1.792e+01 -73.493  < 2e-16 ***
B  -8.720e+02  1.207e+02  -7.222 1.14e-07 ***
ya  2.620e+00  2.791e-02  93.854  < 2e-16 ***
yb  1.407e+00  3.200e-01   4.399 0.000164 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 

Residual standard error: 0.0399 on 26 degrees of freedom

Number of iterations to convergence: 1 

A 的值与Byayb 的值之间没有很大的差异,对于f 的最佳值,但存在一些差异。

(编辑——优雅的答案)

已将问题分为两步,不再需要使用nls()lm() 工作正常,如下:

function (x,y) 
{
    f <- function (Cx) 
        {
        lhs <- function(x) ifelse(x < Cx,Cx-x,0)
        rhs <- function(x) ifelse(x < Cx,0,x-Cx)
        fit <- lm(y ~ lhs(x) + rhs(x))
        c(summary(fit)$r.squared, 
            summary(fit)$coef[1], summary(fit)$coef[2],
            summary(fit)$coef[3])
        }

    r2 <- function(x) -(f(x)[1])

    res <- optimize(r2,interval=c(min(x),max(x)))
    res <- c(res$minimum,f(res$minimum))

    best_Cx <- res[1]
    coef1 <- res[3]
    coef2 <- res[4]
    coef3 <- res[5]
    plot(x,y)
    abline(coef1+best_Cx*coef2,-coef2) #lhs  
    abline(coef1-best_Cx*coef3,coef3)  #rs
}

... 给出:

【讨论】:

  • 正如@Henrik 指出的那样,以这种方式将问题分为两个阶段,不再需要使用nls() - 只需使用两个lm() 调用即可。
【解决方案2】:

如果断点已知,则可以使用线性回归

“Practical Regression and Anova using R”的断棒回归

朱利安·J·法拉维

2000 年 12 月

k <- 0.0025

lhs <- function(x) ifelse(x < k,k-x,0)
rhs <- function(x) ifelse(x < k,0,x-k)
fit <- lm(y ~ lhs(x) + rhs(x))

【讨论】:

  • 谢谢,这让我走上了正轨,我在下面稍微充实了一点
【解决方案3】:

segmented 包是为此类问题设计的。

首先,使用lm 创建一个常规线性回归:

linmod <- lm(y ~ x)
summary(linmod)

这给了我们:

Call:
lm(formula = y ~ x)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.108783 -0.025432 -0.006484  0.040092  0.088638 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  2.630e+00  2.732e-02   96.28   <2e-16 ***
x           -1.326e+03  1.567e+01  -84.63   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.04869 on 28 degrees of freedom
Multiple R-squared:  0.9961,    Adjusted R-squared:  0.996 
F-statistic:  7163 on 1 and 28 DF,  p-value: < 2.2e-16

接下来,我们使用线性模型生成一个有 1 个断点的分段模型:

segmod <- segmented(linmod, seg.Z = ~x)
summary(segmod)

并且分段模型提供了更好的 r-squared:

    ***Regression Model with Segmented Relationship(s)***

Call: 
segmented.lm(obj = linmod, seg.Z = ~x)

Estimated Break-Point(s):
   Est. St.Err 
 0.003  0.000 

Meaningful coefficients of the linear terms:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  2.659e+00  2.882e-02  92.239   <2e-16 ***
x           -1.347e+03  1.756e+01 -76.742   <2e-16 ***
U1.x         5.167e+02  4.822e+02   1.072       NA    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.04582 on 26 degrees of freedom
Multiple R-Squared: 0.9968,  Adjusted R-squared: 0.9964 

Convergence attained in 3 iterations with relative change 0 

可以查看绘图、截距和斜率:

plot(segmod)
intercept(segmod)
slope(segmod)

【讨论】:

    【解决方案4】:

    感谢 Henrik 让我走上了正确的道路! 这是一个更完整且相对优雅的解决方案,带有一个简单的情节:

    range_x<-max(x)-min(x)
    intervals=1000
    coef1=c()
    coef2=c()
    coef3=c()
    r2=c()
    
    for (i in 1:intervals)  
    {
    Cx<-min(x)+(i-1)*(range_x/intervals)
    lhs <- function(x) ifelse(x < Cx,Cx-x,0)
    rhs <- function(x) ifelse(x < Cx,0,x-Cx)
    fit <- lm(y ~ lhs(x) + rhs(x))
    coef1[i]<-summary(fit)$coef[1]
    coef2[i]<-summary(fit)$coef[2]
    coef3[i]<-summary(fit)$coef[3]
    r2[i]<-summary(fit)$r.squared
    }
    best_r2<-max(r2)                             # get best r squared
    pos<-which.max(r2)                                          
    best_Cx<-min(x)+(pos-1)*(range_x/intervals)  # get Cx for best r2
    
    plot(x,y)
    abline(coef1[pos]+best_Cx*coef2[pos],-coef2[pos]) #lhs  
    abline(coef1[pos]-best_Cx*coef3[pos],coef3[pos])  #rs
    

    【讨论】:

    • 使用optimize() 来查找断点比在 min(x) 到 max(x) 范围内测试 1000 个可能的断点更优雅。
    • 我同意这样会更好,但是一直很难重写部分函数以进行优化,有什么提示吗?
    • 我在答案中添加了一个使用 optimize()lm() 的版本。功能代码与您的几乎相同。您似乎在答案中使用了更大的数据集,但相同的代码应该适用于两个数据集。
    • 谢谢西蒙,太完美了!
    猜你喜欢
    • 2011-01-15
    • 2016-11-23
    • 2013-12-04
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    • 2017-12-25
    • 2020-09-17
    相关资源
    最近更新 更多