Skill Tracer
Skills › Scientific Agent Skills › shap › plots.md

plots.md

408 lines · skills/shap/references/plots.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# SHAP Plotting
2
3This reference uses the SHAP 0.52 plotting API. Prefer `shap.plots.*` functions that consume `shap.Explanation` objects. Legacy functions such as `summary_plot`, `dependence_plot`, and top-level `force_plot` remain relevant to old code but lose metadata and should not anchor a new workflow.
4

SHAP Plotting

This reference uses the SHAP 0.52 plotting API. Prefer shap.plots.* functions that consume shap.Explanation objects. Legacy functions such as summary_plot, dependence_plot, and top-level force_plot remain relevant to old code but lose metadata and should not anchor a new workflow.

Raw lines 1 to 4
5## Validate Before Plotting
6
7For every plot:
8
9```python
10print("values:", explanation.values.shape)
11print("base_values:", np.shape(explanation.base_values))
12print("data:", np.shape(explanation.data))
13print("features:", explanation.feature_names)
14print("outputs:", explanation.output_names)
15```
17For multi-output tabular data, select one output:
19```python
20class_exp = explanation[..., class_index]
21# or, when output names are populated:
22class_exp = explanation[..., list(explanation.output_names).index("class_name")]
23```
25Verify output names against the model first; 0.52.0 can discard names in permutation dispatch. Use integer output selection to avoid the ellipsis/string slicing bug.
27Most tabular plots expect either:
29- one local row: `(features,)`; or
30- many rows: `(samples, features)`.
32Passing `(samples, features, outputs)` without selecting an output is a common source of misleading or failed plots.

Validate Before Plotting

For every plot:

print("values:", explanation.values.shape)
print("base_values:", np.shape(explanation.base_values))
print("data:", np.shape(explanation.data))
print("features:", explanation.feature_names)
print("outputs:", explanation.output_names)

For multi-output tabular data, select one output:

class_exp = explanation[..., class_index]
# or, when output names are populated:
class_exp = explanation[..., list(explanation.output_names).index("class_name")]

Verify output names against the model first; 0.52.0 can discard names in permutation dispatch. Use integer output selection to avoid the ellipsis/string slicing bug.

Most tabular plots expect either:

  • one local row: (features,); or
  • many rows: (samples, features).

Passing (samples, features, outputs) without selecting an output is a common source of misleading or failed plots.

Raw lines 5 to 33
Starts an agent dispatch :25
34## Plot Selection
36| Question | Plot | Input |
37|---|---|---|
38| Which features have the greatest average absolute attribution? | `bar` | multi-row `Explanation` |
39| What are the direction, spread, and observed values of attributions? | `beeswarm` | multi-row `Explanation` |
40| How does one row move from baseline to output? | `waterfall` | one-row `Explanation` |
41| How does attribution vary with a feature value? | `scatter` | one feature column |
42| Are there sample-level explanation patterns? | `heatmap` | multi-row `Explanation` |
43| How do cohorts compare descriptively? | `bar` | `Cohorts` or dictionary |
44| Which tokens or regions contribute? | `text`, `image` | modality-specific `Explanation` |
45| Is an interactive additive layout useful? | `force` | one or multiple rows |
46| How do cumulative paths differ? | `decision` | arrays plus base value |
48No plot proves causality or fairness. A plot is an aggregation of attributions under a model, output, background, and masker.

Plot Selection

QuestionPlotInput
Which features have the greatest average absolute attribution?barmulti-row Explanation
What are the direction, spread, and observed values of attributions?beeswarmmulti-row Explanation
How does one row move from baseline to output?waterfallone-row Explanation
How does attribution vary with a feature value?scatterone feature column
Are there sample-level explanation patterns?heatmapmulti-row Explanation
How do cohorts compare descriptively?barCohorts or dictionary
Which tokens or regions contribute?text, imagemodality-specific Explanation
Is an interactive additive layout useful?forceone or multiple rows
How do cumulative paths differ?decisionarrays plus base value

No plot proves causality or fairness. A plot is an aggregation of attributions under a model, output, background, and masker.

