How to de-dupe legend in faceted choropleth chart? - python

I'm trying to create faceted maps by the column rank in my df. Each map will display the product for each state. I want the color of the product to be consistent across maps.
With the solution below I can achieve that, but the legend will show multiple entries for the same product, one for each state. How can I have the legend show only one entry per distinct product?
import pandas as pd
import plotly.express as px
from random import randint
df = pd.DataFrame({'rank': [1,1,1,1,2,2,2,2],'product':['A','B','C','D','C','D','Z','X'],'state':['WA','OR','CA','ID','WA','OR','CA','ID']})
unique_hi = df['product'].unique()
color_discrete_map = {unique_hi[k]: '#%06X' % randint(0, 0xFFFFFF) for k in range(len(unique_hi))}
fig = px.choropleth(df, color='product', facet_col="rank",facet_col_wrap=2,
locations="state", #featureidkey="properties.district",
locationmode="USA-states",
projection="mercator",height=600,
color_discrete_map=color_discrete_map,
title='Regional products'
)
fig.update_geos(fitbounds="locations", visible=False)
fig.update_layout(margin={"r":0,"t":30,"l":0,"b":0})
fig.show()

If you check the contents of the created map in fig.data, you will find the original name of the legend, which is collected and only the names of the non-duplicated.
import pandas as pd
import plotly.express as px
from random import randint
df = pd.DataFrame({'rank': [1,1,1,1,2,2,2,2],'product':['A','B','C','D','C','D','Z','X'],'state':['WA','OR','CA','ID','WA','OR','CA','ID']})
unique_hi = df['product'].unique()
color_discrete_map = {unique_hi[k]: '#%06X' % randint(0, 0xFFFFFF) for k in range(len(unique_hi))}
fig = px.choropleth(df, color='product', facet_col="rank",facet_col_wrap=2,
locations="state", #featureidkey="properties.district",
locationmode="USA-states",
projection="mercator",height=600,
color_discrete_map=color_discrete_map,
title='Regional products'
)
fig.update_geos(fitbounds="locations", visible=False)
fig.update_layout(margin={"r":0,"t":30,"l":0,"b":0})
# update
names = set()
fig.for_each_trace(
lambda trace:
trace.update(showlegend=False)
if (trace.name in names) else names.add(trace.name))
fig.show()
The way to add a product name as an annotation is not possible to specify it using map coordinates (I referred to this for the rationale), so adding the following code will make the annotation, but all products will need to be manually addressed. Upon further investigation, it seems that a combination of go.choroplethmapbox() and go.scattergeo() would do it. In this case, you will need to rewrite the code from scratch.
fig.add_annotation(
x=0.2,
xref='paper',
y=0.85,
yref='paper',
text='A',
showarrow=False,
font=dict(
color='yellow',
size=14
)
)

Related

How plot points based on categorical variable in plotly

I am using Plotly for visualization. I want to make plot, and give the points colors based on categorical variable.
fig = go.Figure()
fig.add_trace(go.Scatter(x=df.Predicted, y=df.Predicted,colors='Category',mode='markers',
))
fig.add_trace(go.Scatter(x=df.Predicted, y=df.real , colors='Category'
))
fig.show()
where Category is column in my dataframe. How can I do this kind of graph
you have implied a data frame structure which I have simulated
it's simpler to use Plotly Express higher level API that graph objects
have used to calls to px.scatter() to generate traces defined in your question. Plus have renamed traces in second call to ensure legend is clear and made them lines
import numpy as np
import pandas as pd
import plotly.express as px
df = pd.DataFrame(
{
"Predicted": np.sort(np.random.uniform(3, 15, 100)),
"real": np.sort(np.random.uniform(3, 15, 100)),
"Category": np.random.choice(list("ABCD"), 100),
}
)
px.scatter(df, x="Predicted", y="Predicted", color="Category").add_traces(
px.line(df, x="Predicted", y="real", color="Category")
.for_each_trace(
lambda t: t.update(name="real " + t.name)
) # make it clear in legend this is second set of traces
.data
)

Plotly Set Trace Position in a Figure

I'm a newbie in Plotly and I was wondering if there is a way to specify where a new trace needs to be centered within the Figure object.
Just to be more clear, this is an example:
import plotly.express as px
import plotly.graph_objects as go
df = pd.DataFrame(something)
fig = go.Figure()
for i in [40,45,50]:
fig.add_shape(
go.layout.Shape(
type='line',
xref='x',
yref='y',
x0=line_data[i]["min"],
y0=i,
x1=line_data[i]["max"],
y1=i,
),
)
fig.add_trace(
go.Scatter(
x=df.ColA.values,
y=df.ColB.values,
mode='markers',
)
)
This is the result
My goal is to build an histogram of the points in each horizontal line.
I don't know if there is a better and faster way, but my idea was to add more traces, each one with an histogram, and then center those traces in each line. Is there a way to do it? Maybe some position parameter for a trace, like (xcenter=7.5, ycenter=50)?
My ideal result should be:
you describe histogram / frequency multiple observed items
have mapped these to y-axis using base
import numpy as np
import plotly.graph_objects as go
df = pd.DataFrame({40:np.random.normal(5,2, 200).astype(int),50:np.random.normal(6,2, 200).astype(int),60:np.random.normal(6.5,2, 200).astype(int)})
# change to frequency of observed values
df2 = df[40].value_counts().to_frame().join(df[50].value_counts(), how="outer").join(df[60].value_counts(), how="outer")
# plot bar of frequency, setting base based on observation
fig = go.Figure([go.Bar(x=df2.index, y=df2[c]/len(df2), base=c, name=c) for c in df2.columns])
fig.update_layout(barmode="overlay")

