'Nested Filtering Using Ipywidgets

I am trying to create a dynamic filtering with Ipywidgets. The word dynamic here refers to: if an option is chosen in one widget, it will affect the choices of the remaining widgets.

Here is a toy dataset for replication purposes.

toy_data = pd.DataFrame({"LETTER": ["A", "B", "A","B","A", "B", "A","B"],
               "PLANT": ["Orange", "Carrots", "Lemon","Potato","Pomelo","Yam","Lime","Radish"],
               "NUMBER": [1,2,3,4,5,6,7,8]})

Creating widgets:

letter_var = widgets.Dropdown(
    description="Letter: ",
    options=toy_data.LETTER.unique(),
    value="A",
)

def letter_filtering(change):
    clear_output(wait = True)
    letter = letter_var.value
    new_toy_data = toy_data[toy_data.LETTER == str(letter)]
    
    plant_var = widgets.Dropdown(description="Plant: ", options=new_toy_data.PLANT.unique(), value="Orange")
    
    return plant_var

the purpose of the letter_filtering function is to filter the choices in the wigets for plants. That is if the letter B has been chosen for letter_var, the choices in plant_var will only be limited to the letter B. but upon implementation,

widgets.HBox([letter_var,letter_filtering])

I am receiving a trait error.

TraitError: The 'children' trait of a HBox instance contains an Instance of a TypedTuple which expected a Widget, not the function 'letter_filtering'.

I think I'm lost on how to go about this.



Solution 1:[1]

I am not familiar with the widget mechanism, but you can achieve this via the "interact" functionality, which I think is also simpler:

from ipywidgets import interact, fixed

toy_data = pd.DataFrame({"LETTER": ["A", "B", "A","B","A", "B", "A","B"],
               "PLANT": ["Orange", "Carrots", "Lemon","Potato","Pomelo","Yam","Lime","Radish"],
               "NUMBER": [1,2,3,4,5,6,7,8]})

def inner_fn(plant, df_in):
    df_ = df_in if plant == 'ALL' else df_in[df_in['PLANT'] == plant]
    return df_

def outer_fn(letter):
    df_ = toy_data if letter == 'ALL' else toy_data[toy_data['LETTER'] == letter]
    plants = ['ALL'] + sorted(df_['PLANT'].unique())
    interact(inner_fn, plant=plants, df_in=fixed(df_))

letters = ['ALL'] + sorted(toy_data['LETTER'].unique())
interact(outer_fn, letter=letters)

This will create a "plant" dropdown underneath the "letter" dropdown, like so:

Starting point

And when a letter is chosen, the list of plants will update, like so:

updated plant list

The little extra indentation of the second dropdown box is a tip-off that this is a nested mechanism and admittedly makes this technique a bit unsightly.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 HHest