Reputation: 151
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()
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
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.
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)
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()
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)))
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')
Upvotes: 3
Reputation: 31146
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