Add dropdown menu to plotly express treemap

I am currently trying to add a dropdown menu to my treemap plot
The code I am using :
import pandas as pd
import plotly.express as px
fig = px.treemap(df,
path=['RuleName','RuleNumber','ParaInvolved',"CreationP","MAjP"],
color='Somme',
hover_data=["RuleDecision","RuleMAJ"],
color_continuous_scale='RdBu')
fig.show()
The problem I am facing is that in my column "RuleName" I have 151 different values (but 1300 rows in total), that's why I'm trying to add a button allowing myself to chose for what RuleName value I want to plot my treemap. For now I am using a barbaric method consisting in filtering my dataframe by each RuleName value, which lead me to get 151 different treemap. I don't find any solution on that website or any other.
Thanks for your help
Here I'm basically using the same logic from this answer but I use px.treemap(...).data[0] to produce the traces instead of go.
import plotly.express as px
import plotly.graph_objects as go
df = px.data.tips()
# We have a list for every day
# In your case will be gropuby('RuleName')
# here for every element d
# d[0] is the name(key) and d[1] is the dataframe
dfs = list(df.groupby("day"))
first_title = dfs[0][0]
traces = []
buttons = []
for i,d in enumerate(dfs):
visible = [False] * len(dfs)
visible[i] = True
name = d[0]
traces.append(
px.treemap(d[1],
path=['day', 'time', 'sex'],
values='total_bill').update_traces(visible=True if i==0 else False).data[0]
)
buttons.append(dict(label=name,
method="update",
args=[{"visible":visible},
{"title":f"{name}"}]))
updatemenus = [{'active':0, "buttons":buttons}]
fig = go.Figure(data=traces,
layout=dict(updatemenus=updatemenus))
fig.update_layout(title=first_title, title_x=0.5)
fig.show()

Change hover text of a plotly express treemap

I want to show in a treemap just the label and value of each item, not parent or ID. I have defined it with plotly express. No matter how much I have tinkered with it, I haven’t been able to restrict hover text to the fields I need. Check the code and capture
import plotly.express as px
fig = px.treemap(dfconcepto, path=['type','name'],
values = 'count',
width=900, height=900,
hover_data = ['count'],
)
fig.show()
I also have tried to create it with non-express treemap. Hovertext is what I want, but then a treemap with two levels is rendered asymmetric.
What I want is something like the hovertext of non-express treemap, but balanced and symmetric as in express treemap
What can I do?
Thanks in advance!
It seems to me you should overwrite your hover template
import pandas as pd
import plotly.express as px
url = "https://gist.githubusercontent.com/jlchulilla/3b4e40f68ba73b5dbcb661a1d861f308/raw/e564973db30a4612aba60c5b26dd108edc98f048/test2sof.csv"
df = pd.read_csv(url).drop("Unnamed: 0", axis=1)
fig = px.treemap(df, path=['type','name'],
values = 'coincidencia',
width=900, height=900,
)
# Now your hovertemplate looks like
# fig.data[0].hovertemplate
# 'labels=%{label}<br>coincidencia=%{value}<br>parent=%{parent}<br>id=%{id}<extra></extra>'
# But it seems to me you want something like
fig.data[0].hovertemplate = '%{label}<br>%{value}'
fig.show()

how can i create subplots with plotly express?

