skills/shap/references/migration.md
This guide covers migration to SHAP 0.52.0 and the modern shap.Explanation API.
| SHAP release | Python requirement | Important compatibility note |
|---|---|---|
| 0.52.0 | >=3.12 | Current release; NumPy >=2; native bindings moved to nanobind/scikit-build-core |
| 0.51.0 | >=3.11 | Latest release suitable for Python 3.11 |
| 0.50.0 | >=3.11 | First release after Python 3.9/3.10 support ended |
| 0.49.1 | >=3.9 | Last release line supporting Python 3.9 and 3.10; fixes the broken 0.49.0 publication |
For a new environment:
uv venv --python 3.12
source .venv/bin/activate
uv pip install "shap[plots]==0.52.0"
For a project that cannot move off Python 3.11:
uv pip install "shap[plots]==0.51.0"
For Python 3.9 or 3.10, upgrade Python if possible. If temporarily constrained:
uv pip install "shap[plots]==0.49.1"
Do not claim 0.49.1 behavior is identical to 0.52. Read the release notes and test model-library compatibility.
feature_dependence parameters were removed from TreeExplainer and LinearExplainer.auto_size_plot argument to summary_plot was removed.TreeExplainer(feature_perturbation="auto") became the default behavior: interventional when background data are supplied, tree-path-dependent otherwise.approximate to the TreeExplainer constructor was deprecated; pass it when calling the explainer.Explanation inputs and returned axes.TreeExplainer gained a pandas nullable-dtype fix.Legacy:
values = explainer.shap_values(X)
Modern:
explanation = explainer(X)
values = explanation.values
base_values = explanation.base_values
Keep the Explanation object instead of immediately extracting .values; plots and slicing use its metadata.
Legacy:
base_value = explainer.expected_value
Modern:
base_values = explanation.base_values
base_values can be scalar, per-row, per-output, or per-row/per-output. Inspect shape.
Pre-0.45 code often assumed a list:
positive_values = values[1]
Modern tabular code uses the final output axis:
positive_exp = explanation[..., 1]
positive_values = explanation.values[..., 1]
explanation[1] selects the second sample, not the second class.
For scikit-learn RandomForestClassifier, a typical shape is:
(samples, features, classes)
For XGBoost binary classification with default raw output, a typical shape is:
(samples, features)
Do not hard-code a binary shape across model families.
Legacy:
shap.summary_plot(values, X)
Modern:
shap.plots.beeswarm(explanation)
Legacy:
shap.summary_plot(values, X, plot_type="bar")
Modern:
shap.plots.bar(explanation)
Legacy:
shap.dependence_plot("age", values, X, interaction_index="bmi")
Modern:
shap.plots.scatter(
explanation[:, "age"],
color=explanation[:, "bmi"],
)
Legacy:
shap.force_plot(
explainer.expected_value,
values[row_index],
X.iloc[row_index],
)
Modern:
shap.plots.force(explanation[row_index])
Legacy code often manually built an Explanation from arrays. Modern:
shap.plots.waterfall(explanation[row_index])
Legacy:
shap.image_plot(values, images)
Modern:
shap.plots.image(explanation)
shap.plots.decision remains largely array-oriented:
shap.plots.decision(
base_value,
values,
features=X_display,
feature_names=feature_names,
)
Select one output and compatible base value before calling it.
feature_dependenceRemoved:
shap.TreeExplainer(model, feature_dependence="independent")
Current:
shap.TreeExplainer(
model,
data=background,
feature_perturbation="interventional",
)
Or:
shap.TreeExplainer(
model,
feature_perturbation="tree_path_dependent",
)
These are not mechanical synonyms for every old setting. Reconfirm the intended game and baseline.
Old code could rely on an interventional default. Current "auto" behavior depends on whether data are passed.
Make reproducibility explicit:
explainer = shap.TreeExplainer(
model,
data=background,
feature_perturbation="interventional",
model_output="raw",
)
Deprecated constructor use:
explainer = shap.TreeExplainer(model, approximate=True)
values = explainer(X)
Current:
explainer = shap.TreeExplainer(model)
explanation = explainer(X, approximate=True)
Label approximate values and do not treat them as ordinary Tree SHAP.
Use:
explainer = shap.TreeExplainer(
model,
data=background,
feature_perturbation="interventional",
model_output="probability",
)
explanation = explainer(X)
Probability and log-loss output modes are currently supported only under interventional feature perturbation.
Pre-0.45 code:
values_by_class = explainer.shap_values(X)
class_values = values_by_class[class_index]
Modern one-input, multi-output result:
values = explainer.shap_values(X)
class_values = values[..., class_index]
Multiple model inputs still produce a list, one array per input. ranked_outputs=k returns (values, indexes); preserve the indexes because selected outputs can differ per row.
Do not use a display link to pretend an explanation was computed in another space.
shap.plots.force(raw_margin_exp[row], link="logit")
This labels raw margins as probabilities for display. It is not equivalent to:
shap.TreeExplainer(
model,
data=background,
feature_perturbation="interventional",
model_output="probability",
)(X)
The two additive decompositions can differ.
.shap_values(X) with explainer(X).import importlib.metadata
import platform
packages = [
"shap",
"numpy",
"pandas",
"scikit-learn",
"xgboost",
"lightgbm",
"catboost",
"tensorflow",
"keras",
"torch",
]
print("Python", platform.python_version())
for package in packages:
try:
print(package, importlib.metadata.version(package))
except importlib.metadata.PackageNotFoundError:
pass
Attach this output to reproducible bug reports, without environment variables or credentials.