lw_pls — Locally-Weighted PLS (LW-PLS)

Group: Nonlinear / local · Registry tolerance: 5.0

Description

LW-PLS — Locally-weighted PLS (§17 Phase 4)

From the pls4all.sklearn.LWPLSRegression docstring:

Locally-weighted PLS (Næs & Centner 1998).

Registry note — In-tree nirs4all.operators.models.sklearn.lwpls.LWPLS is the sanctioned external reference. pls4all defaults to the Gaussian-weighted local PLS that matches nirs4all bit-for-bit (max_abs < 1e-13); the legacy k-NN cutoff variant is opt-in via cfg.solver = SIMPLS.

Parameters

Name

Type

Default

Notes

n_components

int

2

Number of latent components extracted (k).

n_neighbors

int

30

Number of training neighbours used for each local prediction (LW-PLS).

Explanations

Bibliographic source

Centner, V. & Massart, D. L. (1998). Optimisation in locally weighted regression. Analytical Chemistry 70(19), 4206–4211.

Mathematical principle

Instead of fitting a single global PLS, LW-PLS refits a per-prediction-point local PLS using only the \(k\)-nearest calibration samples (in \(\mathbf{X}\)-space distance). This adapts the model to the local geometry around each query point and is effective on calibration sets that span heterogeneous regimes (e.g. a single instrument calibrated across several product classes).

The neighbourhood weight typically combines distance (Gaussian or tricube kernel on the Euclidean / Mahalanobis distance) with the inverse residual variance from a preliminary global fit. The local PLS uses few components (typically 2–4) because the neighbourhood is small.

Prediction cost is \(O(n)\) for the neighbour search plus \(O(k_{\mathrm{nn}} \cdot p \cdot k_{\mathrm{pls}})\) for the local fit, per query. KD-tree / ball-tree indices accelerate the neighbour search; pls4all uses an exhaustive scan because \(p \gg n\) defeats most spatial indices for NIR data anyway.

Implementation

n4m_lw_pls_fit. Reference: sanctioned git-pinned port nirs4all.operators.models.sklearn.lwpls.

MATLAB header (bindings/matlab/+pls4all/lw_pls.m):

pls4all.lw_pls  Locally-weighted PLS (Næs & Centner 1998).

Usage

Every pls4all binding tab dispatches into the same C kernel; the external libraries listed at the bottom of the page are the parity references registered in benchmarks.parity_timing.registry. Switch tabs to read the same fit in your language. The R package now ships drop-in-compatible facades for the CRAN pls package (plsr, pcr, mvr) and for the mdatools::pls(x, y, ...) matrix idiom — those tabs appear only on the methods that have a meaningful equivalence.

pls4all bindings

/* C ABI — libn4m */
n4m_context_t* ctx = n4m_context_create();
n4m_config_t*  cfg = n4m_config_create();
n4m_method_result_t* res = NULL;
n4m_lw_pls_fit(ctx, cfg, &x_view, &y_view, /* hyperparams */, &res);
/* … read coefficients / mask / scores via */
/* n4m_method_result_get_double_matrix / vector / scalar … */
n4m_method_result_destroy(res);
n4m_config_destroy(cfg);
n4m_context_destroy(ctx);
import pls4all
from pls4all._methods import lw_pls_fit
with pls4all.Context() as ctx, pls4all.Config() as cfg:
    res = lw_pls_fit(ctx, cfg, X, y, n_components=3)
# then: res.matrix("predictions"), res.matrix("coefficients"),
# res.vector("mask"), res.scalar("intercept"), …
from pls4all.sklearn import LWPLSRegression
mdl = LWPLSRegression(n_components=2, n_neighbors=30)
mdl.fit(X, y)
y_hat = mdl.predict(X_test)
library(pls4all)
# Unified low-level dispatcher (May 2026 R cleanup):
res <- pls4all_method("lw_pls", X, y,
                      n_components = 3L, params = list(n_neighbors = 30L))
# res is a named list with MethodResult arrays/scalars.
# selected_indices / top_k_intervals are 1-based.
res = pls4all.lw_pls(X, y, 3);
% see header of bindings/matlab/+pls4all/lw_pls.m for full
% parameter surface:
%   res = lw_pls(X, Y, n_components, n_neighbors)
yhat = predict(res, Xtest);

No idiomatic classdef wrapper — invoke pls4all.fit("lw_pls", X, y, …) directly from the unified MEX factory.

Registry parity references 📐

  • 📐 nirs4all (python · python) — nirs4all in-tree · qualitative (rmse_rel ≤ 5e+00) — In-tree Python LW-PLS (sanctioned external reference). Locally-weighted PLS (Naes 1990 / Centner 1998). pls4all’s default solver (NIPALS) implements the same Gaussian-weighted local PLS as the nirs4all reference, deriving the kernel bandwidth lambda = max(1.0, 0.5 * n_neighbors). The legacy k-NN cutoff variant remains available via cfg.solver = SIMPLS.

Benchmarks

Adaptive wall-clock per cell measured against full_matrix.csv. Only backends that implement this method are listed; libraries without the method are omitted.

Verdict  ·  ✓ ref / ≈ ref / ~ shape mark a reference-gate pass at strict / relaxed / qualitative tolerance  ·  ✓ bind = pls4all binding agrees with the C++ baseline  ·  ⇄ cross-check = documented by-design selector/RNG/model, noncanonical API/facade convention, or secondary oracle  ·  ✗ divergent  ·  ⚠ error  ·  — not run. The fastest backend per column is marked 🏆.

Reference gate: strict — numeric equivalence (rmse_rel_tol 1e-08).

Rows tagged with 📐 are the canonical parity references for this method (declared in parity_timing.registry). C++ and external rows show reference parity; pls4all language bindings show binding parity against the C++ backend. Hover the icon for role and tolerance band.

BackendParity200×40 (ms)
C++ native · libn4m
pls4all.cpp.blas+omp✓ ref 7e-1625.6 ms
Python · pls4all
pls4all.python✓ bind37.2 ms
pls4all.sklearn⇄ +1e+009.19 ms🏆
R · pls4all
pls4all.R⇄ +1e+0020.3 ms
pls4all.R.formula⇄ +1e+0023.2 ms
pls4all.R.mdatools⇄ +1e+0024.6 ms
pls4all.R.pls⇄ +1e+0023.2 ms
Python · external
📐nirs4allsource23.9 ms
BackendParity200×40 (ms)
C++ native · libn4m
pls4all.cpp.blas+omp✓ ref 7e-1610.8 ms
Python · pls4all
pls4all.python✓ bind10.3 ms
pls4all.sklearn⇄ +1e+004.44 ms🏆
R · pls4all
pls4all.R⇄ +1e+009.02 ms
pls4all.R.formula⇄ +1e+0010.0 ms
pls4all.R.mdatools⇄ +1e+0010.8 ms
pls4all.R.pls⇄ +1e+0011.1 ms
Python · external
📐nirs4allsource23.7 ms
BackendParity200×40 (ms)
C++ native · libn4m
pls4all.cpp.blas+omp✓ ref 7e-1611.8 ms
Python · pls4all
pls4all.python✓ bind12.9 ms
pls4all.sklearn⇄ +1e+005.14 ms🏆
R · pls4all
pls4all.R⇄ +1e+009.85 ms
pls4all.R.formula⇄ +1e+009.03 ms
pls4all.R.mdatools⇄ +1e+0011.2 ms
pls4all.R.pls⇄ +1e+0012.1 ms
Python · external
📐nirs4allsource29.0 ms

See also: benchmark overview · methods index · interactive dashboard