Feature Selection Lasso#

lasso #

CLASS DESCRIPTION
Lasso

Lasso regression with cross-validation for feature selection.

FUNCTION DESCRIPTION
lasso

Lasso cross-validation with feature selection analysis.

lasso_stability_selection

Stability selection for robust feature selection using bootstrap sampling.

lasso_selection_path

Compute the full regularization path for feature selection analysis.

Classes#

Lasso(n_cv: int = 5, alphas: torch.Tensor | None = None, max_iter: int = 1000, tol: float = 0.0001, eps: float = 0.001, selection: Literal['cyclic', 'random'] = 'cyclic', n_jobs: int = -1, threshold: float = 1e-08, random_state: int | None = None, verbose: bool | int = False) #

Bases: Estimator

Lasso regression with cross-validation for feature selection.

Implements L1-regularized linear regression using scikit-learn's LassoCV or MultiTaskLassoCV for single and multi-target regression respectively.

Note: computation uses sklearn (CPU-only). Input tensors are converted to CPU arrays for computation, then results are returned as tensors.

PARAMETER DESCRIPTION
n_cv

Number of folds for cross-validation.

TYPE: int, optional, by default 5 DEFAULT: 5

alphas

Grid of alpha values for cross-validation. If None, automatically generated.

TYPE: torch.Tensor or None, optional, by default None DEFAULT: None

max_iter

Maximum number of iterations for coordinate descent.

TYPE: int, optional, by default 1000 DEFAULT: 1000

tol

Tolerance for optimization convergence.

TYPE: float, optional, by default 0.0001 DEFAULT: 0.0001

eps

Length of regularization path (ratio of min/max alpha).

TYPE: float, optional, by default 0.001 DEFAULT: 0.001

selection

Feature selection strategy for coordinate descent.

TYPE: (cyclic, random) DEFAULT: "cyclic"

n_jobs

Number of parallel jobs for cross-validation. -1 uses all processors.

TYPE: int, by default -1 DEFAULT: -1

threshold

Threshold for feature selection.

TYPE: float, by default 1e-8 DEFAULT: 1e-08

random_state

Random seed for reproducibility.

TYPE: int or None, optional, by default None DEFAULT: None

verbose

Verbosity level for training progress.

TYPE: bool or int, optional, by default False DEFAULT: False

Properties

coefficients : torch.Tensor Learned coefficients of the model.

bias : torch.Tensor Learned bias/intercept of the model.

alphas : torch.Tensor Optimal alpha value selected from cross-validation.

lasso_model : LassoCV or MultiTaskLassoCV Underlying sklearn Lasso model.

scores : torch.Tensor or None Cross-validation scores for each alpha value.

features : torch.Tensor Indices of selected features with non-zero coefficients.

metrics : dict[str, torch.Tensor] Dictionary containing metrics (coefficients, bias, alphas, scores, features).

METHOD DESCRIPTION
fit

Fit Lasso model to data.

predict

Predict using fitted coefficients.

score

Compute coefficient of determination on the given test data and labels.

transform

Transform data by selecting features with non-zero coefficients.

fit_transform

Fit Lasso and transform data in one step.

ATTRIBUTE DESCRIPTION
features

Indices of selected features with non-zero coefficients.

TYPE: Tensor

metrics

Get all metrics from fitted model.

TYPE: dict[str, Tensor]

coefficients

Get coefficients from fitted Lasso model.

TYPE: Tensor

bias

Get bias/intercept from fitted Lasso model.

TYPE: Tensor

alpha

Get optimal alpha value from fitted Lasso model.

TYPE: Tensor

lasso_model

Get the underlying sklearn Lasso model.

TYPE: LassoCV | MultiTaskLassoCV

scores

Get cross-validation scores from fitted Lasso model.

TYPE: Tensor | None

