How to create Param attributes using string list i.e. creating flexible param classes

Hi, I want to create a class which has a param.Integers attribute for each name in a list (columns = [‘bike’,‘car’,‘plane’,‘train’]).If I hardcode the attributes and the depends names it works, but this is not flexible. See screenshot and code underneath.

How can I implement this given only the list of attribute names? I’ve tried some approaches but failed: see lower…

This figure shows what I want


This code is not ideal/flexible

class MultiParameter(param.Parameterized):
    columns = ['bike','car','plane','train']
    bike = param.Integer(default=4,bounds=(1,10))
    car = param.Integer(default=4,bounds=(1,10))
    plane = param.Integer(default=4,bounds=(1,10))
    train = param.Integer(default=4,bounds=(1,10))
    plot_vars = param.ListSelector(default=columns[:1],
                                   objects=columns)
    data_par = param.DataFrame(pd.DataFrame(data=np.random.rand(100,4),
                                            columns=columns))
    
    @pn.depends('plot_vars','bike','car','plane','train')
    def plot_compare(self):
        layouts = []
        for var in self.plot_vars:
            scatter = self.data_par.hvplot.scatter(y=var,label=var)
            hlines = [hv.HLine(qi) for qi in self.get_quantiles(var)]
            scatterandlines = hv.Overlay([scatter]+hlines).opts(show_legend=False)
            layouts.append(scatterandlines)
        return hv.Layout(layouts).cols(2)
    
    def get_quantiles(self,var):
        val = eval(f'self.{var}')
        return self.data_par[var].quantile([(ii+.5)/val for ii in range(val)])
    
    def layout(self):
        return pn.Column(pn.Row(pn.Param(self.param['plot_vars']),
                                pn.Param(self,parameters=self.plot_vars,show_name=False)),
                         self.plot_compare)

These two approaches aren’t working for me:

1. separate class & objects
Create a separate Parameterized class Bin

class Bin(param.Parameterized):
   value = param.Integer(4,bounds=(1,10))

columns = ['bike','car','plane','train']
mybins = [Bin(name=x) for x in columns]

add ObjectSelector with list of Bin objects

class MultiParameter(param.Parameterized):
    ...
    bin_var = param.ObjectSelector(default=mybins[0],objects=mybins)
    ....
    
    @pn.depends('bin_var.param','bin_var', watch=True)
    def some_function(self):

Issue!
I fail to link the parameter changes on the Bin objects
and not very neat.

2. add a param.Parametrized_class object (seemed promising: Programatically create Param Classes - #2 by AurelienSciarra)

class MultiParameter(param.Parameterized):
    ...
    mybins = param.parameterized_class('mybins',
         {var:param.Integer(default=4,
                            bounds=(1,10)) for var in columns})
    ....
    

seems to work as a param object…

m=MultiParameter()
pn.Param(m.mybins)

param_layout
…but fails when I use it in another method/panel.

Issue! fails on:
RecursionError: maximum recursion depth exceeded while calling a Python object which crashes my notebook

So to summarize: How can I programmatically add attributes in a Param class?
Thanks in advance for thinking about this.

1 Like

Solved it using the undocumented _add_parameter(name,param.Parameter) method in the __init__ method.

    def __init__(self,**params):
        super().__init__(**params)
        for attribute in variable_list_strings:
            self._add_parameter(attribute, param.Integer(default=4,bounds=(1,10)))

Below a working example.

columns = ['bike','car','plane','train']
class MultiParameter(param.Parameterized):
    
#     bike = param.Integer(default=4,bounds=(1,10))
#     car = param.Integer(default=4,bounds=(1,10))
#     plane = param.Integer(default=4,bounds=(1,10))
#     train = param.Integer(default=4,bounds=(1,10))
    plot_vars = param.ListSelector(default=columns[:1],
                                   objects=columns)
    data_par = param.DataFrame(pd.DataFrame(data=np.random.rand(100,4),
                                            columns=columns))
    
    def __init__(self,**params):
        super().__init__(**params)
        for col in columns:
            self._add_parameter(col,param.Integer(default=4,bounds=(1,10)))
            
    @pn.depends('plot_vars',*columns)
    def plot_compare(self):
        layouts = []
        for var in self.plot_vars:
            scatter = self.data_par.hvplot.scatter(y=var,label=var)
            hlines = [hv.HLine(qi).opts(color='k') for qi in self.get_quantiles(var)]
            scatterandlines = hv.Overlay([scatter]+hlines).opts(show_legend=False)
            layouts.append(scatterandlines)
        return hv.Layout(layouts).cols(2)
    
    def get_quantiles(self,var):
        val = eval(f'self.{var}')
        return self.data_par[var].quantile([(ii+.5)/val for ii in range(val)])
    
    def layout(self):
        return pn.Column(pn.Row(pn.Param(self.param['plot_vars']),
                                pn.Param(self,parameters=self.plot_vars,show_name=False)),
                         self.plot_compare)

m = MultiParameter()
pn.Pane(m.layout)
3 Likes

Thanks for sharing. This helps inspire and build the knowledge base.

1 Like