Question
I’ve recently started using Panel again to develop a small app to do a calculation and display the results. I’m using the class design pattern the app and using the @pn.depends
decorator to define the reactivity.
The app allows the user to select various options, which filters the original data sets, and then performs a calculation when the filtered datasets are updated. A simplified example with dummy data is given below.
However, when I start the app, I get a TypeError: 'NoneType' object is not subscriptable
. From debugging, I think is is because on initialisation dataset1_filtered
and dataset2_filtered
are empty, but the calculation is still trying to run as the parameters are initialised.
Is there a better design pattern for doing something like this? Effectively, I don’t want the calculation to run until all other parameters have been initialised and the data sets have been filtered. Would using reactive expressions (i.e. rx()
) be more suitable?
Open to thoughts and ideas.
Simplified example app
"""Example app"""
import random
import pandas as pd
import panel as pn
import param
from panel.viewable import Viewer
@pn.cache
def get_dataset1():
"""Get dataset 1. This is dummy data."""
df = pd.read_csv("land_use_change_carbon_factors.csv")
df = pd.DataFrame(
{
"A": [1, 2, 3, 4, 5],
"B": ["foo", "bar", "foo", "bar", "foo"],
"C": [random.randint(1, 10) for i in range(5)],
}
)
return df
@pn.cache
def get_dataset2():
"""Get the dataset 2. This is dummy data."""
df = pd.DataFrame(
{
"A": [1, 2, 3, 4, 5],
"BB": ["A", "A", "B", "B", "C"],
"CC": [random.randint(10, 20) for i in range(5)],
"DD": [random.randint(100, 200) for i in range(5)],
}
)
return df
class CarbonChangeCalculator(Viewer):
"""The carbon change calculator app."""
data1 = param.DataFrame()
data2 = param.DataFrame()
A = param.Selector(objects=[1, 2, 3, 4, 5])
B = param.Selector(objects=["foo", "bar"])
BB = param.Selector(objects=["A", "B", "C"])
factor = param.Number(default=10)
data1_filtered = param.DataFrame()
data2_filtered = param.DataFrame()
def __init__(self, **params):
super().__init__(**params)
@pn.depends("A", "B", watch=True, on_init=True)
def _update_data1_filtered(self):
self.data1_filtered = self.data1[
(self.data1["A"] == self.A) & (self.data1["B"] == self.B)
]
@pn.depends("A", "BB", watch=True, on_init=True)
def _update_data2_filtered(self):
self.data2_filtered = self.data2[
(self.data2["A"] == self.A) & (self.data2["BB"] == self.BB)
]
@pn.depends("data1_filtered", "data2_filtered", "factor", watch=True)
def calculate(self):
a = self.data1_filtered["C"].sum() * self.factor
b = self.data2_filtered["CC"].sum()
return a + b
def __panel__(self):
return pn.Row(
pn.Column(
self.param.A,
self.param.B,
self.param.BB,
self.param.factor
),
pn.Column(
pn.pane.DataFrame(self.param.data1_filtered),
pn.pane.DataFrame(self.param.data2_filtered)
),
pn.Column(
pn.pane.Markdown("## Results"),
pn.pane.Markdown("The calculated value is:"),
pn.rx(self.calculate)
)
)
data1 = get_dataset1()
data2 = get_dataset2()
app = CarbonChangeCalculator(data1=data1, data2=data2)
app.servable()