Source code in spectre/feature_selection/lasso.py
def __init__(
    self,
    n_cv: int = 5,
    alphas: torch.Tensor | None = None,
    max_iter: int = 1000,
    tol: float = 1e-4,
    eps: float = 1e-3,
    selection: Literal["cyclic", "random"] = "cyclic",
    n_jobs: int = -1,
    threshold: float = 1e-8,
    random_state: int | None = None,
    verbose: bool | int = False,
) -> None:
    super().__init__()

    if not isinstance(n_cv, int):
        raise TypeError(f"Expected `n_cv` to be `int`, got {type(n_cv)}.")
    check_in_interval(n_cv, "[2, inf)")
    self.n_cv = n_cv

    if alphas is not None:
        if not isinstance(alphas, torch.Tensor):
            raise TypeError(
                f"Expected `alphas` to be `torch.Tensor` or None, got {type(alphas)}."
            )
        if alphas.ndim != 1:
            raise ValueError(
                f"Expected `alphas` to be 1D tensor, got shape {alphas.shape}."
            )
    self._alphas_init = alphas

    if not isinstance(max_iter, int):
        raise TypeError(f"Expected `max_iter` to be `int`, got {type(max_iter)}.")
    check_in_interval(max_iter, "[1, inf)")
    self.max_iter = max_iter

    if not isinstance(tol, float):
        raise TypeError(f"Expected `tol` to be `float`, got {type(tol)}.")
    check_in_interval(tol, "(0, inf)")
    self.tol = tol

    if not isinstance(eps, float):
        raise TypeError(f"Expected `eps` to be `float`, got {type(eps)}.")
    check_in_interval(eps, "(0, inf)")
    self.eps = eps

    if selection not in ("cyclic", "random"):
        raise ValueError(
            f"Expected `selection` to be 'cyclic' or 'random', got '{selection}'."
        )
    self.selection = selection

    if n_jobs is not None and not isinstance(n_jobs, int):
        raise TypeError(
            f"Expected `n_jobs` to be `int` or None, got {type(n_jobs)}."
        )
    self.n_jobs = n_jobs

    if not isinstance(threshold, float):
        raise TypeError(
            f"Expected `threshold` to be `float`, got {type(threshold)}."
        )
    check_in_interval(threshold, "(0, inf)")
    self.threshold = threshold

    if random_state is not None and not isinstance(random_state, int):
        raise TypeError(
            f"Expected `random_state` to be `int` or None, got {type(random_state)}."
        )
    self.random_state = random_state

    if not isinstance(verbose, (bool, int)):
        raise TypeError(
            f"Expected `verbose` to be `bool` or `int`, got {type(verbose)}."
        )
    self.verbose = verbose

    # Internal attributes.
    self._coefficients: torch.Tensor | None = None
    self._bias: torch.Tensor | None = None
    self._alphas: float | None = None
    self._lasso: LassoCV | MultiTaskLassoCV | None = None
    self._scores: torch.Tensor | None = None
Attributes#
features: torch.Tensor property #

Indices of selected features with non-zero coefficients.

RETURNS DESCRIPTION
Tensor

Indices of features with non-zero coefficients of shape (n_selected, ).

metrics: dict[str, torch.Tensor] property #

Get all metrics from fitted model.

RETURNS DESCRIPTION
dict[str, Tensor]

Dictionary containing:

  • 'coefficients': Learned coefficients
  • 'bias': Learned bias/intercept
  • 'alpha': Optimal alpha value
  • 'scores': Cross-validation scores
  • 'features': Selected feature indices
  • 'n_features_selected': Number of selected features
coefficients: torch.Tensor property #

Get coefficients from fitted Lasso model.

bias: torch.Tensor property #

Get bias/intercept from fitted Lasso model.

RETURNS DESCRIPTION
Tensor

Learned bias/intercept value.

alpha: torch.Tensor property #

Get optimal alpha value from fitted Lasso model.

RETURNS DESCRIPTION
Tensor

Optimal alpha value selected by cross-validation.

lasso_model: LassoCV | MultiTaskLassoCV property #

Get the underlying sklearn Lasso model.

RETURNS DESCRIPTION
LassoCV or MultiTaskLassoCV

Fitted sklearn Lasso model.

scores: torch.Tensor | None property #

Get cross-validation scores from fitted Lasso model.

Functions#
fit(X: torch.Tensor, weights: torch.Tensor | None = None, target: torch.Tensor | None = None) -> Lasso #

Fit Lasso model to data.

PARAMETER DESCRIPTION
X

Input features of shape (n_samples, in_features).

TYPE: Tensor

weights

Sample weights of shape (n_samples, ).

TYPE: torch.Tensor | None, optional, by default None DEFAULT: None

target

Target values of shape (n_samples, ) or (n_samples, n_targets).

TYPE: Tensor DEFAULT: None

RETURNS DESCRIPTION
Lasso

Fitted Lasso instance.

