Modify values and path in plotly express sunburst using updatemenus - python

I'm plotting datasets with plotly express as sunburst charts.
One thing I'm trying to achieve is the possibility to select the values to be plotted so that the plot gets updated if the values change meaning that a different column in the dataframe is selected.
I've created an example based on this sunburst example in the official docs
https://plotly.com/python/sunburst-charts/#sunburst-of-a-rectangular-dataframe-with-plotlyexpress
There the column 'total_bill' is selected for plotting and that works. I can recreate the plot in that example.
Now I would like to use updatemenus to switch that to the 'tip' column that also holds floats and should be usable.
The example code I've tried:
import plotly.express as px
df = px.data.tips()
updatemenus = [{'buttons': [{'method': 'update',
'label': 'total_bill',
'args': [{'values': 'total_bill'}]
},
{'method': 'update',
'label': 'tip',
'args': [{'values': 'tip'}]
}],
'direction': 'down',
'showactive': True,}]
fig = px.sunburst(df, path=['day', 'time', 'sex'], values='total_bill')
fig.update_layout(updatemenus=updatemenus)
fig.show()
Now this will successfully plot the same plot as in the example, but when I select back and forth between the two updatemenu options, it doesn't behave properly.
I've also tried to use Series everywhere, but the results is the same.
I've also looked at this example, which has a similar focus
Plotly: How to select graph source using dropdown?
but the answers there didn't solve my problem either, since the sunburst in some way seems to behave differently from the scatter plot?
Any idea how to get this working?

similar to solution you arrived at. Use Plotly Express to build all the figures, collect into a dict
menu can now be built with dict comprehension
import plotly.express as px
df = px.data.tips()
# construct figures for all columns that can provide values
figs = {
c: px.sunburst(df, path=["day", "time", "sex"], values=c)
for c in ["total_bill", "tip"]
}
# choose a column that becomes the figure
fig = figs["total_bill"]
# now build menus, that use parameters that have been pre-built using plotly express
fig.update_layout(
updatemenus=[
{
"buttons": [
{
"label": c,
"method": "restyle",
"args": [
{
"values": [figs[c].data[0].values],
"hovertemplate": figs[c].data[0].hovertemplate,
}
],
}
for c in figs.keys()
]
}
]
)

Ok, I found one solution that works, but maybe someone could point me towards a "cleaner" solution, if possible.
I stumbled across this question that is actually unrelated:
Plotly: How to create sunburst subplot using graph_objects?
There, one solution was to save the figure data of a plotly express figure and reuse it in a graph objects figure.
This gave me an idea, that I could maybe save the data of each figure and then reuse (and update/modify) it in a third figure.
And, as it turns out, this works!
So here is the working example:
import plotly.express as px
df = px.data.tips()
# create two figures based on the same data, but with different values
fig1 = px.sunburst(df, path=['day', 'time', 'sex'], values='total_bill')
fig2 = px.sunburst(df, path=['day', 'time', 'sex'], values='tip')
# save the data of each figure so we can reuse that later on
ids1 = fig1['data'][0]['ids']
labels1 = fig1['data'][0]['labels']
parents1 = fig1['data'][0]['parents']
values1 = fig1['data'][0]['values']
ids2 = fig2['data'][0]['ids']
labels2 = fig2['data'][0]['labels']
parents2 = fig2['data'][0]['parents']
values2 = fig2['data'][0]['values']
# create updatemenu dict that changes the figure contents
updatemenus = [{'buttons': [{'method': 'update',
'label': 'total_bill',
'args': [{
'names': [labels1],
'parents': [parents1],
'ids': [ids1],
'values': [values1]
}]
},
{'method': 'update',
'label': 'tip',
'args': [{
'names': [labels2],
'parents': [parents2],
'ids': [ids2],
'values': [values2]
}]
}],
'direction': 'down',
'showactive': True}]
# create the actual figure to be shown and modified
fig = px.sunburst(values=values1, parents=parents1, ids=ids1, names=labels1, branchvalues='total')
fig.update_layout(updatemenus=updatemenus)
fig.show()

Related

interactive PCA with dropdown menu for the both axis with Plotly python

