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

如何在分组柱状图中同时添加百分比和N值作为柱形标签

分组柱状图标签同时显示百分比和N值的问题排查与解决

问题背景

此前在Stack Overflow提问《Include both % and N as bar labels》并获得解答,尝试将该方法应用于分组柱状图时未得到正确结果,原代码如下:

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.ticker import PercentFormatter

data = {
'id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
'baseline': [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
'endline': [1, 0, np.nan, 1, 0, 0, 1, np.nan, 1, 0, 0, 1, 0, 0, 1, 0, np.nan, np.nan, 1, 0, 1, np.nan, 0, 1, 0, 1, 0, np.nan, 1, 0, np.nan, 0, 0, 0, np.nan, 1, np.nan, 1, np.nan, 0, np.nan, 1, 1, 0, 1, 1, 1, 0, 1, 1],
'gender': ['male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female']
}

df = pd.DataFrame(data)

sns.set_style('white')
ax = sns.barplot(data = df.melt(id_vars = ['id', 'gender'], value_vars = ['baseline', 'endline']),
                 x = 'variable', y = 'value',
                 estimator=lambda x: np.sum(x) / np.size(x) * 100, ci=None,
                 color='cornflowerblue', hue = 'gender')

N = df.melt(id_vars = ['id', 'gender'], value_vars = ['baseline', 'endline']).groupby(['gender', 'variable'], sort=False)['value'].count().to_numpy()
N_it = '$\it{N}$'
labels=[f'{np.round(perc,1)}% ({N_it} = {n})' 
        for perc, n in zip(ax.containers[0].datavalues, N)]

ax.bar_label(ax.containers[0], labels = labels, fontsize = 10)
ax.bar_label(ax.containers[1], labels = labels, fontsize = 10)

sns.despine(ax = ax, left = True)
ax.grid(True, axis = 'y')
ax.yaxis.set_major_formatter(PercentFormatter(100))
ax.set_xlabel('')
ax.set_ylabel('')
plt.tight_layout()
plt.show()

问题分析

  1. 标签复用错误:两组柱子(male和female)共用同一组labels,导致标签内容完全重复,未对应各自分组的统计值。
  2. 百分比计算逻辑错误:原estimator用np.size(x)作为分母,包含了NaN值,导致百分比偏离实际有效样本占比。
  3. 标签数据不匹配:仅用ax.containers[0].datavalues生成标签,未对应第二组容器的实际百分比数据。

修正方案

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.ticker import PercentFormatter

data = {
'id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
'baseline': [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1],
'endline': [1, 0, np.nan, 1, 0, 0, 1, np.nan, 1, 0, 0, 1, 0, 0, 1, 0, np.nan, np.nan, 1, 0, 1, np.nan, 0, 1, 0, 1, 0, np.nan, 1, 0, np.nan, 0, 0, 0, np.nan, 1, np.nan, 1, np.nan, 0, np.nan, 1, 1, 0, 1, 1, 1, 0, 1, 1],
'gender': ['male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'male', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female', 'female']
}

df = pd.DataFrame(data)

# 预处理数据为长格式
melted_df = df.melt(id_vars=['id', 'gender'], value_vars=['baseline', 'endline'])

sns.set_style('white')
ax = sns.barplot(
    data=melted_df,
    x='variable',
    y='value',
    # 修正百分比计算:基于非NaN样本统计
    estimator=lambda x: np.nansum(x) / np.count_nonzero(~np.isnan(x)) * 100,
    ci=None,
    hue='gender',
    palette='cornflowerblue'
)

# 按分组统计有效样本数(排除NaN)
grouped_counts = melted_df.groupby(['gender', 'variable'], sort=False)['value'].count()
# 拆分N值为对应两组柱子的数组
N_male = grouped_counts.loc['male'].to_numpy()
N_female = grouped_counts.loc['female'].to_numpy()

N_it = '$\it{N}$'
# 为每组柱子单独生成标签
labels_male = [f'{np.round(perc,1)}% ({N_it} = {n})' for perc, n in zip(ax.containers[0].datavalues, N_male)]
labels_female = [f'{np.round(perc,1)}% ({N_it} = {n})' for perc, n in zip(ax.containers[1].datavalues, N_female)]

# 为两组柱子分别添加对应标签
ax.bar_label(ax.containers[0], labels=labels_male, fontsize=10)
ax.bar_label(ax.containers[1], labels=labels_female, fontsize=10)

sns.despine(ax=ax, left=True)
ax.grid(True, axis='y')
ax.yaxis.set_major_formatter(PercentFormatter(100))
ax.set_xlabel('')
ax.set_ylabel('')
plt.tight_layout()
plt.show()

关键修改点

  • 修正百分比计算:用np.nansum(x)求和,np.count_nonzero(~np.isnan(x))统计有效样本数,确保百分比基于实际非缺失值计算。
  • 拆分N值数组:将分组统计的N值拆分为对应male和female的两个数组,保证标签与分组匹配。
  • 分别生成标签:为每个容器单独生成对应标签,避免复用导致的内容错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 11:55:19