Source code in spectre/feature_selection/lasso.py
def fit(
    self,
    X: torch.Tensor,
    weights: torch.Tensor | None = None,
    target: torch.Tensor | None = None,
) -> "Lasso":
    """
    Fit Lasso model to data.

    Parameters
    ----------
    X : torch.Tensor
        Input features of shape (n_samples, in_features).

    weights : torch.Tensor | None, optional, by default None
        Sample weights of shape (n_samples, ).

    target : torch.Tensor
        Target values of shape (n_samples, ) or (n_samples, n_targets).

    Returns
    -------
    Lasso
        Fitted Lasso instance.
    """
    check_2d(X)

    if target is None:
        raise ValueError("Target values must be provided for Lasso regression.")

    if weights is not None:
        check_same_device(X, weights)

    # Validate in_features consistency if already fitted
    if hasattr(self, "in_features") and self.in_features is not None:
        if X.shape[1] != self.in_features:
            raise ValueError(
                f"Expected {self.in_features} features, got {X.shape[1]}."
            )

    self.in_features = X.shape[1]
    self.out_features = target.shape[1] if target.ndim == 2 else 1

    X_array = check_from_tensor(X)
    target_array = check_from_tensor(target)
    check_same_shape(X_array, target_array, axis=0)

    if weights is not None:
        weights_array = check_from_tensor(weights)
        check_same_shape(X_array, weights_array, axis=0)
    else:
        weights_array = None

    if self._alphas_init is not None:
        alphas_array = check_from_tensor(self._alphas_init)
    else:
        alphas_array = None

    # Create Lasso or MultiTaskLassoCV instance based on target shape
    lasso_fn = LassoCV if target_array.squeeze().ndim == 1 else MultiTaskLassoCV
    self._lasso = lasso_fn(
        alphas=alphas_array,
        cv=self.n_cv,
        max_iter=self.max_iter,
        eps=self.eps,
        selection=self.selection,
        tol=self.tol,
        n_jobs=self.n_jobs,
        random_state=self.random_state,
        verbose=self.verbose,
    )
    self._lasso.fit(X_array, target_array, sample_weight=weights_array)

    self._coefficients = check_to_tensor(self._lasso.coef_)
    if hasattr(self._lasso, "intercept_"):
        bias = self._lasso.intercept_
        if np.isscalar(bias):
            self._bias = torch.tensor(bias)
        else:
            self._bias = torch.from_numpy(bias)
    self._alphas = torch.tensor(self._lasso.alpha_)

    self.is_fitted = True

    return self
predict(X: torch.Tensor) -> torch.Tensor #

Predict using fitted coefficients.

PARAMETER DESCRIPTION
X

Input data.

TYPE: Tensor

RETURNS DESCRIPTION
Tensor

Predictions.

Source code in spectre/feature_selection/lasso.py
def predict(self, X: torch.Tensor) -> torch.Tensor:
    """
    Predict using fitted coefficients.

    Parameters
    ----------
    X : torch.Tensor
        Input data.

    Returns
    -------
    torch.Tensor
        Predictions.
    """
    self.check_fitted()
    check_2d(X)

    pred = X @ (
        self._coefficients.T if self._coefficients.ndim > 1 else self._coefficients
    )
    if self._bias is not None:
        pred = pred + self._bias

    return pred
score(X: torch.Tensor, weights: torch.Tensor | None = None, target: torch.Tensor | None = None) -> torch.Tensor #

Compute coefficient of determination on the given test data and labels.

PARAMETER DESCRIPTION
X

Test samples.

TYPE: Tensor

weights

Sample weights.

TYPE: torch.Tensor | None, optional, by default None DEFAULT: None

target

True values for X.

TYPE: torch.Tensor | None, optional, by default None DEFAULT: None

RETURNS DESCRIPTION
Tensor

Coefficient of determination.

Source code in spectre/feature_selection/lasso.py
def score(
    self,
    X: torch.Tensor,
    weights: torch.Tensor | None = None,
    target: torch.Tensor | None = None,
) -> torch.Tensor:
    """
    Compute coefficient of determination on the given test data and labels.

    Parameters
    ----------
    X : torch.Tensor
        Test samples.

    weights : torch.Tensor | None, optional, by default None
        Sample weights.

    target : torch.Tensor | None, optional, by default None
        True values for X.

    Returns
    -------
    torch.Tensor
        Coefficient of determination.
    """
    self.check_fitted()
    if target is None:
        raise ValueError("Target values are required for scoring.")

    target_pred = self.predict(X)

    return r2_loss(target=target, target_pred=target_pred, weights=weights)
