Filter dataframe regardless of the order of selection

Is it possible to have two multiselect widgets to filter my dataframe and to adjust the selectable options for both regardless of which one I chose first?

With the following code I manage to filter the available options in the second widget. But what if option_b is chosen first. How do I then adjust the selectable options in option_a?

import pandas as pd
import streamlit as st

df = pd.DataFrame({'a': [2, 2, 8, 0],
'b': [2, 0, 0, 1],
'c': [10, 2, 1, 8],
'd': [5,6,7,8]})

option_a = []

option_a = st.sidebar.multiselect(
    'select option a',
    df["a"].unique()
)

option_b = st.sidebar.multiselect(
    'select option b',
    df["b"].loc[df['a'].isin(option_a)].unique()
)

Thank you very much for your help!

A simple answer would be to include a switch for which one you want selected first, so that you can use exactly your solution, but with two versions depending on which one a user wants first.

first = st.radio('Select first:',['a','b'])

if first == 'a':
    # lay out the buttons with a first
else:
    # lay out the buttons with b first

A more complicated solution is to build in some interactivity between the buttons. Here’s an example. However, note that since each button updates relative to action on the other, you can get yourself stuck in a dead end, hence the reset button. It starts by showing all data, and as you remove values, it makes sure to remove options (and selections if affected) accordingly. I can think of other ways to implement this depending on how much back-and-forth you want possible, so this more to show how you can get widgets talking to each other in a bidirectional manner using session_state and callback functions.

import streamlit as st
import pandas as pd

# page prefix
pre = 'interacting_widget_options__'

# column selection
col1 = 'fur'
col2 = 'color'

@st.experimental_memo
def get_data():
    data = pd.DataFrame({'fur':['DSH','DSH','DSH','DSH','DSH','DMH',
                                'DMH','DMH','DLH','DLH','DLH','DLH'],
                        'sex':['M','F','M','M','F','M',
                               'M','F','F','F','F','M'],
                        'color':['White','Black','Orange','Orange','Calico','Orange',
                                 'Black','Brown','Calico','Black','White','Brown']})
    return data

df = get_data()

def initialize():
    if (pre+col1+'_possible') not in st.session_state or \
       (pre+col2+'_possible') not in st.session_state:
        st.session_state[pre+col1+'_possible'] = df[col1].unique()
        st.session_state[pre+col2+'_possible'] = df[col2].unique()

initialize()

def reset():
    st.session_state.clear()
    return

st.button('Reset',on_click=reset)

def get_row_values(data, col1, filter1, options1, col2, filter2, options2):
    filter1_list = st.session_state[filter1]
    filter2_list = st.session_state[filter2]

    filtered_data = data[data[col1].isin(filter1_list)]
    available_col2_values = filtered_data[col2].unique()
    st.session_state[filter2] = [selected for selected in filter2_list \
                                 if selected in available_col2_values]
    st.session_state[options2] = available_col2_values

    filter2_list = st.session_state[filter2]

    filtered_data = data[data[col2].isin(filter2_list)]
    available_col1_values = filtered_data[col1].unique()
    st.session_state[options1] = available_col1_values
    st.session_state[filter1] = filter1_list
    return

filter = {}
filter[col1] = st.multiselect(f'Select {col1}',st.session_state[pre+col1+'_possible'], 
                            key=(pre+col1+'_selected'), 
                            default = st.session_state[pre+col1+'_possible'],
                            on_change = get_row_values, 
                            args=(df,col1,(pre+col1+'_selected'),(pre+col1+'_possible'),
                                  col2,(pre+col2+'_selected'),(pre+col2+'_possible')))
filter[col2] = st.multiselect(f'Select {col2}',st.session_state[pre+col2+'_possible'], 
                            key=(pre+col2+'_selected'),
                            default = st.session_state[pre+col2+'_possible'],
                            on_change = get_row_values, 
                            args=(df,col2,pre+col2+'_selected',pre+col2+'_possible',
                                  col1,pre+col1+'_selected',pre+col1+'_possible'))

df[df[col1].isin(filter[col1])][df[col2].isin(filter[col2])]

st.write(pd.DataFrame({'key':st.session_state.keys(),'value':st.session_state.values()}))

Click here for a demo hosted on Streamlit cloud

(edit to change app url)

I had to fix a bug with the reset button in the above example. It’s more of a work around than a solution since I’m hitting a familiar bug with session state, but it does result in a fully working example here: https://mathcatsand-examples.streamlit.app/interacting_widget_options_(working)

(I had to add a a toggling digit to the keys on the multi-selectors to force them to rebuild on a reset.)

Code for my example app is here: Streamlit-Mechanics-Examples/interacting_widget_options_(working).py at main · MathCatsAnd/Streamlit-Mechanics-Examples · GitHub

(edit to change app url)