我使用 palmer penguins 数据集使用plotly-r 构建了一个 RShiny 仪表板,这样当我单击条形图部分时,它会使用该事件数据来过滤数据集。
我想使用 Shiny for Python 构建一个类似的仪表板,但无法让
customdata
以 R 中的方式工作,而且我不知道如何处理单击事件。
我已经能够建立自定义数据对象来捕获我想要的每个条形段的分类数据,但我无法在 python 的plotly中找到
event_click()
函数类似于plotly-r .
有人可以告诉我如何让这个功能发挥作用吗? 目前,此代码运行闪亮的 python 仪表板,但不响应条形图上任何位置的任何类型的单击事件。
我想我可能需要服务器功能的 @render_widget 部分的帮助。 这是我的Python源代码:
# Load data and compute static values
from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_widget, render_plotly
from plotnine import ggplot, aes, geom_bar
import plotly.graph_objects as go
import palmerpenguins
from plotly.callbacks import Points, InputDeviceState
points, state = Points(), InputDeviceState()
df_penguins = palmerpenguins.load_penguins()
dict_category = {'species':'Species','island':'Island','sex':'Gender'}
def filter_shelf():
return ui.card(
ui.card_header(
"Filters",
align="center",
),
# Gender Filter
ui.input_checkbox_group(
'sex_filter',
label='Gender',
choices={value:(value.capitalize() if (type(value)==str) else value) for value in df_penguins['sex'].unique()},
selected=list(df_penguins['sex'].unique()),
),
# Species Filter
ui.input_checkbox_group(
'species_filter',
label='Species',
choices=list(df_penguins['species'].unique()),
selected=list(df_penguins['species'].unique()),
),
# Island Filter
ui.input_checkbox_group(
'island_filter',
label='Island',
choices=list(df_penguins['island'].unique()),
selected=list(df_penguins['island'].unique()),
),
)
def parameter_shelf():
return ui.card(
ui.card_header(
'Parameters',
align='center',
),
# Category Selector
ui.input_radio_buttons(
'category',
label = 'View Penguins by:',
choices=dict_category,
selected = 'species',
),
),
app_ui = ui.page_fluid(
ui.h1("Palmer Penguins Analysis"),
ui.layout_sidebar(
# Left Sidebar
ui.sidebar(
filter_shelf(),
parameter_shelf(),
width=250,
),
# Main Panel
ui.card( # Plot
ui.card_header(ui.output_text('chart_title')),
output_widget('penguin_plot'),
),
ui.card( # Table
ui.card_header(ui.output_text('total_rows')),
ui.column(
12, #width
ui.output_table('table_view'),
style="height:300px; overflow-y: scroll"
)
)
),
)
def server (input, output, session):
@reactive.calc
def category():
'''This function caches the appropriate Capitalized form of the selected category'''
return dict_category[input.category()]
# Dynamic Chart Title
@render.text
def chart_title():
return "Number of Palmer Penguins by Year, colored by "+category()
@reactive.calc
def df_filtered_stage1():
'''This function caches the filtered datframe based on selections in the view'''
return df_penguins[
(df_penguins['species'].isin(input.species_filter())) &
(df_penguins['island'].isin(input.island_filter())) &
(df_penguins['sex'].isin(input.sex_filter()))]
@reactive.calc
def df_filtered_stage2():
df_filtered_st2 = df_filtered_stage1()
# Eventually add additional filters on dataset from segments selected on the visual
return df_filtered_st2
@reactive.calc
def df_summarized():
return df_filtered_stage2().groupby(['year',input.category()], as_index=False).count().rename({'body_mass_g':"count"},axis=1)[['year',input.category(),'count']]
@reactive.calc
def filter_fn():
print("Clicked!") # This never gets called
@render_widget
def penguin_plot():
df_plot = df_summarized()
bar_columns = list(df_plot['year'].unique()) # x axis column labels
bar_segments = list(df_plot[input.category()].unique()) # bar segment category labels
data = [go.Bar(name=segment, x=bar_columns,y=list(df_plot[df_plot[input.category()]==segment]['count'].values), customdata=[input.category()], customdatasrc='A') for segment in bar_segments]
fig = go.Figure(data)
fig.update_layout(barmode="stack")
fig = go.FigureWidget(fig)
##### TRYING TO CAPTURE CLICK EVENT ON A BAR SEGMENT HERE #####
fig.data[0].on_click(
filter_fn
)
return fig
@render.text
def total_rows():
return "Total Rows: "+str(df_filtered_stage1().shape[0])
@render.table
def table_view():
df_this=df_summarized()
return df_filtered_stage1()
app = App(app_ui, server)
看来我只能通过跟踪捕获点击事件。 我想知道是否有比我上面所做的更好的方法,因为当通过“Species”(Gentoo、Chinstrap 和 Adelie)查看时,fig.data 在运行时有 3 个条形跟踪,并且似乎每个条形跟踪是什么获取 on_click() 方法。
下面是一个变体,我更改了几个部分,以下是最重要的部分:
在您的示例中,
filter_fn
永远不会被调用,因为它不依赖于任何reactive
表达式。 Shiny
无需调用它。
你可以这样做:我们定义一个名为
reactive.value
的 filter
,其中包含来自 on_click
事件 (filter = reactive.value({})
) 的过滤器信息和函数
def setClickedFilterValues(trace, points, selector):
if not points.point_inds:
return
filter.set({"Year": points.xs, "Category": points.trace_name})
设置为每条轨迹上的
on_click
事件:
for trace in fig.data:
trace.on_click(setClickedFilterValues)
函数中的
if
子句检查您是否位于单击的轨迹上,如果不是,则停止。 filter
然后包含正确的值。这里重要的一点是该函数没有像 reactive
这样的 @reactive.calc
装饰器。这不是必需的,因为我们只更新值。
我修改了
df_filtered_stage2()
以考虑filter
,这将计算应用程序下面显示的输出的数据框。
@reactive.calc
def df_filtered_stage2():
if filter.get() == {}:
return df_filtered_stage1()
df_filtered_st2 = df_filtered_stage1()[
(df_filtered_stage1()['year'].isin(filter.get()['Year'])) &
(df_filtered_stage1()[input.category()].isin([filter.get()['Category']]))
]
return df_filtered_st2
与上面类似,您的
R
应用程序可以实现on_hover
事件,这包括在下面。
看起来像这样:
# Load data and compute static values
from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_widget, render_plotly
from plotnine import ggplot, aes, geom_bar
import plotly.graph_objects as go
import palmerpenguins
from plotly.callbacks import Points, InputDeviceState
points, state = Points(), InputDeviceState()
df_penguins = palmerpenguins.load_penguins()
dict_category = {'species':'Species','island':'Island','sex':'Gender'}
def filter_shelf():
return ui.card(
ui.card_header(
"Filters",
align="center",
),
# Gender Filter
ui.input_checkbox_group(
'sex_filter',
label='Gender',
choices={value:(value.capitalize() if (type(value)==str) else value) for value in df_penguins['sex'].unique()},
selected=list(df_penguins['sex'].unique()),
),
# Species Filter
ui.input_checkbox_group(
'species_filter',
label='Species',
choices=list(df_penguins['species'].unique()),
selected=list(df_penguins['species'].unique()),
),
# Island Filter
ui.input_checkbox_group(
'island_filter',
label='Island',
choices=list(df_penguins['island'].unique()),
selected=list(df_penguins['island'].unique()),
),
)
def parameter_shelf():
return ui.card(
ui.card_header(
'Parameters',
align='center',
),
# Category Selector
ui.input_radio_buttons(
'category',
label = 'View Penguins by:',
choices=dict_category,
selected = 'species',
),
),
app_ui = ui.page_fluid(
ui.h1("Palmer Penguins Analysis"),
ui.layout_sidebar(
# Left Sidebar
ui.sidebar(
filter_shelf(),
parameter_shelf(),
width=250,
),
# Main Panel
ui.card( # Plot
ui.card_header(ui.output_text('chart_title')),
output_widget('penguin_plot'),
),
ui.output_text_verbatim('hoverInfoOutput'),
ui.card( # Table
ui.card_header(ui.output_text('total_rows')),
ui.column(
12, #width
ui.output_table('table_view'),
style="height:300px; overflow-y: scroll"
)
)
),
)
def server (input, output, session):
filter = reactive.value({})
hoverInfo = reactive.value({})
@reactive.calc
def category():
'''This function caches the appropriate Capitalized form of the selected category'''
return dict_category[input.category()]
# Dynamic Chart Title
@render.text
def chart_title():
return "Number of Palmer Penguins by Year, colored by "+category()
@reactive.calc
def df_filtered_stage1():
'''This function caches the filtered datframe based on selections in the view'''
return df_penguins[
(df_penguins['species'].isin(input.species_filter())) &
(df_penguins['island'].isin(input.island_filter())) &
(df_penguins['sex'].isin(input.sex_filter()))]
@reactive.calc
def df_filtered_stage2():
if filter.get() == {}:
return df_filtered_stage1()
df_filtered_st2 = df_filtered_stage1()[
(df_filtered_stage1()['year'].isin(filter.get()['Year'])) &
(df_filtered_stage1()[input.category()].isin([filter.get()['Category']]))
]
return df_filtered_st2
@reactive.calc
def df_summarized():
return df_filtered_stage1().groupby(['year',input.category()], as_index=False).count().rename({'body_mass_g':"count"},axis=1)[['year',input.category(),'count']]
def setClickedFilterValues(trace, points, selector):
if not points.point_inds:
return
filter.set({"Year": points.xs, "Category": points.trace_name})
def setHoverValues(trace, points, selector):
if not points.point_inds:
return
hoverInfo.set(points)
@render_widget
def penguin_plot():
df_plot = df_summarized()
bar_columns = list(df_plot['year'].unique()) # x axis column labels
bar_segments = list(df_plot[input.category()].unique()) # bar segment category labels
data = [go.Bar(name=segment, x=bar_columns,y=list(df_plot[df_plot[input.category()]==segment]['count'].values), customdata=[input.category()], customdatasrc='A') for segment in bar_segments]
fig = go.Figure(data)
fig.update_layout(barmode="stack")
fig = go.FigureWidget(fig)
for trace in fig.data:
trace.on_click(setClickedFilterValues)
trace.on_hover(setHoverValues)
return fig
@render.text
def hoverInfoOutput():
return hoverInfo.get()
@render.text
def total_rows():
return "Total Rows: "+str(df_filtered_stage2().shape[0])
@render.table
def table_view():
#df_this=df_summarized()
return df_filtered_stage2()
app = App(app_ui, server)