been loving the plotly express graphs but want to create a dashboard with them now. Did not find any documentation for this. Is this possible?
I was struggling to find a response on this as well so I ended up having to create my own solution (see my full breakdown here: How To Create Subplots Using Plotly Express)
Essentially make_subplots() takes in plot traces to make the subplots instead of figure objects like that which Express returns. So what you can do is, after creating your figures in Express, is break apart the Express figure objects into their traces and then re-assemble their traces into subplots.
Code:
import dash_core_components as dcc
import plotly.express as px
import plotly.subplots as sp
# Create figures in Express
figure1 = px.line(my_df)
figure2 = px.bar(my_df)
# For as many traces that exist per Express figure, get the traces from each plot and store them in an array.
# This is essentially breaking down the Express fig into it's traces
figure1_traces = []
figure2_traces = []
for trace in range(len(figure1["data"])):
figure1_traces.append(figure1["data"][trace])
for trace in range(len(figure2["data"])):
figure2_traces.append(figure2["data"][trace])
#Create a 1x2 subplot
this_figure = sp.make_subplots(rows=1, cols=2)
# Get the Express fig broken down as traces and add the traces to the proper plot within in the subplot
for traces in figure1_traces:
this_figure.append_trace(traces, row=1, col=1)
for traces in figure2_traces:
this_figure.append_trace(traces, row=1, col=2)
#the subplot as shown in the above image
final_graph = dcc.Graph(figure=this_figure)
Output:
Working off #mmarion's solution:
import plotly.express as px
from plotly.offline import plot
from plotly.subplots import make_subplots
figures = [
px.line(df1),
px.line(df2)
]
fig = make_subplots(rows=len(figures), cols=1)
for i, figure in enumerate(figures):
for trace in range(len(figure["data"])):
fig.append_trace(figure["data"][trace], row=i+1, col=1)
plot(fig)
This is easily extended into the column dimension.
From the docs:
**facet_row**
(string: name of column in data_frame) Values from this column are used to assign marks to facetted subplots in the vertical direction.
**facet_col**
(string: name of column in data_frame) Values from this column are used to assign marks to facetted subplots in the horizontal direction.
Get here some examples too.
https://medium.com/#plotlygraphs/introducing-plotly-express-808df010143d
Unfortunately, it is not at the moment. See the following issue to get updated: https://github.com/plotly/plotly_express/issues/83
I solved it by combining all the data in a single dataframe,
with a column called "type" that distinguishes the two plots.
Then I used facet_col to create (some kind of) subplot:
px.scatter(df3, x = 'dim1', y = 'dim2', color = 'labels', facet_col='type')
Try this function out. You have to pass in the plotly express figures into the function and it returns a subplot figure.
#quick_subplot function
def quick_subplot(n,nrows,ncols, *args): #n:number of subplots, nrows:no.of. rows, ncols:no of cols, args
from dash import dcc
import plotly.subplots as sp
from plotly.subplots import make_subplots
fig=[] #list to store figures
for arg in args:
fig.append(arg)
combined_fig_title=str(input("Enter the figure title: "))
tok1=int(input("Do you want to disable printing legends after the first legend is printed ? {0:Disable, 1:Enable} : "))
fig_traces={} #Dictionary to store figure traces
subplt_titles=[]
#Appending the traces of the figures to a list in fig_traces dictionary
for i in range(n):
fig_traces[f'fig_trace{i}']=[]
for trace in range(len(fig[i]["data"])):
fig_traces[f'fig_trace{i}'].append(fig[i]["data"][trace])
if(i!=0 & tok1==0):
fig[i]["data"][trace]['showlegend'] = False #Disabling other legends
subplt_titles.append(str(input(f"Enter subplot title for subplot-{i+1}: ")))
#Creating a subplot
#Change height and width of figure here if necessary
combined_fig=sp.make_subplots(rows = nrows, cols = ncols, subplot_titles = subplt_titles)
combined_fig.update_layout(height = 500, width = 1200, title_text = '<b>'+combined_fig_title+'<b>', title_font_size = 25)
#Appending the traces to the newly created subplot
i=0
for a in range(1,nrows+1):
for b in range(1, ncols+1):
for traces in fig_traces[f"fig_trace{i}"]:
combined_fig.append_trace(traces, row=a, col=b)
i+=1
#Setting axis titles
#X-axis
combined_fig['layout']['xaxis']['title']['font']['color']='blue'
tok2=int(input("Separate x-axis titles?{0:'No',1:'Yes'}: "))
for i in range(max(nrows,ncols)):
if i==0:
combined_fig['layout']['xaxis']['title']=str(input(
f"Enter x-axis's title: "))
if tok2 & i!=0:
combined_fig['layout'][f'xaxis{i+1}']['title']=str(input(
f"Enter x-axis {i+1}'s title: "))
combined_fig['layout'][f'xaxis{i+1}']['title']['font']['color']='blue'
#Y-axis
combined_fig['layout']['yaxis']['title']['font']['color']='blue'
tok3=int(input("Separate y-axis titles?{0:'No',1:'Yes'}: "))
for i in range(max(nrows,ncols)):
if i==0:
combined_fig['layout']['yaxis']['title']=str(input(
f"Enter y-axis's title: "))
if tok3 & i!=0:
combined_fig['layout'][f'yaxis{i+1}']['title']=str(input(
f"Enter y-axis {i+1}'s title: "))
combined_fig['layout'][f'yaxis{i+1}']['title']['font']['color']='blue'
combined_fig['layout']['xaxis']['title']['font']['color']='blue'
combined_fig['layout']['yaxis']['title']['font']['color']='blue'
return combined_fig
f=quick_subplot(2,1,2,fig1,fig2)
f.show()

Categories