如何通过单击条形图段来过滤表格?

问题描述 投票:0回答:1

我使用 palmer penguins 数据集使用plotly-r 构建了一个 RShiny 仪表板,这样当我单击条形图部分时,它会使用该事件数据来过滤数据集。

悬停时:
enter image description here

点击:
enter image description here

我想使用 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() 方法。

python plotly-python reactive py-shiny
1个回答
0
投票

下面是一个变体,我更改了几个部分,以下是最重要的部分:

  • 在您的示例中,

    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
    事件,这包括在下面。

看起来像这样:

enter image description here

# 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)
© www.soinside.com 2019 - 2024. All rights reserved.