Plotly:如何设置分组子图?

Plotly: How to set up grouped subplots?

我正在尝试使用 Plotly 将分组箱线图绘制为包含来自数据框的数据的网格。例如,如果我们有一个数据框,

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

我可以如下绘制单个图(例如: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()

如果我想绘制多个子图,例如,每天一个,我不确定如何在 plotly 中进行绘制。这是我试过的:

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) 

这给出了一个看起来很奇怪的情节。我究竟做错了什么?另外,在带有 plotly 的子图网格中,如何绘制一个图例而不是每个图的图例。

  • 使用 plotly express 的最简单方法是使用 facet 参数 px box
  • 复制您的示例,2 列和 2 行
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()

您已经收到了似乎产生了预期结果的答案。但是由于您似乎很想知道如何使用指定的设置来执行此操作,我想向您展示如何也可以这样做。而且我还确保下面的完整代码段也处理了图例。

情节

您的原始设置问题似乎源于 fig.add_trace(t, row=(v//2)+1, col=(v%2)+1)。你的情节变得奇怪的原因是 for t in px.box(df_plot, x="time", y=f"total_bill", color='sex').data: 产生的痕迹没有正确地索引到子情节。 (v//2)+1 看起来不错,但是 col=(v%2)+1) 有点问题。您在这里需要的是一个在 1 and 2 之间变化的索引。在下面的代码片段中,您可以看到我已经使用以下方法解决了这个问题:

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

这可能不是最有效的方法,但我一直使用它来应对其他挑战,在这种情况下它也很管用。如果您打印出 c,您会看到您得到:[1, 1, 2, 2, 1, 1, 2, 2]。这正是您需要确保第一个子图的前两条轨迹在子图 ​​1 中的 row = 1col = 1 处结束,依此类推。

关于您关于传说的次要问题,这是通过以下方式处理的:

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

编辑:

如果您想在此设置中添加任何内容,例如 y-axis 标题,只需添加:

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

或者如果我误解了你而你只是想将 total_bill 添加到 yaxes 中,只需使用:

fig.update_yaxes(title = 'total_bill')