transform(X: torch.Tensor) -> torch.Tensor #

Transform data by selecting features with non-zero coefficients.

PARAMETER DESCRIPTION
X

Input data of shape (n_samples, in_features).

TYPE: Tensor

RETURNS DESCRIPTION
Tensor

Transformed data with selected features of shape (n_samples, n_selected).

RAISES DESCRIPTION
ValueError

If model has not been fitted yet.

Source code in spectre/feature_selection/lasso.py
def transform(self, X: torch.Tensor) -> torch.Tensor:
    """
    Transform data by selecting features with non-zero coefficients.

    Parameters
    ----------
    X : torch.Tensor
        Input data of shape (n_samples, in_features).

    Returns
    -------
    torch.Tensor
        Transformed data with selected features of shape (n_samples, n_selected).

    Raises
    ------
    ValueError
        If model has not been fitted yet.
    """
    self.check_fitted()
    check_2d(X)
    return X[:, self.features]
fit_transform(X: torch.Tensor, weights: torch.Tensor | None = None, target: torch.Tensor | None = None) -> torch.Tensor #

Fit Lasso and transform data in one step.

PARAMETER DESCRIPTION
X

Training data of shape (n_samples, in_features).

TYPE: Tensor

weights

Sample weights of shape (n_samples, ).

TYPE: torch.Tensor | None, optional, by default None DEFAULT: None

target

Target values of shape (n_samples, ) or (n_samples, n_targets).

TYPE: torch.Tensor | None, optional, by default None DEFAULT: None

RETURNS DESCRIPTION
Tensor

Transformed data with selected features of shape (n_samples, n_selected).

Source code in spectre/feature_selection/lasso.py
def fit_transform(
    self,
    X: torch.Tensor,
    weights: torch.Tensor | None = None,
    target: torch.Tensor | None = None,
) -> torch.Tensor:
    """
    Fit Lasso and transform data in one step.

    Parameters
    ----------
    X : torch.Tensor
        Training data of shape (n_samples, in_features).

    weights : torch.Tensor | None, optional, by default None
        Sample weights of shape (n_samples, ).

    target : torch.Tensor | None, optional, by default None
        Target values of shape (n_samples, ) or (n_samples, n_targets).

    Returns
    -------
    torch.Tensor
        Transformed data with selected features of shape (n_samples, n_selected).
    """
    self.fit(X, weights=weights, target=target)
    return self.transform(X)

Functions#

lasso(X: torch.Tensor, weights: torch.Tensor | None = None, target: torch.Tensor | None = None, n_cv: int = 5, alphas: torch.Tensor | None = None, max_iter: int = 1000, tol: float = 0.0001, eps: float = 0.001, selection: Literal['cyclic', 'random'] = 'cyclic', n_jobs: int | None = None, threshold: float = 1e-08, random_state: int | None = None, verbose: bool | int = False) -> dict[str, torch.Tensor] #

Lasso cross-validation with feature selection analysis.

Performs multi-task Lasso model training with L1/L2 mixed-norm regularization and provides feature selection statistics, stability analysis, and model interpretation tools.

PARAMETER DESCRIPTION
X

Training features matrix.

TYPE: torch.Tensor or np.ndarray of shape (n_samples, n_features)

weights

Sample weights for training.

TYPE: torch.Tensor or None, optional, by default None DEFAULT: None

target

Target values.

TYPE: torch.Tensor or np.ndarray of shape (n_samples,) or (n_samples, n_targets) DEFAULT: None

n_cv

Number of folds for cross-validation.

TYPE: int, optional, by default 5 DEFAULT: 5

alphas

Grid of alpha values for cross-validation. If None, automatically generated.

TYPE: torch.Tensor or None, optional, by default None DEFAULT: None

max_iter

Maximum number of iterations for coordinate descent.

TYPE: int, optional, by default 1000 DEFAULT: 1000

tol

Tolerance for optimization convergence.

TYPE: float, optional, by default 0.0001 DEFAULT: 0.0001

eps

Length of regularization path (ratio of min/max alpha).

TYPE: float, optional, by default 0.001 DEFAULT: 0.001

selection

Feature selection strategy for coordinate descent.

TYPE: (cyclic, random) DEFAULT: "cyclic"

n_jobs

Number of parallel jobs for cross-validation.

TYPE: int or None, optional, by default None DEFAULT: None

threshold

Threshold for feature selection.

