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
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))