SMOOTH_BIVAR_SPL

This function wraps SciPy’s smoothing bivariate spline class to fit a smooth surface to scattered points in two dimensions. It can reduce noise while preserving broad surface structure.

The fitted spline balances data fidelity and smoothness by minimizing a weighted residual objective subject to a smoothing constraint, then evaluates \hat{z}(x, y) at requested query pairs.

Excel Usage

=SMOOTH_BIVAR_SPL(x, y, z, xi, yi, w, kx, ky, s, eps)
  • x (list[list], required): x-coordinates of observed points.
  • y (list[list], required): y-coordinates of observed points.
  • z (list[list], required): Observed values at (x, y) points.
  • xi (list[list], required): x-coordinates of query points.
  • yi (list[list], required): y-coordinates of query points.
  • w (list[list], optional, default: null): Positive observation weights.
  • kx (int, optional, default: 3): Spline degree in x-direction.
  • ky (int, optional, default: 3): Spline degree in y-direction.
  • s (float, optional, default: null): Smoothing factor.
  • eps (float, optional, default: 1e-16): Rank threshold for linear system solve.

Returns (list[list]): Evaluated spline values as a 2D list (column vector), or an error message string.

Example 1: Fit smooth spline to planar surface

Inputs:

x y z xi yi
0 0 0 0.5 0.5
1 0 1 1.5 1.5
2 0 2 2.5 2.5
3 0 3
0 1 1
1 1 2
2 1 3
3 1 4
0 2 2
1 2 3
2 2 4
3 2 5
0 3 3
1 3 4
2 3 5
3 3 6

Excel formula:

=SMOOTH_BIVAR_SPL({0;1;2;3;0;1;2;3;0;1;2;3;0;1;2;3}, {0;0;0;0;1;1;1;1;2;2;2;2;3;3;3;3}, {0;1;2;3;1;2;3;4;2;3;4;5;3;4;5;6}, {0.5;1.5;2.5}, {0.5;1.5;2.5})

Expected output:

Result
1
3
5
Example 2: Fit with linear spline degrees

Inputs:

x y z xi yi kx ky s
0 0 0 0.25 0.5 1 1 0
1 0 1 1.25 0.5
2 0 2
0 1 1
1 1 2
2 1 3

Excel formula:

=SMOOTH_BIVAR_SPL({0;1;2;0;1;2}, {0;0;0;1;1;1}, {0;1;2;1;2;3}, {0.25;1.25}, {0.5;0.5}, 1, 1, 0)

Expected output:

Result
0.75
1.75
Example 3: Weighted smoothing fit

Inputs:

x y z xi yi w s
0 0 0 1 1 1 0.5
1 0 1 2 2 1
2 0 2 1
3 0 3 1
0 1 1 1
1 1 2 2
2 1 3 2
3 1 4 1
0 2 2 1
1 2 3 2
2 2 4 2
3 2 5 1
0 3 3 1
1 3 4 1
2 3 5 1
3 3 6 1

Excel formula:

=SMOOTH_BIVAR_SPL({0;1;2;3;0;1;2;3;0;1;2;3;0;1;2;3}, {0;0;0;0;1;1;1;1;2;2;2;2;3;3;3;3}, {0;1;2;3;1;2;3;4;2;3;4;5;3;4;5;6}, {1;2}, {1;2}, {1;1;1;1;1;2;2;1;1;2;2;1;1;1;1;1}, 0.5)

Expected output:

Result
2
4
Example 4: Fit with custom eps threshold

Inputs:

x y z xi yi eps
0 0 0 0.75 1.25 1e-12
1 0 1 2.25 2.75
2 0 2
3 0 3
0 1 1
1 1 2
2 1 3
3 1 4
0 2 2
1 2 3
2 2 4
3 2 5
0 3 3
1 3 4
2 3 5
3 3 6

Excel formula:

=SMOOTH_BIVAR_SPL({0;1;2;3;0;1;2;3;0;1;2;3;0;1;2;3}, {0;0;0;0;1;1;1;1;2;2;2;2;3;3;3;3}, {0;1;2;3;1;2;3;4;2;3;4;5;3;4;5;6}, {0.75;2.25}, {1.25;2.75}, 1e-12)

Expected output:

Result
2
5

Python Code

import math
import numpy as np
from scipy.interpolate import SmoothBivariateSpline as scipy_SmoothBivariateSpline

