| """ |
| The :mod:`sklearn.pls` module implements Partial Least Squares (PLS). |
| """ |
|
|
| |
| |
|
|
| import warnings |
| from abc import ABCMeta, abstractmethod |
| from numbers import Integral, Real |
|
|
| import numpy as np |
| from scipy.linalg import svd |
|
|
| from ..base import ( |
| BaseEstimator, |
| ClassNamePrefixFeaturesOutMixin, |
| MultiOutputMixin, |
| RegressorMixin, |
| TransformerMixin, |
| _fit_context, |
| ) |
| from ..exceptions import ConvergenceWarning |
| from ..utils import check_array, check_consistent_length |
| from ..utils._param_validation import Interval, StrOptions |
| from ..utils.extmath import svd_flip |
| from ..utils.fixes import parse_version, sp_version |
| from ..utils.validation import FLOAT_DTYPES, check_is_fitted, validate_data |
|
|
| __all__ = ["PLSCanonical", "PLSRegression", "PLSSVD"] |
|
|
|
|
| if sp_version >= parse_version("1.7"): |
| |
| |
| from scipy.linalg import pinv as pinv2 |
| else: |
| from scipy.linalg import pinv2 |
|
|
|
|
| def _pinv2_old(a): |
| |
| |
| |
| |
| |
| u, s, vh = svd(a, full_matrices=False, check_finite=False) |
|
|
| t = u.dtype.char.lower() |
| factor = {"f": 1e3, "d": 1e6} |
| cond = np.max(s) * factor[t] * np.finfo(t).eps |
| rank = np.sum(s > cond) |
|
|
| u = u[:, :rank] |
| u /= s[:rank] |
| return np.transpose(np.conjugate(np.dot(u, vh[:rank]))) |
|
|
|
|
| def _get_first_singular_vectors_power_method( |
| X, Y, mode="A", max_iter=500, tol=1e-06, norm_y_weights=False |
| ): |
| """Return the first left and right singular vectors of X'Y. |
| |
| Provides an alternative to the svd(X'Y) and uses the power method instead. |
| With norm_y_weights to True and in mode A, this corresponds to the |
| algorithm section 11.3 of the Wegelin's review, except this starts at the |
| "update saliences" part. |
| """ |
|
|
| eps = np.finfo(X.dtype).eps |
| try: |
| y_score = next(col for col in Y.T if np.any(np.abs(col) > eps)) |
| except StopIteration as e: |
| raise StopIteration("y residual is constant") from e |
|
|
| x_weights_old = 100 |
|
|
| if mode == "B": |
| |
| |
| |
| |
| |
| |
| X_pinv, Y_pinv = _pinv2_old(X), _pinv2_old(Y) |
|
|
| for i in range(max_iter): |
| if mode == "B": |
| x_weights = np.dot(X_pinv, y_score) |
| else: |
| x_weights = np.dot(X.T, y_score) / np.dot(y_score, y_score) |
|
|
| x_weights /= np.sqrt(np.dot(x_weights, x_weights)) + eps |
| x_score = np.dot(X, x_weights) |
|
|
| if mode == "B": |
| y_weights = np.dot(Y_pinv, x_score) |
| else: |
| y_weights = np.dot(Y.T, x_score) / np.dot(x_score.T, x_score) |
|
|
| if norm_y_weights: |
| y_weights /= np.sqrt(np.dot(y_weights, y_weights)) + eps |
|
|
| y_score = np.dot(Y, y_weights) / (np.dot(y_weights, y_weights) + eps) |
|
|
| x_weights_diff = x_weights - x_weights_old |
| if np.dot(x_weights_diff, x_weights_diff) < tol or Y.shape[1] == 1: |
| break |
| x_weights_old = x_weights |
|
|
| n_iter = i + 1 |
| if n_iter == max_iter: |
| warnings.warn("Maximum number of iterations reached", ConvergenceWarning) |
|
|
| return x_weights, y_weights, n_iter |
|
|
|
|
| def _get_first_singular_vectors_svd(X, Y): |
| """Return the first left and right singular vectors of X'Y. |
| |
| Here the whole SVD is computed. |
| """ |
| C = np.dot(X.T, Y) |
| U, _, Vt = svd(C, full_matrices=False) |
| return U[:, 0], Vt[0, :] |
|
|
|
|
| def _center_scale_xy(X, Y, scale=True): |
| """Center X, Y and scale if the scale parameter==True |
| |
| Returns |
| ------- |
| X, Y, x_mean, y_mean, x_std, y_std |
| """ |
| |
| x_mean = X.mean(axis=0) |
| X -= x_mean |
| y_mean = Y.mean(axis=0) |
| Y -= y_mean |
| |
| if scale: |
| x_std = X.std(axis=0, ddof=1) |
| x_std[x_std == 0.0] = 1.0 |
| X /= x_std |
| y_std = Y.std(axis=0, ddof=1) |
| y_std[y_std == 0.0] = 1.0 |
| Y /= y_std |
| else: |
| x_std = np.ones(X.shape[1]) |
| y_std = np.ones(Y.shape[1]) |
| return X, Y, x_mean, y_mean, x_std, y_std |
|
|
|
|
| def _svd_flip_1d(u, v): |
| """Same as svd_flip but works on 1d arrays, and is inplace""" |
| |
| |
| biggest_abs_val_idx = np.argmax(np.abs(u)) |
| sign = np.sign(u[biggest_abs_val_idx]) |
| u *= sign |
| v *= sign |
|
|
|
|
| |
| def _deprecate_Y_when_optional(y, Y): |
| if Y is not None: |
| warnings.warn( |
| "`Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead.", |
| FutureWarning, |
| ) |
| if y is not None: |
| raise ValueError( |
| "Cannot use both `y` and `Y`. Use only `y` as `Y` is deprecated." |
| ) |
| return Y |
| return y |
|
|
|
|
| |
| def _deprecate_Y_when_required(y, Y): |
| if y is None and Y is None: |
| raise ValueError("y is required.") |
| return _deprecate_Y_when_optional(y, Y) |
|
|
|
|
| class _PLS( |
| ClassNamePrefixFeaturesOutMixin, |
| TransformerMixin, |
| RegressorMixin, |
| MultiOutputMixin, |
| BaseEstimator, |
| metaclass=ABCMeta, |
| ): |
| """Partial Least Squares (PLS) |
| |
| This class implements the generic PLS algorithm. |
| |
| Main ref: Wegelin, a survey of Partial Least Squares (PLS) methods, |
| with emphasis on the two-block case |
| https://stat.uw.edu/sites/default/files/files/reports/2000/tr371.pdf |
| """ |
|
|
| _parameter_constraints: dict = { |
| "n_components": [Interval(Integral, 1, None, closed="left")], |
| "scale": ["boolean"], |
| "deflation_mode": [StrOptions({"regression", "canonical"})], |
| "mode": [StrOptions({"A", "B"})], |
| "algorithm": [StrOptions({"svd", "nipals"})], |
| "max_iter": [Interval(Integral, 1, None, closed="left")], |
| "tol": [Interval(Real, 0, None, closed="left")], |
| "copy": ["boolean"], |
| } |
|
|
| @abstractmethod |
| def __init__( |
| self, |
| n_components=2, |
| *, |
| scale=True, |
| deflation_mode="regression", |
| mode="A", |
| algorithm="nipals", |
| max_iter=500, |
| tol=1e-06, |
| copy=True, |
| ): |
| self.n_components = n_components |
| self.deflation_mode = deflation_mode |
| self.mode = mode |
| self.scale = scale |
| self.algorithm = algorithm |
| self.max_iter = max_iter |
| self.tol = tol |
| self.copy = copy |
|
|
| @_fit_context(prefer_skip_nested_validation=True) |
| def fit(self, X, y=None, Y=None): |
| """Fit model to data. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Training vectors, where `n_samples` is the number of samples and |
| `n_features` is the number of predictors. |
| |
| y : array-like of shape (n_samples,) or (n_samples, n_targets) |
| Target vectors, where `n_samples` is the number of samples and |
| `n_targets` is the number of response variables. |
| |
| Y : array-like of shape (n_samples,) or (n_samples, n_targets) |
| Target vectors, where `n_samples` is the number of samples and |
| `n_targets` is the number of response variables. |
| |
| .. deprecated:: 1.5 |
| `Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead. |
| |
| Returns |
| ------- |
| self : object |
| Fitted model. |
| """ |
| y = _deprecate_Y_when_required(y, Y) |
|
|
| check_consistent_length(X, y) |
| X = validate_data( |
| self, |
| X, |
| dtype=np.float64, |
| force_writeable=True, |
| copy=self.copy, |
| ensure_min_samples=2, |
| ) |
| y = check_array( |
| y, |
| input_name="y", |
| dtype=np.float64, |
| force_writeable=True, |
| copy=self.copy, |
| ensure_2d=False, |
| ) |
| if y.ndim == 1: |
| self._predict_1d = True |
| y = y.reshape(-1, 1) |
| else: |
| self._predict_1d = False |
|
|
| n = X.shape[0] |
| p = X.shape[1] |
| q = y.shape[1] |
|
|
| n_components = self.n_components |
| |
| |
| |
| rank_upper_bound = ( |
| min(n, p) if self.deflation_mode == "regression" else min(n, p, q) |
| ) |
| if n_components > rank_upper_bound: |
| raise ValueError( |
| f"`n_components` upper bound is {rank_upper_bound}. " |
| f"Got {n_components} instead. Reduce `n_components`." |
| ) |
|
|
| self._norm_y_weights = self.deflation_mode == "canonical" |
| norm_y_weights = self._norm_y_weights |
|
|
| |
| Xk, yk, self._x_mean, self._y_mean, self._x_std, self._y_std = _center_scale_xy( |
| X, y, self.scale |
| ) |
|
|
| self.x_weights_ = np.zeros((p, n_components)) |
| self.y_weights_ = np.zeros((q, n_components)) |
| self._x_scores = np.zeros((n, n_components)) |
| self._y_scores = np.zeros((n, n_components)) |
| self.x_loadings_ = np.zeros((p, n_components)) |
| self.y_loadings_ = np.zeros((q, n_components)) |
| self.n_iter_ = [] |
|
|
| |
| |
| |
| y_eps = np.finfo(yk.dtype).eps |
| for k in range(n_components): |
| |
| |
| if self.algorithm == "nipals": |
| |
| yk_mask = np.all(np.abs(yk) < 10 * y_eps, axis=0) |
| yk[:, yk_mask] = 0.0 |
|
|
| try: |
| ( |
| x_weights, |
| y_weights, |
| n_iter_, |
| ) = _get_first_singular_vectors_power_method( |
| Xk, |
| yk, |
| mode=self.mode, |
| max_iter=self.max_iter, |
| tol=self.tol, |
| norm_y_weights=norm_y_weights, |
| ) |
| except StopIteration as e: |
| if str(e) != "y residual is constant": |
| raise |
| warnings.warn(f"y residual is constant at iteration {k}") |
| break |
|
|
| self.n_iter_.append(n_iter_) |
|
|
| elif self.algorithm == "svd": |
| x_weights, y_weights = _get_first_singular_vectors_svd(Xk, yk) |
|
|
| |
| _svd_flip_1d(x_weights, y_weights) |
|
|
| |
| x_scores = np.dot(Xk, x_weights) |
| if norm_y_weights: |
| y_ss = 1 |
| else: |
| y_ss = np.dot(y_weights, y_weights) |
| y_scores = np.dot(yk, y_weights) / y_ss |
|
|
| |
| x_loadings = np.dot(x_scores, Xk) / np.dot(x_scores, x_scores) |
| Xk -= np.outer(x_scores, x_loadings) |
|
|
| if self.deflation_mode == "canonical": |
| |
| y_loadings = np.dot(y_scores, yk) / np.dot(y_scores, y_scores) |
| yk -= np.outer(y_scores, y_loadings) |
| if self.deflation_mode == "regression": |
| |
| y_loadings = np.dot(x_scores, yk) / np.dot(x_scores, x_scores) |
| yk -= np.outer(x_scores, y_loadings) |
|
|
| self.x_weights_[:, k] = x_weights |
| self.y_weights_[:, k] = y_weights |
| self._x_scores[:, k] = x_scores |
| self._y_scores[:, k] = y_scores |
| self.x_loadings_[:, k] = x_loadings |
| self.y_loadings_[:, k] = y_loadings |
|
|
| |
| |
| |
| |
| |
|
|
| |
| self.x_rotations_ = np.dot( |
| self.x_weights_, |
| pinv2(np.dot(self.x_loadings_.T, self.x_weights_), check_finite=False), |
| ) |
| self.y_rotations_ = np.dot( |
| self.y_weights_, |
| pinv2(np.dot(self.y_loadings_.T, self.y_weights_), check_finite=False), |
| ) |
| self.coef_ = np.dot(self.x_rotations_, self.y_loadings_.T) |
| self.coef_ = (self.coef_ * self._y_std).T / self._x_std |
| self.intercept_ = self._y_mean |
| self._n_features_out = self.x_rotations_.shape[1] |
| return self |
|
|
| def transform(self, X, y=None, Y=None, copy=True): |
| """Apply the dimension reduction. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Samples to transform. |
| |
| y : array-like of shape (n_samples, n_targets), default=None |
| Target vectors. |
| |
| Y : array-like of shape (n_samples, n_targets), default=None |
| Target vectors. |
| |
| .. deprecated:: 1.5 |
| `Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead. |
| |
| copy : bool, default=True |
| Whether to copy `X` and `Y`, or perform in-place normalization. |
| |
| Returns |
| ------- |
| x_scores, y_scores : array-like or tuple of array-like |
| Return `x_scores` if `Y` is not given, `(x_scores, y_scores)` otherwise. |
| """ |
| y = _deprecate_Y_when_optional(y, Y) |
|
|
| check_is_fitted(self) |
| X = validate_data(self, X, copy=copy, dtype=FLOAT_DTYPES, reset=False) |
| |
| X -= self._x_mean |
| X /= self._x_std |
| |
| x_scores = np.dot(X, self.x_rotations_) |
| if y is not None: |
| y = check_array( |
| y, input_name="y", ensure_2d=False, copy=copy, dtype=FLOAT_DTYPES |
| ) |
| if y.ndim == 1: |
| y = y.reshape(-1, 1) |
| y -= self._y_mean |
| y /= self._y_std |
| y_scores = np.dot(y, self.y_rotations_) |
| return x_scores, y_scores |
|
|
| return x_scores |
|
|
| def inverse_transform(self, X, y=None, Y=None): |
| """Transform data back to its original space. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_components) |
| New data, where `n_samples` is the number of samples |
| and `n_components` is the number of pls components. |
| |
| y : array-like of shape (n_samples,) or (n_samples, n_components) |
| New target, where `n_samples` is the number of samples |
| and `n_components` is the number of pls components. |
| |
| Y : array-like of shape (n_samples, n_components) |
| New target, where `n_samples` is the number of samples |
| and `n_components` is the number of pls components. |
| |
| .. deprecated:: 1.5 |
| `Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead. |
| |
| Returns |
| ------- |
| X_reconstructed : ndarray of shape (n_samples, n_features) |
| Return the reconstructed `X` data. |
| |
| y_reconstructed : ndarray of shape (n_samples, n_targets) |
| Return the reconstructed `X` target. Only returned when `y` is given. |
| |
| Notes |
| ----- |
| This transformation will only be exact if `n_components=n_features`. |
| """ |
| y = _deprecate_Y_when_optional(y, Y) |
|
|
| check_is_fitted(self) |
| X = check_array(X, input_name="X", dtype=FLOAT_DTYPES) |
| |
| X_reconstructed = np.matmul(X, self.x_loadings_.T) |
| |
| X_reconstructed *= self._x_std |
| X_reconstructed += self._x_mean |
|
|
| if y is not None: |
| y = check_array(y, input_name="y", dtype=FLOAT_DTYPES) |
| |
| y_reconstructed = np.matmul(y, self.y_loadings_.T) |
| |
| y_reconstructed *= self._y_std |
| y_reconstructed += self._y_mean |
| return X_reconstructed, y_reconstructed |
|
|
| return X_reconstructed |
|
|
| def predict(self, X, copy=True): |
| """Predict targets of given samples. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Samples. |
| |
| copy : bool, default=True |
| Whether to copy `X` and `Y`, or perform in-place normalization. |
| |
| Returns |
| ------- |
| y_pred : ndarray of shape (n_samples,) or (n_samples, n_targets) |
| Returns predicted values. |
| |
| Notes |
| ----- |
| This call requires the estimation of a matrix of shape |
| `(n_features, n_targets)`, which may be an issue in high dimensional |
| space. |
| """ |
| check_is_fitted(self) |
| X = validate_data(self, X, copy=copy, dtype=FLOAT_DTYPES, reset=False) |
| |
| X -= self._x_mean |
| Ypred = X @ self.coef_.T + self.intercept_ |
| return Ypred.ravel() if self._predict_1d else Ypred |
|
|
| def fit_transform(self, X, y=None): |
| """Learn and apply the dimension reduction on the train data. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Training vectors, where `n_samples` is the number of samples and |
| `n_features` is the number of predictors. |
| |
| y : array-like of shape (n_samples, n_targets), default=None |
| Target vectors, where `n_samples` is the number of samples and |
| `n_targets` is the number of response variables. |
| |
| Returns |
| ------- |
| self : ndarray of shape (n_samples, n_components) |
| Return `x_scores` if `Y` is not given, `(x_scores, y_scores)` otherwise. |
| """ |
| return self.fit(X, y).transform(X, y) |
|
|
| def __sklearn_tags__(self): |
| tags = super().__sklearn_tags__() |
| tags.regressor_tags.poor_score = True |
| tags.target_tags.required = False |
| return tags |
|
|
|
|
| class PLSRegression(_PLS): |
| """PLS regression. |
| |
| PLSRegression is also known as PLS2 or PLS1, depending on the number of |
| targets. |
| |
| For a comparison between other cross decomposition algorithms, see |
| :ref:`sphx_glr_auto_examples_cross_decomposition_plot_compare_cross_decomposition.py`. |
| |
| Read more in the :ref:`User Guide <cross_decomposition>`. |
| |
| .. versionadded:: 0.8 |
| |
| Parameters |
| ---------- |
| n_components : int, default=2 |
| Number of components to keep. Should be in `[1, n_features]`. |
| |
| scale : bool, default=True |
| Whether to scale `X` and `Y`. |
| |
| max_iter : int, default=500 |
| The maximum number of iterations of the power method when |
| `algorithm='nipals'`. Ignored otherwise. |
| |
| tol : float, default=1e-06 |
| The tolerance used as convergence criteria in the power method: the |
| algorithm stops whenever the squared norm of `u_i - u_{i-1}` is less |
| than `tol`, where `u` corresponds to the left singular vector. |
| |
| copy : bool, default=True |
| Whether to copy `X` and `Y` in :term:`fit` before applying centering, |
| and potentially scaling. If `False`, these operations will be done |
| inplace, modifying both arrays. |
| |
| Attributes |
| ---------- |
| x_weights_ : ndarray of shape (n_features, n_components) |
| The left singular vectors of the cross-covariance matrices of each |
| iteration. |
| |
| y_weights_ : ndarray of shape (n_targets, n_components) |
| The right singular vectors of the cross-covariance matrices of each |
| iteration. |
| |
| x_loadings_ : ndarray of shape (n_features, n_components) |
| The loadings of `X`. |
| |
| y_loadings_ : ndarray of shape (n_targets, n_components) |
| The loadings of `Y`. |
| |
| x_scores_ : ndarray of shape (n_samples, n_components) |
| The transformed training samples. |
| |
| y_scores_ : ndarray of shape (n_samples, n_components) |
| The transformed training targets. |
| |
| x_rotations_ : ndarray of shape (n_features, n_components) |
| The projection matrix used to transform `X`. |
| |
| y_rotations_ : ndarray of shape (n_targets, n_components) |
| The projection matrix used to transform `Y`. |
| |
| coef_ : ndarray of shape (n_target, n_features) |
| The coefficients of the linear model such that `Y` is approximated as |
| `Y = X @ coef_.T + intercept_`. |
| |
| intercept_ : ndarray of shape (n_targets,) |
| The intercepts of the linear model such that `Y` is approximated as |
| `Y = X @ coef_.T + intercept_`. |
| |
| .. versionadded:: 1.1 |
| |
| n_iter_ : list of shape (n_components,) |
| Number of iterations of the power method, for each |
| component. |
| |
| n_features_in_ : int |
| Number of features seen during :term:`fit`. |
| |
| feature_names_in_ : ndarray of shape (`n_features_in_`,) |
| Names of features seen during :term:`fit`. Defined only when `X` |
| has feature names that are all strings. |
| |
| .. versionadded:: 1.0 |
| |
| See Also |
| -------- |
| PLSCanonical : Partial Least Squares transformer and regressor. |
| |
| Examples |
| -------- |
| >>> from sklearn.cross_decomposition import PLSRegression |
| >>> X = [[0., 0., 1.], [1.,0.,0.], [2.,2.,2.], [2.,5.,4.]] |
| >>> y = [[0.1, -0.2], [0.9, 1.1], [6.2, 5.9], [11.9, 12.3]] |
| >>> pls2 = PLSRegression(n_components=2) |
| >>> pls2.fit(X, y) |
| PLSRegression() |
| >>> Y_pred = pls2.predict(X) |
| |
| For a comparison between PLS Regression and :class:`~sklearn.decomposition.PCA`, see |
| :ref:`sphx_glr_auto_examples_cross_decomposition_plot_pcr_vs_pls.py`. |
| """ |
|
|
| _parameter_constraints: dict = {**_PLS._parameter_constraints} |
| for param in ("deflation_mode", "mode", "algorithm"): |
| _parameter_constraints.pop(param) |
|
|
| |
| |
| |
| |
| |
|
|
| def __init__( |
| self, n_components=2, *, scale=True, max_iter=500, tol=1e-06, copy=True |
| ): |
| super().__init__( |
| n_components=n_components, |
| scale=scale, |
| deflation_mode="regression", |
| mode="A", |
| algorithm="nipals", |
| max_iter=max_iter, |
| tol=tol, |
| copy=copy, |
| ) |
|
|
| def fit(self, X, y=None, Y=None): |
| """Fit model to data. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Training vectors, where `n_samples` is the number of samples and |
| `n_features` is the number of predictors. |
| |
| y : array-like of shape (n_samples,) or (n_samples, n_targets) |
| Target vectors, where `n_samples` is the number of samples and |
| `n_targets` is the number of response variables. |
| |
| Y : array-like of shape (n_samples,) or (n_samples, n_targets) |
| Target vectors, where `n_samples` is the number of samples and |
| `n_targets` is the number of response variables. |
| |
| .. deprecated:: 1.5 |
| `Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead. |
| |
| Returns |
| ------- |
| self : object |
| Fitted model. |
| """ |
| y = _deprecate_Y_when_required(y, Y) |
|
|
| super().fit(X, y) |
| |
| self.x_scores_ = self._x_scores |
| self.y_scores_ = self._y_scores |
| return self |
|
|
|
|
| class PLSCanonical(_PLS): |
| """Partial Least Squares transformer and regressor. |
| |
| For a comparison between other cross decomposition algorithms, see |
| :ref:`sphx_glr_auto_examples_cross_decomposition_plot_compare_cross_decomposition.py`. |
| |
| Read more in the :ref:`User Guide <cross_decomposition>`. |
| |
| .. versionadded:: 0.8 |
| |
| Parameters |
| ---------- |
| n_components : int, default=2 |
| Number of components to keep. Should be in `[1, min(n_samples, |
| n_features, n_targets)]`. |
| |
| scale : bool, default=True |
| Whether to scale `X` and `Y`. |
| |
| algorithm : {'nipals', 'svd'}, default='nipals' |
| The algorithm used to estimate the first singular vectors of the |
| cross-covariance matrix. 'nipals' uses the power method while 'svd' |
| will compute the whole SVD. |
| |
| max_iter : int, default=500 |
| The maximum number of iterations of the power method when |
| `algorithm='nipals'`. Ignored otherwise. |
| |
| tol : float, default=1e-06 |
| The tolerance used as convergence criteria in the power method: the |
| algorithm stops whenever the squared norm of `u_i - u_{i-1}` is less |
| than `tol`, where `u` corresponds to the left singular vector. |
| |
| copy : bool, default=True |
| Whether to copy `X` and `Y` in fit before applying centering, and |
| potentially scaling. If False, these operations will be done inplace, |
| modifying both arrays. |
| |
| Attributes |
| ---------- |
| x_weights_ : ndarray of shape (n_features, n_components) |
| The left singular vectors of the cross-covariance matrices of each |
| iteration. |
| |
| y_weights_ : ndarray of shape (n_targets, n_components) |
| The right singular vectors of the cross-covariance matrices of each |
| iteration. |
| |
| x_loadings_ : ndarray of shape (n_features, n_components) |
| The loadings of `X`. |
| |
| y_loadings_ : ndarray of shape (n_targets, n_components) |
| The loadings of `Y`. |
| |
| x_rotations_ : ndarray of shape (n_features, n_components) |
| The projection matrix used to transform `X`. |
| |
| y_rotations_ : ndarray of shape (n_targets, n_components) |
| The projection matrix used to transform `Y`. |
| |
| coef_ : ndarray of shape (n_targets, n_features) |
| The coefficients of the linear model such that `Y` is approximated as |
| `Y = X @ coef_.T + intercept_`. |
| |
| intercept_ : ndarray of shape (n_targets,) |
| The intercepts of the linear model such that `Y` is approximated as |
| `Y = X @ coef_.T + intercept_`. |
| |
| .. versionadded:: 1.1 |
| |
| n_iter_ : list of shape (n_components,) |
| Number of iterations of the power method, for each |
| component. Empty if `algorithm='svd'`. |
| |
| n_features_in_ : int |
| Number of features seen during :term:`fit`. |
| |
| feature_names_in_ : ndarray of shape (`n_features_in_`,) |
| Names of features seen during :term:`fit`. Defined only when `X` |
| has feature names that are all strings. |
| |
| .. versionadded:: 1.0 |
| |
| See Also |
| -------- |
| CCA : Canonical Correlation Analysis. |
| PLSSVD : Partial Least Square SVD. |
| |
| Examples |
| -------- |
| >>> from sklearn.cross_decomposition import PLSCanonical |
| >>> X = [[0., 0., 1.], [1.,0.,0.], [2.,2.,2.], [2.,5.,4.]] |
| >>> y = [[0.1, -0.2], [0.9, 1.1], [6.2, 5.9], [11.9, 12.3]] |
| >>> plsca = PLSCanonical(n_components=2) |
| >>> plsca.fit(X, y) |
| PLSCanonical() |
| >>> X_c, y_c = plsca.transform(X, y) |
| """ |
|
|
| _parameter_constraints: dict = {**_PLS._parameter_constraints} |
| for param in ("deflation_mode", "mode"): |
| _parameter_constraints.pop(param) |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| def __init__( |
| self, |
| n_components=2, |
| *, |
| scale=True, |
| algorithm="nipals", |
| max_iter=500, |
| tol=1e-06, |
| copy=True, |
| ): |
| super().__init__( |
| n_components=n_components, |
| scale=scale, |
| deflation_mode="canonical", |
| mode="A", |
| algorithm=algorithm, |
| max_iter=max_iter, |
| tol=tol, |
| copy=copy, |
| ) |
|
|
|
|
| class CCA(_PLS): |
| """Canonical Correlation Analysis, also known as "Mode B" PLS. |
| |
| For a comparison between other cross decomposition algorithms, see |
| :ref:`sphx_glr_auto_examples_cross_decomposition_plot_compare_cross_decomposition.py`. |
| |
| Read more in the :ref:`User Guide <cross_decomposition>`. |
| |
| Parameters |
| ---------- |
| n_components : int, default=2 |
| Number of components to keep. Should be in `[1, min(n_samples, |
| n_features, n_targets)]`. |
| |
| scale : bool, default=True |
| Whether to scale `X` and `Y`. |
| |
| max_iter : int, default=500 |
| The maximum number of iterations of the power method. |
| |
| tol : float, default=1e-06 |
| The tolerance used as convergence criteria in the power method: the |
| algorithm stops whenever the squared norm of `u_i - u_{i-1}` is less |
| than `tol`, where `u` corresponds to the left singular vector. |
| |
| copy : bool, default=True |
| Whether to copy `X` and `Y` in fit before applying centering, and |
| potentially scaling. If False, these operations will be done inplace, |
| modifying both arrays. |
| |
| Attributes |
| ---------- |
| x_weights_ : ndarray of shape (n_features, n_components) |
| The left singular vectors of the cross-covariance matrices of each |
| iteration. |
| |
| y_weights_ : ndarray of shape (n_targets, n_components) |
| The right singular vectors of the cross-covariance matrices of each |
| iteration. |
| |
| x_loadings_ : ndarray of shape (n_features, n_components) |
| The loadings of `X`. |
| |
| y_loadings_ : ndarray of shape (n_targets, n_components) |
| The loadings of `Y`. |
| |
| x_rotations_ : ndarray of shape (n_features, n_components) |
| The projection matrix used to transform `X`. |
| |
| y_rotations_ : ndarray of shape (n_targets, n_components) |
| The projection matrix used to transform `Y`. |
| |
| coef_ : ndarray of shape (n_targets, n_features) |
| The coefficients of the linear model such that `Y` is approximated as |
| `Y = X @ coef_.T + intercept_`. |
| |
| intercept_ : ndarray of shape (n_targets,) |
| The intercepts of the linear model such that `Y` is approximated as |
| `Y = X @ coef_.T + intercept_`. |
| |
| .. versionadded:: 1.1 |
| |
| n_iter_ : list of shape (n_components,) |
| Number of iterations of the power method, for each |
| component. |
| |
| n_features_in_ : int |
| Number of features seen during :term:`fit`. |
| |
| feature_names_in_ : ndarray of shape (`n_features_in_`,) |
| Names of features seen during :term:`fit`. Defined only when `X` |
| has feature names that are all strings. |
| |
| .. versionadded:: 1.0 |
| |
| See Also |
| -------- |
| PLSCanonical : Partial Least Squares transformer and regressor. |
| PLSSVD : Partial Least Square SVD. |
| |
| Examples |
| -------- |
| >>> from sklearn.cross_decomposition import CCA |
| >>> X = [[0., 0., 1.], [1.,0.,0.], [2.,2.,2.], [3.,5.,4.]] |
| >>> y = [[0.1, -0.2], [0.9, 1.1], [6.2, 5.9], [11.9, 12.3]] |
| >>> cca = CCA(n_components=1) |
| >>> cca.fit(X, y) |
| CCA(n_components=1) |
| >>> X_c, Y_c = cca.transform(X, y) |
| """ |
|
|
| _parameter_constraints: dict = {**_PLS._parameter_constraints} |
| for param in ("deflation_mode", "mode", "algorithm"): |
| _parameter_constraints.pop(param) |
|
|
| def __init__( |
| self, n_components=2, *, scale=True, max_iter=500, tol=1e-06, copy=True |
| ): |
| super().__init__( |
| n_components=n_components, |
| scale=scale, |
| deflation_mode="canonical", |
| mode="B", |
| algorithm="nipals", |
| max_iter=max_iter, |
| tol=tol, |
| copy=copy, |
| ) |
|
|
|
|
| class PLSSVD(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): |
| """Partial Least Square SVD. |
| |
| This transformer simply performs a SVD on the cross-covariance matrix |
| `X'Y`. It is able to project both the training data `X` and the targets |
| `Y`. The training data `X` is projected on the left singular vectors, while |
| the targets are projected on the right singular vectors. |
| |
| Read more in the :ref:`User Guide <cross_decomposition>`. |
| |
| .. versionadded:: 0.8 |
| |
| Parameters |
| ---------- |
| n_components : int, default=2 |
| The number of components to keep. Should be in `[1, |
| min(n_samples, n_features, n_targets)]`. |
| |
| scale : bool, default=True |
| Whether to scale `X` and `Y`. |
| |
| copy : bool, default=True |
| Whether to copy `X` and `Y` in fit before applying centering, and |
| potentially scaling. If `False`, these operations will be done inplace, |
| modifying both arrays. |
| |
| Attributes |
| ---------- |
| x_weights_ : ndarray of shape (n_features, n_components) |
| The left singular vectors of the SVD of the cross-covariance matrix. |
| Used to project `X` in :meth:`transform`. |
| |
| y_weights_ : ndarray of (n_targets, n_components) |
| The right singular vectors of the SVD of the cross-covariance matrix. |
| Used to project `X` in :meth:`transform`. |
| |
| n_features_in_ : int |
| Number of features seen during :term:`fit`. |
| |
| feature_names_in_ : ndarray of shape (`n_features_in_`,) |
| Names of features seen during :term:`fit`. Defined only when `X` |
| has feature names that are all strings. |
| |
| .. versionadded:: 1.0 |
| |
| See Also |
| -------- |
| PLSCanonical : Partial Least Squares transformer and regressor. |
| CCA : Canonical Correlation Analysis. |
| |
| Examples |
| -------- |
| >>> import numpy as np |
| >>> from sklearn.cross_decomposition import PLSSVD |
| >>> X = np.array([[0., 0., 1.], |
| ... [1., 0., 0.], |
| ... [2., 2., 2.], |
| ... [2., 5., 4.]]) |
| >>> y = np.array([[0.1, -0.2], |
| ... [0.9, 1.1], |
| ... [6.2, 5.9], |
| ... [11.9, 12.3]]) |
| >>> pls = PLSSVD(n_components=2).fit(X, y) |
| >>> X_c, y_c = pls.transform(X, y) |
| >>> X_c.shape, y_c.shape |
| ((4, 2), (4, 2)) |
| """ |
|
|
| _parameter_constraints: dict = { |
| "n_components": [Interval(Integral, 1, None, closed="left")], |
| "scale": ["boolean"], |
| "copy": ["boolean"], |
| } |
|
|
| def __init__(self, n_components=2, *, scale=True, copy=True): |
| self.n_components = n_components |
| self.scale = scale |
| self.copy = copy |
|
|
| @_fit_context(prefer_skip_nested_validation=True) |
| def fit(self, X, y=None, Y=None): |
| """Fit model to data. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Training samples. |
| |
| y : array-like of shape (n_samples,) or (n_samples, n_targets) |
| Targets. |
| |
| Y : array-like of shape (n_samples,) or (n_samples, n_targets) |
| Targets. |
| |
| .. deprecated:: 1.5 |
| `Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead. |
| |
| Returns |
| ------- |
| self : object |
| Fitted estimator. |
| """ |
| y = _deprecate_Y_when_required(y, Y) |
| check_consistent_length(X, y) |
| X = validate_data( |
| self, |
| X, |
| dtype=np.float64, |
| force_writeable=True, |
| copy=self.copy, |
| ensure_min_samples=2, |
| ) |
| y = check_array( |
| y, |
| input_name="y", |
| dtype=np.float64, |
| force_writeable=True, |
| copy=self.copy, |
| ensure_2d=False, |
| ) |
| if y.ndim == 1: |
| y = y.reshape(-1, 1) |
|
|
| |
| |
| |
| n_components = self.n_components |
| rank_upper_bound = min(X.shape[0], X.shape[1], y.shape[1]) |
| if n_components > rank_upper_bound: |
| raise ValueError( |
| f"`n_components` upper bound is {rank_upper_bound}. " |
| f"Got {n_components} instead. Reduce `n_components`." |
| ) |
|
|
| X, y, self._x_mean, self._y_mean, self._x_std, self._y_std = _center_scale_xy( |
| X, y, self.scale |
| ) |
|
|
| |
| C = np.dot(X.T, y) |
| U, s, Vt = svd(C, full_matrices=False) |
| U = U[:, :n_components] |
| Vt = Vt[:n_components] |
| U, Vt = svd_flip(U, Vt) |
| V = Vt.T |
|
|
| self.x_weights_ = U |
| self.y_weights_ = V |
| self._n_features_out = self.x_weights_.shape[1] |
| return self |
|
|
| def transform(self, X, y=None, Y=None): |
| """ |
| Apply the dimensionality reduction. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Samples to be transformed. |
| |
| y : array-like of shape (n_samples,) or (n_samples, n_targets), \ |
| default=None |
| Targets. |
| |
| Y : array-like of shape (n_samples,) or (n_samples, n_targets), \ |
| default=None |
| Targets. |
| |
| .. deprecated:: 1.5 |
| `Y` is deprecated in 1.5 and will be removed in 1.7. Use `y` instead. |
| |
| Returns |
| ------- |
| x_scores : array-like or tuple of array-like |
| The transformed data `X_transformed` if `Y is not None`, |
| `(X_transformed, Y_transformed)` otherwise. |
| """ |
| y = _deprecate_Y_when_optional(y, Y) |
| check_is_fitted(self) |
| X = validate_data(self, X, dtype=np.float64, reset=False) |
| Xr = (X - self._x_mean) / self._x_std |
| x_scores = np.dot(Xr, self.x_weights_) |
| if y is not None: |
| y = check_array(y, input_name="y", ensure_2d=False, dtype=np.float64) |
| if y.ndim == 1: |
| y = y.reshape(-1, 1) |
| yr = (y - self._y_mean) / self._y_std |
| y_scores = np.dot(yr, self.y_weights_) |
| return x_scores, y_scores |
| return x_scores |
|
|
| def fit_transform(self, X, y=None): |
| """Learn and apply the dimensionality reduction. |
| |
| Parameters |
| ---------- |
| X : array-like of shape (n_samples, n_features) |
| Training samples. |
| |
| y : array-like of shape (n_samples,) or (n_samples, n_targets), \ |
| default=None |
| Targets. |
| |
| Returns |
| ------- |
| out : array-like or tuple of array-like |
| The transformed data `X_transformed` if `Y is not None`, |
| `(X_transformed, Y_transformed)` otherwise. |
| """ |
| return self.fit(X, y).transform(X, y) |
|
|