POLY_BASIC
Overview
The POLY_BASIC function fits standard polynomial models to data using non-linear least squares regression. It provides eight commonly used polynomial models ranging from a simple horizontal baseline to a ninth-order polynomial, making it suitable for a wide variety of curve fitting applications.
This function leverages scipy.optimize.curve_fit from the SciPy library, which uses the Levenberg-Marquardt algorithm to minimize the sum of squared residuals between the model and observed data. The underlying implementation is provided through the MINPACK Fortran library, a well-established numerical optimization package.
The available polynomial models include:
- Horizontal Baseline: A constant value y = y_0
- Linear Slope Intercept: y = A + Bx
- Quadratic Parabola: y = A + Bx + Cx^2
- Cubic Polynomial: y = A + Bx + Cx^2 + Dx^3
- Fourth/Fifth/Ninth Order Polynomials: Higher-degree polynomials for complex curves
- Linear Through X Intercept: y = a(x - b), where b is the x-intercept
For a polynomial of degree n, the general form is:
y = \sum_{i=0}^{n} a_i x^i = a_0 + a_1 x + a_2 x^2 + \cdots + a_n x^n
The function returns fitted parameter values along with their standard errors, which are computed from the covariance matrix of the parameter estimates. The standard error for each parameter is derived as \sigma_i = \sqrt{\text{diag}(\mathbf{C})_i}, where \mathbf{C} is the covariance matrix. Note that these error estimates assume the model is approximately linear near the optimal solution; for highly nonlinear models, the uncertainty estimates may be less accurate.
When selecting a polynomial order, consider that higher-degree polynomials can capture more complex patterns but may lead to overfitting on noisy data. A good practice is to start with lower-order polynomials and increase complexity only if the fit quality is insufficient. For more information on polynomial curve fitting and least squares methods, see the NumPy polyfit documentation and the SciPy least_squares documentation.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=POLY_BASIC(xdata, ydata, poly_basic_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valuepoly_basic_model(str, required): The poly_basic_model value
Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.
Examples
Example 1: Horizontal baseline constant fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| horizontal_baseline | 0.1 | 0 |
| 1.325 | 0 | |
| 2.55 | 0 | |
| 3.775 | 0 | |
| 5 | 0 |
Excel formula:
=POLY_BASIC("horizontal_baseline", {0.1;1.325;2.55;3.775;5}, {0;0;0;0;0})
Expected output:
| y0 |
|---|
| 0 |
| 0 |
Example 2: Linear slope-intercept fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| linear_slope_intercept | 0.1 | 2.9001769517921048 |
| 1.325 | 4.128674639240271 | |
| 2.55 | 5.486408315144006 | |
| 3.775 | 6.852272017107344 | |
| 5 | 7.978703373645173 |
Excel formula:
=POLY_BASIC("linear_slope_intercept", {0.1;1.325;2.55;3.775;5}, {2.9001769517921048;4.128674639240271;5.486408315144006;6.852272017107344;7.978703373645173})
Expected output:
| A | B |
|---|---|
| 2.788 | 1.051 |
| 0.05913 | 0.01918 |
Example 3: Quadratic parabola fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| quadratic_parabola | 0.1 | 3.393939102193672 |
| 1.325 | 8.311251676273942 | |
| 2.55 | 17.539630409846307 | |
| 3.775 | 30.939801209148083 | |
| 5 | 52.11736115916023 |
Excel formula:
=POLY_BASIC("quadratic_parabola", {0.1;1.325;2.55;3.775;5}, {3.393939102193672;8.311251676273942;17.539630409846307;30.939801209148083;52.11736115916023})
Expected output:
| A | B | C |
|---|---|---|
| 3.58 | 0.8948 | 1.747 |
| 0.8464 | 0.7906 | 0.1491 |
Example 4: Cubic polynomial fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| cubic_polynomial | 0.1 | 10.311749963977787 |
| 1.325 | 34.61031843830554 | |
| 2.55 | 110.9897726101281 | |
| 3.775 | 293.1639187614614 | |
| 5 | 688.2374981176258 |
Excel formula:
=POLY_BASIC("cubic_polynomial", {0.1;1.325;2.55;3.775;5}, {10.311749963977787;34.61031843830554;110.9897726101281;293.1639187614614;688.2374981176258})
Expected output:
| A | B | C | D |
|---|---|---|---|
| 6.585 | 31.11 | -15.44 | 7.29 |
| 7.232 | 14.49 | 7.09 | 0.9148 |
Example 5: Fourth order polynomial fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| fourth_order_polynomial | 0.1 | 23.145744352965757 |
| 1.325 | 20.50260900857478 | |
| 2.55 | 215.71661385437147 | |
| 3.775 | 820.3314595740266 | |
| 5 | 2138.279376640648 |
Excel formula:
=POLY_BASIC("fourth_order_polynomial", {0.1;1.325;2.55;3.775;5}, {23.145744352965757;20.50260900857478;215.71661385437147;820.3314595740266;2138.279376640648})
Expected output:
| A0 | A1 | A2 | A3 | A4 |
|---|---|---|---|---|
| 27.8 | -48.75 | 21.78 | 5.931 | 1.709 |
Example 6: Fifth order polynomial fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| fifth_order_polynomial | -2 | -7.7 |
| -1.5 | -0.56875 | |
| -1 | 1.4 | |
| -0.5 | 1.1875 | |
| 0 | 0.5 | |
| 0.5 | 0.14375 | |
| 1 | 0.4 | |
| 1.5 | 1.4 | |
| 2 | 3.5 |
Excel formula:
=POLY_BASIC("fifth_order_polynomial", {-2;-1.5;-1;-0.5;0;0.5;1;1.5;2}, {-7.7;-0.56875;1.4;1.1875;0.5;0.14375;0.4;1.4;3.5})
Expected output:
| A0 | A1 | A2 | A3 | A4 | A5 |
|---|---|---|---|---|---|
| 0.5 | -1.2 | 0.75 | 0.6 | -0.35 | 0.1 |
| 7.281e-16 | 1.459e-15 | 9.993e-16 | 1.307e-15 | 2.351e-16 | 2.501e-16 |
Example 7: Ninth order polynomial fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| ninth_order_polynomial | -1.5 | 6.74 |
| -1.2 | 2.987673 | |
| -0.9 | 1.654173 | |
| -0.6 | 1.145836 | |
| -0.3 | 0.919425 | |
| 0 | 0.8 | |
| 0.3 | 0.728117 | |
| 0.6 | 0.680883 | |
| 0.9 | 0.648897 | |
| 1.2 | 0.617295 | |
| 1.5 | 0.493789 | |
| 1.8 | -0.150114 |
Excel formula:
=POLY_BASIC("ninth_order_polynomial", {-1.5;-1.2;-0.9;-0.6;-0.3;0;0.3;0.6;0.9;1.2;1.5;1.8}, {6.74;2.987673;1.654173;1.145836;0.919425;0.8;0.728117;0.680883;0.648897;0.617295;0.493789;-0.150114})
Expected output:
| a0 | a1 | a2 | a3 | a4 | a5 | a6 | a7 | a8 | a9 |
|---|---|---|---|---|---|---|---|---|---|
| 0.8 | -0.3 | 0.25 | -0.2 | 0.15 | -0.1 | 0.075 | -0.05 | 0.025 | -0.01 |
| 2.981e-7 | 0.00000107 | 0.000002894 | 0.000004644 | 0.000006643 | 0.000006711 | 0.000005025 | 0.000003922 | 0.000001167 | 7.908e-7 |
Example 8: Linear through x-intercept fitting
Inputs:
| poly_basic_model | xdata | ydata |
|---|---|---|
| linear_through_x_intercept | 0.1 | -2.4941794119730596 |
| 1.325 | 0.7233145313435664 | |
| 2.55 | 4.279283682520016 | |
| 3.775 | 7.856545759090661 | |
| 5 | 10.806723121451643 |
Excel formula:
=POLY_BASIC("linear_through_x_intercept", {0.1;1.325;2.55;3.775;5}, {-2.4941794119730596;0.7233145313435664;4.279283682520016;7.856545759090661;10.806723121451643})
Expected output:
| a | b |
|---|---|
| 2.754 | 1.012 |
| 0.05024 | 0.04226 |
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def poly_basic(xdata, ydata, poly_basic_model):
"""
Fits poly_basic 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
poly_basic_model (str): The poly_basic_model value Valid options: Horizontal Baseline, Linear Slope Intercept, Quadratic Parabola, Cubic Polynomial, Fourth Order Polynomial, Fifth Order Polynomial, Ninth Order Polynomial, Linear Through X Intercept.
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 = {
'horizontal_baseline': {
'params': ['y0'],
'model': lambda x, y0: np.full_like(x, y0, dtype=float),
'guess': lambda xa, ya: (float(np.mean(ya)),),
},
'linear_slope_intercept': {
'params': ['A', 'B'],
'model': lambda x, A, B: A + B * x,
'guess': lambda xa, ya: (float(np.mean(ya)), float(np.polyfit(xa, ya, 1)[0]) if xa.size > 1 else 0.0),
},
'quadratic_parabola': {
'params': ['A', 'B', 'C'],
'model': lambda x, A, B, C: A + B * x + C * np.square(x),
'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0),
},
'cubic_polynomial': {
'params': ['A', 'B', 'C', 'D'],
'model': lambda x, A, B, C, D: ((D * x + C) * x + B) * x + A,
'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0, 0.0),
},
'fourth_order_polynomial': {
'params': ['A0', 'A1', 'A2', 'A3', 'A4'],
'model': lambda x, A0, A1, A2, A3, A4: (((A4 * x + A3) * x + A2) * x + A1) * x + A0,
'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0, 0.0, 0.0),
},
'fifth_order_polynomial': {
'params': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5'],
'model': lambda x, A0, A1, A2, A3, A4, A5: ((((A5 * x + A4) * x + A3) * x + A2) * x + A1) * x + A0,
'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, 0.0, 0.0, 0.0, 0.0),
},
'ninth_order_polynomial': {
'params': ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9'],
'model': lambda x, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9: np.polyval([a9, a8, a7, a6, a5, a4, a3, a2, a1, a0], x),
'guess': lambda xa, ya: (float(np.mean(ya)),) + (0.0,) * 9,
},
'linear_through_x_intercept': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * (x - b),
'guess': lambda xa, ya: (float(np.polyfit(xa, ya, 1)[0]) if xa.size > 1 else 1.0, float(np.mean(xa))),
}
}
# Validate model parameter
if poly_basic_model not in models:
return f"Invalid model: {str(poly_basic_model)}. Valid models are: {', '.join(models.keys())}"
model_info = models[poly_basic_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]