Using st.cache for Matplotlib figures (or hash_funcs for complex objects)

Posted this on https://twitter.com/andfanilo/status/1421886366510616585 and I guess it may be of interest to some of you :slight_smile:

You sometimes create a function that builds a complex Matplotlib figure out of a dataframe, and you don’t want it to rerun everytime you interact with a Streamlit widget, so you decide to decorate it with @st.cache

Part of the solution is provided in the trace: Streamlit does not know how to hash Spines and needs you to specify it through hash_funcs

Now building a hash by using the colors of each spine in the Spines collection is a silly example, but it just shows you can hash it the way you want.

Why silly do you ask? Try it yourself and see that:

  1. it is supppper slooow, maybe interacting with spines is slow, maybe other variables internal to the Matplotlib Figure are also slow to hash
  2. what if you change the underlying Matplotlib function to plot and change the width of the spines? Your hash would keep the same value as it does not check for the width when computing the hash. You could actually end up not updating the cached result when changing the underlying library during your livecoding session. So make sure your hash identifies your object has specifically as possible!

There are multiple ways provided in the Streamlit docs (definitely have a look!), with each it’s own pros&cons.

  • If Python is able to do this, hash the whole thing! Streamlit has a particular way of hashing objects to make sure every referenced variable has weight in the hash, but for a Maplotlib rendered figure it may be overkill. In other cases though, the Streamlit magic hashing may be faster than Python’s native way, so test carefully.
  • If creating the figure is only part of a bigger cached function, you could want to disable its hashing with the lambda _: None anonymous function.
  • If the figure is the returned value of the method, allow_output_mutation deactivates hashing of the resulting figure, so cached result only depends on provided inputs and the body of the function (just be careful to not mutate the output plot in your Streamlit app: as Streamlit doesn’t track those, so this could mess up the cache!).

So yeah, go give it a try!


This wraps up discussions in a number of issues, which you can check for further details:

Hope you liked it. Did that in a rush, but given positive feedback, I’ll probably do a bit more of those based on recurring questions I see floating around in less of a rush :laughing:. In the meantime, happy Streamlitin’ !

Fanilo :balloon:

5 Likes

BEAUTIFUL @andfanilo!!! :heart_eyes_cat:

1 Like

+ve feedback * 20!

2 Likes

I’ve tried all of these and non of it works. then I tried experimental_memo and experimental_singleton, they dont work either, I keep getting the same blank plots each time i reload my app

To help us help you, perhaps you could start by sharing a reproducible code example that demonstrates the blank plots behavior. :balloon:

1 Like

While reading through the st.experimental_memo documentation here , they list the supported static st elements in cache-decorated functions, though other plotting element is listed st.pyplot is not on the list. So I tried to make my plotting functions return a png image that way i can use st.image to call the function instead of st.pyplot, since st.image can be cached by st.experimental_memo. And it works! I even changed the st.experimental_memo to st.cache and that also works, though it throws an error saying cache are supposed to be immutable. the ```
allow_output_mutation=True


@st.experimental_memo
def wordcloud_plot(df):
buf = io.BytesIO()
all_words_lem = ’ '.join(word for word in df[‘review_lem’])
wordcloud = WordCloud(background_color=“white”).generate(all_words_lem)
plt.figure(figsize=(15, 10))
plt.imshow(wordcloud, interpolation=‘bilinear’)
plt.axis(‘off’)
plt.savefig(buf, format=“png”)
return buf

st.image(wordcloud_plot(some_df))

1 Like