.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/pitfalls_and_solutions/plot_skd007_mdi_cardinality_bias.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_pitfalls_and_solutions_plot_skd007_mdi_cardinality_bias.py: .. _example_skd007_mdi_cardinality_bias: SKD007 - MDI feature importance is biased for high-cardinality features ======================================================================= This example demonstrates the limitations that :ref:`SKD007 ` warns against on tree models. Mean decrease in impurity (MDI) tends to rank high-cardinality categorical or continuous columns as more important than features with as much signal but lower cardinality. This is due to the tree building process picking high cardinality features more often as they offer more split points to choose from. Mitigations from the :ref:`automated_checks` user guide: - use permutation importance instead of MDI, - cross-check MDI with permutation importance or drop-column importance. We will compare MDI to permutation importance to show that it gives a more reliable estimate of feature importance. The same contrast is illustrated in scikit-learn's `Permutation Importance vs Random Forest Feature Importance (MDI) `_ example. We fit a :class:`~sklearn.ensemble.RandomForestRegressor` on a 1,500-row subsample of California housing. The goal is to show the limitations of impurity based importance and show they do not affect permutation importance on a test set. .. GENERATED FROM PYTHON SOURCE LINES 33-39 Load the California housing dataset =================================== Continuous columns such as ``AveRooms`` and ``AveOccup`` take many distinct values: above the 50 % of samples threshold SKD007 uses for high-cardinality features. .. GENERATED FROM PYTHON SOURCE LINES 39-54 .. code-block:: Python import numpy as np from sklearn.model_selection import train_test_split from skrub.datasets import fetch_california_housing housing = fetch_california_housing() X_full, y_full = housing.X, housing.y X, _, y, _ = train_test_split( X_full, y_full, train_size=1_500, random_state=42, ) .. GENERATED FROM PYTHON SOURCE LINES 55-61 Let us add two random features that carry no signal about the target: a continuous draw from a normal distribution, and a categorical feature with 20 levels sampled uniformly (stored as integer codes so the forest can split on them directly). High-cardinality noise can still receive non-zero MDI, and often more MDI than low-cardinality noise, while permutation importance on the test set should stay near zero for both. .. GENERATED FROM PYTHON SOURCE LINES 61-66 .. code-block:: Python rng = np.random.default_rng(42) X["noise_cont"] = rng.normal(size=len(X)) X["noise_cat"] = rng.integers(0, 20, size=len(X)) .. GENERATED FROM PYTHON SOURCE LINES 67-68 Let us inspect the feature matrix with :class:`~skrub.TableReport`. .. GENERATED FROM PYTHON SOURCE LINES 68-73 .. code-block:: Python from skrub import TableReport TableReport(X) .. raw:: html

Please enable javascript

The skrub table reports need javascript to display correctly. If you are displaying a report in a Jupyter notebook and you see this message, you may need to re-execute the cell or to trust the notebook (button on the top right or "File > Trust notebook").



.. GENERATED FROM PYTHON SOURCE LINES 74-76 .. code-block:: Python TableReport(y) .. raw:: html

Please enable javascript

The skrub table reports need javascript to display correctly. If you are displaying a report in a Jupyter notebook and you see this message, you may need to re-execute the cell or to trust the notebook (button on the top right or "File > Trust notebook").



.. GENERATED FROM PYTHON SOURCE LINES 77-78 Counting unique values per column previews which features SKD007 will flag. .. GENERATED FROM PYTHON SOURCE LINES 78-81 .. code-block:: Python X.nunique().sort_values(ascending=False) .. rst-class:: sphx-glr-script-out .. code-block:: none noise_cont 1500 AveRooms 1491 AveOccup 1478 AveBedrms 1408 MedInc 1380 Population 1128 Longitude 483 Latitude 458 HouseAge 51 noise_cat 20 dtype: int64 .. GENERATED FROM PYTHON SOURCE LINES 82-87 Trigger SKD007 with a random forest on continuous features ========================================================== A random forest exposes ``feature_importances_`` based on MDI. After fitting, let us inspect impurity decrease with skore. .. GENERATED FROM PYTHON SOURCE LINES 87-101 .. code-block:: Python from sklearn.ensemble import RandomForestRegressor from skore import TrainTestSplit, evaluate splitter = TrainTestSplit(random_state=42) report = evaluate( RandomForestRegressor(random_state=42), X=X, y=y, splitter=splitter, ) report .. raw:: html
RandomForestRegressor(random_state=42)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Please enable javascript

The skrub table reports need javascript to display correctly. If you are displaying a report in a Jupyter notebook and you see this message, you may need to re-execute the cell or to trust the notebook (button on the top right or "File > Trust notebook").



.. GENERATED FROM PYTHON SOURCE LINES 102-105 SKD007 warns about high-cardinality columns such as ``MedInc`` and ``AveOccup``. The synthetic continuous noise column is high-cardinality as well, so it belongs in the same tip. .. GENERATED FROM PYTHON SOURCE LINES 105-108 .. code-block:: Python report.checks.summarize(fast_mode=True) .. raw:: html