Raw lines 34 to 49
50## Global Bar Plot
52```python
53ax = shap.plots.bar(
54 explanation,
55 max_display=20,
56 show=False,
58ax.set_title("Mean absolute SHAP value")
59ax.figure.tight_layout()
60ax.figure.savefig("shap-bar.png", dpi=200, bbox_inches="tight")
61plt.close(ax.figure)
62```
64A multi-row explanation is aggregated by mean absolute attribution. A single row creates a local bar plot.

Global Bar Plot

ax = shap.plots.bar(
    explanation,
    max_display=20,
    show=False,
)
ax.set_title("Mean absolute SHAP value")
ax.figure.tight_layout()
ax.figure.savefig("shap-bar.png", dpi=200, bbox_inches="tight")
plt.close(ax.figure)

A multi-row explanation is aggregated by mean absolute attribution. A single row creates a local bar plot.

Raw lines 50 to 65
66### Cohort bar plot
68```python
69cohorts = explanation.cohorts(group_labels)
70ax = shap.plots.bar(cohorts.abs.mean(0), max_display=15, show=False)
71ax.figure.savefig("shap-cohorts.png", dpi=200, bbox_inches="tight")
72plt.close(ax.figure)
73```
75Alternatively:
77```python
78ax = shap.plots.bar(
79 {
80 "Group A": explanation[group_labels == "A"],
81 "Group B": explanation[group_labels == "B"],
82 },
83 show=False,
85```
87Use one shared explainer and background for direct cohort comparison. Include sample counts and uncertainty; different mean magnitudes can reflect feature distributions, performance, or model behavior.

Cohort bar plot

cohorts = explanation.cohorts(group_labels)
ax = shap.plots.bar(cohorts.abs.mean(0), max_display=15, show=False)
ax.figure.savefig("shap-cohorts.png", dpi=200, bbox_inches="tight")
plt.close(ax.figure)

Alternatively:

ax = shap.plots.bar(
    {
        "Group A": explanation[group_labels == "A"],
        "Group B": explanation[group_labels == "B"],
    },
    show=False,
)

Use one shared explainer and background for direct cohort comparison. Include sample counts and uncertainty; different mean magnitudes can reflect feature distributions, performance, or model behavior.

Raw lines 66 to 88
89### Feature clustering
91```python
92clustering = shap.utils.hclust(X_background, y_background)
93ax = shap.plots.bar(
94 explanation,
95 clustering=clustering,
96 clustering_cutoff=0.5,
97 show=False,
99```
101`hclust(X, y)` defaults to an XGBoost-based outcome-redundancy metric and requires the optional `xgboost` package. This fragment is illustrative unless that integration is installed and exercised. For unsupervised feature clustering, use an explicit distance such as `shap.utils.hclust(X_background, metric="correlation")`; constant columns require care. Outcome-aware clustering can be expensive. Do not compute it using held-out labels if that would contaminate the analysis.

Feature clustering

clustering = shap.utils.hclust(X_background, y_background)
ax = shap.plots.bar(
    explanation,
    clustering=clustering,
    clustering_cutoff=0.5,
    show=False,
)

hclust(X, y) defaults to an XGBoost-based outcome-redundancy metric and requires the optional xgboost package. This fragment is illustrative unless that integration is installed and exercised. For unsupervised feature clustering, use an explicit distance such as shap.utils.hclust(X_background, metric="correlation"); constant columns require care. Outcome-aware clustering can be expensive. Do not compute it using held-out labels if that would contaminate the analysis.

Raw lines 89 to 102
103## Beeswarm Plot
105```python
106ax = shap.plots.beeswarm(
107 explanation,
108 max_display=20,
109 alpha=0.7,
110 s=12,
111 group_remaining_features=True,
112 show=False,
114ax.figure.savefig("shap-beeswarm.png", dpi=200, bbox_inches="tight")
115plt.close(ax.figure)
116```
118Current 0.52 controls include:
120- `order`: default `explanation.abs.mean(0)`;
121- `clustering` and `cluster_threshold`;
122- `ax` (set `plot_size=None` when supplying an existing axis);
123- `log_scale`;
124- `color_bar`;
125- `s` for marker size;
126- `plot_size`;
127- `group_remaining_features`.
129Interpretation:
131- horizontal position: signed attribution in the explained output's units;
132- color: observed feature value when numeric display data are available;
133- vertical density/jitter: observations, not uncertainty;
134- row order: an aggregation rule, not causal importance.
136Useful alternatives:
138```python
139# Highlight features with rare large effects.
140shap.plots.beeswarm(
141 explanation,
142 order=explanation.abs.max(0),
145# Absolute magnitude only; direction is intentionally removed.
146shap.plots.beeswarm(
147 explanation.abs,
148 color="shap_red",
150```
152Do not interpret red as "risk" or blue as "protective" without first defining output and units.

