how can i create subplots with plotly express? - python

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

Related

Plotly to show 2 decimal points when hovering over the chart, not nearest point

I am using Plotly to build a line chart, and when I hover over the line I would like it to display the x and y axis values up to 2 decimal points, instead of displaying the nearest data point on the line chart. To explain better, please see the example:
df = pd.DataFrame({'col1':[0.5,1.5,2.5], 'time':[2,3.5,4.5]})
def plot():
fig = go.Figure()
fig.add_trace(go.Scatter(x = df['time'],
y = df['col1'],
mode='lines', name = 'time plot',
hovertemplate='%{x:.2f}: %{y:.2f}'))
fig.update_layout(title='Plot', xaxis_tickformat = '.3f')
So, when I hover over the line, I can see x and y axis values to the nearest point from my dataset. I would like to be able to see 2 decimal points, for example, if I hover over the line, I want to see the points 2.11, 2.12 etc from the x-axis, even though they are not available on the data points.
I cannot think of a way to do this using plotly methods but I was able to think of a workaround by creating another line plot and setting the opacity to zero.
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# your data
df = pd.DataFrame({'col1':[0.5,1.5,2.5], 'time':[2,3.5,4.5]})
# get the min and max X axis values
min_val, max_val = df['time'].agg([min, max])
# use np.arange to create the range with a step of .01
x = np.arange(min_val, max_val+.01, .01)
# create a zeros array of the same length
y = np.zeros(len(x))
# create your go.Figure object
fig = go.Figure()
# add your traces
fig.add_trace(go.Scatter(x=df['time'],
y=df['col1'],
name='time plot',
hovertemplate='%{x:.2f}: %{y:.2f}'))
fig.add_trace(go.Scatter(x=x,
y=y,
showlegend=False, # remove line from legend
hoverinfo='x',
opacity=0)) # set opacity to zero so it does not display on the graph
# your layout
fig.update_layout(hovermode='x unified', xaxis_tickformat = '.2f', title='Plot')
fig.show()

How to de-dupe legend in faceted choropleth chart?

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

Plotly - Generate Figures in Loop; Put all Figures in Single Subplot

