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.LWPLSis 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 |
|---|---|---|---|
|
|
|
Number of latent components extracted (k). |
|
|
|
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) —nirs4allin-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 bandwidthlambda = 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.
| Backend | Parity | 200×40 (ms) |
|---|---|---|
| C++ native · libn4m | ||
pls4all.cpp.blas+omp | ✓ ref 7e-16 | 25.6 ms |
| Python · pls4all | ||
pls4all.python | ✓ bind | 37.2 ms |
pls4all.sklearn | ⇄ +1e+00 | 9.19 ms🏆 |
| R · pls4all | ||
pls4all.R | ⇄ +1e+00 | 20.3 ms |
pls4all.R.formula | ⇄ +1e+00 | 23.2 ms |
pls4all.R.mdatools | ⇄ +1e+00 | 24.6 ms |
pls4all.R.pls | ⇄ +1e+00 | 23.2 ms |
| Python · external | ||
📐nirs4all | source | 23.9 ms |
| Backend | Parity | 200×40 (ms) |
|---|---|---|
| C++ native · libn4m | ||
pls4all.cpp.blas+omp | ✓ ref 7e-16 | 10.8 ms |
| Python · pls4all | ||
pls4all.python | ✓ bind | 10.3 ms |
pls4all.sklearn | ⇄ +1e+00 | 4.44 ms🏆 |
| R · pls4all | ||
pls4all.R | ⇄ +1e+00 | 9.02 ms |
pls4all.R.formula | ⇄ +1e+00 | 10.0 ms |
pls4all.R.mdatools | ⇄ +1e+00 | 10.8 ms |
pls4all.R.pls | ⇄ +1e+00 | 11.1 ms |
| Python · external | ||
📐nirs4all | source | 23.7 ms |
| Backend | Parity | 200×40 (ms) |
|---|---|---|
| C++ native · libn4m | ||
pls4all.cpp.blas+omp | ✓ ref 7e-16 | 11.8 ms |
| Python · pls4all | ||
pls4all.python | ✓ bind | 12.9 ms |
pls4all.sklearn | ⇄ +1e+00 | 5.14 ms🏆 |
| R · pls4all | ||
pls4all.R | ⇄ +1e+00 | 9.85 ms |
pls4all.R.formula | ⇄ +1e+00 | 9.03 ms |
pls4all.R.mdatools | ⇄ +1e+00 | 11.2 ms |
pls4all.R.pls | ⇄ +1e+00 | 12.1 ms |
| Python · external | ||
📐nirs4all | source | 29.0 ms |
See also: benchmark overview · methods index · interactive dashboard