Johannes Wiesner
Johannes Wiesner

Reputation: 1307

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

enter image description here

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

enter image description here

Upvotes: 2

Views: 751

Answers (1)

r-beginners
r-beginners

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

enter image description here

Upvotes: 2

Related Questions