TYPE: float, by default 1e-8 DEFAULT: 1e-08

random_state

Random seed for reproducibility.

TYPE: int or None, optional, by default None DEFAULT: None

verbose

Verbosity level for training progress.

TYPE: bool or int, optional, by default False DEFAULT: False

RETURNS DESCRIPTION
dict[str, Tensor]

Dictionary containing:

  • 'coefficients': Coefficient values as importance scores
  • 'bias': Intercept value
  • 'alpha': Optimal regularization parameter from cross-validation
  • 'scores': Cross-validation scores
  • 'features': Selected feature indices
  • 'n_features_selected': Number of selected features
Source code in spectre/feature_selection/lasso.py
def lasso(
    X: torch.Tensor,
    weights: torch.Tensor | None = None,
    target: torch.Tensor | None = None,
    n_cv: int = 5,
    alphas: torch.Tensor | None = None,
    max_iter: int = 1000,
    tol: float = 0.0001,
    eps: float = 0.001,
    selection: Literal["cyclic", "random"] = "cyclic",
    n_jobs: int | None = None,
    threshold: float = 1e-8,
    random_state: int | None = None,
    verbose: bool | int = False,
) -> dict[str, torch.Tensor]:
    """
    Lasso cross-validation with feature selection analysis.

    Performs multi-task Lasso model training with L1/L2 mixed-norm regularization
    and provides feature selection statistics, stability analysis, and model
    interpretation tools.

    Parameters
    ----------
    X : torch.Tensor or np.ndarray of shape (n_samples, n_features)
        Training features matrix.

    weights : torch.Tensor or None, optional, by default None
        Sample weights for training.

    target : torch.Tensor or np.ndarray of shape (n_samples,) or (n_samples, n_targets)
        Target values.

    n_cv : int, optional, by default 5
        Number of folds for cross-validation.

    alphas : torch.Tensor or None, optional, by default None
        Grid of alpha values for cross-validation. If None, automatically generated.

    max_iter : int, optional, by default 1000
        Maximum number of iterations for coordinate descent.

    tol : float, optional, by default 0.0001
        Tolerance for optimization convergence.

    eps : float, optional, by default 0.001
        Length of regularization path (ratio of min/max alpha).

    selection : {"cyclic", "random"}, optional, by default "cyclic"
        Feature selection strategy for coordinate descent.

    n_jobs : int or None, optional, by default None
        Number of parallel jobs for cross-validation.

    threshold : float, by default 1e-8
        Threshold for feature selection.

    random_state : int or None, optional, by default None
        Random seed for reproducibility.

    verbose : bool or int, optional, by default False
        Verbosity level for training progress.

    Returns
    -------
    dict[str, torch.Tensor]
        Dictionary containing:

        - 'coefficients': Coefficient values as importance scores
        - 'bias': Intercept value
        - 'alpha': Optimal regularization parameter from cross-validation
        - 'scores': Cross-validation scores
        - 'features': Selected feature indices
        - 'n_features_selected': Number of selected features
    """
    model = Lasso(
        n_cv=n_cv,
        alphas=alphas,
        max_iter=max_iter,
        tol=tol,
        eps=eps,
        selection=selection,
        n_jobs=n_jobs,
        threshold=threshold,
        random_state=random_state,
        verbose=verbose,
    )

    model.fit(X, weights=weights, target=target)

    return model.metrics

lasso_stability_selection(X: torch.Tensor, weights: torch.Tensor | None = None, target: torch.Tensor | None = None, n_cv: int = 5, max_iter: int = 1000, tol: float = 0.0001, eps: float = 0.001, n_bootstrap: int = 100, subsample_ratio: float = 0.8, alpha_grid: torch.Tensor | None = None, n_alphas: int = 20, threshold_prob: float = 0.6, threshold: float = 0.1, random_state: int | None = None, verbose: bool = False) -> dict #

Stability selection for robust feature selection using bootstrap sampling.

Performs feature selection across multiple bootstrap samples to identify features that are consistently selected, providing more robust and interpretable feature selection than single-model approaches.

PARAMETER DESCRIPTION
X

Training features matrix.

TYPE: torch.Tensor of shape (n_samples, n_features)

weights

Sample weights.

TYPE: torch.Tensor of shape (n_samples,) or None, optional, by default None DEFAULT: None

target

Target values.

TYPE: torch.Tensor of shape (n_samples, ), optional, by default None DEFAULT: None