Beeswarm Plot

ax = shap.plots.beeswarm(
    explanation,
    max_display=20,
    alpha=0.7,
    s=12,
    group_remaining_features=True,
    show=False,
)
ax.figure.savefig("shap-beeswarm.png", dpi=200, bbox_inches="tight")
plt.close(ax.figure)

Current 0.52 controls include:

  • order: default explanation.abs.mean(0);
  • clustering and cluster_threshold;
  • ax (set plot_size=None when supplying an existing axis);
  • log_scale;
  • color_bar;
  • s for marker size;
  • plot_size;
  • group_remaining_features.

Interpretation:

  • horizontal position: signed attribution in the explained output's units;
  • color: observed feature value when numeric display data are available;
  • vertical density/jitter: observations, not uncertainty;
  • row order: an aggregation rule, not causal importance.

Useful alternatives:

# Highlight features with rare large effects.
shap.plots.beeswarm(
    explanation,
    order=explanation.abs.max(0),
)

# Absolute magnitude only; direction is intentionally removed.
shap.plots.beeswarm(
    explanation.abs,
    color="shap_red",
)

Do not interpret red as "risk" or blue as "protective" without first defining output and units.

Raw lines 103 to 153
154## Waterfall Plot
156```python
157ax = shap.plots.waterfall(
158 explanation[row_index],
159 max_display=15,
160 show=False,
162ax.figure.savefig("shap-waterfall.png", dpi=200, bbox_inches="tight")
163plt.close(ax.figure)
164```
166Input must be one-dimensional. A waterfall shows how values move from the selected background's base value to the selected output for one row.
168Report:
170- why the row was selected;
171- baseline population and value;
172- output name and units;
173- final model output;
174- whether omitted features were grouped.
176Never describe the arrows as what would happen if a person changed a feature. They decompose a predictive output; they are not recourse or intervention estimates.

Waterfall Plot

ax = shap.plots.waterfall(
    explanation[row_index],
    max_display=15,
    show=False,
)
ax.figure.savefig("shap-waterfall.png", dpi=200, bbox_inches="tight")
plt.close(ax.figure)

Input must be one-dimensional. A waterfall shows how values move from the selected background's base value to the selected output for one row.

Report:

  • why the row was selected;
  • baseline population and value;
  • output name and units;
  • final model output;
  • whether omitted features were grouped.

Never describe the arrows as what would happen if a person changed a feature. They decompose a predictive output; they are not recourse or intervention estimates.

Raw lines 154 to 177
178## Scatter Plot
180```python
181ax = shap.plots.scatter(
182 explanation[:, "age"],
183 color=explanation[:, "bmi"],
184 alpha=0.5,
185 x_jitter="auto",
186 show=False,
188ax.figure.savefig("shap-age-scatter.png", dpi=200, bbox_inches="tight")
189plt.close(ax.figure)
190```
192If `color=explanation` is passed, SHAP selects a likely interaction color automatically:
194```python
195shap.plots.scatter(explanation[:, "age"], color=explanation)
196```
198Vertical spread at one x-value can arise from:
200- interactions;
201- correlated features;
202- heterogeneous subgroups;
203- approximation noise;
204- model discontinuities.
206It does not by itself prove an interaction or causal effect.
208Current scatter features include categorical support, automatic jitter, percentile axis limits, a histogram, one-feature `ax` support, and overlay curves:
210```python
211shap.plots.scatter(
212 explanation[:, "age"],
213 xmin="percentile(1)",
214 xmax="percentile(99)",
215 hist=True,
217```

