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

n_components

int

2

Number of latent components extracted (k).

center_x

bool

True

Subtract the column mean of X before fitting.

scale_x

bool

True

Standardize X columns to unit variance before fitting.

center_y

bool

True

Subtract the column mean of y before fitting.

scale_y

bool

False

Standardize y columns to unit variance before fitting.

tol

float

1e-06

Convergence tolerance for iterative solvers (NIPALS / power-iteration).

max_iter

int

500

Maximum iterations for iterative solvers.

store_scores

bool

False

If True, keep the latent score matrix (x_scores_) after fit.

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-learn 1.8.0 · strict (rmse_rel ≤ 1e-06) — sklearn Pipeline(PCA(svd_solver=’full’) + LinearRegression).

  • 📐 ref.r_pls (R · r) — pls 2.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.

BackendParity200×50 (ms)
C++ native · libn4m
pls4all.cpp.blas+omp✓ ref 3e-122.92 ms
Python · pls4all
pls4all.python✓ bind2.95 ms
pls4all.sklearn✓ bind2.68 ms🏆
R · pls4all
pls4all.R✓ 2e-155.15 ms
pls4all.R.formula✓ 2e-156.41 ms
pls4all.R.mdatools✓ 2e-156.21 ms
pls4all.R.pls✓ 2e-1511.8 ms
Python · external
📐ref.python_scikit_learnsource2.71 ms
R · external
📐ref.r_pls⇄ +2e-147.99 ms
BackendParity200×50 (ms)
C++ native · libn4m
pls4all.cpp.blas+omp✓ ref 3e-122.52 ms
Python · pls4all
pls4all.python✓ bind2.50 ms🏆
pls4all.sklearn✓ bind2.72 ms
R · pls4all
pls4all.R✓ 2e-155.09 ms
pls4all.R.formula✓ 2e-156.36 ms
pls4all.R.mdatools✓ 2e-156.27 ms
pls4all.R.pls✓ 2e-1510.4 ms
Python · external
📐ref.python_scikit_learnsource2.85 ms
R · external
📐ref.r_pls⇄ +2e-147.83 ms
BackendParity200×50 (ms)
C++ native · libn4m
pls4all.cpp.blas+omp✓ ref 3e-122.49 ms🏆
Python · pls4all
pls4all.python✓ bind2.73 ms
pls4all.sklearn✓ bind2.67 ms
R · pls4all
pls4all.R✓ 2e-154.86 ms
pls4all.R.formula✓ 2e-155.87 ms
pls4all.R.mdatools✓ 2e-156.40 ms
pls4all.R.pls✓ 2e-1510.7 ms
Python · external
📐ref.python_scikit_learnsource4.94 ms
R · external
📐ref.r_pls⇄ +2e-1414.0 ms

See also: benchmark overview · methods index · interactive dashboard