I have a semi-complicated code setup so not sure how to provide an example.
That said, I have nested loops that ultimately generate a single plotly plot using FigureFactory from Plotly with ff.create_annotated_heatmap. This heatmap represents a heatmap of a particular parameter I am looping through.
Ultimately, I'd like to compare all of these subplots.
So in my loop I do
fig = ff.create_annotated_heatmap( z, x, y, annotation_text)
Then save the figures and compare manually.
However is there a way I can make a figure_list=[] and then by using figure_list.append(fig) later cycle through them and display them on a subplot?
start by building 20 annotated heat maps in a list
choose two to add to a sub-plots figure (for purpose of this example randomly)
you can use standard technique of adding traces from figures previously created to sub-plots figure
key with annotated heat maps are annotations. these are part of the layout. Key to transferring these is to consider which x&y axis is used by the row so xref and yref can be set correctly
for verification, show() is used but would not form part of code to demonstrate annotations have been correctly transferred. In answer I have shown final output - the sub-plot
import numpy as np
from plotly.subplots import make_subplots
import plotly.figure_factory as ff
# fmt: off
teams = [f"Team {n}" for n in list("ABCDEFG")]
games = [f"Game {num}" for num in ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight']]
# fmt: on
# generate lots of annotated traces with randomized data
figs = [
ff.create_annotated_heatmap(
z=np.random.uniform(0, 1, [3, 3]),
x=np.random.choice(teams, 3, replace=False).tolist(),
y=np.random.choice(games, 3, replace=False).tolist(),
annotation_text=np.random.choice(["Win", "Lose"], [3, 3]),
)
for _ in range(20)
]
fig = make_subplots(
rows=2,
cols=1,
)
# randomly add two annotated heatmaps to sub-plots
for r, f in enumerate(np.random.choice(figs, 2)):
# show plot to test that integrated plot is correct
f.update_layout(margin={"l":0,"r":0,"t":0,"b":0}, height=100, autosize=False).show()
# this step is straight forward, move trace across
fig.add_trace(f.data[0], row=r + 1, col=1)
# now the annotations, need to override xref and yref based on target row
for a in f.to_dict()["layout"]["annotations"]:
fig.add_annotation(
**{
**a,
**{
"xref": "x" if r == 0 else f"x{r+1}",
"yref": "y" if r == 0 else f"y{r+1}",
},
}
)
# now let's view the sub-plots
fig.update_layout(margin={"l":0,"r":0,"t":0,"b":0}, height=200, autosize=False).show()
commentary
fig.update_layout(margin={"l":0,"r":0,"t":0,"b":0}, height=200, autosize=False).show() is not part of the solution. With any figure you can set margins https://plotly.com/python/setting-graph-size/. l-left, r-right, t-top, b-bottom
you stated that you want sub-plots. https://plotly.com/python/subplots/ https://plotly.com/python-api-reference/generated/plotly.subplots.make_subplots.html
start by creating a figure with sub-plots fig = make_subplots( rows=2, cols=1, )
then add traces to this sub-plotted figure. enumerating over list of traces, knowing will add to row in sub plot that corresponds to iterator enumeration

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

Plotly subplot represent same y-axis name with same color and single legend

I am trying to create a plot for two categories in a subplot. 1st column represent category FF and 2nd column represent category RF in the subplot.
The x-axis is always time and y-axis is remaining columns. In other words, it is a plot with one column vs rest.
1st category and 2nd category always have same column names just only the values differs.
I tried to generate the plot in a for loop but the problem is plotly treats each column name as distinct and thereby it represents the lines in different color for y-axis with same name. As a consequence, in legend also an entry is created.
For example, in first row Time vs price2010 I want both subplot FF and RF to be represented in same color (say blue) and a single entry in legend.
I tried adding legendgroup in go.Scatter but it doesn't help.
import pandas as pd
from pandas import DataFrame
from plotly import tools
from plotly.offline import init_notebook_mode, plot, iplot
import plotly.graph_objs as go
from plotly.subplots import make_subplots
CarA = {'Time': [10,20,30,40 ],
'Price2010': [22000,26000,27000,35000],
'Price2011': [23000,27000,28000,36000],
'Price2012': [24000,28000,29000,37000],
'Price2013': [25000,29000,30000,38000],
'Price2014': [26000,30000,31000,39000],
'Price2015': [27000,31000,32000,40000],
'Price2016': [28000,32000,33000,41000]
}
ff = DataFrame(CarA)
CarB = {'Time': [8,18,28,38 ],
'Price2010': [19000,20000,21000,22000],
'Price2011': [20000,21000,22000,23000],
'Price2012': [21000,22000,23000,24000],
'Price2013': [22000,23000,24000,25000],
'Price2014': [23000,24000,25000,26000],
'Price2015': [24000,25000,26000,27000],
'Price2016': [25000,26000,27000,28000]
}
rf = DataFrame(CarB)
Type = {
'FF' : ff,
'RF' : rf
}
fig = make_subplots(rows=len(ff.columns), cols=len(Type), subplot_titles=('FF','RF'),vertical_spacing=0.3/len(ff.columns))
labels = ff.columns[1:]
for indexC, (cat, values) in enumerate(Type.items()):
for indexP, params in enumerate(values.columns[1:]):
trace = go.Scatter(x=values.iloc[:,0], y=values[params], mode='lines', name=params,legendgroup=params)
fig.append_trace(trace,indexP+1, indexC+1)
fig.update_xaxes(title_text=values.columns[0],row=indexP+1, col=indexC+1)
fig.update_yaxes(title_text=params,row=indexP+1, col=indexC+1)
fig.update_layout(height=2024, width=1024,title_text="Car Analysis")
iplot(fig)
It might not be a good solution, but so far I can able to come up only with this hack.
fig = make_subplots(rows=len(ff.columns), cols=len(Type), subplot_titles=('FF','RF'),vertical_spacing=0.2/len(ff.columns))
labels = ff.columns[1:]
colors = [ '#a60000', '#f29979', '#d98d36', '#735c00', '#778c23', '#185900', '#00a66f']
legend = True
for indexC, (cat, values) in enumerate(Type.items()):
for indexP, params in enumerate(values.columns[1:]):
trace = go.Scatter(x=values.iloc[:,0], y=values[params], mode='lines', name=params,legendgroup=params, showlegend=legend, marker=dict(
color=colors[indexP]))
fig.append_trace(trace,indexP+1, indexC+1)
fig.update_xaxes(title_text=values.columns[0],row=indexP+1, col=indexC+1)
fig.update_yaxes(title_text=params,row=indexP+1, col=indexC+1)
fig.update_layout(height=1068, width=1024,title_text="Car Analysis")
legend = False
If you combine your data into a single tidy data frame, you can use a simple Plotly Express call to make the chart: px.line() with color, facet_row and facet_col

Categories