nls函数表现不佳

时间:2014-01-29 15:21:38

标签: r nls

我需要适合这个公式

 y ~ 1/(pi*a*(1+((x-2.15646)/a)^2))+1/(pi*b*(1+((x-2.16355)/b)^2))

变量x和y

 x<- c(2.15011, 2.15035, 2.15060, 2.15084, 2.15109, 2.15133, 2.15157, 2.15182, 2.15206, 2.15231, 2.15255, 2.15280, 2.15304, 2.15329, 2.15353, 2.15377, 2.15402, 2.15426, 2.15451, 2.15475, 2.15500, 2.15524, 2.15549, 2.15573, 2.15597, 2.15622, 2.15646, 2.15671, 2.15695, 2.15720, 2.15744, 2.15769, 2.15793, 2.15817, 2.15842, 2.15866, 2.15891, 2.15915, 2.15940, 2.15964, 2.15989, 2.16013, 2.16037, 2.16062, 2.16086, 2.16111, 2.16135, 2.16160, 2.16184, 2.16209, 2.16233, 2.16257, 2.16282, 2.16306, 2.16331, 2.16355, 2.16380, 2.16404, 2.16429, 2.16453, 2.16477, 2.16502, 2.16526, 2.16551, 2.16575, 2.16600, 2.16624, 2.16649, 2.16673, 2.16697, 2.16722, 2.16746, 2.16771, 2.16795, 2.16820, 2.16844, 2.16869, 2.16893, 2.16917, 2.16942, 2.16966, 2.16991)
 y<- c(3.77212,  3.79541,  3.84574,  3.91918, 4.01056,  4.11677,  4.23851,  4.37986, 4.54638,  4.74367,  4.97765,  5.25593,  5.58823,  5.98405,  6.44850,  6.98006, 7.57280,  8.22085,  8.92094,  9.66990, 10.45900, 11.26720, 12.05540, 12.76920, 13.34830, 13.74250, 13.92420, 13.89250, 13.67090, 13.29980, 12.82780, 12.30370, 11.76950, 11.25890, 10.80020, 10.41860, 10.13840,  9.98005,  9.95758, 10.07690, 10.33680, 10.73210, 11.25730, 11.90670, 12.67240, 13.54110, 14.49530, 15.51670, 16.58660, 17.67900, 18.75190, 19.74600, 20.59680, 21.24910, 21.66800, 21.83910, 21.76560, 21.46020, 20.94020, 20.22730, 19.35360, 18.36460, 17.31730, 16.26920, 15.26920, 14.35320, 13.54360, 12.85230, 12.28520, 11.84690, 11.54040, 11.36610, 11.32130, 11.39980, 11.59230, 11.88310, 12.25040, 12.66660, 13.09810, 13.50220, 13.82580, 14.01250)

根据x和y估算'a'和'b'值。 'a'和'b'的范围在0到1之间。

但是,当我使用nls命令时:

nls(y ~1/(pi*a*(1+((x-2.15646)/a)^2))+1/(pi*b*(1+((x-2.16355)/b)^2)), control = list(maxiter = 500), start=list(a=0.4,b=0.4))

控制台报告了以下错误:

singular gradient

任何人都可以向我解释为什么控制台会打印此消息?

2 个答案:

答案 0 :(得分:5)

这样可以更好地适应:

