首页 > 解决方案 > Seaborn 箱线图

问题描述

我有一个多索引 Pandas 数据框,我想将其绘制为箱线图。这应该很容易做到,但我发现自己无法得到我想要的东西。数据如下所示:

                       hedges  mask model_name  hedges_std  hedges_min  \
    period    season                                                      
    2021-2025 winter  0.864328   1.0   ensemble    0.301748    0.124708   
          spring  0.740410   1.0   ensemble    0.202963    0.049319   
          summer  0.526264   1.0   ensemble    0.105750    0.162856   
          fall    0.531141   1.0   ensemble    0.046278    0.388827   
2025-2050 winter  1.715075   1.0   ensemble    0.373866    0.582819   
          spring  1.252963   1.0   ensemble    0.370402    0.408695   
          summer  0.854958   1.0   ensemble    0.076193    0.528038   
          fall    0.759645   1.0   ensemble    0.068928    0.498271   
2050-2075 winter  2.981373   1.0   ensemble    0.928940    1.139801   
          spring  2.042320   1.0   ensemble    0.748642    0.716289   
          summer  1.299277   1.0   ensemble    0.092611    0.812979   
          fall    1.108852   1.0   ensemble    0.109014    0.653199   
2021-2025 winter  0.864328   1.0   ensemble    0.301748    0.124708   
          spring  0.740410   1.0   ensemble    0.202963    0.049319   
          summer  0.526264   1.0   ensemble    0.105750    0.162856   
          fall    0.531141   1.0   ensemble    0.046278    0.388827   
2025-2050 winter  1.715075   1.0   ensemble    0.373866    0.582819   
          spring  1.252963   1.0   ensemble    0.370402    0.408695   
          summer  0.854958   1.0   ensemble    0.076193    0.528038   
          fall    0.759645   1.0   ensemble    0.068928    0.498271   
2050-2075 winter  2.981373   1.0   ensemble    0.928940    1.139801   
          spring  2.042320   1.0   ensemble    0.748642    0.716289   
          summer  1.299277   1.0   ensemble    0.092611    0.812979   
          fall    1.108852   1.0   ensemble    0.109014    0.653199   

                  hedges_max model_scenario  
period    season                             
2021-2025 winter    1.760912         ssp245  
          spring    1.189956         ssp245  
          summer    0.662142         ssp245  
          fall      0.687793         ssp245  
2025-2050 winter    2.423660         ssp245  
          spring    2.040903         ssp245  
          summer    1.055890         ssp245  
          fall      0.965831         ssp245  
2050-2075 winter    5.179203         ssp245  
          spring    3.898118         ssp245  
          summer    1.536149         ssp245  
          fall      1.435503         ssp245  
2021-2025 winter    1.760912         ssp585  
          spring    1.189956         ssp585  
          summer    0.662142         ssp585  
          fall      0.687793         ssp585  
2025-2050 winter    2.423660         ssp585  
          spring    2.040903         ssp585  
          summer    1.055890         ssp585  
          fall      0.965831         ssp585  
2050-2075 winter    5.179203         ssp585  
          spring    3.898118         ssp585  
          summer    1.536149         ssp585  
          fall      1.435503         ssp585  

我想绘制数据,显示每个时期和季节的一个框,按场景以颜色分隔。每个框将由其平均值(对冲)、标准差(std)以及可能的最小和最大范围定义。这个想法是显示未来期间将如何改变估计的对冲分布。我尝试了各种组合:

sns.boxplot(data=df, x="season", y="hedges", hue="model_scenario")

我的错误Could not interpret input 'season'与我显然必须以某种方式分组或拆分的多索引有关,但这就是我一直失败的地方。对如何绘制这些数据的建议表示赞赏。

标签: pythonpandasdataframeboxplot

解决方案


我假设您的目标是生成这样的图形:

bxp() 生成的箱线图

由于您已经计算了盒子的箱线图统计信息,因此该函数sns.boxplot()以及matplotlib.axes.Axes.boxplot()来自 matplotlib(这是 seaborn 后端并在内部调用sns.boxplot())不再是您可以使用的函数。试图自己计算统计数据,因此这ax.boxplot()不是要走的路。

在计算 boxplot-statisticsmatplotlib.axes.Axes.boxplot()调用 [ matplotlib.axes.Axes.bxp()]( https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bxp.html ) 之后,这也是您可以使用的函数。

该函数matplotlib.axes.Axes.boxplot()采用具有此名称约定的字典:

  • med:中位数(标量浮点数),
  • q1:第一个四分位数(第 25 个百分位数)(标量浮点数),
  • q3:第三个四分位数(第 75 个百分位数)(标量浮点数),
  • whislo:下须线的下限(标量浮点数),
  • whishi:上胡须的上限(标量浮点数),

只需稍作修改,我们就可以重命名或分类您 DataFrame 所需的列。但首先重置你的多索引。

# df is defined and the multiinde
df = df.rename({'hedges':'med', 'hedges_min':'whislo', 'hedges_max':'whishi'}, axis=1)
df['q1'] = df['med'] - df['hedges_std']
df['q3'] = df['med'] + df['hedges_std']
df['label'] = df.apply(lambda x: '('+ x['period'] +' , '+ x['season'] + ')', axis=1)
df = df[['med', 'whislo','whishi','q1','q3', 'label']] # this are the columns we need

>>> df.head(5)
        med    whislo    whishi        q1        q3                 label
0  0.864328  0.124708  1.760912  0.562580  1.166076  (2021-2025 , winter)
1  0.740410  0.049319  1.189956  0.537447  0.943373  (2021-2025 , spring)
2  0.526264  0.162856  0.662142  0.420514  0.632014  (2021-2025 , summer)
3  0.531141  0.388827  0.687793  0.484863  0.577419    (2021-2025 , fall)
4  1.715075  0.582819  2.423660  1.341209  2.088941  (2025-2050 , winter)

我决定创建一个结合period和的标签season。每个标签出现两次,每个标签model_scenario恰好出现一次。

这是我如何创建上图的代码。它并不完美,但它显示了它是如何工作的。某些部分与sns.boxplot().

from matplotlib import rcParams
import matplotlib.pyplot as plt

colors = ['lightblue', 'olive']
model_scenario = ["ssp245", "ssp585"]
fig, ax = plt.subplots(figsize=(9, 4))
ax.set_title('box plot')

x_tick_label = []
x_tick_position = []
for i, group in enumerate(data_to_plt.groupby('label')):
    for j in range(group[1].shape[0]):
        x_tick_label.append(group[0])
        x_tick_position.append(i)
        if j ==0:
            p = i - 0.15
        else:
            p = i + 0.15
        artist_dict  = ax.bxp(
            bxpstats=[group[1].drop('label', axis=1).iloc[j].to_dict()], 
            showfliers=False, 
            patch_artist=True,
            positions=[p]
        )
        for box in artist_dict["boxes"]:
            box.update(dict(facecolor=colors[j],
                            zorder=.9,
                            edgecolor='gray',
                            linewidth=rcParams["lines.linewidth"])
            )
        if i == 0:
            rect = plt.Rectangle([0,0], 0, 0,
                                 linewidth=0,
                                 edgecolor='gray',
                                 facecolor=colors[j],
                                 label=model_scenario[j])
            ax.add_patch(rect)
            

ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.xticks(x_tick_position, x_tick_label, rotation = 90)

总结一下我在 matplotlib 上所做的事情:

  1. 我按model_scenarioaka分组labels
  2. 我为图例生成标签
  3. 我使用bxp()
  4. 我重写了x-ticks

推荐阅读