GROWTH_POWER
Overview
The GROWTH_POWER function fits a collection of power-law and power-based growth models to observed data using non-linear least squares optimization. Power laws describe functional relationships where one quantity varies as a power of another, exhibiting scale invariance—a property that appears throughout physics, biology, economics, and many other disciplines. These models are particularly valuable for describing allometric relationships, where biological or physical quantities scale with size according to characteristic exponents.
This implementation leverages scipy.optimize.curve_fit from the SciPy library to perform non-linear least squares fitting. The function minimizes the sum of squared residuals between the observed data and the model predictions using the Levenberg-Marquardt algorithm (for unconstrained problems) or trust region reflective methods (when bounds are specified).
The function supports nine distinct power-based growth models:
- Allometric Power Scaling: y = a \cdot x^b, the classic power law used in biological scaling studies such as Kleiber’s law relating metabolic rate to body mass
- Power Law With Baseline: y = a + b \cdot x^c, adding a constant offset to the standard power law
- Temperature Dependent Power: y = a \cdot (x - b)^c, incorporating a threshold or shift parameter
- Bounded Power Growth: y = a \cdot (1 - x^{-b}), approaching an asymptote as x increases
- Unit Offset Power Growth: y = a \cdot (1 + x)^b, useful when x values start near zero
- Asymptotic Cumulative Power: y = 1 - (1 + ax)^{-b}, modeling saturation effects
- Simple Allometric Scaling: y = x^A, a single-parameter power function
- Monomolecular Growth: y = A \cdot (1 - e^{-k(x - x_c)}) for x \geq x_c, combining exponential saturation with a threshold
- Monomolecular Asymptotic: y = A_1 - A_2 \cdot e^{-kx}, modeling approach to a limiting value
The function returns the fitted parameter values along with their standard errors, derived from the covariance matrix of the fit. For more information on the underlying optimization algorithm, see the SciPy curve_fit documentation and the SciPy GitHub repository.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=GROWTH_POWER(xdata, ydata, growth_power_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valuegrowth_power_model(str, required): The growth_power_model value
Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.
Examples
Example 1: Demo case 1
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| allometric_power_scaling | 0.1 | 0.374064608026873 |
| 1.3250000000000002 | 3.6594822459396226 | |
| 2.5500000000000003 | 7.516690772388122 | |
| 3.7750000000000004 | 11.489638331540139 | |
| 5 | 14.841430473579063 |
Excel formula:
=GROWTH_POWER("allometric_power_scaling", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.374064608026873;3.6594822459396226;7.516690772388122;11.489638331540139;14.841430473579063})
Expected output:
| a | b |
|---|---|
| 2.878 | 1.025 |
| 0.1311 | 0.03201 |
Example 2: Demo case 2
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| power_law_with_baseline | 0.1 | 2.9556927955786785 |
| 1.3250000000000002 | 4.861862952838822 | |
| 2.5500000000000003 | 8.415807793967966 | |
| 3.7750000000000004 | 13.229237782671369 | |
| 5 | 20.43619182517326 |
Excel formula:
=GROWTH_POWER("power_law_with_baseline", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.9556927955786785;4.861862952838822;8.415807793967966;13.229237782671369;20.43619182517326})
Expected output:
| a | b | c |
|---|---|---|
| 3.078 | 0.9783 | 1.783 |
| 0.2649 | 0.1482 | 0.09111 |
Example 3: Demo case 3
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| temperature_dependent_power | 0.6 | 0.17513616236186072 |
| 1.1 | 1.2998244444701483 | |
| 1.6 | 3.0790577159743244 | |
| 2.1 | 5.375837224784517 | |
| 2.6 | 8.120899301991956 |
Excel formula:
=GROWTH_POWER("temperature_dependent_power", {0.6;1.1;1.6;2.1;2.6}, {0.17513616236186072;1.2998244444701483;3.0790577159743244;5.375837224784517;8.120899301991956})
Expected output:
| a | b | c |
|---|---|---|
| 2.3 | 0.4 | 1.6 |
| 4.945e-15 | 1.312e-15 | 1.612e-15 |
Example 4: Demo case 4
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| bounded_power_growth | 0.1 | -27.80927559573639 |
| 1.3250000000000002 | 0.6210685038833452 | |
| 2.5500000000000003 | 2.1071516786522095 | |
| 3.7750000000000004 | 2.9766459143041493 | |
| 5 | 2.102880439067675 |
Excel formula:
=GROWTH_POWER("bounded_power_growth", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {-27.80927559573639;0.6210685038833452;2.1071516786522095;2.9766459143041493;2.102880439067675})
Expected output:
| a | b |
|---|---|
| 3.336 | 0.9702 |
| 0.4627 | 0.05437 |
Example 5: Demo case 5
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| unit_offset_power_growth | 0.1 | 3.171323085774019 |
| 1.3250000000000002 | 6.632539056851305 | |
| 2.5500000000000003 | 10.572892291348802 | |
| 3.7750000000000004 | 14.603251594729274 | |
| 5 | 17.984272980322157 |
Excel formula:
=GROWTH_POWER("unit_offset_power_growth", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {3.171323085774019;6.632539056851305;10.572892291348802;14.603251594729274;17.984272980322157})
Expected output:
| a | b |
|---|---|
| 2.848 | 1.034 |
| 0.1123 | 0.02484 |
Example 6: Demo case 6
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| asymptotic_cumulative_power | 0.1 | 0.2318461919718201 |
| 1.3250000000000002 | 0.7987089849163287 | |
| 2.5500000000000003 | 0.8962519472881344 | |
| 3.7750000000000004 | 0.9427099539411663 | |
| 5 | 0.9375852671398648 |
Excel formula:
=GROWTH_POWER("asymptotic_cumulative_power", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.2318461919718201;0.7987089849163287;0.8962519472881344;0.9427099539411663;0.9375852671398648})
Expected output:
| a | b |
|---|---|
| 2.697 | 1.086 |
| 0.3602 | 0.07891 |
Example 7: Demo case 7
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| simple_allometric_scaling | 0.1 | 0.7771971538788792 |
| 1.3250000000000002 | 1.9523277107451198 | |
| 2.5500000000000003 | 14.132665275784085 | |
| 3.7750000000000004 | 40.971760227468735 | |
| 5 | 83.2270020382099 |
Excel formula:
=GROWTH_POWER("simple_allometric_scaling", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.7771971538788792;1.9523277107451198;14.132665275784085;40.971760227468735;83.2270020382099})
Expected output:
| A |
|---|
| 2.754 |
| 0.009169 |
Example 8: Demo case 8
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| monomolecular_growth | 0.01 | 0.03115493403185241 |
| 2.0075 | 2.0664014107726754 | |
| 4.005 | 2.7937494899027224 | |
| 6.0024999999999995 | 2.7074270198804915 | |
| 8 | 2.7719490878826014 |
Excel formula:
=GROWTH_POWER("monomolecular_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.03115493403185241;2.0664014107726754;2.7937494899027224;2.7074270198804915;2.7719490878826014})
Expected output:
| A | xc | k |
|---|---|---|
| 2.758 | 1.853 | 8.94 |
| 0.03536 | 49570 | 2864000 |
Example 9: Demo case 9
Inputs:
| growth_power_model | xdata | ydata |
|---|---|---|
| monomolecular_asymptotic | 0.01 | 0.16140033730483777 |
| 2.0075 | 2.7426612620791997 | |
| 4.005 | 2.783868226460633 | |
| 6.0024999999999995 | 2.8296406358000055 | |
| 8 | 2.737755904088638 |
Excel formula:
=GROWTH_POWER("monomolecular_asymptotic", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.16140033730483777;2.7426612620791997;2.783868226460633;2.8296406358000055;2.737755904088638})
Expected output:
| A1 | A2 | k |
|---|---|---|
| 2.784 | 2.678 | 2.079 |
| 0.02682 | 0.05281 | 0.6481 |
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def growth_power(xdata, ydata, growth_power_model):
"""
Fits growth_power 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
growth_power_model (str): The growth_power_model value Valid options: Allometric Power Scaling, Power Law With Baseline, Temperature Dependent Power, Bounded Power Growth, Unit Offset Power Growth, Asymptotic Cumulative Power, Simple Allometric Scaling, Monomolecular Growth, Monomolecular Asymptotic.
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 = {
'allometric_power_scaling': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * np.power(x, b),
'guess': lambda xa, ya: (float(np.max(ya)), 1.0),
},
'power_law_with_baseline': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: a + b * np.power(x, c),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 1.0),
},
'temperature_dependent_power': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: a * np.power(x - b, c),
'guess': lambda xa, ya: (float(np.ptp(ya) if np.ptp(ya) else 1.0), float(np.min(xa)), 1.0),
},
'bounded_power_growth': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * (1.0 - np.power(np.clip(x, 1e-9, None), -b)),
'guess': lambda xa, ya: (float(np.max(ya) if np.max(ya) else 1.0), 1.0),
},
'unit_offset_power_growth': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * np.power(1.0 + x, b),
'guess': lambda xa, ya: (float(np.max(ya) if np.max(ya) else 1.0), 1.0),
},
'asymptotic_cumulative_power': {
'params': ['a', 'b'],
'model': lambda x, a, b: 1.0 - np.power(1.0 + a * x, -b),
'guess': lambda xa, ya: (0.5, 1.0),
},
'simple_allometric_scaling': {
'params': ['A'],
'model': lambda x, A: np.power(np.clip(x, 1e-9, None), A),
'guess': lambda xa, ya: (1.0,),
},
'monomolecular_growth': {
'params': ['A', 'xc', 'k'],
'model': lambda x, A, xc, k: np.where(x >= xc, A * (1.0 - np.exp(-k * (x - xc))), 0.0),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.median(xa)), 0.5),
'bounds': ([0.0, -np.inf, 0.0], np.inf),
},
'monomolecular_asymptotic': {
'params': ['A1', 'A2', 'k'],
'model': lambda x, A1, A2, k: A1 - A2 * np.exp(-k * x),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 0.5),
'bounds': (0.0, np.inf),
}
}
# Validate model parameter
if growth_power_model not in models:
return f"Invalid model: {str(growth_power_model)}. Valid models are: {', '.join(models.keys())}"
model_info = models[growth_power_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]