Display SHAP diagrams with Streamlit

Hi,

I am building a dashboard for a ML model, using Streamlit.
For the interpretability of the model, I would like to use the SHAP library.

Is there a way to display the SHAP diagrams on my Streamlit dashboard ? (And if yes, how… ;-))

Right now, I run the following minimal code:

import streamlit as st
import xgboost
import shap

# train XGBoost model
X,y = shap.datasets.boston()
model = xgboost.train({"learning_rate": 0.01}, xgboost.DMatrix(X, label=y), 100)

# explain the model's predictions using SHAP values
# (same syntax works for LightGBM, CatBoost, scikit-learn and spark models)
explainer = shap.TreeExplainer(model)

shap_values = explainer.shap_values(X)

# visualize the first prediction's explanation (use matplotlib=True to avoid Javascript)
st.write(shap.force_plot(explainer.expected_value, shap_values[0,:], X.iloc[0,:], matplotlib=True), unsafe_allow_html=True)

I got this in my navigator, instead of the diagram:
**ValueError** : signal only works in main thread

File "C:\Users\Lebrun\AppData\Roaming\Python\Python37\site-packages\streamlit\ScriptRunner.py", line 311, in _run_script exec(code, module.__dict__)

File "D:\OneDrive\OpenClassrooms\Parcours Data Scientist\projet7\dashboard\shap_streamlit.py", line 24, in <module> st.write(shap.force_plot(explainer.expected_value, shap_values[0,:], X.iloc[0,:], matplotlib=True), unsafe_allow_html=True)

File "C:\Users\Lebrun\AppData\Roaming\Python\Python37\site-packages\shap\plots\force.py", line 138, in force_plot return visualize(e, plot_cmap, matplotlib, figsize=figsize, show=show, text_rotation=text_rotation)

File "C:\Users\Lebrun\AppData\Roaming\Python\Python37\site-packages\shap\plots\force.py", line 295, in visualize return AdditiveForceVisualizer(e, plot_cmap=plot_cmap).matplotlib(figsize=figsize, show=show, text_rotation=text_rotation)

File "C:\Users\Lebrun\AppData\Roaming\Python\Python37\site-packages\shap\plots\force.py", line 393, in matplotlib fig = draw_additive_plot(self.data, figsize=figsize, show=show, text_rotation=text_rotation)

File "C:\Users\Lebrun\AppData\Roaming\Python\Python37\site-packages\shap\plots\force_matplotlib.py", line 395, in draw_additive_plot plt.show()

File "c:\users\lebrun\anaconda3\envs\ds_projet7\lib\site-packages\matplotlib\pyplot.py", line 269, in show return _show(*args, **kw)

File "c:\users\lebrun\anaconda3\envs\ds_projet7\lib\site-packages\matplotlib\cbook\deprecation.py", line 413, in wrapper return func(*args, **kwargs)

File "c:\users\lebrun\anaconda3\envs\ds_projet7\lib\site-packages\matplotlib\backend_bases.py", line 3302, in show cls.mainloop()

File "c:\users\lebrun\anaconda3\envs\ds_projet7\lib\site-packages\matplotlib\backends\backend_qt5.py", line 1094, in mainloop signal.signal(signal.SIGINT, signal.SIG_DFL)

File "c:\users\lebrun\anaconda3\envs\ds_projet7\lib\signal.py", line 47, in signal handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))

Thank you very much !

2 Likes

https://boston-xgb-app.herokuapp.com
please give it a moment on first load (ETA: 45 seconds boot time)
:slight_smile:

3 Likes

Great work !

Is it possible to access the source code of it ?
I have the feeling that for the SHAP diagrams, they are registered and displayed as images. Right ?

Thank you in advance !

1 Like

Sure,


:slight_smile:

3 Likes

Thank you ! These lines did the trick :


def main():
    
    # My previous code here
    # […]

    st.pyplot(bbox_inches='tight')
    plt.clf()

if __name__== '__main__':
   main()
3 Likes

I was also struggling with this, especially to make the force_plot look nice. With the following I could make it look a bit nicer:

shap.force_plot(expected_value, shap_values[idx,:], features = X.iloc[idx,4:], 
link='logit', matplotlib=True, figsize=(12,3))
st.pyplot(bbox_inches='tight',dpi=300,pad_inches=0)
plt.clf()

Do you think we will eventually be able to include the javascript based plots?

1 Like

Thank you for your suggestion. Just pushed the new force plot :slight_smile:, hope you appreciate it.

I am struggling with this. tried the suggested approach:

shap.force_plot(shap_explainer.expected_value[1], shap_values[1], df[cols].iloc[0],matplotlib=True,figsize=(16,5))
st.pyplot(bbox_inches='tight',dpi=300,pad_inches=0)
pl.clf()

But I am getting below error:

TypeError: can only concatenate str (not “float”) to str

Further log of the error:

lib\site-packages\shap\plots\force.py", line 138, in force_plot
return visualize(e, plot_cmap, matplotlib, figsize=figsize, show=show, text_rotation=text_rotation)

lib\site-packages\shap\plots\force.py", line 295, in visualize
return AdditiveForceVisualizer(e, plot_cmap=plot_cmap).matplotlib(figsize=figsize, show=show, text_rotation=text_rotation)

lib\site-packages\shap\plots\force.py", line 393, in matplotlib
fig = draw_additive_plot(self.data, figsize=figsize, show=show, text_rotation=text_rotation)

lib\site-packages\shap\plots\force_matplotlib.py", line 386, in draw_additive_plot
 offset_text, total_effect, min_perc=0.05, text_rotation=text_rotation)

lib\site-packages\shap\plots\force_matplotlib.py", line 117, in draw_labels
 text = feature[2] + ' = ' + feature[1]

Though when I try to print shap_values, I am getting expected output.
Also when I am trying to plot the summary plot it is working fine.

shap.summary_plot(shap_values, df[cols], plot_type="bar")

Am I missing anything here?

Oh, I just stumbled on this one, it’s now easier with Streamlit Components and latest SHAP v0.36+ (which define a new getjs method), to plot JS SHAP plots

(some plots like summary_plot are actually Matplotlib and can be plotted with st.pyplot)

Here is an example.

import shap
import streamlit as st
import streamlit.components.v1 as components
import xgboost

@st.cache
def load_data():
    return shap.datasets.boston()

def st_shap(plot, height=None):
    shap_html = f"<head>{shap.getjs()}</head><body>{plot.html()}</body>"
    components.html(shap_html, height=height)

st.title("SHAP in Streamlit")

# train XGBoost model
X,y = load_data()
model = xgboost.train({"learning_rate": 0.01}, xgboost.DMatrix(X, label=y), 100)

# explain the model's predictions using SHAP
# (same syntax works for LightGBM, CatBoost, scikit-learn and spark models)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X)

# visualize the first prediction's explanation (use matplotlib=True to avoid Javascript)
st_shap(shap.force_plot(explainer.expected_value, shap_values[0,:], X.iloc[0,:]))

# visualize the training set predictions
st_shap(shap.force_plot(explainer.expected_value, shap_values, X), 400)

Fanilo

1 Like

Very cool components!

Thanks for the update. I am planning to update my app accordingly. Will release it when ready :).

What is the right way to display a Shap plot now, after deprecation.showPyplotGlobalUse in version 0.67.1?
I used to plot with this code:
st.pyplot(shap.summary_plot(shap_values, X),bbox_inches=‘tight’,dpi=300,pad_inches=0)
Thanks in advance!
Wilber