n_cv

Number of cross-validation splits.

TYPE: int, optional, by default 5 DEFAULT: 5

max_iter

Maximum iterations for Lasso solver.

TYPE: int, optional, by default 1000 DEFAULT: 1000

tol

Tolerance for convergence.

TYPE: float, optional, by default 1e-4 DEFAULT: 0.0001

eps

Length of regularization path.

TYPE: float, optional, by default 1e-3 DEFAULT: 0.001

n_bootstrap

Number of bootstrap samples.

TYPE: int, optional, by default 100 DEFAULT: 100

subsample_ratio

Fraction of samples to use in each bootstrap.

TYPE: float, optional, by default 0.8 DEFAULT: 0.8

alpha_grid

Grid of alpha values to try, If None, generated automatically.

TYPE: torch.Tensor or None, optional, by default None DEFAULT: None

n_alphas

Number of alphas in automatic grid.

TYPE: int, optional, by default 20 DEFAULT: 20

threshold_prob

Selection probability threshold_prob for stable features.

TYPE: float, optional, by default 0.6 DEFAULT: 0.6

threshold

Coefficient threshold for stable features.

TYPE: float, optional, by default 0.1 DEFAULT: 0.1

random_state

Random seed for reproducibility.

TYPE: int or None, optional, by default None DEFAULT: None

verbose

Whether to show progress.

TYPE: bool, optional, by default False DEFAULT: False

RETURNS DESCRIPTION
dict

Dictionary containing:

  • 'stable_features': Features selected in >= threshold_prob fraction of models
  • 'selection_probabilities': Probability each feature was selected
  • 'stability_scores': Stability scores across bootstrap samples
  • 'alpha_grid': Grid of alpha values used
  • 'model': Fitted model

Examples:

