EXP_GROWTH
Overview
The EXP_GROWTH function fits a variety of exponential growth models to x-y data using nonlinear least squares regression. Exponential growth describes processes where the rate of change is proportional to the current value, making these models essential for analyzing population dynamics, compound interest, radioactive decay, and biological growth phenomena.
This implementation uses scipy.optimize.curve_fit from the SciPy library, which employs the Levenberg-Marquardt algorithm for unconstrained problems or Trust Region Reflective (TRF) methods when parameter bounds are specified. The function minimizes the sum of squared residuals between the observed data and the model predictions.
The function supports ten distinct exponential growth models:
- Standard exponential growth: y = a \cdot e^{bx} — the classic unbounded growth model
- Geometric growth: y = a \cdot b^x — discrete-time exponential growth with base b
- Asymptotic saturation: y = 1 - e^{-Ax} — growth approaching a maximum limit
- Power base saturation: y = 1 - B^x — saturation with power-law base decay
- Log-linear form: y = e^{a + bx} — exponential model expressed in log-linear terms
- Single-phase growth: y = y_0 + A_1 \cdot e^{x/t_1} — growth with baseline offset
- Dual-phase growth: combines two exponential terms with different time constants
- Triple-phase growth: three-component exponential for complex growth dynamics
- Biphasic association: sum of two saturation terms for multi-stage processes
- Delayed onset growth: exponential growth starting after a threshold x_0
For each model, the function returns the fitted parameter values and, when covariance estimation succeeds, the standard errors calculated as the square root of the diagonal elements of the covariance matrix. These standard errors provide uncertainty estimates derived from a linear approximation to the model at the optimum.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=EXP_GROWTH(xdata, ydata, exp_growth_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valueexp_growth_model(str, required): The exp_growth_model value
Returns (list[list]): A 2D list where the first row contains parameter names, the second row contains fitted parameter values, and an optional third row contains standard errors (when covariance estimation succeeds). Returns an error string on failure.
Examples
Example 1: Demo case 1
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_asymptotic_saturation | 0.01 | 0.03678022283335006 |
| 2.0075 | 0.9933091358855604 | |
| 4.005 | 1.012573003679107 | |
| 6.0024999999999995 | 1.0296038987500231 | |
| 8 | 0.9954486321684086 |
Excel formula:
=EXP_GROWTH("exp_asymptotic_saturation", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.03678022283335006;0.9933091358855604;1.012573003679107;1.0296038987500231;0.9954486321684086})
Expected output:
| A |
|---|
| 3.664 |
| 1.699 |
Example 2: Demo case 2
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_power_base_saturation | 0.1 | 0.06610565381268688 |
| 1.3250000000000002 | 0.5448752512710233 | |
| 2.5500000000000003 | 0.7927946466234451 | |
| 3.7750000000000004 | 0.9200750011371784 | |
| 5 | 0.9458654821398577 |
Excel formula:
=EXP_GROWTH("exp_power_base_saturation", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.06610565381268688;0.5448752512710233;0.7927946466234451;0.9200750011371784;0.9458654821398577})
Expected output:
| B |
|---|
| 0.5414 |
| 0.007212 |
Example 3: Demo case 3
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_geometric_growth_base | 0.1 | 2.7700051177663165 |
| 1.3250000000000002 | 2.9318268135473864 | |
| 2.5500000000000003 | 3.1228829896653267 | |
| 3.7750000000000004 | 3.3262471742631257 | |
| 5 | 3.5066842214375136 |
Excel formula:
=EXP_GROWTH("exp_geometric_growth_base", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.7700051177663165;2.9318268135473864;3.1228829896653267;3.3262471742631257;3.5066842214375136})
Expected output:
| a | b |
|---|---|
| 2.756 | 1.05 |
| 0.008175 | 0.0009384 |
Example 4: Demo case 4
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_growth_standard | 0.01 | 120.83587509019658 |
| 2.0075 | 0.01 | |
| 4.005 | 338.2925056679918 | |
| 6.0024999999999995 | 1863.496122144786 | |
| 8 | 12173.781005925137 |
Excel formula:
=EXP_GROWTH("exp_growth_standard", {0.01;2.0075;4.005;6.0024999999999995;8}, {120.83587509019658;0.01;338.2925056679918;1863.496122144786;12173.781005925137})
Expected output:
| a | b |
|---|---|
| 6.818 | 0.9359 |
| 1.067 | 0.01967 |
Example 5: Demo case 5
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_log_linear_form | 0.01 | 687.342222705341 |
| 2.0075 | 0.01 | |
| 4.005 | 1924.2855037614677 | |
| 6.0024999999999995 | 10599.99412957201 | |
| 8 | 69247.2636052396 |
Excel formula:
=EXP_GROWTH("exp_log_linear_form", {0.01;2.0075;4.005;6.0024999999999995;8}, {687.342222705341;0.01;1924.2855037614677;10599.99412957201;69247.2636052396})
Expected output:
| a | b |
|---|---|
| 3.658 | 0.9359 |
| 0.1566 | 0.01968 |
Example 6: Demo case 6
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_biphasic_association | 0.01 | 0.049587290376612685 |
| 2.0075 | 1.7933284068008737 | |
| 4.005 | 3.061975136237548 | |
| 6.0024999999999995 | 3.9431458463858697 | |
| 8 | 4.3535914439026975 |
Excel formula:
=EXP_GROWTH("exp_biphasic_association", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.049587290376612685;1.7933284068008737;3.061975136237548;3.9431458463858697;4.3535914439026975})
Expected output:
| y0 | A1 | t1 | A2 | t2 |
|---|---|---|---|---|
| 0.01992 | 1.747 | 4.947 | 3.724 | 4.947 |
Example 7: Demo case 7
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_single_phase_growth | 0.01 | 2.8499898323607833 |
| 2.0075 | 4.066060082367748 | |
| 4.005 | 6.201240348543285 | |
| 6.0024999999999995 | 9.316828126451941 | |
| 8 | 13.36219754582273 |
Excel formula:
=EXP_GROWTH("exp_single_phase_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {2.8499898323607833;4.066060082367748;6.201240348543285;9.316828126451941;13.36219754582273})
Expected output:
| y0 | A1 | t1 |
|---|---|---|
| -0.5557 | 3.309 | 5.557 |
| 0.6704 | 0.5714 | 0.5013 |
Example 8: Demo case 8
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_dual_phase_growth | 0 | 21 |
| 0.5 | 28.433751 | |
| 1 | 40.175443 | |
| 1.5 | 58.96562 | |
| 2 | 89.316983 | |
| 2.5 | 138.663431 | |
| 3 | 219.254438 | |
| 3.5 | 351.278294 | |
| 4 | 568.011369 |
Excel formula:
=EXP_GROWTH("exp_dual_phase_growth", {0;0.5;1;1.5;2;2.5;3;3.5;4}, {21;28.433751;40.175443;58.96562;89.316983;138.663431;219.254438;351.278294;568.011369})
Expected output:
| y0 | A1 | t1 | A2 | t2 |
|---|---|---|---|---|
| 2 | 10 | 1 | 9 | 5 |
| 0.00002545 | 0.000001007 | 1.667e-8 | 0.00002438 | 0.00001477 |
Example 9: Demo case 9
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_triple_phase_growth | 0 | 1.4 |
| 0.5 | 2.208792 | |
| 1 | 4.229361 | |
| 1.5 | 9.499059 | |
| 2 | 23.544291 | |
| 2.5 | 61.372562 | |
| 3 | 163.75968 |
Excel formula:
=EXP_GROWTH("exp_triple_phase_growth", {0;0.5;1;1.5;2;2.5;3}, {1.4;2.208792;4.229361;9.499059;23.544291;61.372562;163.75968})
Expected output:
"non-error"
Example 10: Demo case 10
Inputs:
| exp_growth_model | xdata | ydata |
|---|---|---|
| exp_delayed_onset_growth | 0.01 | 2.8499898323607833 |
| 2.0075 | 4.066060082367748 | |
| 4.005 | 6.201240348543285 | |
| 6.0024999999999995 | 9.316828126451941 | |
| 8 | 13.36219754582273 |
Excel formula:
=EXP_GROWTH("exp_delayed_onset_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {2.8499898323607833;4.066060082367748;6.201240348543285;9.316828126451941;13.36219754582273})
Expected output:
"non-error"
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def exp_growth(xdata, ydata, exp_growth_model):
"""
Fits exponential growth models to data using scipy.optimize.curve_fit.
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
exp_growth_model (str): The exp_growth_model value Valid options: Exp Asymptotic Saturation, Exp Power Base Saturation, Exp Geometric Growth Base, Exp Growth Standard, Exp Log Linear Form, Exp Biphasic Association, Exp Single Phase Growth, Exp Dual Phase Growth, Exp Triple Phase Growth, Exp Delayed Onset Growth.
Returns:
list[list]: A 2D list where the first row contains parameter names, the second row contains fitted parameter values, and an optional third row contains standard errors (when covariance estimation succeeds). Returns an error string on failure.
"""
def _validate_data(xdata, ydata):
"""Validate and convert both xdata and ydata to numpy arrays."""
x_vals = []
y_vals = []
for name, arg, vals_list in [("xdata", xdata, x_vals), ("ydata", ydata, y_vals)]:
if not isinstance(arg, list) or len(arg) < 2:
raise ValueError(f"{name} must be a 2D list with at least two rows")
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_list.append(float(row[0]))
except (ValueError, TypeError, IndexError) as e:
raise ValueError(f"{name} row {i} contains non-numeric value")
x_arr = np.asarray(x_vals, dtype=np.float64)
y_arr = np.asarray(y_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 = {
'exp_asymptotic_saturation': {
'params': ['A'],
'model': lambda x, A: 1.0 - np.exp(-A * x),
'guess': lambda xa, ya: (1.0,),
'bounds': (0.0, np.inf),
},
'exp_power_base_saturation': {
'params': ['B'],
'model': lambda x, B: 1.0 - np.power(B, x),
'guess': lambda xa, ya: (0.5,),
'bounds': (0.0, 1.0),
},
'exp_geometric_growth_base': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * np.power(b, x),
'guess': lambda xa, ya: (float(np.max(ya) if len(ya) > 0 and np.max(ya) != 0 else 1.0), 1.1),
'bounds': (0.0, np.inf),
},
'exp_growth_standard': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * np.exp(b * x),
'guess': lambda xa, ya: (float(np.max(ya) if len(ya) > 0 and np.max(ya) != 0 else 1.0), 0.1),
'bounds': ([0.0, -np.inf], np.inf),
},
'exp_log_linear_form': {
'params': ['a', 'b'],
'model': lambda x, a, b: np.exp(a + b * x),
'guess': lambda xa, ya: (float(np.log(np.max(ya)) if len(ya) > 0 and np.all(ya > 0) and np.max(ya) > 0 else 0.0), 0.1),
},
'exp_biphasic_association': {
'params': ['y0', 'A1', 't1', 'A2', 't2'],
'model': lambda x, y0, A1, t1, A2, t2: y0 + A1 * (1 - np.exp(-x / t1)) + A2 * (1 - np.exp(-x / t2)),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) / 2 if np.ptp(ya) != 0 else 0.5), 1.0, float(np.ptp(ya) / 2 if np.ptp(ya) != 0 else 0.5), 5.0),
'bounds': ([-np.inf, -np.inf, 0.0, -np.inf, 0.0], np.inf),
},
'exp_single_phase_growth': {
'params': ['y0', 'A1', 't1'],
'model': lambda x, y0, A1, t1: y0 + A1 * np.exp(x / t1),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 1.0),
},
'exp_dual_phase_growth': {
'params': ['y0', 'A1', 't1', 'A2', 't2'],
'model': lambda x, y0, A1, t1, A2, t2: y0 + A1 * np.exp(x / t1) + A2 * np.exp(x / t2),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 1.0, float(np.ptp(ya) if np.ptp(ya) != 0 else 0.5), 5.0),
},
'exp_triple_phase_growth': {
'params': ['y0', 'A1', 't1', 'A2', 't2', 'A3', 't3'],
'model': lambda x, y0, A1, t1, A2, t2, A3, t3: y0 + A1 * np.exp(x / t1) + A2 * np.exp(x / t2) + A3 * np.exp(x / t3),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 0.5, float(np.ptp(ya) if np.ptp(ya) != 0 else 0.5), 2.0, float(np.ptp(ya) if np.ptp(ya) != 0 else 0.25), 5.0),
},
'exp_delayed_onset_growth': {
'params': ['y0', 'x0', 'A1', 't1'],
'model': lambda x, y0, x0, A1, t1: y0 + A1 * np.exp((x - x0) / t1),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.min(xa)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 1.0),
'bounds': ([-np.inf, -np.inf, -np.inf, 0.0], np.inf),
}
}
# Validate and normalize model parameter
if not isinstance(exp_growth_model, str):
return "exp_growth_model must be a string"
exp_growth_model = exp_growth_model.strip()
if exp_growth_model not in models:
return f"Invalid model: '{exp_growth_model}'. Valid models are: {', '.join(sorted(models.keys()))}"
model_info = models[exp_growth_model]
# Validate and convert input data
try:
x_arr, y_arr = _validate_data(xdata, ydata)
except ValueError as e:
return f"Data validation error: {str(e)}"
# Perform curve fitting
try:
p0 = model_info['guess'](x_arr, y_arr)
bounds = model_info.get('bounds', (-np.inf, np.inf))
# Call curve_fit with or without bounds
if 'bounds' in model_info:
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)
else:
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)
# Convert fitted parameters to float and validate
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"Parameter estimation error: {str(e)}"
except RuntimeError as e:
return f"Curve fitting failed to converge: {str(e)}"
except Exception as e:
return f"Curve fitting error: {str(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]