.. GENERATED FROM PYTHON SOURCE LINES 109-110 Let us plot MDI with features sorted by importance. .. GENERATED FROM PYTHON SOURCE LINES 110-116 .. code-block:: Python import matplotlib.pyplot as plt mdi_display = report.inspection.impurity_decrease() _ = mdi_display.plot(sorting_order="descending") .. image-sg:: /auto_examples/pitfalls_and_solutions/images/sphx_glr_plot_skd007_mdi_cardinality_bias_001.png :alt: Mean decrease in impurity (MDI) of RandomForestRegressor :srcset: /auto_examples/pitfalls_and_solutions/images/sphx_glr_plot_skd007_mdi_cardinality_bias_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 117-120 The two synthetic noise columns are not near zero under MDI: impurity still assigns them mass. ``noise_cont`` in particular is high-cardinality, so the forest can keep finding splits on it even though it carries no target signal. .. GENERATED FROM PYTHON SOURCE LINES 122-128 Use permutation importance instead of MDI ========================================= :meth:`~skore.EstimatorReport.inspection.permutation_importance` shuffles each column on the test set and measures the score drop. The result is not biased toward high-cardinality split points the way MDI is. .. GENERATED FROM PYTHON SOURCE LINES 128-135 .. code-block:: Python perm_display = report.inspection.permutation_importance( seed=42, n_repeats=5, ) _ = perm_display.plot(sorting_order="descending") .. image-sg:: /auto_examples/pitfalls_and_solutions/images/sphx_glr_plot_skd007_mdi_cardinality_bias_002.png :alt: Permutation importance of RandomForestRegressor on test set :srcset: /auto_examples/pitfalls_and_solutions/images/sphx_glr_plot_skd007_mdi_cardinality_bias_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 136-139 Under permutation importance the noisy features sit at (or very near) zero: shuffling them does not change the test score, so they are not contributing to predicting the target. .. GENERATED FROM PYTHON SOURCE LINES 141-148 Cross-check MDI with permutation importance =========================================== Let us put the two rankings side by side. Sorting by MDI tends to push high-cardinality columns (including ``noise_cont``) upward; permutation importance on the test set should keep both synthetic features near the bottom even when MDI does not. .. GENERATED FROM PYTHON SOURCE LINES 148-172 .. code-block:: Python mdi = ( mdi_display.frame(sorting_order="descending") .set_index("feature") .rename(columns={"importance": "mdi"}) ) perm = ( perm_display.frame(sorting_order="descending") .set_index("feature")[["value_mean"]] .rename(columns={"value_mean": "permutation"}) ) nunique = X.nunique().rename("nunique") comparison = ( mdi.join(perm) .join(nunique) .assign( mdi_rank=lambda df: df["mdi"].rank(ascending=False).astype(int), perm_rank=lambda df: df["permutation"].rank(ascending=False).astype(int), ) .sort_values("mdi", ascending=False) ) comparison .. raw:: html
mdi permutation nunique mdi_rank perm_rank
feature
MedInc 0.522390 1.134807 1380 1 1
AveOccup 0.140019 0.128296 1478 2 2
HouseAge 0.067881 0.050188 51 3 5
Longitude 0.054675 0.082798 483 4 4
Latitude 0.050791 0.128265 458 5 3
AveRooms 0.049150 0.044559 1491 6 6
AveBedrms 0.037725 0.014047 1408 7 7
Population 0.031908 0.011334 1128 8 8
noise_cont 0.028676 0.004603 1500 9 9
noise_cat 0.016786 0.001149 20 10 10


.. GENERATED FROM PYTHON SOURCE LINES 173-176 The side-by-side bars make the disagreement easier to read: impurity can assign mass to ``noise_cont`` (and sometimes more than to ``noise_cat``), while permutation importance stays close to zero for both. .. GENERATED FROM PYTHON SOURCE LINES 176-191 .. code-block:: Python fig, axes = plt.subplots(1, 2, figsize=(12, 5), sharey=True) order = comparison.sort_values("mdi", ascending=True).index axes[0].barh(order, comparison.loc[order, "mdi"]) axes[0].set_title("MDI (impurity decrease)") axes[0].set_xlabel("Importance") axes[1].barh(order, comparison.loc[order, "permutation"]) axes[1].set_title("Permutation importance (test)") axes[1].set_xlabel("Mean score drop") fig.tight_layout() _ = fig .. image-sg:: /auto_examples/pitfalls_and_solutions/images/sphx_glr_plot_skd007_mdi_cardinality_bias_003.png :alt: MDI (impurity decrease), Permutation importance (test) :srcset: /auto_examples/pitfalls_and_solutions/images/sphx_glr_plot_skd007_mdi_cardinality_bias_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 192-200 Conclusion ========== SKD007 warns that MDI feature importance favors high-cardinality inputs such as ``AveOccup`` and can inflate the role of irrelevant high-cardinality noise. In this walkthrough, permutation importance gave a more reliable picture of which features actually move test scores. When importance is a decision factor, we prefer permutation (or drop-column tests) over impurity alone. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 4.958 seconds) .. _sphx_glr_download_auto_examples_pitfalls_and_solutions_plot_skd007_mdi_cardinality_bias.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_skd007_mdi_cardinality_bias.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_skd007_mdi_cardinality_bias.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_skd007_mdi_cardinality_bias.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_