Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plotly: How to display and filter a dataframe with multiple dropdowns?

I'm new to Python, Pandas and Plotly so maybe the answer is easy but I couldn't find anything on the forum or anywhere else …

I don’t want to use Dash nor ipywidgets since I want to be able to export in HTML using plotly.offline.plot (I need an interactive HTML file to dynamically control the figure without any server running like Dash seems to do).

Well my problem is that I would like to filter a plotly figure using several (cumulative) dropdown buttons (2 in this example, but it could be more) by filtering the original data with the selected value in the dropdown lists.

num label   color   value
1   A       red     0.4
2   A       blue    0.2
3   A       green   0.3
4   A       red     0.6
5   A       blue    0.7
6   A       green   0.4
7   B       blue    0.2
8   B       green   0.4
9   B       red     0.4
10  B       green   0.2
11  C       red     0.1
12  C       blue    0.3
13  D       red     0.8
14  D       blue    0.4
15  D       green   0.6
16  D       yellow  0.5

In this example, if I choose label ‘A’ and color ‘red’ I would like to display ONLY the values of rows with label ‘A’ AND color ‘red’, as follow :

num label   color   value
1   A       red     0.4
4   A       red     0.6

Then, the figure should display only 2 values

1) So here is the code I have for the moment (see below) but I don’t know how to continue. Do you have any idea ?

2) Extra question : is it possible to use checkboxes instead of dropdown lists, to be able to select multiple values inside a criteria, for example : Labels filter could be A or B, not only one in the list …

Thanks in advance for your help !

import pandas as pd
import plotly.graph_objects as go

d = {
    'num' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    'label' : ['A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', 'D', 'D', 'D', 'D'],
    'color' : ['red', 'blue', 'green', 'red', 'blue', 'green', 'blue', 'green', 'red', 'green', 'red', 'blue', 'red', 'blue', 'green', 'yellow'],
    'value' : [0.4, 0.2, 0.3, 0.6, 0.7, 0.4, 0.2, 0.4, 0.4, 0.2, 0.1, 0.3, 0.8, 0.4, 0.6, 0.5]
    }

# Build dataframe
df = pd.DataFrame(data=d)

# Build dropdown Labels
labels = df["label"].unique()
buttonsLabels = [dict(label = "All labels",
                            method = "restyle",
                            args = [{'y' : [df["value"] * 100]}] # or what else ?
                            )]
for label in labels:
    buttonsLabels.append(dict(label = label,
                              method = "restyle",
                              visible = True,
                              #args = [{'y' : ??? }]
                              ))
# Build dropdown Colors
colors = df["color"].unique()
buttonsColors = [dict(label = "All colors",
                            method = "restyle",
                            args = [{'y' : [df["value"] * 100]}] # or what else ?
                            )]
for color in colors:
    buttonsColors.append(dict(label = color,
                              method = "restyle",
                              visible = True,
                              # args = [{'y' : ??? }]
                              ))

# Display figure
fig = go.Figure(data = [ go.Scatter(x = df["num"], y = df["value"] * 100 ) ])

fig.update_layout(updatemenus = [
   dict(buttons = buttonsLabels, showactive = True),
   dict(buttons = buttonsColors, showactive = True, y = 0.8)
   ])

fig.show()
like image 564
Yas Avatar asked May 02 '20 08:05

Yas


1 Answers

It's certainly possible to display and filter a dataframe with multiple dropdowns. The code snippet below will do exactly that for you. The snippet has quite a few elements in common with your provided code, but I had to build it from scratch to make sure everything harmonized. Run the snippet below, and select A and Red to see that you will in fact get:

num label   color   value
1   A       red     0.4
4   A       red     0.6

Plot:

enter image description here

There's still room for improvement. I'll polish the code and improve the layout when I find the time. First, please let me know if this is in fact what you were looking for.

Complete code:

# Imports
import plotly.graph_objs as go
import pandas as pd
import numpy as np

# source data
df = pd.DataFrame({0: {'num': 1, 'label': 'A', 'color': 'red', 'value': 0.4},
                    1: {'num': 2, 'label': 'A', 'color': 'blue', 'value': 0.2},
                    2: {'num': 3, 'label': 'A', 'color': 'green', 'value': 0.3},
                    3: {'num': 4, 'label': 'A', 'color': 'red', 'value': 0.6},
                    4: {'num': 5, 'label': 'A', 'color': 'blue', 'value': 0.7},
                    5: {'num': 6, 'label': 'A', 'color': 'green', 'value': 0.4},
                    6: {'num': 7, 'label': 'B', 'color': 'blue', 'value': 0.2},
                    7: {'num': 8, 'label': 'B', 'color': 'green', 'value': 0.4},
                    8: {'num': 9, 'label': 'B', 'color': 'red', 'value': 0.4},
                    9: {'num': 10, 'label': 'B', 'color': 'green', 'value': 0.2},
                    10: {'num': 11, 'label': 'C', 'color': 'red', 'value': 0.1},
                    11: {'num': 12, 'label': 'C', 'color': 'blue', 'value': 0.3},
                    12: {'num': 13, 'label': 'D', 'color': 'red', 'value': 0.8},
                    13: {'num': 14, 'label': 'D', 'color': 'blue', 'value': 0.4},
                    14: {'num': 15, 'label': 'D', 'color': 'green', 'value': 0.6},
                    15: {'num': 16, 'label': 'D', 'color': 'yellow', 'value': 0.5},
                    16: {'num': 17, 'label': 'E', 'color': 'purple', 'value': 0.68}}
                    ).T

df_input = df.copy()

# split df by labels
labels = df['label'].unique().tolist()
dates = df['num'].unique().tolist()

# dataframe collection grouped by labels
dfs = {}
for label in labels:
    dfs[label]=pd.pivot_table(df[df['label']==label],
                                    values='value',
                                    index=['num'],
                                    columns=['color'],
                                    aggfunc=np.sum)

# find row and column unions
common_cols = []
common_rows = []
for df in dfs.keys():
    common_cols = sorted(list(set().union(common_cols,list(dfs[df]))))
    common_rows = sorted(list(set().union(common_rows,list(dfs[df].index))))

# find dimensionally common dataframe
df_common = pd.DataFrame(np.nan, index=common_rows, columns=common_cols)

# reshape each dfs[df] into common dimensions
dfc={}
for df_item in dfs:
    #print(dfs[unshaped])
    df1 = dfs[df_item].copy()
    s=df_common.combine_first(df1)
    df_reshaped = df1.reindex_like(s)
    dfc[df_item]=df_reshaped

# plotly start 
fig = go.Figure()
# one trace for each column per dataframe: AI and RANDOM
for col in common_cols:
    fig.add_trace(go.Scatter(x=dates,
                             visible=True,
                             marker=dict(size=12, line=dict(width=2)),
                             marker_symbol = 'diamond',name=col
                  )
             )

# menu setup    
updatemenu= []

# buttons for menu 1, names
buttons=[]

# create traces for each color: 
# build argVals for buttons and create buttons
for df in dfc.keys():
    argList = []
    for col in dfc[df]:
        #print(dfc[df][col].values)
        argList.append(dfc[df][col].values)
    argVals = [ {'y':argList}]

    buttons.append(dict(method='update',
                        label=df,
                        visible=True,
                        args=argVals))

# buttons for menu 2, colors
b2_labels = common_cols

# matrix to feed all visible arguments for all traces
# so that they can be shown or hidden by choice
b2_show = [list(b) for b in [e==1 for e in np.eye(len(b2_labels))]]
buttons2=[]
buttons2.append({'method': 'update',
                 'label': 'All',
                 'args': [{'visible': [True]*len(common_cols)}]})

# create buttons to show or hide
for i in range(0, len(b2_labels)):
    buttons2.append(dict(method='update',
                        label=b2_labels[i],
                        args=[{'visible':b2_show[i]}]
                        )
                   )

# add option for button two to hide all
buttons2.append(dict(method='update',
                        label='None',
                        args=[{'visible':[False]*len(common_cols)}]
                        )
                   )

# some adjustments to the updatemenus
updatemenu=[]
your_menu=dict()
updatemenu.append(your_menu)
your_menu2=dict()
updatemenu.append(your_menu2)
updatemenu[1]
updatemenu[0]['buttons']=buttons
updatemenu[0]['direction']='down'
updatemenu[0]['showactive']=True
updatemenu[1]['buttons']=buttons2
updatemenu[1]['y']=0.6

fig.update_layout(showlegend=False, updatemenus=updatemenu)
fig.update_layout(yaxis=dict(range=[0,df_input['value'].max()+0.4]))

# title
fig.update_layout(
    title=dict(
        text= "<i>Filtering with multiple dropdown buttons</i>",
        font={'size':18},
        y=0.9,
        x=0.5,
        xanchor= 'center',
        yanchor= 'top'))

# button annotations
fig.update_layout(
    annotations=[
        dict(text="<i>Label</i>", x=-0.2, xref="paper", y=1.1, yref="paper",
            align="left", showarrow=False, font = dict(size=16, color = 'steelblue')),
        dict(text="<i>Color</i>", x=-0.2, xref="paper", y=0.7, yref="paper",
            align="left", showarrow=False, font = dict(size=16, color = 'steelblue')

                             )
    ])

fig.show()
like image 136
vestland Avatar answered Nov 15 '22 17:11

vestland