Plotly: How to create monthly and annual average dropdown options?

Viewed 495

I am looking to create a dropdown in Plotly to toggle my original daily timeseries to be resampled to monthly averages and annual averages.

In summary, I would expect 3 options in the dropdown: one would be the original daily timestep plotted, the second is the monthly average resampled daily data, and the third is the annual average values one could select from.

Here is all the code I have so far:

import pandas as pd
import numpy as np
from numpy import random
import plotly.express as px
import cufflinks as cf

random.seed(30)

df = pd.DataFrame({
    "Site 1": np.random.rand(366),
    "Site 2": np.random.rand(366),
    "Site 3": np.random.rand(366),
    "Site 4": np.random.rand(366),
    "Site 5": np.random.rand(366),
    "Site 6": np.random.rand(366)})

idx = pd.date_range(start='2000-01-01', end='2000-12-31',freq ='D')
df = df.set_index(idx)

dfmon = df.resample('M').mean()
dfyr = df.resample('AS').mean()

df.index.names = ['Date']
df.reset_index(inplace= True)

dfmon.index.names = ['Date']
dfmon.reset_index(inplace= True)

dfyr.index.names = ['Date']
dfyr.reset_index(inplace= True)

print(df)
dfd_long = dfmon.melt(id_vars='Date',var_name='SiteID', value_name='Values')
dfm_long = dfmon.melt(id_vars='Date',var_name='SiteID', value_name='Values')
dfyr_long = dfyr.melt(id_vars='Date',var_name='SiteID', value_name='Values')

fig = px.line(dfd_long, x=dfd_long['Date'], y="Values", color="SiteID", hover_name="SiteID")

fig.update_layout(updatemenus=list([
    dict(
        buttons=list([   
            dict(
                args=[{'x': [dfm_long.index], 'y': [dfm_long.Values]}],
                label='Monthly',
                method='restyle'
            ),
            dict(
                args=[{'x': [dfyr_long.index], 'y': [dfyr_long.Values]}],
                label='Yearly',
                method='restyle'
            ),
            dict(
                args=[{'x': [dfd_long.index], 'y': [dfd_long.Values]}],
                label='Daily',
                method='restyle'
            )
        ]))]))

fig.show()

Am I going about this the right way regarding the resampling to monthly and yearly dropdown; should I be using a long or wide data format for this? When I click through the dropdown with this code it only shows one line out of the 6 lines I would expect. Do I need a separate dropdown for each site for this to work properly?

I have limited experience with Plotly thus far so any help or suggestions you can offer would be a huge help!

Thank you in advance for the help!

1 Answers

Below is a complete setup that will give you the following three figures for these options:

['daily', 'monthly', 'yearly']

I've used your setup for reproducible data with a slight twist to make the figures arguably a bit more interesting. As an example, you're using "Site 1": np.random.rand(366) while I'm using np.random.randint(-1, 2, 366).cumsum(). This produces an accumulated series which is likely to display a (stochastic) trend rather than noise. And this makes it easier to see what's going on in the figure - particularly the version showing the daily data.

Plot 1 - daily

enter image description here

Plot 2 - monthly

enter image description here

Plot 3a - yearly data with scatter traces

enter image description here

This looks a bit funny, don't you think? Let me know if you'd like to change it into this for the last option:

Plot 3b - yearly data with bar traces

enter image description here

And let me know if you'd like some more details about what's going on in the complete code snippet if the comments don't quite cover it.

import pandas as pd
import numpy as np
from numpy import random
import plotly.express as px
import cufflinks as cf
import plotly.graph_objects as go

random.seed(30)

df = pd.DataFrame({
    "Site 1": np.random.randint(-1, 2, 366).cumsum(),
    "Site 2": np.random.randint(-1, 2, 366).cumsum(),
    "Site 3": np.random.randint(-1, 2, 366).cumsum(),
    "Site 4": np.random.randint(-1, 2, 366).cumsum(),
    "Site 5": np.random.randint(-1, 2, 366).cumsum(),
    "Site 6": np.random.randint(-1, 2, 366).cumsum()})

idx = pd.date_range(start='2000-01-01', end='2000-12-31',freq ='D')
df = df.set_index(idx)

dfmon = df.resample('M').mean()
dfyr = df.resample('AS').mean()

df.index.names = ['Date']
df.reset_index(inplace= True)

dfmon.index.names = ['Date']
dfmon.reset_index(inplace= True)

dfyr.index.names = ['Date']
dfyr.reset_index(inplace= True)

fig = go.Figure()
dfs = {'daily':df, 'monthly': dfmon, 'yearly' :dfyr}

# your setup this far...

# ... here is where I've added my contributions:

# specify visibility for traces accross dataframes
frames = len(dfs) # number of dataframes organized in  dict
columns = len(dfs['daily'].columns) - 1 # number of columns i df, minus 1 for Date
scenarios = [list(s) for s in [e==1 for e in np.eye(frames)]]
visibility = [list(np.repeat(e, columns)) for e in scenarios] 

# container for buttons
buttons = []

# iterate of dataframes in dfs:
# - i is used to reference visibility attributes
# - k is the name for each dataframe
# - v is the dataframe itself
for i, (k, v) in enumerate(dfs.items()):
    print(i)
    for c, column in enumerate(v.columns[1:]):
        fig.add_scatter(name = column,
                        x = v['Date'],
                        y = v[column], 
                        visible=True if k=='daily' else False # 'daily' values are shown from the start
                       )
                
    # one button per dataframe to trigger the visibility
    # of all columns / traces for each dataframe
    button =  dict(label=k,
                   method = 'restyle',
                   args = ['visible',visibility[i]])
    buttons.append(button)

# include dropdown updatemenu in layout
fig.update_layout(updatemenus=[dict(type="dropdown",
                                    direction="down",
                                    buttons = buttons)])
fig.show()
Related