Share parameter-dependent object between panes?

Hi all,

I would like to do various operations on a table given a set of parameters, but want to avoid performing the operations more than once. What is the correct way to do this? I’m guessing there is an embarrassingly simple answer but I spent a couple hours going through docs/gallery examples and couldn’t figure it out.

Example:

 class VolcanoPlots(param.Parameterized):
    genes = param.Selector(objects=gene_list)
    comparison = param.Selector(objects=comparison_list)

    def view(self):
        # Pandas operations 
        sub = des[des.label == self.comparison]
        sub = sub[sub.gene_identifier.isin(gene_sets[self.genes])]
        plot = sub.hvplot.scatter(...)
        return plot
    
    def table(self):
        # Same pandas operations that I want to avoid doing a second time
        sub = des[des.label == self.comparison]
        sub = sub[sub.gene_identifier.isin(gene_sets[self.genes])]
        return pn.pane.DataFrame(sub, width=1000, max_rows=25)

I want a final panel output like:

obj = VolcanoPlots()
panel = pn.Column(
    pn.Row(obj.param, obj.view),
    obj.table
)

I tried several things such as returning both in the same method, but then couldn’t figure out how to re-compose the panes as I wanted.

Have you tried this?

class VolcanoPlots(param.Parameterized):
    genes = param.Selector(objects=gene_list)
    comparison = param.Selector(objects=comparison_list)

    def view(self):
        # Pandas operations 
        sub = des[des.label == self.comparison]
        sub = sub[sub.gene_identifier.isin(gene_sets[self.genes])]
        plot = sub.hvplot.scatter(...)
        return plot

    def table(self):
        # Same pandas operations that I want to avoid doing a second time
        sub = des[des.label == self.comparison]
        sub = sub[sub.gene_identifier.isin(gene_sets[self.genes])]
        return pn.pane.DataFrame(sub, width=1000, max_rows=25)

    def panel(self):
        return pn.Column(pn.Row(self.param,self.view),self.table)

obj = VolcanoPlots().panel().servable()

However, that doesn’t allow you to recompose the panes.
Maybe you want something like the panel pipelines?

Hi @jvivian and @Material-Scientist

You could do something like the below. Or alternatively refactor the transformation part into a seperate function depending on comparison_list and gene_list and use caching of the result.

import pandas as pd
import param
import panel as pn
import hvplot.pandas

data = {
    "obs": [1,2,3,4,5,6,7,8],
    "label": ["a","b", "a", "b", "a","b", "a", "b"],
    "gene_identifier": ["1","1", "2", "2", "2","2", "1", "1"],
    "value": [1,2,2,4,7,6,9,8],
}
des = pd.DataFrame(data)
gene_list = list(des["gene_identifier"].unique())
comparison_list = list(des["label"].unique())

class VolcanoPlots(param.Parameterized):
    comparison = param.Selector(objects=comparison_list)
    genes = param.Selector(objects=gene_list)

    source_data = param.DataFrame()
    transform_data = param.DataFrame()

    def __init__(self, **params):
        super().__init__(**params)

        self._plot_pane = pn.pane.HoloViews()
        self._selections_pane = pn.Param(self, parameters=["comparison", "genes"])
        self._table_pane = pn.pane.DataFrame(width=1000, max_rows=25)

        self._update()

    @param.depends("comparison", "genes", watch=True)
    def _update(self):
        source_data = self.source_data
        sub = source_data[source_data.label == self.comparison]
        sub = sub[sub.gene_identifier == self.genes]
        self.transform_data = sub

    @param.depends("transform_data", watch=True)
    def _update_plot(self):
        self._plot_pane.object = self.transform_data.hvplot.scatter(x="obs", y="value")

    @param.depends("transform_data", watch=True)
    def _update_table(self):
        self._table_pane.object = self.transform_data

    def panel(self):
        return pn.Column(pn.Row(self._selections_pane,self._plot_pane),self._table_pane)

obj = VolcanoPlots(source_data=des).panel().servable()

2 Likes

Hi @Marc — this is precisely what I was looking for, thank you for taking the time to write it up. I was trying to do something similar by creating a None object that later stored the transformed table, but couldn’t quite get it to work.

1 Like

Hi @jvivian

Thanks for asking a great question.

I think this way of “architecting” a component or app is great. One of the super powers of Param/ Panel.

One tip though is not to build too big components. Because if there are a lot of @depends or watch then you (or at least I :-)) can get overwhelmed. Then it’s time to refactor into smaller, independent components.

I believe Dash can generate some kind of dependency tree viz for their callbacks. Would be great to have in Panel as well.