Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support returning the actual matplotlib Figure #3494

Merged
merged 1 commit into from Feb 13, 2024

Conversation

richarddli
Copy link
Contributor

Overview

I'm using SHAP in software (as opposed to a Jupyter notebook). One use case I would like to do is display the SHAP bar chart from an Explanation object. With Force plots, there is an HTML method that returns raw HTML. I've implemented a simple alternative to adding the HTML method everywhere.

In essence, if show=False, return the actual current matplotlib Figure. This means that the calling function can then manipulate the actual figure. This approach also means that a lot of the styling / color options that are part of the library can be made directly on the Figure itself, instead of passing through to the actual plot.

If this approach is acceptable, I'd suggest modifying all the other plot functions as well to behave similarly.

Please let me know your thoughts! cc @CloseChoice

@JSchoeck
Copy link

JSchoeck commented Feb 9, 2024

I wanted to make quite the same PR, as for example shap.plots.scatter does currently not return anything, even if show=False.

Definitely should be fixed and harmonized across the different plot types.
Some return nothing, others a plt.Figure, others a plt.Axes.

@CloseChoice
Copy link
Collaborator

CloseChoice commented Feb 9, 2024

Shouldn't calling pl.cfg() outside of calling bar return the same figure? E.g.

import xgboost
import matplotlib.pyplot as plt
import shap

# train XGBoost model
X, y = shap.datasets.adult(n_points=1000)
model = xgboost.XGBClassifier().fit(X, y)

# compute SHAP values
explainer = shap.Explainer(model, X)
shap_values = explainer(X)

shap.plots.bar(shap_values, show=False)
fig = plt.gcf()
fig.savefig('shap_bar_plot.png')

This still gives me the same figure as expected.
Hmm, at beeswarm we return the axis, often we do not return anything. Actually I do not really see an upgrade in this, especially with show=False might not be really intuitive.

Edit: @JSchoeck I totally agree that this should be harmonized and the solution provided by @richarddli might be a generally applicable but that needs to be discussed if it is viable for all plots. @connortann what do you think about this?

@richarddli
Copy link
Contributor Author

The Jupyter notebook implementation that I'm using in VSCode automatically invokes pl.show() which clears the figure before I can save it. You can argue whether or not that the VSCode behavior is correct, but I see no downside to being explicit about the semantics.

I would also suggest that this approach seems like a much cleaner separation of concerns than something like

def html(self, label_margin=20):
. _force.py in particular takes a bunch of args that it passes to matplotlib such as figsize. I think having each plot return a matplotlib figure (or equiv), and then a separate set of functions (if desired) to manipulate these figures, is a much cleaner implementation.

Copy link
Collaborator

@CloseChoice CloseChoice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also do not see a downside to this and if this helps users, then it's fine for me.

@connortann connortann added the visualization Relating to plotting label Feb 12, 2024
@connortann
Copy link
Collaborator

Thanks for the PR! This is a good step towards #3411

@connortann connortann added the enhancement Indicates new feature requests label Feb 12, 2024
@connortann connortann added this to the 0.45.0 milestone Feb 12, 2024
Copy link

codecov bot commented Feb 12, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (bf2e449) 59.23% compared to head (19da43a) 59.24%.
Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3494   +/-   ##
=======================================
  Coverage   59.23%   59.24%           
=======================================
  Files          90       90           
  Lines       12728    12729    +1     
=======================================
+ Hits         7540     7541    +1     
  Misses       5188     5188           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@connortann connortann merged commit c3cb524 into shap:master Feb 13, 2024
15 of 16 checks passed
@richarddli richarddli deleted the return-fig branch February 13, 2024 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Indicates new feature requests visualization Relating to plotting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants