pythonjupyter-notebookipywidgets

ipywidgets interactive with variable containers of widgets


For some interactive data analysis with Jupyter and ipywidgets, I generate a number of widgets depending on the data.

I.e. eventually I have a number of GridBoxes with Checkboxes, RangeSliders and ColorPickers which I layout in a Tab-Widget.

Now I tried to use

categories = ["A", "B", "C"] # retrieve from dataset
cbox = widgets.GridBox(children=[ widgets.Checkbox(value=True, description=v) for v in categories ])
rbox = widgets.GridBox(children=[ widgets.IntRangeSlider(value=[1,10], max=20, min=0, description=v) for v in categories ])


def analysis(variables, ranges):
    print("Hello. Currently I do nothing with the input!")

display(widgets.Tab(children=[cbox, rbox], titles=('Variables', 'Ranges')))
display(widgets.interactive_output(analysis, {"variables":cbox, "ranges":rbox}))

Which does not work:

AttributeError: 'GridBox' has no attribute 'value'

I also tried:

display(widgets.interactive_output(analysis, {"variables":cbox.children, "ranges":rbox.children}))

which also does not work.

Is it somehow possible to pass any type of container of some sort to my interactivized function, or do I need to ressort to kwargs? And if so, how would you do that efficiently?


I use ipywidget version 8.1.5


Solution

  • I think you were on the right track with trying display(widgets.interactive_output(analysis, {"variables":cbox.children, "ranges":rbox.children})). Your code works well with the variation display(widgets.interactive_output(analysis, {"variables":cbox.children[0], "ranges":rbox.children[0]})), but of course, only passes information about the first one of each in. The key thing is that it still wants a dictionary as the second argument, like in the documentation example for widgets.interactive().

    You can still use a dictionary like in the documentation with a variable number of widgets being passed in, but you do essentially need to use kwargs. kwargs are really just a dictionary and you specify the handling when it goes into function with double asterisks. Since the interactive-ized function wants a dictionary, these are fully compatible and you just need the signal of the ** in the function parameter.

    Here is an option for implementing this idea fleshed out to more of what I think you were after:

    import ipywidgets as widgets
    categories = ["A", "B", "C"] # retrieve from dataset
    cbox = widgets.GridBox(children=[ widgets.Checkbox(value=True, description=v) for v in categories ])
    rbox = widgets.GridBox(children=[ widgets.IntRangeSlider(value=[1,10], max=20, min=0, description=v) for v in categories ])
    
    
    def analysis(**widget_dict):
        print("Hello. Currently I do nothing with the input!")
        variables = {}
        ranges = {}
        for k, v in widget_dict.items():
            if k.startswith('cbox'):
                variables[categories[int(k[4:])]] = v #slice of key leaves off 'cbox' and then use as integer to get relative category
            else:
                ranges[categories[int(k[4:])]] = v #slice of key leaves off 'rbox' and then use as integer to get relative category
        print("VARIABLES:")
        [print (f"  {category}: {value}") for category,value in variables.items()]
        print("RANGES:")
        [print (f"  {category}: {value}") for category,value in ranges.items()]
    
    widget_dict = {}
    for i, child in enumerate(cbox.children):
        widget_dict[f'cbox{i}'] = child
    for i, child in enumerate(rbox.children):
        widget_dict[f'rbox{i}'] = child
    
    display(widgets.Tab(children=[cbox, rbox], titles=('Variables', 'Ranges')))
    display(widgets.interactive_output(analysis, widget_dict))