如何为带hue的多级柱状图添加柱形标签?
解决带Hue的Seaborn柱状图标注问题
问题分析
你当前通过两次调用sns.barplot绘制总用户数和购买者数的柱状图,但因共享x='age'和hue='expenses',导致两组柱子重叠,原有标注逻辑失效——核心原因是带hue参数时,柱子的位置、数量与无hue场景不同,需重新匹配柱子与数据的对应关系,同时处理重复图例问题。
解决方案
方案1:修正绘图逻辑(推荐,避免重叠)
将宽格式数据转为长格式,用hue区分用户类型(总用户/购买者)、style区分消费档位,让柱子并列显示而非重叠,简化标注逻辑:
import pandas as pd import seaborn as sns import matplotlib.pyplot as plt df = pd.DataFrame({'age': ['20-30', '20-30', '20-30', '30-40', '30-40', '30-40', '40-50', '40-50', '40-50', '50-60', '50-60', '50-60'], 'expenses':['50$', '100$', '200$', '50$', '100$', '200$', '50$', '100$', '200$', '50$', '100$', '200$'], 'users': [59, 42, 57, 68, 47, 98, 75, 73, 54, 81, 52, 43], 'buyers': [22, 35, 18, 27, 12, 57, 19, 29, 31, 47, 10, 5], 'percentage': [37.2881, 83.3333, 31.5789, 39.7058, 25.5319, 58.1632, 25.3333, 39.7260, 57.4074, 58.0246, 19.2307, 11.6279]}) # 转换为长格式,合并users和buyers为统一列 df_melt = df.melt(id_vars=['age', 'expenses', 'percentage'], value_vars=['users', 'buyers'], var_name='user_type', value_name='count') fig, ax = plt.subplots(figsize=(20, 10)) # 绘制分组柱状图:x为年龄,hue区分用户类型,style区分消费档位 sns.barplot(x='age', y='count', hue='user_type', style='expenses', data=df_melt, palette={'users': '#639df0', 'buyers': '#1a56db'}, hatch={'buyers': '//'}, edgecolor='grey', ax=ax) # 添加标注 patches = ax.patches for idx, patch in enumerate(patches): # 匹配对应数据索引 data_idx = idx // 2 * 3 + idx % 3 count_val = df_melt['count'].iloc[data_idx] # 标注柱子数值 ax.text(patch.get_x() + patch.get_width()/2, patch.get_height(), f'{count_val}', ha='center', va='bottom', fontsize=12) # 购买者柱子额外标注转化率百分比 if df_melt['user_type'].iloc[data_idx] == 'buyers': pct_val = df_melt['percentage'].iloc[data_idx] ax.text(patch.get_x() + patch.get_width()/2, patch.get_height() + 3, f'({pct_val:.1f}%)', ha='center', va='bottom', fontsize=10, color='#8b0000') # 调整图例与图表样式 ax.legend(loc='upper right', bbox_to_anchor=(1.15, 1), fontsize=12) plt.title('不同年龄组的用户数与购买者数分布', fontsize=16) plt.xlabel('年龄区间', fontsize=14) plt.ylabel('人数', fontsize=14) plt.tick_params(labelsize=12) plt.show()
方案2:保留原重叠绘图逻辑
若需保持原有的重叠绘制(总用户柱为底层,购买者柱为上层),可通过调整柱子宽度区分两组,并分别标注:
fig, ax = plt.subplots(figsize=(20, 10)) # 绘制总用户柱,设置较大宽度 users_plot = sns.barplot(x='age', y='users', data=df, hue='expenses', palette='Blues', edgecolor='grey', alpha=0.7, ax=ax, width=0.8) # 绘制购买者柱,设置较小宽度避免完全重叠 buyers_plot = sns.barplot(x='age', y='buyers', data=df, hue='expenses', palette='Blues', edgecolor='darkgrey', hatch='//', ax=ax, width=0.4) # 标注总用户数值 for i, patch in enumerate(users_plot.patches): height = patch.get_height() ax.text(patch.get_x() + patch.get_width()/2, height, f'{df["users"].iloc[i]}', ha='center', va='bottom', fontsize=12) # 标注购买者数值与百分比 for i, patch in enumerate(buyers_plot.patches): height = patch.get_height() ax.text(patch.get_x() + patch.get_width()/2, height, f'{df["buyers"].iloc[i]}\n({df["percentage"].iloc[i]:.1f}%)', ha='center', va='bottom', fontsize=10) # 合并重复图例 handles, labels = ax.get_legend_handles_labels() unique_handles = handles[:3] + handles[6:9] unique_labels = labels[:3] + ['购买者_' + lab for lab in labels[6:9]] ax.legend(unique_handles, unique_labels, loc='upper right', fontsize=12) plt.show()
关键要点
- 带
hue的柱状图中,ax.patches的顺序为:按x轴类别分组,每组内按hue的排序依次排列,需严格匹配数据索引顺序。 - 多组柱子场景下,优先用长格式数据统一绘制,可大幅降低标注复杂度。
- 标注时可灵活调整文本位置(顶部/内部)、字体大小与颜色,提升图表可读性。
内容的提问来源于stack exchange,提问作者Kate
相关产品推荐
相关产品推荐

