plots.md
408 lines · skills/shap/references/plots.md at 5f2bc03
Most marks come from matching words in the file, so treat them as pointers.
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.
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.
Plot Selection
| Question | Plot | Input |
|---|---|---|
| Which features have the greatest average absolute attribution? | bar | multi-row Explanation |
| What are the direction, spread, and observed values of attributions? | beeswarm | multi-row Explanation |
| How does one row move from baseline to output? | waterfall | one-row Explanation |
| How does attribution vary with a feature value? | scatter | one feature column |
| Are there sample-level explanation patterns? | heatmap | multi-row Explanation |
| How do cohorts compare descriptively? | bar | Cohorts or dictionary |
| Which tokens or regions contribute? | text, image | modality-specific Explanation |
| Is an interactive additive layout useful? | force | one or multiple rows |
| How do cumulative paths differ? | decision | arrays 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 49Global 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 65Cohort 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 88Feature 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.
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: defaultexplanation.abs.mean(0);clusteringandcluster_threshold;ax(setplot_size=Nonewhen supplying an existing axis);log_scale;color_bar;sfor 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 153Waterfall 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 177Scatter 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 218Heatmap
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 241Violin 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.
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 284Decision 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 301Text 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 316Image 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 326Group 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.
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 355Publication 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 (
.svgor.pdf) when journal workflows allow. - Do not expose identifiers or sensitive raw values in local plot labels.
Common Failures
Raw lines 367 to 368"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 383Missing feature names
Use a DataFrame through preprocessing where possible, or pass feature_names when creating the explainer. Verify order before assigning names manually.
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.
Figures overlap in loops
Use show=False, save the returned axis/figure, and close it every iteration.
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 399Sources
- 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