Source code for spectrochempy.analysis.crossdecomposition.pls

# ======================================================================================
# Copyright (©) 2015-2025 LCS - Laboratoire Catalyse et Spectrochimie, Caen, France.
# CeCILL-B FREE SOFTWARE LICENSE AGREEMENT
# See full LICENSE agreement in the root directory.
# ======================================================================================
"""Implementation of Partial Least Square regression (using scikit-learn library)."""

import traitlets as tr
from sklearn import cross_decomposition

from spectrochempy.analysis._base._analysisbase import CrossDecompositionAnalysis
from spectrochempy.analysis._base._analysisbase import _wrap_ndarray_output_to_nddataset
from spectrochempy.core.dataset.nddataset import NDDataset
from spectrochempy.utils.decorators import signature_has_configurable_traits
from spectrochempy.utils.docreps import _docstring

__all__ = ["PLSRegression"]
__configurables__ = ["PLSRegression"]


# ======================================================================================
# class PLSRegression
# ======================================================================================
[docs] @signature_has_configurable_traits class PLSRegression(CrossDecompositionAnalysis): _docstring.delete_params("DecompositionAnalysis.see_also", "PLSRegression") __doc__ = _docstring.dedent( """ Partial Least Squares regression (PLSRegression). The Partial Least Squares regression wraps the `sklearn.cross_decomposition.PLSRegression` model, with few additional methods. Parameters ---------- %(AnalysisConfigurable.parameters)s """, ) # ---------------------------------------------------------------------------------- # Runtime Parameters, # only those specific to PLSRegression, the other being defined in AnalysisConfigurable. # ---------------------------------------------------------------------------------- # define here only the variable that you use in fit or transform functions _plsregression = tr.Instance( cross_decomposition.PLSRegression, help="The instance of sklearn.cross_decomposition.PLSRegression used in this model", ) # ---------------------------------------------------------------------------------- # Configuration parameters # ---------------------------------------------------------------------------------- n_components = tr.Int( default_value=2, help="Number of components to keep. Should be in the range [1, min(n_samples, " "n_features, n_targets)].", ).tag(config=True) scale = tr.Bool(default_value=True, help="Whether to scale X and Y.").tag( config=True, ) max_iter = tr.Int( default_value=500, help="The maximum number of iterations of the power method when " "algorithm='nipals'. Ignored otherwise.", ).tag(config=True) tol = tr.Float( default_value=1.0e-6, help="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.", ).tag(config=True) # ---------------------------------------------------------------------------------- # Initialization # ---------------------------------------------------------------------------------- def __init__( self, *, log_level="WARNING", warm_start=False, **kwargs, ): # call the super class for initialisation of the configuration parameters # to do before anything else! super().__init__( log_level=log_level, warm_start=warm_start, **kwargs, ) # initialize sklearn PLSRegression self._plsregression = cross_decomposition.PLSRegression( n_components=self.n_components, scale=self.scale, max_iter=self.max_iter, tol=self.tol, ) # ---------------------------------------------------------------------------------- # Private methods (overloading abstract classes) # ---------------------------------------------------------------------------------- def _fit(self, X, Y): # this method is called by the abstract class fit. # Input X and Y are np.ndarray self._plsregression.fit(X, Y) self._x_weights = self._plsregression.x_weights_.T self._y_weights = self._plsregression.y_weights_.T self._x_loadings = self._plsregression.x_loadings_.T self._y_loadings = self._plsregression.y_loadings_.T self._x_scores = self._plsregression.x_scores_ self._y_scores = self._plsregression.y_scores_ self._x_rotations = self._plsregression.x_rotations_.T self._y_rotations = self._plsregression.y_rotations_.T self._coef = self._plsregression.coef_.T self._intercept = self._plsregression.intercept_ self._n_iter = self._plsregression.n_iter_ self._n_feature_in = self._plsregression.n_features_in_ # todo: check self.feature_names_in = self._plsregression.feature_names_in_ # for compatibility with superclass methods self._n_components = self.n_components def _fit_transform(self, X, Y=None): # Learn and apply the dimension reduction on the train data. # return self._plsregression.fit_transform(X, Y) def _inverse_transform(self, X_transform, Y_transform=None): # Transform data back to its original space. return self._plsregression.inverse_transform(X_transform, Y=Y_transform) def _transform(self, X, Y=None): # Apply the dimension reduction. return self._plsregression.transform(X, Y) def _predict(self, X): # Predict targets of given samples. return self._plsregression.predict(X) def _score(self, X, Y, sample_weight=None): # this method is called by the abstract class score. # Input X, Y, sample_weights are np.ndarray return self._plsregression.score(X, Y, sample_weight=sample_weight) # ---------------------------------------------------------------------------------- # Public methods and properties specific to PLSRegression # ---------------------------------------------------------------------------------- _docstring.keep_params("analysis_fit.parameters", "X")
[docs] @_docstring.dedent def fit(self, X, Y): """ Fit the PLSRegression model on X and Y. Parameters ---------- %(analysis_fit.parameters.X)s Y : :term:`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. Returns ------- %(analysis_fit.returns)s See Also -------- %(analysis_fit.see_also)s """ return super().fit(X, Y)
@property @_wrap_ndarray_output_to_nddataset( units=None, title=None, typey="components", ) def x_loadings(self): return self._x_loadings @property @_wrap_ndarray_output_to_nddataset( meta_from="_Y", units=None, title=None, typey="components", ) def y_loadings(self): return self._y_loadings @property @_wrap_ndarray_output_to_nddataset( units=None, title=None, typex="components", ) def x_scores(self): return self._x_scores @property @_wrap_ndarray_output_to_nddataset( meta_from="_Y", units=None, title=None, typex="components", ) def y_scores(self): return self._y_scores @property @_wrap_ndarray_output_to_nddataset( units=None, title=None, typey="components", ) def x_rotations(self): return self._x_rotations @property @_wrap_ndarray_output_to_nddataset( meta_from="_Y", typey="components", ) def y_rotations(self): return self._y_rotations @property @_wrap_ndarray_output_to_nddataset( typey="components", ) def x_weights(self): return self._x_weights @property @_wrap_ndarray_output_to_nddataset( meta_from="_Y", typey="components", ) def y_weights(self): return self._y_weights @property def coef(self): coef = NDDataset(self._coef) coef.set_coordset( y=self._Y.x, x=self._X.x, ) return coef @property @_wrap_ndarray_output_to_nddataset( meta_from="_Y", typesingle="targets", ) def intercept(self): return self._intercept @property def n_iter(self): return self._n_iter_
if __name__ == "__main__": pass