Dushi Fdz
Dushi Fdz

Reputation: 151

Plotly: How to set up grouped subplots?

I am trying to plot grouped boxplots as a grid with data from a data-frame using Plotly. For example if we have a dataframe,

import plotly.express as px
df = px.data.tips() 

total_bill  tip sex smoker  day time    size
0   16.99   1.01    Female  No  Sun Dinner  2
1   10.34   1.66    Male    No  Sun Dinner  3
2   21.01   3.50    Male    No  Sun Dinner  3
3   23.68   3.31    Male    No  Sun Dinner  2
4   24.59   3.61    Female  No  Sun Dinner  4

I can draw a single plot as follows (e.g: for day = Friday):

import plotly.graph_objects as go
import plotly.express as px

df_plot=df[df['day']== 'Fri']
fig = px.box(df_plot, x='time', y="total_bill", color="sex",width=600, height=400)
fig.show()

enter image description here

If I want to draw multiple subplots, for example, one for each day, I am not sure how to do that in plotly. This is what I tried:

fig = make_subplots(rows=2, cols=2)

for v in range(4):
  for i, met in enumerate(df['day'].unique()): #'Sun', 'Sat', 'Thur', 'Fri'
    df_plot=df[df['day']== met] 
    for t in px.box(df_plot, x="time", y=f"total_bill", color='sex').data:
        fig.add_trace(t, row=(v//2)+1, col=(v%2)+1)

fig.update_layout(
    boxmode="group", margin={"l": 0, "r": 0, "t": 20, "b": 0}
).update_traces(showlegend=True) 

This gives a weird looking plot. What am I doing wrong? Also, in a grid of subplots with plotly, how can I draw one legend instead of legends for each plots.

Upvotes: 2

Views: 1826

Answers (2)

vestland
vestland

Reputation: 61104

You've already received an answer that seems to produce the desired result. But since you seem to be eager to know how to do this using your specified setup, I'd like to show you how you can do that too. And I've also made sure that the complete snippet below takes care of the legends as well.

Plot

enter image description here

The problem with your original setup seemed to stem from fig.add_trace(t, row=(v//2)+1, col=(v%2)+1). The reason why your plot turned out weird is that the traces produced by for t in px.box(df_plot, x="time", y=f"total_bill", color='sex').data: were not correctly indexed towards the subplot. (v//2)+1 seemed fine, but col=(v%2)+1) a bit problematic. What you need here is an index that alters between 1 and 2. In the snippet below you can see that I've solved this using:

from itertools import cycle
cols = cycle([1,2])
c = next(cols)

This might not be the most efficient approach, but I use it all the time for other challenges, and it does the trick in this case too. If you print out c you'll see that you get: [1, 1, 2, 2, 1, 1, 2, 2]. And this is exactly what you need to make sure that the two first traces for the first subplot ends up in subplot 1 at row = 1 and col = 1, and so on.

Regarding your secondary question about the legends, this is handled through:

fig.add_trace(t.update(showlegend=True if i == 0 else False), row=r, col=c)

Complete snippet

import plotly.express as px
from plotly.subplots import make_subplots
from itertools import cycle

df = px.data.tips()

fig = make_subplots(rows=2, cols=2, subplot_titles = df['day'].unique())

cols = cycle([1,2])

for i, met in enumerate(df['day'].unique()): #'Sun', 'Sat', 'Thur', 'Fri'
    df_plot=df[df['day']== met].sort_values(['time', 'sex'], ascending = False)
    c = next(cols)
    for t in px.box(df_plot, x="time", y="total_bill", color='sex').data:
        r = (i//2)+1
#         print(c)
        fig.add_trace(t.update(showlegend=True if i == 0 else False), row=r, col=c)
            
fig.update_layout(
    boxmode="group", margin={"l": 0, "r": 0, "t": 20, "b": 0}
)#.update_traces(showlegend=True) 

fig.show()

Edit:

If you'd like to add anything to this setup, like y-axis titles, just add:

titles = cycle(df['day'].unique())
fig.for_each_yaxis(lambda y: y.update(title = next(titles)))

enter image description here

Or if I misunderstood you and you'd just like to add total_bill to the yaxes, just use:

fig.update_yaxes(title = 'total_bill')

enter image description here

Upvotes: 3

Rob Raymond
Rob Raymond

Reputation: 31146

  • simplest way with plotly express is to use facet parameters px box
  • copying your example, 2 cols and 2 rows
import plotly.express as px
df = px.data.tips() 
df.loc[df["day"].eq("Sun")]

px.box(df.loc[df["day"].eq("Sun")], x='time', y="total_bill", color="sex",width=600, height=400).show()
px.box(df, x='time', y="total_bill", color="sex", facet_col="day", facet_col_wrap=2, width=600, height=400).show()

Upvotes: 1

Related Questions