基于两个分类变量的百分比条形图绘制需求(含N与%标签)
问题需求
我有一个包含两个分类变量的数据集,想要绘制y轴为百分比的条形图,条形标签需同时显示百分比和观测数N(格式如65.5% (N=12))。要求百分比按x轴的survey分组,每个survey下的level占比总和为100%(即baseline和endline各自的level百分比总和均为100%)。尝试用Seaborn的countplot但y轴是计数而非百分比,标签也不符合需求,现有代码如下:
import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import numpy as np data = { 'id': [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20], 'survey': ['baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline'], 'level': ['low', 'high', 'medium', 'low', 'high', 'medium', 'medium', 'high', 'low', 'low', 'medium', 'high', 'low', 'medium', 'low', 'high', 'low', 'low', 'medium', 'high', 'high', 'high', 'high', 'medium', 'low', 'low', 'medium', 'high', 'low', 'medium', 'high', 'medium', 'low', 'high', 'high', 'medium', 'medium', 'low', 'high', 'low'] } df = pd.DataFrame(data) df.survey.value_counts() plt.figure(figsize=(8,5)) sns.countplot(x = 'survey', data = df, palette = 'rainbow', hue = 'level') N = df.groupby(['survey','level']).count().sort_index(ascending=False) N_it = '$\it{N}$' labels = [ [f'{pct:.1f}% $(N={_n})$' for pct, _n in zip(c.datavalues, n)] for c, n in zip(ax.containers, N.to_numpy()) ] for container, label in zip(ax.containers, labels): ax.bar_label(container, label, 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()
修正后的代码
import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import numpy as np from matplotlib.ticker import PercentFormatter data = { 'id': [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20], 'survey': ['baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline', 'baseline', 'endline'], 'level': ['low', 'high', 'medium', 'low', 'high', 'medium', 'medium', 'high', 'low', 'low', 'medium', 'high', 'low', 'medium', 'low', 'high', 'low', 'low', 'medium', 'high', 'high', 'high', 'high', 'medium', 'low', 'low', 'medium', 'high', 'low', 'medium', 'high', 'medium', 'low', 'high', 'high', 'medium', 'medium', 'low', 'high', 'low'] } df = pd.DataFrame(data) # 计算每个survey分组下各level的计数和百分比 grouped = df.groupby(['survey', 'level'])['id'].count().reset_index(name='count') total_per_survey = grouped.groupby('survey')['count'].transform('sum') grouped['percentage'] = (grouped['count'] / total_per_survey) * 100 plt.figure(figsize=(8,5)) # 使用barplot绘制百分比,替代countplot的计数展示 ax = sns.barplot(x='survey', y='percentage', hue='level', data=grouped, palette='rainbow') # 生成符合要求的标签:百分比 + (N=观测数) labels = [f'{p:.1f}% (N={c})' for p, c in zip(grouped['percentage'], grouped['count'])] # 给每个分组的条形添加标签 for container, label_slice in zip(ax.containers, [labels[:3], labels[3:]]): ax.bar_label(container, label_slice, fontsize=10) # 调整图表样式 sns.despine(ax=ax, left=True) ax.grid(True, axis='y') ax.yaxis.set_major_formatter(PercentFormatter()) # 自动将y轴转为百分比格式 ax.set_xlabel('') ax.set_ylabel('') plt.tight_layout() plt.show()
核心调整说明
- 替换
countplot为barplot:countplot默认展示原始计数,改用barplot直接绘制预计算的百分比,满足分组占比100%的需求 - 预计算分组占比:先统计每个
survey-level组合的观测数,再除以对应survey的总观测数,得到该level在分组内的百分比 - 修复标签生成:直接用计算好的百分比和观测数生成标签,避免原代码中未定义
ax、提取错误数值的问题 - 优化轴格式化:使用
PercentFormatter()无需额外参数,自动将y轴数值转为百分比显示
内容的提问来源于stack exchange,提问作者Stephen Okiya
相关产品推荐
相关产品推荐

