UNIVARIATE_SPLINE
Overview
The UNIVARIATE_SPLINE function fits a one-dimensional smoothing spline to a set of data points and evaluates the spline at new x-coordinates. This technique is widely used in data analysis, signal processing, and scientific computing to create smooth curves that approximate noisy data while controlling the trade-off between fidelity to the original points and overall smoothness.
This implementation wraps SciPy’s UnivariateSpline class, which is based on the FITPACK library algorithms developed by Paul Dierckx. The underlying methodology is described in Dierckx’s foundational work, Curve and Surface Fitting with Splines (Oxford University Press, 1993). The SciPy library provides a robust Python interface for scientific computing, with source code available on GitHub.
Smoothing splines differ from interpolating splines in a critical way: rather than passing exactly through every data point, they balance data fidelity against smoothness. The spline is constructed by minimizing a weighted sum of squared residuals subject to a smoothing constraint:
\sum_{i=1}^{n} w_i \cdot (y_i - s(x_i))^2 \leq S
where w_i are optional weights, s(x) is the spline function, and S is the smoothing factor. When S = 0, the spline interpolates exactly through all data points. Larger values of S produce smoother curves with greater deviation from the original data.
The degree parameter k controls the polynomial degree of the spline segments (default is k=3 for cubic splines). Cubic splines are popular because they provide smooth first and second derivatives while remaining computationally efficient. The function supports degrees from 1 (linear) to 5 (quintic).
The extrapolation mode determines behavior when evaluating the spline outside the original data range. Options include extrapolating based on the fitted spline, returning zeros, raising an error, or returning the boundary value.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=UNIVARIATE_SPLINE(x, y, x_new, w, k, s, ext)
x(list[list], required): The x-coordinates of the data pointsy(list[list], required): The y-coordinates of the data pointsx_new(list[list], required): The x-coordinates at which to evaluate the splinew(list[list], optional, default: null): Weights for spline fittingk(int, optional, default: 3): Degree of the splines(float, optional, default: null): Smoothing factorext(str, optional, default: “extrapolate”): Extrapolation mode
Returns (list[list]): 2D list (column vector) of interpolated values, or error message (str).
Examples
Example 1: Demo case 1
Inputs:
| x | y | x_new |
|---|---|---|
| 0 | 0 | 0.5 |
| 1 | 1 | 1.5 |
| 2 | 2 | 2.5 |
| 3 | 3 |
Excel formula:
=UNIVARIATE_SPLINE({0;1;2;3}, {0;1;2;3}, {0.5;1.5;2.5})
Expected output:
| Result |
|---|
| 0.5 |
| 1.5 |
| 2.5 |
Example 2: Demo case 2
Inputs:
| x | y | x_new | w | k |
|---|---|---|---|---|
| 0 | 0 | 0.5 | 1 | 3 |
| 1 | 1 | 1.5 | 1 | |
| 2 | 4 | 2.5 | 1 | |
| 3 | 9 | 1 | ||
| 4 | 16 | 1 |
Excel formula:
=UNIVARIATE_SPLINE({0;1;2;3;4}, {0;1;4;9;16}, {0.5;1.5;2.5}, {1;1;1;1;1}, 3)
Expected output:
| Result |
|---|
| 0.25 |
| 2.25 |
| 6.25 |
Example 3: Demo case 3
Inputs:
| x | y | x_new | k | s |
|---|---|---|---|---|
| 0 | 0 | 0.5 | 3 | 0 |
| 1 | 1 | 1.5 | ||
| 2 | 8 | 2.5 | ||
| 3 | 27 | 3.5 | ||
| 4 | 64 |
Excel formula:
=UNIVARIATE_SPLINE({0;1;2;3;4}, {0;1;8;27;64}, {0.5;1.5;2.5;3.5}, 3, 0)
Expected output:
| Result |
|---|
| 0.125 |
| 3.375 |
| 15.62 |
| 42.88 |
Example 4: Demo case 4
Inputs:
| x | y | x_new | w | k | s | ext |
|---|---|---|---|---|---|---|
| 1 | 2 | 1.5 | 1 | 1 | 0 | extrapolate |
| 2 | 4 | 2.5 | 1 | |||
| 3 | 6 | 3.5 | 1 | |||
| 4 | 8 | 1 | ||||
| 5 | 10 | 1 |
Excel formula:
=UNIVARIATE_SPLINE({1;2;3;4;5}, {2;4;6;8;10}, {1.5;2.5;3.5}, {1;1;1;1;1}, 1, 0, "extrapolate")
Expected output:
| Result |
|---|
| 3 |
| 5 |
| 7 |
Python Code
import math
from scipy.interpolate import UnivariateSpline as scipy_UnivariateSpline
def univariate_spline(x, y, x_new, w=None, k=3, s=None, ext='extrapolate'):
"""
1-D smoothing spline fit to data.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.UnivariateSpline.html
This example function is provided as-is without any representation of accuracy.
Args:
x (list[list]): The x-coordinates of the data points
y (list[list]): The y-coordinates of the data points
x_new (list[list]): The x-coordinates at which to evaluate the spline
w (list[list], optional): Weights for spline fitting Default is None.
k (int, optional): Degree of the spline Default is 3.
s (float, optional): Smoothing factor Default is None.
ext (str, optional): Extrapolation mode Valid options: extrapolate, zeros, raise, const. Default is 'extrapolate'.
Returns:
list[list]: 2D list (column vector) of interpolated values, or error message (str).
"""
def to2d(val):
return [[val]] if not isinstance(val, list) else val
def flatten(arr):
flat = []
for row in arr:
if isinstance(row, list):
flat.extend(row)
else:
flat.append(row)
return flat
def _convert_float(value, name):
try:
converted = float(value)
except Exception:
return f"Invalid input: {name} must be a number."
if math.isnan(converted) or math.isinf(converted):
return f"Invalid input: {name} must be finite."
return converted
# Normalize inputs to 2D lists
x = to2d(x)
y = to2d(y)
x_new = to2d(x_new)
# Flatten 2D lists to 1D
try:
x_flat = flatten(x)
y_flat = flatten(y)
x_new_flat = flatten(x_new)
except Exception:
return "Invalid input: x, y, and x_new must be valid 2D lists."
# Validate that x and y have the same length
if len(x_flat) != len(y_flat):
return "Invalid input: x and y must have the same length."
if len(x_flat) == 0:
return "Invalid input: x and y must not be empty."
# Validate numeric values in x, y, x_new
for i, val in enumerate(x_flat):
converted = _convert_float(val, f"x[{i}]")
if isinstance(converted, str):
return converted
x_flat[i] = converted
for i, val in enumerate(y_flat):
converted = _convert_float(val, f"y[{i}]")
if isinstance(converted, str):
return converted
y_flat[i] = converted
for i, val in enumerate(x_new_flat):
converted = _convert_float(val, f"x_new[{i}]")
if isinstance(converted, str):
return converted
x_new_flat[i] = converted
# Process weights if provided
w_flat = None
if w is not None:
w = to2d(w)
try:
w_flat = flatten(w)
except Exception:
return "Invalid input: w must be a valid 2D list."
if len(w_flat) != len(x_flat):
return "Invalid input: w must have the same length as x and y."
for i, val in enumerate(w_flat):
converted = _convert_float(val, f"w[{i}]")
if isinstance(converted, str):
return converted
if converted <= 0:
return f"Invalid input: all weights must be positive (w[{i}] = {converted})."
w_flat[i] = converted
# Sort data by x
# Zip x, y, and w (if present) together, sort, and unzip
try:
if w_flat is None:
combined = sorted(zip(x_flat, y_flat))
x_flat, y_flat = map(list, zip(*combined))
else:
combined = sorted(zip(x_flat, y_flat, w_flat))
x_flat, y_flat, w_flat = map(list, zip(*combined))
except Exception as e:
return f"Error sorting data: {e}"
# Validate k
if not isinstance(k, (int, float)):
return "Invalid input: k must be an integer."
k = int(k)
if k < 1 or k > 5:
return "Invalid input: k must be between 1 and 5."
if len(x_flat) <= k:
return f"Invalid input: number of data points ({len(x_flat)}) must be greater than k ({k})."
# Validate s
if s is not None:
converted_s = _convert_float(s, "s")
if isinstance(converted_s, str):
return converted_s
if converted_s < 0:
return "Invalid input: s must be non-negative."
s = converted_s
# Validate ext parameter
valid_ext = ["extrapolate", "zeros", "raise", "const"]
if not isinstance(ext, str):
return "Invalid input: ext must be a string."
if ext not in valid_ext:
return f"Invalid input: ext must be one of {valid_ext} (got '{ext}')."
# Map ext to numeric codes for scipy (0=extrapolate, 1=zeros, 2=raise, 3=const)
ext_code = {"extrapolate": 0, "zeros": 1, "raise": 2, "const": 3}[ext]
# Create and evaluate spline
try:
spline = scipy_UnivariateSpline(x_flat, y_flat, w=w_flat, k=k, s=s, ext=ext_code)
result = spline(x_new_flat)
except ValueError as error:
return f"Invalid input: {error}"
except Exception as exc:
return f"scipy.interpolate.UnivariateSpline error: {exc}"
# Convert result to 2D list (column vector)
try:
result_2d = [[float(val)] for val in result]
except Exception:
return "scipy.interpolate.UnivariateSpline error: unable to convert result to 2D list."
# Validate result values
for row in result_2d:
for val in row:
if math.isnan(val) or math.isinf(val):
return "scipy.interpolate.UnivariateSpline error: result contains non-finite values."
return result_2d