skills/shap/references/plots.md
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.
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[..., "class_name"]
Most tabular plots expect either:
(features,); or(samples, features).Passing (samples, features, outputs) without selecting an output is a common source of misleading or failed plots.
| 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.
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.
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.
clustering = shap.utils.hclust(X_background, y_background)
ax = shap.plots.bar(
explanation,
clustering=clustering,
clustering_cutoff=0.5,
show=False,
)
hclust(X, y) uses outcome-redundancy information and can be expensive. Do not compute it using held-out labels if that would contaminate the analysis.
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;log_scale;color_bar;s for marker size;plot_size;group_remaining_features.Interpretation:
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.
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:
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.
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:
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,
)
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:
Use a subset for readability, but choose it before viewing explanations or document the selection rule.
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.
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.
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.
shap.plots.text(text_explanation)
For multi-output text:
class_text = text_explanation[..., "anger"]
shap.plots.text(class_text)
Token merging and tokenizer special tokens affect presentation. Preserve the tokenizer and masker configuration.
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.
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.
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.
.svg or .pdf) when journal workflows allow.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])
Use a DataFrame through preprocessing where possible, or pass feature_names when creating the explainer. Verify order before assigning names manually.
Color requires usable feature data in explanation.data or display_data. An explanation built from values alone cannot recover original feature values.
Use show=False, save the returned axis/figure, and close it every iteration.
Check output selection and units. For example, raw XGBoost margins are not probabilities. Reconstruct the exact explained output before changing plot settings.