使用R语言nls拟合Sigmoid函数时出现singular gradient错误的解决建议咨询
nls() Hey there! That singular gradient error is super common when using nls() for curve fitting—it usually boils down to poor initial parameter guesses or a model parameterization that causes collinearity between parameters. Let's break down how to fix this for your sigmoid fit.
First, Understand the Root Cause
Your original sigmoid function phi1/(1 + exp(-phi3 * x - phi2)) is mathematically valid, but the way parameters are combined (-phi3*x - phi2) makes it hard to pick good starting values, and can lead the optimizer to hit a "singular gradient" where it can't adjust parameters effectively. Also, your initial guesses (especially phi3 = 0.0005) were way too small to match your data's growth rate.
Step 1: Re-Parameterize the Sigmoid for Better Interpretability
Switch to a more intuitive sigmoid form where parameters map directly to meaningful features of your data:
phi1: The upper asymptote (matches the maximum value in yourcumulativocolumn)phi3: The x-value where the curve inflects (the point of fastest growth)phi2: The steepness of the curve (negative for an increasing sigmoid)
Here's the adjusted function:
f <- function(x, phi1, phi2, phi3) { phi1 / (1 + exp(phi2 * (x - phi3))) }
Step 2: Set Realistic Initial Values
Looking at your data:
cumulativotops out around 19639, so setphi1 = 19700(a little higher than the max)- The fastest growth happens between 2000-2005, so set
phi3 = 2005(the inflection point) - For a moderate growth steepness, start with
phi2 = -0.5(negative because we want an increasing S-curve)
Your initial values list becomes:
st <- list(phi1 = 19700, phi2 = -0.5, phi3 = 2005)
Step 3: Use a More Robust Fitting Function (Optional but Recommended)
The base nls() is sensitive to initial values. The nlsLM() function from the minpack.lm package uses a more robust algorithm that handles poor initial guesses better.
Full Working Code
library(ggplot2) library(minpack.lm) # Load the package for nlsLM # Your original data data <- structure(list(cumulativo = c(2, 3, 17, 191, 819, 1699, 2679, 3907, 5535, 7254, 9226, 11543, 13809, 15542, 16852, 17709, 18246, 18661, 18976, 19256, 19412, 19539, 19639), eixox = 1994:2016), class = "data.frame", row.names = c(NA, -23L)) # Plot raw data to visualize trend plot(cumulativo ~ eixox, data = data) # Re-parameterized sigmoid function f <- function(x, phi1, phi2, phi3) { phi1 / (1 + exp(phi2 * (x - phi3))) } # Realistic initial values st <- list(phi1 = 19700, phi2 = -0.5, phi3 = 2005) # Fit the model with nlsLM (more robust than base nls) curvaS <- nlsLM(cumulativo ~ f(eixox, phi1, phi2, phi3), start = st, data = data, trace = TRUE) # Check the fit summary summary(curvaS) # Add predicted values to your data frame data$predicted <- predict(curvaS, newdata = data) # Plot the fit against raw data ggplot(data, aes(x = eixox)) + geom_point(aes(y = cumulativo), color = "black") + geom_line(aes(y = predicted), color = "red", linewidth = 1) + labs(title = "Sigmoid Fit to Cumulative Data", x = "Year", y = "Cumulative Value")
What If You Want to Stick With Your Original Model?
If you prefer your original sigmoid form, adjust the initial values to match the re-parameterized version. For example, since -phi3*x - phi2 = phi2_new*(x - phi3_new), you can calculate:
phi3_original = -phi2_new = 0.5phi2_original = phi2_new * phi3_new = -0.5 * 2005 = -1002.5
Your initial values would be:
st <- list(phi1=20000, phi2=-1002.5, phi3=0.5)
Then you can use this with base nls()—though nlsLM() is still more reliable.
内容的提问来源于stack exchange,提问作者Antonio Abrantes