Scatter Plot

ax = shap.plots.scatter(
    explanation[:, "age"],
    color=explanation[:, "bmi"],
    alpha=0.5,
    x_jitter="auto",
    show=False,
)
ax.figure.savefig("shap-age-scatter.png", dpi=200, bbox_inches="tight")
plt.close(ax.figure)

If color=explanation is passed, SHAP selects a likely interaction color automatically:

shap.plots.scatter(explanation[:, "age"], color=explanation)

Vertical spread at one x-value can arise from:

  • interactions;
  • correlated features;
  • heterogeneous subgroups;
  • approximation noise;
  • model discontinuities.

It does not by itself prove an interaction or causal effect.

Current scatter features include categorical support, automatic jitter, percentile axis limits, a histogram, one-feature ax support, and overlay curves:

shap.plots.scatter(
    explanation[:, "age"],
    xmin="percentile(1)",
    xmax="percentile(99)",
    hist=True,
)
Raw lines 178 to 218
219## Heatmap
221```python
222subset = explanation[:200]
223ax = shap.plots.heatmap(
224 subset,
225 max_display=20,
226 instance_order=subset.sum(1),
227 show=False,
229ax.figure.savefig("shap-heatmap.png", dpi=200, bbox_inches="tight")
230plt.close(ax.figure)
231```
233Heatmaps are useful for sample-level patterns but can invite over-interpretation of visual clusters. Record:
235- row sampling and ordering;
236- any clustering method;
237- output and baseline;
238- whether colors share a fixed scale across panels.
240Use a subset for readability, but choose it before viewing explanations or document the selection rule.

Heatmap

subset = explanation[:200]
ax = shap.plots.heatmap(
    subset,
    max_display=20,
    instance_order=subset.sum(1),
    show=False,
)
ax.figure.savefig("shap-heatmap.png", dpi=200, bbox_inches="tight")
plt.close(ax.figure)

Heatmaps are useful for sample-level patterns but can invite over-interpretation of visual clusters. Record:

  • row sampling and ordering;
  • any clustering method;
  • output and baseline;
  • whether colors share a fixed scale across panels.

Use a subset for readability, but choose it before viewing explanations or document the selection rule.

Raw lines 219 to 241
242## Violin Plot
244```python
245shap.plots.violin(
246 explanation.values,
247 features=explanation.data,
248 feature_names=explanation.feature_names,
249 max_display=20,
251```
253The violin API retains array-oriented parameters. Prefer beeswarm for the richest modern `Explanation` workflow; use violin when density is more important than individual points.

Violin Plot

shap.plots.violin(
    explanation.values,
    features=explanation.data,
    feature_names=explanation.feature_names,
    max_display=20,
)

The violin API retains array-oriented parameters. Prefer beeswarm for the richest modern Explanation workflow; use violin when density is more important than individual points.

Raw lines 242 to 254
255## Force Plot
257The current API recommends passing an `Explanation`:
259```python
260shap.plots.force(explanation[row_index])
261```
263This returns a JavaScript visualization by default. Initialize notebook JavaScript if required:
265```python
266shap.plots.initjs()
267```
269For a static local plot:
271```python
272shap.plots.force(
273 explanation[row_index],
274 matplotlib=True,
275 show=False,
277plt.gcf().savefig("shap-force.png", dpi=200, bbox_inches="tight")
278plt.close(plt.gcf())
279```
281Use `link="logit"` only as a display transform when the additive values are in log-odds. It does not recompute probability-space SHAP values.
283Stacked force plots can accept multiple rows but become hard to audit. Prefer heatmaps or cohort summaries for large samples.

Force Plot

The current API recommends passing an Explanation:

shap.plots.force(explanation[row_index])

This returns a JavaScript visualization by default. Initialize notebook JavaScript if required:

shap.plots.initjs()

For a static local plot:

shap.plots.force(
    explanation[row_index],
    matplotlib=True,
    show=False,
)
plt.gcf().savefig("shap-force.png", dpi=200, bbox_inches="tight")
plt.close(plt.gcf())

