pcr — Principal Components Regression¶
Group: Core PLS · Registry tolerance: 1e-06
Description¶
Principal Components Regression
From the pls4all.sklearn.PCR docstring:
Principal Components Regression — fits a least-squares regression on the SVD of X.
Registry note — PCR via SVD on X then linear regression; references are sklearn Pipeline(PCA(svd_solver=’full’) + LinearRegression) and R
pls::pcr.
Parameters¶
Name |
Type |
Default |
Notes |
|---|---|---|---|
|
|
|
Number of latent components extracted (k). |
|
|
|
Subtract the column mean of X before fitting. |
|
|
|
Standardize X columns to unit variance before fitting. |
|
|
|
Subtract the column mean of y before fitting. |
|
|
|
Standardize y columns to unit variance before fitting. |
|
|
|
Convergence tolerance for iterative solvers (NIPALS / power-iteration). |
|
|
|
Maximum iterations for iterative solvers. |
|
|
|
If True, keep the latent score matrix ( |
Explanations¶
Bibliographic source¶
Massy, W. F. (1965). Principal Components Regression in Exploratory Statistical Research. JASA 60(309), 234–256.
Mathematical principle¶
PCR sidesteps the multicollinearity of \(\mathbf{X}\) by regressing on its orthogonal principal-component scores rather than on the raw columns. The factorisation \(\mathbf{X} = \mathbf{U}\boldsymbol{\Sigma}\mathbf{V}^{\top}\) (SVD) yields scores \(\mathbf{T}_k = \mathbf{U}_k\boldsymbol{\Sigma}_k\) for the top \(k\) components, and the regression \(\mathbf{Y} = \mathbf{T}_k\mathbf{Q}_k + \mathbf{E}\) is fit by ordinary least squares.
Unlike PLS, PCR is unsupervised in its dimensionality reduction: the first \(k\) directions maximise the variance of \(\mathbf{X}\) regardless of how relevant they are to \(\mathbf{Y}\). This makes PCR a useful baseline for diagnosing whether the predictive directions in a calibration set really do coincide with the high-variance directions (in which case PCR ≈ PLS) or not (in which case PLS is strictly preferable at the same \(k\)).
Coefficients in the original feature scale are recovered as \(\mathbf{B} = \mathbf{V}_k \boldsymbol{\Sigma}_k^{-1} \mathbf{T}_k^{\top}\mathbf{Y}\). Total cost is dominated by the partial SVD: \(O(np\min(n,p))\) for a full decomposition, or \(O(npk)\) with a truncated method (Lanczos, randomised SVD).
Implementation¶
Algorithm.PCR + Solver.SVD in libn4m. Reference implementations are scikit-learn’s Pipeline(PCA(n_components=k), LinearRegression()) and R pls::pcr.
MATLAB header (bindings/matlab/+pls4all/PcrRegression.m):
pls4all.PcrRegression — Principal Component Regression model.
Example:
mdl = pls4all.PcrRegression(X, y, 5);
yhat = predict(mdl, Xnew);
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 direct MethodResult path */
n4m_context_t* ctx = n4m_context_create();
n4m_config_t* cfg = n4m_config_create();
n4m_config_set_n_components(cfg, 4);
n4m_method_result_t* res = NULL;
n4m_pcr_fit(ctx, cfg, &x_view, &y_view, &res);
/* res contains coefficients, predictions, x_mean/x_scale, y_mean/y_scale,
* weights_w, loadings_p, rotations_r, rmse and n_components. */
n4m_method_result_destroy(res);
n4m_config_destroy(cfg);
n4m_context_destroy(ctx);
import pls4all
from pls4all import Algorithm, Solver
with pls4all.Context() as ctx, pls4all.Config() as cfg:
cfg.algorithm = Algorithm.PCR
cfg.solver = Solver.SVD
cfg.n_components = 4
with pls4all.Model.fit(ctx, cfg, X, y) as mdl:
y_hat = mdl.predict(X_test)
import n4m
from n4m.sklearn import NativePCRRegressor
res = n4m.pcr(X, y, n_components=4, scale_x=True)
mdl = NativePCRRegressor(n_components=4, scale_x=True).fit(X, y)
y_hat = mdl.predict(X_test)
from pls4all.sklearn import PCR
mdl = PCR(n_components=2, center_x=True, scale_x=True, center_y=True, scale_y=False, tol=1e-06, max_iter=500, store_scores=False)
mdl.fit(X, y)
y_hat = mdl.predict(X_test)
library(pls4all)
# Unified low-level dispatcher (May 2026 R cleanup):
res <- pls4all_method("pcr", X, y,
n_components = 4L)
# res is a named list with MethodResult arrays/scalars.
# selected_indices / top_k_intervals are 1-based.
library(pls4all)
# Drop-in for CRAN `pls::pcr` (same signature).
fit <- pcr(y ~ ., ncomp = 4L, data = train,
validation = "CV", segments = 10L)
yhat <- predict(fit, newdata = test, ncomp = 4L)
RMSEP(fit)
library(pls4all)
# Drop-in for `mdatools::pls(x, y, ncomp, method = "pcr")`.
fit <- pls_mdatools(X, y, ncomp = 4L, method = "pcr",
center = TRUE, scale = FALSE)
yhat <- predict(fit, newdata = X_test, ncomp = 4L)
res = pls4all.pcr(X, y, 4);
% see header of bindings/matlab/+pls4all/pcr.m for full
% parameter surface:
% [coefs, x_mean, y_mean, predictions] = pcr(X, Y, n_components)
yhat = predict(res, Xtest);
mdl = pls4all.fit("pcr", X, y, "NumComponents", 4);
yhat = predict(mdl, Xtest);
Registry parity references 📐
📐
ref.python_scikit_learn(python · python) —scikit-learn1.8.0 · strict (rmse_rel ≤ 1e-06) — sklearn Pipeline(PCA(svd_solver=’full’) + LinearRegression).📐
ref.r_pls(R · r) —pls2.8.5 · strict (rmse_rel ≤ 1e-06) — R pls::pcr(scale=FALSE).
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-06).
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×50 (ms) |
|---|---|---|
| C++ native · libn4m | ||
pls4all.cpp.blas+omp | ✓ ref 3e-12 | 2.92 ms |
| Python · pls4all | ||
pls4all.python | ✓ bind | 2.95 ms |
pls4all.sklearn | ✓ bind | 2.68 ms🏆 |
| R · pls4all | ||
pls4all.R | ✓ 2e-15 | 5.15 ms |
pls4all.R.formula | ✓ 2e-15 | 6.41 ms |
pls4all.R.mdatools | ✓ 2e-15 | 6.21 ms |
pls4all.R.pls | ✓ 2e-15 | 11.8 ms |
| Python · external | ||
📐ref.python_scikit_learn | source | 2.71 ms |
| R · external | ||
📐ref.r_pls | ⇄ +2e-14 | 7.99 ms |
| Backend | Parity | 200×50 (ms) |
|---|---|---|
| C++ native · libn4m | ||
pls4all.cpp.blas+omp | ✓ ref 3e-12 | 2.52 ms |
| Python · pls4all | ||
pls4all.python | ✓ bind | 2.50 ms🏆 |
pls4all.sklearn | ✓ bind | 2.72 ms |
| R · pls4all | ||
pls4all.R | ✓ 2e-15 | 5.09 ms |
pls4all.R.formula | ✓ 2e-15 | 6.36 ms |
pls4all.R.mdatools | ✓ 2e-15 | 6.27 ms |
pls4all.R.pls | ✓ 2e-15 | 10.4 ms |
| Python · external | ||
📐ref.python_scikit_learn | source | 2.85 ms |
| R · external | ||
📐ref.r_pls | ⇄ +2e-14 | 7.83 ms |
| Backend | Parity | 200×50 (ms) |
|---|---|---|
| C++ native · libn4m | ||
pls4all.cpp.blas+omp | ✓ ref 3e-12 | 2.49 ms🏆 |
| Python · pls4all | ||
pls4all.python | ✓ bind | 2.73 ms |
pls4all.sklearn | ✓ bind | 2.67 ms |
| R · pls4all | ||
pls4all.R | ✓ 2e-15 | 4.86 ms |
pls4all.R.formula | ✓ 2e-15 | 5.87 ms |
pls4all.R.mdatools | ✓ 2e-15 | 6.40 ms |
pls4all.R.pls | ✓ 2e-15 | 10.7 ms |
| Python · external | ||
📐ref.python_scikit_learn | source | 4.94 ms |
| R · external | ||
📐ref.r_pls | ⇄ +2e-14 | 14.0 ms |
See also: benchmark overview · methods index · interactive dashboard