Skip to content

Latest commit

 

History

History
415 lines (291 loc) · 8.97 KB

File metadata and controls

415 lines (291 loc) · 8.97 KB

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

  • Python 3.8 support ended.
  • Multi-output SHAP values changed from a list to a NumPy array with the output dimension last.
  • Deprecated feature_dependence parameters were removed from TreeExplainer and LinearExplainer.
  • Python 3.12 support was added.

0.46.0

  • NumPy 2, Keras 3, and TensorFlow 2.16 support was added.
  • The deprecated auto_size_plot argument to summary_plot was removed.

0.47.0

  • TreeExplainer(feature_perturbation="auto") became the default behavior: interventional when background data are supplied, tree-path-dependent otherwise.
  • Passing approximate to the TreeExplainer constructor was deprecated; pass it when calling the explainer.
  • Plotting APIs continued moving toward Explanation inputs and returned axes.
  • Legacy bar plotting gained deprecation guidance.

0.49.x

  • 0.49.1 repaired the 0.49.0 release publication.
  • 0.49.x was the final line supporting Python 3.9 and 3.10.
  • C++ categorical-split support expanded.

0.50.0–0.51.0

  • Python 3.11 became the minimum.
  • Type coverage and tree/path-dependent behavior continued to improve.

0.52.0

  • Python 3.12 became the minimum.
  • NumPy 2 became a core minimum requirement.
  • Native bindings moved from Cython/setup.py to nanobind with scikit-build-core and CMake.
  • Tree/GPU fixes improved missing-value routing, vector-valued XGBoost base scores, and multiclass additivity.
  • TreeExplainer gained a pandas nullable-dtype fix.
  • Plot and documentation examples continued moving to the modern API.

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.

Link and Output Migration

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