【问题标题】:Plotly python : how to make a dropdown menu with several lines?Plotly python:如何制作包含多行的下拉菜单?
【发布时间】:2022-10-25 11:09:28
【问题描述】:

我想在一个绘图线图中制作一个下拉菜单,例如in this post,但有几条线。
让我们看一个示例数据框:

import pandas as pd
df = pd.DataFrame({"Date": ["2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03"],
                   "Animal" :["Cat","Cat","Cat","Cat","Cat","Cat","Dog","Dog","Dog","Dog","Dog","Dog"],
                   "Category":["Small","Small","Small","Big","Big","Big","Small","Small","Small","Big","Big","Big"],
                   "Quantity":[2,4,3,5,1,2,6,5,6,4,2,1]})

df["Date"] = df["Date"].astype('datetime64')

我想用 x 轴上的Date、y 轴上的Quantity、每个Animal 的曲线和每个Category 的过滤器制作一个曲线图。我尝试了以下功能,但结果不好,因为线条在晃动。请问你知道我的错误在哪里吗?

import plotly.graph_objects as go
def plot_line_go_graph(df,col_x,col_y,col_color = None,col_filter = None,add_points = False) :
    
    df_graph = df.copy()
        
    if add_points :
        param_mode='lines+markers'
        param_name='lines+markers'
    else :
        param_mode='lines'
        param_name='lines'
    
    fig = go.Figure()
    
    if col_filter is None :
    
        if col_color is None :
            fig.add_trace(go.Scatter(x=df_graph[col_x], y=df_graph[col_y],mode=param_mode,name=param_name))
        else :
            for c in df_graph[col_color].unique() :
                fig.add_trace(go.Scatter(x=df_graph[df_graph[col_color]==c][col_x], y=df_graph[df_graph[col_color]==c][col_y],mode=param_mode,name=c))
                
    else :
        
        df_graph[col_filter] = df_graph[col_filter].fillna("NaN")
        
        if col_color is None :
            fig.add_trace(go.Scatter(x=df_graph[col_x], y=df_graph[col_y],mode=param_mode,name=param_name,visible = True))
        else :
            for c in df_graph[col_color].unique() :
                fig.add_trace(go.Scatter(x=df_graph[df_graph[col_color]==c][col_x], y=df_graph[df_graph[col_color]==c][col_y],mode=param_mode,name=c,visible = True))
        
        updatemenu = []
        buttons = []

        # button with one option for each dataframe
        buttons.append(dict(method='restyle',
                                label="All",
                                visible=True,
                                args=[{'y':[df_graph[col_y]],
                                       'x':[df_graph[col_x]],
                                       'type':'scatter'}, [0]],
                                )
                          )
        for f in df_graph[col_filter].unique():
            buttons.append(dict(method='restyle',
                                label=f,
                                visible=True,
                                args=[{'y':[df_graph[df_graph[col_filter]==f][col_y]],
                                       'x':[df_graph[df_graph[col_filter]==f][col_x]],
                                       'type':'scatter'}, [0]],
                                )
                          )

        # some adjustments to the updatemenus
        updatemenu = []
        your_menu = dict()
        updatemenu.append(your_menu)

        updatemenu[0]['buttons'] = buttons
        updatemenu[0]['direction'] = 'down'
        updatemenu[0]['showactive'] = True

        # add dropdown menus to the figure
        fig.update_layout(updatemenus=updatemenu)
        
        if col_color is None :
            fig.update_layout(showlegend=False)
    
    fig.update_layout({
            'plot_bgcolor': 'rgba(0,0,0,0)',
            'paper_bgcolor': 'rgba(0,0,0,0)',
        },
        hoverlabel=dict(
            #bgcolor="white", 
            font_size=12, 
            #font_family="Rockwell"
        ),
        hovermode = "x"
    )

    fig.update_xaxes(showspikes=True, spikecolor = 'black', showline=True, linewidth=1,linecolor='black', ticks = "outside", tickwidth = 1, tickcolor = 'black',ticklen = 5)
    fig.update_yaxes(showspikes=True, spikecolor = 'black', showline=True, linewidth=1,linecolor='black', ticks = "outside", tickwidth = 1, tickcolor = 'black',ticklen = 5)
    
    fig.show()
