MAKE_INTERP_SPLINE

Overview

The MAKE_INTERP_SPLINE function constructs an interpolating B-spline (basis spline) through a set of data points and evaluates it at new positions. B-splines are piecewise polynomial functions that provide smooth interpolation while maintaining local control—changes to data points only affect the curve in a limited region. This function is particularly useful for generating smooth curves through discrete measurements, resampling time series data, or creating differentiable approximations to tabular data.

This implementation uses SciPy’s make_interp_spline function from the scipy.interpolate module. The underlying algorithm constructs a BSpline object that passes exactly through each provided data point.

A B-spline of degree k is a piecewise polynomial function defined over a sequence of knots t_0, t_1, \ldots, t_m. The basis functions B_{i,k}(x) are computed using the Cox–de Boor recursion formula:

B_{i,0}(x) = \begin{cases} 1 & \text{if } t_i \leq x < t_{i+1} \\ 0 & \text{otherwise} \end{cases}
B_{i,k}(x) = \frac{x - t_i}{t_{i+k} - t_i} B_{i,k-1}(x) + \frac{t_{i+k+1} - x}{t_{i+k+1} - t_{i+1}} B_{i+1,k-1}(x)

The default degree is k = 3 (cubic spline), which provides C^2 continuity—the function and its first two derivatives are continuous at all knot locations. Higher degrees produce smoother curves but require more data points.

The bc_type parameter controls boundary conditions at the endpoints:

  • not-a-knot (default): The first and second polynomial segments are the same polynomial, as are the last two. This is appropriate for general-purpose interpolation.
  • clamped: First derivatives at both ends are zero, creating a curve that approaches endpoints horizontally.
  • natural: Second derivatives at both ends are zero, minimizing overall curvature.
  • periodic: The function and its first k-1 derivatives match at both endpoints, suitable for closed curves.

For more background on B-splines, see the Wikipedia article on B-splines and Carl de Boor’s foundational work A Practical Guide to Splines (Springer, 1978).

This example function is provided as-is without any representation of accuracy.

Excel Usage

=MAKE_INTERP_SPLINE(x, y, x_new, k, bc_type)
  • x (list[list], required): The x-coordinates of the data points
  • y (list[list], required): The y-coordinates of the data points
  • x_new (list[list], required): The x-coordinates at which to evaluate the spline
  • k (int, optional, default: 3): B-spline degree
  • bc_type (str, optional, default: “not-a-knot”): Boundary condition type

Returns (list[list]): A 2D list of interpolated values, or error message string.

Examples

Example 1: Demo case 1

Inputs:

x y x_new k
0 0 0.5 1
1 1 1.5
2 4

Excel formula:

=MAKE_INTERP_SPLINE({0;1;2}, {0;1;4}, {0.5;1.5}, 1)

Expected output:

Result
0.5
2.5

Example 2: Demo case 2

Inputs:

x y x_new
0 0 0.5
1 1 1.5
2 0 2.5
3 1

Excel formula:

=MAKE_INTERP_SPLINE({0;1;2;3}, {0;1;0;1}, {0.5;1.5;2.5})

Expected output:

Result
1
0.5
0

Example 3: Demo case 3

Inputs:

x y x_new k
0 0 0.5 2
1 1 2.5
2 4
3 9

Excel formula:

=MAKE_INTERP_SPLINE({0;1;2;3}, {0;1;4;9}, {0.5;2.5}, 2)

Expected output:

Result
0.25
6.25

Example 4: Demo case 4

Inputs:

x y x_new k bc_type
0 0 0.5 3 clamped
1 1 1.5
2 0 2.5
3 -1 3.5
4 0

Excel formula:

=MAKE_INTERP_SPLINE({0;1;2;3;4}, {0;1;0;-1;0}, {0.5;1.5;2.5;3.5}, 3, "clamped")

Expected output:

Result
0.45
0.77
-0.77
-0.45

Python Code

import math
from scipy.interpolate import make_interp_spline as scipy_make_interp_spline

def make_interp_spline(x, y, x_new, k=3, bc_type='not-a-knot'):
    """
    Compute interpolating B-spline and evaluate at new points.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.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
        k (int, optional): B-spline degree Default is 3.
        bc_type (str, optional): Boundary condition type Valid options: not-a-knot, periodic, clamped, natural. Default is 'not-a-knot'.

    Returns:
        list[list]: A 2D list of interpolated values, or error message string.
    """
    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 a 1D list."""
        return [item for sublist in arr for item in sublist]

    def validate_numeric_2d(arr, name):
        """Validate that arr is a 2D list of finite numbers."""
        if not isinstance(arr, list):
            return f"Invalid input: {name} must be a 2D list."
        for i, row in enumerate(arr):
            if not isinstance(row, list):
                return f"Invalid input: {name} must be a 2D list (row {i} is not a list)."
            for j, val in enumerate(row):
                if not isinstance(val, (int, float)):
                    return f"Invalid input: {name}[{i}][{j}] must be a number."
                if math.isnan(val) or math.isinf(val):
                    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 inputs
    error = validate_numeric_2d(x, "x")
    if error:
        return error
    error = validate_numeric_2d(y, "y")
    if error:
        return error
    error = validate_numeric_2d(x_new, "x_new")
    if error:
        return error

    # Validate k parameter
    if not isinstance(k, (int, float)):
        return "Invalid input: k must be an integer."
    if k != int(k):
        return "Invalid input: k must be an integer."
    k = int(k)
    if k < 0:
        return "Invalid input: k must be non-negative."

    # Validate bc_type parameter
    valid_bc_types = ["not-a-knot", "periodic", "clamped", "natural"]
    if bc_type not in valid_bc_types:
        return f"Invalid input: bc_type must be one of {valid_bc_types}."

    # Convert bc_type to None for scipy if it's 'not-a-knot' (scipy's default)
    bc_type_scipy = None if bc_type == "not-a-knot" else bc_type

    # Flatten 2D lists to 1D arrays
    try:
        x_flat = flatten(x)
        y_flat = flatten(y)
        x_new_flat = flatten(x_new)
    except Exception as exc:
        return f"Invalid input: unable to flatten arrays: {exc}"

    # 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 length requirement
    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)})."

    # Check that x values are strictly increasing
    for i in range(len(x_flat) - 1):
        if x_flat[i] >= x_flat[i + 1]:
            return "Invalid input: x values must be strictly increasing."

    # Create and evaluate the spline
    try:
        spline = scipy_make_interp_spline(x_flat, y_flat, k=k, bc_type=bc_type_scipy)
        result = spline(x_new_flat)
    except Exception as exc:
        return f"scipy.interpolate.make_interp_spline error: {exc}"

    # Validate result
    if not hasattr(result, "__iter__"):
        result = [result]

    # Convert result to 2D list (column vector)
    try:
        result_2d = [[float(val)] for val in result]
    except Exception as exc:
        return f"Error converting result to 2D list: {exc}"

    # Check for non-finite values in result
    for i, row in enumerate(result_2d):
        for val in row:
            if math.isnan(val) or math.isinf(val):
                return f"scipy.interpolate.make_interp_spline error: result contains non-finite value at index {i}."

    return result_2d

Online Calculator