在进入代码之前(下面),您的模型存在一些问题:

  1. 假设这是质子NMR,峰下面积与质子丰度(质子数)成正比。您的模型不允许这样做,基本上强制所有峰具有相同的区域。这是不合身的主要原因。我们可以通过为每个峰值包含“高度”因子来轻松实现这一目标。
  2. 您的模型假设峰值位置。为什么不让算法找到真正的峰值位置?
  3. 您的模型未考虑基线漂移,您可以看到数据集中的基线漂移非常严重。我们可以通过在模型中添加线性漂移函数来实现这一目的。
  4. nls(...)对于这种类型的建模很差 - 它使用的算法并不是特别健壮。在拟合偏移数据时,默认算法Gauss-Newton特别差。因此,在p1模型中估算p2f(x-p1,x-p2)几乎总是失败。

    更好的方法是使用包nls.lm(...)minpack中实现的极其强大的Levenberg-Marquardt算法。这个包有点难以使用,但它能够处理nls(...)无法访问的问题。如果你要做很多这样的事情,你应该阅读documentation以了解这个例子的工作原理。

    最后,即使nls.lm(...),起点也必须合理。在您的模型中,a和b是峰宽。显然,它们必须与峰值位置的差异相当或更小,否则峰值会被涂抹在一起。你对(a,b)=(0.4,0.4)的估计太大了。

    plot(x,y)
    library(minpack.lm)
    lorentzian <- function(par,x){ 
      a  <- par[1]
      b  <- par[2]
      p1 <- par[3]
      p2 <- par[4]
      h1 <- par[5]
      h2 <- par[6]
      drift.a <- par[7]
      drift.b <- par[8]
      h1/(pi*a*(1+((x-p1)/a)^2))+h2/(pi*b*(1+((x-p2)/b)^2)) + drift.a + drift.b*x
    }
    
    resid   <- function(par,obs,xx) {obs-lorentzian(par,xx)}
    par=c(a=0.001,b=0.001, p1=2.157, p2=2.163, h1=1, h2=1, drift.a=0, drift.b=0)
    lower=c(a=0,b=0,p1=0,p2=0,h1=0, h2=0,drift.a=NA,drift.b=NA)
    nls.out <- nls.lm(par=par, lower=lower, fn=resid, obs=y, xx=x, 
                      control = nls.lm.control(maxiter=500))
    coef(nls.out)
    #             a             b            p1            p2            h1            h2       drift.a       drift.b 
    #  1.679632e-03  1.879690e-03  2.156308e+00  2.163500e+00  4.318793e-02  8.199394e-02 -9.273083e+02  4.323897e+02 
    
    lines(x,lorentzian(coef(nls.out), x), col=2, lwd=2)
    

    最后一件事:关于SO的约定是在“接受”答案之前等待一天。原因是接受答案的问题很少得到额外的关注 - 一旦你接受答案,其他任何人都不会看到答案。

答案 1 :(得分:2)

尝试使用ab的倒数:

fm<-nls(y~1/(pi*(1/a)*(1+((x-2.15646)/(1/a))^2))+1/(pi*(1/b)*(1+((x-2.16355)/(1/b))^2)), 
   lower = 1, alg = "port",
   control = list(maxiter = 500), 
   start = list(a = 1/.4, b = 1/.4))

给出:

> 1/coef(fm)
         a          b 
1.00000000 0.02366843 

不幸的是,模型不能很好地工作,如底部的图表所示。

plot(y ~ x, pch = 20)
lines(x, fitted(fm), col = "red")

增加:

在他的回答中,@ jloward基于8个参数提供了更好的拟合模型。我只想指出,如果我们将他的模型与abp1p2的起始值一起使用(我们不需要线性的起始值)参数如果我们指定alg = "plinear")那么nls也会起作用。

fo <- y ~ cbind(1/(pi*a*(1+((x-p1)/a)^2)), 1/(pi*b*(1+((x-p2)/b)^2)), 1, x)
start <- c(a = 0.001, b = 0.001, p1 = 2.157, p2 = 2.163)
fm2 <- nls(fo, start = start, alg = "plinear")

,并提供:

> coef(fm2)
            a             b            p1            p2         .lin1 
 1.679635e-03  1.879682e-03  2.156308e+00  2.163500e+00  4.318798e-02 
        .lin2         .lin3        .lin.x 
 8.199364e-02 -9.273104e+02  4.323907e+02 

图表显示fm不适合

enter image description here

修改添加约束。