>>> import torch
>>> from spectre.feature_selection import lasso_stability_selection
>>>
>>> X = torch.randn(200, 100)
>>> target = torch.randn(200)
>>>
>>> stability_results = lasso_stability_selection(X, target=target, n_bootstrap=50)
Source code in spectre/feature_selection/lasso.py
def lasso_stability_selection(
    X: torch.Tensor,
    weights: torch.Tensor | None = None,
    target: torch.Tensor | None = None,
    n_cv: int = 5,
    max_iter: int = 1000,
    tol: float = 1e-4,
    eps: float = 1e-3,
    n_bootstrap: int = 100,
    subsample_ratio: float = 0.8,
    alpha_grid: torch.Tensor | None = None,
    n_alphas: int = 20,
    threshold_prob: float = 0.6,
    threshold: float = 0.1,
    random_state: int | None = None,
    verbose: bool = False,
) -> dict:
    """
    Stability selection for robust feature selection using bootstrap sampling.

    Performs feature selection across multiple bootstrap samples to identify
    features that are consistently selected, providing more robust and
    interpretable feature selection than single-model approaches.

    Parameters
    ----------
    X : torch.Tensor of shape (n_samples, n_features)
        Training features matrix.

    weights : torch.Tensor of shape (n_samples,) or None, optional, by default None
        Sample weights.

    target : torch.Tensor of shape (n_samples, ), optional, by default None
        Target values.

    n_cv : int, optional, by default 5
        Number of cross-validation splits.

    max_iter : int, optional, by default 1000
        Maximum iterations for Lasso solver.

    tol : float, optional, by default 1e-4
        Tolerance for convergence.

    eps : float, optional, by default 1e-3
        Length of regularization path.

    n_bootstrap : int, optional, by default 100
        Number of bootstrap samples.

    subsample_ratio : float, optional, by default 0.8
        Fraction of samples to use in each bootstrap.

    alpha_grid : torch.Tensor or None, optional, by default None
        Grid of alpha values to try, If None, generated automatically.

    n_alphas : int, optional, by default 20
        Number of alphas in automatic grid.

    threshold_prob : float, optional, by default 0.6
        Selection probability threshold_prob for stable features.

    threshold : float, optional, by default 0.1
        Coefficient threshold for stable features.

    random_state : int or None, optional, by default None
        Random seed for reproducibility.

    verbose : bool, optional, by default False
        Whether to show progress.

    Returns
    -------
    dict
        Dictionary containing:

        - 'stable_features': Features selected in >= threshold_prob fraction of models
        - 'selection_probabilities': Probability each feature was selected
        - 'stability_scores': Stability scores across bootstrap samples
        - 'alpha_grid': Grid of alpha values used
        - 'model': Fitted model

    Examples
    --------
    >>> import torch
    >>> from spectre.feature_selection import lasso_stability_selection
    >>>
    >>> X = torch.randn(200, 100)
    >>> target = torch.randn(200)
    >>>
    >>> stability_results = lasso_stability_selection(X, target=target, n_bootstrap=50)
    """
    n_samples, n_features = X.shape

    if not isinstance(threshold_prob, float):
        raise TypeError("threshold_prob must be a float")
    check_in_interval(threshold_prob, "(0, 1]")

    if not isinstance(threshold, float):
        raise TypeError("threshold must be a float")
    check_in_interval(threshold, "(0, inf)")

    if not isinstance(subsample_ratio, float):
        raise TypeError("subsample_ratio must be a float")
    check_in_interval(subsample_ratio, "(0, 1]")

    if not isinstance(n_alphas, int):
        raise TypeError("n_alphas must be an integer")
    check_in_interval(n_alphas, "(0, inf)")

    subsample_size = int(subsample_ratio * n_samples)

    if alpha_grid is None:
        alpha_max = torch.max(torch.abs(X.T @ target)) / n_samples
        alpha_grid = torch.logspace(
            torch.log10(alpha_max * eps).item(),
            torch.log10(alpha_max).item(),
            n_alphas,
        )

    # Track feature selection across bootstrap samples
    selection_matrix = torch.zeros((n_bootstrap, n_features), dtype=torch.float32)

    iterator = range(n_bootstrap)
    if verbose:
        iterator = tqdm(iterator, desc="Bootstrap sampling", unit="sample")

    for i in iterator:
        indices = torch.randint(0, n_samples, (subsample_size,))
        X_boot = X[indices]
        weights_boot = weights[indices] if weights is not None else None
        target_boot = target[indices]

        model = Lasso(
            alphas=alpha_grid,
            n_cv=n_cv,
            max_iter=max_iter,
            tol=tol,
            eps=eps,
            n_jobs=1,  # Use 1 to avoid nested parallelism
            random_state=random_state + i if random_state is not None else None,
            verbose=False,
        )
        model.fit(X_boot, weights=weights_boot, target=target_boot)

        selected = torch.abs(model.coefficients) > threshold
        selection_matrix[i] = selected

    selection_probabilities = torch.mean(selection_matrix, dim=0)
    stable_features = torch.where(selection_probabilities >= threshold_prob)[0]

    stability_scores = torch.var(selection_matrix, dim=0)

    return {
        "features": stable_features,
        "selection_probabilities": selection_probabilities,
        "stability_scores": stability_scores,
        "alphas": alpha_grid,
        "selection_matrix": selection_matrix,
    }

lasso_selection_path(X: torch.Tensor, weights: torch.Tensor | None = None, target: torch.Tensor | None = None, alpha_grid: torch.Tensor | None = None, standardize: bool = True, threshold: float = 0.1, n_cv: int = 5, max_iter: int = 1000, tol: float = 0.0001, eps: float = 0.001, n_alphas: int = 20) -> dict #

Compute the full regularization path for feature selection analysis.

Traces how feature selection changes across different regularization strengths, providing insights into feature importance hierarchy and selection stability.

PARAMETER DESCRIPTION
X

Training features matrix.

TYPE: torch.Tensor of shape (n_samples, n_features)

weights

Sample weights.

TYPE: torch.Tensor of shape (n_samples,) or None, optional, by default None DEFAULT: None

target

Target values.

TYPE: torch.Tensor of shape (n_samples, ), optional, by default None DEFAULT: None

alpha_grid

Grid of alpha values. If None, generated automatically.

TYPE: torch.Tensor or None, optional, by default None DEFAULT: None

n_alphas

Number of alphas in automatic grid.

TYPE: int, optional, by default 20 DEFAULT: 20

standardize

Whether to standardize features.

TYPE: bool, optional, by default True DEFAULT: True

threshold

Threshold for feature selection.

TYPE: float, optional, by default 0.1 DEFAULT: 0.1

n_cv

Number of cross-validation splits.

TYPE: int, optional, by default 5 DEFAULT: 5

max_iter

Maximum iterations for Lasso solver.

TYPE: int, optional, by default 1000 DEFAULT: 1000

tol

Tolerance for convergence.

TYPE: float, optional, by default 1e-4 DEFAULT: 0.0001

eps

Length of regularization path.

