为何每个Seaborn柱状图首柱显示0.00%?
Countplot分组后部分柱子标注0.00%的原因及解决办法
问题描述
运行基于Pandas、Seaborn、Matplotlib的Python代码绘制以fraud为分组的countplot时,图表整体显示正常,但每个柱状图的第一个柱子上均标注了0.00%。
重现代码
dt = pd.DataFrame({'witness':['No', 'No', 'No', 'No', 'No', 'No', 'No', 'Yes', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No'], 'category':['Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Utility', 'Sedan', 'Sport', 'Sport', 'Sport', 'Sport', 'Sedan', 'Sedan', 'Sport', 'Sport', 'Sport', 'Sedan', 'Sport', 'Sport', 'Sedan', 'Sport', 'Sedan'], 'make':['Honda', 'Honda', 'Honda', 'Toyota', 'Honda', 'Honda', 'Honda', 'Honda', 'Ford', 'Mazda', 'Honda', 'Ford', 'Ford', 'Ford', 'Ford', 'Chevrolet', 'Pontiac', 'Honda', 'Mazda', 'Chevrolet', 'Mazda', 'Pontiac', 'Mazda', 'Pontiac', 'Honda'], 'sex':['Female', 'Male', 'Male', 'Male', 'Female', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Female', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male'], 'fraud':[0, 0, 0, 0,1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,1]}) fea = ['witness', 'category', 'make', 'sex'] fig, axes =plt.subplots(10,2,figsize=(10,40)) plt.subplots_adjust(wspace=0.9, hspace=0.9) i=0 total = len(dt['witness']) for fea_name in fea: ax= axes[i//2, i%2] i += 1 sns.countplot(x=fea_name, data=dt, ax=ax,hue='fraud' ) ax.set_title(f'Countplot of {fea_name}') ax.tick_params(axis='x', rotation=45) for i in range(len(fea), 10 * 2): fig.delaxes(axes[i//2, i%2]) for ax in axes.flat: for p in ax.patches: percentage = '{:.1f}%'.format(100 * p.get_height() / total) x = p.get_x() + p.get_width()/3 y = p.get_y() + p.get_height() ax.annotate(percentage, (x, y)) plt.tight_layout() plt.show()
问题原因
- 代码创建了10×2共20个子图,但实际仅使用4个,剩余16个通过
fig.delaxes()删除。 - 标注遍历逻辑用了
axes.flat,会包含所有20个子图的轴对象,包括已被删除的轴。 - 已删除的轴没有有效柱子(
patches高度为0),但代码仍尝试计算百分比并标注,这些无效标注会被绘制到当前活跃的可见轴上,导致每个子图的第一个柱子出现错误的0.00%。
解决方案
方案1:收集并遍历使用过的轴
绘制countplot时,将实际使用的轴存入列表,后续仅遍历该列表:
dt = pd.DataFrame({'witness':['No', 'No', 'No', 'No', 'No', 'No', 'No', 'Yes', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No', 'No'], 'category':['Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Sport', 'Utility', 'Sedan', 'Sport', 'Sport', 'Sport', 'Sport', 'Sedan', 'Sedan', 'Sport', 'Sport', 'Sport', 'Sedan', 'Sport', 'Sport', 'Sedan', 'Sport', 'Sedan'], 'make':['Honda', 'Honda', 'Honda', 'Toyota', 'Honda', 'Honda', 'Honda', 'Honda', 'Ford', 'Mazda', 'Honda', 'Ford', 'Ford', 'Ford', 'Ford', 'Chevrolet', 'Pontiac', 'Honda', 'Mazda', 'Chevrolet', 'Mazda', 'Pontiac', 'Mazda', 'Pontiac', 'Honda'], 'sex':['Female', 'Male', 'Male', 'Male', 'Female', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Female', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male', 'Male'], 'fraud':[0, 0, 0, 0,1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,1]}) fea = ['witness', 'category', 'make', 'sex'] fig, axes =plt.subplots(10,2,figsize=(10,40)) plt.subplots_adjust(wspace=0.9, hspace=0.9) i=0 total = len(dt['witness']) used_axes = [] # 存储使用过的轴对象 for fea_name in fea: ax= axes[i//2, i%2] used_axes.append(ax) i += 1 sns.countplot(x=fea_name, data=dt, ax=ax,hue='fraud' ) ax.set_title(f'Countplot of {fea_name}') ax.tick_params(axis='x', rotation=45) for i in range(len(fea), 10 * 2): fig.delaxes(axes[i//2, i%2]) # 仅遍历实际使用过的轴 for ax in used_axes: for p in ax.patches: percentage = '{:.1f}%'.format(100 * p.get_height() / total) x = p.get_x() + p.get_width()/3 y = p.get_y() + p.get_height() ax.annotate(percentage, (x, y)) plt.tight_layout() plt.show()
方案2:跳过不可见的轴
遍历所有轴时,通过ax.get_visible()判断轴是否有效,跳过已删除的轴:
# 替换原代码中的遍历标注部分 for ax in axes.flat: if not ax.get_visible(): # 跳过已删除的轴 continue for p in ax.patches: percentage = '{:.1f}%'.format(100 * p.get_height() / total) x = p.get_x() + p.get_width()/3 y = p.get_y() + p.get_height() ax.annotate(percentage, (x, y))
效果说明
修改后,错误的0.00%标注会消失,每个柱子的百分比标注将准确对应其实际计数。
内容的提问来源于stack exchange,提问作者Manish Patel
相关产品推荐
相关产品推荐