def smooth_bivar_spl(x, y, z, xi, yi, w=None, kx=3, ky=3, s=None, eps=1e-16):
    """
    Smooth bivariate spline fit for scattered 2D observations.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.SmoothBivariateSpline.html

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

    Args:
        x (list[list]): x-coordinates of observed points.
        y (list[list]): y-coordinates of observed points.
        z (list[list]): Observed values at (x, y) points.
        xi (list[list]): x-coordinates of query points.
        yi (list[list]): y-coordinates of query points.
        w (list[list], optional): Positive observation weights. Default is None.
        kx (int, optional): Spline degree in x-direction. Default is 3.
        ky (int, optional): Spline degree in y-direction. Default is 3.
        s (float, optional): Smoothing factor. Default is None.
        eps (float, optional): Rank threshold for linear system solve. Default is 1e-16.

    Returns:
        list[list]: Evaluated spline values as a 2D list (column vector), or an error message string.
    """
    try:
        def to2d(x):
            return [[x]] if not isinstance(x, list) else x

        def flatten_2d(arr):
            out = []
            for row in arr:
                if not isinstance(row, list):
                    return None
                for val in row:
                    out.append(val)
            return out

        def as_int(name, value):
            if not isinstance(value, int):
                return None, f"Error: Invalid input: {name} must be an integer."
            return value, None

        x = to2d(x)
        y = to2d(y)
        z = to2d(z)
        xi = to2d(xi)
        yi = to2d(yi)

        x_flat = flatten_2d(x)
        y_flat = flatten_2d(y)
        z_flat = flatten_2d(z)
        xi_flat = flatten_2d(xi)
        yi_flat = flatten_2d(yi)

        if x_flat is None or y_flat is None or z_flat is None or xi_flat is None or yi_flat is None:
            return "Error: Invalid input: all array inputs must be 2D lists."

        if len(x_flat) == 0 or len(y_flat) == 0 or len(z_flat) == 0:
            return "Error: Invalid input: x, y, and z must not be empty."
        if len(xi_flat) == 0 or len(yi_flat) == 0:
            return "Error: Invalid input: xi and yi must not be empty."

        if len(x_flat) != len(y_flat) or len(x_flat) != len(z_flat):
            return "Error: Invalid input: x, y, and z must have the same length."
        if len(xi_flat) != len(yi_flat):
            return "Error: Invalid input: xi and yi must have the same length."

        for i, val in enumerate(x_flat):
            if not isinstance(val, (int, float)) or math.isnan(val) or math.isinf(val):
                return f"Error: Invalid input: x element {i} must be finite numeric."
        for i, val in enumerate(y_flat):
            if not isinstance(val, (int, float)) or math.isnan(val) or math.isinf(val):
                return f"Error: Invalid input: y element {i} must be finite numeric."
        for i, val in enumerate(z_flat):
            if not isinstance(val, (int, float)) or math.isnan(val) or math.isinf(val):
                return f"Error: Invalid input: z element {i} must be finite numeric."
        for i, val in enumerate(xi_flat):
            if not isinstance(val, (int, float)) or math.isnan(val) or math.isinf(val):
                return f"Error: Invalid input: xi element {i} must be finite numeric."
        for i, val in enumerate(yi_flat):
            if not isinstance(val, (int, float)) or math.isnan(val) or math.isinf(val):
                return f"Error: Invalid input: yi element {i} must be finite numeric."

        kx_value, kx_error = as_int("kx", kx)
        if kx_error:
            return kx_error
        ky_value, ky_error = as_int("ky", ky)
        if ky_error:
            return ky_error

        if kx_value < 1 or ky_value < 1:
            return "Error: Invalid input: kx and ky must be at least 1."

        min_points = (kx_value + 1) * (ky_value + 1)
        if len(x_flat) < min_points:
            return f"Error: Invalid input: need at least {min_points} points for kx={kx_value}, ky={ky_value}."

        if s is not None:
            if not isinstance(s, (int, float)):
                return "Error: Invalid input: s must be numeric when provided."
            if s < 0:
                return "Error: Invalid input: s must be non-negative."

        if not isinstance(eps, (int, float)):
            return "Error: Invalid input: eps must be numeric."
        if eps <= 0 or eps >= 1:
            return "Error: Invalid input: eps must be in the interval (0, 1)."

        w_arr = None
        if w is not None:
            w = to2d(w)
            w_flat = flatten_2d(w)
            if w_flat is None:
                return "Error: Invalid input: w must be a 2D list."
            if len(w_flat) != len(x_flat):
                return "Error: Invalid input: w must match length of x, y, and z."
            for i, val in enumerate(w_flat):
                if not isinstance(val, (int, float)) or math.isnan(val) or math.isinf(val):
                    return f"Error: Invalid input: w element {i} must be finite numeric."
                if val <= 0:
                    return f"Error: Invalid input: w element {i} must be positive."
            w_arr = np.array(w_flat, dtype=float)

        x_arr = np.array(x_flat, dtype=float)
        y_arr = np.array(y_flat, dtype=float)
        z_arr = np.array(z_flat, dtype=float)
        xi_arr = np.array(xi_flat, dtype=float)
        yi_arr = np.array(yi_flat, dtype=float)

        spline = scipy_SmoothBivariateSpline(
            x_arr,
            y_arr,
            z_arr,
            w=w_arr,
            kx=kx_value,
            ky=ky_value,
            s=s,
            eps=float(eps)
        )

        result = spline.ev(xi_arr, yi_arr)
        result_arr = np.asarray(result, dtype=float).reshape(-1)
        return [[float(v)] for v in result_arr]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

x-coordinates of observed points.
y-coordinates of observed points.
Observed values at (x, y) points.
x-coordinates of query points.
y-coordinates of query points.
Positive observation weights.
Spline degree in x-direction.
Spline degree in y-direction.
Smoothing factor.
Rank threshold for linear system solve.