Reputation: 1307
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()
Upvotes: 2
Views: 751
Reputation: 35155
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()
Upvotes: 2