How to Use SHAP Values in Python

Model interpretability isn't optional anymore. Regulators demand it, stakeholders expect it, and your debugging process depends on it. SHAP (SHapley Additive exPlanations) has become the gold...

Key Insights

  • SHAP values provide model-agnostic explanations by calculating each feature’s contribution to individual predictions using game theory principles, making black-box models interpretable for stakeholders and regulators.
  • TreeExplainer offers exact, fast computations for tree-based models, while KernelExplainer works with any model type but requires more computation time and careful background data selection.
  • SHAP visualizations reveal not just which features matter most, but how they interact and affect predictions differently across your dataset—critical for debugging models and building trust in production systems.

Introduction to SHAP and Model Interpretability

Model interpretability isn’t optional anymore. Regulators demand it, stakeholders expect it, and your debugging process depends on it. SHAP (SHapley Additive exPlanations) has become the gold standard for explaining machine learning predictions because it’s grounded in solid mathematical theory and works across model types.

SHAP values come from cooperative game theory, specifically Shapley values. The core idea: imagine each feature as a player in a game where the payout is your model’s prediction. SHAP calculates how much each feature contributes to moving the prediction away from the baseline (average) prediction. Unlike simple feature importance, SHAP tells you the direction and magnitude of each feature’s impact on specific predictions.

Use SHAP when you need to explain individual predictions to users, debug unexpected model behavior, satisfy regulatory requirements, or identify which features drive predictions in production. It’s particularly valuable for high-stakes applications like credit scoring, medical diagnosis, or fraud detection where you need to justify decisions.

Setting Up SHAP in Python

Install SHAP with pip. The library integrates seamlessly with scikit-learn, XGBoost, LightGBM, and most Python ML frameworks.

pip install shap

Here’s your standard import block:

import shap
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
import matplotlib.pyplot as plt

SHAP provides different explainers optimized for specific model types. TreeExplainer works with tree-based models (Random Forests, XGBoost, LightGBM) and delivers exact SHAP values quickly. KernelExplainer is model-agnostic but slower—it approximates SHAP values through sampling. DeepExplainer handles neural networks. LinearExplainer works with linear models. Choose the right explainer for your model type to balance accuracy and computation time.

Training a Model for SHAP Analysis

Let’s build a regression model to predict California housing prices. This dataset has meaningful features that demonstrate SHAP’s explanatory power well.

# Load data
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = housing.target

# Split data
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Train model
model = RandomForestRegressor(n_estimators=100, random_state=42, max_depth=10)
model.fit(X_train, y_train)

print(f"Model R² score: {model.score(X_test, y_test):.3f}")

The model achieves reasonable performance on this dataset. Now we can explain its predictions. Note that SHAP works equally well with poorly performing models—sometimes explaining a bad model helps you understand why it’s bad.

Computing and Visualizing SHAP Values

Initialize the appropriate explainer and compute SHAP values. For tree-based models, TreeExplainer is the obvious choice.

# Create explainer
explainer = shap.TreeExplainer(model)

# Calculate SHAP values for test set
shap_values = explainer.shap_values(X_test)

# Get expected value (baseline prediction)
expected_value = explainer.expected_value

print(f"Base value (average prediction): {expected_value:.3f}")
print(f"SHAP values shape: {shap_values.shape}")

The SHAP values array has the same shape as your input data—one value per feature per prediction. Each value represents that feature’s contribution to pushing the prediction above or below the baseline.

The summary plot provides the best overview of your model’s behavior:

# Summary plot - shows feature importance and impact distribution
shap.summary_plot(shap_values, X_test, plot_type="bar")
plt.tight_layout()
plt.show()

# Detailed summary plot with value distributions
shap.summary_plot(shap_values, X_test)
plt.tight_layout()
plt.show()

The bar plot ranks features by average absolute SHAP value—overall importance. The beeswarm plot shows how feature values correlate with impact. Red points indicate high feature values, blue indicates low values. If red points cluster on the right, high feature values increase predictions.

For individual predictions, waterfall plots excel:

# Explain a single prediction
instance_idx = 0
shap.waterfall_plot(
    shap.Explanation(
        values=shap_values[instance_idx],
        base_values=expected_value,
        data=X_test.iloc[instance_idx],
        feature_names=X_test.columns.tolist()
    )
)

Waterfall plots show how each feature pushes the prediction up or down from the baseline, stacking contributions to reach the final prediction. This is what you show stakeholders when they ask “why did the model predict this?”

