如何用Pandas绘制带双重分组轴的水平堆叠条形图
问题:如何实现带主次分组标签的水平堆叠条形图
我正在绘制一幅图表,需要在一个轴上展示两个分组(major_group和minor_group),第三个分组(legend_group)作为填充值。但执行以下代码绘图后,y轴显示为元组形式:
import numpy as np import pandas as pd data_dict = {'major_group': list(np.array([['A']*10, ['B']*10]).flat), 'minor_group': ['q ','r ','s ','t ']*5, 'legend_group':np.repeat(['d','e','f','g','h','i'],[7,3,1,5,1,3])} (pd.DataFrame(data= data_dict) .groupby(['major_group', 'minor_group','legend_group'], observed = True) .size() .unstack() .plot(kind='barh', stacked=True))
当前y轴会显示('A', 'q ')这类元组形式的标签,我期望实现的是将major_group作为大组标签、minor_group作为子标签的分组展示效果。
解决方案
要实现这种主次分组的y轴展示,需要手动自定义轴标签和布局,pandas默认不会自动处理多级索引的分组展示。以下是具体实现方式:
方法1:加粗区分大组标签
通过修改y轴标签文本,将大组标签加粗并与子标签合并,快速实现视觉区分:
import numpy as np import pandas as pd import matplotlib.pyplot as plt # 数据准备 data_dict = {'major_group': list(np.array([['A']*10, ['B']*10]).flat), 'minor_group': ['q ','r ','s ','t ']*5, 'legend_group':np.repeat(['d','e','f','g','h','i'],[7,3,1,5,1,3])} df = pd.DataFrame(data=data_dict) # 分组统计并填充缺失值 grouped = df.groupby(['major_group', 'minor_group','legend_group'], observed=True).size().unstack(fill_value=0) # 绘图 fig, ax = plt.subplots(figsize=(10,6)) grouped.plot(kind='barh', stacked=True, ax=ax) # 自定义y轴标签 y_labels = grouped.index new_ytick_labels = [] current_major = None for major, minor in y_labels: if major != current_major: # 首次出现的大组,加粗显示大组名 new_ytick_labels.append(f'**{major}**\n{minor}') current_major = major else: new_ytick_labels.append(minor) ax.set_yticklabels(new_ytick_labels) plt.tight_layout() plt.show()
方法2:添加分组分隔线与独立大组标题
如果想要更规整的分组效果(类似表格的合并单元格标题),可以添加分隔线和独立的大组标题:
import numpy as np import pandas as pd import matplotlib.pyplot as plt # 数据准备与分组统计 data_dict = {'major_group': list(np.array([['A']*10, ['B']*10]).flat), 'minor_group': ['q ','r ','s ','t ']*5, 'legend_group':np.repeat(['d','e','f','g','h','i'],[7,3,1,5,1,3])} df = pd.DataFrame(data=data_dict) grouped = df.groupby(['major_group', 'minor_group','legend_group'], observed=True).size().unstack(fill_value=0) # 绘图 fig, ax = plt.subplots(figsize=(10,6)) grouped.plot(kind='barh', stacked=True, ax=ax) # 整理大组对应的y轴位置范围 major_ranges = {} for idx, (major, _) in enumerate(grouped.index): if major not in major_ranges: major_ranges[major] = [idx, idx] else: major_ranges[major][1] = idx # 绘制分组分隔线和大组标题 for major, (start_idx, end_idx) in major_ranges.items(): # 绘制分组顶部的分隔线 ax.axhline(y=end_idx + 0.5, color='#333333', linewidth=1.2) # 计算大组标题的垂直中心位置 center_y = (start_idx + end_idx) / 2 # 在y轴外侧添加大组标题 ax.text(-0.08, center_y, major, ha='right', va='center', fontweight='bold', fontsize=12, transform=ax.get_yaxis_transform()) # 仅保留子标签作为y轴刻度文本 ax.set_yticklabels([minor for _, minor in grouped.index]) plt.tight_layout() plt.show()
关于主次轴标签的说明
不需要设置真正的主次轴,通过上述自定义y轴标签的方式,就能实现视觉上的主次分组效果——将major_group作为大组标题,minor_group作为子标签,完美匹配你期望的展示样式。
内容的提问来源于stack exchange,提问作者saQuist
相关产品推荐
相关产品推荐

