LSQ_BIVAR_SPL

This function wraps SciPy’s least-squares bivariate spline class to fit a smooth surface to scattered 2D data with explicit interior knot placement. It provides more direct control of spline flexibility than automatic-knot methods.

For observations (x_i, y_i, z_i) and knot vectors in each axis, the fitted spline minimizes a weighted least-squares objective and then evaluates \hat{z}(x, y) at requested query pairs.

Excel Usage

=LSQ_BIVAR_SPL(x, y, z, tx, ty, xi, yi, w, kx, ky, 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.
  • tx (list[list], required): Strictly increasing interior knots in x-direction.
  • ty (list[list], required): Strictly increasing interior knots in y-direction.
  • 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.
  • eps (float, optional, default: null): 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: LSQ spline fit on planar surface with linear degrees

Inputs:

x y z tx ty xi yi kx ky
0 0 0 1.5 1.5 0.5 0.5 1 1
1 0 1 2.5 2.5
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:

=LSQ_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.5}, {1.5}, {0.5;2.5}, {0.5;2.5}, 1, 1)

Expected output:

Result
1
5
Example 2: Weighted LSQ spline fit

Inputs:

x y z tx ty xi yi w kx ky
0 0 0 1.5 1.5 1 1 1 1 1
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:

=LSQ_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.5}, {1.5}, {1;2}, {1;2}, {1;1;1;1;1;2;2;1;1;2;2;1;1;1;1;1}, 1, 1)

Expected output:

Result
2
4
Example 3: LSQ spline with two interior knots per axis

Inputs:

x y z tx ty xi yi kx ky
0 0 0 1 1 0.75 1.25 1 1
1 0 1 2 2 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:

=LSQ_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}, {0.75;2.25}, {1.25;2.75}, 1, 1)

Expected output:

Result
2
5
Example 4: LSQ spline with custom eps threshold

Inputs:

x y z tx ty xi yi kx ky eps
0 0 0 1.5 1.5 1.2 0.8 1 1 1e-12
1 0 1 2.2 2.1
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:

=LSQ_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.5}, {1.5}, {1.2;2.2}, {0.8;2.1}, 1, 1, 1e-12)

Expected output:

Result
2
4.3

Python Code

import math
import numpy as np
from scipy.interpolate import LSQBivariateSpline as scipy_LSQBivariateSpline

def lsq_bivar_spl(x, y, z, tx, ty, xi, yi, w=None, kx=3, ky=3, eps=None):
    """
    Weighted least-squares bivariate spline with user-specified knots.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.LSQBivariateSpline.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.
        tx (list[list]): Strictly increasing interior knots in x-direction.
        ty (list[list]): Strictly increasing interior knots in y-direction.
        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.
        eps (float, optional): Rank threshold for linear system solve. Default is None.

    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 validate_finite_numeric(values, name):
            for i, val in enumerate(values):
                if not isinstance(val, (int, float)):
                    return f"Error: Invalid input: {name} element {i} must be numeric."
                if math.isnan(val) or math.isinf(val):
                    return f"Error: Invalid input: {name} element {i} must be finite."
            return None

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

        x_flat = flatten_2d(x)
        y_flat = flatten_2d(y)
        z_flat = flatten_2d(z)
        tx_flat = flatten_2d(tx)
        ty_flat = flatten_2d(ty)
        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 tx_flat is None or ty_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."

        error = validate_finite_numeric(x_flat, "x")
        if error:
            return error
        error = validate_finite_numeric(y_flat, "y")
        if error:
            return error
        error = validate_finite_numeric(z_flat, "z")
        if error:
            return error
        error = validate_finite_numeric(tx_flat, "tx")
        if error:
            return error
        error = validate_finite_numeric(ty_flat, "ty")
        if error:
            return error
        error = validate_finite_numeric(xi_flat, "xi")
        if error:
            return error
        error = validate_finite_numeric(yi_flat, "yi")
        if error:
            return error

        if not isinstance(kx, int) or not isinstance(ky, int):
            return "Error: Invalid input: kx and ky must be integers."
        if kx < 1 or ky < 1:
            return "Error: Invalid input: kx and ky must be at least 1."

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

        for i in range(len(tx_flat) - 1):
            if tx_flat[i] >= tx_flat[i + 1]:
                return "Error: Invalid input: tx must be strictly increasing."
        for i in range(len(ty_flat) - 1):
            if ty_flat[i] >= ty_flat[i + 1]:
                return "Error: Invalid input: ty must be strictly increasing."

        x_min = min(x_flat)
        x_max = max(x_flat)
        y_min = min(y_flat)
        y_max = max(y_flat)

        for i, knot in enumerate(tx_flat):
            if knot <= x_min or knot >= x_max:
                return f"Error: Invalid input: tx element {i} must lie strictly inside the x data range."
        for i, knot in enumerate(ty_flat):
            if knot <= y_min or knot >= y_max:
                return f"Error: Invalid input: ty element {i} must lie strictly inside the y data range."

        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."
            error = validate_finite_numeric(w_flat, "w")
            if error:
                return error
            for i, val in enumerate(w_flat):
                if val <= 0:
                    return f"Error: Invalid input: w element {i} must be positive."
            w_arr = np.array(w_flat, dtype=float)

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

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

        spline = scipy_LSQBivariateSpline(
            x_arr,
            y_arr,
            z_arr,
            tx_arr,
            ty_arr,
            w=w_arr,
            kx=kx,
            ky=ky,
            eps=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.
Strictly increasing interior knots in x-direction.
Strictly increasing interior knots in y-direction.
x-coordinates of query points.
y-coordinates of query points.
Positive observation weights.
Spline degree in x-direction.
Spline degree in y-direction.
Rank threshold for linear system solve.