Use link="logit" only as a display transform when the additive values are in log-odds. It does not recompute probability-space SHAP values.

Stacked force plots can accept multiple rows but become hard to audit. Prefer heatmaps or cohort summaries for large samples.

Raw lines 255 to 284
285## Decision Plot
287Decision plots retain an array-oriented API:
289```python
290shap.plots.decision(
291 base_value,
292 values,
293 features=X_display,
294 feature_names=feature_names,
295 highlight=highlight_rows,
296 show=False,
298```
300Before plotting, select one output and ensure a compatible scalar base value. Use decision plots for cumulative path comparison, not as a default multiclass plot.

Decision Plot

Decision plots retain an array-oriented API:

shap.plots.decision(
    base_value,
    values,
    features=X_display,
    feature_names=feature_names,
    highlight=highlight_rows,
    show=False,
)

Before plotting, select one output and ensure a compatible scalar base value. Use decision plots for cumulative path comparison, not as a default multiclass plot.

Raw lines 285 to 301
302## Text Plot
304```python
305shap.plots.text(text_explanation)
306```
308For multi-output text:
310```python
311class_text = text_explanation[..., list(text_explanation.output_names).index("anger")]
312shap.plots.text(class_text)
313```
315Token merging and tokenizer special tokens affect presentation. Preserve the tokenizer and masker configuration.

Text Plot

shap.plots.text(text_explanation)

For multi-output text:

class_text = text_explanation[..., list(text_explanation.output_names).index("anger")]
shap.plots.text(class_text)

Token merging and tokenizer special tokens affect presentation. Preserve the tokenizer and masker configuration.

Raw lines 302 to 316
317## Image Plot
319```python
320shap.plots.image(image_explanation)
321```
323Legacy examples may use `shap.image_plot`; prefer `shap.plots.image` for current code. Select only the outputs that were actually explained and label them explicitly.
325Image overlays show attribution under a masking method; they are not object localization or segmentation ground truth.

Image Plot

shap.plots.image(image_explanation)

Legacy examples may use shap.image_plot; prefer shap.plots.image for current code. Select only the outputs that were actually explained and label them explicitly.

Image overlays show attribution under a masking method; they are not object localization or segmentation ground truth.

Raw lines 317 to 326
327## Group Difference and Monitoring
329`shap.plots.group_difference` plots differences in mean SHAP values between two groups. It is descriptive and should be paired with confidence intervals or resampling when decisions depend on the difference.
331`shap.plots.monitoring` visualizes attribution behavior over an ordered axis. For production drift, store numeric summaries and statistical checks as the source of truth; use the plot as a diagnostic.

Group Difference and Monitoring

shap.plots.group_difference plots differences in mean SHAP values between two groups. It is descriptive and should be paired with confidence intervals or resampling when decisions depend on the difference.

shap.plots.monitoring visualizes attribution behavior over an ordered axis. For production drift, store numeric summaries and statistical checks as the source of truth; use the plot as a diagnostic.

Raw lines 327 to 332
333## Saving Figures Reliably
335Most modern matplotlib-backed plots return an `Axes` when `show=False`:
337```python
338def save_axes(ax, path):
339 figure = ax.figure
340 figure.tight_layout()
341 figure.savefig(path, dpi=200, bbox_inches="tight")
342 plt.close(figure)
343```
345If a plot does not return an axis, capture the current figure immediately:
347```python
348shap.plots.heatmap(explanation, show=False)
349figure = plt.gcf()
350figure.savefig("plot.png", dpi=200, bbox_inches="tight")
351plt.close(figure)
352```
354Do not call another plotting function before capturing the figure.

Saving Figures Reliably

Most modern matplotlib-backed plots return an Axes when show=False:

def save_axes(ax, path):
    figure = ax.figure
    figure.tight_layout()
    figure.savefig(path, dpi=200, bbox_inches="tight")
    plt.close(figure)

If a plot does not return an axis, capture the current figure immediately:

shap.plots.heatmap(explanation, show=False)
figure = plt.gcf()
figure.savefig("plot.png", dpi=200, bbox_inches="tight")
plt.close(figure)

Do not call another plotting function before capturing the figure.

