I have a drop down menu in plotly to display all graphs for every state in the country. I also have a graph that shows the whole US.
In my drop down menu I want to move the US to the top of the display and not in the alphabetical order.
Any suggestions? Really stuck and reorganizing the data frame doesn't work.
state_names = summary['state'].unique()
state_names.sort()
age_groups = summary['age_group'].unique()
x = summary['ca_monthly'].unique()
data_list = []
for state in state_names:
state_list = []
state_data = summary[summary['state']==state]
for age in age_groups:
state_list.append(
state_data[state_data['age_group']==age]['poverty_rate'])
data_list.append(state_list)
data = pd.DataFrame(data_list, columns=age_groups)
data['State'] = state_names
data = data.set_index('State')
fig = go.Figure()
legend_names = {'child': 'Child poverty',
'adult': 'Adult poverty',
'all': 'Overall poverty'}
default = state_names[0]
for age in age_groups:
fig.add_trace(go.Scatter(
x=x,
y=data[age][default],
name=legend_names[age]
))
buttons = []
title = 'Poverty impact of a child allowance in '
for state in state_names:
new_button = {'method': 'update',
'label': state,
'args': [{'y': data.loc[state]},
{'title.text': title + state}]}
buttons.append(new_button)
# construct menus
updatemenus = [{'buttons': buttons,
'direction': 'down',
'showactive': True,}]
# update layout with buttons, and show the figure
fig.update_layout(updatemenus=updatemenus)
fig.update_layout(
title= title + default,
xaxis_title='Monthly Child Allowance',
yaxis_title='SPM poverty rate',
yaxis_ticksuffix='%',
font=dict(family='Roboto'),
hovermode='x',
xaxis_tickprefix='$',
xaxis_ticksuffix='',
plot_bgcolor='white',
legend_title_text='',
legend=dict(yanchor='top', y=0.99, xanchor='right', x=0.99),
xaxis=dict(tickmode='linear', dtick = 50),
yaxis=dict(range=[0, summary.poverty_rate.max() * 1.05], dtick=2)
)
fig.update_traces(mode='markers+lines', hovertemplate=None,
marker=dict(size=4))
fig.show(config={'displayModeBar': False})
The answer:
Just add the buttons to the dropdown menu and each corresponding subset of your data to the button args in whatever order you prefer.
The details:
Regarding:
[...] reorganizing the data frame doesn't work.
Yes it would. But you don't have to. We seem to be talking about dropdown menus here. So just add the buttons in whatever order you'd like.
How it all turns out will depend entirely on your dataset and what you would in fact like to display. But you've not provided the former nor described the latter in full detail. But since you're using functions such as state_names = summary['state'].unique() I'm going to assume that your dataset is of a long format.
I'm also going to assume that you're only displaying one trace at a time here. Or else this approach wouldn't make much sense since you would obtain the very same functionality with the interactivity of plotlys default legend functionality.
I'll use the px.data.gapminder() dataset where running dfi['continent'].unique().tolist() will give ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']. I'm also going to throw in some aggregated data for the entire world, and define the order of the buttons to be ['World', 'Africa', 'Americas', 'Asia', 'Europe', 'Oceania'.
I hope this will reflect the structure of your real world data. And if it doesn't, then I strongly suggest that you take the time to learn how to efficiently build and share a pandas dataframe. The dataset also contains observations for individual countries. You'll just have to pretend that the world is USA and that the countries are states. But I suspect that won't be a problem.
Following the logic I've just described, the code snippet below will produce the following plot, with world defined to be placed at the top, and with the individual continents following in alphabetical order.
Complete code:
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
# dataframe, input
dfi = px.data.gapminder()
# dataframe, aggregated by continent
dfc = dfi.groupby( [ "continent", "year"] ).mean().reset_index()
# dataframe with calculated mean for all continents
dfw = dfc.groupby( ["year"] ).mean().reset_index()
dfw['continent']='World'
dfw = dfw.append(dfc)
dfw
# select a category (world),
# take it out of the categories,
# put it first in a list,
# and add the rest of the categories alphabetically
mainGeo = dfw['continent'].unique().tolist()
mainGeo
mainCat = 'World'
mainGeo.remove(mainCat)
mainGeo.sort()
order = [mainCat] + mainGeo
order
colors = px.colors.qualitative.Plotly
# plotly figure setup
fig=go.Figure()
fig.add_traces(go.Scatter(x=df['year'], y = df['lifeExp'], name=geo,
mode='lines', line=dict(color = colors[2], width = 1))
)
# dropdown menu
updatemenu = []
buttons = []
# button with one option for each dataframe
for geo in order:
buttons.append(dict(method='restyle',
label=geo,
visible=True,
args=[{'y':[dfw[dfw['continent']==geo]['lifeExp'].values],
'x':[dfw[dfw['continent']==geo]['year'].values],
'type':'scatter'}, ],
)
)
# 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(showlegend=False, updatemenus=updatemenu)
fig.show()
Related
This question already has answers here:
Plotly: How to filter a pandas dataframe using a dropdown menu?
(1 answer)
Plotly: How to display and filter a dataframe with multiple dropdowns?
(1 answer)
Closed 2 years ago.
I am new to Plotly and fascinated by its interactive features. I have three pandas dataframes of electricity generation mix of three countries, which looks like this:
I have been able to create an interactive bar chart using Plotly for electricity generation mix based on df1 using
import plotly.express as px
fig=px.bar(df1, title="Electricity generation mix of Germany in TWh (2000-2019)", color_discrete_sequence=colors)
fig
I intend to add a button or dropdown to this bar chart, where I can select countries based on each data frame (df1,df2 and df3). What would be the best approach to do it? Should I rather have the data of all three countries in one dataframe?
The easiest way to do this is to use the graph objects library and iterate through your data with the "add_trace" method of a Plotly figure.
import pandas as pd
import plotly.graph_objects as go
#Dummy data
df_germany = pd.DataFrame({'Fuels':[2010,2011],'Coal':[200,250],'Gas':[400,500]})
df_poland = pd.DataFrame({'Fuels':[2010,2011],'Coal':[500,150],'Gas':[600,100]})
df_spain = pd.DataFrame({'Fuels':[2010,2011],'Coal':[700,260],'Gas':[900,400]})
#put dataframes into object for easy access:
df_dict = {'Germany': df_germany,
'Poland': df_poland,
'Spain': df_spain}
#create a figure from the graph objects (not plotly express) library
fig = go.Figure()
buttons = []
i = 0
#iterate through dataframes in dict
for country, df in df_dict.items():
#iterate through columns in dataframe (not including the year column)
for column in df.drop(columns=['Fuels']):
#add a bar trace to the figure for the country we are on
fig.add_trace(go.Bar(
name = column,
#x axis is "fuels" where dates are stored as per example
x = df.Fuels.to_list(),
#y axis is the data for the column we are on
y = df[column].to_list(),
#setting only the first country to be visible as default
visible = (i==0)
)
)
#args is a list of booleans that tells the buttons which trace to show on click
args = [False] * len(df_dict)
args[i] = True
#create a button object for the country we are on
button = dict(label = country,
method = "update",
args=[{"visible": args}])
#add the button to our list of buttons
buttons.append(button)
#i is an iterable used to tell our "args" list which value to set to True
i+=1
fig.update_layout(
updatemenus=[
dict(
#change this to "buttons" for individual buttons
type="dropdown",
#this can be "left" or "right" as you like
direction="down",
#(1,1) refers to the top right corner of the plot
x = 1,
y = 1,
#the list of buttons we created earlier
buttons = buttons)
],
#stacked bar chart specified here
barmode = "stack",
#so the x axis increments once per year
xaxis = dict(dtick = 1))
fig.show()
Should yield:
I have monthly sales information on various companies (Amount, Profit) and would like to display all of this information in a single interactive Plotly chart - I.e. for each unique company, there should be a bar chart displaying sales amount per month as well as a line chart displaying profit per month, the two charts will share the x axis for time but have separate y axes (see example below).
This is simple enough to do for a single company using subplots, but I would like to be able to switch between companies using a dropdown menu. I have been able to get soething working, but am running into various bugs that I cannot get around.
Code to Reproduce Data:
import pandas as pd
import numpy as np
import itertools
from datetime import datetime
np.random.seed(2021)
company_list = ['Company_A', 'Company_B', 'Company_C', 'Company_D', 'Company_E']
datelist = pd.date_range(start="2020-01-01", end='2021-01-01', freq='MS').to_list()
df = pd.DataFrame(list(itertools.product(company_list, datelist)))
df.columns = ['Company', 'Date']
df['Amount'] = np.random.choice(range(0,10000), df.shape[0])
df['Profit'] = np.random.choice(range(0,10000), df.shape[0])
df.head()
I have a function which takes a data frame (assuming the same format as the one just created above) and creates a Plotly chart which has the two plots (Amount and Profit) for each company, and has a drop down menu to change from company to company.
Function to Make Multiple Plots and Dropdown Menu:
from plotly import graph_objs as go
from plotly.subplots import make_subplots
def make_multi_plot(df):
fig = make_subplots(rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.02)
for customer in list(df.Company.unique()):
trace1 = go.Bar(
x=df.loc[df.Company.isin([customer])].Date,
y=df.loc[df.Company.isin([customer])].Amount,
name = "Amount - " + str(customer))
trace2 = go.Scatter(
x=df.loc[df.Company.isin([customer])].Date,
y=df.loc[df.Company.isin([customer])].Profit,
name = "Profit - " + str(customer)
)
fig.append_trace(trace1,1,1)
fig.append_trace(trace2,2,1)
def create_layout_button(customer):
return dict(label = customer,
method = 'restyle',
args = [{'visible': [cust == customer for cust in list(df.Company.unique())],
'title': customer,
'showlegend': True}])
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active = 0,
buttons = [create_layout_button(customer) for customer in list(df.Company.unique())]
)
])
fig.show()
At first glance, this seems to be doing what I want. However, I am running into 2 issues which I can't solve:
When the function is first called, it plots the data for ALL of the companies on the two plots, rather than just the first company (which is what I want). This does fix itself once you do select a company from the dropdown menu, although that introduces us to our next issue...
When you do select a Company from the dropdown menu, it doesn't actually update the plots correctly, it is using the wrong company's data to make the plots. If you look at the legend for the two plots, you can see that it is actually plotting the data for different companies in the set. I have no idea why this is happening and haven't been able to find any real pattern in how it's confusing the various plots with the buttons.
Appreciate any and all help!
I'm using python and creating standalone html files with interactive plots (no Dash). I have been able to build a plotly plot with buttons that can toggle the visibility of traces in the plot. However, this functionality removes the traces from the legend as well. What I would like is to be able to keep the functionality of the legend (click a single trace to toggle visibility) but also have a set of buttons that extends that functionality to a group of traces that I define.
The goal is to be able toggle everything (or a select group) to invisible but add individual items from that group back to visible as needed.
Below is an example (using modified code from this answer by vestland) to show what I am currently attempting.
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import datetime
# mimic OP's datasample
NPERIODS = 200
np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(NPERIODS, 4)),
columns=list('ABCD'))
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),
periods=NPERIODS).tolist()
df['dates'] = datelist
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum()
# set up multiple traces
traces = []
buttons = []
for col in df.columns:
traces.append(go.Scatter(x=df.index,
y=df[col],
visible=True,
name=col)
)
buttons.append(dict(method='update',
label=col,
visible=True,
args=[{'visible':[x == col for x in df.columns]}],
args2=[{'visible':[x != col for x in df.columns]}]
)
)
# create the layout
layout = go.Layout(
updatemenus=[
dict(
type='buttons',
direction='right',
x=0.7,
y=1.3,
showactive=True,
buttons=buttons
)
],
title=dict(text='Toggle Traces',x=0.5),
showlegend=True
)
fig = go.Figure(data=traces,layout=layout)
# add dropdown menus to the figure
fig.show()
That example does not work how I would like. Below is a screenshot of what it looks like at first.
The problem is that if I use one of those buttons, it does hide all the other traces but it also removes them from the legend so they can't be toggled back to visible.
So my question becomes, is there a different value in the args list/dictionary that can be given for the functionality to match that of simply clicking a trace in the legend?
Sort of related, is there some way to get the current state of visibility for each trace?
In order to make it possible to toggle any trace on and off without affecting the others, it seems you'll have to include one updatemenu per button. There might be other ways to do it, but the code snippet below will produce the following plot:
Plot 1 - At launch all are selected
Plot 2 - C and D toggled off
Plot 3 - All off
Plot 4 - All on
Complete code:
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import datetime
import plotly.express as px
periods = 200
cols = list('ABCD')
np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(periods, len(cols))),
columns=cols)
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),
periods=periods).tolist()
df['dates'] = datelist
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum()
# # plotly
fig = go.Figure()
colors = px.colors.qualitative.Plotly
# set up multiple traces
for col in df.columns:
fig.add_trace(go.Scatter(x=df.index,
y=df[col],
name = col,
visible=True
)
)
um = [ {} for _ in range(len(df.columns)) ]
buttons = []
menuadjustment = 0.15
buttonX = -0.1
buttonY = 1 + menuadjustment
for i, col in enumerate(df.columns):
button = dict(method='restyle',
label=col,
visible=True,
args=[{'visible':True,
'line.color' : colors[i]}, [i]],
args2 = [{'visible': False,
'line.color' : colors[i]}, [i]],
)
# adjust some button features
buttonY = buttonY-menuadjustment
um[i]['buttons'] = [button]
um[i]['showactive'] = False
um[i]['y'] = buttonY
um[i]['x'] = buttonX
# add a button to toggle all traces on and off
button2 = dict(method='restyle',
label='All',
visible=True,
args=[{'visible':True}],
args2 = [{'visible': False}],
)
# assign button2 to an updatemenu and make some adjustments
um.append(dict())
um[i+1]['buttons'] = [button2]
um[i+1]['showactive'] = True
um[i+1]['y']=buttonY - menuadjustment
um[i+1]['x'] = buttonX
# add dropdown menus to the figure
fig.update_layout(showlegend=True, updatemenus=um)
# adjust button type
for m in fig.layout.updatemenus:
m['type'] = 'buttons'
f = fig.full_figure_for_development(warn=False)
fig.show()
After a decent bit of searching, I have been able to figure it out thanks to this answer on the Plotly forum. I have not been able to find somewhere that lists all of these options yet, but that would be very helpful.
It appears that the list given to 'visible' in the args dictionary does not need to be only booleans. In order to keep the items visible in the legend but hidden in the plot, you need to set the values to 'legendonly'. The legend entries can then still be clicked to toggle individual visibility. That answers the main thrust of my question.
args = [{'visible': True}]
args = [{'visible': 'legendonly'}]
args = [{'visible': False}]
Vestland's answer helped solve the second part of my question, only modifying the traces I want and leaving everything else the same. It turns out that you can pass a list of indices after the dictionary to args and those args will only apply to the traces at the indices provided. I used list comprehension in the example to find the traces that match the given name. I also added another trace for each column to show how this works for multiple traces.
args = [{'key':arg}, [list of trace indices to apply key:arg to]]
Below is the now working code.
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import datetime
# mimic OP's datasample
NPERIODS = 200
np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(NPERIODS, 4)),
columns=list('ABCD'))
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),
periods=NPERIODS).tolist()
df['dates'] = datelist
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum()
# set up multiple traces
traces = []
buttons = []
for col in df.columns:
traces.append(go.Scatter(x=df.index,
y=df[col],
visible=True,
name=col)
)
traces.append(go.Scatter(x=df.index,
y=df[col]+20,
visible=True,
name=col)
)
buttons.append(dict(method='restyle',
label=col,
visible=True,
args=[{'visible':True},[i for i,x in enumerate(traces) if x.name == col]],
args2=[{'visible':'legendonly'},[i for i,x in enumerate(traces) if x.name == col]]
)
)
allButton = [
dict(
method='restyle',
label=col,
visible=True,
args=[{'visible':True}],
args2=[{'visible':'legendonly'}]
)
]
# create the layout
layout = go.Layout(
updatemenus=[
dict(
type='buttons',
direction='right',
x=0.7,
y=1.3,
showactive=True,
buttons=allButton + buttons
)
],
title=dict(text='Toggle Traces',x=0.5),
showlegend=True
)
fig = go.Figure(data=traces,layout=layout)
# add dropdown menus to the figure
fig.show()
This gives the following functionality:
the "All" button can toggle visibility of all traces.
Each other button will only toggle the traces with the matching name. Those traces will still be visible in the legend and can be turned back to visible by clicking on them in the legend or clicking the button again.
After clicking the "B" button (twice to hit arg2).
And then after clicking the first B trace in the legend.
I am plotting chart using below code:
fig = px.line(df, x='Time', y=['one','two'], color= df.index)
fig['layout']['xaxis']['autorange'] = "reversed"
fig.update_layout(legend_title="Price")
fig.show()
Dataframe i am working with like is below:
Time one two
100 9:30 129 243
110 10:30 234 453
120 11:00 155 234
Want to add dropdown menu to select from index and show one row at a time in chart.
example if i select 110 from drop down it should only show chart for that row.
Is there any easy fix for it.
Thank you in adavance.
Here's my solution:
In order to set the proper options for the dropdown menu, it would be helpful to have a function that creates the list of options (shown below)
# Create proper buttons list
def makeButtonsList(idxs):
buttons = []
for i, idx in enumerate(idxs):
visibleArr = np.full((2*df.index.shape[0],),
False, dtype=bool) # 2x number of booleans since one/two vals are separate plots
visibleArr[2*i] = True # Set two booleans next to each other (representing one & two) to true
visibleArr[(2*i)+1] = True
buttons.append(dict(label=str(idx),
method='update',
args=[{'visible': list(visibleArr)}])) # 'Visible' arg determines which plots are shown
# depending on which dropdown is selected
return buttons
Next create the traces for the data (with your sample data, I created a bar chart but you could easily modify this)
traces = []
for i in range(df.Time.shape[0]):
rowData = df.iloc[i, :]
time = rowData.Time
one = rowData.one
two = rowData.two
traces.append(go.Bar(x=[time], y=[one], name='One'))
traces.append(go.Bar(x=[time], y=[two], name='Two'))
where df is the dataframe you are working with.
Finally put it all together and create the Plotly plot!
# Import packages
import pandas as pd
import numpy as np
import plotly.graph_objs as go
import plotly.express as px
# Create proper buttons list
def makeButtonsList(idxs):
buttons = []
for i, idx in enumerate(idxs):
visibleArr = np.full((2*df.index.shape[0],),
False, dtype=bool) # 2x number of booleans since one/two vals are separate plots
visibleArr[2*i] = True # Set two booleans next to each other (representing one & two) to true
visibleArr[(2*i)+1] = True
buttons.append(dict(label=str(idx),
method='update',
args=[{'visible': list(visibleArr)}])) # 'Visible' arg determines which plots are shown
# depending on which dropdown is selected
return buttons
# Create traces
traces = []
for i in range(df.Time.shape[0]):
rowData = df.iloc[i, :]
time = rowData.Time
one = rowData.one
two = rowData.two
traces.append(go.Bar(x=[time], y=[one], name='One'))
traces.append(go.Bar(x=[time], y=[two], name='Two'))
# Create figure
fig = go.Figure(data=traces)
# Add dropdown options
fig.update_layout(
updatemenus=[
dict(
buttons=makeButtonsList(df.index),
direction="down",
pad={"r": 10, "t": 10},
showactive=True,
x=0.55,
xanchor="left",
y=1.2,
yanchor="top"
),
]
)
# Add annotation for index selected
fig.update_layout(
annotations=[
dict(text="Index:", showarrow=False,
x=0, y=1.15, yref="paper", align="left")
],
xaxis_title = 'Time',
yaxis_title = 'Value',
)
# Show the plot
fig.show()
Here is a sample plot:
BONUS:
If you think this method is tedious, and a slider bar would do the job just fine, Plotly supports animation of bar charts. Here is the following code you could use:
fig = px.bar(df, x='Time', y=['one','two'], animation_frame=df.index)
fig.update_layout(title='Data', barmode='group')
fig.show()
Here is the resulting plot:
I have made a graph using time axis on the X axis, however I want to show only today's data. (i.e restrict the date to current date)
Here is the code I used to produce those graphs :
fig = go.Figure()
fig = make_subplots(rows=2,cols=1,shared_xaxes=True,vertical_spacing=0.02)
fig.add_trace(go.Scatter(x=data['time'],y=data['x_last_recorded'],name='xyz',mode='lines+markers'),row=2,col=1)
fig.add_trace(go.Scatter(x=predict_data['time'],y=predict_data['x1_last_recorded'],name='x1yz',mode='lines'),row=2,col=1)
fig.update_layout(height=800,width=1500,title='first_graph',yaxis_title='Values')
This has got me a graph of how I want, but it is showing all the dates present in the dataframe. How do I fetch only the current date's data?
Structure of time : dd-mm-yyyy hh:mm
We can solve your challenge by subsetting your dataframe using an approach such as df_current = df[df.index.date==df.index.date[-1]], and then restyle your figure by letting different subsets be represented by different options in a dropdown menu.
Here's the resulting figure for the different subsets / selection options:
All dates
Current date
Complete code:
# imports
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# sample data in the form of an hourlt
np.random.seed(1234)
tseries = pd.date_range("01.01.2020", "01.04.2020", freq="H")
data = np.random.randint(-10, 12, size=(len(tseries), 2))
df = pd.DataFrame(index=tseries, data=data)
df.drop(df.tail(1).index,inplace=True)
df.columns=list('AB')
df.iloc[0]=0
df=df.cumsum()
# subset method
df_current = df[df.index.date==df.index.date[-1]]
# plotly setup
fig = go.Figure()
# set up a trace for each column in a dataframe
for col in df.columns:
fig.add_trace(go.Scatter(x=df.index, y =df[col], name=col))
# container for updatemenus and buttons
updatemenu = []
buttons = []
# button 1
buttons.append(dict(method='restyle',
label='All',
visible=True,
args=[{'y':[df[col].values for col in df.columns],
'x':[df.index],
'type':'scatter'}, ],))
# button 2
buttons.append(dict(method='restyle',
label='Current',
visible=True,
args=[{'y':[df_current[col].values for col in df_current.columns],
'x':[df_current.index],
'type':'scatter'}, ],))
# menu setup
your_menu = dict()
updatemenu.append(your_menu)
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(showlegend=False, updatemenus=updatemenu)
fig.show()