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

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

Related

Python / Seaborn - How to plot the names of each value in a scatterplot

first of all, in case I comment on any mistakes while writing this, sorry, English is not my first language.
I'm a begginer with Data vizualiation with python, I have a dataframe with 115 rows, and I want to do a scatterplot with 4 quadrants and show the values in R1 (image below for reference)
enter image description here
At moment this is my scatterplot. It's a football player dataset so I want to plot the name of the players name in the 'R1'. Is that possible?
enter image description here
You can annotate each point by making a sub-dataframe of just the players in a quadrant that you care about based on their x/y values using plt.annotate. So something like this:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
##### Making a mock dataset #################################################
names = ['one', 'two', 'three', 'four', 'five', 'six']
value_1 = [1, 2, 3, 4, 5, 6]
value_2 = [1, 2, 3, 4, 5, 6]
df = pd.DataFrame(zip(names, value_1, value_2), columns = ['name', 'v_1', 'v_2'])
#############################################################################
plt.rcParams['figure.figsize'] = (10, 5) # sizing parameter to make the graph bigger
ax1 = sns.scatterplot(x = value_1, y = value_2, s = 100) # graph code
# Code to make a subset of data that fits the specific conditions that I want to annotate
quadrant = df[(df.v_1 > 3) & (df.v_2 > 3)].reset_index(drop = True)
# Code to annotate the above "quadrant" on the graph
for x in range(len(quadrant)):
plt.annotate('Player: {}\nValue 1: {}\nValue 2: {}'.format(quadrant.name[x], quadrant.v_1[x], quadrant.v_2[x]),
(quadrant.v_1[x], quadrant.v_2[x])
Output graph:
If you're just working in the notebook and don't need to save the image with all the player's names, then using a "hover" feature might be a better idea. Annotating every player's name might become too busy for the graph, so just hovering over the point might work out better for you.
%matplotlib widget # place this or "%matplotlib notebook" at the top of your notebook
# This allows you to work with matplotlib graphs in line
import mplcursors # this is the module that allows hovering features on your graph
# using the same dataframe from above
ax1 = sns.scatterplot(x = value_1, y = value_2, s = 100)
#mplcursors.cursor(ax1, hover=2).connect("add") # add the plot to the hover feature of mplcursors
def _(sel):
sel.annotation.set_text('Player: {}\nValue 1: {}\nValue 2: {}'.format(df.name[sel.index], sel.target[0], sel.target[0])) # set the text
# you don't need any of the below but I like to customize
sel.annotation.get_bbox_patch().set(fc="lightcoral", alpha=1) # set the box color
sel.annotation.arrow_patch.set(arrowstyle='-|>', connectionstyle='angle3', fc='black', alpha=.5) # set the arrow style
Example outputs from hovering:
You can do two (or more) scatter plots on a single figure.
If I understand correctly what you want to do, you could separate your dataset in two :
Points for which you don't want the name to be plotted
Points for which you want the name to be plotted
You can then plot the second data set and display the name.
Without any other details on your problem, it is difficult to do more. You could edit your question and add a minimal example of your data set.

How do I add a secondary legend that explains what symbols mean with plotly express?

I have a plot which uses US states to map symbols. I currently assign symbols using the "state" column in my dataframe so that I can select particular states of interest by clicking or double clicking on the Plotly Express legend. This part is working fine. However, the symbol mapping I'm using also communicates information about territory, e.g. triangle-down means poor coverage in that state and many states will share this symbol. I would like to add another legend that shows what each shape means. How can I do this in Plotly Express? Alternatively, is there a way to display symbols in a footnote? I could also give the symbol definitions there.
The goal is to display that circle=Medium coverage, triangle-down=poor coverage, etc. in addition to the individual state legend I already have. If the legend is clickable such that I can select entire groups based on the symbol shape that would be the best possible outcome.
Thank you for any tips!
I tried using html and footnotes to display the symbols but it did not work.
as noted in comment, it can be achieved by additional traces on different axes
have simulated some data that matches what is implied in image and comments
from scatter figure extract out how symbols and colors have been assigned to states
build another scatter that is effectively a legend.
import pandas as pd
import numpy as np
import plotly.express as px
df_s = pd.read_html(
"https://en.wikipedia.org/wiki/List_of_states_and_territories_of_the_United_States"
)[1].iloc[:, 0:2]
df_s.columns = ["name", "state"]
# generate a dataframe that matches structure in image and question
df = pd.DataFrame(
{"activity_month": pd.date_range("1-jan-2020", "today", freq="W")}
).assign(
value=lambda d: np.random.uniform(0, 1, len(d)),
state=lambda d: np.random.choice(df_s["state"], len(d)),
)
# straight forward scatter
fig = px.scatter(df, x="activity_month", y="value", symbol="state", color="state")
# extract out how symbols and colors have been assigned to states
df_symbol = pd.DataFrame(
[
{"symbol": t.marker.symbol, "state": t.name, "color": t.marker.color}
for t in fig.data
]
).assign(y=lambda d: d.index//20, x=lambda d: d.index%20)
# build a figure that effectively the legend
fig_legend = px.scatter(
df_symbol,
x="x",
y="y",
symbol="symbol",
color="state",
text="state",
color_discrete_sequence=df_symbol["color"]
).update_traces(textposition="middle right", showlegend=False, xaxis="x2", yaxis="y2")
# insert legend into scatter and format axes
fig.add_traces(fig_legend.data).update_layout(
yaxis_domain=[.15, 1],
yaxis2={"domain": [0, .15], "matches": None, "visible": False},
xaxis2={"visible":False},
xaxis={"position":0, "anchor":"free"},
showlegend=False
)

Maintaining Data Color After Dropdown Updates In Plotly

I'm trying to create a plot using Plotly that allows you to select from dropdown menus what features are being plotted on the x and y axis. My approach works, but there's a set of actions that remove the coloring of the points being plotted.
Here's a Colab with the steps to reproduce this written out, and done with minimal code (Plotly plays nice with Colab):
https://colab.research.google.com/drive/19PCS8QH9n6VVN9UBOKMay99VuSXq1QGG?usp=sharing
If you want to use your own environment, the following code will reproduce the issue after you've done the following 2 steps:
Pick one of the two dropdown menus and change the selected value at least one time
Change the selected value on the dropdown menu you have not changed yet
You should then see that the original coloring of the points is lost.
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.io as pio
pio.templates.default = "plotly_dark"
def get_correlation_figure_please(merged_df):
cols = [col for col, t in zip(merged_df.columns, merged_df.dtypes) if t != object]
start_dropdown_indices = [0, 0]
# Create the scatter plot of the initially selected variables
fig = px.scatter(
merged_df,
x=cols[start_dropdown_indices[0]],
y=cols[start_dropdown_indices[1]],
color='serial_number_id',
)
# Create the drop-down menus which will be used to choose the desired file characteristics for comparison
drop_downs = []
for axis in ['x', 'y']:
drop_downs.append([
dict(
method = 'update',
args = [
{axis : [merged_df[cols[k]]]},
{'%saxis.title.text'%axis: cols[k]},
# {'color':[merged_df['serial_number_id']],'color_discrete_map':SERIALS_TO_INDEX},
],
label = cols[k]) for k in range(len(cols))
])
# Sets up various apsects of the Plotly figure that is currently being produced. This ranges from
# aethetic things, to setting the dropdown menues as part of the figure
fig.update_layout(
title_x=0.4,
showlegend=False,
updatemenus=[{
'active': start_j,
'buttons': drop_down,
'x': 1.125,
'y': y_height,
'xanchor': 'left',
'yanchor': 'top',
} for drop_down, start_j, y_height in zip(drop_downs, start_dropdown_indices, [1, .85])])
return fig
# Set up a dummy dataframe with 20 points each with 5 featuers
df = pd.DataFrame({str(j):np.random.rand(20) for j in range(5)})
# Set up a column of dummied serial numbers (to be used to decide the coloring of each point)
df['serial_number_id'] = df['1'].map(lambda x : '0' if x < 1/3 else ('1' if x < 2/3 else '2'))
fig = get_correlation_figure_please(df)
fig.show()

Plotly: How to set a fill color between two vertical lines?

Using matplotlib, we can "trivially" fill the area between two vertical lines using fill_between() as in the example:
https://matplotlib.org/3.2.1/gallery/lines_bars_and_markers/fill_between_demo.html#selectively-marking-horizontal-regions-across-the-whole-axes
Using matplotlib, I can make what I need:
We have two signals, and I''m computing the rolling/moving Pearson's and Spearman's correlation. When the correlations go either below -0.5 or above 0.5, I want to shade the period (blue for Pearson's and orange for Spearman's). I also darken the weekends in gray in all plots.
However, I'm finding a hard time to accomplish the same using Plotly. And it will also be helpful to know how to do it between two horizontal lines.
Note that I'm using Plotly and Dash to speed up the visualization of several plots. Users asked for a more "dynamic type of thing." However, I'm not a GUI guy and cannot spend time on this, although I need to feed them with initial results.
BTW, I tried Bokeh in the past, and I gave up for some reason I cannot remember. Plotly looks good since I can use either from Python or R, which are my main development tools.
Thanks,
Carlos
I don't think there is any built-in Plotly method that that is equivalent to matplotlib's fill_between() method. However you can draw shapes so a possible workaround is to draw a grey rectangle and set the the parameter layer="below" so that the signal is still visible. You can also set the coordinates of the rectangle outside of your axis range to ensure the rectangle extends to the edges of the plot.
You can fill the area in between horizontal lines by drawing a rectangle and setting the axes ranges in a similar manner.
import numpy as np
import plotly.graph_objects as go
x = np.arange(0, 4 * np.pi, 0.01)
y = np.sin(x)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=x,
y=y
))
# hard-code the axes
fig.update_xaxes(range=[0, 4 * np.pi])
fig.update_yaxes(range=[-1.2, 1.2])
# specify the corners of the rectangles
fig.update_layout(
shapes=[
dict(
type="rect",
xref="x",
yref="y",
x0="4",
y0="-1.3",
x1="5",
y1="1.3",
fillcolor="lightgray",
opacity=0.4,
line_width=0,
layer="below"
),
dict(
type="rect",
xref="x",
yref="y",
x0="9",
y0="-1.3",
x1="10",
y1="1.3",
fillcolor="lightgray",
opacity=0.4,
line_width=0,
layer="below"
),
]
)
fig.show()
You haven't provided a data sample so I'm going to use a synthetical time-series to show you how you can add a number of shapes with defined start and stop dates for several different categories using a custom function bgLevel
Two vertical lines with a fill between them very quickly turns into a rectangle. And rectangles can easily be added as shapes using fig.add_shape. The example below will show you how to find start and stop dates for periods given by a certain critera. In your case these criteria are whether or not the value of a variable is higher or lower than a certain level.
Using shapes instead of traces with fig.add_trace() will let you define the position with regards to plot layers using layer='below'. And the shapes outlines can easily be hidden using line=dict(color="rgba(0,0,0,0)).
Plot 1: Time series figure with random data:
Plot 2: Background is set to an opaque grey when A > 100 :
Plot 2: Background is also set to an opaque red when D < 60
Complete code:
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import datetime
pd.set_option('display.max_rows', None)
# data sample
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().reset_index()
# function to set background color for a
# specified variable and a specified level
# plotly setup
fig = px.line(df, x='dates', y=df.columns[1:])
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')
def bgLevels(fig, variable, level, mode, fillcolor, layer):
"""
Set a specified color as background for given
levels of a specified variable using a shape.
Keyword arguments:
==================
fig -- plotly figure
variable -- column name in a pandas dataframe
level -- int or float
mode -- set threshold above or below
fillcolor -- any color type that plotly can handle
layer -- position of shape in plotly fiugre, like "below"
"""
if mode == 'above':
m = df[variable].gt(level)
if mode == 'below':
m = df[variable].lt(level)
df1 = df[m].groupby((~m).cumsum())['dates'].agg(['first','last'])
for index, row in df1.iterrows():
#print(row['first'], row['last'])
fig.add_shape(type="rect",
xref="x",
yref="paper",
x0=row['first'],
y0=0,
x1=row['last'],
y1=1,
line=dict(color="rgba(0,0,0,0)",width=3,),
fillcolor=fillcolor,
layer=layer)
return(fig)
fig = bgLevels(fig = fig, variable = 'A', level = 100, mode = 'above',
fillcolor = 'rgba(100,100,100,0.2)', layer = 'below')
fig = bgLevels(fig = fig, variable = 'D', level = -60, mode = 'below',
fillcolor = 'rgba(255,0,0,0.2)', layer = 'below')
fig.show()
I think that fig.add_hrect() and fig.add_vrect() are the simplest approaches to reproducing the MatPlotLib fill_between functionality in this case:
https://plotly.com/python/horizontal-vertical-shapes/
For your example, add_vrect() should do the trick.

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