Referencing a cached object

Dear colleagues,

I am trying to create a simple app using Streamlit which essentially boils down to three steps:

  1. A user specifies what data to load.
  2. The data is loaded and cached.
  3. Some statistics for this data is displayed.

A simple toy example:

x1 = st.sidebar.text_input('Input x1')
x2 = st.sidebar.text_input('Input x2')

if st.button('Load data'):
    data = load_data(x1, x2)

if st.checkbox('Show mean'):
    st.text(data.mean())

if st.checkbox('Show median'):
    st.text(data.median())

The load_data function is decorated with st.cache.

The problem is that when I switch on a checkbox, I get:
NameError: name 'data' is not defined.

I do realise that it happens because Streamlit reads the whole file from top to bottom each time I change something (e.g. I switch on/off a checkbox) and when I use one of the checkboxes, data is lost. However, there must be a way to get this data somehow since it’s been cached.

Is there some way how I can get a reference to the cached data apart from duplicating data = load_data(x1, x2) within each st.checkbox block?

Note, I am new to Streamlit and might be missing some basic functionality.

Any help is appreciated.

Hi @ezavarygin,

This looks like a case where you just need to insert a line near the top of your script that says data = None so that the variable’s scope is global. Right now your data declarations are all structured underneath if statements.

I’m only basing my response on the sample code you’ve provided. So if this doesn’t solve your problem, let me see the rest of your code and I’ll try to help some more.

Thanks for using Streamlit!

Hi @nthmost,

Thanks for your reply.

This leads to
AttributeError: 'NoneType' object has no attribute 'mean'

I found an ugly work around by replacing
if st.button('Load data'):
with
if st.checkbox('Load data'):

Here is a code that illustrates the problem:

import streamlit as st
import numpy as np
import time

@st.cache
def load_data(x1, x2):
    time.sleep(5)
    return np.arange(x1, x2)

x1 = st.sidebar.number_input('Input x1', value=1.)
x2 = st.sidebar.number_input('Input x2', value=10.)

if st.button('Load data'):
    data = load_data(x1, x2)

if st.checkbox('Show mean'):
    st.text(data.mean())

Just get rid of the if statement to load data. Make sure that the default values for x1 and x2 (currently 1 and 10) are sane, and load the data every time the script runs. Otherwise you’re going to have all kinds of problems with the button activating and deactivating.

I realise that this is a toy example. If your real example has intermediate states where the data is invalid while the user is entering one of the boxes, but not the other, you probably will need to detect this somehow, and only run the rest of the script when the entries are sane.

For example:

import streamlit as st
import numpy as np
import time

@st.cache
def load_data(x1, x2):
    time.sleep(5)
    return np.arange(x1, x2)

x1 = st.sidebar.number_input('Input x1', value=1.)
x2 = st.sidebar.number_input('Input x2', value=10.)

data = load_data(x1, x2)

if st.checkbox('Show mean'):
    st.text(data.mean())

Hi @madflier,

Thanks for your input,

I realise that this is a toy example. If your real example has intermediate states where the data is invalid while the user is entering one of the boxes, but not the other, you probably will need to detect this somehow, and only run the rest of the script when the entries are sane.

This is exactly why I wrapped the load_data up with a button - I was trying to prevent the app from executing further until a user is happy with the input parameters. So far the best what I’ve got is:

import streamlit as st
import numpy as np
import time

@st.cache
def load_data(x1, x2):
    time.sleep(5)
    return np.arange(x1, x2)

x1 = st.sidebar.number_input('Input x1', value=1.)
x2 = st.sidebar.number_input('Input x2', value=10.)

if st.checkbox('I am done with input parameters'):

    data = load_data(x1, x2)
    
    st.text(data.mean())

    # other widgets, etc.

What I would really love to have in Streamlit is a native input form with multiple variables to enter and with a Submit/Ready button. The app would only be rerun after the user clicks on that button.

You can do that. It’s just that the button will turn off again the next time any alteration is made to the form. The checkbox will stay on, but then you’re just back to the original issue of it running through every time you change the inputs.

Sounds like you might be better off with the button. If you want further checkboxes for the mean etc, probably put them before the “calculation” button.

You can use st.empty() if you want them to appear in a different order (eg have the “proceed” button further up the page, but at a later point in the flow of the actual script to block the execution).

st.empty() is indeed interesting and gives more flexibility. However, I could not come up with anything that would solve my problem. I will keep trying.
If you are aware of any good streamlit code examples relevant to this, feel free to give a link here.
Thanks again.

I’m still not quite sure what actual problem you are trying to solve.

Using a button to trigger the calculation rather than having the calculation always run seems to solve the problem that you have actually described.

If what you want is to display a result, and then keep displaying the result but turn off the live calculations, then you have a different problem. This is about maintaining state, which is tricky but entirely possible - there are many potential threads about maintaining state between program runs.

Perhaps you could give us a more thorough description of what you would like your script to do on first and subsequent runs, and what it doesn’t currently do.

As a ludicrously simple example, look at this code:

import streamlit as st

@st.cache(allow_output_mutation=True,persist=True)
def persist_dict():
    return []

dict=persist_dict()

st.write("The dict contains", dict)
new=st.text_area("Enter new stuff")
if st.button("Rerun"):
    st.balloons()
dict.append(new)
st.write(new)

Note that this data will be visible to anyone using the app until you reset the cache.

Sorry, that’s an old set of code - fairly easy to make a persistent dictionary rather than a list if that is your preference!

Do you need to have the checkboxes lower down?

x1 = st.sidebar.text_input('Input x1')
x2 = st.sidebar.text_input('Input x2')
show_mean = st.sidebar.checkbox('Show mean')
show_median = st.sidebar.checkbox('Show median')

if st.button('Load data'):
    data = load_data(x1, x2)
    if show_mean:
        st.text(data.mean())
    if show_median:
        st.text(data.median())

You could also use a multiselect for the statistics, if you don’t want lots of checkboxes.

Personally I’d move some of this over to the sidebar as that’s where my “config” lives, so I added that in the code.

Hi @Ian_Calvert

Thanks for your suggestion.

In my real app, I have multiple plots and statistics to show. It is hard to know in advance which of them are interesting. E.g. I want to look at plots B and C only if plot A shows something interesting and the other way around. There are also interactions with the plots using other widgets (e.g. to change the smoothing window size).
Any time I want to interact with a plot or select more plots, I would also need to click on the ‘Load data’ button to see the change (even though there will be no actual re-calculation). Seems like an unnecessary complication.

Yes, this is my problem. However, I could not come up with a good button solution.

I am new to Steamlit and am probably still thinking in terms of Jupyter notebook where I have 3 cells with input parameters, a data loader, and a plotting script. I will try to come up with a better logic for my app.
Thanks.