Multivariate Analysis#
Models#
PCA#
- class process_improve.multivariate.methods.PCA(n_components, *, algorithm='auto', tol=np.float64(1.4901161193847656e-08), max_iter=1000, missing_data_settings=None)[source]#
Bases:
_LatentVariableModel,TransformerMixin,BaseEstimatorPrincipal Component Analysis with support for missing data.
- Parameters:
n_components (int) – Number of principal components to extract.
Noneasks for as many components as the data supports: it is resolved at fit time tomin(n_samples, n_features), which is also the ceiling an explicit request is clamped to (with aSpecificationWarning). The resolved count is available on the fitted attributen_components_.algorithm (str, default="auto") – Algorithm to use for fitting the model. -
"auto": Uses SVD when data is complete, NIPALS when data has missing values. -"svd": Singular Value Decomposition. Requires complete data. -"nipals": Non-linear Iterative Partial Least Squares. Handles missing data. -"tsr": Trimmed Score Regression. Handles missing data.tol (float, default=``epsqrt`` (about 1.5e-8)) – Relative convergence tolerance for the iterative algorithms: the loop stops once the norm of the difference between two successive score vectors, relative to the norm of the score vector, falls below this. See
terminate_check(). Ignored byalgorithm="svd", which is direct rather than iterative.max_iter (int, default=1000) – Maximum number of iterations per component for the iterative algorithms. A component that reaches the cap without converging emits a
SpecificationWarning. Ignored byalgorithm="svd".missing_data_settings (dict or None, default=None) – Settings for the iterative algorithms (NIPALS, TSR), overriding the constructor for this fit. Keys:
md_tolandmd_max_iter, which default to this model’stolandmax_iter. Prefer setting those two directly; this dict exists for the case where the missing-data path needs to differ from the fit.fitting) (Attributes (after)
--------------------------
n_components – The resolved number of components actually fitted (the constructor parameter clamped to
min(n_samples, n_features); the parameter itself is left as the user set it, includingNone).scores (pd.DataFrame of shape (n_samples, n_components)) – The score matrix (T).
loadings (pd.DataFrame of shape (n_features, n_components)) – The loading matrix (P).
r2_per_component (pd.Series of length n_components) – Fractional R² explained by each component.
r2_cumulative (pd.Series of length n_components) – Cumulative R² after each component.
r2_per_variable (pd.DataFrame of shape (n_features, n_components)) – Per-variable cumulative R² after each component.
spe (pd.DataFrame of shape (n_samples, n_components)) – Per-row SPE diagnostic; stored as the square root of the row sum-of-squared X-residuals (so it is on the residual scale, not the squared scale). One column per component, not one value per row: column
ais the SPE of the model truncated atacomponents, and the last column is the value at the full fitted model. Reach for a single number per observation withmodel.spe_.iloc[:, -1], not withnp.asarray(model.spe_).ravel(): ravel happens to give the right answer at one component and silently givesn_samples * n_componentsvalues above it.hotellings_t2 (pd.DataFrame of shape (n_samples, n_components)) – Cumulative Hotelling’s T² statistic. Per-component, exactly as
spe_above: columnauses the firstacomponents and the last column is the value at the full fitted model.explained_variance (np.ndarray of shape (n_components,)) – Variance explained by each component.
scaling_factor_for_scores (pd.Series of length n_components) – Standard deviation per score (sqrt of explained variance).
has_missing_data (bool) – Whether the training data contained missing values.
fitting_info (dict) – Timing and iteration info from the fitting algorithm.
- scores_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- loadings_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- spe_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- get_feature_names_out(input_features=None)[source]#
Return the output column names of
transform().PCA’stransformproduces scores, one column per component, named["PC1", "PC2", ..., "PC{n_components}"]. Theinput_featuresargument is accepted (Pipeline introspection passes it through) but unused: the output column count is the fittedn_components, not the input feature count.Used by
set_output()(sklearn 1.2+) to label theDataFrameview of the scores whenset_output(transform="pandas")is on, and by Pipeline introspection.- Return type:
- diagnose(X)[source]#
Project new data and compute diagnostics (scores, Hotelling’s T², SPE).
The same logic that historically lived in
predict(). The rename (since 1.38.1, #396) matchesPLS.diagnose()and clears thepredictname for a future return-type contract that does what its sklearn-convention name implies (a regression-style prediction).predict()is kept as a deprecation shim for now.- Parameters:
X (array-like of shape (n_samples, n_features))
- Returns:
result – With keys
scores,hotellings_t2,spe.- Return type:
- predict(X)[source]#
Forward to
diagnose(); emits aDeprecationWarning.Deprecated since version 1.38.1: Use
PCA.diagnose()instead.predictmatches the sklearn-convention name (a regression-style prediction), but PCA isn’t a regressor; the historical return is a diagnostics Bunch. The rename aligns withPLS.diagnose()and frees the name for a future contract. Will be removed in 2.0.0.
- project(X, *, method='tsr', ridge=0.0)[source]#
Estimate scores and diagnostics for rows that may contain missing values.
Whereas
transform()anddiagnose()propagate NaN, this method estimates the scores of partially-observed rows from the observed columns only, using the missing-data estimators of Arteaga and Ferrer (2002): trimmed score regression ("tsr", the default and the statistically strongest), single-component projection ("scp"), or projection to the model plane ("pmp"). Rows with no missing values take the standard complete-data path, so their scores are bitwise identical totransform().This is the “batch so far” primitive of online batch monitoring: the future part of an unfolded batch row is missing by construction, and the score estimate at each time sample is this projection (see Garcia-Munoz, Kourti and MacGregor, 2004).
- Parameters:
X (array-like of shape (n_samples, n_features)) – New data in the model’s (centred and scaled) space; NaN marks a missing entry. Rows that are entirely NaN are rejected.
method ({"tsr", "scp", "pmp"}, default="tsr") – The score estimator; see
process_improve.multivariate._projection.ridge (float, default=0.0) – Non-negative regularisation added to the matrix inverted by the
"tsr"and"pmp"estimators. Raise it above zero whencondition_numberreports near-singularity (typically very early in a batch, when few columns are observed).
- Returns:
result – With keys
scores(DataFrame, n_samples x n_components),hotellings_t2(Series; total over all components, computed with the training score variances),spe(Series; the square root of the residual sum of squares over the observed columns only),condition_number(Series; the conditioning diagnostic of each row’s estimator, 1.0 when nothing is missing) andn_observed(Series; observed features per row). SPE and T2 for a partially-observed row must be compared against limits built from the same missingness pattern, not against the full-observation limits; seeprocess_improve.batch.BatchMonitor.- Return type:
- projection_matrix(observed, *, method='tsr', ridge=0.0)[source]#
Build the fixed linear operator mapping observed columns to score estimates.
For a fixed missingness pattern, every estimator in
project()is a fixed linear mapt_hat = M @ z_observed. This method exposes that matrix so callers that reuse one pattern many times (an online monitor at time samplek, or an optimiser treating candidate columns as observed) can precompute it once.- Parameters:
observed (array-like) – Either a boolean mask of length
n_features_in_(True = observed), or a list of feature labels to treat as observed.method ({"tsr", "scp", "pmp"}, default="tsr")
ridge (float, default=0.0)
- Returns:
result – With keys
matrix(DataFrame, n_components x n_observed, columns labelled by the observed features),condition_number(float) andmethod.- Return type:
- score(X, y=None)[source]#
Negative mean squared reconstruction error (higher is better).
Follows the sklearn convention where higher scores indicate better model fit. This makes PCA compatible with
cross_val_score,GridSearchCV, and other sklearn model-selection utilities.- Parameters:
X (array-like of shape (n_samples, n_features)) – Test data to score.
y (ignored)
- Returns:
score – Negative mean squared reconstruction error.
- Return type:
Examples
>>> from sklearn.model_selection import cross_val_score >>> scores = cross_val_score(PCA(n_components=2), X_scaled, cv=5) >>> print(f"Mean CV score: {scores.mean():.4f}")
- classmethod minka_mle(X)[source]#
Minka (2000) automatic-dimensionality estimate for PCA.
Closed-form Bayesian model selection on the PPCA evidence (Minka, T. P. 2000. Automatic Choice of Dimensionality for PCA. NIPS 13, pp. 598-604). Operates only on the covariance eigenvalues of
Xand is therefore very cheap; in the simulations Minka reports it beats cross-validation. Use it alongside the ekf-CV recommendation fromselect_n_components()as a fast cross-check.Internally
Xis mean-centred before estimation (a PPCA assumption); it is not unit-variance scaled, because dividing each column by its standard deviation compresses the noise eigenvalues to near-zero values the MLE misreads as additional latent signal. If your columns are on wildly different scales, pass the analysis-scaleXproduced by your own preprocessing (e.g. SNV for spectral data) and accept the centring this method applies.- Parameters:
X (array-like of shape (n_samples, n_features)) – Data matrix.
- Returns:
n_components – The MLE estimate of the effective dimensionality.
- Return type:
References
Minka, T. P. (2000). Automatic Choice of Dimensionality for PCA. Advances in Neural Information Processing Systems, 13, 598-604.
See also
parallel_analysisHorn (1965) eigenvalue-vs-null retention.
select_n_componentsekf cross-validation; pass
return_consensus=Trueto report all three side by side.
- classmethod parallel_analysis(X, *, n_simulations=200, quantile=0.95, surrogate='normal', scale=True, random_state=None)[source]#
Horn (1965) parallel analysis component-count estimate.
Generates
n_simulationsrandom matrices of the same shape asX, computes their eigenvalues, and retains every observed component whose eigenvalue exceeds thequantileof the null distribution at the same rank. Widely regarded in psychometrics as the best simple retention rule for PCA.- Parameters:
X (array-like of shape (n_samples, n_features)) – Data matrix.
n_simulations (int, default 200) – Number of random matrices drawn to build the null eigenvalue distribution.
surrogate ({"normal", "permutation"}, default "normal") –
How the null matrices are built.
"normal": independent standard-normal entries, which is Horn’s original proposal. Fast, and exactly right when the columns really are Gaussian."permutation": each column of the real data is permuted independently (Buja and Eyuboglu, 1992). This breaks the correlation between columns, which is what parallel analysis is testing for, while leaving every column’s own distribution untouched. Prefer it on process data, where a tag may be skewed, heavy-tailed, bounded at zero or quantised by its instrument: a Gaussian null then answers a question about Gaussian data rather than about this block.
quantile (float, default 0.95) – Quantile of the null eigenvalues used as the retention threshold. Horn’s original proposal was the mean (0.5); the more conservative 95th-percentile threshold is the modern recommendation.
scale (bool, default True) – Mean-centre and unit-variance scale
Xbefore estimation. Unlikeminka_mle(), which is mean-centred but never unit-variance scaled, parallel analysis defaults to autoscaling here so wildly different column scales do not dominate the null comparison.random_state (int, optional) – Seed for the null-matrix simulations.
- Returns:
result – With keys:
n_components- number of components retained (can be 0 on pure noise).observed_eigenvalues- eigenvalues ofXafter centring/scaling (np.ndarray of lengthmin(n, p)).null_threshold- per-rankquantileof the null eigenvalue distribution (same length asobserved_eigenvalues). Plot it againstobserved_eigenvaluesfor the scree-versus-null picture the method is usually read from.surrogate- which null was used, echoed back so a stored result says how it was produced.
- Return type:
References
Horn, J. L. (1965). A rationale and test for the number of factors in factor analysis. Psychometrika, 30(2), 179-185.
Buja, A., & Eyuboglu, N. (1992). Remarks on parallel analysis. Multivariate Behavioral Research, 27(4), 509-540.
See also
minka_mleclosed-form PPCA evidence rule.
select_n_componentsekf cross-validation; pass
return_consensus=Trueto report all three side by side.
- classmethod select_n_components(X, *, max_components=None, cv=5, cv_scheme='ekf', n_repeats=1, selection_rule='min', min_q2_increase=0.01, scale_inside_folds=True, n_iter=50, tol=1e-06, random_state=None, return_consensus=False, threshold=None, **pca_kwargs)[source]#
Select the number of PCA components via cross-validation.
Evaluates every component count
1, 2, ..., max_componentsand recommends one via the configuredselection_rule. The defaultcv_scheme="ekf"is the element-wise k-fold algorithm of Bro, Kjeldahl, Smilde & Kiers (2008, Anal. Bioanal. Chem. 390:1241-1251), which holds out individual cells ofXand predicts them via EM-style imputation from a model that never sees their true values. This restores the prediction-independence requirement the legacy row-wise scheme violates, fixing the trivial-fit pathology where PRESS shrinks monotonically with components.- Parameters:
X (array-like of shape (n_samples, n_features)) – Training data. With the default
scale_inside_folds=Truepass the raw, unscaled X; mean-centring and unit-variance scaling are fit on each fold’s in-fold cells. Pre-scale it yourself only withscale_inside_folds=False.max_components (int, optional) – Maximum number of components to evaluate. Default is
min(n_samples - 1, n_features).cv (int or sklearn CV splitter, default 5) – For
cv_scheme="ekf": the integer number of element-folds (splitter objects are ignored). Forcv_scheme="ek": the number of row groups, and of column groups. Forcv_scheme="row_wise": either an integer K (fed toKFold) or any sklearn splitter. Ignored by"sacv"and"gcv", which hold nothing out.cv_scheme ({"ekf", "ckf", "ek", "sacv", "gcv", "row_wise"}, default "ekf") –
How a held-out value is produced. Every one of these except
"row_wise"keeps the prediction independent of the value being predicted; they differ in what they hold out and what they cost."ekf", the default: element-wise k-fold with EM imputation. Scattered cells are held out and each is imputed from a model that never saw it. Fits its centring and scaling inside every fold, and is the only scheme here that takes a block with missing cells. Cost:n_folds * n_repeats * max_componentsdecompositions."ckf": column-wise k-fold. Groups of columns are held out, and their values are predicted from scores computed on the retained columns only, so no held-out value appears in the score that predicts it. Information still reaches the model through the loadings, which are fitted on the whole block, and that is the respect in which"ekf"is stricter. Offered because a great deal of published chemometrics uses it, so a number that has to line up with a paper may need it, and because on a block with far more columns than samples it costs one decomposition rather thann_folds * n_repeats * max_components."ek": the two-model scheme of Eastment and Krzanowski (1982). An element is predicted by a score from a model without its column and a loading from a model without its row. This is what Simca-P reports and whatpcaMethods::Q2computes by default, so use it when a number has to line up with either. Cost:2 * n_foldsdecompositions."sacv": leave-one-cell-out, approximated by inflating each residual by the leverage of the cell that produced it, after Josse and Husson (2012). The cheap version of holding cells out one at a time. Cost: one decomposition."gcv": the same idea with a single averaged leverage instead of one per cell. Blunter, and it tends to keep more components. Cost: one decomposition. This and"sacv"are the defaults inFactoMineRandmissMDA."row_wise": deprecated, removed in 2.0. See the warning admonition below.
"ek","sacv"and"gcv"factorise the matrix directly, so they raise on a block with missing cells and they ignorescale_inside_folds,n_repeats,n_iterandtol. They also report no per-fold spread, soselection_rule="1se"has nothing to work with;"min"takes the global optimum, whereFactoMineRinstead stops at the first local worsening, which can return a smaller count on the same data.n_repeats (int, default 1) – Repeat the ekf pass with a fresh random fold permutation this many times. Each repeat covers every cell exactly once;
n_repeats > 1narrows the per-component PRESS standard error (helpful when the 1-SE rule sits on a borderline) at roughly linear extra runtime. Ignored undercv_scheme="row_wise".selection_rule ({"min", "1se", "q2_increment"}, default "min") – How the recommended component count is chosen.
"min"is the GlobalMin criterion Bro 2008 pairs with ekf - the component count with the lowest pooled PRESS."1se"is the one- standard-error rule (needsper_fold_press, available under both schemes)."q2_increment"is the Wold’s-R-style cumulative-\(Q^2\) threshold from PR #371;min_q2_increasesets the threshold.min_q2_increase (float, default 0.01) – Threshold used only when
selection_rule="q2_increment".scale_inside_folds (bool, default True) –
With the default, mean-centring and unit-variance scaling constants are fit on each fold’s in-fold cells and applied to the whole matrix before EM, removing the centring/scaling leakage of the prior implementation. Set to
Falseto reproduce the previous behaviour (column mean recomputed each EM iteration from the imputed matrix, no scaling); this is useful only whenXis already pre-scaled, and aSpecificationWarningis emitted because scaling constants fit on the full matrix leak into every element-fold. Ignored undercv_scheme="row_wise".Pass the raw, unscaled X under the default. In-fold re-standardisation overwrites whatever scaling the caller applied, so two deliberately different strategies (autoscale versus Pareto, say) become the same model and report the same PRESS: a comparison between them shows no difference for reasons that have nothing to do with the data. A
SpecificationWarningis emitted whenXarrives already centred and unit-variance scaled, which is the detectable half of that case; a block scaled some other way cannot be recognised, so the rule is the caller’s to keep. Same contract asPLS.select_n_components().n_iter (int and float, default 50 and 1e-6) – EM iteration cap and convergence tolerance for the ekf imputation step. Ignored under
cv_scheme="row_wise".tol (int and float, default 50 and 1e-6) – EM iteration cap and convergence tolerance for the ekf imputation step. Ignored under
cv_scheme="row_wise".random_state (int, optional) – Seed for the ekf element-fold permutation.
return_consensus (bool, default False) – When
True, also cross-check the CV recommendation against two cheap alternative selectors: Minka’s PPCA MLE (minka_mle()) and Horn’s parallel analysis (parallel_analysis()). The result Bunch then gains theminka_n_components,parallel_analysis_n_components,consensus, andconsensus_countskeys (see Returns).threshold (float, optional) – Deprecated. The original Wold PRESS-ratio cutoff. Passing it emits a
DeprecationWarning; the value is ignored. Useselection_rule="q2_increment"(and tunemin_q2_increase) for a comparable parsimony preference.**pca_kwargs – Additional keyword arguments passed to the
PCA()constructor undercv_scheme="row_wise"(e.g.algorithm="nipals"). Ignored undercv_scheme="ekf"because ekf runs its own SVD loop.
- Returns:
result – With keys:
n_components- recommended number of components (int).press- pooled PRESS per component count (pd.Series, indexed1..A_max). Undercv_scheme="ekf"withscale_inside_folds=Truethis is measured in the space each fold was fitted in, so every variable weighs the same; seepress_input_unitsfor the other scale.press_input_units- the same curve in the units of the matrix that was passed in, for comparing prediction error against instrument error (pd.Series, indexed1..A_max).per_fold_press- per-fold PRESS contributions (pd.DataFrame,A_maxrows xn_folds * n_repeatscolumns under ekf; a singlefold_1column under row-wise).se_press- standard error of the per-fold PRESS curve (pd.Series, indexed1..A_max). Drives the 1-SE rule.q2_se- the same standard error rescaled onto the Q2 scale (se_press / null_model_ss, pd.Series indexed1..A_max), i.e. the half-width of a +/-1 SE band aroundq2.press_ratio-PRESS_a / PRESS_{a-1}for inspection (pd.Series, indexed2..A_max).q2- cross-validated \(R^2_X\) per component count (pd.Series, indexed1..A_max). Computed as1 - press / null_model_ss, where the null model predicts each held-out cell by its in-fold column mean, measured the same waypressis. Directly comparable tor2_cumulative_and to PLS’sr2y_validated.q2_per_variable- that same quantity split by variable (pd.DataFrame,A_maxrows xKcolumns), which is what shows whether one column is carrying the pooled figure. AllNaNundercv_scheme="row_wise", which has no per-cell error to split.cv_scores- alias ofper_fold_pressunder ekf, or per-fold negative MSE fromcross_val_scoreunder row-wise (preserved for back-compat).cv_scheme- the scheme used ("ekf"or"row_wise").selection_rule- the rule used to pickn_components.
When
return_consensus=True, the Bunch additionally carries:minka_n_components- the Minka PPCA MLE estimate (int).parallel_analysis_n_components- Horn’s parallel-analysis estimate (int).consensus-"agree"if the three integer estimates (CV recommendation, Minka, parallel analysis) span at most 1, otherwise"disagree".consensus_counts- the tuple(recommended, minka_n, parallel_analysis_n).
- Return type:
References
Bro, R., Kjeldahl, K., Smilde, A. K., & Kiers, H. A. L. (2008). Cross-validation of component models: a critical look at current methods. Anal. Bioanal. Chem., 390(5), 1241-1251.
Camacho, J., & Ferrer, A. (2012). Cross-validation in PCA models with the element-wise k-fold (ekf) algorithm: theoretical aspects. J. Chemometrics, 26(7), 361-373.
Eastment, H. T., & Krzanowski, W. J. (1982). Cross-validatory choice of the number of components from a principal component analysis. Technometrics, 24(1), 73-77.
Josse, J., & Husson, F. (2012). Selecting the number of components in principal component analysis using cross-validation approximations. Computational Statistics & Data Analysis, 56(6), 1869-1879.
Warning
cv_scheme="row_wise"is deprecated since 1.84 and will be removed in 2.0. It emits aDeprecationWarningand aSpecificationWarning. It suffers from the trivial-fit problem: holding out whole rows and projecting them back viatransform()lets the held-out row’s own values reach its prediction, so PRESS shrinks monotonically with the component count and reaches zero once the components equal the variables. It measures compression, not prediction, and cannot select a component count. Use"ekf","ek","sacv"or"gcv".
- detect_outliers(conf_level=0.95)[source]#
Detect outlier observations using SPE and Hotelling’s T² diagnostics.
Combines two approaches:
Statistical limits - observations exceeding the SPE or T² limit at
conf_levelare flagged.Robust ESD test - the generalized ESD test identifies observations that are unusual relative to the rest of the data, even if they fall below the statistical limit. The mean/std variant is used here; the underlying
detect_outliers_esdalso offers an opt-in robust median/MAD variant.
An observation can be flagged for one or both reasons.
- Parameters:
conf_level (float, default 0.95) – Confidence level in [0.8, 0.999]. Controls both the statistical limits and the ESD test’s significance level (alpha = 1 - conf_level).
- Returns:
outliers – Sorted from most severe to least. Each dict contains:
observation- index label of the observationoutlier_types- list of"spe"and/or"hotellings_t2"spe- SPE value for this observationhotellings_t2- T² value for this observationspe_limit- SPE limit at the given confidence levelhotellings_t2_limit- T² limit at the given confidence levelseverity- max(spe/spe_limit, t2/t2_limit), rounded to 4 decimals. A ratio whose denominator is 0 (perfect-fit SPE limit) or non-finite (T2 limit when A == N) is treated as 0 and does not contribute to the ranking.
- Return type:
Examples
>>> pca = PCA(n_components=3).fit(X_scaled) >>> outliers = pca.detect_outliers(conf_level=0.95) >>> for o in outliers: ... print(f"{o['observation']}: {o['outlier_types']} (severity={o['severity']})")
PLS#
- class process_improve.multivariate.methods.PLS(n_components, *, scale=True, max_iter=1000, tol=np.float64(1.4901161193847656e-08), copy=True, warn_on_uncentred=True, missing_data_settings=None)[source]#
Bases:
_LatentVariableModel,RegressorMixin,TransformerMixin,BaseEstimatorProjection to Latent Structures (PLS) regression with diagnostics.
Implements PLS via the NIPALS algorithm with production diagnostics: SPE, Hotelling’s T², score contributions, and outlier detection. The API mirrors
PCAso thatmodel.scores_,model.spe_, andmodel.detect_outliers()work identically for both model types.- Parameters:
n_components (int) – Number of latent components to extract.
Noneasks for as many components as the data supports: it is resolved at fit time tomin(n_samples, n_features), which is also the ceiling an explicit request is clamped to (with aSpecificationWarning). The resolved count is available on the fitted attributen_components_.scale (bool, default=True) –
Mean-center and unit-variance-scale both the X and Y blocks internally before fitting (
ddof=1, done withMCUVScaler). This mirrorssklearn.cross_decomposition.PLSRegression, whosescale=Truedefault also scales X and Y; the parameter exists soPLSis a drop-in for the sklearn estimator. Predictions,predictions_andbeta_coefficients_are returned on the original (un-scaled) data scale. When you scale externally (e.g. withMCUVScaler), setscale=Falseto avoid the (idempotent) double scaling. Note: the cross-validation helpers (select_n_components()) always re-fit anMCUVScalerinside each training fold regardless of this flag.scale=Falsefits no intercept, so both blocks must already be centred. A response left on its natural scale is the trap: predictions come out offset by the response mean, and R² / Q² go large and negative on data that does contain a relationship.fitemits anUncentredDataWarningwhen either block’s column means are large relative to their spread; it does not centre for you, becausescale=Falsemeans “touch nothing”. Setwarn_on_uncentred=Falsewhen that fit is deliberate.max_iter (int, default=1000) – Maximum number of iterations for the NIPALS algorithm.
tol (float, default=sqrt(machine epsilon)) – Relative convergence tolerance for the NIPALS algorithm: the change between two successive score-vector iterations, relative to the norm of the current score vector (see
terminate_check()).copy (bool, default=True) – Whether to copy X and Y before fitting.
warn_on_uncentred (bool, default=True) –
Emit the
UncentredDataWarningdescribed underscalewhenscale=Falseand a block arrives un-centred. Set it toFalsefor a fit that is un-centred on purpose (a demonstration of the offset, or a test that some other centring check fires), where the diagnostic is the expected outcome rather than a problem.This is the narrowest of the three opt-outs, and the one to reach for first. It silences the check for this model only, so an unrelated
SpecificationWarningraised elsewhere in the same block, or by this samefitcall, still arrives. FilteringUncentredDataWarningas a category is next narrowest; suppressing all ofSpecificationWarningis the blunt instrument, and hides clamped component counts and NIPALS non-convergence along with it.Has no effect when
scale=True: the model centres both blocks itself, so the condition cannot arise. Like every constructor parameter it is stored verbatim and survivesclone(), so a deliberately un-centred fit stays quiet inside aPipelineor a grid search.missing_data_settings (dict or None, default=None) –
Settings for the NIPALS fit when the data has missing cells. Keys:
md_method:"nipals"(the default, and the only one implemented), or"tsr"/"pmp", which are recognised and raiseNotImplementedError. Any other value is refused. This is a different and smaller set than themethod=accepted byproject()and the contribution helpers, which do implement"tsr","scp"and"pmp".md_tolandmd_max_iter: the NIPALS convergence tolerance and iteration cap. They default to this model’stolandmax_iter, so set those instead unless you need the fit and the missing-data path to differ.
fitting) (Attributes (after)
--------------------------
n_components – The resolved number of components actually fitted (the constructor parameter clamped to
min(n_samples, n_features); the parameter itself is left as the user set it, includingNone).scores (pd.DataFrame of shape (n_samples, n_components)) – X-block score matrix (T). This is the primary score matrix; equivalent to
x_scoresin older versions.y_scores (pd.DataFrame of shape (n_samples, n_components)) – Y-block score matrix (U).
x_loadings (pd.DataFrame of shape (n_features, n_components)) – X-block loading matrix (P).
y_loadings (pd.DataFrame of shape (n_targets, n_components)) – Y-block loading matrix (C).
x_weights (pd.DataFrame of shape (n_features, n_components)) – X-block weight matrix (W).
y_weights (pd.DataFrame of shape (n_targets, n_components)) – Y-block weight matrix.
direct_weights (pd.DataFrame of shape (n_features, n_components)) – Direct (W*) weights:
W (P'W)^{-1}. Used for direct projectionT = X @ W*.beta_coefficients (pd.DataFrame of shape (n_features, n_targets)) – Regression coefficients linking X directly to Y.
predictions (pd.DataFrame of shape (n_samples, n_targets)) – Y predictions from the training data.
spe (pd.DataFrame of shape (n_samples, n_components)) – Per-row SPE diagnostic; stored as the square root of the row sum-of-squared X-residuals (so it is on the residual scale, not the squared scale). One column per component, not one value per row: column
ais the SPE of the model truncated atacomponents, and the last column is the value at the full fitted model. Reach for a single number per observation withmodel.spe_.iloc[:, -1], not withnp.asarray(model.spe_).ravel(): ravel happens to give the right answer at one component and silently givesn_samples * n_componentsvalues above it.hotellings_t2 (pd.DataFrame of shape (n_samples, n_components)) – Cumulative Hotelling’s T² statistic. Per-component, exactly as
spe_above: columnauses the firstacomponents and the last column is the value at the full fitted model.r2_per_component (pd.Series of length n_components) – Fractional R² (on Y) explained by each component.
r2_cumulative (pd.Series of length n_components) – Cumulative R² (on Y) after each component.
r2_per_variable (pd.DataFrame of shape (n_features, n_components)) – Per-variable cumulative R² for X after each component.
r2y_per_variable (pd.DataFrame of shape (n_targets, n_components)) – Per-variable R² for Y after each component.
rmse (pd.DataFrame of shape (n_targets, n_components)) – Root mean squared error of Y predictions per component, on the original (un-scaled) Y scale, consistent with
predictions_andprediction_interval.explained_variance (np.ndarray of shape (n_components,)) – Variance explained by each component in X.
scaling_factor_for_scores (pd.Series of length n_components) – Standard deviation per score (sqrt of explained variance).
has_missing_data (bool) – Whether the training data contained missing values.
fitting_info (dict) – Timing and iteration info from the fitting algorithm.
See also
PCAPrincipal Component Analysis.
MCUVScalerMean-center unit-variance scaler.
References
Abdi, “Partial least squares regression and projection on latent structure regression (PLS Regression)”, 2010, DOI: 10.1002/wics.51
Examples
>>> import pandas as pd >>> from process_improve.multivariate.methods import PLS, MCUVScaler >>> X = pd.DataFrame({"A": [1, 2, 3, 4], "B": [4, 3, 2, 1]}) >>> Y = pd.DataFrame({"y": [2.1, 3.9, 6.2, 7.8]}) >>> pls = PLS(n_components=1) >>> pls = pls.fit(MCUVScaler().fit_transform(X), MCUVScaler().fit_transform(Y)) >>> pls.scores_.shape (4, 1)
- get_feature_names_out(input_features=None)[source]#
Return the output column names of
transform().PLS’stransformreturns the X scores (T matrix), labelled["T1", "T2", ..., "T{n_components}"]. Theinput_featuresargument is accepted (Pipeline introspection passes it through) but unused: the output column count is the fittedn_components, not the input feature count.Used by
set_output()(sklearn 1.2+) to label theDataFrameview of the scores whenset_output(transform="pandas")is on, and by Pipeline introspection.- Return type:
- predictions_vs_observed_plot(*, y_observed, variable=None, settings=None, fig=None)#
Generate an observed-vs-predicted (parity) plot for a fitted PLS model.
Plots the calibration predictions against the observed Y values, with a
y = xreference line and an RMSE annotation. Points lying close to the reference line indicate good predictions.- Parameters:
model (PLS object) – A fitted PLS model generated by this library.
y_observed (array-like of shape (n_samples, n_targets)) – The observed Y values, on the same scale as the data used to fit the model (for example the scaled Y from
MCUVScaler).variable (str, optional) – Which Y-variable to plot. Defaults to the first Y-variable.
settings (dict) –
Default settings:
{ "title": "Observed vs predicted ...", # str: overall plot title "marker_color": None, # str|None: data-marker colour; None uses the theme "reference_color": "#9CA3AF", # str: colour of the y = x line "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 1.0, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Return type:
Figure
Examples
>>> pls.predictions_vs_observed_plot(y_observed=Y_scaled) >>> pls.predictions_vs_observed_plot(y_observed=Y_scaled, variable="quality")
- coefficient_plot(variable=None, settings=None, fig=None)#
Generate a bar plot of the PLS regression coefficients.
Shows
beta_coefficients_for one Y-variable: one bar per X-variable, mapping the (preprocessed) X onto the predicted Y. Tall bars mark the X-variables that most strongly drive the prediction.- Parameters:
model (PLS object) – A fitted PLS model generated by this library.
variable (str, optional) – Which Y-variable’s coefficients to plot. Defaults to the first one.
settings (dict) –
Default settings:
{ "title": "Regression coefficients ...", # str: overall plot title "bar_color": None, # str|None: bar colour; None uses the theme "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Return type:
Figure
Examples
>>> pls.coefficient_plot() >>> pls.coefficient_plot(variable="quality")
- target_projection(X, response=None)#
Target-projected (TP) component of a fitted PLS model for one response.
Target projection (Kvalheim and Karstang, 1989) rotates the PLS solution so that a single latent component carries all of the predictive information for one response. The component points along the regression vector \(b\) (the column of
beta_coefficients_for that response):\[w_{\text{TP}} = \frac{b}{\lVert b \rVert}, \qquad t_{\text{TP}} = X\, w_{\text{TP}}, \qquad p_{\text{TP}} = \frac{X^\top t_{\text{TP}}}{t_{\text{TP}}^\top t_{\text{TP}}}.\]The TP component is the basis for the selectivity ratio (
selectivity_ratio()).- Parameters:
model (PLS) – A fitted PLS model (must expose
beta_coefficients_).X (array-like of shape (n_samples, n_features)) – Preprocessed data, scaled the same way as the training data (for example with
MCUVScaler).response (str or int or None, default=None) – Which response (Y column) to project onto.
Noneis allowed only for a single-response model; otherwise pass the response label (or its integer position).
- Returns:
With fields
scores(pd.Series, the TP scores per sample),loadings(pd.Series, the TP loading per feature),weights(pd.Series, the unit TP weight per feature) andresponse(the resolved response label).- Return type:
- Raises:
ValueError – If the model is not a fitted PLS, the response selector is invalid, or the regression vector / TP scores are degenerate (~0).
References
Kvalheim, O. M. and Karstang, T. V. (1989). Interpretation of latent-variable regression models. Chemometrics and Intelligent Laboratory Systems, 7(1-2), 39-51.
Examples
>>> pls = PLS(n_components=3).fit(X_scaled, y_scaled) >>> tp = pls.target_projection(X_scaled) # bound convenience method >>> tp.scores.head()
See also
selectivity_ratioPer-variable explained/residual ratio on the TP component.
- selectivity_ratio(X, response=None, *, conf_level=0.95)#
Compute the selectivity ratio of each feature on the target-projected component.
The selectivity ratio (Rajalahti et al., 2009) ranks each feature by how much of its variance the predictive (target-projected) direction explains. On the TP component (
target_projection()), for feature \(j\):\[\text{SR}_j = \frac{\text{SS}_{\text{explained},j}} {\text{SS}_{\text{residual},j}} = \frac{p_{\text{TP},j}^2\, (t_{\text{TP}}^\top t_{\text{TP}})} {\sum_i (x_{ij} - t_{\text{TP},i}\, p_{\text{TP},j})^2}.\]A large SR means the feature is well aligned with the predictive direction. Unlike VIP, it is a true explained/residual variance ratio and can be compared against an F distribution. Note that two collinear features carry near-identical SR: the selectivity ratio ranks predictive relevance, it does not break ties between mutually collinear features.
- Parameters:
model (PLS) – A fitted PLS model (must expose
beta_coefficients_).X (array-like of shape (n_samples, n_features)) – Preprocessed data, scaled the same way as the training data (for example with
MCUVScaler).response (str or int or None, default=None) – Which response to compute SR for.
Nonereturns a feature-by-response DataFrame when the model has several responses, or a Series for a single-response model.conf_level (float, default=0.95) – Confidence level for the advisory F-based critical value, attached to the result’s
.attrs["f_critical"].
- Returns:
Selectivity ratios indexed by feature. A Series for one response (with
f_critical/conf_level/responsein.attrs), or a feature-by-response DataFrame whenresponseisNoneand the model has several responses.- Return type:
pd.Series or pd.DataFrame
- Raises:
ValueError – If the model is not a fitted PLS or the response selector is invalid.
References
Rajalahti, T., Arneberg, R., Berven, F. S., Myhr, K.-M., Ulvik, R. J. and Kvalheim, O. M. (2009). Biomarker discovery in mass spectral profiles by means of selectivity ratio plot. Chemometrics and Intelligent Laboratory Systems, 95(1), 35-48.
Examples
>>> pls = PLS(n_components=3).fit(X_scaled, y_scaled) >>> pls.selectivity_ratio(X_scaled).sort_values(ascending=False).head()
See also
target_projectionThe target-projected component the ratio is built on.
vipVariable Importance in Projection, an alternative importance measure.
- scores_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- spe_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- x_loadings_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- x_weights_#
Expose a private ndarray as a lazily-built, cached
pandas.DataFrame(ENG-18).Declared as a class attribute, e.g.:
scores_ = _LazyFrame("_scores", index="_sample_index", columns="_component_names") loadings_ = _LazyFrame("_loadings", index="_feature_names", columns="_component_names")
The private ndarray (
self._scores) is the source of truth; the publicDataFrameis built on first access from the ndarray plus the index/column metadata attributes, cached inself.__dict__["_frame_cache"](so repeated access returns the same object and is cheap), and excluded from pickling by_LatentVariableModel.__getstate__(). Internal math reads the ndarray directly and avoids the per-call.valuesconversion.On an unfitted model the backing ndarray is absent, so
getattrraisesAttributeError- the same “not fitted” signal as before this change, sohasattr/check_is_fittedbehave identically.
- fit(X, Y, sample_weight=None)[source]#
Fit a projection to latent structures (PLS) model to the data.
- Parameters:
X (array-like, shape (n_samples, n_features)) – Training data, where
n_samplesis the number of samples (rows) andn_featuresis the number of features (columns).Y (array-like, shape (n_samples, n_targets)) – Training data, where
n_samplesis the number of samples (rows) andn_targetsis the number of target outputs (columns).sample_weight (array-like of shape (n_samples,), optional) – Non-negative row weights for a weighted PLS fit (#394). NIPALS is run on
sqrt(w)-rescaled X and Y, which is equivalent to weighting the cross-productsX' W uandY' W t. Loadings, weights and beta are computed correctly; scores are returned on the original sample scale. Zero weights effectively exclude the corresponding rows (sample_weight=[1,1,0,0,1]reproduces the unweighted fit on rows[0,1,4]). Forwarded toscore()/r2_scorefor any caller that also threads it through.
- Returns:
Model object.
- Return type:
References
Abdi, “Partial least squares regression and projection on latent structure regression (PLS Regression)”, 2010, DOI: 10.1002/wics.51
- transform(X, Y=None)[source]#
Project X (and optionally Y) into the latent space.
- Parameters:
- Returns:
X_scores – Projected X data (scores).
- Return type:
pd.DataFrame of shape (n_samples, n_components)
- fit_transform(X, Y=None)[source]#
Fit the model and return X scores.
- Parameters:
X (array-like of shape (n_samples, n_features))
Y (array-like of shape (n_samples, n_targets)) – Required despite the
Nonedefault: the default is present only for signature-compatibility with the sklearnfit_transformprotocol, and PLS cannot be fitted without responses. OmittingYraisesValueError, underpython -Oas well.
- Returns:
X_scores
- Return type:
pd.DataFrame of shape (n_samples, n_components)
- predict(X)[source]#
Predict Y for new observations.
Returns just the predicted
y_hatso the call satisfies the scikit-learnRegressorMixincontract (and therefore composes insidePipeline,cross_val_score(), andGridSearchCV). For the rich diagnostic view (scores, Hotelling’s T², SPE, plusy_hat), seediagnose().- Parameters:
X (array-like of shape (n_samples, n_features))
- Returns:
y_hat – Predicted target values, indexed by
X’s rows and labelled with the target column names captured duringfit.- Return type:
pd.DataFrame of shape (n_samples, n_targets)
See also
diagnosericher per-prediction diagnostics.
Examples
>>> y_pred = pls.predict(X_new) >>> diag = pls.diagnose(X_new) # for scores / T² / SPE
- diagnose(X)[source]#
Project new data and compute predictions plus diagnostics.
This is the rich view that
predict()used to return before 1.35.0: alongsidey_hatit reports the X scores, cumulative Hotelling’s T², and SPE for every row ofXso the user can flag out-of-model observations and read their predicted Y from one call.- Parameters:
X (array-like of shape (n_samples, n_features))
- Returns:
result – With keys
scores,hotellings_t2,spe,y_hat.- Return type:
See also
predictsklearn-compatible call returning just
y_hat.
Examples
>>> result = pls.diagnose(scaler_x.transform(X_new)) >>> result.y_hat # Predicted Y values >>> result.spe # SPE for each new observation >>> result.hotellings_t2 # T² for each new observation
- project(X, *, method='tsr', ridge=0.0)[source]#
Estimate scores, prediction and diagnostics for rows with missing values.
Whereas
transform()anddiagnose()propagate NaN into the scores, this method estimates the scores of partially-observed rows from the observed columns only, using the missing-data estimators of Arteaga and Ferrer (2002): trimmed score regression ("tsr", the default and statistically the strongest), single-component projection ("scp", the score step of NIPALS itself: project onto the observed part of each weight vector, deflate with the loadings), or projection to the model plane ("pmp"). Rows with no missing values take the standard complete-data path, so their scores are bitwise identical totransform(). As the observed part grows to the whole row, TSR and SCP tend to the model’s own scores; PMP, the least-squares fit of the observed columns onto the loadings, does not for a PLS model, whose scores come from the weights rather than the loadings, so prefer the other two here.This is the “batch so far” primitive for predicting the final quality of a running batch: the future part of the unfolded row is missing by construction, and the prediction at each decision point is this projection followed by the ordinary Y regression (Garcia-Munoz, Kourti and MacGregor, 2004; Flores-Cerrillo and MacGregor, 2004).
- Parameters:
X (array-like of shape (n_samples, n_features)) – New observations on the original (unscaled) X units when the model was fitted with
scale=True, or in the model’s scaled space otherwise, exactly astransform()expects. NaN marks a missing entry; rows that are entirely NaN are rejected.method ({"tsr", "scp", "pmp"}, default="tsr") – The score estimator; see
process_improve.multivariate._projection.ridge (float, default=0.0) – Non-negative regularisation added to the matrix inverted by the
"tsr"and"pmp"estimators. Raise it above zero whencondition_numberreports near-singularity (typically very early in a batch, when few columns are observed).
- Returns:
result – With keys
scores(DataFrame),y_hat(DataFrame, on the original Y units),hotellings_t2(Series; total over all components),spe(Series; square root of the residual sum of squares over the observed columns only),condition_number(Series; 1.0 when nothing is missing) andn_observed(Series). SPE and T2 of a partially-observed row must be compared against limits built from the same missingness pattern, not the full-observation limits.- Return type:
- projection_matrix(observed, *, method='tsr', ridge=0.0)[source]#
Build the fixed linear operator mapping observed columns to score estimates.
For a fixed missingness pattern, every estimator in
project()is a fixed linear mapt_hat = M @ z_observedon the model’s scaled X space. This method exposes that matrix so callers that reuse one pattern many times (an online monitor at time samplek, or a mid-course optimiser treating the candidate future columns as observed) can precompute it once. Note the matrix acts on scaled values: when the model was fitted withscale=True, apply the internal centring and scaling first (asproject()does).- Parameters:
observed (array-like) – Either a boolean mask of length
n_features_in_(True = observed), or a list of feature labels to treat as observed.method ({"tsr", "scp", "pmp"}, default="tsr")
ridge (float, default=0.0)
- Returns:
result – With keys
matrix(DataFrame, n_components x n_observed, columns labelled by the observed features),condition_number(float) andmethod.- Return type:
- invert(y_desired, *, null_space_coordinates=None)[source]#
Invert the PLS model: find inputs that yield a desired response.
PLS is normally used in the forward direction (
predict()): given inputsX, predict the responseY. Model inversion runs the model backwards: fix the response you want (y_desired) and solve for an input vector that the model predicts will achieve it. This is the basis of latent-variable product and process design (Jaeckle and MacGregor, 2000).Because a PLS model usually retains more components
Athan the rankrof the response, the target pins down onlyrof theAscore directions and the inversion is underdetermined: a whole(A - r)-dimensional family of input vectors yields the same prediction. That family is the null space. This method returns the minimum-norm (direct-inversion) solution together with an orthonormal basis for the null space, so callers can move along it to satisfy secondary criteria (cost, safety, operability) without changing the predicted response.For a single response (
r = 1), García-Carrión et al. (2025) proved that this null space is the same linear space as the orthogonal space isolated by an O-PLS model with the same total number of components.- Parameters:
y_desired (float, array-like, pandas Series/DataFrame, or dict) – The desired response, on the original (un-scaled) Y scale. A scalar is accepted for a single-target model; otherwise supply one value per target. A Series/DataFrame/dict is aligned to the fitted target names; a plain array must follow the fitted target order.
null_space_coordinates (np.ndarray, optional) – Coordinates along the null-space basis, of length
A - r(the null-space dimension). When given, the returned solution istau_direct_inversion + null_space_basis @ null_space_coordinatesreconstructed into the input space. All such solutions yield the same predicted response. When omitted, the minimum-norm (direct-inversion) solution is returned.
- Returns:
result – With keys:
x_newpd.Series of shape (n_features,)The estimated input vector, on the original (un-scaled) X scale.
scorespd.Series of length AThe score vector (tau) of the solution.
y_hatpd.Series of length n_targetsThe model’s prediction at
x_new; equalsy_desiredup to numerical error, a check that the inversion is consistent.null_space_basispd.DataFrame of shape (A, A - r)Orthonormal basis of the null space, in score coordinates. Empty (zero columns) when
A == rand the solution is unique.null_space_dimensionintA - r, the number of free directions.hotellings_t2floatHotelling’s T² of the solution, to flag extrapolation beyond the calibration data. Compare against
hotellings_t2_limit().
- Return type:
See also
predictthe forward direction, X -> Y.
hotellings_t2_limitconfidence limit to judge
hotellings_t2.
References
C. M. Jaeckle and J. F. MacGregor, “Industrial applications of product design through the inversion of latent variable models”, Chemometrics and Intelligent Laboratory Systems, 50 (2000): 199-210, DOI: 10.1016/S0169-7439(99)00058-1.
S. García-Carrión et al., “On the equivalence between null space and orthogonal space in latent variable regression modeling”, Journal of Chemometrics, 39 (2025): e70057, DOI: 10.1002/cem.70057.
Examples
>>> result = pls.invert(y_desired=25.0) >>> result.x_new # input vector giving the target response >>> result.null_space_basis # directions that leave the response fixed >>> pls.predict(result.x_new.to_frame().T) # ~= 25.0
- classmethod select_n_components(X, Y, *, max_components=None, cv=5, n_repeats=None, random_state=None, selection_rule='1se', scale_inside_folds=True, min_q2_increase=0.01, n_permutations=999, alpha=0.01, stability_threshold=0.6, **pls_kwargs)[source]#
Select the number of PLS components via cross-validation.
Fits PLS models on cross-validation training folds and evaluates the out-of-fold prediction error for every component count
1, 2, ..., max_components. Reports per-fold and pooled RMSECV plus the validated cumulative R² curves, and recommends a component count from one of three rules (seeselection_rulebelow).The defaults are the research-backed combination: the one-standard-error rule on top of repeated, shuffled K-fold CV, with
MCUVScalerre-fit inside every training fold so test data never leaks into the centring/scaling estimates.Unlike the calibration statistics stored on a fitted model (
rmse_,r2_cumulative_), the metrics returned here estimate performance on unseen data and are therefore suitable for choosingn_components.- Parameters:
X (array-like of shape (n_samples, n_features)) – Training X. With the default
scale_inside_folds=Truethe raw, unscaled X may be passed; scaling is fit inside every training fold.Y (array-like of shape (n_samples, n_targets)) – Training Y. Same treatment as
Xunderscale_inside_folds.max_components (int, optional) – Maximum number of components to evaluate. Default is the largest value supported by every cross-validation training fold,
min(min_fold_size, n_features).cv (int or sklearn CV splitter, default 5) – If an integer, used as the
n_splitsof a shuffledKFold(orRepeatedKFoldwhenn_repeats > 1). Any sklearn splitter object (e.g.KFold(10, shuffle=True)orLeaveOneOut()) is also accepted and is used as-is (n_repeatsis then ignored).n_repeats (int, optional) – Number of times the K-fold split is repeated with a fresh shuffle, used only when
cvis an integer. The signature default isNone, which is resolved to10inside the function (giving acv * 10per-fold sample for the 1-SE rule); pass1to disable repeats. Repeated K-fold’s standard errors are slightly optimistic because test folds overlap across repeats; that is fine for the 1-SE selection rule but should not be reported as an unbiased generalisation variance.random_state (int, optional) – Seed forwarded to
KFold/RepeatedKFoldfor reproducible shuffling. Ignored whencvis a pre-built splitter.selection_rule ({"1se", "min", "q2_increment", "randomization"}, default "1se") – How the recommended component count is chosen. See
SelectionRulefor the rule semantics."1se"is the default;"min"is the argmin RMSECV (the pre-1.28 default, prone to running to the maximum component count);"q2_increment"is the Wold’s-R-style cumulative-Q² threshold;"randomization"is Van der Voet’s (1994) permutation test (usesn_permutationsandalpha) that picks the smallest model whose predictive ability is statistically indistinguishable from the reference (argmin RMSECV) one.scale_inside_folds (bool, default True) –
When True (the default), fit a fresh
MCUVScaleron each training fold’s X and Y, apply it to the held-out rows, fit PLS in scaled space, then inverse-transform the predictions so RMSECV is reported on the original Y scale. This removes the centring / scaling leakage of the prior default. Set to False to keep the pre-1.28 behaviour, in which caseXandYshould already be scaled; aSpecificationWarningis emitted.Pass the raw, unscaled blocks under the default. In-fold re-standardisation overwrites whatever scaling the caller applied, so two deliberately different strategies (autoscale versus Pareto, say) become the same model and report RMSECV identical to several decimal places: a comparison between them shows no difference for reasons that have nothing to do with the data. A
SpecificationWarningis emitted whenXarrives already centred and unit-variance scaled, which is the detectable half of that case; a block scaled some other way cannot be recognised, so the rule is the caller’s to keep.min_q2_increase (float, default 0.01) – Threshold used only when
selection_rule="q2_increment": the smallest increase in cumulative validated \(Q^2_Y\) that justifies keeping an extra component.n_permutations (int, default 999) – Used only when
selection_rule="randomization": number of sign-flip permutations driving the Van der Voet test.alpha (float, default 0.01) – Used only when
selection_rule="randomization": significance level. The smallest component count whose Van der Voet p-value exceedsalphais recommended. R’spls::selectNcompuses the same default; smaller values pick more parsimonious models.stability_threshold (float, default 0.6) – For the per-repeat stability-selection diagnostic (
"1se"/"min"rules withn_repeats > 1only): the recommendation is judgedselection_is_stable=Trueiff the modal vote share inselection_distributionis at least this fraction. Meinshausen & Bühlmann (2010, JRSS-B) suggest 0.6-0.9 for their variable-selection analogue; we default to the permissive end.**pls_kwargs – Additional keyword arguments passed to the
PLS()constructor (e.g.missing_data_settings).
- Returns:
result – With keys:
n_components- recommended number of components (int).rmsecv- pooled RMSECV per component count (pd.DataFrame, indexed1..A; columns are the Y-variable names plus"total").per_fold_rmsecv- per-fold total RMSECV (pd.DataFrame, indexed1..A; one column per fold across all repeats). Drives the 1-SE rule.se_rmsecv- standard error of the per-fold RMSECV per component count (pd.Series, indexed1..A).q2_se- standard error on the Q2 scale (the per-fold total PRESS standard error divided by the total Y sum-of-squares), i.e. the half-width of a +/-1 SE band aroundr2y_validated["total"](pd.Series, indexed1..A).r2y_validated- validated cumulative \(R^2_Y\) (pd.DataFrame, indexed1..A; one column per Y-variable, then"total"and"scaled_total")."total"pools the targets on the original Y scale, so a wide-ranging target dominates it;"scaled_total"weights every target equally, which is the pooling a fitted model’sr2_y_cumulative_uses, so those two are the columns to compare fitted against held-out.r2x_validated- validated cumulative \(R^2_X\) (pd.DataFrame, indexed1..A; columns are the X-variable names plus"total").press- pooled Y prediction error sum of squares per component count (pd.Series, indexed1..A).cv_predictions- out-of-fold predictions of Y at the recommended component count, on the original Y scale (pd.DataFrame). For repeated K-fold, the first repeat’s held-out predictions are reported so each row appears exactly once.selection_rule- the rule used to pickn_components.randomization_pvalues- per-component Van der Voet right-tail p-values whenselection_rule="randomization";Noneotherwise.selection_distribution- per-repeat vote share over candidate component counts (pd.Series indexed1..A). Populated only forselection_rule in {"1se", "min"}andn_repeats > 1;Noneotherwise. A concentrated distribution signals a confident recommendation; a flat or multi-modal one flags it for review.selection_mode- the most-voted component count, orNonewhenselection_distributionisNone.selection_is_stable-Trueiff the modal vote share meetsstability_threshold;Nonewhen no distribution was computed.
- Return type:
Notes
The pooled RMSECV in
rmsecv["total"]is the square root of the total PRESS over all fold-test rows divided by(N_eff * M)whereN_eff = N * n_repeatsunder repeated CV; theper_fold_rmsecvcolumn for fold f is the square root of fold-f’s sum-of-squared residuals over its own test rows.References
Breiman, Friedman, Olshen & Stone (1984), CART, sec.3.4.3 (1-SE rule). Hastie, Tibshirani & Friedman, ESL, sec.7.10. Kohavi (1995, IJCAI) recommends 10-fold stratified CV for model selection.
Examples
>>> from sklearn.model_selection import KFold >>> # Default: 1-SE on 10 x 5-fold repeated CV with in-fold scaling. >>> result = PLS.select_n_components(X, Y, max_components=6, random_state=0) >>> result.n_components, result.selection_rule >>> # Opt-in to the older argmin-RMSECV rule: >>> PLS.select_n_components(X, Y, max_components=6, selection_rule="min") >>> # Caller-supplied splitter (n_repeats is ignored here): >>> PLS.select_n_components(X, Y, cv=KFold(10, shuffle=True, random_state=0))
- classmethod nested_cv(X, Y, *, max_components=None, outer_cv=5, inner_cv=5, n_inner_repeats=10, selection_rule='1se', scale_inside_folds=True, min_q2_increase=0.01, n_permutations=999, alpha=0.01, random_state=None, **pls_kwargs)[source]#
Nested cross-validation for an honest PLS performance estimate.
Outer loop splits the data into outer-train / outer-test; the inner loop runs
select_n_components()on the outer-train (with the configuredselection_ruleoverinner_cv * n_inner_repeatsfolds) to pick the component count; a final PLS is fit on the outer-train at that count and used to predict the outer-test. The accumulated out-of-fold predictions give RMSEP that is not optimism-biased by the selection decision - the headline number to report when a clean test set is not available.- Parameters:
X (array-like) – Training data. Treated as in
select_n_components()(raw ifscale_inside_folds=True, pre-scaled otherwise).Y (array-like) – Training data. Treated as in
select_n_components()(raw ifscale_inside_folds=True, pre-scaled otherwise).max_components (int, optional) – Forwarded to the inner
select_n_components().outer_cv (int or sklearn splitter, default 5) – Number of outer folds (or a custom splitter).
inner_cv (int, default 5) – Number of inner folds passed to
select_n_components().n_inner_repeats (int, default 10) – Number of inner-CV repeats per outer fold; the inner
random_stateis offset by the outer-fold index so each outer fold sees a fresh inner shuffle.selection_rule (str, default "1se") – Selection rule applied inside the inner loop. See
SelectionRule.scale_inside_folds (bool, default True) – Mirrors
select_n_components(). Also applied to the final outer-train fit, with the test-fold predictions inverse- transformed to the original Y scale before RMSEP accumulates.min_q2_increase (float) – Forwarded to the inner
select_n_components()per rule.n_permutations (int) – Forwarded to the inner
select_n_components()per rule.alpha (float) – Forwarded to the inner
select_n_components()per rule.random_state (int, optional) – Seed for the outer-fold shuffle and the inner CV. The inner seed is offset per outer fold so each outer split sees a fresh shuffled inner CV.
**pls_kwargs – Forwarded to
PLSfor both the inner CV and the final outer-train fits.
- Returns:
result – With keys:
rmsep- honest held-out RMSEP per Y column plus a"total"entry (pd.Series).q2y- validated \(Q^2_Y\) per Y column plus"total"(pd.Series).cv_predictions- out-of-fold predictions of Y at the per-outer-fold selected component counts (pd.DataFrame on the original Y scale).selected_components_per_fold- list of inner recommendations, one per outer fold.selected_components_distribution- vote share over candidate counts (pd.Series).
- Return type:
Notes
Runtime is roughly
outer_cv * inner_cv * n_inner_repeats * max_componentsPLS fits. With the defaults that is 5 * 5 * 10 * max_components fits per call; formax_components=10and a moderate dataset that completes in seconds. Dropn_inner_repeatsif you need to bring it down further.Examples
>>> from process_improve.multivariate import PLS >>> result = PLS.nested_cv(X, Y, max_components=8, random_state=0) >>> result.rmsep["total"]
- detect_outliers(conf_level=0.95)[source]#
Detect outlier observations using SPE and Hotelling’s T² diagnostics.
Same approach as
PCA.detect_outliers: combines statistical limits with the robust generalized ESD test.- Parameters:
conf_level (float, default 0.95) – Confidence level in [0.8, 0.999].
- Returns:
outliers – Sorted from most severe to least. Each dict contains
observation,outlier_types,spe,hotellings_t2,spe_limit,hotellings_t2_limit,severity.- Return type:
Examples
>>> pls = PLS(n_components=3).fit(X_scaled, Y_scaled) >>> outliers = pls.detect_outliers(conf_level=0.95) >>> for o in outliers: ... print(f"{o['observation']}: {o['outlier_types']}")
- cross_validate(X, Y, *, cv='loo', n_bootstrap=0, conf_level=0.95, random_state=None, show_progress=True, sample_weight=None)[source]#
Cross-validate the PLS model and compute error bars for beta coefficients.
Refits the model on data subsets (jackknife, K-fold, or bootstrap), collects
beta_coefficients_from each refit, and computes confidence intervals. Also returns cross-validated predictions and prediction-error metrics (RMSE, Q²).- Parameters:
X (array-like of shape (n_samples, n_features)) – Predictor matrix (same data used for
fit).Y (array-like of shape (n_samples, n_targets)) – Response matrix (same data used for
fit).cv (int or
"loo", default"loo") –Cross-validation strategy:
"loo"- leave-one-out (jackknife). Produces N resamples.int- number of folds for K-fold CV.
n_bootstrap (int, default 0) – If > 0, use bootstrap resampling instead of CV folds. The value specifies the number of bootstrap rounds. Overrides the
cvparameter when set.conf_level (float, default 0.95) – Confidence level for the beta-coefficient intervals, in (0, 1).
random_state (int or None, default None) – Random seed for reproducibility (K-fold shuffle and bootstrap).
show_progress (bool, default True) – Whether to display a
tqdmprogress bar.sample_weight (np.ndarray of shape (n_samples,), optional) – Per-sample non-negative weights. Threaded into every sub-fit so each resample’s PLS uses the same weighting scheme as the parent model. Default
None(all samples weighted equally).
- Returns:
result – Dictionary-like object with the following keys:
Beta-coefficient uncertainty
- beta_samplesnp.ndarray of shape (n_resamples, n_features, n_targets)
Raw beta coefficients from every resample.
- beta_meanpd.DataFrame of shape (n_features, n_targets)
Mean beta across resamples.
- beta_stdpd.DataFrame of shape (n_features, n_targets)
Standard error of the beta coefficients.
- beta_ci_lowerpd.DataFrame of shape (n_features, n_targets)
Lower bound of the confidence interval.
- beta_ci_upperpd.DataFrame of shape (n_features, n_targets)
Upper bound of the confidence interval.
- significantpd.DataFrame of shape (n_features, n_targets)
Truewhere the confidence interval excludes zero.
Prediction metrics
- y_hat_cvpd.DataFrame of shape (n_samples, n_targets)
Cross-validated predictions (out-of-fold). Only available for jackknife and K-fold;
Nonefor bootstrap.- pressfloat
Prediction Error Sum of Squares (sum over all Y elements). Only for jackknife / K-fold.
- rmse_cvpd.Series of length n_targets
Root-mean-square error per Y variable (cross-validated). Only for jackknife / K-fold.
- q_squaredpd.Series of length n_targets
Cross-validated R² (Q²) per Y variable. Only for jackknife / K-fold.
Metadata
- n_resamplesint
Number of resamples performed.
- methodstr
"jackknife","kfold", or"bootstrap".- conf_levelfloat
The confidence level used.
- Return type:
Examples
>>> from process_improve.multivariate import PLS, MCUVScaler >>> scaler_x = MCUVScaler().fit(X) >>> scaler_y = MCUVScaler().fit(Y) >>> X_s, Y_s = scaler_x.transform(X), scaler_y.transform(Y) >>> pls = PLS(n_components=2).fit(X_s, Y_s) >>> cv_results = pls.cross_validate(X_s, Y_s, cv="loo") >>> cv_results.beta_mean # mean beta across LOO resamples >>> cv_results.significant # which betas are significantly != 0 >>> cv_results.q_squared # cross-validated R²
- prediction_interval(X, *, conf_level=0.95, cv_result=None)[source]#
Prediction interval for the Y predictions of new observations.
The interval combines the residual error variance with the leverage of each new observation in the latent-variable space. For a new observation the prediction-interval half-width on target
mist * s_E[m] * sqrt(1 + 1/N + T2_new / (N - 1))where
s_Eis the residual error standard deviation,T2_newis the Hotelling’s T² of the new observation,Nis the number of calibration samples, andtis the Student-t quantile.- Parameters:
X (array-like of shape (n_new, n_features)) – New observations, pre-processed the same way as the training data.
conf_level (float, default=0.95) – Confidence level for the interval, in (0.5, 1.0).
cv_result (sklearn.utils.Bunch or None, default=None) – The result of
cross_validate(). When supplied, its cross-validated RMSE (rmse_cv) is used for the error variance, which is preferable to the optimistic calibration RMSE used otherwise.
- Returns:
With keys
y_hat(point predictions),lowerandupper(prediction-interval bounds) - each a DataFrame of shape (n_new, n_targets) - andconf_level.- Return type:
- set_fit_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- set_score_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
scoremethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed toscoreif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it toscore.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
PLS-DA#
PLS discriminant analysis: PLS regression against a one-hot class indicator, with
the decision rule, the classifier diagnostics and the permutation test on top.
Everything PLS offers is inherited, so a fitted PLSDA also has scores,
loadings, VIP, Hotelling’s T2 and SPE.
- class process_improve.multivariate.methods.PLSDA(n_components, *, decision_rule='max', priors='empirical', scale=True, max_iter=1000, tol=np.float64(1.4901161193847656e-08), copy=True, missing_data_settings=None, warn_on_uncentred=True)[source]#
Bases:
ClassifierMixin,PLSPLS discriminant analysis: PLS regression against a class-indicator matrix.
The labels are one-hot encoded into an
N x GindicatorY, a PLS model is fitted on it, and a new sample’s class comes from theGpredicted indicator values. Two decision rules are offered, and they answer different questions:"max"Take the largest predicted indicator. Every sample is assigned to exactly one class. This is the usual default and is what most PLS-DA software does.
"bayes"Take the largest class posterior, built from Gaussians fitted to the training indicator values of each class (in-class and out-of-class) and weighted by the class priors.
This is the rule to prefer when the classes are badly unbalanced, and the reason is not the one that first suggests itself. A rare class’s indicator column is pulled toward zero, because nine rows in ten want it there, so
"max"hands almost everything to the common class: on a 1:9 fixture in the test suite it finds two of eight rare samples while reporting 92.5% accuracy, which is the classic accuracy trap."bayes"reads each column against its own in-class and out-of-class spread instead of against the other columns, so a value that is high for the rare column still counts. On the same fixture it finds seven of eight, and comes out ahead on accuracy too.
Both rules return exactly one label per sample. The per-class Bayesian thresholds, which do allow “in no class” and “in more than one class” readings, are exposed separately as
thresholds_.- Parameters:
n_components (int) – Number of latent variables. As for
PLS, more is not better: PLS-DA on wide data will separate anything given enough components, which is whatpermutation_test()exists to check.decision_rule ({"max", "bayes"}, optional) – Which rule
predict()applies. Default is"max".priors ("empirical", "uniform", or array-like of shape (n_classes,), optional) – Class priors used by the
"bayes"rule and bythresholds_."empirical"(the default) takes them from the training class frequencies;"uniform"gives every class1 / G, which is what you want when the training set was deliberately balanced but the population is not.scale (bool, optional) – Passed to
PLS. Default True, which mean-centres and unit-variance-scales both blocks. Scaling the indicator block is standard for PLS-DA: without it a rare class contributes less variance and is fitted less well.missing_data_settings (dict | None) – Passed through to
PLSunchanged.
- classes_#
Sorted unique labels seen in
fit, in the column order of everything below.- Type:
np.ndarray of shape (n_classes,)
- priors_#
The resolved priors, summing to 1.
- Type:
np.ndarray of shape (n_classes,)
- thresholds_#
The per-class Bayesian decision threshold on the predicted indicator; see
_bayes_threshold().- Type:
pd.Series indexed by
classes_
- class_statistics_#
One row per class, columns
mean_in,sd_in,mean_out,sd_out,prior,threshold: the fitted Gaussians the"bayes"rule uses.- Type:
pd.DataFrame
- confusion_matrix_#
Training-set confusion matrix, rows the true class and columns the predicted one.
- Type:
pd.DataFrame
- accuracy_#
Training-set accuracy. Optimistic by construction; use
score()on held-out data, orpermutation_test(), to learn anything about generalisation.- Type:
- sensitivity_, specificity_
Per-class true-positive and true-negative rates on the training set.
- Type:
pd.Series indexed by
classes_
- Every fitted attribute of :class:`PLS` is also present (``scores_``, ``x_loadings_``,
- ``x_weights_``, ``r2_cumulative_``, ``hotellings_t2_``, ...), along with its
- convenience methods (``score_plot``, ``loading_plot``, ``vip``, ``spe_limit``,
- ``t2_contributions``, ...), because this class is a :class:`PLS`.
Examples
>>> model = PLSDA(n_components=2).fit(X, labels) >>> model.predict(X_new) array(['good', 'bad', 'good'], dtype=object) >>> model.confusion_matrix_ >>> model.permutation_test(X, labels, n_permutations=99).p_value
References
Barker, M. & Rayens, W. (2003). Partial least squares for discrimination. Journal of Chemometrics 17:166-173.
Brereton, R.G. & Lloyd, G.R. (2014). Partial least squares discriminant analysis: taking the magic away. Journal of Chemometrics 28:213-225.
Westerhuis, J.A. et al. (2008). Assessment of PLSDA cross validation. Metabolomics 4:81-89.
- confusion_matrix_plot(matrix=None, settings=None, fig=None)#
Generate a confusion-matrix heat map for a fitted
PLSDAmodel.Rows are the true class, columns the predicted one, so the diagonal is what the model got right and every off-diagonal cell names a specific confusion: which class this one is mistaken for, which is the question a classification report cannot answer.
- Parameters:
model (PLSDA object) – A fitted PLS-DA model generated by this library.
matrix (pd.DataFrame, optional) – A confusion matrix to plot instead of the model’s training-set one, indexed and labelled by class. Pass
model.confusion(X_test, y_test).matrixto see the held-out picture, which is the one worth acting on:confusion_matrix_is fitted on the same rows it is scored on and will always look better.settings (dict) –
Default settings:
{ "normalize": False, # bool: show row fractions, not counts "title": "Confusion matrix", # str: overall plot title "colorscale": "Blues", # str: any Plotly colorscale name "show_values": True, # bool: print the value in each cell "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 1.0, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Returns:
fig
- Return type:
go.Figure
- Raises:
ValueError – If the model is not fitted and no
matrixis supplied.
Examples
>>> model.confusion_matrix_plot() >>> held_out = model.confusion(X_test, y_test).matrix >>> model.confusion_matrix_plot(held_out, {"normalize": True})
- fit(X, y, sample_weight=None)[source]#
Fit the PLS model on a one-hot encoding of
y, then the decision layer.- Parameters:
X (array-like of shape (n_samples, n_features)) – Training data. May contain missing values, as for
PLS.y (array-like of shape (n_samples,)) – Class labels: strings, integers, or anything
numpy.unique()can sort.sample_weight (array-like of shape (n_samples,), optional) – Passed through to
PLS.fit().
- Returns:
self
- Return type:
- Raises:
ValueError – If
yholds fewer than two distinct labels (there is nothing to discriminate), if its length does not matchX, or ifdecision_rule/priorsis not a recognised value.
- decision_function(X)[source]#
Predicted class-indicator values, one column per class.
These are the raw PLS predictions of the one-hot
Y, so they are centred near 1 for the class a sample belongs to and near 0 for the others, but they are not bounded to[0, 1]and do not sum to 1. Usepredict_proba()for a normalised quantity.
- predict_proba(X)[source]#
Class posteriors from the fitted per-class Gaussians.
In column
gthe training indicator values of classg’s members are modelled asN(mean_in, sd_in)and everyone else’s asN(mean_out, sd_out). The evidence for classgisprior_gtimes the ratio of those two densities, and the posteriors are those normalised across the classes. This is a genuine probability model rather than a rescaling of the indicators: it accounts for how tightly each class scores, for how well it separates from the rest, and for how common it is.It is still only as good as the Gaussian assumption, which a bimodal class or one with three training members will not satisfy. The raw quantity the model actually computes is
decision_function().
- predict(X)[source]#
Predict a class label for every row of
X.The return type deliberately narrows
PLS.predict(), which hands back a DataFrame of predicted Y values: a classifier’spredictreturns labels, and sklearn’s classifier contract requires exactly that. The DataFrame thatPLS.predict()would have returned is still available, asdecision_function().- Parameters:
X (array-like of shape (n_samples, n_features))
- Returns:
labels – Values drawn from
classes_, chosen bydecision_rule.- Return type:
np.ndarray of shape (n_samples,)
See also
decision_functionthe indicator values the rule is applied to.
predict_probaclass posteriors.
- score(X, y, sample_weight=None)[source]#
Return the mean accuracy on
Xagainst the true labelsy.Overrides
PLS.score(), which returns R2 of the indicator predictions: a number that goes up when the indicators are fitted more tightly, not when more samples land in the right class. sklearn’s convention (higher is better) holds either way, but only accuracy answers the question a classifier is asked.
- confusion(X, y)[source]#
Confusion matrix and per-class rates on data the caller supplies.
The
confusion_matrix_/sensitivity_/specificity_attributes are the training-set versions and are optimistic; this is the same calculation on a held-out split.- Parameters:
- Returns:
result –
matrix(DataFrame),sensitivityandspecificity(Series indexed by class), andaccuracy(float).- Return type:
- Raises:
ValueError – If
yholds a label the model was not fitted on.
- roc_auc(X, y, *, positive_class=None)[source]#
Area under the ROC curve, from the continuous indicator rather than the label.
- Parameters:
X (array-like of shape (n_samples, n_features))
y (array-like of shape (n_samples,)) – True labels.
positive_class (object, optional) – Which class counts as positive. Required when there are more than two classes, where the result is that class against all the others; for two classes it defaults to
classes_[1].
- Returns:
auc – Computed on
decision_function(), not on the hard labels, so it measures the ranking the model produces and is unaffected by the decision rule.- Return type:
- Raises:
ValueError – If
positive_classis omitted with more than two classes, or names a class the model was not fitted on.
- permutation_test(X, y, *, n_permutations=99, cv=5, random_state=None)[source]#
Test whether the model separates the classes better than shuffled labels do.
PLS-DA on wide data will separate almost anything: with more variables than samples there is always a direction that happens to line up with the labels. The permutation test is what tells a real effect from that, by refitting the same model on shuffled labels and asking how often chance does as well.
- Parameters:
X (array-like of shape (n_samples, n_features))
y (array-like of shape (n_samples,)) – True labels.
n_permutations (int, optional) – Number of label shuffles. Default 99, which puts the smallest attainable p-value at
1 / 100.cv (int, splitter, or None, optional) –
Cross-validation for the accuracy that is compared. Default 5, stratified.
Noneuses training accuracy, which is far quicker and actively misleading. Measured on 40 samples of 30 pure-noise variables with two random labels, 49 permutations, four folds:Statistic
Separable
Pure noise
Training accuracy
1.000
1.000
Cross-validated
1.000
0.575
p,
cv=40.020
0.220
p,
cv=None0.020
0.020
The noise column is the whole argument. With more variables than samples the model fits random labels perfectly, so training accuracy says 1.000 either way and the cross-validated test correctly declines to call it significant, while the training-accuracy test reports p = 0.02 on data that has no signal in it at all. Westerhuis et al. (2008) is explicit that the comparison has to be made on cross-validated performance;
cv=Noneis offered for a quick look and for well-conditioned data, not for a result to report.random_state (int, np.random.Generator, or None, optional) – Seeds the shuffles, per the reproducibility contract.
- Returns:
result –
observed(float),null(np.ndarray of accuracies),p_value(float),n_permutations(int, the number of draws the null actually holds) andn_failed(int).A shuffled label set can leave a fold with nothing to fit at this component count, and NIPALS then goes singular. Those draws are dropped rather than scored as zero, which would push the null down and the p-value with it. When any are dropped a
SpecificationWarningsays how many, because a null that keeps collapsing means the component count is too high for the data rather than that the model is good.The p-value is
(1 + #{null >= observed}) / (1 + n_permutations). The observed statistic counts itself among the permutations, because no finite set of shuffles licenses a claim of exactly zero; the same convention is used by the Van der Voet and multiblock randomization tests in this package.- Return type:
- set_fit_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- set_score_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
scoremethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed toscoreif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it toscore.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
PRM#
Partial Robust M-regression: PLS with a bounded influence per observation, so that
a handful of outlying rows move the fit a little instead of a lot. Each row carries
the product of a residual weight and a leverage weight, and the fit and the weights
are recomputed from each other until they settle. Everything PLS offers is
inherited, so a fitted PRM also has scores, loadings, VIP, Hotelling’s T2 and SPE.
- class process_improve.multivariate.methods.PRM(n_components, *, cutoff=4.0, max_weight_iter=100, weight_tol=0.0001, scale=True, max_iter=1000, tol=np.float64(1.4901161193847656e-08), copy=True, missing_data_settings=None, warn_on_uncentred=True)[source]#
Bases:
PLSPartial Robust M-regression: PLS with a bounded influence per observation.
Each row carries a weight \(w_i = w_i^r \cdot w_i^x\), the product of a residual weight (how badly the current model predicts that row) and a leverage weight (how far its scores sit from the middle of the score cloud). Both come from the Fair function, so the two kinds of outlier that break a least-squares fit are handled by the same mechanism:
a vertical outlier has an ordinary position in X but a y that does not follow the relationship, and gets a small \(w^r\);
a bad leverage point sits far out in X as well, where least squares gives it the most influence of all, and gets a small \(w^x\).
The weights are recomputed from the fit and the fit is recomputed from the weights until they settle.
Note
Centring and scaling are weighted too, which is not a refinement but the part that makes the method work. The column mean and standard deviation have a breakdown point of zero, so leaving them unweighted leaves the outliers setting the coordinate system that the weighted fit then runs in: measured on a fixture with 15% vertical outliers, weighting only the fit recovers none of the damage, and weighting the scaling as well recovers essentially all of it. See
_make_scalers().- Parameters:
n_components (int) – Number of components to extract.
cutoff (float, optional) – Tuning constant \(c\) of the Fair weight function, default 4.0 as recommended by Serneels et al. Smaller is more aggressive: an observation at \(c\) standardised units gets weight 0.25. This trades robustness against efficiency, and 4.0 keeps roughly 95% of the efficiency of ordinary PLS on clean Gaussian data.
max_weight_iter (int, optional) – Maximum reweighting iterations, default 100. Each one is a full PLS fit.
weight_tol (float, optional) – Convergence tolerance, default 1e-4, on the largest absolute change in any row’s weight between iterations.
- robust_weights_#
The converged row weights, in (0, 1]. Small entries are this model’s statement about which rows it declined to be led by; see
outlier_summary().- Type:
np.ndarray of shape (n_samples,)
- weights_converged_#
Whether the loop met
weight_tol.Falseis not necessarily a failure: readweight_shift_before treating it as one.- Type:
- weight_shift_#
The largest change in any row’s weight on the final iteration. This is what makes
weights_converged_ = Falseactionable rather than a bare flag, because the reweighting can settle into a small limit cycle rather than a point; a shift of 0.03 says the weights are stable to 3%, which for most purposes is settled.- Type:
- All the fitted attributes of :class:`~process_improve.multivariate.methods.PLS`
- are also present, and mean the same thing, computed on the final weighted fit.
References
S. Serneels, C. Croux, P. Filzmoser and P.J. Van Espen, “Partial Robust M-regression”, Chemometrics and Intelligent Laboratory Systems, 79 (2005), 55-64.
Examples
>>> model = PRM(n_components=2).fit(X, y) >>> model.outlier_summary(threshold=0.1) >>> model.predict(X_new)
- fit(X, Y, sample_weight=None)[source]#
Fit by alternating between a weighted PLS fit and a reweighting step.
- Parameters:
X (array-like of shape (n_samples, n_features)) – Training data.
Y (array-like of shape (n_samples, n_targets)) – Target values.
sample_weight (array-like of shape (n_samples,), optional) – Prior row weights, multiplied into the robust weights at every iteration. Use this to express knowledge the data cannot carry, such as a run you already know was compromised; it is not needed to downweight outliers, which is what the model is for.
- Returns:
self, fitted.- Return type:
- Raises:
ValueError – If
cutoff,max_weight_iterorweight_tolis out of range, or if the data contain missing values (see Notes).
Notes
Missing data are refused rather than threaded through. The weights are built from residual and leverage distances, and a row with missing cells has a distance that is not comparable with a complete row’s, so it would be downweighted for being incomplete rather than for being wrong. The underlying
PLSdoes handle missing data; use it, or impute first.
- outlier_summary(threshold=0.1)[source]#
Rows the fit declined to be led by, weakest weight first.
- Parameters:
threshold (float, optional) – Report rows whose final weight is below this, default 0.1. There is no distinguished value: the weights are continuous by design, so this is a reading aid and not a test.
- Returns:
Indexed as the training X was, with one
weightcolumn. Empty if nothing falls belowthreshold.- Return type:
pd.DataFrame
- Raises:
AttributeError – If the model is not fitted.
- set_fit_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- set_score_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
scoremethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed toscoreif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it toscore.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
TPLS#
- class process_improve.multivariate.methods.TPLS(n_components, d_matrix, max_iter=500, skip_f_matrix_preprocessing=False, tol=np.float64(1.4901161193847656e-08))[source]#
Bases:
RegressorMixin,BaseEstimatorTPLS algorithm for T-shaped data structures (we also include standard pre-processing of the data inside this class).
Source: Garcia-Munoz, https://doi.org/10.1016/j.chemolab.2014.02.006, Chem.Intell.Lab.Sys. v133, p 49 to 62, 2014.
We change the notation from the original paper to avoid confusion with a generic “X” matrix, and match symbols that are more natural for our use.
Notation mapping (paper → this code):
X^T → D:
d_matrix(external),d_mats(internal) - Database of propertiesX → D^T: transposed D (not used directly)
R → F:
f_mats- Formula matricesZ → Z:
z_mats- Process conditionsY → Y:
y_mats- Quality indicators
Notes 1. Matrices in F, Z and Y must all have the same number of rows. 2. Columns in F must be the same as the rows in D. 3. Conditions in Z may be missing (turning it into an L-shaped data structure).
- Parameters:
n_components (int) – A parameter used to specify the number of components.
d_matrix (dict[str, pd.DataFrame]) – A dictionary containing the properties of each group of materials. Keys are group names; values are DataFrames with properties as columns and materials as rows. This “D” matrix is provided once at construction and reused for fitting, prediction and cross-validation.
max_iter (int, optional) – The maximum number of iterations for the TPLS algorithm. Default is 500.
skip_f_matrix_preprocessing (bool, optional) – If True, the F (formula) matrices are used as-is, skipping the internal centering and scaling of the F block. Default is False.
tol (float, optional) – Relative convergence tolerance for the per-component super-score loop: the component is converged once the norm of the difference between two successive super-score vectors, relative to the norm of the previous one, falls below this. Default is
epsqrt(about 1.5e-8), which is the value that was hard-coded before this became a parameter, so the default fit is unchanged. It is listed last so that no existing positional argument changed position.
Notes
The input
Xpassed tofit()andpredict()is a dictionary with 3 keys:F: Formula matrices (rows = blends, columns = materials).F = {"Group A": df_formulas_a, "Group B": df_formulas_b, ...}Z: Process conditions - one row per blend, one column per condition.Y: Product quality indicators - one row per blend, one column per indicator.
The
Dmatrix (database of material properties) is supplied once at construction via thed_matrixargument; it is not part ofX.- fitting_statistics#
Per-component
iterations,convergance_toleranceandmillisecondslists.- Type:
- preproc_#
Nested per-block per-group preprocessors, indexed as
preproc_[block][group]whereblockis"D","F","Y"or"Z".- Type:
- d_mats, f_mats, z_mats, y_mats
Deflated block matrices (per group for D/F, per Z-block / Y-block for Z/Y).
- not_na_d, not_na_f, not_na_z, not_na_y
Boolean observed-value masks matching the shapes above.
- observation_names#
Row index shared across the F/Z/Y blocks.
- Type:
pd.Index
- condition_names, quality_names
Column names of each Z-block and Y-block respectively.
- w_loadings_super#
Super-block weights, rows
["Z", "F"](or just["F"]when there are no process conditions).- Type:
pd.DataFrame
- hat_#
Per-Y-block predictions on the preprocessed (centred / scaled) scale.
- spe#
Nested
spe[block][group]squared prediction error tables.
- spe_limit#
Nested
spe_limit[block][group]callables. Each is afunctools.partial()overspe_calculation()bound to the block’s SPE array; call it with a confidence level to obtain the SPE limit. This is a deliberate divergence from the flatspe_limitmethod on PCA / PLS.
- hotellings_t2#
Cumulative Hotelling’s T^2 per super-component: column
aholdssum_{j<=a} (t_j / s_j)^2, the same form as PCA / PLS and asdiagnose()(#502).- Type:
pd.DataFrame, shape (n_samples, n_components)
- scaling_factor_for_scores#
Standard deviation of each super-score column, computed with the unbiased (N - 1) divisor; used by the ellipse / T^2 helpers.
- Type:
pd.Series
- .. note::
TPLS deliberately does not follow the sklearn trailing- underscore convention for its fitted attributes. Names such as
t_scores_super,spe,hotellings_t2, and the*_loadings_*family are written without the trailing_to keep the chemometrics symbol names readable. Attributes that are set in__init__and refined duringfit(for exampleis_fitted_,preproc_,required_blocks_,required_inputs_) do carry the underscore.
Example
>>> import numpy as np >>> import pandas as pd >>> rng = np.random.default_rng() >>> >>> n_props_a, n_props_b = 6, 4 # Two groups of properties: A and B. >>> n_materials_a, n_materials_b = 12, 8 # Number of materials in each group. >>> n_formulas = 40 # Number of formulas in matrix F. >>> n_outputs = 3 >>> n_conditions = 2 >>> >>> properties = { >>> "Group A": pd.DataFrame(rng.standard_normal((n_materials_a, n_props_a))), >>> "Group B": pd.DataFrame(rng.standard_normal((n_materials_b, n_props_b))), >>> } >>> formulas = { >>> "Group A": pd.DataFrame(rng.standard_normal((n_formulas, n_materials_a))), >>> "Group B": pd.DataFrame(rng.standard_normal((n_formulas, n_materials_b))), >>> } >>> process_conditions = {"Conditions": pd.DataFrame(rng.standard_normal((n_formulas, n_conditions)))} >>> quality_indicators = {"Quality": pd.DataFrame(rng.standard_normal((n_formulas, n_outputs)))} >>> all_data = {"Z": process_conditions, "F": formulas, "Y": quality_indicators} >>> estimator = TPLS(n_components=4, d_matrix=properties) >>> estimator.fit(DataFrameDict(all_data))
- property tolerance_: float#
Deprecated alias for
tol, kept for one deprecation cycle.It was assigned in
__init__and read at two unrelated sites: the convergence test, whichtolnow owns, and a zero-variance column test, which has its own floor (_ZERO_VARIANCE_FLOOR).
- hotellings_t2_limit(conf_level=0.95)[source]#
Hotelling’s T2 limit at the given confidence level (see
hotellings_t2_limit()).
- ellipse_coordinates(score_horiz, score_vert, conf_level=0.95, n_points=100)[source]#
Coordinates of the T2 confidence ellipse (see
ellipse_coordinates()).
- fit(X, y=None)[source]#
Fit the preprocessing parameters and also the latent variable model from the training data.
- Parameters:
X ({dictionary of dataframes}, keys that must be present: "F", "Z", and "Y") – The training input samples. See documentation in the class definition for more information on each matrix.
y (object, optional) – Must be
None. The signature exists only for sklearn API compatibility; a T-shaped model takes its response fromX["Y"]. Anything else raises, rather than being silently ignored as it was before #565.
- Returns:
self – Returns self.
- Return type:
- Raises:
ValueError – If
yis notNone.TypeError – If
Xis not aDataFrameDict.
- predict(X)[source]#
Forward to
diagnose(); emits aDeprecationWarning.Deprecated since version 1.38.4: Use
TPLS.diagnose()instead.predictmatches the sklearn-convention name (regression-style ndarray return), but TPLS’s natural return is the rich per-group / per-block diagnostics Bunch and TPLS cannot be placed in a standard sklearn Pipeline anyway (its input is a nestedDataFrameDict). The rename aligns withPLS.diagnose()andPCA.diagnose(). Will be removed in 2.0.0.- Parameters:
X (DataFrameDict)
- Return type:
- diagnose(X)[source]#
Model inference on new data.
This will pre-process the new data and apply those subsequently to the latent variable model.
Example
# Training phase: estimator = TPLS(n_components=2).fit(training_data)
# Testing/inference phase: new_data = {“Z”: …, “F”: …} # you need at least the F block for a new prediction. “Z” is optional. predictions = estimator.diagnose(new_data)
- Parameters:
X (DataFrameDict) – The input samples.
- Returns:
A bunch with the following fields:
hat(dict[str, DataFrame]) : predicted Y per Y-group, on the original (un-scaled) scale, indexed by the observation names ofX.t_scores_super(DataFrame, shape(n_new, n_components)) : super-scores for the new observations.spe(dict[str, dict[str, DataFrame]]) : per-component squared prediction error for the"Z"and"F"blocks, keyed first by block name and then by group; each inner DataFrame has shape(n_new, n_components).hotellings_t2(DataFrame, shape(n_new, n_components)) : cumulative Hotelling’s T² per new observation after each component.
- Return type:
- score(X, y=None, sample_weight=None)[source]#
Return the mean
r2_score()across Y blocks on test data.See RegressorMixin.score for the general contract.
- Parameters:
X (DataFrameDict) – Test samples. The nested
"Y"block supplies the actual response values;X["Z"]andX["F"]drive the prediction.y (object, optional) – Must be
None. The Y-data comes fromX["Y"], not from a separate argument (the sklearnRegressorMixin.scoresignature is preserved only for API compatibility). Anything else raises, rather than being silently ignored as it was before #565.sample_weight (np.ndarray or None) – Optional per-sample weight forwarded to
r2_score().
- Returns:
score – The mean of
r2_score()computed separately on every Y block inX["Y"], usingself.diagnose(X).hatas the prediction. A single Y block gives its own \(R^2\); more than one is an unweighted average across blocks (not a pooled multiblock \(R^2\)).- Return type:
- Raises:
ValueError – If
yis notNone, or ifX["Y"]holds no blocks.
Notes
Only sklearn’s default scoring reaches this method. A named scorer string never does:
cross_val_score(TPLS(...), X=DataFrameDict(blocks), cv=5) # works cross_val_score(TPLS(...), X=DataFrameDict(blocks), cv=5, scoring="r2") # all NaN
sklearn builds
scoring="r2"into a_Scorerwhose__call__requires ay_trueargument. TPLS carries its response insideX["Y"], socross_val_scorereceives noyto hand the scorer, and the call fails inside sklearn before this method is reached: instrumentation shows this method called 3 times out of 3 folds under default scoring and 0 times underscoring="r2". Because TPLS never gets control it cannot turn that into a clear error, and sklearn’serror_score(defaultnp.nan) records every fold asNaNbehind aUserWarning.Use
make_tpls_scorer()for any metric other than the default. sklearn passes a callablescoring=through untouched and calls it asscorer(estimator, X_test), which a callable with an optionalyaccepts:cross_val_score(TPLS(...), X=DataFrameDict(blocks), cv=5, scoring=make_tpls_scorer("r2")) # works cross_val_score(TPLS(...), X=DataFrameDict(blocks), cv=5, scoring=make_tpls_scorer("neg_mean_squared_error"))
make_tpls_scorer("r2")reproduces this method’s value fold for fold, so it is a drop-in replacement for the broken string form.If a string scorer is used anyway, pass
error_score="raise"to see the underlyingTypeErrorinstead of a silentNaN:cross_val_score(TPLS(...), X=DataFrameDict(blocks), cv=5, scoring="r2", error_score="raise") # TypeError: _Scorer._score() missing 1 required positional argument: 'y_true'
See also
make_tpls_scorerBuild a
scoring=callable that TPLS can honour.
- help()[source]#
Help for the TPLS Estimator.
Data organization#
Quick tips#
Build model: tpls = TPLS(n_components=2, d_matrix=d_matrix).fit(X) Get model’s predictions: tpls.hat <– the hat-matrix, i.e., the predictions Predict on new data: tpls.diagnose(X_new) See model summary: tpls.display_results() This help page: tpls.help()
Statistical values#
.t_scores_super Super scores for the entire model [pd.DataFrame] .hotellings_t2 Hotelling’s T2 values for each observation, per component [pd.DataFrame] .spe Squared prediction error for each block [dict of pd.DataFrames]
.hotellings_t2_limit() Returns the Hotelling’s T2 limit for the model [float] .spe_limit[block][group]() Return the SPE limit for a group in a block; e.g. .spe_limit[“Y”][group]() [float]
.vip() Variable importance (VIP) for the D- and F-blocks [dict]
- Return type:
- vip(block=None, method='vip')[source]#
Return Variable Importance in Projection (VIP) scores for TPLS blocks.
VIP scores are computed during fitting for the D-block (material properties) and F-block (formulation variables) and stored in
feature_importance.- Parameters:
block (str or None, default=None) – Which block to return. Must be
"D"or"F", orNoneto return all blocks.method ({"vip", "deflated"}, default="vip") –
How importance is measured.
"vip"(default): standard VIP on the raw block loadings, as reported by Garcia-Munoz (2014). These are the values stored infeature_importance."deflated": VIP computed on the direct (rotated) weights that map the original variables onto the scores while accounting for the deflation across components,S(V^T S)^-1for the D-block andP(P^T P)^-1for the F-block. This is an alternative importance measure; it does not changefeature_importance.
- Returns:
If block is
None:{"D": {group: pd.Series, ...}, "F": {group: pd.Series, ...}}. If block is"D"or"F": the inner dict{group: pd.Series, ...}for that block, where eachpd.Seriesis indexed by feature names.- Return type:
- Raises:
ValueError – If the model is not fitted, block is not
"D","F", orNone, or method is not"vip"or"deflated".
Examples
>>> tpls = TPLS(...).fit(data) >>> tpls.vip() # all blocks, standard VIP >>> tpls.vip("D") # D-block only → {group_name: pd.Series, ...} >>> tpls.vip("D", method="deflated") # D-block deflated direct-weights importance
- property d_block_scaling_: dict[str, float]#
Block-scaling factor applied to each D-block (read-only).
After column-wise centring and auto-scaling, every D-block
X_iis additionally divided bysqrt(P_i * M_i)(P_i= number of lots/rows,M_i= number of properties/columns) so thattrace(X_i^T X_i) ~= 1, removing bias toward blocks with more lots or properties (Garcia-Munoz, 2014, section 2.1).- Returns:
Mapping of D-block group name to its scalar block-scaling factor.
- Return type:
- Raises:
AttributeError – If the model has not been fitted yet.
- set_score_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
scoremethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed toscoreif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it toscore.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
A T-shaped model carries its response inside X["Y"], so
cross_val_score() is called without a y and a
scorer string such as scoring="r2" cannot be honoured: sklearn’s
_Scorer needs a y_true it was never given, the call fails before TPLS is
reached, and every fold is recorded as NaN. Build the scorer with
make_tpls_scorer() instead; sklearn passes a callable scoring= through
untouched.
- process_improve.multivariate.methods.make_tpls_scorer(metric='r2', *, greater_is_better=True, **metric_kwargs)[source]#
Build a
scoring=callable that works withTPLS(#565).A named scorer string cannot be used with TPLS. sklearn turns
scoring="r2"into a_Scorerwhose__call__takesy_trueas a required positional argument, but a T-shaped model carries its response insideX["Y"]rather than in a separatey, socross_val_score()has noyto hand over and calls the scorer asscorer(estimator, X_test). The call then fails on the missing argument before TPLS is reached, and sklearn’serror_scorerecords every fold asNaNbehind aUserWarning.sklearn passes a callable
scoring=through untouched and invokes it the same way, so a callable whoseyis optional receives that two-argument call cleanly and can read the response out ofX["Y"]itself. That is what this factory returns.- Parameters:
metric (str or Callable[..., float], optional) – Either one of
"r2","neg_mean_absolute_error","neg_mean_squared_error"or"neg_root_mean_squared_error", whose sign convention matches sklearn’s, or a callablemetric(y_true, y_pred, **kwargs)returning a float. Default is"r2", which reproducesTPLS.score().greater_is_better (bool, optional) – Consulted only when
metricis a callable:Falseflips the sign so the result still obeys sklearn’s “higher is better” contract. The named metrics carry their own sign and ignore this. Default is True.**metric_kwargs (object) – Extra keyword arguments forwarded to
metricon every call.
- Returns:
scorer – A callable with signature
scorer(estimator, X, y=None, sample_weight=None), usable as thescoring=argument ofcross_val_score(),cross_validate()and the*SearchCVclasses. Several Y blocks are combined the wayTPLS.score()combines them: an unweighted mean over the blocks inX["Y"], not a pooled multiblock statistic.- Return type:
Callable[…, float]
- Raises:
ValueError – If
metricis a string that is not a known metric name. The returned scorer raisesValueErrorin turn if it is called with a non-Noney, or with anXwhose"Y"block is empty.
See also
TPLS.scoreThe default scoring path, equivalent to
make_tpls_scorer("r2").
Examples
>>> from sklearn.model_selection import cross_val_score >>> scorer = make_tpls_scorer("neg_root_mean_squared_error") >>> model = TPLS(n_components=2, d_matrix=d_matrix) >>> cross_val_score(model, X=blocks, cv=5, scoring=scorer) array([-1.07, -0.98, -1.11, -1.02, -1.05])
ASCA#
ANOVA-Simultaneous Component Analysis: partition a response matrix by its design terms,
then give each term’s effect matrix its own PCA. This is the bridge between
process_improve.experiments and the latent-variable models: it answers which
factor owns which direction of multivariate variation, whether that is more than
chance, and which variables carry it.
- class process_improve.multivariate.methods.ASCA(n_components=2, *, model='interactions', add_residuals=False, scale=False)[source]#
Bases:
BaseEstimatorANOVA-Simultaneous Component Analysis: PCA of each design term’s effect.
The response matrix is written as
\[X = \mathbf{1} m^T + \sum_f X_f + E\]with \(m\) the grand mean, one effect matrix \(X_f\) per design term, and \(E\) the residual. Each \(X_f\) then gets its own PCA, so a term’s scores and loadings describe the multivariate structure of that factor’s effect alone.
- Parameters:
n_components (int, optional) – Components to extract per term. Capped per term at the rank its design columns can support, because an effect matrix for a two-level factor has rank 1 and asking for two components of it is asking for a component that does not exist. Default 2.
model (str, optional) –
"interactions"(the default) adds every two-way interaction to the main effects;"main_effects"fits main effects only. Anything else is taken as a patsy right-hand side and used verbatim, in which case the caller is responsible for the coding.add_residuals (bool, optional) – If True, add the residual matrix back onto each effect matrix before its PCA (APCA / ASCA+). The effect matrix alone has one distinct row per factor-level combination, so a score plot of it is a handful of points; adding the residuals back restores the scatter and shows whether the levels actually separate. Default False.
scale (bool, optional) – If True, unit-variance scale the columns of
Xafter centring, so a variable measured in large units does not dominate the decomposition. Default False, which is the right choice when the columns are already on one scale (a spectrum, say) and the wrong one when they are not.
- terms_#
Design term names, in the order patsy resolved them, with the coding wrapper stripped:
["A", "B", "A:B"].
- grand_mean_#
Column means removed before the decomposition.
- Type:
pd.Series
- column_scale_#
Column scaling applied, all ones when
scale=False.- Type:
pd.Series
- residuals_#
What no term explains.
- Type:
pd.DataFrame
- ssq_#
Sum of squares per term, plus
"residual"and"total".- Type:
pd.Series
- ssq_percent_#
The same as a percentage of the total, which is the “factor effect” summary worth reading first.
- Type:
pd.Series
- pvalues_#
Permutation p-values per term. Absent until
permutation_test()is called.- Type:
pd.Series
Notes
Balance matters, and the model says so rather than assuming it. On a balanced design the terms are orthogonal, the per-term sums of squares add up to the model sum of squares, and the decomposition is unique. On an unbalanced design they are not orthogonal: the split of the shared variation between correlated terms depends on how you choose to attribute it, which is the Type I / II / III question. This implementation fits every term simultaneously by least squares and reads each term’s fitted contribution off that single fit, which is the usual ASCA treatment. It warns when the design is unbalanced, and
ssq_then no longer partitions the total exactly; the gap is reported asssq_["total"]minus the sum of the parts.Examples
>>> model = ASCA(n_components=2).fit(X, design) >>> model.ssq_percent_ >>> model.permutation_test(n_permutations=999, random_state=0) >>> model.models_["A"].scores_
References
Smilde, A. K., Jansen, J. J., Hoefsloot, H. C. J., Lamers, R.-J. A. N., van der Greef, J., & Timmerman, M. E. (2005). ANOVA-simultaneous component analysis (ASCA): a new tool for analyzing designed metabolomics data. Bioinformatics, 21(13), 3043-3048.
Zwanenburg, G., Hoefsloot, H. C. J., Westerhuis, J. A., Jansen, J. J., & Smilde, A. K. (2011). ANOVA-principal component analysis and ANOVA-simultaneous component analysis: a comparison. J. Chemometrics, 25(10), 561-567.
Camacho, J., Vitale, R., Morales-Jimenez, D., & Gomez-Llorente, C. (2022). Variable-selection ANOVA Simultaneous Component Analysis (VASCA). Bioinformatics, 38(1), 295-298.
- effect_summary_plot(settings=None, fig=None)#
Generate the per-term effect summary for a fitted
ASCAmodel.One bar per design term, showing the share of the total sum of squares it carries, with the residual alongside for scale. This is the plot to read first: it says which factor the variation actually belongs to, before any score plot is opened.
Permutation p-values are annotated on the bars when
permutation_test()has been run, because a term’s share and its significance answer different questions: a term can hold a large share simply by having many degrees of freedom.- Parameters:
model (ASCA object) – A fitted ASCA model generated by this library.
settings (dict) –
Default settings:
{ "include_residual": True, # bool: draw the residual bar too "title": "Variation by design term", # str: overall plot title "bar_color": None, # str|None: bar colour; None uses the theme "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Returns:
fig
- Return type:
go.Figure
- Raises:
ValueError – If the model is not fitted.
Examples
>>> model.effect_summary_plot() >>> model.permutation_test(random_state=0) >>> model.effect_summary_plot() # now annotated with p-values
- fit(X, design, y=None)[source]#
Decompose
Xby the design terms and fit a PCA to each term’s effect.- Parameters:
X (array-like of shape (n_samples, n_features)) – The multivariate response. Must be complete: ASCA solves a least-squares problem over every column at once and has no missing-data path.
design (pd.DataFrame) – One row per sample and one column per experimental factor, holding that factor’s level for each row. Values may be strings or numbers; they are treated as categorical.
y (object, optional) – Ignored, for sklearn API compatibility.
- Returns:
self
- Return type:
- Raises:
ValueError – If
Xanddesigndisagree on the number of rows, if either holds missing values, or if a factor has only one level (it can carry no effect).
- permutation_test(*, n_permutations=999, random_state=None)[source]#
Test each term’s effect against the null of exchangeable rows.
For every term the rows of the response are permuted, the decomposition is refitted, and the term’s sum of squares is recomputed. A term whose observed sum of squares sits in the upper tail of that null is carrying more variation than the design’s shape alone would produce.
- Parameters:
- Returns:
pvalues – One p-value per term, also stored as
pvalues_. Each is(1 + #{null >= observed}) / (1 + n_permutations): the observed statistic counts itself among the permutations, because no finite set of shuffles licenses a claim of exactly zero. The same convention is used by the Van der Voet test, the multiblock randomization test and PLSDA.permutation_test.- Return type:
pd.Series
Notes
One permutation refits every term at once, so the whole test costs
n_permutationsleast-squares solves rather than one per term. The PCA step is not repeated: the statistic is the sum of squares of the effect matrix, which the decomposition produces directly.
- vasca(term, *, n_permutations=999, alpha=0.05, random_state=None)[source]#
Variable-selection ASCA: which variables carry this term’s effect.
The ASCA permutation test asks one question of the whole response matrix, so an effect that lives in three variables out of two hundred is diluted by the other hundred and ninety-seven and can fail to register at all. VASCA ranks the variables by their contribution to the term, then tests each nested subset of the top-ranked ones. A subset that contains the effect and little else gives a far smaller p-value than the whole matrix does.
- Parameters:
- Returns:
result –
table(pd.DataFrame, one row per subset size: the variable added at that step, the subset’s cumulative sum of squares, how many standard deviations it sits above its own null, and its raw and FDR-corrected p-values),selected(list of variable names),ranking(the variables in contribution order) andp_value(the smallest corrected p-value found).selectedis the subset that clearsalphaand stands furthest above its own null. The second half of that matters: with a few hundred permutations the smallest attainable p-value is1 / (1 + n_permutations)and many subset sizes reach it at once, so choosing by p-value alone would return every variable that happened to tie at the floor. The z-score does not tie, and it peaks where the effect is concentrated.selectedis empty when no subset clearsalpha, which is the honest answer for a term that carries nothing.- Return type:
- Raises:
ValueError – If
termis not one of the fitted terms.
Notes
The permutations are shared across subset sizes: one shuffle produces a per-variable sum of squares vector, and every subset’s null statistic is a partial sum of it. The whole walk therefore costs the same
n_permutationssolves as the single-term test, rather than one run per subset size.Because one test is made per subset size, the raw p-values are corrected across those tests with
benjamini_hochberg(), which controls the false-discovery rate rather than the family-wise error rate.
- set_fit_request(*, design='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
MBPLS#
Multi-block PLS in the hierarchical / superblock formulation of
Westerhuis, Kourti & MacGregor (1998). Each X-block is preprocessed
independently and weighted by 1/sqrt(K_b) before the inner NIPALS
loop, so blocks of unequal width contribute fairly to the consensus
super-score.
- class process_improve.multivariate.methods.MBPLS(n_components, *, max_iter=500, tol=None, algorithm='auto', missing_data_settings=None)[source]#
Bases:
_HotellingsT2LimitMixin,RegressorMixin,BaseEstimatorMulti-block PLS (hierarchical / superblock formulation).
Generic multi-block PLS as described by Westerhuis, Kourti & MacGregor (1998) and Westerhuis & Smilde (2001). Each X-block is preprocessed independently (mean-centred and unit-variance scaled), then divided by
sqrt(K_b)so that blocks of unequal width contribute fairly to the super-score.- Parameters:
n_components (int) – Number of latent variables to extract.
max_iter (int, default=500) – Maximum NIPALS iterations per latent variable.
tol (float or None, default=None) – Relative convergence tolerance on the change in the Y-block score
u: the norm of the change between two successive iterations, divided by the norm of the currentuvector (#504). IfNone,epsqrt(about 1.49e-8) is used, the same default as PCA / PLS / TPLS. The legacy absolute tolerancenp.finfo(float).eps ** (6/7)(about 3.8e-14) sits below the floating-point oscillation floor of a relative criterion, so it would never be reached in practice.algorithm (str) –
Algorithm to use for fitting the model.
"auto": dense vectorised hierarchical NIPALS when every block (X and Y) is complete; mask-aware NIPALS when any block contains missing values."dense": dense vectorised hierarchical NIPALS. Raises if any block contains missing values."nipals": mask-aware hierarchical NIPALS. Always uses the NaN-tolerant inner-loop primitives, even when the data is complete (slower than"dense"but produces equivalent results).
With missing data the super score of each row is estimated by one masked regression of the whole row onto the stacked block weights, rather than by adding up a score per block. The two are the same number on a complete row, so no fitted model changes; on an incomplete row the pooled form lets every observed cell carry its share, wherever in the blocks it sits, instead of letting a block seen in one variable speak as loudly as a block seen in twenty. A row with nothing observed in one block is therefore still scored, from the blocks it does have; only a row observed in no block at all is refused.
missing_data_settings (dict or None, default=None) – Settings for the iterative
"nipals"path. Keys:md_tol(convergence tolerance on the score-vector change between iterations),md_max_iter(maximum NIPALS iterations per component). Defaults to{"md_tol": epsqrt, "md_max_iter": 1000}.fitting) (Attributes (after)
--------------------------
block_names (list[str]) – Ordered list of X-block names (the keys of the input dict).
block_widths (dict[str, int]) – Number of variables in each X-block.
n_samples (int) – Number of rows fitted.
n_targets (int) – Number of Y columns.
n_features_in (int) – Total number of X variables summed across blocks.
feature_names_in (np.ndarray) – Concatenated column names, one per feature, in block order.
preproc (dict[str, MCUVScaler]) – Per-block preprocessors used to mean-centre and unit-variance scale each X-block.
y_preproc (MCUVScaler) – Preprocessor used on Y.
super_scores (pd.DataFrame, shape (n_samples, n_components)) – Super-block (consensus) X-scores
T. Finite for every row that has at least one observed cell, in any block.super_y_scores (pd.DataFrame, shape (n_samples, n_components)) – Super-block Y-scores
U.super_weights (pd.DataFrame, shape (n_blocks, n_components)) – Super-block weights
w_super; rows indexed by block name.super_y_loadings (pd.DataFrame, shape (n_targets, n_components)) – Y-block loadings
c.super_hotellings_t2 (pd.DataFrame, shape (n_samples, n_components)) – Cumulative Hotelling’s T^2 on the super-scores per component.
super_vip (pd.Series) – Variable-importance in projection for each X-block, indexed by block name.
block_scores (dict[str, pd.DataFrame]) – Per-block X-scores
t_b, each shape(n_samples, n_components). NaN for a row with nothing observed in that block: the block has no score of its own there, and reporting zero would place the row at the block’s average instead.block_weights (dict[str, pd.DataFrame]) – Per-block X-weights
w_b, each shape(K_b, n_components). Each column has unit norm.block_loadings (dict[str, pd.DataFrame]) – Per-block X-loadings
p_b(used for deflation), each shape(K_b, n_components).block_spe (dict[str, pd.DataFrame]) – Per-block squared prediction error per sample and component. NaN where the row has nothing observed in that block, for the same reason as
block_scores_.block_hotellings_t2 (dict[str, pd.DataFrame]) – Per-block cumulative Hotelling’s T^2 per sample and component.
block_vip (dict[str, pd.Series]) – Per-block variable-importance in projection, indexed by variable name inside each block.
predictions (pd.DataFrame, shape (n_samples, n_targets)) – In-sample Y predictions on the original scale.
explained_variance (np.ndarray, shape (n_components,)) – Variance of the super-score per component (ddof=1).
scaling_factor_for_super_scores (pd.Series) –
sqrt(explained_variance_)per component.r2_x_per_block_cumulative (pd.DataFrame, shape (n_blocks, n_components)) – Cumulative R^2X per block and component.
r2_x_per_block_per_component (pd.DataFrame, shape (n_blocks, n_components)) – Incremental R^2X per block and component.
r2_x_per_variable (dict[str, pd.DataFrame]) – Cumulative R^2X per variable within each block.
r2_y_cumulative (pd.Series, shape (n_components,)) – Cumulative R^2Y per component.
r2_y_per_component (pd.Series, shape (n_components,)) – Incremental R^2Y per component.
r2_y_per_variable (pd.DataFrame, shape (n_targets, n_components)) – Cumulative R^2Y per Y-variable and component.
fitting_info (dict) – Per-component iteration count and timing.
has_missing_data (bool) – Whether any X-block or Y had NaN values.
algorithm – The resolved algorithm actually used for the fit. With
algorithm="auto", this is"dense"for complete data and"nipals"for NaN-containing data.
Notes
Block weighting uses the convention \(X_b / \sqrt{K_b}\) so that every block contributes the same total sum of squares to the super-score, regardless of how many variables it has.
Missing data#
When any X-block or Y contains NaN entries, the
"auto"algorithm routes to a mask-aware NIPALS variant. The X-block weights, block scores, block loadings used for deflation, Y-block loadings and Y-block scores are each computed as a regression that uses only the observed entries; the masked sum-of-squares is used as the denominator so missing values neither bias the latent direction nor contribute to the score. The mask is preserved across components automatically because deflation propagates NaN through subtraction. This is the standard skip-NaN NIPALS update; see Walczak & Massart (2001) and Arteaga & Ferrer (2002).The fit refuses to run if any X-block or Y has a column with all entries missing, or a row with all entries missing for that block; either case leaves the masked denominator at zero. Drop or impute such rows or columns before fitting. Predict-time score estimation for new observations with NaN (Trimmed Score Regression / Projection to the Model Plane) is a separate follow-up.
References
Westerhuis, J. A., Kourti, T. & MacGregor, J. F. Analysis of multiblock and hierarchical PCA and PLS models. Journal of Chemometrics, 12 (1998), 301-321.
Westerhuis, J. A. & Smilde, A. K. Deflation in multiblock PLS. Journal of Chemometrics, 15 (2001), 485-493.
Walczak, B. & Massart, D. L. Dealing with missing data: Part I. Chemom. Intell. Lab. Syst., 58 (2001), 15-27.
Arteaga, F. & Ferrer, A. Dealing with missing data in MSPC: several methods, different interpretations, some examples. J. Chemometrics, 16 (2002), 408-418.
- block_spe_limit(block, conf_level=0.95)[source]#
SPE limit for one X-block using the Nomikos & MacGregor chi-square approximation.
Operates on the same scale as
block_spe_[block](sqrt of row sum of squares), so the value can be drawn directly on a SPE plot.
- super_spe_limit(conf_level=0.95)[source]#
SPE limit for the merged super-block (sum of per-block SPE squared).
- spe_contributions(X)[source]#
Per-variable squared residuals for each X-block (SPE contributions).
For each new observation and each X-block, reconstruct the block as
T_super @ P_b^T(matching the deflation step used during fit) and return the squared per-variable residuals. Useful for fault diagnosis: the variable with the largest contribution is the most likely culprit for a high SPE.
- score_contributions(X, component=1, scaling='none')[source]#
Per-block per-variable contributions to a super-score.
The multi-block analogue of
PLS.score_contributions(). A super score is a weighted sum of the (deflated, preprocessed) variables across every block, so it splits exactly into one term per variable:\[c_{b,ij}^{(a)} = \tilde{x}_{b,ij}^{(a)}\, \frac{w_b[j, a]\, w_\mathrm{super}[b, a]}{\sqrt{K_b}}, \qquad \sum_b \sum_j c_{b,ij}^{(a)} = t_{\mathrm{super},ia},\]where \(\tilde{x}^{(a)}\) is the block data deflated through the first \(a-1\) components, which is what the super score at component \(a\) is actually formed from.
- Parameters:
X (dict[str, pd.DataFrame]) – Raw (un-preprocessed) X-blocks, keyed by block name, exactly as passed to
fit(). The stored per-block preprocessing is applied internally.component (int, default=1) – 1-based component index whose super score is decomposed.
scaling ({"none", "maximum", "within"}, default="none") – Presentation scaling, as for
PLS.score_contributions(). Under"none"the contributions sum across all blocks to the super score."maximum"divides by the largest absolute contribution over every block;"within"divides each observation by the total absolute contribution it accumulates across every block, so both scalings are taken over the blocks jointly rather than one block at a time.
- Returns:
One frame per X-block, of shape (n_samples, K_b).
- Return type:
Examples
>>> mbpls = MBPLS(n_components=2).fit(blocks, Y) >>> contrib = mbpls.score_contributions(blocks, component=1) >>> sum(frame.sum(axis=1) for frame in contrib.values()) # super score 1
- group_contributions(X, group, reference=None, component=1)[source]#
Per-block per-variable contributions to a group’s average super score.
The multi-block analogue of
PLS.group_contributions(). See that method for the definition; the only difference is that the result is returned one Series per X-block, and the sum over every block equals the group’s average super score (or the difference between the two groups’ average super scores whenreferenceis given).
- super_weights_bar_plot(component=1)[source]#
Bar plot of super-weights
w_superfor a single component.- Parameters:
component (int)
- Return type:
Figure
- predictions_vs_observed_plot(y_observed, variable=None)[source]#
Scatter plot of predicted vs observed Y, with y=x reference and RMSEE annotation.
- Parameters:
y_observed (pd.DataFrame) – The observed Y on the original scale, same columns as the training Y.
variable (str or None, default=None) – If given, plot only that Y-variable. If
None, plot the first one.
- Return type:
Figure
- display_results(show_cumulative=True)[source]#
Format a short text summary of per-block R²X, overall R²Y, iterations and timing.
- diagnose(X)[source]#
Project new data and return the full diagnostics Bunch.
Returns a
sklearn.utils.Bunchwith fieldssuper_scores(DataFrame, n_samples x n_components),block_scores(dict[str, DataFrame]),predictions(DataFrame on original Y scale),block_spe(dict[str, Series], per-block SPE of the new observations) andhotellings_t2(Series of cumulative Hotelling’s T² over all components, per new observation).The rename (since 1.38.4, #395) matches
PLS.diagnose()and PCA.diagnose;predict()is kept as a deprecation shim.
- classmethod select_n_components(X, y, *, max_components=None, cv=5, n_repeats=None, random_state=None, selection_rule='1se', **mbpls_kwargs)[source]#
Select the number of multi-block PLS components by cross-validation.
Whole rows are held out. The super score of a held-out row is computed from its X-blocks alone and its Y is what the model predicts, so the value being predicted never enters its own prediction. That is the same argument that makes row-wise cross-validation sound for a single-block
PLS.select_n_components(), and it is unaffected by there being several X-blocks.Each block is centred and scaled inside
fit(), on the training rows only, so the fold statistics never see the held-out rows.One model is fitted per fold and per component count, because the hierarchical NIPALS deflation means an
a-component model is not recoverable from anA-component one. The cost iscv * n_repeats * max_componentsfits.- Parameters:
X (dict[str, pd.DataFrame]) – X-blocks, keyed by block name, all sharing
y’s row index.y (pd.DataFrame) – Y-block, one row per observation.
max_components (int, optional) – Largest component count to evaluate. Defaults to the largest the smallest training fold supports, capped at the total width of the X-blocks.
cv (int or sklearn CV splitter, default 5) – An integer is used as the
n_splitsof a shuffledKFold, or of aRepeatedKFoldwhenn_repeats > 1. A splitter object is used as given, andn_repeatsis then ignored.n_repeats (int, optional) – How many times to repeat the split with a fresh shuffle. Resolved to 10 when
cvis an integer; pass 1 to disable repeats.random_state (int, optional) – Seed for the shuffling. Ignored when
cvis a splitter.selection_rule ({"1se", "min", "q2_increment"}, default "1se") – How
n_componentsis chosen from the curve. SeeSelectionRule."randomization"is not offered here.**mbpls_kwargs – Passed to every
MBPLSfitted, for instancetoloralgorithm.
- Returns:
With
n_components(int),rmsecvandse_rmsecv(Series indexed1..A),per_fold_rmsecv(DataFrame, components by fold),press(Series),r2y_validated(DataFrame with one column per target, plus"total"on the original Y scale and"scaled_total"with every target weighted equally),cv_predictions(DataFrame of the held-out predictions of the recommended model, averaged over repeats) andselection_rule.- Return type:
- Raises:
ValueError – If
Xis not a non-empty dict of frames sharingy’s index, or if no component count could be evaluated.
Examples
>>> import numpy as np, pandas as pd >>> from process_improve.multivariate.methods import MBPLS >>> rng = np.random.default_rng(0) >>> t = rng.standard_normal((40, 2)) >>> blocks = { ... "a": pd.DataFrame(t @ rng.standard_normal((2, 5)) + rng.standard_normal((40, 5)) * 0.3), ... "b": pd.DataFrame(t @ rng.standard_normal((2, 4)) + rng.standard_normal((40, 4)) * 0.3), ... } >>> Y = pd.DataFrame(t @ rng.standard_normal((2, 2)) + rng.standard_normal((40, 2)) * 0.3) >>> out = MBPLS.select_n_components(blocks, Y, max_components=3, cv=5, n_repeats=2, random_state=0) >>> 1 <= out.n_components <= 3 True
- predict(X)[source]#
Forward to
diagnose(); emits aDeprecationWarning.Deprecated since version 1.38.4: Use
MBPLS.diagnose()instead. The sklearn-conventionpredictname suggests a regression-style ndarray return, but the historical return is the rich diagnostics Bunch. The rename aligns withPLS.diagnose()andPCA.diagnose()and frees the name for a future contract that returns just thepredictionsfield. Will be removed in 2.0.0.
- set_score_request(*, sample_weight='$UNCHANGED$')#
Configure whether metadata should be requested to be passed to the
scoremethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed toscoreif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it toscore.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- process_improve.multivariate.methods.randomization_test_mbpls(model, X, y, n_permutations=200, *, seed=None)[source]#
Randomization (permutation) test for component significance in MBPLS.
For each component
a, the null hypothesis is “there is no real relationship between X and Y at this component”; the test permutes the rows ofy, refits a fresh MBPLS with the same number of components, and recomputes the test statistic. The risk is the fraction of permutations whose statistic equals or exceeds the original model’s.Statistic: per-component absolute correlation between the super X-score and the super Y-score,
|t_super(:,a)' u_super(:,a)| / (||t|| * ||u||).- Parameters:
model (MBPLS) – A fitted MBPLS model.
X (dict[str, DataFrame], DataFrame) – The same training data used to fit
model.y (dict[str, DataFrame], DataFrame) – The same training data used to fit
model.n_permutations (int, default=200) – Number of Y-row permutations to evaluate.
seed (int or None, default=None) – Seed for the permutation RNG (
Noneuses non-reproducible randomness).
- Returns:
Indexed by component
1..Awith columns:observed: the actual model’s per-component statistic.risk_pct: Monte-Carlo estimate (in %) of the right-tail probability,100 * (n_exceed + 1) / (n_permutations + 1). Low values (e.g. < 5%) suggest the component is significant; values near 50% suggest the component is no better than chance.The
+ 1on each side counts the observed statistic among the permutations, which is what keeps the estimate a valid p-value: the uncorrectedn_exceed / n_permutationscan report exactly 0, and no finite permutation set can license the claim that the true tail probability is zero. The floor is100 / (n_permutations + 1), so the default 999 permutations cannot resolve below 0.1%. This matches the convention already used by the Van der Voet test in_pls(#513).
- Return type:
pd.DataFrame
References
Wiklund, S., Nilsson, D., Eriksson, L., Sjöström, M., Wold, S. & Faber, K. A randomization test for PLS component selection. J. Chemometrics, 21 (2007), 427-439.
MBPCA#
Multi-block PCA / consensus-PCA. Same dict-of-DataFrames API as
MBPLS; no Y-block.
- class process_improve.multivariate.methods.MBPCA(n_components, *, max_iter=500, tol=None, algorithm='auto', missing_data_settings=None)[source]#
Bases:
_HotellingsT2LimitMixin,TransformerMixin,BaseEstimatorMulti-block PCA (hierarchical / consensus PCA).
Generic multi-block PCA following the consensus-PCA / hierarchical PCA formulation of Westerhuis, Kourti & MacGregor (1998). Each X-block is preprocessed independently (mean-centred and unit-variance scaled), then divided by
sqrt(K_b)so blocks of unequal width contribute fairly to the consensus super-score.The hierarchical NIPALS loop alternates: (i) regress each block on the super-score to get block loadings and block scores, (ii) collect block scores into a super-block, (iii) regress the super-block to get a new super-score / super-loading, repeat to convergence. After convergence, deflate every block by the super-score and the corresponding block loading scaled by the super-loading element.
- Parameters:
n_components (int) – Number of super-components (consensus latent variables) to extract.
max_iter (int, default=500) – Maximum NIPALS iterations per component in the hierarchical outer loop.
tol (float or None, default=None) – Relative convergence tolerance on the super-score change: the norm of the change between two successive super-score iterations, divided by the norm of the current super-score vector (#504).
Noneusesepsqrt(about 1.49e-8), the same default as PCA / PLS / TPLS. The legacy absolute tolerancenp.finfo(float).eps ** (9/10)(about 8.2e-15) sits below the floating-point oscillation floor of a relative criterion, so it would never be reached in practice.algorithm (str) –
Algorithm to use for fitting the model.
"auto": dense vectorised hierarchical NIPALS when the data is complete; mask-aware NIPALS (NaN-tolerant) when any block contains missing values."dense": dense vectorised hierarchical NIPALS. Raises if any block contains missing values."nipals": mask-aware hierarchical NIPALS. Always uses the NaN-tolerant inner-loop primitives, even when the data is complete (slower than"dense"but produces equivalent results).
missing_data_settings (dict or None, default=None) – Settings for the iterative
"nipals"path. Keys:md_tol(convergence tolerance on the score-vector change between iterations),md_max_iter(maximum NIPALS iterations per component). Defaults to{"md_tol": epsqrt, "md_max_iter": 1000}.fitting) (Attributes (after)
--------------------------
block_names (list[str]) – Ordered list of X-block names (the keys of the input dict).
block_widths (dict[str, int]) – Number of variables in each X-block.
n_samples (int) – Number of rows fitted.
n_features_in (int) – Total number of X variables summed across blocks.
feature_names_in (np.ndarray) – Concatenated column names, one per feature, in block order.
preproc (dict[str, MCUVScaler]) – Per-block preprocessors used to mean-centre and unit-variance scale each X-block.
super_scores (pd.DataFrame, shape (n_samples, n_components)) – Super-block (consensus) scores
T.super_loadings (pd.DataFrame, shape (n_blocks, n_components)) – Super-block loadings
p_super; rows indexed by block name.super_hotellings_t2 (pd.DataFrame, shape (n_samples, n_components)) – Cumulative Hotelling’s T^2 on the super-scores per component.
block_scores (dict[str, pd.DataFrame]) – Per-block scores
t_b, each shape(n_samples, n_components).block_loadings (dict[str, pd.DataFrame]) – Per-block loadings
p_b, each shape(K_b, n_components).block_spe (dict[str, pd.DataFrame]) – Per-block squared prediction error per sample and component.
block_hotellings_t2 (dict[str, pd.DataFrame]) – Per-block cumulative Hotelling’s T^2 per sample and component.
block_vip (dict[str, pd.Series]) – Per-block variable-importance in projection, indexed by variable name inside each block.
r2_x_per_block_cumulative (pd.DataFrame, shape (n_blocks, n_components)) – Cumulative R^2X per block and component.
r2_x_per_block_per_component (pd.DataFrame, shape (n_blocks, n_components)) – Incremental R^2X per block and component.
r2_x_per_variable (dict[str, pd.DataFrame]) – Cumulative R^2X per variable within each block.
explained_variance (np.ndarray, shape (n_components,)) – Variance of the super-score per component (ddof=1).
scaling_factor_for_super_scores (pd.Series) –
sqrt(explained_variance_)per component.fitting_info (dict) – Per-component iteration count and timing.
has_missing_data (bool) – Whether any X-block had NaN values.
algorithm – The resolved algorithm actually used for the fit. With
algorithm="auto", this is"dense"for complete data and"nipals"for NaN-containing data.
Notes
The deflation step is \(X_b \leftarrow X_b - t_{\rm super}\, (p_b\,p_s[b]\,\sqrt{K_b})^\top\), derived in Westerhuis et al. 1998. An earlier implementation of this method had this step marked as broken by its author; this implementation re-derives it directly from the paper and is independently validated against the pure-numpy reference oracles in the test suite.
Missing data#
When any block contains NaN entries, the
"auto"algorithm routes to a mask-aware NIPALS variant. Each per-block projection in the inner loop is computed as a regression that uses only the observed entries; the masked sum-of-squares is used as the denominator so missing values neither bias the loading direction nor contribute to the score. The mask is preserved across components automatically because deflation propagates NaN through subtraction. This is the standard skip-NaN NIPALS update; see Walczak & Massart (2001) and Arteaga & Ferrer (2002).The fit refuses to run if any block has a column with all entries missing, or any block has a row with all entries missing for that block; either case leaves the masked denominator at zero. Drop or impute such rows or columns before fitting. Predict-time score estimation for new observations with NaN (Trimmed Score Regression / Projection to the Model Plane) is a separate follow-up.
References
Westerhuis, J. A., Kourti, T. & MacGregor, J. F. Analysis of multiblock and hierarchical PCA and PLS models. J. Chemometrics, 12 (1998), 301-321.
Walczak, B. & Massart, D. L. Dealing with missing data: Part I. Chemom. Intell. Lab. Syst., 58 (2001), 15-27.
Arteaga, F. & Ferrer, A. Dealing with missing data in MSPC: several methods, different interpretations, some examples. J. Chemometrics, 16 (2002), 408-418.
- diagnose(X)[source]#
Project new data; return super_scores, block_scores, block_spe, hotellings_t2.
The rename (since 1.38.4, #395) matches
PCA.diagnose()andPLS.diagnose();predict()is kept as a deprecation shim.
- predict(X)[source]#
Forward to
diagnose(); emits aDeprecationWarning.Deprecated since version 1.38.4: Use
MBPCA.diagnose()instead.predictmatches the sklearn-convention name but MBPCA isn’t a regressor; the historical return is a diagnostics Bunch. The rename aligns withPCA.diagnose(). Will be removed in 2.0.0.
- block_spe_limit(block, conf_level=0.95)[source]#
SPE limit for one X-block (Nomikos & MacGregor chi-square approximation).
- spe_contributions(X)[source]#
Per-variable squared residuals for each X-block (SPE contributions).
Reconstruction matches the MBPCA deflation step:
X_b = T_super @ (P_b * p_super[b] * sqrt(K_b))^Tsummed over components. Returns squared residuals on the preprocessed scale; sum across columns equalsblock_spe_[b].iloc[:, -1] ** 2.
- score_contributions(X, component=1, scaling='none')[source]#
Per-block per-variable contributions to a super-score (MBPCA).
The multi-block analogue of
PCA.score_contributions(). A super score is a weighted sum of the (deflated, preprocessed) variables across every block, so it splits exactly into one term per variable:\[c_{b,ij}^{(a)} = \tilde{x}_{b,ij}^{(a)}\, \frac{P_b[j, a]\, p_\mathrm{super}[b, a]} {(p_b^\top p_b)(p_\mathrm{super}^\top p_\mathrm{super})\sqrt{K_b}}, \qquad \sum_b \sum_j c_{b,ij}^{(a)} = t_{\mathrm{super},ia},\]where \(\tilde{x}^{(a)}\) is the block data deflated through the first \(a-1\) components.
See
MBPLS.score_contributions()for the parameter and return descriptions; the API is identical.
- group_contributions(X, group, reference=None, component=1)[source]#
Per-block per-variable contributions to a group’s average super score.
The multi-block analogue of
PCA.group_contributions(). See that method for the definition; the result is returned one Series per X-block, and the sum over every block equals the group’s average super score (or the difference between the two groups’ average super scores whenreferenceis given).
- super_score_plot(pc_horiz=1, pc_vert=2)[source]#
Scatter plot of MBPCA super-scores for two components.
Analysis#
- process_improve.multivariate.methods.rv_coefficient(X, Y)[source]#
Compute the RV coefficient between two data blocks.
The RV coefficient (Robert and Escoufier, 1976) measures how much common structure two matrices, measured on the same observations, share. It is a multivariate generalisation of the squared Pearson correlation: it compares the observation-by-observation configuration matrices \(XX^T\) and \(YY^T\) rather than individual variables.
- Parameters:
- Returns:
The RV coefficient in the range [0, 1]. A value of 1 means the two blocks describe the same configuration of observations up to a rotation and an overall scaling; 0 means no shared structure.
nanis returned if either block has no variance.- Return type:
Notes
Each column is mean-centred internally, since the RV coefficient is defined on centred data. The blocks are not scaled; scale the columns yourself (for example with
MCUVScaler) when the variables have different units.For high-dimensional data (many more variables than observations) the RV coefficient is biased upwards and tends towards 1 even for unrelated blocks. Use
rv2_coefficient()in that regime.References
Robert, P. and Escoufier, Y. (1976). A unifying tool for linear multivariate statistical methods: the RV-coefficient. Journal of the Royal Statistical Society, Series C, 25(3), 257-265.
See also
rv2_coefficientModified RV coefficient, unbiased for high-dimensional data.
Examples
>>> rv_coefficient(X, Y) >>> rv_coefficient(X, X) # 1.0: a block is perfectly correlated with itself
- process_improve.multivariate.methods.rv2_coefficient(X, Y)[source]#
Compute the modified RV coefficient (RV2) between two data blocks.
The modified RV coefficient (Smilde et al., 2009) is a variant of
rv_coefficient()that removes the diagonals of the configuration matrices \(XX^T\) and \(YY^T\) before comparing them. This removes the upward bias that makes the ordinary RV coefficient tend towards 1 for high-dimensional data, so RV2 stays near 0 for genuinely unrelated blocks.- Parameters:
- Returns:
The modified RV coefficient, in the range [-1, 1]. A value of 1 means the two blocks describe the same configuration of observations; values near 0 mean no shared structure, and small negative values can occur.
nanis returned if either block has no variance.- Return type:
Notes
Each column is mean-centred internally but the blocks are not scaled; scale the columns yourself (for example with
MCUVScaler) when the variables have different units.References
Smilde, A. K., Kiers, H. A. L., Bijlsma, S., Rubingh, C. M. and van Erk, M. J. (2009). Matrix correlations for high-dimensional data: the modified RV-coefficient. Bioinformatics, 25(3), 401-405.
See also
rv_coefficientThe original RV coefficient.
Examples
>>> rv2_coefficient(X, Y)
Containers#
- class process_improve.multivariate.methods.BlockSet(blocks)[source]#
Bases:
dictA
dict[str, pd.DataFrame]of equal-height blocks that can also be sliced by row (#193).MBPCA.fitandMBPLS.fittake a plaindict[str, pd.DataFrame], which is convenient to build and impossible to resample: a dict has no notion of “row 7 of every block”. Any resampling or cross-validation pass needs exactly that.Resampler, for instance, asks its data only forlen(x)andx[indices].TPLS already has
DataFrameDictfor this, but it is hardwired to theZ/F/Yblock names and to a nesteddict[str, dict[str, DataFrame]]layout, so the multi-block models could not borrow it.BlockSetis the flat equivalent: a realdictsubclass, so anything that already accepts the plain dict keeps working, plus row indexing.Warning
len(blocks)is the number of rows, not the number of blocks. That is surprising for a dict, and it is deliberate: it is the conventionDataFrameDictalready set, and it is what the resampling code means by the length of a dataset. Uselen(blocks.keys())to count blocks.- Parameters:
blocks (dict[str, pd.DataFrame]) – One entry per block. Every block must be a DataFrame with the same number of rows; widths may differ.
- Raises:
ValueError – If
blocksis empty, or the blocks disagree on their row count.TypeError – If any value is not a DataFrame.
Examples
>>> blocks = BlockSet({"a": df_a, "b": df_b}) >>> len(blocks) # rows, not blocks 40 >>> blocks[[0, 1, 2]].keys() # a 3-row BlockSet dict_keys(['a', 'b'])
- class process_improve.multivariate.methods.DataFrameDict(datadict)[source]#
Bases:
dictContainer for the partitionable (Z, F) and static (Y) data blocks used by TPLS.
Preprocessing#
- class process_improve.multivariate.methods.MCUVScaler[source]#
Bases:
TransformerMixin,BaseEstimatorMean-centre, unit-variance (MCUV) scaler.
Unlike
sklearn.preprocessing.StandardScalerthis uses the sample standard deviation (ddof=1), the convention for chemometric data analysis where the population is the training set itself rather than a sampled super-population.The estimator follows the standard sklearn contract:
n_features_in_andfeature_names_in_are populated byfit; sparse / complex / object dtype / empty input are rejected with sklearn-style errors; NaN values pass through (the chemometric preprocessing pipeline expects to thread missing-data through to the downstream NIPALS estimator).- get_feature_names_out(input_features=None)[source]#
Return the output column names of
transform().MCUVScaleris column-preserving (centring + scaling leave the X column layout unchanged), so the returned names mirror those captured duringfit()(or theinput_featuresargument when nofeature_names_in_was captured - the standard sklearn fallback for ndarray-fit estimators).Used by
set_output()(sklearn 1.2+) to label theDataFrameview of the output whenset_output(transform="pandas")is on, and by Pipeline introspection.- Return type:
- fit(X, y=None)[source]#
Compute the column means and sample standard deviations.
yis accepted (and ignored) so the scaler plugs intosklearn.pipeline.Pipeline, which threadsythrough every step’sfiteven when (as for a transformer) it is unused.
- process_improve.multivariate.methods.center(X, func=<function mean>, axis=0, extra_output=False)[source]#
Perform centering of data, using a function, func (default: np.mean). The function, if supplied, must return a vector with as many columns as the matrix X.
axis [optional; default=0] {integer}
This specifies the axis along which the centering vector will be calculated if not provided. The function is applied along the axis: 0=down the columns; 1 = across the rows.
Missing values: with the default
func=np.mean, any NaN along the reduction axis propagates into the centring vector, so an entire row or column of the returned data can end up NaN. To skip missing entries instead (summing along the axis, dividing by the number of values that are present, and leaving pre-existing NaNs as NaNs in the output), passfunc=np.nanmean.- Returns:
centred (DataMatrix) – The centred data, returned when
extra_output=False(the default).(centred, centre_vector) (tuple[DataMatrix, np.ndarray]) – When
extra_output=True, a tuple of the centred data and the centring vector.
- Parameters:
- Return type:
Notes
The extra output of
center()andscale()are not the same kind of quantity.center()returns the value that was subtracted, so replaying it means subtracting again.scale()returns the multiplier it applied, which is the reciprocal of func, so replaying that one means multiplying, not dividing. Getting the two the same way round is wrong by a factor of the variance:centred, subtrahend = center(X, extra_output=True) scaled, multiplier = scale(centred, extra_output=True) # replay on new rows: new_scaled = (new_X - subtrahend) * multiplier # note: minus, then times
They also disagree on degrees of freedom:
scale()defaults toddof=0whileMCUVScalerusesddof=1, a factor ofsqrt(n / (n - 1)). PreferMCUVScalerwhen preparing data for a PCA / PLS fit; it does both steps together, keeps the constants as fitted attributes, and has aninverse_transform().See also
MCUVScalerMean-centre and unit-variance scale in one fitted estimator.
scaleThe scaling counterpart, whose extra output is a multiplier.
- process_improve.multivariate.methods.scale(X, func=<function std>, axis=0, extra_output=False, ddof=0, **kwargs)[source]#
Scales the data (does NOT do any centering); scales to unit variance by default.
- func [optional; default=np.std] {a function}
The default (
np.std) uses NumPy to calculate the standard deviation of the data along the required axis and uses that as scale. Any NaN along the reduction axis propagates into the resulting scale vector, so an entire row or column of the returned data can end up NaN. Passfunc=np.nanstdto skip missing entries instead.- axis [optional; default=0] {integer}
Transformations are applied on slices of data. This specifies the axis along which the transformation will be applied.
- ddof [optional; default=0] {integer}
Delta degrees of freedom, forwarded to np.std when func is the default np.std. The standard deviation is computed by dividing by
N - ddof, where N is the number of values which are present. The default (ddof=0) divides by N (the population standard deviation); passddof=1for the sample standard deviation (dividing by N-1).Note:
MCUVScalerusesddof=1and is the preferred scaler for fitting PCA / PLS models. Usescale(center(X), ddof=1)here to match it. Theddofargument is ignored when a custom func is supplied (forward your own keyword arguments via**kwargsinstead).
Constant (zero-variance) columns are left unchanged: a zero entry in the computed scaling vector is replaced by 1.0 before inversion, mirroring
MCUVScaler, so noinf/NaNis introduced.Usage#
X = … # data matrix X = scale(center(X)) X = scale(center(X), ddof=1) # sample standard deviation, matches MCUVScaler from scipy.stats import median_abs_deviation as my_scale X = scale(center(X), func=my_scale)
- returns:
scaled (DataMatrix) – The scaled data, returned when
extra_output=False(the default).(scaled, scale_vector) (tuple[DataMatrix, np.ndarray]) – When
extra_output=True, a tuple of the scaled data and the per-column scaling vector (the reciprocal of func applied along axis, with zero entries replaced by 1.0 to leave constant columns unchanged) is returned instead.
Notes
The extra output of
scale()andcenter()are not the same kind of quantity. This function returns the multiplier it applied (the reciprocal of func), whereascenter()returns the value it subtracted. Replaying a scaling on new rows therefore means multiplying byscale_vector; dividing by it is wrong by a factor of the variance. If dividing reads more naturally, invert it explicitly and name the variable for what it is:scaled, multiplier = scale(centred, extra_output=True) divisor = 1.0 / multiplier
The two also disagree on degrees of freedom: this function defaults to
ddof=0whileMCUVScalerusesddof=1, a factor ofsqrt(n / (n - 1)). PreferMCUVScalerwhen preparing data for a PCA / PLS fit.See also
MCUVScalerMean-centre and unit-variance scale in one fitted estimator.
centerThe centring counterpart, whose extra output is a subtrahend.
Diagnostics#
These functions work with fitted PCA and PLS models. Each is
also bound as a convenience method on the model after fit().
Note
Two different “contributions” diagnostics. The library has two methods whose names both contain “contributions”; they are not interchangeable and answer different questions about the same fitted score matrix.
PCA.score_contributions()(andPLS.score_contributions()) is per-variable and signed. It splits each score into the K terms \(x_{ik} R_{ka}\) that form it, answering “which variables explain why this observation sits where it does?”. It takes the preprocessed data and returns a sample-by-variable table whose rows sum to the score being decomposed.observation_contributions()is per-observation and non-negative. It reports each observation’s share of a component’s total inertia (\(t_{ia}^2 / \sum_i t_{ia}^2\)), answering “which observations most strongly shape this component?”. It returns a sample-by-component table whose columns each sum to 1, and it takes no input beyond the fitted model.
In short, score_contributions decomposes across variables while
observation_contributions decomposes across observations.
- process_improve.multivariate.methods.vip(model, n_components=None)[source]#
Calculate Variable Importance in Projection (VIP) scores.
Works with fitted
PCAandPLSmodels. For PCA the principal-component loadingsloadings_are used as the weight matrix; for PLS the X-block weightsx_weights_are used.The formula is:
\[\begin{split}\\text{VIP}_j = \\sqrt{K \\cdot \\frac{\\sum_{a=1}^{A} r2_a \\cdot w_{ja}^2}{\\sum_{a=1}^{A} r2_a}}\end{split}\]where \(K\) is the number of features, \(A\) the number of components, \(r2_a\) the fraction of variance explained by component \(a\), and \(w_{ja}\) the weight for feature \(j\) in component \(a\).
- Parameters:
- Returns:
VIP scores indexed by feature names, named
"VIP".- Return type:
pd.Series
- Raises:
ValueError – If the model is not fitted, if neither
x_weights_norloadings_is found, or if n_components is out of range.
Notes
The
Kfactor in the formula above normalises the scores so that\[\sum_{j=1}^{K} \text{VIP}_j^2 = K\]exactly, for any model, on any data. That identity is what makes the familiar “VIP > 1” rule a sensible relative cut-off: the mean square is 1 by construction, so a score above 1 means the variable is above-average within this model.
It also means the number of variables exceeding VIP 1 is not a test statistic. That count describes the shape of the VIP distribution, not whether any relationship exists, and it barely moves when the response is permuted: a null built on it has almost no power and will report a false-discovery rate near 100% on data that genuinely contains signal. If you want to ask “is there anything here at all”, permute the response and compare out-of-sample performance instead. See
check_predictive_signal().See also
process_improve.multivariate.check_predictive_signalA permutation null on Q² that does respond to signal.
Examples
>>> pls = PLS(n_components=3).fit(X_scaled, Y_scaled) >>> pls.vip() # bound convenience method after fit() >>> vip(pls) # or call the standalone function directly >>> pca = PCA(n_components=3).fit(X_scaled) >>> pca.vip(n_components=2)
- process_improve.multivariate.methods.squared_cosine(model, n_components=None)[source]#
Calculate the squared cosine (cos2): quality of representation of observations.
Works with fitted
PCAandPLSmodels. The squared cosine of observation \(i\) on component \(a\) is the squared score divided by that observation’s total variation budget:\[\begin{split}\\cos^2_{ia} = \\frac{t_{ia}^2} {\\sum_{a=1}^{A} t_{ia}^2 + \\text{SPE}_i^2}\end{split}\]where \(t_{ia}\) is the score and \(\\text{SPE}_i\) the residual (squared prediction error) of the observation. Across all components the cos2 values plus the residual fraction sum to 1. A value close to 1 means the observation is well represented on that component. For
PCA, whose loadings are orthonormal, the denominator equals the squared distance of the observation from the origin, matching the classical definition.cos2 complements the existing diagnostics: Hotelling’s T² measures distance within the model plane, SPE measures distance to it, and cos2 reports how much of an observation’s total variation a given component captures.
- Parameters:
- Returns:
cos2 values of shape (n_samples, n_components), indexed by sample.
- Return type:
pd.DataFrame
- Raises:
ValueError – If the model is not fitted, or if n_components is out of range.
Examples
>>> pca = PCA(n_components=3).fit(X_scaled) >>> pca.squared_cosine() # bound convenience method after fit() >>> squared_cosine(pca, n_components=2) # or call the function directly
- process_improve.multivariate.methods.observation_contributions(model, n_components=None)[source]#
Calculate the contribution of each observation to each component.
Works with fitted
PCAandPLSmodels. The contribution of observation \(i\) to component \(a\) is its squared score divided by the sum of squared scores of all observations on that component:\[\begin{split}\\text{contribution}_{ia} = \\frac{t_{ia}^2}{\\sum_{i=1}^{N} t_{ia}^2}\end{split}\]Values lie between 0 and 1 and each column sums to 1, so a contribution well above the average \(1/N\) flags an observation that strongly shapes that component. The exception is a component whose score column has zero variance (
sum(t_{ia}^2) = 0): the division cannot be computed, so the column is returned as zeros rather than NaN, and its sum is 0 rather than 1.Note that this is not the same diagnostic as the
score_contributionsmethod, despite the similar name.score_contributionsis per-variable and signed: it decomposes one observation’s position in score space back onto the original variables (“which variables explain why this observation sits where it does?”).observation_contributionsis per-observation and non-negative: it reports each observation’s share of a component’s total inertia (“which observations most strongly shape this component?”). The two are orthogonal views of the same score matrix and are not interchangeable.- Parameters:
- Returns:
Contributions of shape (n_samples, n_components), indexed by sample. Each column sums to 1 except columns for components whose score has zero variance, which are returned as zeros and sum to 0.
- Return type:
pd.DataFrame
- Raises:
ValueError – If the model is not fitted, or if n_components is out of range.
Examples
>>> pca = PCA(n_components=3).fit(X_scaled) >>> pca.observation_contributions() >>> observation_contributions(pca, n_components=2)
See also
PCA.score_contributionsThe per-variable counterpart - decomposes one observation’s score-space position back onto the original variables.
- process_improve.multivariate.methods.score_contributions(model, X, component=1, scaling='none', *, method='scp', ridge=0.0, **deprecated)[source]#
Per-variable contributions to a single score, \(t_a\).
Works with fitted
PCAandPLSmodels. A score is a weighted sum of the (preprocessed) variables, so it splits exactly into one term per variable. The contribution of variable \(k\) to the score of observation \(i\) on component \(a\) is\[c_{ik}^{(a)} = x_{ik}\, R_{ka}, \qquad \sum_{k=1}^{K} c_{ik}^{(a)} = t_{ia},\]where \(R\) is the score-generating matrix (
loadings_for PCA,direct_weights_for PLS, so that \(T = XR\)). This is the contribution of Miller, Swanson and Heckler (1994); the generalisation from PCA loadings to any latent-variable model’s score-generating weights follows Westerhuis, Gurden and Smilde (2000).The distinction from a loading plot is the point of the diagnostic. A loading \(R_{ka}\) describes the whole data set; a contribution \(x_{ik} R_{ka}\) describes one observation, and a variable with a large loading contributes nothing when that observation sits at its mean. Ranking variables by loading can therefore point at a different cause than ranking them by contribution.
A row with missing cells (NaN) gets its scores from its observed cells alone, with the estimator named by
method, the missing-data operators ofPCA.project()andPLS.project(). Its contributions are then defined at every observed cell and NaN at the missing ones, so the row sums (sum(axis=1), which skips NaN) keep their meaning. The default"scp"is the single-component projection the NIPALS fit uses, so on a PCA model fitted with missing data the training rows reproduce the storedscores_,hotellings_t2_andspe_to within the NIPALS convergence tolerance, as the complete rows do.- Parameters:
X (array-like of shape (n_samples, n_features)) – Preprocessed data, scaled the same way as the training data (for example with
MCUVScaler). Passing the training data reproduces the model’s storedscores_.component (int, default=1) – 1-based component index whose score is decomposed, matching the model’s column convention.
scaling ({"none", "maximum", "within"}, default="none") – Presentation scaling from Miller, Swanson and Heckler (1994).
"none"returns the raw contributions, which sum to the score."maximum"divides by the largest absolute contribution anywhere inX, so a bar of \(\pm 1\) marks the most extreme variable-observation pair in the data set."within"divides each row by the sum of its absolute contributions, so each row is on a common footing. Both scalings leave the pattern of bars within a row unchanged; neither preserves the sum to the score.method ({"scp", "tsr", "pmp"}, default="scp") – Score estimator for the rows with missing cells; ignored when
Xis complete. SeePCA.project().ridge (float, default=0.0) – Regularisation for the
"tsr"and"pmp"estimators, as inPCA.project().**deprecated – Rejected. Captures
t_end,componentsandweightedso that a call passing a score vector rather thanXraises aTypeErrorexplaining the correct usage. Passing a 1-DXraises the same error.
- Returns:
Signed contributions of shape (n_samples, n_features), NaN at the missing cells. With the default
scaling="none", each row sums to that observation’s score on the selected component.- Return type:
pd.DataFrame
Examples
>>> pca = PCA(n_components=2).fit(X_scaled) >>> contrib = pca.score_contributions(X_scaled, component=1) >>> contrib.sum(axis=1) # equals pca.scores_[1] >>> contrib.loc["33"].abs().sort_values() # what makes observation 33 extreme
References
Miller, P., Swanson, R.E. and Heckler, C.E. (1994). “Contribution plots: a missing link in multivariate quality control.” Applied Mathematics and Computer Science, 8(4), 775-792.
Westerhuis, J.A., Gurden, S.P. and Smilde, A.K. (2000). “Generalized contribution plots in multivariate statistical process monitoring.” Chemometrics and Intelligent Laboratory Systems, 51(1), 95-114.
See also
group_contributionsThe same decomposition for a group of observations, or for the difference between two groups.
t2_contributionsDecomposes Hotelling’s \(T^2\), which pools all components rather than reading one at a time.
spe_contributionsThe residual-space counterpart.
- process_improve.multivariate.methods.group_contributions(model, X, group=None, reference=None, component=1, weights=None)[source]#
Per-variable contributions to a group’s average score, or to a shift.
The group form of
score_contributions(). Combining the data rows before multiplying by the score-generating weights answers “what do these observations have in common?” rather than “why is this one observation unusual?”, which is the question a cluster on a score plot, or a level shift part-way through a data set, actually poses.In general any linear combination of the rows may be used (Miller, Swanson and Heckler, 1994):
\[c_k = \Bigl(\sum_i w_i x_{ik}\Bigr) R_{ka}, \qquad \sum_k c_k = \sum_i w_i t_{ia}.\]The common cases have their own arguments. With
groupalone the weights are \(1/n_G\) over the group, comparing its mean against the model centre. Withgroupandreferencethey are \(+1/n_G\) and \(-1/n_H\), so the contributions sum to the difference in average score, which is the level-shift diagnostic of the paper’s Figure 9. Passweightsdirectly for anything else: the paper suggests the first-order orthogonal polynomial when a run of batches is drifting rather than stepping.- Parameters:
X (array-like of shape (n_samples, n_features)) – Preprocessed data, scaled the same way as the training data.
group (sequence, optional) – Index labels of the observations of interest, or a boolean mask the same length as
X. Selection is by label, not by position; passX.index[...]to select positionally. Required unlessweightsis given.reference (sequence, optional) – Index labels selecting the observations to compare against.
None(default) compares the group against the model centre.component (int, default=1) – 1-based component index whose score is decomposed.
weights (sequence, optional) – One weight per row of
X, giving the linear combination directly. Mutually exclusive withgroup/reference.
- Returns:
Signed contributions, one per variable. Sums to the weighted combination of the scores: the group’s average score, the difference in average score between the two groups, or \(\sum_i w_i t_{ia}\).
- Return type:
pd.Series
Examples
>>> pca = PCA(n_components=2).fit(X_scaled) >>> # Five batches that cluster together on the score plot: >>> pca.group_contributions(X_scaled, group=[31, 142, 147, 220, 221]) >>> # What shifted at batch 74? (ten batches either side, by position) >>> pca.group_contributions( ... X_scaled, group=X_scaled.index[64:74], reference=X_scaled.index[74:84] ... ) >>> # A run of batches drifting rather than stepping: weight by a >>> # first-order orthogonal polynomial over the run. >>> slope = np.zeros(len(X_scaled)) >>> slope[40:60] = np.arange(20) - 9.5 >>> pca.group_contributions(X_scaled, weights=slope, component=3)
See also
score_contributionsThe single-observation form.
- process_improve.multivariate.methods.eigenvalue_summary(model)[source]#
Summarize the variance captured by each component as a tidy table.
Works with fitted
PCAandPLSmodels. Returns one row per component, collectingexplained_variance_,r2_per_component_andr2_cumulative_into a single table.- Parameters:
- Returns:
Indexed by component, with columns
eigenvalue(the variance of the component scores),percent_varianceandcumulative_percent. For PCA the percentages refer to variance in X; for PLS they refer to the variance in Y explained by each component.- Return type:
pd.DataFrame
- Raises:
ValueError – If the model is not fitted.
Examples
>>> pca = PCA(n_components=3).fit(X_scaled) >>> pca.eigenvalue_summary() >>> eigenvalue_summary(pca)
- process_improve.multivariate.methods.project_variables(model, supplementary_data)[source]#
Project supplementary (passive) variables onto a fitted model.
Works with fitted
PCAandPLSmodels. Supplementary variables are extra columns that did not take part in fitting the model but were measured on the same observations. Each supplementary variable is represented by its correlation with each component’s scores, the standard representation for passive quantitative variables. This is the column-wise counterpart oftransform, which projects supplementary rows (new observations).- Parameters:
- Returns:
Correlations of shape (n_supplementary, n_components): the coordinate of each supplementary variable on each component.
- Return type:
pd.DataFrame
- Raises:
ValueError – If the model is not fitted, or if supplementary_data does not have the same number of rows as the training data.
Examples
>>> pca = PCA(n_components=3).fit(X_scaled) >>> pca.project_variables(passive_columns) >>> project_variables(pca, passive_columns)
Warnings#
Both classes are importable from process_improve.multivariate as well as
from process_improve.multivariate.methods, so a filterwarnings entry
never has to name a private module.
- class process_improve.multivariate.SpecificationWarning[source]#
Bases:
UserWarningParent warning class.
- class process_improve.multivariate.UncentredDataWarning[source]#
Bases:
SpecificationWarningEmitted when a model that fits no intercept is handed an un-centred block.
Raised by
PLS.fitunderscale=False, which centres nothing and fits no intercept, so a block carrying a non-zero mean displaces every prediction.It exists as its own class so a caller who fits un-centred data on purpose can permit this diagnostic without going blind to the rest. Under a
filterwarnings = errorpolicy:# pytest.ini / pyproject.toml, or @pytest.mark.filterwarnings on one test filterwarnings = error ignore::process_improve.multivariate.UncentredDataWarning
Narrower still, and with no global filter at all, is the estimator flag:
PLS(..., scale=False, warn_on_uncentred=False)silences the check for that one model and leaves every otherSpecificationWarningin force.Subclasses
SpecificationWarning, so filters andpytest.warnsassertions written against the parent keep matching it.
Plots#
- process_improve.multivariate.plots.score_plot(model, pc_horiz=1, pc_vert=2, pc_depth=-1, items_to_highlight=None, settings=None, fig=None, *, sizes=None, size_name='')[source]#
Generate a 2D or 3D score plot for the given latent variable model.
A 2D scatter on (
pc_horiz,pc_vert) is produced by default. Supplyingpc_depth >= 1adds a third score axis and switches the underlying trace toScatter3d.- Parameters:
model (MVmodel object (PCA, or PLS)) – A latent variable model generated by this library.
pc_horiz (int, optional) – Which component to plot on the horizontal axis, by default 1 (the first component)
pc_vert (int, optional) – Which component to plot on the vertical axis, by default 2 (the second component)
pc_depth (int, optional) – If pc_depth >= 1, then a 3D score plot is generated, with this component on the 3rd axis
items_to_highlight (dict, optional) –
Keys are JSON strings parseable by
json.loadsinto a Plotly line specifier; values are lists of index names to highlight. For example:items_to_highlight = {'{"color": "red", "symbol": "cross"}': items_in_red}
will highlight the items in
items_in_redwith the given colour and shape.sizes (pd.Series, optional) – One non-negative value per observation, indexed as the scores are. The marker area is made proportional to it, so that a marker of twice the area stands for twice the value, and the largest value is drawn
settings["size_max"]pixels across. The plain and the highlighted traces share one scale, and a highlighted point keeps its own area rather than being enlarged, because two meanings on one channel cannot both be read. Give the reader that scale as well: an area cannot be read off a plot on its own.size_name (str, optional) – What
sizesmeasures, for example"SPE"; it names the value in the hover text.settings (dict) –
Default settings:
{ "show_ellipse": True, # bool: show the Hotelling's T2 ellipse "ellipse_conf_level": 0.95, # float: ellipse confidence level (< 1.00) "title": "", # str: overall plot title. The # default is the empty string on # the 2D path (pc_depth <= 0) and # a "Score plot of component ..." # sentence on the 3D path # (pc_depth > 0). "show_labels": False, # bool: add a label for each observation "show_legend": True, # bool: show clickable legend "size_max": 26, # float: diameter in pixels of the # largest marker, when `sizes` is given "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (Figure | None)
- Return type:
Figure
Examples
>>> pca = PCA(n_components=3).fit(X_scaled) >>> pca.score_plot() # PC1 vs PC2 >>> pca.score_plot(pc_horiz=1, pc_vert=3) # PC1 vs PC3 >>> pca.score_plot(pc_horiz=1, pc_vert=2, pc_depth=3) # 3D
- process_improve.multivariate.plots.loading_plot(model, loadings_type='p', pc_horiz=1, pc_vert=2, settings=None, fig=None)[source]#
Generate a 2-dimensional loadings for the given latent variable model.
- Parameters:
model (MVmodel object (PCA, or PLS)) – A latent variable model generated by this library.
loadings_type (str, optional) –
- A choice of the following:
’p’ : (default for PCA) : the P (projection) loadings: only option possible for PCA ‘w’ : the W loadings: Suitable for PLS ‘w*’ : (default for PLS) the W* (or R) loadings: Suitable for PLS ‘w*c’ : the W* (from X-space) with C loadings from the Y-space: Suitable for PLS ‘c’ : the C loadings from the Y-space: Suitable for PLS
For PCA model any other choice besides ‘p’ will be ignored.
pc_horiz (int, optional) – Which component to plot on the horizontal axis, by default 1 (the first component)
pc_vert (int, optional) – Which component to plot on the vertical axis, by default 2 (the second component)
settings (dict) –
Default settings:
{ "title": "Loadings plot ...", # str: overall plot title "show_labels": True, # bool: add a label for each variable "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (Figure | None)
- Return type:
Figure
Examples
>>> pca.loading_plot() # P loadings, PC1 vs PC2 >>> pls.loading_plot(loadings_type="w*c") # W* and C loadings >>> pls.loading_plot(loadings_type="w", pc_vert=3) # W loadings, PC1 vs PC3
- process_improve.multivariate.plots.spe_plot(model, with_a=-1, items_to_highlight=None, settings=None, fig=None)[source]#
Generate a squared-prediction error (SPE) plot for the given latent variable model using with_a number of latent variables. The default will use the total number of latent variables which have already been fitted.
- Parameters:
model (MVmodel object (PCA, or PLS)) – A latent variable model generated by this library.
with_a (int, optional) – Uses this many number of latent variables, and therefore shows the SPE after this number of model components. By default the total number of components fitted will be used.
items_to_highlight (dict, optional) –
Keys are JSON strings parseable by
json.loadsinto a Plotly line specifier; values are lists of index names to highlight. For example:items_to_highlight = {'{"color": "red", "symbol": "cross"}': items_in_red}
will highlight the items in
items_in_redwith the given colour and shape.settings (dict) –
Default settings:
{ "show_limit": True, # bool: show the SPE confidence limit line "conf_level": 0.95, # float: confidence level for limit (< 1.00) "title": "SPE plot ...", # str: overall plot title "default_marker": {...}, # dict: e.g. dict(symbol="circle", size=7) "show_labels": False, # bool: add a label for each observation "show_legend": False, # bool: show clickable legend "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (Figure | None)
- Return type:
Figure
Examples
>>> pca.spe_plot() >>> pca.spe_plot(settings={"conf_level": 0.99, "show_labels": True})
- process_improve.multivariate.plots.t2_plot(model, with_a=-1, items_to_highlight=None, settings=None, fig=None)[source]#
Generate a Hotelling’s T2 (T^2) plot for the given latent variable model using with_a number of latent variables. The default will use the total number of latent variables which have already been fitted.
- Parameters:
model (MVmodel object (PCA, or PLS)) – A latent variable model generated by this library.
with_a (int, optional) – Uses this many number of latent variables, and therefore shows the Hotelling’s T2 after this number of model components. By default the total number of components fitted will be used.
items_to_highlight (dict, optional) –
Keys are JSON strings parseable by
json.loadsinto a Plotly line specifier; values are lists of index names to highlight. For example:items_to_highlight = {'{"color": "red", "symbol": "cross"}': items_in_red}
will highlight the items in
items_in_redwith the given colour and shape.settings (dict) –
Default settings. The default
titleinterpolates the class-levelconf_leveldefault (0.95), so a user-suppliedconf_levelcorrectly changes the limit line but the auto-generated title text still reads95.0%unless atitleoverride is passed too:{ "show_limit": True, # bool: show the T2 confidence limit line "conf_level": 0.95, # float: confidence level for limit (< 1.00) "title": "T2 plot ...", # str: overall plot title "default_marker": {...}, # dict: e.g. dict(symbol="circle", size=7) "show_labels": False, # bool: add a label for each observation "show_legend": False, # bool: show clickable legend "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (Figure | None)
- Return type:
Figure
Examples
>>> pca.t2_plot() >>> pca.t2_plot(settings={"conf_level": 0.99, "show_labels": True})
- process_improve.multivariate.plots.explained_variance_plot(model, settings=None, fig=None)[source]#
Generate an explained-variance plot for a fitted latent variable model.
Shows the variance explained by each component as bars, with the cumulative variance explained overlaid as a line. For PCA the variance refers to the X-block; for PLS it refers to the Y-block.
- Parameters:
model (MVmodel object (PCA, or PLS)) – A fitted latent variable model generated by this library.
settings (dict) –
Default settings:
{ "as_percentage": True, # bool: y-axis as a percentage, else a fraction "title": "Variance explained ...", # str: overall plot title "bar_color": None, # str|None: bar colour; None uses the theme "line_color": None, # str|None: line colour; None uses the theme "show_legend": True, # bool: show clickable legend "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Return type:
Figure
Examples
>>> pca.explained_variance_plot() >>> pls.explained_variance_plot(settings={"as_percentage": False})
- process_improve.multivariate.plots.correlation_loadings_plot(model, pc_horiz=1, pc_vert=2, variance_ellipses=(0.5, 1.0), settings=None, fig=None)[source]#
Generate a correlation loadings plot for a fitted latent variable model.
Each variable is placed by its correlation with the scores of two components. A variable’s squared distance from the origin is the fraction of its variance explained by those two components, so every variable lies inside the unit circle. Concentric ellipses mark variance-explained thresholds: a variable beyond the 50% ellipse has at least half of its variance captured by the two components shown.
For PCA the X-variables are shown. For PLS both the X-variables and the Y-variables are overlaid against the X-scores, which reveals how process variables relate to quality variables.
- Parameters:
model (MVmodel object (PCA, or PLS)) – A fitted latent variable model generated by this library.
pc_horiz (int, default 1) – Component shown on the horizontal axis (1-based).
pc_vert (int, default 2) – Component shown on the vertical axis (1-based).
variance_ellipses (sequence of float, default (0.5, 1.0)) – Variance-explained thresholds, each a fraction in (0, 1], at which to draw a concentric ellipse. The conventional choice is the 50% and 100% ellipses; any other thresholds (for example 0.75 and 0.95) are equally valid.
settings (dict) –
Default settings:
{ "title": "Correlation loadings ...", # str: overall plot title "x_marker_color": None, # str|None: X-variable marker colour; None uses the theme "y_marker_color": None, # str|None: Y-variable marker colour (PLS); None uses the theme "ellipse_color": "grey", # str: colour of the variance ellipses "show_labels": True, # bool: label each variable "show_legend": True, # bool: show clickable legend (PLS only) "html_image_height": 600, # int: image height in pixels "html_aspect_ratio_w_over_h": 1.0, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Return type:
Figure
Examples
>>> pca.correlation_loadings_plot() >>> pls.correlation_loadings_plot(pc_horiz=1, pc_vert=3) >>> pca.correlation_loadings_plot(variance_ellipses=(0.75, 0.95))
- process_improve.multivariate.plots.predictions_vs_observed_plot(model, *, y_observed, variable=None, settings=None, fig=None)[source]#
Generate an observed-vs-predicted (parity) plot for a fitted PLS model.
Plots the calibration predictions against the observed Y values, with a
y = xreference line and an RMSE annotation. Points lying close to the reference line indicate good predictions.- Parameters:
model (PLS object) – A fitted PLS model generated by this library.
y_observed (array-like of shape (n_samples, n_targets)) – The observed Y values, on the same scale as the data used to fit the model (for example the scaled Y from
MCUVScaler).variable (str, optional) – Which Y-variable to plot. Defaults to the first Y-variable.
settings (dict) –
Default settings:
{ "title": "Observed vs predicted ...", # str: overall plot title "marker_color": None, # str|None: data-marker colour; None uses the theme "reference_color": "#9CA3AF", # str: colour of the y = x line "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 1.0, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Return type:
Figure
Examples
>>> pls.predictions_vs_observed_plot(y_observed=Y_scaled) >>> pls.predictions_vs_observed_plot(y_observed=Y_scaled, variable="quality")
- process_improve.multivariate.plots.coefficient_plot(model, variable=None, settings=None, fig=None)[source]#
Generate a bar plot of the PLS regression coefficients.
Shows
beta_coefficients_for one Y-variable: one bar per X-variable, mapping the (preprocessed) X onto the predicted Y. Tall bars mark the X-variables that most strongly drive the prediction.- Parameters:
model (PLS object) – A fitted PLS model generated by this library.
variable (str, optional) – Which Y-variable’s coefficients to plot. Defaults to the first one.
settings (dict) –
Default settings:
{ "title": "Regression coefficients ...", # str: overall plot title "bar_color": None, # str|None: bar colour; None uses the theme "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Return type:
Figure
Examples
>>> pls.coefficient_plot() >>> pls.coefficient_plot(variable="quality")
- process_improve.multivariate.plots.confusion_matrix_plot(model, matrix=None, settings=None, fig=None)[source]#
Generate a confusion-matrix heat map for a fitted
PLSDAmodel.Rows are the true class, columns the predicted one, so the diagonal is what the model got right and every off-diagonal cell names a specific confusion: which class this one is mistaken for, which is the question a classification report cannot answer.
- Parameters:
model (PLSDA object) – A fitted PLS-DA model generated by this library.
matrix (pd.DataFrame, optional) – A confusion matrix to plot instead of the model’s training-set one, indexed and labelled by class. Pass
model.confusion(X_test, y_test).matrixto see the held-out picture, which is the one worth acting on:confusion_matrix_is fitted on the same rows it is scored on and will always look better.settings (dict) –
Default settings:
{ "normalize": False, # bool: show row fractions, not counts "title": "Confusion matrix", # str: overall plot title "colorscale": "Blues", # str: any Plotly colorscale name "show_values": True, # bool: print the value in each cell "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 1.0, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Returns:
fig
- Return type:
go.Figure
- Raises:
ValueError – If the model is not fitted and no
matrixis supplied.
Examples
>>> model.confusion_matrix_plot() >>> held_out = model.confusion(X_test, y_test).matrix >>> model.confusion_matrix_plot(held_out, {"normalize": True})
- process_improve.multivariate.plots.effect_summary_plot(model, settings=None, fig=None)[source]#
Generate the per-term effect summary for a fitted
ASCAmodel.One bar per design term, showing the share of the total sum of squares it carries, with the residual alongside for scale. This is the plot to read first: it says which factor the variation actually belongs to, before any score plot is opened.
Permutation p-values are annotated on the bars when
permutation_test()has been run, because a term’s share and its significance answer different questions: a term can hold a large share simply by having many degrees of freedom.- Parameters:
model (ASCA object) – A fitted ASCA model generated by this library.
settings (dict) –
Default settings:
{ "include_residual": True, # bool: draw the residual bar too "title": "Variation by design term", # str: overall plot title "bar_color": None, # str|None: bar colour; None uses the theme "html_image_height": 500, # int: image height in pixels "html_aspect_ratio_w_over_h": 16/9, # float: width as ratio of height "template": "pi_journal", # str: registered Plotly theme name }
fig (go.Figure, optional) – An existing figure to draw onto. A new figure is created if omitted.
- Returns:
fig
- Return type:
go.Figure
- Raises:
ValueError – If the model is not fitted.
Examples
>>> model.effect_summary_plot() >>> model.permutation_test(random_state=0) >>> model.effect_summary_plot() # now annotated with p-values