← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / scientific-agent-skills / skills / shap / references / migration.md

SHAP Migration and Compatibility

This guide covers migration to SHAP 0.52.0 and the modern shap.Explanation API.

Version Baseline

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.

Major Changes Affecting Existing Code

0.45.0

0.46.0

0.47.0

0.49.x

0.50.0–0.51.0

0.52.0

Explanation API Migration

Compute explanations

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.

Base values

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.

Multi-output values

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.

Plot Migration

Summary plot to beeswarm

Legacy:

shap.summary_plot(values, X)

Modern:

shap.plots.beeswarm(explanation)

Summary bar to bar

Legacy:

shap.summary_plot(values, X, plot_type="bar")

Modern:

shap.plots.bar(explanation)

Dependence plot to scatter

Legacy:

shap.dependence_plot("age", values, X, interaction_index="bmi")

Modern:

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

Force plot

Legacy:

shap.force_plot(
    explainer.expected_value,
    values[row_index],
    X.iloc[row_index],
)

Modern:

shap.plots.force(explanation[row_index])

Local waterfall

Legacy code often manually built an Explanation from arrays. Modern:

shap.plots.waterfall(explanation[row_index])

Image plot

Legacy:

shap.image_plot(values, images)

Modern:

shap.plots.image(explanation)

Decision plot

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.

TreeExplainer Migration

feature_dependence

Removed:

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.

Default feature perturbation

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",
)

Approximation

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.

Probability output

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.

DeepExplainer Output Migration

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.

End-to-End Migration Recipe

  1. Pin old and new environments.
  2. Capture model predictions and existing SHAP outputs on a small immutable fixture.
  3. Replace .shap_values(X) with explainer(X).
  4. Print every result shape.
  5. Replace list-based output selection with final-axis slicing.
  6. Replace legacy plotting calls.
  7. Make tree perturbation and output explicit.
  8. Validate additive reconstruction against the exact selected output.
  9. Compare baselines and local values; do not require numerical identity if the game/default changed.
  10. Run model-library compatibility tests, especially for XGBoost, LightGBM, CatBoost, TensorFlow, Keras, and PyTorch.
  11. Update stored report metadata and migration notes.

Compatibility Diagnostic

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.

Sources