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 value
  • ydata (list[list], required): The ydata value
  • gs_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]

Online Calculator