INTERP_UV_SPLINE
Overview
The INTERP_UV_SPLINE function performs one-dimensional interpolation using a univariate spline that passes exactly through all provided data points. This approach is ideal when the data is known precisely and the interpolated curve must honor every observation.
Spline interpolation is a form of piecewise polynomial interpolation where low-degree polynomials (typically cubic, degree 3) are fitted between consecutive data points. Unlike fitting a single high-degree polynomial to all data, splines avoid Runge’s phenomenon—unwanted oscillations that can occur at the edges of the interpolation interval. The term “spline” originates from flexible rulers historically used in shipbuilding and drafting to draw smooth curves through predefined points.
This implementation uses SciPy’s InterpolatedUnivariateSpline class, which internally relies on FITPACK routines for B-spline construction. The function fits a spline of degree k (default k = 3 for cubic splines) such that the spline passes through all data points with zero residual. The spline is constructed by solving a system of equations that ensures continuity of the function and its derivatives up to order k - 1 at each interior knot.
The degree parameter (k) controls the polynomial order of each spline segment. Common choices include:
- k = 1: Linear interpolation (piecewise linear)
- k = 3: Cubic spline (smooth curves with continuous first and second derivatives)
- k = 5: Quintic spline (even smoother, continuous through fourth derivative)
Optional weights allow emphasizing certain data points during spline fitting. The extrapolation mode (ext) determines behavior when evaluating the spline outside the original data range: the function can extrapolate, return zeros, raise an error, or return the boundary value.
For more details on spline theory, see the Wikipedia article on spline interpolation or the SciPy interpolation tutorial. The source code is available on GitHub.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=INTERP_UV_SPLINE(x, y, x_new, w, k, 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 smoothing splineext(str, optional, default: “extrapolate”): Extrapolation mode
Returns (list[list]): A 2D list containing the interpolated values, or an error message (str) if invalid.
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:
=INTERP_UV_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 | k |
|---|---|---|---|
| 0 | 0 | 1.5 | 3 |
| 1 | 1 | 2.5 | |
| 2 | 4 | ||
| 3 | 9 | ||
| 4 | 16 |
Excel formula:
=INTERP_UV_SPLINE({0;1;2;3;4}, {0;1;4;9;16}, {1.5;2.5}, 3)
Expected output:
| Result |
|---|
| 2.25 |
| 6.25 |
Example 3: Demo case 3
Inputs:
| x | y | x_new | w | k |
|---|---|---|---|---|
| 0 | 1 | 0.5 | 1 | 3 |
| 1 | 2 | 2 | 1 | |
| 2 | 3 | 3.5 | 2 | |
| 3 | 2.5 | 1 | ||
| 4 | 1.5 | 1 |
Excel formula:
=INTERP_UV_SPLINE({0;1;2;3;4}, {1;2;3;2.5;1.5}, {0.5;2;3.5}, {1;1;2;1;1}, 3)
Expected output:
| Result |
|---|
| 1.367 |
| 3 |
| 1.961 |
Example 4: Demo case 4
Inputs:
| x | y | x_new | k | ext |
|---|---|---|---|---|
| 1 | 1 | 0 | 2 | extrapolate |
| 2 | 4 | 2.5 | ||
| 3 | 9 | 5 | ||
| 4 | 16 |
Excel formula:
=INTERP_UV_SPLINE({1;2;3;4}, {1;4;9;16}, {0;2.5;5}, 2, "extrapolate")
Expected output:
| Result |
|---|
| 0 |
| 6.25 |
| 25 |
Python Code
import math
from scipy.interpolate import InterpolatedUnivariateSpline as scipy_InterpolatedUnivariateSpline
def interp_uv_spline(x, y, x_new, w=None, k=3, ext='extrapolate'):
"""
1-D interpolating spline for data.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.InterpolatedUnivariateSpline.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 smoothing spline Default is 3.
ext (str, optional): Extrapolation mode Valid options: extrapolate, zeros, raise, const. Default is 'extrapolate'.
Returns:
list[list]: A 2D list containing the interpolated values, or an error message (str) if invalid.
"""
def to2d(val):
"""Convert scalar to 2D list if needed."""
return [[val]] if not isinstance(val, list) else val
def flatten(arr):
"""Flatten a 2D list to 1D."""
return [item for sublist in arr for item in sublist]
def validate_numeric_2d_list(val, name):
"""Validate that a value is a 2D list of numbers."""
if not isinstance(val, list):
return f"Invalid input: {name} must be a 2D list."
if not val:
return f"Invalid input: {name} must not be empty."
for i, row in enumerate(val):
if not isinstance(row, list):
return f"Invalid input: {name} must be a 2D list."
if not row:
return f"Invalid input: {name} must not contain empty rows."
for j, item in enumerate(row):
if not isinstance(item, (int, float)):
return f"Invalid input: {name}[{i}][{j}] must be a number."
if math.isnan(item) or math.isinf(item):
return f"Invalid input: {name}[{i}][{j}] must be finite."
return None
# Normalize inputs to 2D lists
x = to2d(x)
y = to2d(y)
x_new = to2d(x_new)
# Validate x, y, x_new
error = validate_numeric_2d_list(x, "x")
if error:
return error
error = validate_numeric_2d_list(y, "y")
if error:
return error
error = validate_numeric_2d_list(x_new, "x_new")
if error:
return error
# Flatten input arrays
x_flat = flatten(x)
y_flat = flatten(y)
x_new_flat = flatten(x_new)
# Check that x and y have the same length
if len(x_flat) != len(y_flat):
return f"Invalid input: x and y must have the same length (got {len(x_flat)} and {len(y_flat)})."
# Check minimum data points for degree k
if len(x_flat) < k + 1:
return f"Invalid input: need at least {k + 1} data points for degree {k} spline (got {len(x_flat)})."
# Validate and process weights if provided
w_flat = None
if w is not None:
w = to2d(w)
error = validate_numeric_2d_list(w, "w")
if error:
return error
w_flat = flatten(w)
if len(w_flat) != len(x_flat):
return f"Invalid input: w must have the same length as x and y (got {len(w_flat)} and {len(x_flat)})."
# Check for non-positive weights
for i, weight in enumerate(w_flat):
if weight <= 0:
return f"Invalid input: all weights must be positive (w[{i}] = {weight})."
# Sort data by x to ensure increasing order required by InterpolatedUnivariateSpline
if w_flat is not None:
data = sorted(zip(x_flat, y_flat, w_flat), key=lambda p: p[0])
x_flat, y_flat, w_flat = zip(*data)
x_flat, y_flat, w_flat = list(x_flat), list(y_flat), list(w_flat)
else:
data = sorted(zip(x_flat, y_flat), key=lambda p: p[0])
x_flat, y_flat = zip(*data)
x_flat, y_flat = list(x_flat), list(y_flat)
# Validate k parameter
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."
# Validate ext parameter
valid_ext_names = ["extrapolate", "zeros", "raise", "const"]
if not isinstance(ext, str):
return f"Invalid input: ext must be a string (got {type(ext).__name__})."
if ext not in valid_ext_names:
return f"Invalid input: ext must be one of {valid_ext_names} (got '{ext}')."
# Create and evaluate spline
try:
spline = scipy_InterpolatedUnivariateSpline(x_flat, y_flat, w=w_flat, k=k, ext=ext)
result = spline(x_new_flat)
except ValueError as e:
return f"Invalid input: {e}"
except Exception as e:
return f"scipy.interpolate.InterpolatedUnivariateSpline error: {e}"
# Validate result
if not hasattr(result, "__iter__"):
return "scipy.interpolate.InterpolatedUnivariateSpline error: result is not iterable."
# Convert result to 2D list (column vector)
output = []
for val in result:
if not isinstance(val, (int, float)):
return "scipy.interpolate.InterpolatedUnivariateSpline error: non-numeric result."
val_float = float(val)
if math.isnan(val_float) or math.isinf(val_float):
return "scipy.interpolate.InterpolatedUnivariateSpline error: result contains non-finite values."
output.append([val_float])
return output