You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Seaborn箱线图分组层级X轴标签实现问题求助

为Seaborn箱线图实现分组层级X轴标签

需求说明

  • 给Seaborn箱线图添加多层级分组X轴标签,效果类似带分组表头的柱状图
  • 实际数据分组大小不一致,不能用均匀分组逻辑
  • 所有箱线图保持同一颜色,不使用Seaborn的hue参数
  • 此前尝试适配热图/柱状图的层级标签方案,但因箱线图无需聚合数据,无法正确匹配分组位置

原有问题代码

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from itertools import groupby

def test_table():
    data_table = pd.DataFrame({'Room':['Room A']*24 + ['Room B']*24,
                               'Shelf':(['Shelf 1']*12 + ['Shelf 2']*12)*2,
                               'Staple':['Milk','Water','Sugar','Honey','Wheat','Corn']*8,
                               'Quantity':np.random.randint(1, 20, 48),
                               })
    return data_table

def add_line(ax, xpos, ypos):
    line = plt.Line2D([xpos, xpos], [ypos + .1, ypos],
                      transform=ax.transAxes, color='black')
    line.set_clip_on(False)
    ax.add_line(line)

def label_len(my_index,level):
    labels = my_index.get_level_values(level)
    return [(k, sum(1 for i in g)) for k,g in groupby(labels)]
    
def label_group_bar_table(ax, df):
    ypos = -.1
    scale = 1./df.index.size
    for level in range(df.index.nlevels)[::-1]:
        pos = 0
        for label, rpos in label_len(df.index,level):
            lxpos = (pos + .5 * rpos)*scale
            ax.text(lxpos, ypos, label, ha='center', transform=ax.transAxes)
            add_line(ax, pos*scale, ypos)
            pos += rpos
        add_line(ax, pos*scale , ypos)
        ypos -= .1

df = test_table().groupby(['Room','Shelf','Staple']).sum()

fig = plt.figure()
fig = plt.figure(figsize = (15, 10))
ax = fig.add_subplot(111)

sns.boxplot(x=df.Quantity, y=df.Quantity,data=df)

# 移除默认标签
labels = ['' for item in ax.get_xticklabels()]
ax.set_xticklabels(labels)
ax.set_xlabel('')

label_group_bar_table(ax, df)
fig.subplots_adjust(bottom=.1*df.index.nlevels)
plt.show()

问题分析

  1. 错误对数据执行groupby.sum(),箱线图需要原始数据计算四分位数等统计量,聚合后的数据无法生成正确分布
  2. 箱线图x轴参数设置错误,导致箱体位置与分组标签不匹配
  3. 层级标签函数基于聚合后的索引计算位置,与箱线图实际x轴位置脱节

解决方案

核心思路

  • 保留原始数据直接绘制箱线图
  • 按最细粒度分类(如Staple)绘制箱体,再根据上层分组(Room、Shelf)的范围,在x轴下方添加层级标签和分隔线
  • 自定义标签绘制逻辑,适配箱线图x轴的实际位置(每个箱体对应x轴0,1,2...n-1坐标)

修正后完整代码

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from itertools import groupby

def test_table():
    data_table = pd.DataFrame({'Room':['Room A']*24 + ['Room B']*24,
                               'Shelf':(['Shelf 1']*12 + ['Shelf 2']*12)*2,
                               'Staple':['Milk','Water','Sugar','Honey','Wheat','Corn']*8,
                               'Quantity':np.random.randint(1, 20, 48),
                               })
    return data_table

def add_group_labels(ax, df, group_cols):
    """
    为箱线图添加多层级分组X轴标签
    :param ax: 绘图轴对象
    :param df: 原始数据DataFrame
    :param group_cols: 从上层到下层的分组列名列表,如['Room', 'Shelf']
    """
    x_cats = df['Staple'].unique()
    n_cats = len(x_cats)
    y_pos = -0.15  # 最上层标签的相对y轴位置
    line_height = 0.1  # 每层标签的间距

    # 从上层到下层遍历分组
    for col in group_cols:
        sorted_df = df.sort_values([col, 'Staple'])
        group_counts = [(k, len(list(g))) for k, g in groupby(sorted_df[col])]
        
        current_pos = 0
        for group_name, count in group_counts:
            # 计算分组中心的轴坐标
            group_center = current_pos + count/2 - 0.5
            ax_coord_x = (group_center + 0.5) / n_cats
            # 添加分组标签
            ax.text(ax_coord_x, y_pos, group_name, ha='center', va='top', transform=ax.transAxes)
            # 添加左侧分隔线
            line_x = current_pos / n_cats
            ax.plot([line_x, line_x], [y_pos, y_pos + line_height], transform=ax.transAxes, color='black')
            current_pos += count
        
        # 添加最右侧分隔线
        ax.plot([1, 1], [y_pos, y_pos + line_height], transform=ax.transAxes, color='black')
        y_pos -= line_height
    
    # 调整底部边距容纳标签
    plt.subplots_adjust(bottom=0.15 + len(group_cols)*line_height)

# 获取原始数据
df = test_table()

# 创建绘图对象
fig, ax = plt.subplots(figsize=(15, 10))

# 绘制同色箱线图
sns.boxplot(x='Staple', y='Quantity', data=df, hue=None, ax=ax, color='#3377bb')

# 隐藏x轴主标题,可选择保留/隐藏细粒度标签
ax.set_xlabel('')
# ax.set_xticklabels([])

# 添加层级分组标签
add_group_labels(ax, df, ['Room', 'Shelf'])

plt.show()

关键说明

  1. 数据处理:直接使用原始DataFrame,确保箱线图能正确计算数据分布
  2. 箱线图绘制:指定x='Staple'为最细粒度分类,hue=None强制所有箱体同色
  3. 层级标签函数:
    • 根据原始数据的分组顺序计算箱体范围
    • 将箱体位置转换为轴坐标,保证标签和分隔线与分组对齐
    • 动态调整底部边距,避免标签被截断

内容的提问来源于stack exchange,提问作者flashliquid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 20:35:00