I'm trying to create an interactive PCA plot using plotly-express and graph objects in python (go.Scatter).
The plot should have 2 dropdowns menus (for x-axis and y-axis) to change between the first 5 PCA in the data.
Each data point also belongs to a treatment group either Before, After, or QC.
I was able to plot the PCA1 and PCA2 with plotly-express package but when trying to add the 2 dropdown menus that will update the plot between the 5 PCA it become a mess.
The example data is in my GitHub link,the first 5 columns are the first 5 PCAs.
The code the generate PC1 vs PC2 is:
labels={'0': 'PC 1 (22.0%)',
'1': 'PC 2 (19.6%)',
'2': 'PC 3 (11.1%)',
'3': 'PC 4 (8.2%)',
'4': 'PC 5 (3.9%)',
'color': 'Group'}
fig1 = px.scatter(components_df, x=0 , y=2 ,
color = 'Class',
width=1000, height=700,
template='presentation',
labels=labels,
title="PCA Score Plot (PC{} vs. PC{})".format(1, 2) ,
hover_data=['idx', 'SampleID']
)
fig1.show()
and it looks like this :
I'm trying to add 2 dropdown menus like I draw above to update the x-axis and the y-axis with the different PC's.
So first step was to add_trace on the figure to add other PCs to the figure but dont know how to add graph object to plotly-express to that what i did:
fig = go.Figure()
for Class, group in components_df.groupby("Class"):
# print(group[0])
fig.add_trace(go.Scatter(x=group[0], y=group[1], name=Class, mode='markers',
hovertemplate="Class=%s<br>PC1=%%{x}<br>PC2=%%{y}<extra></extra>"% Class))
for Class, group in components_df.groupby("Class"):
# print(group[0])
fig.add_trace(go.Scatter(x=group[0], y=group[2], name=Class, mode='markers',
hovertemplate="Class=%s<br>PC1=%%{x}<br>PC3=%%{y}<extra></extra>"% Class))
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active=0,
buttons=list(
[dict(label = 'All',
method = 'update',
args = [{'visible': [True, True, True, True,True]},
{'title': 'All',
'showlegend':True}]),
dict(label = 'PC1 PC1',
method = 'update',
args = [{'visible': [True, False, False, False, False]}, # the index of True aligns with the indices of plot traces
{'title': 'PC1 PC1',
'showlegend':True}]),
dict(label = 'PC1 PC2',
method = 'update',
args = [{'visible': [False, True, False, False, False]},
{'title': 'AAPL',
'showlegend':True}]),
dict(label = 'PC1 PC3',
method = 'update',
args = [{'visible': [False, False, True, False, False]},
{'title': 'AMZN',
'showlegend':True}]),
])
)
])
and that is the result:
There are many problems with that:
when changing the different options in the dropdown menu also the legends change (they suppose the stay fixed)
when changing the different options in the dropdown menu it does not lools like the data should be
it does not look nice like in the plotly-express.
there is only one dropdown
The code is base on many explanations in the documentation and blogs:
How to change plot data using dropdowns
Dropdown Menus in Python
Adding interactive filters
Setting the Font, Title, Legend Entries, and Axis Titles in Python
Any hint will be appreciated on how to add correct add_trac or correct dropdown menu
Thank you!!!
it's all about being highly structured and systematic. Plotly Express does generate a decent base chart. Use fig1.to_dict() to view graph object structures it has built
challenge I found with adding updatemenus to Plotly Express figure - it's a multi-trace figure with trace defining marker color. This can be simplified to a single trace figure with an array defining marker color
then it's a case of building updatemenus. This I have done as nested list comprehensions. Outer loop axis (each menu), inner loop principle component (each menu item)
Updates
magic colors - fair critique. I had used a hard coded dict for color mapping. Now programmatically build cmap Reverted back to static definition of cmap as dict comprehension is not wanted. Changed to a pandas approach to building cmap with lambda function
"y": 1 if ax == "x" else 0.9 We are building two drop downs, one for xaxis and one for yaxis. Hopefully it's obvious that the positions of these menus needs to be different. See docs: https://plotly.com/python/reference/layout/updatemenus/ For similar reason active property s being set. Make sure drop downs show what is actually plotted in the figure
legend refer back to point I made about multi-trace figures. Increases complexity! Have to use synthetic traces and this technique Plotly: How to update one specific trace using updatemenus?
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
components_df = pd.read_csv(
"https://raw.githubusercontent.com/TalWac/stakoverflow-Qustion/main/components_df.csv"
)
labels = {
"0": "PC 1 (22.0%)",
"1": "PC 2 (19.6%)",
"2": "PC 3 (11.1%)",
"3": "PC 4 (8.2%)",
"4": "PC 5 (3.9%)",
"color": "Group",
}
# cmap = {
# cl: px.colors.qualitative.Plotly[i]
# for i, cl in enumerate(
# components_df.groupby("Class", as_index=False).first()["Class"]
# )
# }
# revert back to static dictionary as dynamic building is not wanted
# cmap = {'After': '#636EFA', 'Before': '#EF553B', 'QC': '#00CC96'}
# use lambda functions instead of dict comprehension
df_c = components_df.groupby("Class", as_index=False).first()
df_c["color"] = df_c.apply(lambda r: px.colors.qualitative.Plotly[r.name], axis=1)
cmap = df_c.set_index("Class").loc[:,"color"].to_dict()
fig1 = go.Figure(
go.Scatter(
x=components_df["0"],
y=components_df["1"],
customdata=components_df.loc[:, ["idx", "SampleID", "Class"]],
marker_color=components_df["Class"].map(cmap),
mode="markers",
hovertemplate="Class=%{customdata[2]}<br>x=%{x}<br>y=%{y}<br>idx=%{customdata[0]}<br>SampleID=%{customdata[1]}<extra></extra>",
)
).update_layout(
template="presentation",
xaxis_title_text=labels["0"],
yaxis_title_text=labels["1"],
height=700,
)
fig1.update_layout(
updatemenus=[
{
"active": 0 if ax == "x" else 1,
"buttons": [
{
"label": f"{ax}-PCA{pca+1}",
"method": "update",
"args": [
{ax: [components_df[str(pca)]]},
{f"{ax}axis": {"title": {"text": labels[str(pca)]}}},
[0],
],
}
for pca in range(5)
],
"y": 1 if ax == "x" else 0.9,
}
for ax in ["x", "y"]
]
).update_traces(showlegend=False)
# add a legend by using synthetic traces. NB, this will leave markers at 0,0
fig1.add_traces(
px.scatter(
components_df.groupby("Class", as_index=False).first(),
x="0",
y="1",
color="Class",
color_discrete_map=cmap,
)
.update_traces(x=[0], y=[0])
.data
)

