I have a dataframe with 3 columns, let's say they are ['animal type','breed','colour']. I have created 3 multi select filters for the 3 columns. Now, when I change one filter, I want the options of the other filters to be updated.
Ex:
Animal type | Breed | Colour |
---|---|---|
Dog | bulldog | brown |
Dog | X | Black |
Dog | X | Black |
Dog | asky | Green |
Cat | Y | Yellow |
Cat | Y | Brown |
Spider | asky | White |
Spider | asky | Black |
If I select the option['Animal Type']=['Dog'] in a multiselect, I want to update the possible colour options to ['Brown','Black','Green']. When I select the option['Colour']=['Black'] in a multiselect, I want to update the possible animal type options to ['Dog','Spider'].
I have tried this, but it only updates when I filter by animal type.
ANIMAL = list(set(df['Animal Type']))
BREED = list(set(df['Breed']))
COLOR = list(set(df['Colour']))
options1= st.sidebar.multiselect('Animal Type',ANIMAL)
if options1:
options2 = st.sidebar.multiselect('Select BREED',set(df[df['Animal Type'].isin(options1)]['Breed']))
options3 = st.sidebar.multiselect('Select COLOR',set(df[df['Breed'].isin(options2)]['Color']))
else:
options2 = st.sidebar.multiselect('Select BREED',BREED)
options3 = st.sidebar.multiselect('Select COLOR',COLOR)
Does anyone know how to update all option filters, after a filtering?
This is basically the same solution mentioned in the comments. However, the mechanism by which a user chooses the order of filters has been changed. This creates extra code, so the logic is a bit more convoluted.
As mentioned in the other solution, there is a lot more complexity if you want to allow a user to go back and forth between filters to make selections, as such some kind of "order of filtering" should be established. In the implementation below, three filters are presented, and as a selection is made, session state values track what was last edited and moves things into an ordered "confirmed" list as the user moves on to edit the next filter. If the currently-being-edited filter is cleared, the last confirmation will be undone.
import streamlit as st
import pandas as pd
if 'df' not in st.session_state:
df = pd.DataFrame({
'Animal':['Dog','Dog','Dog','Dog','Cat','Cat','Spider','Spider'],
'Breed':['bulldog','X','X','asky','Y','Y','asky','asky'],
'Color':['Brown','Black','Black','Green','Yellow','Brown','White','Black']
})
st.session_state.df = df
df = st.session_state.df
df_filtered = df.copy()
# Session state values to track the order of filter selection
if 'confirmed' not in st.session_state:
st.session_state.confirmed = []
st.session_state.last_edited = None
def last_edited(i,col):
'''Update session state values to track order of editing
i:int
index of the column that was last edited
col:str
name of the column that was last edited
'''
if st.session_state.last_edited is None: # Nothing was previously selected/edited
st.session_state.last_edited = (i,col)
return
if st.session_state.last_edited == (i,col): # The same column was last edited
undo(col)
return
# Some other column was last edited:
confirmed(*st.session_state.last_edited)
st.session_state.last_edited = (i,col)
return
def undo(col):
'''Undoes the last confirmation if the last edit was to clear a filter
col : str
name of the column that was last edited
'''
if st.session_state['col_'+col] == []: # Check state of widget by key
last_confirmed = safe_pop(st.session_state.confirmed,-1)
st.session_state.last_edited = last_confirmed
def safe_pop(lst, i):
'''Pops the ith element of a list, returning None if the index is out of bounds
lst : list
list to pop from
i : int
index to pop
'''
try:
return lst.pop(i)
except IndexError:
return None
def confirmed(i,col):
'''Adds the last edited column to the confirmed list
i:int
index of the column that was last edited
col:str
name of the column that was last edited
'''
st.session_state.confirmed.append((i,col))
# Columns to display the filters (Streamlit with create multiselect widgets
# according to the order of user edits, but columns will keep them displaying
# in their original order for the user)
cols = st.columns(3)
selected = dict(zip(df.columns, [[],[],[]]))
# Confirmed filters
for i,col in st.session_state.confirmed:
selected[col] = cols[i].multiselect(
col, df_filtered[col].unique(), key=f'col_{col}',
on_change=last_edited, args=[i,col], disabled=True
)
df_filtered = df_filtered[df_filtered[col].isin(selected[col])]
#Currently editing
if st.session_state.last_edited is not None:
i,col = st.session_state.last_edited
selected[col] = cols[i].multiselect(
col, df_filtered[col].unique(), key=f'col_{col}',
on_change=last_edited, args=[i,col]
)
df_filtered = df_filtered[df_filtered[col].isin(selected[col])]
# Not yet edited filters
for i, col in enumerate(df_filtered.columns):
if (i,col) not in st.session_state.confirmed and (i,col) != st.session_state.last_edited:
selected[col] = cols[i].multiselect(
col, df_filtered[col].unique(), key=f'col_{col}',
on_change=last_edited, args=[i,col]
)
if selected[col] != []:
df_filtered = df_filtered[df_filtered[col].isin(selected[col])]
cols = st.columns(2)
cols[0].write(df)
cols[1].write(df_filtered)