Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ under development (0.3.2.dev0)
- :bdg-secondary:`Maint` temporary fix for the CI upper-bounding scikit-learn to 1.9.0 (:gh:`669` by `Joseph Paillard`_).
- :bdg-secondary:`Maint` remove pinned poosh dependency (problem with somato dataset solved) (:gh:`670` by `Joseph Paillard`_).
- :bdg-secondary:`Maint`: remove extra term in variance of X-residual (DOCRT). See [Reid et al., A Study of Error Variance Estimation in Lasso Regression 2016](https://arxiv.org/pdf/1311.5274) for reference. (:gh:`649` by `Joseph Paillard`_).
- :bdg-primary:`Doc` example gallery reorganization: added basic "How to get started" examples (:gh:`684` by `Marc Hulcelle`_).
- :bdg-secondary:`Maint`: Fix conditional sampling test by verifying that sampler produces diverse samples. (:gh:`692` by `Joseph Paillard`_).
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ unmodified test data—following the same distribution as the training data—
to its performance when the studied feature is conditionally perturbed. Thus, this approach
does not require retraining the model.

.. figure:: ../generated/gallery/examples/images/sphx_glr_plot_cfi_001.png
:target: ../generated/gallery/examples/plot_cfi.html
:align: center
#.. figure:: ../generated/gallery/examples/images/sphx_glr_plot_cfi_001.png
# :target: ../generated/gallery/examples/plot_cfi.html
# :align: center


Theoretical index
Expand Down
3 changes: 3 additions & 0 deletions examples/basic_examples/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Getting Started
===============
Examples to illustrate the basic usage of HiDimStat.
143 changes: 143 additions & 0 deletions examples/basic_examples/plot_basic_1_general_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""
General pipeline to assess feature importance
==================================================================================
This example demonstrates how to measure feature importance using hidimstat, and the
general pipeline of functions to call when doing so.
The functions used in the following are generic to any of the feature importance assessment
methods existing in the library. Here, we will use the Conditional Feature Importance
(CFI) [:footcite:t:`Chamma_NeurIPS2023`] on a simulated regression dataset.
"""

# %%
# Loading and preparing the data
# ------------------------------
# We begin by simulating a regression dataset with 10 features, 5 of which
# are in the support set, meaning they contribute to generating the outcome. In this example,
# we use a simulated dataset to have access to the true support set of features and
# evaluate how well the different models identify these important features.
# The data is then split into training and test sets. These sets are used both to fit
# the predictive models and within the LOCO procedure, which refits models on subsets
# of features that exclude the feature of interest.

from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

X, y, beta = make_regression(
n_samples=300,
n_features=10,
n_informative=5,
random_state=0,
coef=True,
noise=10.0,
)

# We convert the coefficients of the data-generating process into a binary array
# indicating the true support set of features.
beta = beta != 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
beta = beta != 0
support = beta != 0
``` is clearer IMHO


X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=0,
shuffle=True,
)

# %%
# Fitting the model and computing feature importance
# ------------------------------------------------------
# To solve the classification task, we use a pipeline that first standardizes the features with StandardScaler,
# followed by a neural network (MLPClassifier) with one hidden layer of 8 neurons.
# Before measuring feature importance, we evaluate the estimator's performance by reporting its :math:`R^2` score.

from sklearn.ensemble import RandomForestRegressor

clf = RandomForestRegressor(n_estimators=150, random_state=0)

clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(f"Accuracy: {clf.score(X_test, y_test):.3f}")

# %%
# Next, we use the CFI class to measure feature importance. Here, we use a RidgeCV
# model to estimate the conditional expectation :math:`\mathbb{E}[X^j | X^{-j}]`.
# Since this is a regression task, we use mean_squared_error.

from sklearn.linear_model import RidgeCV
from sklearn.metrics import mean_squared_error

from hidimstat import CFI

cfi = CFI(
estimator=clf,
loss=mean_squared_error,
imputation_model_continuous=RidgeCV(),
features_groups={f"Feat {i}": [i] for i in range(X.shape[1])},
random_state=0,
)
cfi.fit(
X_train,
y_train,
)
importances = cfi.importance(X_test, y_test)

# %%
# An alternative is to call the `fit_importance` method which conveniently combines both functions.
# Please keep in mind that both the fit and feature importance will subsequently use the same data.


# %%
# Visualization of CFI feature importance and feature selection
# ----------------------------------------------------------------
# Finally, we visualize the importance of each feature using a bar plot
# thanks to a built-in importance visualization function, and perform
# feature selection with statistical guarantees.

import matplotlib.pyplot as plt
import numpy as np

_, ax = plt.subplots(figsize=(6, 3))
ax = cfi.plot_importance(ax=ax)
ax.set_xlabel("Feature Importance")

# Since the figure displays importance measures in a descending order,
# we need to sort betas accordingly:

sorted_beta = beta[np.argsort(importances)[::-1]]

for i, support in enumerate(sorted_beta):
if support != 0:
ax.axhspan(
i - 0.45,
i + 0.45,
color="tab:olive",
alpha=0.3,
zorder=-1,
label="True Support" if i == 1 else None,
)
ax.legend()

plt.tight_layout()
plt.show()


# %%
# Next, we can make a function call to select the most important features. This can be done through
# one of three difference control mechanisms: through p-values, on False Discovery Rate (FDR)
# or on Family-Wise Error Rate (FWER). Each function returns a boolean mask that indicates us
# which features to keep based on the selected control mechanism.
# Here, we will use select importance through p_values and set a standard threshold of 0.05.

selection = cfi.pvalue_selection(threshold_max=0.05)
important_features = X[:, selection]

# %%
# This is the basic pipeline for feature importance assessment and feature selection.
# We demonstrated the general workflow using CFI, but other implemented methods can be used
# in a similar manner.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether its would be useful to plot error bars on the estimated importance ?


# %%
# References
# ----------
# .. footbibliography::
119 changes: 119 additions & 0 deletions examples/basic_examples/plot_basic_2_individual_vs_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Measuring Individual and Group Variable Importance for Classification
======================================================================

In this example, we show on the Iris dataset how to measure variable importance for
classification tasks. This time, we use the Permutation Feature Importance (PFI) method
with a Support Vector Classifier (SVC). We start by measuring the importance of
individual variables and then show how to measure the importance of groups of variables.

To briefly summarize, PFI (Permutation Feature Importance) shuffles the values of
a feature and measures the increase in the loss when predicting (using om the same
full model) on the shuffled data.

"""

import matplotlib.pyplot as plt

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid having all imports at the beginning, and rather have them when needed along the script ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think it makes more sense as it's closer to a notebook. I'll make the change over all the scripts

import numpy as np
from sklearn.datasets import load_wine
from sklearn.linear_model import RidgeCV
from sklearn.metrics import log_loss
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

from hidimstat import CFI

# Define the seeds for the reproducibility of the example
rng = np.random.default_rng(0)

# %%
# Load the iris dataset and add a spurious feature
# ------------------------------------------------
# We start by loading the iris dataset as is, and splitting for training and testing.

dataset = load_wine()
X, y = dataset.data, dataset.target
feature_names = np.array(dataset.feature_names)
print(feature_names)
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=0,
shuffle=True,
)