How to add a button to a Plotly Express graph to update a specific value?

What I'm Trying To Do...
I am trying to keep the formatting of a Plotly Express Scatterplot for data from the happiness report (2018). I simply want to create a button that can change the x-axis value between certain columns in a pandas dataframe (e.g. "GDP per Capita", "Social Support", etc.)
Here's an example of the scatter plot I am trying to create a button for to switch the X-value of the graph and have it update accordingly.
df = pd.read_csv("https://media.githubusercontent.com/media/ajgallard/happiness_report/main/data/2018_eng.csv")
fig = px.scatter(df,
x="GDP per capita", # The Value I am creating a button for
y="Score",
size="Population",
color="Continent",
hover_name="Country/Region",
size_max=60,
color_discrete_sequence=px.colors.qualitative.G10)
fig.show()
I get the following as a result:
Plotly Express Scatter Plot:
Attempted Solutions...
I attempted to implement a solution to a similar question from:
Build a plotly scatterplot with two drop down buttons one for x and one for y axis
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
df = pd.read_csv("https://media.githubusercontent.com/media/ajgallard/happiness_report/main/data/2018_eng.csv")
cols = df.columns[2:4].values.tolist() # "GDP per Capita" & "Social Support"
fig = go.Figure()
for col in cols:
figpx = px.scatter(df,
x=col,
y="Score",
size="Population",
color="Continent",
hover_name="Country/Region",
size_max=60,
color_discrete_sequence=px.colors.qualitative.G10).update_traces(visible=False)
fig.add_traces(figpx.data)
fig.update_layout(
updatemenus=[
{
"buttons":
[
{
"label": k,
"method": "update",
"args":
[
{"visible": [k for k in cols]},
],
}
for k in cols
]
}
]
).update_traces(visible=True, selector=0)
fig.show()
Using the above mentioned code I get the following as a result:
Plotly Express with Button Attempt:
What seems to be happening is that the data is overlayed one on top of the other and the button itself does not update anything data related.
Open to Any Potential Workarounds...
I'm fairly new to implementing Plotly Graphs in my data visualizations and I'm open to any other potential workarounds to get the kind of interactive visualization I was hoping to achieve.
there is a core concept with this approach. Need to be able to identify the traces that belong to a column. In this case where color is a categorical there are multiple traces per column
added a synthetic column to dataframe dynamically and included in hoverdata Plot. This then means it is in each trace and accessible as customdata[0][0]
updatemenus visible then builds truth list based on value in each trace
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
df = pd.read_csv("https://media.githubusercontent.com/media/ajgallard/happiness_report/main/data/2018_eng.csv")
cols = df.columns[2:4].values.tolist() # "GDP per Capita" & "Social Support"
fig = go.Figure()
for col in cols:
figpx = px.scatter(df.assign(Plot=col),
x=col,
y="Score",
size="Population",
color="Continent",
hover_name="Country/Region",
hover_data=["Plot"],
size_max=60,
color_discrete_sequence=px.colors.qualitative.G10).update_traces(visible=False)
fig.add_traces(figpx.data)
fig.update_layout(
updatemenus=[
{
"buttons":
[
{
"label": k,
"method": "update",
"args":
[
{"visible": [t.customdata[0][0]==k for t in fig.data]},
],
}
for k in cols
]
}
]
).update_traces(visible=True, selector=lambda t: t.customdata[0][0]==cols[0] )
fig
note Plot in hover, this is customdata[0][0]
Following the referenced answer, we need to add an element to be updated: the value of the x-axis and the title. As an additional response, the legend has been changed from duplicate to single. I am quoting #A. Donda answer response.
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
df = pd.read_csv("https://media.githubusercontent.com/media/ajgallard/happiness_report/main/data/2018_eng.csv")
cols = df.columns[2:4].values.tolist() # "GDP per Capita" & "Social Support"
fig = go.Figure()
for col in cols:
figpx = px.scatter(df,
x=col,
y="Score",
size="Population",
color="Continent",
hover_name="Country/Region",
size_max=60,
color_discrete_sequence=px.colors.qualitative.G10).update_traces(visible=False)
fig.add_traces(figpx.data)
fig.update_layout(
updatemenus=[
{
"buttons":
[
{
"label": f'{k}',
"method": "update",
"args":
[
{'x': [df[k]]},
{'xaxis':{'title':k}},
{"visible": k},
],
}
for k in cols
]
}
]
).update_traces(visible=True, selector=0)
names = set()
fig.for_each_trace(
lambda trace:
trace.update(showlegend=False)
if (trace.name in names) else names.add(trace.name))
figpx.data[0]['hovertemplate'] = '<b>%{hovertext}</b><br><br>Continent=Europe<br>GDP per capita=%{x}<br>Score=%{y}<br>Population=%{marker.size}<extra></extra>'
fig.show()

