Setting boundaries for datetime x axis on Plotly (Python) - python

I have to plot some chronologically-ordered values (one value per month, in my case) on a Plotly (Python) graph. Also, I have to add a "end of period label" (i.e. a marker with text indicating the last value of the series) that has to be positioned at 'middle right'.
A working example would be something like this:
import pandas as pd
import numpy as np
import plotly.graph_objects as go
date_range = pd.to_datetime(pd.date_range(start='1/1/2013', end='9/1/2022', freq='M').tolist()).date
values = np.random.randint(100, size=len(date_range)).tolist()
fig = go.Figure(
)
fig.add_trace(go.Scatter(
showlegend=False,
x=date_range,
y=values,
mode='lines',
line=dict(
width=2,
color="red",
)
)
)
fig.add_trace(go.Scatter(
showlegend=False,
x=[date_range[-1]],
y=[values[-1]],
text=[values[-1]],
textposition='middle right',
texttemplate="%{text:.3f}",
mode='markers+text',
line=dict(
width=2,
color="red",
)
)
)
fig.update_layout(
xaxis=dict(
tickformat="%m\n<b>%Y", dtick="M3",
)
)
which produces the following plot:
I am facing the following problem: the end of period label "extends" beyond the last value of the date range and makes the x axis go into the green area, which are all undesired months (for example, those that extend beyond the last value of the date range and into 2023).
I tried several things to "erase" or delete that undesired part of the x axis, but nothing worked properly: either the end of period label was cut in half or the whole x axis disappeared.
Thank you in advance for any help or suggestion.

as per #r0beginners comments
given text is outside graph area use an annotation for the text
make marker scatter just mode=markers
explicitly state xaxis range range=date_range[[0,-1]]
import pandas as pd
import numpy as np
import plotly.graph_objects as go
date_range = pd.to_datetime(
pd.date_range(start="1/1/2013", end="9/1/2022", freq="M").tolist()
).date
values = np.random.randint(100, size=len(date_range)).tolist()
fig = go.Figure()
fig.add_trace(
go.Scatter(
showlegend=False,
x=date_range,
y=values,
mode="lines",
line=dict(
width=2,
color="red",
),
)
)
fig.add_trace(go.Scatter(
showlegend=False,
x=[date_range[-1]],
y=[values[-1]],
mode='markers',
marker_size=15
)
)
fig.add_annotation(
x = date_range[-1],
y = values[-1],
text = values[-1],
xshift=10,
yshift=0,
showarrow=False
)
fig.update_layout(
xaxis=dict(
tickformat="%m\n<b>%Y",
dtick="M3",
range=date_range[[0,-1]]
)
)

Related

How to create a polar plot with error bands in plotly?