Advanced SHAP Visualizations

Force plots provide an interactive alternative to waterfall plots, useful for presentations:

# Force plot for single prediction
shap.force_plot(
    expected_value,
    shap_values[instance_idx],
    X_test.iloc[instance_idx],
    matplotlib=True
)
plt.show()

Force plots visualize the same information as waterfall plots but emphasize the “forces” pushing predictions higher (red) or lower (blue).

Dependence plots reveal feature interactions and non-linear relationships:

# Dependence plot - shows how a feature's value affects its SHAP value
shap.dependence_plot(
    "MedInc",  # Feature to analyze
    shap_values,
    X_test,
    interaction_index="AveOccup"  # Color by interaction feature
)
plt.show()

Dependence plots scatter one feature’s values against its SHAP values. The relationship’s shape reveals how the model uses that feature. Vertical spread at specific feature values indicates interactions with other features. SHAP automatically suggests interaction features, or you can specify them.

Decision plots help compare multiple predictions:

# Decision plot for multiple predictions
shap.decision_plot(
    expected_value,
    shap_values[:20],  # First 20 predictions
    X_test.iloc[:20],
    feature_order='importance'
)
plt.show()

Decision plots show how predictions evolve as features are added. Each line represents one prediction, starting at the baseline and moving up or down with each feature’s contribution. Predictions that diverge at a specific feature indicate that feature strongly differentiates those cases.

Interpreting SHAP Results

Reading SHAP outputs correctly requires understanding a few key principles. First, SHAP values are additive: sum all SHAP values for a prediction plus the base value, and you get the model’s prediction. This additivity makes SHAP mathematically rigorous.

# Verify additivity
prediction = model.predict(X_test.iloc[[instance_idx]])[0]
shap_sum = expected_value + shap_values[instance_idx].sum()

print(f"Model prediction: {prediction:.3f}")
print(f"Base + SHAP sum: {shap_sum:.3f}")
print(f"Difference: {abs(prediction - shap_sum):.6f}")

The difference should be negligible (within floating-point precision).

Second, SHAP values are in the model’s output units. For a regression model predicting house prices in $100,000s, a SHAP value of 0.5 means that feature contributes $50,000 to the prediction.

Third, interpret SHAP values in context. A feature with small SHAP values might still be important if it consistently pushes predictions in the same direction. A feature with large SHAP values might be unreliable if those values are highly variable.

Extract insights programmatically:

# Identify most impactful features for a prediction
feature_importance = pd.DataFrame({
    'feature': X_test.columns,
    'shap_value': shap_values[instance_idx]
}).sort_values('shap_value', key=abs, ascending=False)

print(f"\nTop 5 features for prediction {instance_idx}:")
print(feature_importance.head())

# Find predictions where a specific feature had high impact
medinc_impact = np.abs(shap_values[:, X_test.columns.get_loc('MedInc')])
high_impact_indices = np.where(medinc_impact > medinc_impact.mean() + medinc_impact.std())[0]

print(f"\nPredictions where MedInc had high impact: {len(high_impact_indices)}")

Common pitfalls: Don’t confuse SHAP values with feature importance. Feature importance tells you which features matter overall; SHAP values tell you how they matter for specific predictions. Don’t ignore the base value—it’s crucial context. Don’t over-interpret small SHAP values; focus on the features that actually move the needle.

Conclusion and Best Practices

SHAP has become essential infrastructure for production ML systems. Use TreeExplainer for tree-based models—it’s fast and exact. Reserve KernelExplainer for models without specialized explainers, and expect longer computation times.

For large datasets, compute SHAP values on a representative sample rather than your entire dataset. A few thousand instances usually suffice for understanding model behavior. Store SHAP values alongside predictions in production systems so you can explain decisions retroactively.

Performance considerations matter. TreeExplainer handles millions of predictions efficiently, but KernelExplainer doesn’t scale well. For real-time applications, precompute SHAP values for common scenarios or use approximations.

Integrate SHAP into your ML pipeline early. Use it during model development to debug unexpected behavior. Use it in production to explain decisions to users. Use it in monitoring to detect when feature contributions drift over time—a signal that your model may need retraining.

SHAP isn’t perfect. It assumes feature independence, which rarely holds in practice. It can be computationally expensive for complex models. But it’s the best tool we have for understanding what our models actually learn and ensuring they make decisions for the right reasons.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.