Search code examples
rcurve-fittingnls

Why can't the R function "nls" reach convergence when the initial curve looks so close to the points?


I want to fit the points below with the function whose first guess is represented by the line:

Points with curve with initially guessed parameters.

But I get the following error message:

Error in nls(y ~ A * (1 + erf(E * B * (x - C)/sqrt(2))) * dnorm(B * (x - : gradiente singular
Traceback:

1. nls(y ~ A * (1 + erf(E * B * (x - C)/sqrt(2))) * dnorm(B * (x - 
 .     C)) + D, data = fdata, start = list(A = A0, B = B0, C = C0, 
 .     D = D0, E = E0))

Here's my code:

erf <- function(x) 2 * pnorm(x * sqrt(2)) - 1

x = c(0.275, 0.325, 0.375, 0.425, 0.475, 0.525, 0.575, 0.625, 0.675, 0.725, 0.775, 0.825, 0.875, 0.925, 0.975)
y = c(33, 69, 48, 57, 51, 46, 36, 42, 26, 22, 22, 18, 16, 9, 5)

plot(x, y, xlim=c(0,1))

fdata = data.frame(x = x, y = y)

# Initial parameter values:
A0 = 1.3*max(y); B0 = 3.8*max(x); C0 = 1.0*x[which.max(y)]; D0 = 0.0; E0 = 4.0

# Plot curve with initial parameter values:
xfit0 = seq(from=0.0, to=1.0, length.out=100)
yfit0 = A0*(1+erf(E0*B0*(xfit0-C0)/sqrt(2)))*dnorm(B0*(xfit0-C0))+D0
lines(xfit0, yfit0, lwd=3)

# Do the fit:
fit <- nls(y~A*(1+erf(E*B*(x-C)/sqrt(2)))*dnorm(B*(x-C))+D, data = fdata, start = list(A = A0, B = B0, C = C0, D = D0, E = E0))
lines(fdata$x, predict(fit), col="red", lwd=2)

Why can't nls converge even for such a close initial guess?


Solution

  • There aren't enough points to get a decent fit, particularly in some regions of the graph. Add some points using spline interpolation and then fit. To do that replace the last two lines of code in the question with these:

    fdata.s <- spline(fdata)
    fit <- nls(y~A*(1+erf(E*B*(x-C)/sqrt(2)))*dnorm(B*(x-C))+D, data = fdata.s, 
      start = list(A = A0, B = B0, C = C0, D = D0, E = E0))
    lines(fdata.s$x, fitted(fit), col="red", lwd=2)
    

    screenshot