如何在Seaborn中实现已分组箱线图的双侧展示?
我完全理解你的需求——你不想把两个索引的箱线图上下分开展示,而是希望把它们并排放在同一子图里,形成双侧对比的效果,每个主类别(Info1的One/Two/Three/Four)下,左右分别对应Index1和Index2的分组箱线图(按Info2的A/B/C/D细分)。其实这个需求在Seaborn里完全可以实现,核心是先把数据整理成Seaborn更擅长处理的长格式,再配合分组参数调整就能达到效果,我来一步步给你说明:
首先修正原代码的小笔误
你原代码里把index2写成了intex2,这会导致变量未定义的错误,先把这个笔误修正,后面的代码才能正常运行~
关键步骤:把宽格式数据转成长格式
你原来的DataFrame是宽格式(有Index1和Index2两列),而Seaborn的可视化工具对长格式数据的分组支持更好。我们可以用pd.melt把宽格式转成长格式,这样就能把Index1和Index2统一成一个“索引类型”列,对应的值放在另一列里:
# 转成长格式DataFrame df_long = df.melt( id_vars=['Info1', 'Info2'], # 保留作为分组依据的列 value_vars=['Index1', 'Index2'], # 需要转换的宽格式列 var_name='Index_Type', # 新列名:标记是Index1还是Index2 value_name='Value' # 新列名:对应的值 )
转成之后,df_long就有四列:Info1、Info2、Index_Type(值为Index1/Index2)、Value(对应索引的数值),这样Seaborn就能轻松处理多层分组了。
实现方式1:分面双侧对比(简单直观)
这种方式用seaborn.catplot,把Index_Type作为分面的列参数,生成左右两个子图,分别对应Index1和Index2的分组箱线图,实现起来非常简单,而且对比效果清晰:
import numpy as np import seaborn as sns import pandas as pd import matplotlib.pyplot as plt # 修正笔误,生成数据 np.random.seed(42) # 设置随机种子,保证结果可复现 index1 = np.random.random((4,100,4)) index2 = np.random.random((4,100,4))/2. # 修正变量名 info1 = np.zeros(shape=index1.shape,dtype='object') info1[0,:,:] = 'One' info1[1,:,:] = 'Two' info1[2,:,:] = 'Three' info1[3,:,:] = 'Four' info2 = np.zeros(shape=index1.shape, dtype='object') info2[:, :, 0] = 'A' info2[:, :, 1] = 'B' info2[:, :, 2] = 'C' info2[:, :, 3] = 'D' # 构建宽格式DataFrame df = pd.DataFrame( columns=['Info1', 'Info2', 'Index1', 'Index2'], data=np.array( (info1.flatten(), info2.flatten(), index1.flatten(), index2.flatten())).T) # 转成长格式 df_long = df.melt( id_vars=['Info1', 'Info2'], value_vars=['Index1', 'Index2'], var_name='Index_Type', value_name='Value' ) # 分面展示双侧箱线图 g = sns.catplot( data=df_long, x='Info1', y='Value', hue='Info2', # 按Info2细分分组 col='Index_Type', # 按索引类型分面(左右两个子图) kind='box', dodge=True, # 让同一Info1下的Info2箱线图分开 height=4, aspect=1.2, order=['One', 'Two', 'Three', 'Four'], # 指定Info1的展示顺序 hue_order=['A', 'B', 'C', 'D'] # 指定Info2的展示顺序 ) g.set_axis_labels('Info1', '数值') g.fig.suptitle('Index1 vs Index2 - 分面对比', y=1.02) plt.show()
实现方式2:同一子图内双侧展示(更贴近你的示例图)
如果你希望所有内容都放在同一个子图里,每个Info1类别下左右分别对应Index1和Index2的分组箱线图,可以通过组合Info1和Index_Type生成新的x轴分组来实现:
# 生成新的x轴分组:Info1 + 索引类型 df_long['X_Group'] = df_long['Info1'] + ' - ' + df_long['Index_Type'] # 自定义x轴顺序,确保每个Info1下先展示Index1再展示Index2 x_order = [ 'One - Index1', 'One - Index2', 'Two - Index1', 'Two - Index2', 'Three - Index1', 'Three - Index2', 'Four - Index1', 'Four - Index2' ] # 绘制同一子图内的双侧箱线图 fig, ax = plt.subplots(figsize=(14, 6)) sns.boxplot( data=df_long, x='X_Group', y='Value', hue='Info2', # 按Info2细分分组 dodge=True, order=x_order, hue_order=['A', 'B', 'C', 'D'], ax=ax ) # 旋转x轴标签,避免重叠 ax.tick_params(axis='x', rotation=45) # 调整图例位置,避免遮挡图表 ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left') ax.set_title('Index1 vs Index2 - 同一子图双侧对比') ax.set_xlabel('Info1 - 索引类型') plt.tight_layout() plt.show()
这样生成的图就和你提供的示例图风格非常接近了,每个Info1类别下左右两个分组分别对应Index1和Index2,每个分组里又按Info2的四个类别细分展示。
最后补充
两种方式各有优劣:分面展示更清晰,同一子图展示更紧凑。你可以根据自己的需求选择~
备注:内容来源于stack exchange,提问作者ymmx

