Stakeholders always ask the same question: “Why did the model make that prediction?” You can email them a static plot, or you can hand them a dashboard where they answer the question themselves. The second option saves everyone time.
We’ll train an XGBoost classifier, compute SHAP values, and wrap everything in a Streamlit app with interactive visualizations. Here’s the full stack working end-to-end in under 100 lines.
Train a Model and Compute SHAP Values
Start with the basics. Train a model on the UCI breast cancer dataset, then generate SHAP values:
| |
TreeExplainer is the right choice for XGBoost, LightGBM, and random forests. It’s exact and fast because it uses the tree structure directly instead of sampling perturbations. For neural networks, you’d reach for DeepExplainer or GradientExplainer instead.
The shap_values object holds everything: base values, SHAP values per feature, and the input data. That single object powers every visualization we’ll build.
Setting Up SHAP With XGBoost
Install the dependencies:
| |
Pin your versions. SHAP’s API has changed across releases, and mismatched versions between shap and xgboost cause cryptic segfaults.
One thing worth noting: shap.TreeExplainer returns an Explanation object (not a raw numpy array) when you call explainer(X_test). This object carries .values, .base_values, and .data together. Older tutorials call explainer.shap_values(X_test) which returns a plain array. The newer Explanation object is what the plotting functions expect, so stick with explainer(X_test).
Building the Streamlit Dashboard
Here’s the full dashboard. Save this as app.py:
| |
Run it:
| |
The @st.cache_resource decorator is critical. Without it, Streamlit retrains the model and recomputes SHAP values on every interaction. With it, the expensive work happens once and stays in memory.
Why Waterfall Instead of Force Plots
You might have seen shap.force_plot() in older tutorials. It renders as JavaScript/HTML, which doesn’t play well with Streamlit’s rendering pipeline. The waterfall plot gives you the same information (base value, feature contributions, final prediction) in a matplotlib figure that Streamlit handles natively. Use waterfall plots in dashboards. Save force plots for Jupyter notebooks.
Adding Feature Filtering
Stakeholders often care about specific features. Add a multi-select filter:
| |
This is the kind of interactivity that makes a dashboard worth building. A static report can’t do this.
Deploying the Dashboard
Streamlit Community Cloud
The fastest path to a shared URL. Create a requirements.txt:
| |
Push to GitHub, connect the repo at share.streamlit.io, pick app.py as the entrypoint, and deploy. Free tier gives you one app.
Docker
For internal deployments where you control the infrastructure:
| |
Build and run:
| |
For production, swap out the in-memory model training for loading a serialized model. Save your model with model.save_model("model.json") and load it in the app with model.load_model("model.json"). Same goes for SHAP values: precompute them and store with joblib.dump().
Common Errors and Fixes
TypeError: TreeExplainer does not support model type
You passed a scikit-learn pipeline object instead of the raw estimator. Extract the model first:
| |
AttributeError: 'numpy.ndarray' object has no attribute 'values'
You used explainer.shap_values() (returns numpy array) but passed the result to a plot function that expects an Explanation object. Switch to explainer():
| |
streamlit: Your app is experiencing a memory issue
SHAP values for large datasets eat memory fast. Subsample your test set:
| |
ValueError: could not broadcast input array from shape...
This usually means your feature names don’t match the data shape. Double-check that X_test has the same columns as what the model was trained on. Pandas DataFrames with named columns prevent this:
| |
Plots not rendering in Streamlit
Always pass show=False to SHAP plot functions. Without it, matplotlib tries to display the plot outside Streamlit’s context and you get a blank space. Also call plt.clf() after st.pyplot() to prevent plot accumulation across reruns.
Related Guides
- How to Build a Model A/B Testing Framework with FastAPI
- How to Build a Model Warm Pool with Preloaded Containers on ECS
- How to Build a Model Health Dashboard with FastAPI and SQLite
- How to Build a Model Serving Pipeline with LitServe and Lightning
- How to Build a Model Monitoring Dashboard with Prometheus and Grafana
- How to Build a Model Deployment Pipeline with Terraform and AWS
- How to Build a Model Endpoint Load Balancer with NGINX and FastAPI
- How to Build Feature Flags for ML Model Rollouts
- How to Build a Model CI Pipeline with GitHub Actions and DVC
- How to Build a Model Warm-Up and Health Check Pipeline with FastAPI