This post is closely related to this one but I need a solution that works with plotly and python. I would like to use plotly to create a polar plot with error bands. My dataset can be divided into multiple groups, where each of them should have its own trace. Samples within each group should be aggregated so that only the mean line and the error band are plotted.
I noticed that seaborn has the function sns.lineplot implemented that already goes into the right direction but I would like to bend the x-axis in a 360 degree circle so we end up with a polar plot:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
rng = np.random.default_rng(12345)
# create data
a = rng.random(size=(10,10))
df = pd.DataFrame(a,columns=[f"r_{idx}" for idx in range(10)])
df['id'] = [1,2,3,4,5,6,7,8,9,10]
df['group'] = ['a','a','a','a','a','b','b','b','b','b']
df = df.melt(id_vars=['id','group'],var_name='region')
# use seaborn
plt.figure()
sns.lineplot(data=df,x='region',y='value',hue='group')
plotly.express in contrast offers the function px.scatter_polar which creates a polar plot but apparently does not allow to aggregate the samples which leads to a quite unreadable plot:
# plot scatter polarplot with plotly. Does not allow to aggregate
fig = px.scatter_polar(df,r='value',theta='region',color='group')
fig.show()
Based on the data in the question, we have created a graph with each region replaced by an angle, creating maximum, average, and minimum values. The code consists of three graphs for each group. Finally, the data is updated to the names of the regions, starting from the north and working clockwise.' Since the labels for 'r_0' and 'r_9' overlap, the last label is none and the first label is added with the removed portion.
df_a = (df
.query('group == "a"')
.groupby(['group','region']).value.agg(['mean','max','min'], axis=1)
.assign(y=np.arange(0,361,40))
).reset_index()
df_a.head()
group region mean max min y
0 a r_0 0.372739 0.854742 0.081595 0
1 a r_1 0.538439 0.948881 0.159896 40
2 a r_2 0.613516 0.931988 0.330891 80
3 a r_3 0.573116 0.903454 0.095898 120
4 a r_4 0.443399 0.860551 0.257074 160
df_b = (df
.query('group == "b"')
.groupby(['group','region']).value.agg(['mean','max','min'], axis=1)
.assign(y=np.arange(0,361,40))
).reset_index()
fig = go.Figure()
# df_a:group a
fig.add_trace(go.Scatterpolar(
r=df_a['max'],
theta=df_a['y'],
mode='lines',
name='a:Max',
line_color='rgb(230,230,250)',
opacity=0.6
))
fig.add_trace(go.Scatterpolar(
r=df_a['mean'],
theta=df_a['y'],
mode='lines',
name='a:mean',
line_color='blue',
fill='tonext',
fillcolor='rgb(230,230,250)',
opacity=0.6
))
fig.add_trace(go.Scatterpolar(
r=df_a['min'],
theta=df_a['y'],
mode='lines',
name='a:Min',
line_color='rgb(230,230,250)',
fill='tonext',
fillcolor='rgb(230,230,250)',
opacity=0.6
))
# df_b: group b
fig.add_trace(go.Scatterpolar(
r=df_b['max'],
theta=df_b['y'],
mode='lines',
name='b:Max',
line_color='rgb(255,250,205)',
opacity=0.6
))
fig.add_trace(go.Scatterpolar(
r=df_b['mean'],
theta=df_b['y'],
mode='lines',
name='b:mean',
line_color='orange',
fill='tonext',
fillcolor='rgb(255,250,205)',
opacity=0.6
))
fig.add_trace(go.Scatterpolar(
r=df_b['min'],
theta=df_b['y'],
mode='lines',
name='b:Min',
line_color='rgb(255,250,205)',
fill='tonext',
fillcolor='rgb(255,250,205)',
opacity=0.6
))
labels = df_a['region'].tolist()
labels[-1] = ''
labels[0] = 'r_0<br>r_9'
fig.update_layout(
height=600,
template=None,
polar=dict(
radialaxis=dict(
angle=90,
tickvals=np.arange(0,1.0,0.2),
),
angularaxis=dict(
rotation=90,
direction = "clockwise",
thetaunit='radians',
dtick=40,
showticklabels=True,
tickvals=np.arange(0,361,40),
ticktext=labels
)
)
)
fig.show()

Python Plotly display other information on Hover

