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.