# %%
# We use a Multi-Layer Perceptron with one hidden layer of size 100.
# We fit the estimator, compute the feature importance, and use the
# built-in importance plot function.

model = make_pipeline(
StandardScaler(),
MLPClassifier(hidden_layer_sizes=(100), random_state=0, max_iter=500),
)
model.fit(X_train, y_train)

cfi = CFI(
estimator=model,
imputation_model_continuous=RidgeCV(),
n_permutations=50,
random_state=2,
method="predict_proba",
loss=log_loss,
)

cfi.fit(X_train, y_train)
importance = cfi.importance(X_test, y_test)
selection = cfi.pvalue_selection(threshold_max=0.05)

_, ax = plt.subplots(figsize=(6, 3))
ax = cfi.plot_importance(ax=ax)
ax.set_xlabel("Feature Importance")
ax.set_yticklabels(feature_names[np.argsort(importance)[::-1]])
# ax.text(0, 0, f"Selected features: {feature_names[selection]}")
plt.tight_layout()
plt.show()


# %%
# Measuring the importance of groups of features
# ----------------------------------------------
# In the example above, CFI did not select some features. This is because it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where can this be found ?
Again, displaying error bars would clarify that the importance values are not significant.

# measures conditional importance, which is the additional independent information a
# feature provides knowing all the other features. When features are highly correlated,
# this additional information decreases, resulting in lower importance rankings. To
# mitigate this issue, we can group correlated features together and measure the
# importance of these feature groups. Here, we regroup variables into the following groups:
# - "acid": `malic_acid` and `proline`
# - "ash": `ash` and `alcalinity of ash`
# - "phenols": `total_phenols`, `flavanoids`, `nonflavanoid_phenols`, and `proanthocyanins`
# - "color": `hue`, and `color_intensity`

features_groups = {
"acid": [1, 12],
"ash": [2, 3],
"phenols": [5, 6, 7, 8],
"color": [9, 10],
}


cfi = CFI(
estimator=model,
imputation_model_continuous=RidgeCV(),
n_permutations=50,
random_state=2,
method="predict_proba",
loss=log_loss,
features_groups=features_groups,
)

importance = cfi.fit_importance(X_test, y_test)

_, ax = plt.subplots(figsize=(6, 3))
ax = cfi.plot_importance(ax=ax)
ax.set_xlabel("Feature Importance")
plt.tight_layout()
plt.show()
Loading
Loading