Plotly: Define colours for a pie graph - (using low-level API)

So I got this code to make a pie chart, but I wanted to changes the colors of each class to the colors listed in the colors variable. The documentation about formatting plots in json is really hard to find so I can't figure it out. Does anyone know how to add colors to the plot? The code can be found below.
def plot_donut(df):
colors = ['#ca0020','#f4a582','#D9DDDC','#92c5de','#0571b0']
trace1 = {
"hole": 0.8,
"type": "pie",
"labels": ['-2','-1','0','1','2'],
"values": df['Time Spent (seconds)'],
"showlegend": False
}
fig = go.Figure(data=data, layout=layout)
fig.show()
plot_donut(df)
Further to my earlier comment, please see the code below for specifying named colours for a Plotly donut (pie) graph.
Like you, I much prefer to use the low-level Plotly API, rather than relying on the convenience wrappers. The code below shows how this is done at a low level.
Example code:
import plotly.io as pio
values = [2, 3, 5, 7, 11]
colours = ['#440154', '#3e4989', '#26828e', '#35b779', '#fde725']
trace1 = {'values': values,
'marker': {'colors': colours}, # <--- This is the key.
'type': 'pie',
'hole': 0.8,
'showlegend': False}
pio.show({'data': [trace1]})
Output:

Create plotly linegraph with drop down menu using gapminder data

I'm trying to create a drop down menu using the following code:
import plotly.express as px
df = px.data.gapminder()
fig = px.line(df, x="year", y="lifeExp", color="country")
buttons = [
{'method' : 'update', 'label' : val, 'args' : df[df['continent'].eq(val)]['lifeExp']}
for val in df['continent'].unique()
]
# construct menus
updatemenus = [{'buttons': buttons,
'direction': 'down',
'showactive': True,}]
# update layout with buttons, and show the figure
fig.update_layout(updatemenus=updatemenus)
fig.show()
I've added the list comprehension to generate the y values, but I"m not sure what I'm currently doing wrong here.
I'm expecting to be able to select a different continent and have the plot update

Plotly: How to select graph source using dropdown?

