Source code for chemotools.feature_selection._vip_selector

"""
The :mod:`chemotools.feature_selection._vip_selector` module implements the Variables Importance in
Projection (VIP) feature selector for PLS regression models.
"""

# Author: Pau Cababeros
# License: MIT

import numpy as np
from sklearn.utils.validation import validate_data

from ._base import _PLSFeatureSelectorBase


[docs] class VIPSelector(_PLSFeatureSelectorBase): """ This selector is used to select features that contribute significantly to the latent variables in a PLS regression model using the Variables Importance in Projection (VIP) method. Parameters ---------- model : Union[_PLS, Pipeline] The PLS regression model or a pipeline with a PLS regression model as last step. threshold : float, default=1.0 The threshold for feature selection. Features with importance above this threshold will be selected. Attributes ---------- estimator_ : ModelTypes The fitted model of type _BasePCA or _PLS feature_scores_ : np.ndarray The calculated feature scores based on the selected method. support_mask_ : np.ndarray The boolean mask indicating which features are selected. References ---------- [1] Kim H. Esbensen, "Multivariate Data Analysis - In Practice", 5th Edition, 2002. Examples -------- >>> from chemotools.datasets import load_fermentation_train >>> from chemotools.feature_selection import VIPSelector >>> from sklearn.cross_decomposition import PLSRegression >>> # Load sample data >>> X, y = load_fermentation_train() >>> # Instantiate the PLS regression model >>> pls_model = PLSRegression(n_components=2).fit(X, y) >>> # Instantiate the VIP selector with the PLS model >>> selector = VIPSelector(model=pls_model, threshold=1.0) >>> selector.fit(X) VIPSelector(model=PLSRegression(n_components=2), threshold=1.0) >>> # Get the selected features >>> X_selected = selector.transform(X) >>> X_selected.shape (21, 527) """ def __init__( self, model, threshold: float = 1.0, ): self.model = model self.threshold = threshold super().__init__(self.model)
[docs] def fit(self, X: np.ndarray, y=None) -> "VIPSelector": """ Fit the transformer to calculate the feature scores and the support mask. Parameters ---------- X : array-like of shape (n_samples, n_features) The input data to fit the transformer to. y : None Ignored to align with API. Returns ------- self : VIPSelector The fitted transformer. """ # Check that X is a 2D array and has only finite values X = validate_data( self, X, y="no_validation", ensure_2d=True, reset=True, dtype=np.float64 ) # Calculate the VIP scores self.feature_scores_ = self._calculate_features(X) # Calculate the support mask self.support_mask_ = self._get_support_mask() return self
def _get_support_mask(self) -> np.ndarray: """ Get the support mask based on the feature scores and threshold. Features with scores above the threshold are selected. Parameters ---------- self : VIPSelector The fitted transformer. Returns ------- support_mask_ : np.ndarray The boolean mask indicating which features are selected. """ return self.feature_scores_ > self.threshold def _calculate_features(self, X: np.ndarray) -> np.ndarray: """ Calculate the VIP scores based on the fitted model. Parameters ---------- self : VIPSelector The fitted transformer. Returns ------- feature_scores_ : np.ndarray The calculated feature scores based on the selected method. """ # Calculate sum of squares of y_loadings and x_scores sum_of_squares_y_loadings = ( np.linalg.norm(self.estimator_.y_loadings_, ord=2, axis=0) ** 2 ) sum_of_squares_x_scores = ( np.linalg.norm(self.estimator_.x_scores_, ord=2, axis=0) ** 2 ) # Calculate the sum of squares sum_of_squares = sum_of_squares_y_loadings * sum_of_squares_x_scores # Calculate the numerator numerator = self.estimator_.n_features_in_ * np.sum( sum_of_squares * self.estimator_.x_weights_**2, axis=1, ) # Calculate the denominator denominator = np.sum(sum_of_squares, axis=0) # Calculate the VIP scores return np.sqrt(numerator / denominator)