Search code examples
pythonpandasdataframeartificial-intelligencestreamlit

streamlit only updates dataframe after every second change using st.data_editor


I am trying to change the value in a column based on changes made by the user in a different column with st.data_editor, and want to do it live on change. But the following code only works after 2 changes and only shows the initial change.

code is run by running streamlit run main.py

main.py

from page import page

page()

page.py

import streamlit as st
import pandas as pd


boq_df = pd.DataFrame()


def update_dataframe():
    boq_df.loc[boq_df["ANALYZE"] == False, "TYPE"] = None
    boq_df.ffill(inplace=True)


def page():
    global boq_df

    st.write("# Test")

    if st.button("Analyze", type="primary"):
        boq_df = pd.DataFrame(
            data={
                "ANALYZE": [True, False, True],
                "CAT": ["Car", "Truck", "Bike"],
                "TYPE": ["blue", False, "yellow"],
                "DESC": ["two door", "four door", "single"],
            }
        )

    if not boq_df.empty:
        boq_df = st.data_editor(boq_df, height=700, on_change=update_dataframe())


Solution

  • Do not use a global variable in streamlit. Streamlit always rerun the code from top to bottom when there is a change in the user interface. Instead use the built-in dictionary-like session_state.

    Also the on_change in the data editor is only useful when you add or delete a row. For editing, use the return value of the data_editor directly.

    I tried to fix your code.

    import streamlit as st
    import pandas as pd
    
    
    # Create a session variable
    if 'boq' not in st.session_state:
        st.session_state['boq'] = pd.DataFrame()
    
    
    def page():
        st.write("# Test")
    
        # If button is pressed, assign a pre-built dataframe to the variable.
        if st.button("Analyze", type="primary"):
            st.session_state['boq'] = pd.DataFrame(
                data={
                    "ANALYZE": [True, False, True],
                    "CAT": ["Car", "Truck", "Bike"],
                    "TYPE": ["blue", False, "yellow"],
                    "DESC": ["two door", "four door", "single"],
                }
            )
    
        # If variable is not empty, construct a data_editor.
        if not st.session_state['boq'].empty:
            edited_df = st.data_editor(
                st.session_state['boq'],
                height=700
            )
    
            # If column "ANALYZE" is set to False, set the value of
            # "TYPE" to None. Be sure to update column "TYPE" only if
            # there are changes to column "ANALYZE".
            is_equal = edited_df['ANALYZE'].equals(st.session_state['boq']['ANALYZE'])
            if not is_equal:
                edited_df.loc[edited_df["ANALYZE"] == False, "TYPE"] = None
                edited_df.ffill(inplace=True)
                st.session_state['boq'] = edited_df  # update the variable
    
                # Set streamlit to rerun the script from top to bottom
                # to update the data editor.
                st.rerun()
    
    page()
    

    enter image description here