Synthèse SHAP (essaim)

La valeur SHAP de chaque district scolaire pour chaque caractéristique, colorée selon la valeur de la caractéristique, avec l'importance moyenne |SHAP|.

Synthèse SHAP (essaim)

SHAP (SHapley Additive exPlanations ; Lundberg et Lee, 2017) décompose chaque prédiction en contributions additives, une par variable, de sorte que valeur de base + somme des contributions = prédiction. Les contributions sont des valeurs de Shapley issues de la théorie des jeux coopératifs : l'effet marginal moyen de chaque variable sur tous les ordres dans lesquels les variables pourraient être ajoutées.

L'essaim montre un point par district scolaire et par variable. La position est la valeur SHAP — de combien cette variable a poussé la prédiction de ce district vers le haut ou vers le bas — et la couleur est la valeur de la variable elle-même, de faible (bleu) à élevée (rouge). Les variables sont triées par moyenne |SHAP|, affichée à droite comme importance globale. Une séparation nette, rouge à droite et bleu à gauche, signale un effet monotone ; des couleurs mêlées signalent une non-linéarité ou des interactions.

Les données sont les 303 districts scolaires californiens du test STAR de 1998, fournis avec statsmodels (sm.datasets.star98) ; la variable à prédire est la part des élèves au-dessus de la médiane nationale en mathématiques, en points de pourcentage, à partir de dix caractéristiques des districts. Le modèle est un gradient boosting et les valeurs SHAP exactes pour arbres viennent de shap.TreeExplainer. La figure est dessinée avec Matplotlib pour respecter le style de la galerie ; shap.plots.beeswarm donne une vue semblable en une ligne.

Le code et le texte à l'intérieur des figures sont en anglais.

shap-beeswarm.py · 54 lignes
Télécharger GitHub
import matplotlib.pyplot as plt
import numpy as np
import shap
import statsmodels.api as sm
from sklearn.ensemble import GradientBoostingRegressor

# Data: 303 California school districts; outcome = % of students above the national median in maths
d = sm.datasets.star98.load_pandas().data
y = 100 * d["NABOVE"] / (d["NABOVE"] + d["NBELOW"])
names = {"LOWINC": "% low-income students", "PERASIAN": "% Asian students", "PERBLACK": "% Black students",
         "PERHISP": "% Hispanic students", "AVYRSEXP": "Teacher experience (years)",
         "AVSALK": "Teacher salary ($000)", "PERSPENK": "Spending per pupil ($000)",
         "PTRATIO": "Pupils per teacher", "PCTAF": "% in college-prep courses",
         "PCTYRRND": "% year-round schools"}
X = d[list(names)]
model = GradientBoostingRegressor(learning_rate=0.05, n_estimators=300, max_depth=2,
                                  subsample=0.8, random_state=0).fit(X, y)

# Exact SHAP values for tree ensembles
sv = shap.TreeExplainer(model).shap_values(X)          # shape: (districts, features)
importance = np.abs(sv).mean(axis=0)
order = np.argsort(importance)                          # least important at the bottom

fig, ax = plt.subplots(figsize=(8, 5.6))
rng = np.random.default_rng(0)
cmap = plt.get_cmap("coolwarm")
for i, j in enumerate(order):
    v = sv[:, j]
    xv = X.iloc[:, j].rank(pct=True).to_numpy()         # colour = percentile of the feature value
    # Beeswarm-style vertical jitter: wider where the points are denser
    hist, edges = np.histogram(v, bins=60)
    dens = hist[np.clip(np.digitize(v, edges) - 1, 0, len(hist) - 1)] / hist.max()
    jitter = rng.uniform(-1, 1, len(v)) * 0.38 * np.sqrt(dens)
    ax.scatter(v, i + jitter, c=xv, cmap=cmap, s=9, alpha=0.85, lw=0, vmin=0, vmax=1)
    ax.text(1.01, i, f"{importance[j]:.1f}", transform=ax.get_yaxis_transform(), va="center",
            fontsize=9.5, color="#3A3A3C")
ax.axvline(0, color="#3A3A3C", lw=0.8)
ax.set_yticks(range(len(order)), [names[X.columns[j]] for j in order])
ax.set_xlabel("SHAP value (percentage points)")
ax.grid(axis="x"); ax.grid(axis="y", visible=False)
ax.text(1.01, len(order) - 0.35, "mean\n|SHAP|", transform=ax.get_yaxis_transform(),
        fontsize=8.5, color="#6E6E73", va="bottom")

cb = fig.colorbar(plt.cm.ScalarMappable(cmap=cmap), ax=ax, pad=0.09, aspect=40, ticks=[0, 1])
cb.ax.set_yticklabels(["low", "high"])
cb.set_label("Feature value (percentile)", fontsize=9.5)
cb.outline.set_visible(False)

low = sv[:, list(X.columns).index("LOWINC")]
ax.set_title(f"The share of low-income students moves predictions from {low.min():+.0f} to {low.max():+.0f} points", pad=26)
ax.text(0, 1.02, f"Gradient boosting · exact tree SHAP · n = {len(X)} districts · "
        f"base value = {model.predict(X).mean():.0f}%", transform=ax.transAxes, fontsize=9.5,
        color="#6E6E73")
plt.show()

Les figures utilisent le style PyStatlas (facultatif) : ajoutez plt.style.use("pystatlas.mplstyle") pour obtenir exactement le même rendu.

Source des données

  • California STAR test results, 1998 (303 school districts). Gill, J. (2001). Generalized Linear Models: A Unified Approach (Quantitative Applications in the Social Sciences, No. 134). Sage. https://www.statsmodels.org/stable/datasets/generated/star98.html
    Original data from the California Department of Education's Standardized Testing and Reporting (STAR) program, 1998. Subset distributed with statsmodels (sm.datasets.star98) with the author's permission.

Les exemples illustrent des méthodes statistiques. Leurs titres décrivent ce que chaque figure montre dans ces données précises — souvent un échantillon restreint, daté ou partiel —, et non la position de l'auteur sur la question de fond. Pour l'interprétation, le contexte et la qualité des données, reportez-vous aux sources originales citées ici.

Comment citer

PyStatlas est créé et maintenu par Thierry Warin. S'il vous a été utile dans vos travaux, merci de le citer.

Warin, T. (2026). SHAP summary (beeswarm). In PyStatlas: A Python Gallery of Statistical Graphics for the Social Sciences. https://warint.github.io/pystatlas/machine-learning/shap-beeswarm/

@misc{warin2026statlas,
  author       = {Warin, Thierry},
  title        = {PyStatlas: A Python Gallery of Statistical Graphics for the Social Sciences},
  year         = {2026},
  howpublished = {\url{https://warint.github.io/pystatlas/}},
  note         = {Source code: https://github.com/warint/pystatlas}
}

Plus dans Apprentissage automatique