Raw lines 333 to 355
356## Publication and Accessibility
358- Put output units in the x-axis label or caption.
359- Define the baseline population in the caption.
360- Include sample size and aggregation.
361- Use direct labels in addition to color.
362- Avoid red/green-only encodings.
363- Keep consistent axis limits for panels intended for comparison.
364- Export vector formats (`.svg` or `.pdf`) when journal workflows allow.
365- Do not expose identifiers or sensitive raw values in local plot labels.

Publication and Accessibility

  • Put output units in the x-axis label or caption.
  • Define the baseline population in the caption.
  • Include sample size and aggregation.
  • Use direct labels in addition to color.
  • Avoid red/green-only encodings.
  • Keep consistent axis limits for panels intended for comparison.
  • Export vector formats (.svg or .pdf) when journal workflows allow.
  • Do not expose identifiers or sensitive raw values in local plot labels.
Raw lines 356 to 366
367## Common Failures

Common Failures

Raw lines 367 to 368
369### "Waterfall requires a scalar explanation"
371Select one row and one output:
373```python
374shap.plots.waterfall(explanation[row_index, ..., output_index])
375```
377For tabular data, the clearer sequence is:
379```python
380class_exp = explanation[..., output_index]
381shap.plots.waterfall(class_exp[row_index])
382```

"Waterfall requires a scalar explanation"

Select one row and one output:

shap.plots.waterfall(explanation[row_index, ..., output_index])

For tabular data, the clearer sequence is:

class_exp = explanation[..., output_index]
shap.plots.waterfall(class_exp[row_index])
Raw lines 369 to 383
384### Missing feature names
386Use a DataFrame through preprocessing where possible, or pass `feature_names` when creating the explainer. Verify order before assigning names manually.

Missing feature names

Use a DataFrame through preprocessing where possible, or pass feature_names when creating the explainer. Verify order before assigning names manually.

Raw lines 384 to 387
388### No colors in beeswarm
390Color requires usable feature data in `explanation.data` or `display_data`. An explanation built from values alone cannot recover original feature values.

No colors in beeswarm

Color requires usable feature data in explanation.data or display_data. An explanation built from values alone cannot recover original feature values.

Raw lines 388 to 391
392### Figures overlap in loops
394Use `show=False`, save the returned axis/figure, and close it every iteration.

Figures overlap in loops

Use show=False, save the returned axis/figure, and close it every iteration.

Raw lines 392 to 395
396### Plot and prediction disagree
398Check output selection and units. For example, raw XGBoost margins are not probabilities. Reconstruct the exact explained output before changing plot settings.

Plot and prediction disagree

Check output selection and units. For example, raw XGBoost margins are not probabilities. Reconstruct the exact explained output before changing plot settings.

Raw lines 396 to 399
400## Sources
402- Plot API: https://shap.readthedocs.io/en/latest/api.html#plots
403- Beeswarm: https://shap.readthedocs.io/en/latest/generated/shap.plots.beeswarm.html
404- Bar: https://shap.readthedocs.io/en/latest/generated/shap.plots.bar.html
405- Waterfall: https://shap.readthedocs.io/en/latest/generated/shap.plots.waterfall.html
406- Scatter: https://shap.readthedocs.io/en/latest/generated/shap.plots.scatter.html
407- Force: https://shap.readthedocs.io/en/latest/generated/shap.plots.force.html
408- Plot examples: https://shap.readthedocs.io/en/latest/api_examples.html#plots

Sources

  • Plot API: https://shap.readthedocs.io/en/latest/api.html#plots
  • Beeswarm: https://shap.readthedocs.io/en/latest/generated/shap.plots.beeswarm.html
  • Bar: https://shap.readthedocs.io/en/latest/generated/shap.plots.bar.html
  • Waterfall: https://shap.readthedocs.io/en/latest/generated/shap.plots.waterfall.html
  • Scatter: https://shap.readthedocs.io/en/latest/generated/shap.plots.scatter.html
  • Force: https://shap.readthedocs.io/en/latest/generated/shap.plots.force.html
  • Plot examples: https://shap.readthedocs.io/en/latest/api_examples.html#plots
Raw lines 400 to 408