GROWTH_SIGMOID
Overview
The GROWTH_SIGMOID function fits a variety of growth and sigmoid curve models to data using non-linear least squares optimization. These S-shaped curves are fundamental in modeling phenomena where growth is slow at the start, accelerates through a rapid growth phase, and then decelerates as it approaches an upper limit or carrying capacity. Applications include population dynamics, tumor growth modeling, market adoption curves, and biological growth processes.
This implementation provides ten specialized models, each with distinct mathematical properties:
Logistic Sigmoid models (Standard, Growth Rate, Scale Factor): The classic logistic function describes symmetric S-curves with the form f(x) = \frac{a}{1 + e^{-k(x - x_c)}}, where a is the asymptote, x_c is the inflection point, and k controls the steepness.
Gompertz Asymmetric Sigmoid: The Gompertz function produces asymmetric S-curves where growth decelerates more gradually than it accelerates: f(x) = a \cdot e^{-e^{-k(x - x_c)}}. Originally developed by Benjamin Gompertz in 1825 to model human mortality, it is widely used in tumor growth and bacterial population studies.
Richards Generalized Growth (V1, V2): The generalized logistic function, proposed by F.J. Richards in 1959, adds a shape parameter d that controls asymmetry and encompasses both logistic and Gompertz curves as special cases.
Chapman-Richards Tree Growth: A specialized model for forestry applications using the form f(x) = a(1 - e^{-bx})^c.
Weibull Sigmoid (Centered, Baseline): Based on the Weibull distribution, these models provide flexibility in modeling growth onset and saturation behavior.
Stirling Exponential Growth: Models early-stage exponential growth patterns.
The function uses scipy.optimize.curve_fit from the SciPy library, which employs the Levenberg-Marquardt algorithm or Trust Region Reflective method for optimization. The fitting process returns optimal parameter values along with standard errors derived from the covariance matrix, providing confidence estimates for the fitted parameters.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=GROWTH_SIGMOID(xdata, ydata, gs_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valuegs_model(str, required): The growth/sigmoid model to use for curve fitting
Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.
Examples
Example 1: Demo case 1
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| chapman_richards_tree_growth | 0.01 | 0.03182570079793111 |
| 2.0075 | 2.2572403973314343 | |
| 4.005 | 2.7220181885677848 | |
| 6.0024999999999995 | 2.698984861246657 | |
| 8 | 2.7706783938270685 |
Excel formula:
=GROWTH_SIGMOID("chapman_richards_tree_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.03182570079793111;2.2572403973314343;2.7220181885677848;2.698984861246657;2.7706783938270685})
Expected output:
"non-error"
Example 2: Demo case 2
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| gompertz_asymmetric_sigmoid | 0.01 | 0.031148788392549653 |
| 2.0075 | 2.159769238917366 | |
| 4.005 | 2.7937408538830355 | |
| 6.0024999999999995 | 2.707435417849546 | |
| 8 | 2.7719447581935377 |
Excel formula:
=GROWTH_SIGMOID("gompertz_asymmetric_sigmoid", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.031148788392549653;2.159769238917366;2.7937408538830355;2.707435417849546;2.7719447581935377})
Expected output:
"non-error"
Example 3: Demo case 3
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| logistic_sigmoid_standard | 0.01 | 0.031583061114896976 |
| 2.0075 | 2.226819957904996 | |
| 4.005 | 2.7937610855253303 | |
| 6.0024999999999995 | 2.7074157444636264 | |
| 8 | 2.7719549010796705 |
Excel formula:
=GROWTH_SIGMOID("logistic_sigmoid_standard", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.031583061114896976;2.226819957904996;2.7937610855253303;2.7074157444636264;2.7719549010796705})
Expected output:
"non-error"
Example 4: Demo case 4
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| logistic_sigmoid_growth_rate | 0.01 | 0.5387498895905355 |
| 2.0075 | 2.739803940436444 | |
| 4.005 | 2.7789169568640695 | |
| 6.0024999999999995 | 2.8180009985916086 | |
| 8 | 2.739545402970054 |
Excel formula:
=GROWTH_SIGMOID("logistic_sigmoid_growth_rate", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.5387498895905355;2.739803940436444;2.7789169568640695;2.8180009985916086;2.739545402970054})
Expected output:
"non-error"
Example 5: Demo case 5
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| logistic_sigmoid_scale_factor | 0.01 | 1.3898231895865445 |
| 2.0075 | 2.7460868574260067 | |
| 4.005 | 2.7677958329351275 | |
| 6.0024999999999995 | 2.7918466446781487 | |
| 8 | 2.7435664208872486 |
Excel formula:
=GROWTH_SIGMOID("logistic_sigmoid_scale_factor", {0.01;2.0075;4.005;6.0024999999999995;8}, {1.3898231895865445;2.7460868574260067;2.7677958329351275;2.7918466446781487;2.7435664208872486})
Expected output:
"non-error"
Example 6: Demo case 6
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| richards_generalized_growth_v1 | 0.01 | 1.0250735413389094 |
| 2.0075 | 1.052781180534077 | |
| 4.005 | 2.7856978947449056 | |
| 6.0024999999999995 | 2.715737216990588 | |
| 8 | 2.767664661457457 |
Excel formula:
=GROWTH_SIGMOID("richards_generalized_growth_v1", {0.01;2.0075;4.005;6.0024999999999995;8}, {1.0250735413389094;1.052781180534077;2.7856978947449056;2.715737216990588;2.767664661457457})
Expected output:
"non-error"
Example 7: Demo case 7
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| richards_generalized_growth_v2 | 0.01 | 0.2510702652820274 |
| 2.0075 | 2.3491788232497353 | |
| 4.005 | 2.7902961788825964 | |
| 6.0024999999999995 | 2.710785073074052 | |
| 8 | 2.770217797127339 |
Excel formula:
=GROWTH_SIGMOID("richards_generalized_growth_v2", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.2510702652820274;2.3491788232497353;2.7902961788825964;2.710785073074052;2.770217797127339})
Expected output:
"non-error"
Example 8: Demo case 8
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| weibull_sigmoid_centered | 0.01 | 0.0319904102937737 |
| 2.0075 | 2.7537859799551163 | |
| 4.005 | 2.794954714955878 | |
| 6.0024999999999995 | 2.7062853492618304 | |
| 8 | 2.7725376926243004 |
Excel formula:
=GROWTH_SIGMOID("weibull_sigmoid_centered", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.0319904102937737;2.7537859799551163;2.794954714955878;2.7062853492618304;2.7725376926243004})
Expected output:
"non-error"
Example 9: Demo case 9
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| weibull_sigmoid_baseline | 0.01 | 1.0668887573420247 |
| 2.0075 | 2.7452990150900534 | |
| 4.005 | 2.772021404065732 | |
| 6.0024999999999995 | 2.8017830004688475 | |
| 8 | 2.742038787511574 |
Excel formula:
=GROWTH_SIGMOID("weibull_sigmoid_baseline", {0.01;2.0075;4.005;6.0024999999999995;8}, {1.0668887573420247;2.7452990150900534;2.772021404065732;2.8017830004688475;2.742038787511574})
Expected output:
"non-error"
Example 10: Demo case 10
Inputs:
| gs_model | xdata | ydata |
|---|---|---|
| stirling_exponential_growth | 0.01 | 725315575202236 |
| 2.0075 | 0.01 | |
| 4.005 | 945772622909289.9 | |
| 6.0024999999999995 | 2227007213555768.5 | |
| 8 | 72670208044713760 |
Excel formula:
=GROWTH_SIGMOID("stirling_exponential_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {725315575202236;0.01;945772622909289.9;2227007213555768.5;72670208044713760})
Expected output:
"non-error"
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def growth_sigmoid(xdata, ydata, gs_model):
"""
Fits growth_sigmoid models to data using scipy.optimize.curve_fit. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html for details.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
This example function is provided as-is without any representation of accuracy.
Args:
xdata (list[list]): The xdata value
ydata (list[list]): The ydata value
gs_model (str): The growth/sigmoid model to use for curve fitting Valid options: Chapman Richards Tree Growth, Gompertz Asymmetric Sigmoid, Logistic Sigmoid Standard, Logistic Sigmoid Growth Rate, Logistic Sigmoid Scale Factor, Richards Generalized Growth V1, Richards Generalized Growth V2, Weibull Sigmoid Centered, Weibull Sigmoid Baseline, Stirling Exponential Growth.
Returns:
list[list]: 2D list [param_names, fitted_values, std_errors], or error string.
"""
def _validate_data(xdata, ydata):
"""Validate and convert both xdata and ydata to numpy arrays."""
for name, arg in [("xdata", xdata), ("ydata", ydata)]:
if not isinstance(arg, list) or len(arg) < 2:
raise ValueError(f"{name}: must be a 2D list with at least two rows")
vals = []
for i, row in enumerate(arg):
if not isinstance(row, list) or len(row) == 0:
raise ValueError(f"{name} row {i}: must be a non-empty list")
try:
vals.append(float(row[0]))
except Exception:
raise ValueError(f"{name} row {i}: non-numeric value")
if name == "xdata":
x_arr = np.asarray(vals, dtype=np.float64)
else:
y_arr = np.asarray(vals, dtype=np.float64)
if x_arr.shape[0] != y_arr.shape[0]:
raise ValueError("xdata and ydata must have the same number of rows")
return x_arr, y_arr
# Model definitions dictionary
models = {
'chapman_richards_tree_growth': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: a * np.power(1.0 - np.exp(-b * x), c),
'guess': lambda xa, ya: (float(np.max(ya)), 0.5, 1.0),
'bounds': (0.0, np.inf),
},
'gompertz_asymmetric_sigmoid': {
'params': ['a', 'xc', 'k'],
'model': lambda x, a, xc, k: a * np.exp(-np.exp(-k * (x - xc))),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.median(xa)), 1.0),
'bounds': ([0.0, -np.inf, 0.0], np.inf),
},
'logistic_sigmoid_standard': {
'params': ['a', 'xc', 'k'],
'model': lambda x, a, xc, k: a / (1.0 + np.exp(-k * (x - xc))),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.median(xa)), 1.0),
'bounds': ([0.0, -np.inf, 0.0], np.inf),
},
'logistic_sigmoid_growth_rate': {
'params': ['y0', 'a', 'Wmax'],
'model': lambda x, y0, a, Wmax: a / (1.0 + ((a - y0) / np.clip(y0, 1e-9, None)) * np.exp(-4.0 * Wmax * x / a)),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.max(ya)), 1.0),
'bounds': (0.0, np.inf),
},
'logistic_sigmoid_scale_factor': {
'params': ['a', 'b', 'k'],
'model': lambda x, a, b, k: a / (1.0 + b * np.exp(-k * x)),
'guess': lambda xa, ya: (float(np.max(ya)), 1.0, 1.0),
'bounds': (0.0, np.inf),
},
'richards_generalized_growth_v1': {
'params': ['a', 'xc', 'd', 'k'],
'model': lambda x, a, xc, d, k: np.power(np.power(a, 1.0 - d) - np.exp(-k * (x - xc)), 1.0 / (1.0 - d)),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.min(xa)), 0.5, 1.0),
'bounds': ([0.0, -np.inf, -np.inf, 0.0], np.inf),
},
'richards_generalized_growth_v2': {
'params': ['a', 'xc', 'd', 'k'],
'model': lambda x, a, xc, d, k: a * np.power(1.0 + (d - 1.0) * np.exp(-k * (x - xc)), 1.0 / (1.0 - d)),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.min(xa)), 0.5, 1.0),
'bounds': ([0.0, -np.inf, -np.inf, 0.0], np.inf),
},
'weibull_sigmoid_centered': {
'params': ['A', 'xc', 'd', 'k'],
'model': lambda x, A, xc, d, k: A * (1.0 - np.exp(-np.power(np.clip(k * (x - xc), 0.0, None), d))),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.median(xa)), 1.0, 1.0),
'bounds': ([0.0, -np.inf, 0.0, 0.0], np.inf),
},
'weibull_sigmoid_baseline': {
'params': ['a', 'b', 'd', 'k'],
'model': lambda x, a, b, d, k: a - (a - b) * np.exp(-np.power(k * x, d)),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.min(ya)), 1.0, 1.0),
'bounds': (0.0, np.inf),
},
'stirling_exponential_growth': {
'params': ['a', 'b', 'k'],
'model': lambda x, a, b, k: a + b * (np.exp(k * x) - 1.0) / (k if np.abs(k) > 1e-9 else 1.0),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 0.1),
}
}
# Validate model parameter
if gs_model not in models:
return f"Invalid model: {str(gs_model)}. Valid models are: {', '.join(models.keys())}"
model_info = models[gs_model]
# Validate and convert input data
try:
x_arr, y_arr = _validate_data(xdata, ydata)
except ValueError as e:
return f"Invalid input: {e}"
# Perform curve fitting
try:
p0 = model_info['guess'](x_arr, y_arr)
bounds = model_info.get('bounds', (-np.inf, np.inf))
if bounds == (-np.inf, np.inf):
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)
else:
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)
fitted_vals = [float(v) for v in popt]
for v in fitted_vals:
if math.isnan(v) or math.isinf(v):
return "Fitting produced invalid numeric values (NaN or inf)."
except ValueError as e:
return f"Initial guess error: {e}"
except Exception as e:
return f"curve_fit error: {e}"
# Calculate standard errors
std_errors = None
try:
if pcov is not None and np.isfinite(pcov).all():
std_errors = [float(v) for v in np.sqrt(np.diag(pcov))]
except Exception:
pass
return [model_info['params'], fitted_vals, std_errors] if std_errors else [model_info['params'], fitted_vals]