TYPE: float, optional, by default 1e-3 DEFAULT: 0.001

RETURNS DESCRIPTION
dict

Dictionary containing:

  • 'alpha_grid': Regularization parameter values
  • 'coefficients_path': Coefficient values for each alpha
  • 'n_selected_path': Number of selected features for each alpha
  • 'scores': Cross-validation scores
  • 'feature_entry_order': Order in which features enter the model
Source code in spectre/feature_selection/lasso.py
def lasso_selection_path(
    X: torch.Tensor,
    weights: torch.Tensor | None = None,
    target: torch.Tensor | None = None,
    alpha_grid: torch.Tensor | None = None,
    standardize: bool = True,
    threshold: float = 0.1,
    n_cv: int = 5,
    max_iter: int = 1000,
    tol: float = 1e-4,
    eps: float = 1e-3,
    n_alphas: int = 20,
) -> dict:
    """
    Compute the full regularization path for feature selection analysis.

    Traces how feature selection changes across different regularization
    strengths, providing insights into feature importance hierarchy and
    selection stability.

    Parameters
    ----------
    X : torch.Tensor of shape (n_samples, n_features)
        Training features matrix.

    weights : torch.Tensor of shape (n_samples,) or None, optional, by default None
        Sample weights.

    target : torch.Tensor of shape (n_samples, ), optional, by default None
        Target values.

    alpha_grid : torch.Tensor or None, optional, by default None
        Grid of alpha values. If None, generated automatically.

    n_alphas : int, optional, by default 20
        Number of alphas in automatic grid.

    standardize : bool, optional, by default True
        Whether to standardize features.

    threshold : float, optional, by default 0.1
        Threshold for feature selection.

    n_cv : int, optional, by default 5
        Number of cross-validation splits.

    max_iter : int, optional, by default 1000
        Maximum iterations for Lasso solver.

    tol : float, optional, by default 1e-4
        Tolerance for convergence.

    eps : float, optional, by default 1e-3
        Length of regularization path.

    Returns
    -------
    dict
        Dictionary containing:

        - 'alpha_grid': Regularization parameter values
        - 'coefficients_path': Coefficient values for each alpha
        - 'n_selected_path': Number of selected features for each alpha
        - 'scores': Cross-validation scores
        - 'feature_entry_order': Order in which features enter the model
    """
    n_samples, n_features = X.shape

    if not isinstance(threshold, float):
        raise TypeError("threshold must be a float")
    check_in_interval(threshold, "(0, inf)")

    if standardize:
        X_mean = X.mean(dim=0)
        X_std = X.std(dim=0)
        X_std = torch.where(X_std == 0, torch.ones_like(X_std), X_std)
        X = (X - X_mean) / X_std

    if alpha_grid is None:
        alpha_max = torch.max(torch.abs(X.T @ target)) / n_samples
        alpha_grid = torch.logspace(
            torch.log10(alpha_max * eps).item(),
            torch.log10(alpha_max).item(),
            n_alphas,
        )
    else:
        alpha_grid = torch.sort(alpha_grid, descending=True)[0]

    coefficients_path = torch.zeros((n_features, len(alpha_grid)))
    cv_scores = torch.zeros(len(alpha_grid))

    for i, alpha in enumerate(alpha_grid):
        single_alpha = alpha.unsqueeze(0)

        model = Lasso(
            alphas=single_alpha,
            n_cv=n_cv,
            max_iter=max_iter,
            tol=tol,
            eps=eps,
            verbose=False,
        )
        model.fit(X, weights=weights, target=target)

        coefficients_path[:, i] = (
            model._coefficients
            if model._coefficients.ndim == 1
            else model._coefficients.squeeze()
        )

        cv_scores[i] = model.score(X, weights=weights, target=target)

    n_selected_path = torch.sum(torch.abs(coefficients_path) > threshold, dim=0)

    # Feature entry order (when each feature first becomes non-zero)
    feature_entry_order = []
    active_features = set()

    for i in range(len(alpha_grid)):
        coef = coefficients_path[:, i]
        current_active = set(torch.where(torch.abs(coef) > threshold)[0])
        new_features = current_active - active_features
        feature_entry_order.extend(list(new_features))
        active_features = current_active

    return {
        "alpha_grid": alpha_grid,
        "coefficients_path": coefficients_path,
        "n_selected_path": n_selected_path,
        "scores": cv_scores,
        "feature_entry_order": feature_entry_order,
    }