Managing a lot of selections and widgets via dictionary, and using their data reactively

I have a lot of settings in the following format:

{
    key1: {
        cat1: [choices_k1c1],
        cat2: [choices_k1c2],
        ...
    },
    key2: {
        cat1: [choices_k2c1],
        cat2: [choices_k2c2],
        ...
    }
}

On top of that, the inner choices can have multiple selections (not the single select that NestedSelect requires), so what I have right now is using a NestedSelect to let the user choose the top two levels, and then a function bound to the NestedSelect that returns the appropriate inner MultiSelect widget. I correspondingly store all the widgets in a dictionary of dictionaries for retrieval.

The challenge for me now is that I have other analysis code that takes in all the user settings, but in a dictionary format, i.e. that inner level should be a list instead of a MultiSelect widget, i.e. I call f(dict_of_dict_of_lists).

How do I properly hook/bind this function up to all the user selections? My first instinct is that I should make a reactive dictionary that the widgets “write to” using callbacks, but I haven’t been able to get a d = param.Dict working. When I try to use it (e.g. d[label] = value, as I saw in other posts), I get errors like 'Dict' object does not support item assignment Note, I can get widget.param.watch to write properly to a regular dictionary and print the changes to the terminal in a callback. I just don’t know how to write to a reactive object that other functions can be bound to.

Downstream, I’m also doing a lot of more complex data transformations (e.g. manipulating columns, storing results processing in a column etc.). One problem that I’ve been running into is that rx(data frame) does not allow for setting values, only filtering. Am I missing something?

Thanks in advance!

minimal example of something I’ve tried:

choices_w1 = pn.widgets.MultiSelect(name='Choices',options=[1,2,3,4,5])
choices_w2 = pn.widgets.MultiSelect(name='Choices',options=[1,2,3,4,5])

# The following doesn't work
drx = pn.rx({1:choices_w1.param.value, 2:choices_w2.param.value, ...})  # try to bind into a reactive dictionary
pn.ReactiveExpr(drx) #when choices_w1,2 change, this view doesn't change. it also shows the default value of the multiselects instead of the actually selected values

# kind of works, doesn't seem like good practice, though
def collect(*args):
    return [*args]
collected = pn.bind(collect, choices_w1, choices_w2)
pn.panel(collected)

# updates for now
def collect(**kwargs):
    return dict(**kwargs)
collected = pn.bind(collect, w0=choices_w, w1=choices_w1, w2=choices_w2)
pn.panel(collected)

# how I'm driving subsequent actions
def pipeline(collected):
    tmp = {kw:val for kw,val in collected.items()}
    s = pd.Series(tmp)
    return s

evaluated = pn.bind(pipeline,collected) 
evaluated # renders ok 

Again, I am hoping to have a reactive dictionary drive subsequent changes, instead of having to track and explicitly enumerate the 30+ multiselect widgets.

Ultimately, my subsequent code is uses a pipeline of calculations that does not know about param and is used in other scripts, so hoping that there is something I can do to turn the widgets into a standard data structure – is the this design pattern I’m using ok, where I take a reactive object, process it into a normal pandas object, process it, then continue passing it on?

Thanks!

Is there a reason why you don’t like the collect functions you’ve got above? I have something very similar in my code (a reactive plot with lots of widgets wired in) and using pn.bind has really simplified things for me. I don’t think there’s anything wrong with this pattern.

@jerry.vinokurov thanks for the corroborating voice!

There are so many ways of doing things in panel that I was curious if there were other ways. One thing is that with thepn.bind, it forces me to use string keys and a flat dict, whereas originally due to my nested structures I was hoping to have a reactive nested dict and/or at least tuples as keys. For now, I’m converting the tuple to a string, and then ast.literal_evaling it back to recover the keys for the nested structure. Not the worst, but doesn’t feel clean.

The above felt a bit clunky, and not the most intuitive, so I was wondering if there may be some other reactive dictionary that would update when its elements update.

Thanks!

Ah, I think I see what you mean. Have you tried wiring a param.Dict to the inputs of pn.bind? That might do the trick.

I tried the following:

choices_w = pn.widgets.MultiSelect(name='Choices',options=[1,2,3,4,5])
choices_w1 = pn.widgets.MultiSelect(name='Choices',options=[1,2,3,4,5])
choices_w2 = pn.widgets.MultiSelect(name='Choices',options=[1,2,3,4,5])

class DRX(param.Parameterized):
    reactive_dict = param.Dict(default={},instantiate=True)
drx = DRX(reactive_dict={1:choices_w.param.value, 2:choices_w1, 3:choices_w2}) # variant 1, testing a couple ways to bind
drx2 = DRX(reactive_dict={1:{"choice":choices_w.param.value}, 
                         2:{"choice":choices_w1}, 
                         3:{"choice":choices_w2}
                         }) # variant 2, introducing nesting

# this updates properly in the view, i.e. changing widgets changes drx and results in drx.reactive-dict driving change
mdrx = pn.rx("value: {x}").format(x=drx.reactive_dict) 
pn.ReactiveExpr(mdrx)

This is probably expected, but calling drx.param.reactive_dict.rx.value did not resolve the rx.value of the inner widgets, meaning I think I have to chain the rx.value calls to get the widget values into a dict. This time around I decided not to introduce this extra complexity, but I may revisit this approach later. The bind behavior turned out to be easier to reason about.

1 Like