英文:
Scatter Plot Not Updating With Widget Selection in Python Panel Holoviz
问题
以下是您提供的内容的翻译:
我想要一个带有动态图表的Python Panel应用程序,根据侧边栏中选择的菜单/按钮不同,显示不同的图表。因此,我从这个很好的起点开始:https://discourse.holoviz.org/t/multi-page-app-documentation/3108/2
每个页面都有一个正弦和余弦图,根据小部件的值进行更新,使用@pn.depends
。
然后,我更新了代码,使正弦图成为散点图,小部件变成选择下拉菜单,而不是滑块。但现在,当我更新选择下拉小部件时,散点图不会更新。我做错了什么?显然,我对@pn.depends()
的工作原理或不工作原理有什么误解。任何帮助都将不胜感激!
完整的代码(app.py
)如下:
import pandas as pd
import panel as pn
import holoviews as hv
import hvplot.pandas
import numpy as np
import time
from datetime import datetime
pn.extension()
template = pn.template.FastListTemplate(title='My Dashboard')
# 加载详细数据
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']
df = pd.DataFrame({'Duration': durations,
'ActivityCode': activity_codes,
'ActivityCategory': activity_categories})
# 第一页小部件控制
ac_categories = ['A', 'B', 'C']
ac_cat_radio_button = pn.widgets.Select(name='Activity Category', options=ac_categories)
# 第一页绘图代码
@pn.depends(ac_cat=ac_cat_radio_button)
def scatter_detail_by_ac(df, ac_cat):
print(f"ac_cat is {ac_cat}")
print(f"ac_cat.value is {ac_cat.value}")
df_subset = df.loc[df.ActivityCategory==ac_cat.value]
print(f"number of records in df_subset to scatter plot: {len(df_subset):,}")
return df_subset.hvplot.scatter(x='ActivityCode', y='Duration')
freq2 = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
phase2 = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
@pn.depends(freq=freq2, phase=phase2)
def cosine(freq, phase):
xs = np.linspace(0, np.pi)
return hv.Curve((xs, np.cos(xs*freq+phase))).opts(
responsive=True, min_height=400)
page = pn.Column(sizing_mode='stretch_width')
content1 = [
pn.Row(ac_cat_radio_button),
scatter_detail_by_ac(df, ac_cat_radio_button),
]
content2 = [
pn.Row(freq2, phase2),
hv.DynamicMap(cosine),
]
link1 = pn.widgets.Button(name='Scatter')
link2 = pn.widgets.Button(name='Cosine')
template.sidebar.append(link1)
template.sidebar.append(link2)
template.main.append(page)
def load_content1(event):
template.main[0].objects = content1
def load_content2(event):
template.main[0].objects = content2
link1.on_click(load_content1)
link2.on_click(load_content2)
template.show()
我通过以下方式在本地运行此Panel应用程序:
panel serve app.py --autoreload
另外,以下是我使用的库版本,来自我的requirements.in
(我将其编译为requirements.txt/lockfile):
panel==v1.0.0rc6
pandas==1.5.3
holoviews==1.16.0a2
hvplot
pandas-gbq>=0.19.1
希望这有所帮助!
英文:
I want a Python Panel app with dynamic plots, and different plots depending on which menu/button is selected from a sidepanel, so I started from this great starting point: https://discourse.holoviz.org/t/multi-page-app-documentation/3108/2
Each page had a sine & cosine plot which updated based on widget values, using @pn.depends
.
Then I updated the code so that the sine plot would be a scatter plot, and the widget would be a selection drop-down menu instead of a slider. But now that scatter plot is not updating when I update the selection drop-down widget. What am I doing wrong? Clearly I’m misunderstanding something about what @pn.depends()
is doing or not doing. Any help would be super appreciated!
Full code (app.py
) below:
import pandas as pd
import panel as pn
import holoviews as hv
import hvplot.pandas
import numpy as np
import time
from datetime import datetime
pn.extension()
template = pn.template.FastListTemplate(title='My Dashboard')
# load detailed data
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']
df = pd.DataFrame({'Duration': durations,
'ActivityCode': activity_codes,
'ActivityCategory': activity_categories})
# Page 1 Widget Controls
ac_categories = ['A', 'B', 'C']
ac_cat_radio_button = pn.widgets.Select(name='Activity Category', options=ac_categories)
# Page 1 Plotting Code
@pn.depends(ac_cat=ac_cat_radio_button)
def scatter_detail_by_ac(df, ac_cat):
print(f"ac_cat is {ac_cat}")
print(f"ac_cat.value is {ac_cat.value}")
df_subset = df.loc[df.ActivityCategory==ac_cat.value]
print(f"number of records in df_subset to scatter plot: {len(df_subset):,}")
return df_subset.hvplot.scatter(x='ActivityCode', y='Duration')
freq2 = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
phase2 = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
@pn.depends(freq=freq2, phase=phase2)
def cosine(freq, phase):
xs = np.linspace(0,np.pi)
return hv.Curve((xs, np.cos(xs*freq+phase))).opts(
responsive=True, min_height=400)
page = pn.Column(sizing_mode='stretch_width')
content1 = [
pn.Row(ac_cat_radio_button), #grouping_vars_radio_button),
scatter_detail_by_ac(df, ac_cat_radio_button),
]
content2 = [
pn.Row(freq2, phase2),
hv.DynamicMap(cosine),
]
link1 = pn.widgets.Button(name='Scatter')
link2 = pn.widgets.Button(name='Cosine')
template.sidebar.append(link1)
template.sidebar.append(link2)
template.main.append(page)
def load_content1(event):
template.main[0].objects = content1
def load_content2(event):
template.main[0].objects = content2
link1.on_click(load_content1)
link2.on_click(load_content2)
template.show()
I serve this Panel app locally by running (from shell):
panel serve app.py --autoreload
Also, here's the library versions I'm using, from my requirements.in
(which I pip-compile
into a requirements.txt/lockfile):
panel==v1.0.0rc6
pandas==1.5.3
holoviews==1.16.0a2
hvplot
pandas-gbq>=0.19.1
答案1
得分: 2
你遇到的问题是当你使用 pn.depends
标记一个函数时,你需要将它原封不动地传递给 Panel,Panel 会负责在 pn.depends
装饰器中列出的任何小部件更新时重新执行它并重新渲染其输出。以下是修复你问题的代码的简化版本:
import pandas as pd
import panel as pn
import hvplot.pandas
import numpy as np
pn.extension()
# 加载详细数据
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']
df = pd.DataFrame({'Duration': durations,
'ActivityCode': activity_codes,
'ActivityCategory': activity_categories})
# 第一页小部件控制
ac_categories = ['A', 'B', 'C']
ac_cat_radio_button = pn.widgets.Select(name='Activity Category', options=ac_categories)
from functools import partial
# 第一页绘图代码
@pn.depends(ac_cat=ac_cat_radio_button)
def scatter_detail_by_ac(df=df, ac_cat=None):
df_subset = df.loc[df.ActivityCategory == ac_cat]
print(f"要散点图的 df_subset 记录数: {len(df_subset):,}")
return df_subset.hvplot.scatter(x='ActivityCode', y='Duration')
pn.Column(ac_cat_radio_button, scatter_detail_by_ac)
通常,建议 Panel 用户在希望在其应用程序中添加交互性时使用 pn.bind
而不是 pn.depends
。它的行为类似于 functools.partial
,使得它更容易上手,特别是如果你已经熟悉了 functools.partial
。
特别是对于数据应用程序,你还可以利用 hvplot.interactive
,这是一个可以用 Panel 小部件替换流水线值的 API。我使用这个 API 重新编写了你的应用程序:
import pandas as pd
import panel as pn
import hvplot.pandas
import numpy as np
pn.extension(sizing_mode='stretch_width')
# 加载详细数据
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']
df = pd.DataFrame({'Duration': durations,
'ActivityCode': activity_codes,
'ActivityCategory': activity_categories})
# 应用程序 1
w_ac_cat = pn.widgets.Select(name='Activity Category', options=['A', 'B', 'C'])
dfi = df.interactive()
dfi = dfi.loc[dfi.ActivityCategory == w_ac_cat]
app1 = dfi.hvplot.scatter(x='ActivityCode', y='Duration', responsive=True, min_height=400)
# 应用程序 2
def cosine(freq, phase):
xs = np.linspace(0, np.pi)
return pd.DataFrame(dict(y=np.cos(xs * freq + phase)), index=xs)
w_freq = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
w_phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
dfi_cosine = hvplot.bind(cosine, w_freq, w_phase).interactive()
app2 = pn.Column(
pn.Row(*dfi_cosine.widgets()),
dfi_cosine.hvplot(responsive=True, min_height=400).output()
)
# 模板
page = pn.Column(sizing_mode='stretch_width')
link1 = pn.widgets.Button(name='Scatter')
link2 = pn.widgets.Button(name='Cosine')
template = pn.template.FastListTemplate(
title='My Dashboard', main=[page], sidebar=[link1, link2]
)
def load_content1(event):
template.main[0][:] = [app1]
def load_content2(event):
template.main[0][:] = [app2]
link1.on_click(load_content1)
link2.on_click(load_content2)
template.show()
希望这对你有所帮助!
英文:
The issue you encountered is that when you mark a function with pn.depends
then you need to pass it as is to Panel, which will take care of re-executing it and re-rendering its output anytime one of the listed widgets in the pn.depends
decorator is updated. Here's a simplified version of your code, fixing the issue you had:
import pandas as pd
import panel as pn
import hvplot.pandas
import numpy as np
pn.extension()
# load detailed data
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']
df = pd.DataFrame({'Duration': durations,
'ActivityCode': activity_codes,
'ActivityCategory': activity_categories})
# Page 1 Widget Controls
ac_categories = ['A', 'B', 'C']
ac_cat_radio_button = pn.widgets.Select(name='Activity Category', options=ac_categories)
from functools import partial
# Page 1 Plotting Code
@pn.depends(ac_cat=ac_cat_radio_button)
def scatter_detail_by_ac(df=df, ac_cat=None):
df_subset = df.loc[df.ActivityCategory==ac_cat]
print(f"number of records in df_subset to scatter plot: {len(df_subset):,}")
return df_subset.hvplot.scatter(x='ActivityCode', y='Duration')
pn.Column(ac_cat_radio_button, scatter_detail_by_ac)
Generally Panel users are now recommended to use pn.bind
instead of pn.depends
when they want to add interactivity in their apps. Its behavior is similar to functools.partial
which makes it easier to approach, specially if you're already acquainted with functools.partial
.
Now when it comes more specifically to data apps, such as yours, you can also leverage hvplot.interactive
which is an API with which you can replace pipeline values by Panel widgets. I re-wrote your cool app using this API:
import pandas as pd
import panel as pn
import hvplot.pandas
import numpy as np
pn.extension(sizing_mode='stretch_width')
# load detailed data
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']
df = pd.DataFrame({'Duration': durations,
'ActivityCode': activity_codes,
'ActivityCategory': activity_categories})
# App 1
w_ac_cat = pn.widgets.Select(name='Activity Category', options=['A', 'B', 'C'])
dfi = df.interactive()
dfi = dfi.loc[dfi.ActivityCategory == w_ac_cat]
app1 = dfi.hvplot.scatter(x='ActivityCode', y='Duration', responsive=True, min_height=400)
# App 2
def cosine(freq, phase):
xs = np.linspace(0, np.pi)
return pd.DataFrame(dict(y=np.cos(xs*freq+phase)), index=xs)
w_freq = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
w_phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
dfi_cosine = hvplot.bind(cosine, w_freq, w_phase).interactive()
app2 = pn.Column(
pn.Row(*dfi_cosine.widgets()),
dfi_cosine.hvplot(responsive=True, min_height=400).output()
)
# Template
page = pn.Column(sizing_mode='stretch_width')
link1 = pn.widgets.Button(name='Scatter')
link2 = pn.widgets.Button(name='Cosine')
template = pn.template.FastListTemplate(
title='My Dashboard', main=[page], sidebar=[link1, link2]
)
def load_content1(event):
template.main[0][:] = [app1]
def load_content2(event):
template.main[0][:] = [app2]
link1.on_click(load_content1)
link2.on_click(load_content2)
template.show()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论