plot_line_go_graph(df,"Date","Quantity",col_color = "Animal", col_filter = "Category",add_points = False)

【问题讨论】:

    标签: python drop-down-menu plotly plotly-python linegraph


    【解决方案1】:

    我认为这里有几个问题。第一个问题是,当您绘制由"Category"(例如"Big""Small")过滤的数据时,您将绘制重复日期的数据,这就是导致线条在时间上倒退的原因,这使得情节难以解读。

    您可以通过在col_color, col_filter 上执行groupby 来添加跟踪 - 在您的示例中,如果您按["Animal", "Category"] 分组,则应该有四个唯一的组,因此应该有四个跟踪。

    然后我认为您可以重新设计按钮以根据跟踪名称是否包含特定的 col_filter 来切换每个跟踪的可见性。例如,如果您的迹线名为("Cat","Big"),("Cat,"Small"),("Dog","Big"),("Dog","Small"),当您单击“大”按钮时,它会将四个迹线的可见性切换为[True, False, True, False]。而"All" 按钮会将所有迹线的可见性设置为True

    import pandas as pd
    import plotly.graph_objects as go
    
    df = pd.DataFrame({"Date": ["2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03"],
                       "Animal" :["Cat","Cat","Cat","Cat","Cat","Cat","Dog","Dog","Dog","Dog","Dog","Dog"],
                       "Category":["Small","Small","Small","Big","Big","Big","Small","Small","Small","Big","Big","Big"],
                       "Quantity":[2,4,3,5,1,2,6,5,6,4,2,1]})
    
    df["Date"] = df["Date"].astype('datetime64')
    
    def plot_line_go_graph(df,col_x,col_y,col_color = None,col_filter = None,add_points = False) :
        
        df_graph = df.copy()
            
        if add_points :
            param_mode='lines+markers'
            param_name='lines+markers'
        else :
            param_mode='lines'
            param_name='lines'
        
        fig = go.Figure()
        
        if col_filter is None :
        
            if col_color is None :
                fig.add_trace(go.Scatter(x=df_graph[col_x], y=df_graph[col_y],mode=param_mode,name=param_name))
            else :
                for c in df_graph[col_color].unique() :
                    fig.add_trace(go.Scatter(x=df_graph[df_graph[col_color]==c][col_x], y=df_graph[df_graph[col_color]==c][col_y],mode=param_mode,name=c))
                    
        else :
            
            df_graph[col_filter] = df_graph[col_filter].fillna("NaN")
    
            if col_color is None :
                fig.add_trace(go.Scatter(x=df_graph[col_x], y=df_graph[col_y],mode=param_mode,name=param_name,visible = True))
            else :
                for group, df_group in df_graph.groupby([col_color, col_filter]):
                    fig.add_trace(go.Scatter(
                        x=df_group[col_x], 
                        y=df_group[col_y],
                        mode=param_mode,
                        name=f"{group}",
                        visible=True
                    ))
        
            updatemenu = []
            buttons = []
    
            # button with one option for each dataframe
            buttons.append(dict(method='restyle',
                                    label="All",
                                    visible=True,
                                    args=[{'visible' : [True]*len(fig.data)}]
                                    )
                              )
        
            for group, df_filter in df_graph.groupby([col_filter]):
                visible_traces = [group in trace['name'] for trace in fig.data]
                buttons.append(dict(method='restyle',
                                    label=group,
                                    visible=True,
                                    args=[{'visible' : visible_traces}]
                                ))
    
            # some adjustments to the updatemenus
            updatemenu = []
            your_menu = dict()
            updatemenu.append(your_menu)
    
            updatemenu[0]['buttons'] = buttons
            updatemenu[0]['direction'] = 'down'
            updatemenu[0]['showactive'] = True
    
            # add dropdown menus to the figure
            fig.update_layout(updatemenus=updatemenu)
            
            if col_color is None :
                fig.update_layout(showlegend=False)
        
        fig.update_layout({
                'plot_bgcolor': 'rgba(0,0,0,0)',
                'paper_bgcolor': 'rgba(0,0,0,0)',
            },
            hoverlabel=dict(
                #bgcolor="white", 
                font_size=12, 
                #font_family="Rockwell"
            ),
            hovermode = "x"
        )
    
        fig.update_xaxes(showspikes=True, spikecolor = 'black', showline=True, linewidth=1,linecolor='black', ticks = "outside", tickwidth = 1, tickcolor = 'black',ticklen = 5)
        fig.update_yaxes(showspikes=True, spikecolor = 'black', showline=True, linewidth=1,linecolor='black', ticks = "outside", tickwidth = 1, tickcolor = 'black',ticklen = 5)
        
        return fig
    
    fig = plot_line_go_graph(df,"Date","Quantity",col_color = "Animal", col_filter = "Category",add_points = False)
    fig.show()
    

    更新:根据下面评论线程中的讨论,需要重新设计一些痕迹。每个animal + category 组合仍然必须有一条迹线——没有好的方法可以解决这个问题,因为如果您不单独绘制每条迹线,那么您将无法单独控制它们。例如,如果您有 Dog (big+small) 和 Cat (big+small) 的跟踪,那么如何只选择 small?

    但是我们可以做的是拥有所有四个迹线,但使用图例组使它们表现得像两条迹线,并且每个图例组只显示一次 - 特定动物的任何迹线在图例中都有自己的条目,并一起选择和取消选择。棘手的部分是,当您单击“Big”或“Small”按钮时,该按钮需要知道哪些痕迹是“Big”或“Small”,以便我们可以将有关类别的信息放入customdata,然后检索它当您从下拉列表中单击“大”或“小”时,确定哪些痕迹应该可见。

    import pandas as pd
    import plotly.express as px
    import plotly.graph_objects as go
    
    df = pd.DataFrame({"Date": ["2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03","2022-10-01","2022-10-02","2022-10-03"],
                       "Animal" :["Cat","Cat","Cat","Cat","Cat","Cat","Dog","Dog","Dog","Dog","Dog","Dog"],
                       "Category":["Small","Small","Small","Big","Big","Big","Small","Small","Small","Big","Big","Big"],
                       "Quantity":[2,4,3,5,1,2,6,5,6,4,2,1]})
    
    df["Date"] = df["Date"].astype('datetime64')
    
    def plot_line_go_graph(df,col_x,col_y,col_color = None,col_filter = None,add_points = False) :
        
        df_graph = df.copy()
            
        if add_points :
            param_mode='lines+markers'
            param_name='lines+markers'
        else :
            param_mode='lines'
            param_name='lines'
        
        fig = go.Figure()
        
        if col_filter is None :
        
            if col_color is None :
                fig.add_trace(go.Scatter(x=df_graph[col_x], y=df_graph[col_y],mode=param_mode,name=param_name))
            else :
                for c in df_graph[col_color].unique() :
                    fig.add_trace(go.Scatter(x=df_graph[df_graph[col_color]==c][col_x], y=df_graph[df_graph[col_color]==c][col_y],mode=param_mode,name=c))
                    
        else :
            plotly_colors = px.colors.qualitative.Plotly
            color_map_length = len(df_graph[col_color].unique())
            color_map = {name:color for (name,color )in zip(df_graph[col_color].unique(),plotly_colors[:color_map_length])}
            df_graph[col_filter] = df_graph[col_filter].fillna("NaN")
            
    
            if col_color is None :
                fig.add_trace(go.Scatter(x=df_graph[col_x], y=df_graph[col_y],mode=param_mode,name=param_name,visible = True))
            else :
    
                ## the traces have no information regarding the col_filter
                ## so we put this information about col_color and col_filter
                ## (in this case the information about animal and category)
                ## into the customdata as an f-string
                for color, df_color in df_graph.groupby(col_color):
                    color_count = 0
                    for filter, df_filter in df_color.groupby(col_filter):
                        if color_count == 0:
                            showlegend=True
                            color_count += 1
                        else:
                            showlegend=False
                        fig.add_trace(go.Scatter(
                            x=df_filter[col_x], 
                            y=df_filter[col_y],
                            marker=dict(color=color_map[color]),
                            mode=param_mode,
                            name=f"{color}",
                            customdata=[f"{color} + {filter}"],
                            legendgroup=color,
                            showlegend=showlegend,
                            visible=True
                        ))
        
            updatemenu = []
            buttons = []
    
            # button with one option for each dataframe
            buttons.append(dict(method='restyle',
                                    label="All",
                                    visible=True,
                                    args=[{'visible' : [True]*len(fig.data)}]
                                    )
                              )
    
            for group, df_filter in df_graph.groupby([col_filter]):
                visible_traces = [group in trace['customdata'][0] for trace in fig.data]
                buttons.append(dict(method='restyle',
                                    label=group,
                                    visible=True,
                                    args=[{'visible' : visible_traces}]
                                ))
    
            # some adjustments to the updatemenus
            updatemenu = []
            your_menu = dict()
            updatemenu.append(your_menu)
    
            updatemenu[0]['buttons'] = buttons
            updatemenu[0]['direction'] = 'down'
            updatemenu[0]['showactive'] = True
    
            # add dropdown menus to the figure
            fig.update_layout(updatemenus=updatemenu)
            
            if col_color is None :
                fig.update_layout(showlegend=False)
        
        fig.update_layout({
                'plot_bgcolor': 'rgba(0,0,0,0)',
                'paper_bgcolor': 'rgba(0,0,0,0)',
            },
            hoverlabel=dict(
                #bgcolor="white", 
                font_size=12, 
                #font_family="Rockwell"
            ),
            hovermode = "x"
        )
    
        fig.update_xaxes(showspikes=True, spikecolor = 'black', showline=True, linewidth=1,linecolor='black', ticks = "outside", tickwidth = 1, tickcolor = 'black',ticklen = 5)
        fig.update_yaxes(showspikes=True, spikecolor = 'black', showline=True, linewidth=1,linecolor='black', ticks = "outside", tickwidth = 1, tickcolor = 'black',ticklen = 5)
        
        return fig
    
    fig = plot_line_go_graph(df,"Date","Quantity",col_color = "Animal", col_filter = "Category",add_points = False)
    fig.show()
    

    【讨论】:

    • 非常感谢@DerekO 的回答!这几乎是完美的,我希望当我选择全部时,我只有两条曲线:一条用于猫(在该曲线中大小一起),一条用于狗。你知道这是否可能吗?
    • @Ewdlam 当您选择All 时,如果您有一条Cat 曲线,您想如何将大小放在一起?他们有共同的日期,所以当你从一个类别转到另一个类别时,这条线会在时间上从2022-10-03 倒退到2022-10-01。一种可能性是将大小绘制为单独的迹线,但使它们具有相同的颜色并由一个图例条目控制,该条目使用设置为"Cat" 的公共legendgroup。这要实现起来要困难得多,但应该是可行的——当我以后有更多时间时,我可以回过头来
    • @Ewdlam 看到更新的答案!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 1970-01-01
    • 2021-08-29
    • 2017-12-27
    • 2022-01-13
    • 2015-03-01
    • 2020-12-27
    相关资源
    最近更新 更多