I am using Plotly in python to generate figures. As in the title, I cannot update figure annotations with the update_annotations function.
The following is an example of multiplot.
data = pd.DataFrame(np.random.rand(10,3), columns=['A', 'B', 'C'], index=pd.date_range(start='2001-01-01', periods=10))
fig = make_subplots(rows=3, cols=1, subplot_titles=['Top','Middle', 'Bottom'])
fig.add_trace(go.Scatter(x=data.index, y=data['A'], mode='lines'), row=1, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['B'], mode='lines'), row=2, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['C'], mode='lines'), row=3, col=1)
I can change the name of the top figure from 'TOP' to 'TOP_TEST' and its position with the following code.
fig['layout']['annotations'][0]['text'] = 'TOP_TEST'
fig['layout']['annotations'][0]['x'] = 0.02
However, I do not understand why I cannot do the same with the function update_annotations. If it works, it seems to be much easier to change multiple parameters at once.
fig.update_annotations(row=1, col=1, text='TOP_TEST', x=0.02)
Thank you for any comment in advance.
have looked into plotly code. update_annotations() uses _select_annotations_like()
whenever you specify row or col parameters the internal method returns effectively an empty list. Code gets a bit more challenging to follow after that. This appears to be a bug
as a work around you can use update_annotations() with selector parameter. Demonstrated in code below
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px
data = pd.DataFrame(np.random.rand(10,3), columns=['A', 'B', 'C'], index=pd.date_range(start='2001-01-01', periods=10))
fig = make_subplots(rows=3, cols=1, subplot_titles=['Top','Middle', 'Bottom'])
fig.add_trace(go.Scatter(x=data.index, y=data['A'], mode='lines'), row=1, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['B'], mode='lines'), row=2, col=1)
fig.add_trace(go.Scatter(x=data.index, y=data['C'], mode='lines'), row=3, col=1)
# empty so nothing to update...
list(fig._select_annotations_like(prop="annotations", row=1, col=1))
# select based on text on we're ok
fig.update_annotations(selector={"text":"Top"}, text="TOP_TEST", x=.02)
Related
I wanted to know if there was an easier way I could put a linear regression line on a plotly subplot. The code I made below does not appear to be efficient and it makes it difficult to add annotations to the graph for the linear trendlines, which I want placed on the graph. Furthermore, it is hard to make axes and titles with this code.
I was wondering if there was a way I could create a go.Figure and somehow put it on the subplot. I have tried that, but plotly will only allow me to put the data from the figure on the subplot rather than the actual Figure, so I lose the title, axis, and trendline information. In addition, the trendline is hidden on the graphs because the scatterplot is overlaid on top of it. I tried changing how the data was displayed with data=(data[1],data[0]), but that did not work.
Basically, I want to know if there is a more efficient way of putting a trendline on the scatter plots than I pursued, so I can make it easier to set axes, set the graph size, create legends, etc, since it is difficult to work with what I coded .
sheets_dict=pd.ExcelFile('10.05.22_EMS172LabReport1.xlsx')
sheets_list=np.array(sheets_dict.sheet_names[2:])
fig=make_subplots(rows=7,cols=1)
i=0
for name in sheets_list:
df=sheets_dict.parse(name)
df.columns=df.columns.str.replace(' ','')
df=df.drop(df.index[0])
slope,y_int=np.polyfit(df.CURR1,df.VOLT1,1)
LR="Linear Fit: {:,.3e}x + {:,.3e}".format(slope,y_int)
rmse=np.sqrt(sum(slope*df.CURR1+y_int-df.VOLT1)**2)
df['Best Fit']=slope*df.CURR1+y_int
i+=1
fig.add_trace(
go.Scatter(name='Best Fit Line'+" ± {:,.3e}V".format(rmse),x=df['CURR1'],y=df['Best Fit'],
mode='lines',line_color='red',line_width=2),row=i, col=1)
fig.add_trace(
go.Scatter(name='Voltage',x=df['CURR1'],y=df['VOLT1'],mode='markers'),
row=i, col=1)
# fig.data = (fig.data[1],fig.data[0])
fig.show()
You can add titles and axes labels as follows:
import pandas as pd
import plotly.subplots as ps
fig=ps.make_subplots(rows=5,cols=1,subplot_titles=['Plot 1', 'Plot 2', 'Plot 3', 'Plot 4', 'Plot 5'])
fig.add_scatter(y=[2, 1, 3], row=1, col=1)
fig.add_scatter(y=[3, 1, 5], row=2, col=1)
fig.add_scatter(y=[2, 6, 3], row=3, col=1)
fig.add_scatter(y=[4, 0, 3], row=4, col=1)
fig.add_scatter(y=[3, 2, 3], row=5, col=1)
fig['layout']['xaxis']['title']='X-axis 1'
fig['layout']['xaxis2']['title']='X-axis 2'
fig['layout']['xaxis3']['title']='X-axis 3'
fig['layout']['xaxis4']['title']='X-axis 4'
fig['layout']['xaxis5']['title']='X-axis 5'
fig['layout']['yaxis']['title']='Y-axis 1'
fig['layout']['yaxis2']['title']='Y-axis 2'
fig['layout']['yaxis3']['title']='Y-axis 3'
fig['layout']['yaxis4']['title']='Y-axis 4'
fig['layout']['yaxis5']['title']='Y-axis 5'
fig.show()
The subplot_titles parameter in the make_subplots function is used to add plot titles. The fig['layout']['(x/y)axis(number)']['title'] is used to set the axes labels. Alternatively you can use:
fig.update_yaxes(title_text="yaxis 1 title", row=1, col=1)
or
fig.update_xaxes(title_text="xaxis 1 title", row=1, col=1)
To alter the plot sizes or spacing you can play around with the column_widths/row_heights or vertical_spacing/horizontal_spacing parameters of make_subplots:
https://plotly.com/python-api-reference/plotly.subplots.html#subplots
As for the legend, there's no direct way of associating a legend with a subplot other than what you already have but the first comment in the following link shows a way of adding an annotation on the subplot that can act sort of like a legend:
https://community.plotly.com/t/associating-subplots-legends-with-each-subplot-and-formatting-subplot-titles/33786
Trendlines are implemented in plotly.express with extensive functionality. See here. It is possible to create a subplot using that graph data, but I have created a subplot with a graph object to take advantage of your current code.
Since you did not provide specific data, I used the example data in ref. It is a data frame showing the rate of change in stock prices for several companies. It is in the form of a trend line added to it.
As for the graph, I have changed the height because a subplot requires height. The addition of axis labels for each subplot is specified in a matrix. If you need axis titles for all subplots, add them. Also, as a customization of the legend, we have grouped A group for the torrent lines and a group for the rate of change. As an example of the annotations, the slope values are set to 0 on the x-axis of each subplot and the y-axis is set to the position of the maximum value of each value.
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
df = px.data.stocks()
df.head()
date GOOG AAPL AMZN FB NFLX MSFT
0 2018-01-01 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
1 2018-01-08 1.018172 1.011943 1.061881 0.959968 1.053526 1.015988
2 2018-01-15 1.032008 1.019771 1.053240 0.970243 1.049860 1.020524
3 2018-01-22 1.066783 0.980057 1.140676 1.016858 1.307681 1.066561
4 2018-01-29 1.008773 0.917143 1.163374 1.018357 1.273537 1.040708
from plotly.subplots import make_subplots
fig = make_subplots(rows=6,cols=1, subplot_titles=df.columns[1:].tolist())
for i,c in enumerate(df.columns[1:]):
dff = df[[c]].copy()
slope,y_int=np.polyfit(dff.index, dff[c], 1)
LR="Linear Fit: {:,.3e}x + {:,.3e}".format(slope,y_int)
rmse=np.sqrt(sum(slope*dff.index+y_int-df[c])**2)
dff['Best Fit'] = slope*df.index+y_int
fig.add_trace(go.Scatter(
name='Best Fit Line'+" ± {:,.3e}V".format(rmse),
x=dff.index,
y=dff['Best Fit'],
mode='lines',
line_color='blue',
line_width=2,
legendgroup='group1',
legendgrouptitle_text='Trendline'), row=i+1, col=1)
fig.add_trace(go.Scatter(
x=dff.index,
y=dff[c],
legendgroup='group2',
legendgrouptitle_text='Rate of change',
mode='markers+lines', name=c), row=i+1, col=1)
fig.add_annotation(x=0.1,
y=dff[c].max(),
xref='x',
yref='y',
text='{:,.3e}'.format(rmse),
showarrow=False,
yshift=5, row=i+1, col=1)
fig.update_layout(autosize=True, height=800, title_text="Stock and Trendline")
fig.update_xaxes(title_text="index", row=6, col=1)
fig.update_yaxes(title_text="Rate of change", row=3, col=1)
fig.show()
Using Timelines with plotly.express, I can get a working Gantt Chart:
import plotly.express as px
import pandas as pd
df = pd.DataFrame([
dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28'),
dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'),
dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')
])
fig = px.timeline(df, x_start="Start", x_end="Finish", y="Task")
fig.update_yaxes(autorange="reversed") # otherwise tasks are listed from the bottom up
fig.show()
Following advice from here, I try to add it to a subplot with shared_xaxes = True
from plotly.subplots import make_subplots
fig_sub = make_subplots(rows=2, shared_xaxes=True)
fig_sub.append_trace(fig['data'][0], row=1, col=1)
fig_sub.append_trace(fig['data'][0], row=2, col=1)
fig_sub.show()
But it treats it like a graph_objects and doesn't display a Gantt chart.
Does anyone have any workarounds or suggestionts?
It is unclear why this is the case, but it appears that the date has been de-formatted, so once again, setting the date in the x-axis format will restore the timeline.
from plotly.subplots import make_subplots
fig_sub = make_subplots(rows=2, shared_xaxes=True)
fig_sub.append_trace(fig['data'][0], row=1, col=1)
fig_sub.append_trace(fig['data'][0], row=2, col=1)
fig_sub.update_xaxes(type='date')
fig_sub.show()
So I'm trying to combine two plots into one. I've made these plots with the plotly.express library rather than the plotly.graphs_objs.
Now, plotly suggests using: fig = make_subplots(rows=3, cols=1) and then append_trace or add_trace
However, this doesn't work for express objects since the append trace expects a single. trace. How can I add a express figure to a subplot? Or is this simply not possible. One option I've tried was fig.data[0] but this will only add the first line/data entry. Rn my code looks like:
double_plot = make_subplots(rows=2, cols=1, shared_xaxes=True)
histo_phases = phases_distribution(match_file_, range)
fig = px.line(match_file,
x="Minutes", y=["Communicatie", 'Gemiddelde'], color='OPPONENT')
fig.update_layout(
xaxis_title="Minuten",
yaxis_title="Communicatie per " + str(range) + "minuten",
legend_title='Tegenstander',
)
double_plot.append_trace(fig.data, row=1, col=1)
double_plot.append_trace(histo_phases.data, row=2, col=1)
Thanks in advance.
your code sample does not include creation of data frames and figures. Have simulated
it is as simple as adding each traces from figures created with plotly express to figure created with make_subplots()
for t in fig.data:
double_plot.append_trace(t, row=1, col=1)
for t in histo_phases.data:
double_plot.append_trace(t, row=2, col=1)
full code
from plotly.subplots import make_subplots
import plotly.express as px
import pandas as pd
import numpy as np
df = px.data.tips()
double_plot = make_subplots(rows=2, cols=1, shared_xaxes=True)
# histo_phases = phases_distribution(match_file_, range)
histo_phases = px.histogram(df, x="total_bill")
match_file = pd.DataFrame(
{
"Minutes": np.repeat(range(60), 10),
"Communicatie": np.random.uniform(1, 3, 600),
"Gemiddelde": np.random.uniform(3, 5, 600),
"OPPONENT": np.tile(list("ABCDEF"), 100),
}
)
fig = px.line(match_file, x="Minutes", y=["Communicatie", "Gemiddelde"], color="OPPONENT")
fig.update_layout(
xaxis_title="Minuten",
yaxis_title="Communicatie per " + str(range) + "minuten",
legend_title="Tegenstander",
)
for t in fig.data:
double_plot.append_trace(t, row=1, col=1)
for t in histo_phases.data:
double_plot.append_trace(t, row=2, col=1)
double_plot
I have the following code in a Jupyter Lab cell:
import investpy
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import pandas as pd
import numpy as np
search_result = investpy.search_quotes(
text='EXPO',
countries=['Sri Lanka'],
products=['stocks'],
n_results=1)
df = search_result.retrieve_historical_data(
from_date='01/01/2000',
to_date='21/04/2021')
fig = make_subplots(rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.,
specs=[[{'type':'candlestick'}],[{'type':'bar'}]])
fig.add_trace(go.Candlestick(x=df.index,
open=df.loc[:, 'Open'],
high=df.loc[:, 'High'],
low=df.loc[:, 'Low'],
close=df.loc[:, 'Close']), row=1, col=1)
fig.add_trace(go.Bar(x=df.index, y=df.loc[:, 'Volume']), row=2, col=1)
fig.update_layout(height=600, width=600, title_text="EXPO.N0000")
fig.show()
I don't get an error message but I get a blank output. I am trying plot price Candlestick with volume.
When I try the same on Jupyter Notebook it works.
I am wondering what is best practice to create subplots using Python Plotly. Is it to use plotly.express or the standard plotly.graph_objects?
I'm trying to create a figure with two subplots, which are stacked bar charts. The following code doesn't work. I didn't find anything useful in the official documentation. The classic Titanic dataset was imported as train_df here.
import plotly.express as px
train_df['Survived'] = train_df['Survived'].astype('category')
fig1 = px.bar(train_df, x="Pclass", y="Age", color='Survived')
fig2 = px.bar(train_df, x="Sex", y="Age", color='Survived')
trace1 = fig1['data'][0]
trace2 = fig2['data'][0]
fig = make_subplots(rows=1, cols=2, shared_xaxes=False)
fig.add_trace(trace1, row=1, col=1)
fig.add_trace(trace2, row=1, col=2)
fig.show()
I got the following figure:
What I expect is as follows:
I'm hoping that the existing answer suits your needs, but I'd just like to note that the statement
it's not possible to subplot stakedbar (because stacked bar are in facted figures and not traces
is not entirely correct. It's possible to build a plotly subplot figure using stacked bar charts as long as you put it together correctly using add_trace() and go.Bar(). And this also answers your question regarding:
I am wondering what is best practice to create subplots using Python Plotly. Is it to use plotly.express or the standard plotly.graph_objects?
Use plotly.express ff you find a px approach that suits your needs. And like in your case where you do not find it; build your own subplots using plotly.graphobjects.
Below is an example that will show you one such possible approach using the titanic dataset. Note that the column names are noe the same as yours since there are no capital letters. The essence of this approav is that you use go.Bar() for each trace, and specify where to put those traces using the row and col arguments in go.Bar(). If you assign multiple traces to the same row and col, you will get stacked bar chart subplots if you specify barmode='stack' in fig.update_layout(). Usingpx.colors.qualitative.Plotly[i]` will let you assign colors from the standard plotly color cycle sequentially.
Plot:
Code:
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
url = "https://raw.github.com/mattdelhey/kaggle-titanic/master/Data/train.csv"
titanic = pd.read_csv(url)
#titanic.info()
train_df=titanic
train_df
# data for fig 1
df1=titanic.groupby(['sex', 'pclass'])['survived'].aggregate('mean').unstack()
# plotly setup for fig
fig = make_subplots(2,1)
fig.add_trace(go.Bar(x=df1.columns.astype('category'), y=df1.loc['female'],
name='female',
marker_color = px.colors.qualitative.Plotly[0]),
row=1, col=1)
fig.add_trace(go.Bar(x=df1.columns.astype('category'), y=df1.loc['male'],
name='male',
marker_color = px.colors.qualitative.Plotly[1]),
row=1, col=1)
# data for plot 2
age = pd.cut(titanic['age'], [0, 18, 80])
df2 = titanic.pivot_table('survived', [age], 'pclass')
groups=['(0, 18]', '(18, 80]']
fig.add_trace(go.Bar(x=df2.columns, y=df2.iloc[0],
name=groups[0],
marker_color = px.colors.qualitative.Plotly[3]),
row=2, col=1)
fig.add_trace(go.Bar(x=df2.columns, y=df2.iloc[1],
name=groups[1],
marker_color = px.colors.qualitative.Plotly[4]),
row=2, col=1)
fig.update_layout(title=dict(text='Titanic survivors by sex and age group'), barmode='stack', xaxis = dict(tickvals= df1.columns))
fig.show()
fig.show()
From what I know, it's not possible to subplot stakedbar (because stacked bar are in facted figures and not traces)...
On behalf of fig.show(), you can put to check if the html file is okay for you (The plots are unfortunately one under the other...) :
with open('p_graph.html', 'a') as f:
f.write(fig1.to_html(full_html=False, include_plotlyjs='cdn',default_height=500))
f.write(fig2.to_html(full_html=False, include_plotlyjs='cdn',default_height=500))
try the code below to check if the html file generate can be okay for you:
import pandas as pd
import plotly.graph_objects as go
#Remove the .astype('category') to easily
#train_df['Survived'] = train_df['Survived'].astype('category')
Pclass_pivot=pd.pivot_table(train_df,values='Age',index='Pclass',
columns='Survived',aggfunc=lambda x: len(x))
Sex_pivot=pd.pivot_table(train_df,values='Age',index='Sex',
columns='Survived',aggfunc=lambda x: len(x))
fig1 = go.Figure(data=[
go.Bar(name='Survived', x=Pclass_pivot.index.values, y=Pclass_pivot[1]),
go.Bar(name='NotSurvived', x=Pclass_pivot.index.values, y=Pclass_pivot[0])])
# Change the bar mode
fig1.update_layout(barmode='stack')
fig2 = go.Figure(data=[
go.Bar(name='Survived', x=Sex_pivot.index.values, y=Sex_pivot[1]),
go.Bar(name='NotSurvived', x=Sex_pivot.index.values, y=Sex_pivot[0])])
# Change the bar mode
fig2.update_layout(barmode='stack')
with open('p_graph.html', 'a') as f:
f.write(fig1.to_html(full_html=False, include_plotlyjs='cdn',default_height=500))
f.write(fig2.to_html(full_html=False, include_plotlyjs='cdn',default_height=500))
I managed to generate the subplots using the add_bar function.
Code:
from plotly.subplots import make_subplots
# plotly can only support one legend per graph at the moment.
fig = make_subplots(
rows=1, cols=2,
subplot_titles=("Pclass vs. Survived", "Sex vs. Survived")
)
fig.add_bar(
x=train_df[train_df.Survived == 0].Pclass.value_counts().index,
y=train_df[train_df.Survived == 0].Pclass.value_counts().values,
text=train_df[train_df.Survived == 0].Pclass.value_counts().values,
textposition='auto',
name='Survived = 0',
row=1, col=1
)
fig.add_bar(
x=train_df[train_df.Survived == 1].Pclass.value_counts().index,
y=train_df[train_df.Survived == 1].Pclass.value_counts().values,
text=train_df[train_df.Survived == 1].Pclass.value_counts().values,
textposition='auto',
name='Survived = 1',
row=1, col=1
)
fig.add_bar(
x=train_df[train_df.Survived == 0].Sex.value_counts().index,
y=train_df[train_df.Survived == 0].Sex.value_counts().values,
text=train_df[train_df.Survived == 0].Sex.value_counts().values,
textposition='auto',
marker_color='#636EFA',
showlegend=False,
row=1, col=2
)
fig.add_bar(
x=train_df[train_df.Survived == 1].Sex.value_counts().index,
y=train_df[train_df.Survived == 1].Sex.value_counts().values,
text=train_df[train_df.Survived == 1].Sex.value_counts().values,
textposition='auto',
marker_color='#EF553B',
showlegend=False,
row=1, col=2
)
fig.update_layout(
barmode='stack',
height=400, width=1200,
)
fig.update_xaxes(ticks="inside")
fig.update_yaxes(ticks="inside", col=1)
fig.show()
Resulting plot:
Hope this is helpful to the newbies of plotly like me.