I'm trying to embed multiple, selectable graphs in a single figure using Plotly, using a dropdown figure. I followed the dropdown example from Plotly, but they only show how to change graph characteristics (like visible, or type), not the underlying data. In my situation, I have a static X-axis and want to change the Y-values. Here's a minimal working example that can be run in a jupyter notebook:
import plotly
from plotly import graph_objs as go, offline as po, tools
po.init_notebook_mode()
import numpy as np
import json
x = list(np.linspace(-np.pi, np.pi, 100))
values_1 = list(np.sin(x))
values_2 = list(np.tan(x))
line = go.Scatter(
x=x,
y=values_1
)
updatemenus = [
{
'buttons': [
{
'method': 'restyle',
'label': 'Val 1',
'args': [
{'y': json.dumps(values_1)},
]
},
{
'method': 'restyle',
'label': 'Val 2',
'args': [
{'y': json.dumps(values_2)},
]
}
],
'direction': 'down',
'showactive': True,
}
]
layout = go.Layout(
updatemenus=updatemenus,
)
figure = go.Figure(data=[line], layout=layout)
po.iplot(figure)
However, while the approach seems to work like advertised for general graph attributes (like 'visible'), when I use 'y', it produces a straight line, where y goes from 0 to len(y), instead of the actual data I gave it. Here are images of the initial render, and then what happens when I select the dropdown item for the Tan(X) graph, then go back to the Sin(X):
How do I embed the data for multiple graphs into a single figure so that the user can select which one they want to view?
Updated answer using graph_objects:
As of version 4, you don't have to worry about offline versus online functionality. So drop the from plotly import graph_objs as go, offline as po and po.init_notebook_mode(), and just use import plotly.graph_objects as go. I've updated my original answer with a complete code snippet that shows the whole approach with multiple traces using plotly.graph_objects at the end. The solution to the question as it still stands will still be the same, namely:
'y' in updatemenus does not take a single list as an argument, but rather a list of lists like in 'y' = [values_1] where values_1 is a list in itself. So just replace your lines
{'y': json.dumps(values_1)}, and {'y': json.dumps(values_2)},
with
{'y': [values_1]}, and {'y': [values_2]},
to get these plots for the different options Val 1 and Val 2:
Some Details:
Values_1 is, unsurprisingly, a list of length 100 where each element is of type numpy.float. Replacing json.dumps(values_1) with values_1, and json.dumps(values_2) with values_2 will render the same plots as in your question. The reason why these plots are just straight lines, seems to be that it's the length of your lists that are being plotted, and not the values contained in that list. Or something to that effect.
Setting 'y' = values_1 is the same thing as assigning a single list to 'y'. But 'y' in updatemenus does not take a single list as an argument, but rather a list of lists like in 'y' = [values_1]. Why? Because you might want to plot multiple lists in the same figure like 'y' = [values_1, values_1b]. Have a look:
Plot for dropdown option Var 1:
Plot for dropdown option Var 2
Complete original code:
import plotly
from plotly import graph_objs as go, offline as po, tools
po.init_notebook_mode()
import numpy as np
import json
x = list(np.linspace(-np.pi, np.pi, 100))
values_1 = list(np.sin(x))
values_1b = [elem*-1 for elem in values_1]
values_2 = list(np.tan(x))
values_2b = [elem*-1 for elem in values_2]
line = go.Scatter(
x=x,
y=values_1
)
line2 = go.Scatter(
x=x,
y=values_1b
)
updatemenus = [
{
'buttons': [
{
'method': 'restyle',
'label': 'Val 1',
'args': [
{'y': [values_1, values_1b]},
]
},
{
'method': 'restyle',
'label': 'Val 2',
'args': [
{'y': [values_2, values_2b]},
]
}
],
'direction': 'down',
'showactive': True,
}
]
layout = go.Layout(
updatemenus=updatemenus,
)
figure = go.Figure(data=[line, line2], layout=layout)
po.iplot(figure)
Complete updated code:
# imports
import plotly.graph_objects as go
import numpy as np
# data
x = list(np.linspace(-np.pi, np.pi, 100))
values_1 = list(np.sin(x))
values_1b = [elem*-1 for elem in values_1]
values_2 = list(np.tan(x))
values_2b = [elem*-1 for elem in values_2]
# plotly setup]
fig = go.Figure()
# Add one ore more traces
fig.add_traces(go.Scatter(x=x, y=values_1))
fig.add_traces(go.Scatter(x=x, y=values_1b))
# construct menus
updatemenus = [{'buttons': [{'method': 'update',
'label': 'Val 1',
'args': [{'y': [values_1, values_1b]},]
},
{'method': 'update',
'label': 'Val 2',
'args': [{'y': [values_2, values_2b]},]}],
'direction': 'down',
'showactive': True,}]
# update layout with buttons, and show the figure
fig.update_layout(updatemenus=updatemenus)
fig.show()
Plot with version 4 default layout:

Categories

Resources