Here is the code that I have tried:
# import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
df = pd.read_csv("resultant_data.txt", index_col = 0, sep = ",")
display=df[["Velocity", "WinLoss"]]
pos = lambda col : col[col > 0].sum()
neg = lambda col : col[col < 0].sum()
Related_Display_Info = df.groupby("RacerCount").agg(Counts=("Velocity","count"),
WinLoss=("WinLoss","sum"),
Positives=("WinLoss", pos),
Negatives=("WinLoss", neg),
)
# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add traces
fig.add_trace(
go.Scatter(x=display.index, y=display["Velocity"], name="Velocity", mode="markers"),
secondary_y=False
)
fig.add_trace(
go.Scatter(x=Related_Display_Info.index,
y=Related_Display_Info["WinLoss"],
name="Win/Loss",
mode="markers",
marker=dict(
color=(
(Related_Display_Info["WinLoss"] < 0)
).astype('int'),
colorscale=[[0, 'green'], [1, 'red']]
)
),
secondary_y=True,
)
# Add figure title
fig.update_layout(
title_text="Race Analysis"
)
# Set x-axis title
fig.update_xaxes(title_text="<b>Racer Counts</b>")
# Set y-axes titles
fig.update_yaxes(title_text="<b>Velocity</b>", secondary_y=False)
fig.update_yaxes(title_text="<b>Win/Loss/b>", secondary_y=True)
fig.update_layout(hovermode="x unified")
fig.show()
The output is:
But I was willing to display the following information when I hover on the point:
RaceCount = From Display dataframe value Number of the race corresponding to the dot I hover on.
Velocity = From Display Dataframe value Velocity at that point
Counts = From Related_Display_Info Column
WinLoss = From Related_Display_Info Column
Positives = From Related_Display_Info Column
Negatives = From Related_Display_Info Column
Please can anyone tell me what to do to get this information on my chart?
I have checked this but was not helpful since I got many errors: Python/Plotly: How to customize hover-template on with what information to show?
Data:
RacerCount,Velocity,WinLoss
111,0.36,1
141,0.31,1
156,0.3,1
141,0.23,1
147,0.23,1
156,0.22,1
165,0.2,1
174,0.18,1
177,0.18,1
183,0.18,1
114,0.32,1
117,0.3,1
120,0.29,1
123,0.29,1
126,0.28,1
129,0.27,1
120,0.32,1
144,0.3,1
147,0.3,1
159,0.27,1
165,0.26,1
168,0.25,1
156,0.29,1
165,0.26,1
168,0.26,1
165,0.28,1
213,0.17,1
243,0.15,1
249,0.14,1
228,0.54,1
177,0.67,1
180,0.66,1
183,0.65,1
192,0.66,1
195,0.62,1
198,0.6,1
180,0.66,1
222,0.56,1
114,0.41,1
81,0.82,1
102,0.56,1
111,0.55,1
90,1.02,1
93,1.0,1
90,1.18,1
90,1.18,1
93,1.1,1
96,1.07,1
99,1.04,1
102,0.99,1
105,0.94,1
108,0.92,1
111,0.9,1
162,0.66,1
159,0.63,1
162,0.65,-1
162,0.66,-1
168,0.64,-1
159,0.68,-1
162,0.67,-1
174,0.62,-1
168,0.65,-1
171,0.64,-1
198,0.55,-1
300,0.47,-1
201,0.56,-1
174,0.63,-1
180,0.61,-1
171,0.64,-1
174,0.62,-1
303,0.47,-1
312,0.48,-1
258,0.51,-1
261,0.51,-1
264,0.5,-1
279,0.47,-1
288,0.48,-1
294,0.47,-1
258,0.52,-1
261,0.51,-1
267,0.5,-1
222,0.53,-1
171,0.64,-1
177,0.63,-1
177,0.63,-1
Essentially, this code ungroups the data frame before plotting to create the hovertemplate you're looking for.
As stated in the comments, the data has to have the same number of rows to be shown in the hovertemplate. At the end of my answer, I added the code all in one chunk.
Since you have hovermode as x unified, you probably only want one of these traces to have hover content.
I slightly modified the creation of Related_Display_Info. Instead of WinLoss, which is already in the parent data frame, I modified it to WinLoss_sum, so there wouldn't be a naming conflict when I ungrouped.
Related_Display_Info = df.groupby("RacerCount").agg(
Counts=("Velocity","count"), WinLoss_sum=("WinLoss","sum"),
Positives=("WinLoss", pos), Negatives=("WinLoss", neg))
Now it's time to ungroup the data you grouped. I created dui (stands for display info ungrouped).
dui = pd.merge(df, Related_Display_Info, how = "outer", on="RacerCount",
suffixes=(False, False))
I created the hovertemplate for both traces. I passed the entire ungrouped data frame to customdata. It looks like the only column that isn't in the template is the original WinLoss.
# create hover template for all traces
ht="<br>".join(["<br>RacerCount: %{customdata[0]}",
"Velocity: %{customdata[1]:.2f}",
"Counts: %{customdata[3]}",
"Winloss: %{customdata[4]}",
"Positives: %{customdata[5]}",
"Negatives: %{customdata[6]}<br>"])
The creation of fig is unchanged. However, the traces are both based on dui. Additionally, the index isn't RacerCount, so I used the literal field instead.
# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add traces
fig.add_trace(go.Scatter(x=dui["RacerCount"], y=dui["Velocity"],
name="Velocity", mode="markers",
customdata=dui, hovertemplate=ht),
secondary_y=False)
fig.add_trace(
go.Scatter(x = dui["RacerCount"], y=dui["WinLoss_sum"], customdata=dui,
name="Win/Loss", mode="markers",
marker=dict(color=((dui["WinLoss_sum"] < 0)).astype('int'),
colorscale=[[0, 'green'], [1, 'red']]),
hovertemplate=ht),
secondary_y=True)
All the code altogether (for easier copy + paste)
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
df = pd.read_clipboard(sep = ',')
display=df[["Velocity", "WinLoss"]]
pos = lambda col : col[col > 0].sum()
neg = lambda col : col[col < 0].sum()
Related_Display_Info = df.groupby("RacerCount").agg(
Counts=("Velocity","count"), WinLoss_sum=("WinLoss","sum"),
Positives=("WinLoss", pos), Negatives=("WinLoss", neg))
# ungroup the data for the hovertemplate
dui = pd.merge(df, Related_Display_Info, how = "outer", on="RacerCount",
suffixes=(False, False))
# create hover template for all traces
ht="<br>".join(["<br>RacerCount: %{customdata[0]}",
"Velocity: %{customdata[1]:.2f}",
"Counts: %{customdata[3]}",
"Winloss: %{customdata[4]}",
"Positives: %{customdata[5]}",
"Negatives: %{customdata[6]}<br>"])
# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add traces
fig.add_trace(go.Scatter(x=dui["RacerCount"], y=dui["Velocity"],
name="Velocity", mode="markers",
customdata=dui, hovertemplate=ht),
secondary_y=False)
fig.add_trace(
go.Scatter(x = dui["RacerCount"], y=dui["WinLoss_sum"], customdata=dui,
name="Win/Loss", mode="markers",
marker=dict(color=((dui["WinLoss_sum"] < 0)).astype('int'),
colorscale=[[0, 'green'], [1, 'red']]),
hovertemplate=ht),
secondary_y=True)
# Add figure title
fig.update_layout(
title_text="Race Analysis"
)
# Set x-axis title
fig.update_xaxes(title_text="<b>Racer Counts</b>")
# Set y-axes titles
fig.update_yaxes(title_text="<b>Velocity</b>", secondary_y=False)
fig.update_yaxes(title_text="<b>Win/Loss/b>", secondary_y=True)
fig.update_layout(hovermode="x unified")
fig.show()

