Plotly - Combining Multiple Subplots with Drop Down Menu Buttons - python

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!

Related

Plotly Express - plot subset of dataframe columns by default and the rest as option

I am using plotly express to plot figures this way :
fig = px.line(df,
x=df.index,
y=df.columns
)
It displays the graph properly and shows all the columns by default (as lines in the graph) with option to uncheck (or check) them to disable showing whatever we want if needed.
What I would like is to show the same graph but by default uncheking some of the columns initially and keep the option to check or uncheck them for visualization.
This means that I cannot take only a subset of columns as new data frame to show as the other columns are still relevant.
Did not find anything in the documentation unfortunately...
Thank you in advance.
You can use the visible property of the traces to state it is only in the legend. Below shows all columns in the figure then first two columns are set as visible, all other columns are only in the legend.
import plotly.express as px
import pandas as pd
import numpy as np
# simulate dataframe
df = pd.DataFrame(
{c: np.random.uniform(0, 1, 100) + cn for cn, c in enumerate("ABCDEF")}
)
fig = px.line(df, x=df.index, y=df.columns)
# for example only display first two columns of data frame, all others can be displayed
# by clicking on legend item
fig.for_each_trace(
lambda t: t.update(visible=True if t.name in df.columns[:2] else "legendonly")
)

slider for choropleth map plotly shows error - <ipython-input-17-c5022c9e0aab>:13: SettingWithCopyWarning:

I am trying to add a slider for my choropleth map using plotly. I have been unable to see the map in google colab but jupyter notebook has worked fine.
I have a dataset with values for each country of the world over a period of years. I want to make a slider underneath so you can scroll through the years and see the colours change. I have been able to get data from one year, but I cannot get the slider to appear under my map, and I get a large pink error message saying the value is trying to be set on a copy of a slice from a DataFrame.
Is there anything I can do to fix this? Please find attached my code
pip install chart-studio
import pandas as pd
import chart_studio.plotly as py
import plotly.offline as po
import matplotlib.pyplot as plt
%matplotlib inline
import plotly
import plotly.graph_objs as go
import plotly.offline as offline
from plotly.graph_objs import *
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, pilot
df=pd.read_csv("https://raw.githubusercontent.com/jamesjeffery77/jamesjeffery77.github.io/main/share-electricity-low-carbon_fullDataset.csv")
year=1985
### create empty list for data object:
data_slider = []
# Populate data object
for year in df.year.unique():
#select year
df2=df[(df['year']==year)]
for col in df2.columns:
df2[col] = df2[col].astype(str)
#dictionary with date for current year
data_one_year = dict(
type='choropleth',
locations=df2['code'],
z=df2['percentage'].astype(float),
text=df2['country'],)
#add data to next year
data_slider.append(data_one_year)
#steps for the slider
steps = []
for i in range(len(data_slider)):
step=dict(method='restyle',
args=['visible',[False] * len(data_slider)],
label='year {}'.format(i+1985))
step['args'][1][i] = True
steps.append(step)
## create the sliders object from the steps
sliders = [dict(active=0, pad={"t": 1}, steps=steps)]
#layout such that there is a global view
layout = dict(title = 'Global GDP - natural earth',
geo = dict( projection = {'type':'natural earth'},
showlakes = True,
lakecolor = 'rgb(0,191,255)'))
fig = dict(data=data_slider, layout=layout)
#plot graph
plotly.offline.iplot(fig)
You can get around the value is trying to be set on a copy of a slice from a DataFrame warning, you can set the columns as strings in one step - you don't need to loop through each column name separately.
# Populate data object
for year in df.year.unique():
#select year
df2=df[(df['year']==year)]
df2.columns = df2.columns.astype(str)
#dictionary with date for current year
data_one_year = dict(
type='choropleth',
locations=df2['code'],
z=df2['percentage'].astype(float),
text=df2['country'],)
#add data to next year
data_slider.append(data_one_year)
After this change, I ran your code in Google Colab, and I am able to display the map.

How can I add a button or dropdown in a plot created using Plotly in Python? [duplicate]

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:

Plotly: How to organize dropdown menu?

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()

How can I get all Plotly plots created inside a for loop display in single browser window?

I am trying to create several plots inside a for loop using plotly. Currently all the charts appear in separate tabs of browser. I want all the charts to appear in the same browser window.
In my data frame df, for each unique element in Tool_MeasurementSet column (unique elements saved as a list meas_set) has 16 data points for X-BAR and 16 for SIGMA. I was able to use subplot function to combine X-BAR and SIGMA plot for each element in meas_set. Currently the code is creating plots for each element in meas_set list in a separate tab of the browser. But I want to make all the plots appear in the same browser window with a vertical scroll bar instead of having to move from one tab to another to look at plots.
from plotly import tools
import plotly.plotly as py
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import plotly.offline as pyo
import plotly.graph_objs as go
df = pd.read_csv("C:\\DATA_REPORT_subset.csv")
meas_set = df['Tool_MeasurementSet'].unique()
## params are the column labels in the df dataframe
params = ['Data','UCL','LCL','CL']
for i in meas_set:
fig = tools.make_subplots(rows=2, cols=1,subplot_titles=('X-BAR Subplot','SIGMA Subplot'))
for j in range(0,len(params)):
y_xbar = df[(df['Tool_MeasurementSet']== i) & (df['Chart Type']== 'X-BAR')][params[j]]
x_xbar = df[(df['Tool_MeasurementSet']== i) & (df['Chart Type']== 'X-BAR')]['Date']
y_sigma = df[(df['Tool_MeasurementSet']== i) & (df['Chart Type']== 'SIGMA')][params[j]]
x_sigma = df[(df['Tool_MeasurementSet']== i) & (df['Chart Type']== 'SIGMA')]['Date']
trace1 = go.Scatter(x=x_xbar,y=y_xbar,mode='lines',name=params[j])
trace2 = go.Scatter(x=x_sigma,y=y_sigma,mode='lines',name=params[j])
fig.append_trace(trace1,1,1)
fig.append_trace(trace2,2,1)
fig['layout'].update(title= i)
pyo.plot(fig)
I want all the plots to appear in a single browser window with a scroll bar.
You could just move the point where you declare the figure outside of the loop and give it more rows or columns.
For example, make a figure with as many columns as there are datapoints. Then put the plots in the ith column. Something like:
# use len(meas_set) as number of columns
fig = tools.make_subplots(rows=2, cols=len(meas_set), subplot_titles=('X-BAR Subplot','SIGMA Subplot'))
for i in meas_set:
for j in range(0,len(params)):
# your logic here
trace1 = go.Scatter(x=x_xbar,y=y_xbar,mode='lines',name=params[j])
trace2 = go.Scatter(x=x_sigma,y=y_sigma,mode='lines',name=params[j])
# use i for column position
fig.append_trace(trace1,1,i)
fig.append_trace(trace2,2,i)

Categories

Resources