CUBIC_SPLINE
Overview
The CUBIC_SPLINE function performs cubic spline interpolation for a set of data points, returning interpolated values at specified query points. Cubic spline interpolation fits a piecewise cubic polynomial that is twice continuously differentiable, ensuring smooth transitions between data points. This method is widely used for smooth curve fitting in engineering, graphics, and data analysis. The underlying implementation uses SciPy’s CubicSpline class, which constructs the spline based on the provided data and boundary conditions.
The cubic spline is defined such that:
where the coefficients are determined to ensure and its first two derivatives are continuous at each .
For more details, see the SciPy CubicSpline documentation .
This function simplifies the interface by only supporting 1D data, a subset of boundary conditions (not-a-knot, clamped, natural, periodic), and does not expose the axis or extrapolate options. This example function is provided as-is without any representation of accuracy.
Usage
To use the function in Excel:
=CUBIC_SPLINE(x, y, x_new, [bc_type])x(2D list, required): Table with one column, the independent variable (must be strictly increasing and contain at least two rows).y(2D list, required): Table with one column, the dependent variable (same number of rows asx).x_new(scalar or 2D list, required): Either a single value or a table with one column (at least one row) specifying the points where the spline is evaluated.bc_type(string (enum), optional, default=“not-a-knot”): Boundary condition type. Valid options:"not-a-knot","clamped","natural","periodic".
The function returns a 2D list with one column, containing the interpolated values at each x_new point, or an error message (2D list of str) if the input is invalid.
Examples
Example 1: Interpolate a Sine Curve (Default Boundary)
Inputs:
| x | y | x_new |
|---|---|---|
| 0 | 0.0000 | 0.5 |
| 1 | 0.8415 | 1.5 |
| 2 | 0.9093 | 2.5 |
| 3 | 0.1411 | 3.5 |
| 4 | -0.7568 | 4.5 |
Excel formula:
=CUBIC_SPLINE({0;1;2;3;4}, {0;0.8415;0.9093;0.1411;-0.7568}, {0.5;1.5;2.5;3.5;4.5})Expected output:
| Interpolated |
|---|
| 0.502 |
| 0.988 |
| 0.598 |
| -0.348 |
| -0.974 |
Example 2: Natural Boundary Condition
Inputs:
| x | y | x_new | bc_type |
|---|---|---|---|
| 0 | 0.0000 | 0.5 | natural |
| 1 | 0.8415 | 1.5 | |
| 2 | 0.9093 | 2.5 | |
| 3 | 0.1411 | 3.5 | |
| 4 | -0.7568 | 4.5 |
Excel formula:
=CUBIC_SPLINE({0;1;2;3;4}, {0;0.8415;0.9093;0.1411;-0.7568}, {0.5;1.5;2.5;3.5;4.5}, "natural")Expected output:
| Interpolated |
|---|
| 0.477 |
| 0.997 |
| 0.586 |
| -0.312 |
| -1.202 |
Example 3: Clamped Boundary Condition
Inputs:
| x | y | x_new | bc_type |
|---|---|---|---|
| 0 | 0.0000 | 0.5 | clamped |
| 1 | 0.8415 | 1.5 | |
| 2 | 0.9093 | 2.5 | |
| 3 | 0.1411 | 3.5 | |
| 4 | -0.7568 | 4.5 |
Excel formula:
=CUBIC_SPLINE({0;1;2;3;4}, {0;0.8415;0.9093;0.1411;-0.7568}, {0.5;1.5;2.5;3.5;4.5}, "clamped")Expected output:
| Interpolated |
|---|
| 0.322 |
| 1.029 |
| 0.613 |
| -0.450 |
| -0.286 |
Example 4: Periodic Boundary Condition
Inputs:
| x | y | x_new | bc_type |
|---|---|---|---|
| 0 | 0 | 0.5 | periodic |
| 1 | 1 | 1.5 | |
| 2 | 0 | 2.5 | |
| 3 | -1 | 3.5 | |
| 4 | 0 |
Excel formula:
=CUBIC_SPLINE({0;1;2;3;4}, {0;1;0;-1;0}, {0.5;1.5;2.5;3.5}, "periodic")Expected output:
| Interpolated |
|---|
| 0.688 |
| 0.688 |
| -0.688 |
| -0.688 |
Python Code
import math
from typing import List, Union
from scipy.interpolate import CubicSpline as scipy_cubicspline
ExcelNumeric = Union[int, float, bool, str]
ExcelColumn = List[List[Union[float, str]]]
def cubic_spline(
x: Union[List[List[ExcelNumeric]], ExcelNumeric],
y: Union[List[List[ExcelNumeric]], ExcelNumeric],
x_new: Union[List[List[ExcelNumeric]], ExcelNumeric],
bc_type: str = "not-a-knot"
) -> ExcelColumn:
"""
Perform cubic spline interpolation for 1D data.
Args:
x: 2D list with at least two rows, one column, independent variable (strictly increasing).
y: 2D list with at least two rows, one column, dependent variable (same length as x).
x_new: Scalar or 2D list, one column, points to evaluate the spline at.
bc_type: Boundary condition type (default: "not-a-knot").
Returns:
2D list with one column of interpolated values, or 2D list of str if error.
This example function is provided as-is without any representation of accuracy.
"""
def _error(message: str) -> ExcelColumn:
return [[message]]
def _normalize_column(
data: Union[List[List[ExcelNumeric]], ExcelNumeric],
name: str,
min_rows: int
) -> Union[List[float], str]:
# Wrap scalars into a 2D list to standardize processing.
if isinstance(data, (int, float, bool, str)):
data = [[data]]
if not isinstance(data, list):
return f"Invalid input: {name} must be a 2D list with at least {min_rows} rows."
if len(data) < min_rows:
return f"Invalid input: {name} must be a 2D list with at least {min_rows} rows."
values: List[float] = []
for row in data:
if not isinstance(row, list) or not row:
return f"Invalid input: {name} must be a 2D list containing numeric values."
if len(row) != 1:
return f"Invalid input: {name} must be a 2D list with exactly one column."
try:
candidate = row[0]
except Exception:
return f"Invalid input: {name} must contain numeric values."
try:
value = float(candidate)
except Exception:
return f"Invalid input: {name} must contain numeric values."
if not math.isfinite(value):
return f"Invalid input: {name} must contain finite numbers."
values.append(value)
return values
def _normalize_x_new(
data: Union[List[List[ExcelNumeric]], ExcelNumeric]
) -> Union[List[float], str]:
# Accept scalar inputs or 2D lists and normalize to a list of floats.
if isinstance(data, (int, float, bool, str)):
try:
value = float(data)
except Exception:
return "Invalid input: x_new must contain numeric values."
if not math.isfinite(value):
return "Invalid input: x_new must contain finite numbers."
return [value]
if not isinstance(data, list):
return "Invalid input: x_new must be a scalar or 2D list containing numeric values."
if len(data) == 0:
return "Invalid input: x_new must contain at least one row when provided as a 2D list."
values: List[float] = []
for row in data:
if not isinstance(row, list) or not row:
return "Invalid input: x_new must be a 2D list containing numeric values."
if len(row) != 1:
return "Invalid input: x_new must be a 2D list with exactly one column."
try:
value = float(row[0])
except Exception:
return "Invalid input: x_new must contain numeric values."
if not math.isfinite(value):
return "Invalid input: x_new must contain finite numbers."
values.append(value)
return values
x_values = _normalize_column(x, "x", min_rows=2)
if isinstance(x_values, str):
return _error(x_values)
y_values = _normalize_column(y, "y", min_rows=2)
if isinstance(y_values, str):
return _error(y_values)
dyd_len = len(y_values)
if len(x_values) != dyd_len:
return _error("Invalid input: x and y must have the same number of rows.")
if any(x2 <= x1 for x1, x2 in zip(x_values, x_values[1:])):
return _error("Invalid input: x must be strictly increasing.")
x_new_values = _normalize_x_new(x_new)
if isinstance(x_new_values, str):
return _error(x_new_values)
allowed_bc_types = {"not-a-knot", "clamped", "natural", "periodic"}
if not isinstance(bc_type, str):
return _error("Invalid input: bc_type must be one of 'not-a-knot', 'clamped', 'natural', or 'periodic'.")
normalized_bc_type = bc_type.lower()
if normalized_bc_type not in allowed_bc_types:
return _error("Invalid input: bc_type must be one of 'not-a-knot', 'clamped', 'natural', or 'periodic'.")
if normalized_bc_type == "periodic":
if abs(y_values[0] - y_values[-1]) > 1e-9:
return _error("Invalid input: y must have matching endpoints when bc_type is 'periodic'.")
# Construct spline and evaluate at requested points.
try:
spline = scipy_cubicspline(x_values, y_values, bc_type=normalized_bc_type)
interpolated = spline(x_new_values)
except Exception as exc:
return _error(f"scipy.CubicSpline error: {exc}")
results: ExcelColumn = []
for value in interpolated:
try:
numeric_value = float(value)
except Exception:
return _error("Invalid result: interpolation produced a non-numeric value.")
if not math.isfinite(numeric_value):
return _error("Invalid result: interpolation produced a non-finite value.")
results.append([numeric_value])
return results