How to set properties on a row/column in a grid of plotly plots?

Suppose I'm plotting 2 charts on each row, 10 rows, using plotly:
from plotly.subplots import make_subplots
import plotly.graph_objects as go
N=10
fig = make_subplots(rows=N, cols=2)
fig.add_trace(
go.Scatter(x=x, y=y),
row=1, col=1
)
fig.add_trace(
go.Candlestick(
x=df_kline.index,
open=df_kline['O'],
high=df_kline['H'],
low=df_kline['L'],
close=df_kline['C']
),
row=1, col=2
)
:
fig.show()
How can I set a yaxis_title for each row?
How can I set the y-axis range to be [1,10] for the entire first column, and only show the ticklabels at the bottom of the plot?
I hope this qualifies as a single question rather than two, as it's dealing with group-by-row / group-by-col.
FOOTNOTE:
Following from the comments in the accepted answer, one can set settings on multiple subplots thus:
subplot_settings = {
'rangeslider_visible': True,
'rangeslider_thickness': 0.05
}
kwargs = {
f'xaxis{k}' : subplot_settings
for k in range(2, 2*N, 2)
}
fig.update_layout(**kwargs)
(Untested)
Since no data was presented, I responded to the challenge with four subplots using a certain stock price; the title and range of the y-axis for each row in the first one can be set in the y-axis settings. Also, in the settings section of the subplot, if you set the shared axis to x-axis, only the bottom x-axis will be available.
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import numpy as np
import pandas as pd
x = np.linspace(0,1, 100)
y = np.cumsum(x)
import yfinance as yf
df_kline = yf.download("AAPL", start="2021-01-01", end="2021-03-01")
df_kline.rename(columns={'Open':'O','High':'H','Low':'L','Close':'C'}, inplace=True)
N=2
fig = make_subplots(rows=N, cols=2,
shared_xaxes=True, )# vertical_spacing=0.1
fig.add_trace(
go.Scatter(x=x, y=y),
row=1, col=1
)
fig.add_trace(
go.Candlestick(
x=df_kline.index,
open=df_kline['O'],
high=df_kline['H'],
low=df_kline['L'],
close=df_kline['C'],
),
row=1, col=2,
)
fig.add_trace(
go.Scatter(x=x, y=y),
row=2, col=1
)
fig.add_trace(
go.Candlestick(
x=df_kline.index,
open=df_kline['O'],
high=df_kline['H'],
low=df_kline['L'],
close=df_kline['C'],
),
row=2, col=2
)
fig.update_layout(autosize=False, height=600, width=1000, showlegend=False)
# rangeslider visible false
fig.update_layout(title='Custome subplots',
xaxis2=dict(rangeslider=dict(visible=False)),
xaxis4=dict(rangeslider=dict(visible=False)))
# yxais customize
fig.update_layout(yaxis1=dict(range=[0,10], title='test'),
yaxis3=dict(range=[0,10], title='test2'))
fig.show()

How to get rid of scribbled lines in plotly line plot?

I am trying to plot a subplot using plotly where I have some line plots and all the plots in the subplot needs to share the same x-axis as shown.
fig = make_subplots(
rows=5,
cols=1,
vertical_spacing=0.05,
subplot_titles=['Count / Anzahl', 'min_nValue', 'max_nValue', 'avg_nValue', 'sum_nValue'],
shared_xaxes=True,
)
fig.append_trace(go.Scatter(
x=df_dict_nValueAgg['Erste_15_Minuten']['KurzName'],
y=df_dict_nValueAgg['Erste_15_Minuten']['min_nValueNorm'],
name = "min_nValue_" + "Erste_15_Minuten",
mode='lines+markers',
#legendgroup = 2
), row=2, col=1)
fig.append_trace(go.Scatter(
x=df_dict_nValueAgg['Erste_15_Minuten']['KurzName'],
y=df_dict_nValueAgg['Erste_15_Minuten']['max_nValueNorm'],
name = "max_nValue_" + "Erste_15_Minuten",
mode='lines+markers',
#legendgroup = 2
), row=2, col=1)
.
.
.
# couple of plots more
.
.
fig.update_layout(
legend_orientation="v",
height=1000,
width=2000,
title_text=currentEventTitle+pastEventTitle+nAttributes,
)
fig.update_xaxes(tickangle=45)
fig.write_image('fig1.png')
fig.show()
which gives me this figure
So I filter the data for each
The last three plots produces scribbled lines. Now I understand that since I am filtering the data based on four values of a column i.e. Erste_15_Minuten, Zweite_15_Minuten, Dritte_15_Minuten and Letzte_15_Minuten the number of xticks for the last three plots are unequal or maybe in different order. Is there a way where I can avoid this problem? Switching to Bar Plot would avoid this problem but I need to use only line plot. Thank you in advance.
from looking at your code. There are multiple data frames of same format in a dict
there is no guarantee that these dataframes are in same KurzName order
have simulated data to match above understanding
then have provided a way to re-order data frames to be consistent with third for generating line plots
merge to first dataframe on KurzName
use index of first dataframe to define sort order
looking at image
bar chart - ok, not order dependent
first line chart is scribbled, second is not
hence forcing order of data frames has resolved the issue
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# fmt: off
words = ['adipisci', 'aliquam', 'amet', 'consectetur', 'dolor', 'dolore', 'dolorem', 'eius', 'est', 'etincidunt', 'ipsum', 'labore', 'magnam', 'modi', 'neque', 'non', 'numquam', 'porro', 'quaerat', 'quiquia', 'quisquam', 'sed', 'sit', 'tempora', 'ut', 'velit', 'voluptatem']
# fmt: on
r = np.random.choice(words, [2, 30])
r = np.char.add(r[0], np.char.add("_", r[1]))
# Erste_15_Minuten, Zweite_15_Minuten, Dritte_15_Minuten and Letzte_15_Minuten the number
df_dict_nValueAgg = {}
for k in [
"Erste_15_Minuten",
"Zweite_15_Minuten",
"Dritte_15_Minuten",
"Letzte_15_Minuten",
]:
np.random.shuffle(r)
df_dict_nValueAgg[k] = pd.DataFrame(
{
"KurzName": r,
"Count": np.random.randint(100, 300, len(r)),
"min_nValueNorm": np.random.uniform(0, 0.5, len(r)),
"max_nValueNorm": np.random.uniform(0.5, 1, len(r)),
}
)
fig = make_subplots(
rows=5,
cols=1,
vertical_spacing=0.05,
subplot_titles=[
"Count / Anzahl",
"min_nValue",
"max_nValue",
"avg_nValue",
"sum_nValue",
],
shared_xaxes=True,
)
for k in df_dict_nValueAgg.keys():
fig.add_trace(
go.Bar(
x=df_dict_nValueAgg[k]["KurzName"], y=df_dict_nValueAgg[k]["Count"], name=k
),
row=1,
col=1,
)
# this will be scibbled as each dataframe is in a different order
for k in df_dict_nValueAgg.keys():
fig.add_trace(
go.Scatter(
x=df_dict_nValueAgg[k]["KurzName"],
y=df_dict_nValueAgg[k]["max_nValueNorm"],
name=k + " scribble max",
),
row=4,
col=1,
)
# force order of dataframes to be same as first
for i, k in enumerate(df_dict_nValueAgg.keys()):
df = df_dict_nValueAgg[k]
if i > 0:
df = df.merge(
df_dict_nValueAgg[list(df_dict_nValueAgg.keys())[0]]
.loc[:, "KurzName"]
.reset_index(),
on="KurzName",
).sort_values("index")
fig.add_trace(
go.Scatter(
x=df["KurzName"],
y=df["max_nValueNorm"],
name=k + " max",
),
row=5,
col=1,
)
fig

Convert plotly marker from continuous to discrete

Following is my input file i'm trying to display on a map using plotly.
data.csv
lat,long,type
-7.80715,110.371203,1
-7.791087,110.368346,3
-7.778744,110.365107,7
-7.77877,110.365379,4
The script works but the scale is displayed in a continuous format. I tried to convert the column type to text as mentioned here but I couldn't get it to work. Is there a easier way to fix this problem?
df = pd.read_csv("data.csv").dropna()
fig = go.Figure(go.Scattermapbox(
lat=df["lat"].tolist(),
lon=df["long"].tolist(),
mode='markers',
text=df['type'].tolist(),
marker=go.scattermapbox.Marker(
size=10,
color=df['type'],
showscale=True
),
))
fig.show()
If you want to specify a discrete color, you can either deal with it directly as a list of color specifications, or you can specify the default color name in plotly_express.
import plotly.graph_objects as go
import plotly.express as px
mapbox_access_token = open("mapbox_api_key.txt").read()
colors = px.colors.qualitative.D3
fig = go.Figure(go.Scattermapbox(
lat=df["lat"].tolist(),
lon=df["long"].tolist(),
mode='markers',
text=df['type'].tolist(),
marker=go.scattermapbox.Marker(
size=10,
color=colors,
showscale=False
),
))
fig.update_layout(
autosize=False,
height=450,
width=1000,
mapbox=dict(
accesstoken=mapbox_access_token,
style="outdoors",
center=dict(
lat=-7.78,
lon=110.365
),
zoom=10),
showlegend = False
)
fig.show()

Categories