如何解决Seaborn带Hue的CountPlot中首个X轴刻度的错误标注问题
解决Seaborn CountPlot(带Hue)首个X轴刻度错误标注0的问题
问题场景
使用Seaborn进行双变量分析时,针对分类变量使用带hue参数的countplot绘制图表,会在首个X轴刻度处错误显示0的百分比标注,该问题仅出现在带hue的图表中;数值变量使用barplot展示,分类变量使用带hue的countplot展示,原代码如下:
categorical_var = ['Complains', 'Charge Amount', 'Age Group', 'Tariff Plan', 'Status'] numerical_var = ['Call Failures', 'Subscription Length', 'Seconds of Use', 'Frequency of use', 'Frequency of SMS', 'Distinct Called Numbers','Customer Value'] def bi_var_analysis(category,target_variables): fig, axes = plt.subplots(2, 3, figsize=(18, 10)) axes = axes.flatten() for i, col in enumerate(target_variables): if col in categorical_var: countplot = sns.countplot(x=df[category], ax=axes[i], hue=df[col]) axes[i].set_title(f'{col} based on {category}') total = len(df[category]) for p in countplot.patches: percentage = '{:.1f}%'.format(100 * p.get_height() / total) x = p.get_x() + p.get_width() / 2 y = p.get_height() axes[i].annotate(percentage, (x, y), ha='center', va='bottom') else: barplot = sns.barplot(x=df[category], y=df[col], ax=axes[i]) axes[i].set_title(f'{category} vs Average {col}') axes[i].set_xlabel(category) axes[i].set_ylabel(col) for p in barplot.patches: height = p.get_height() axes[i].annotate(f'{height:.2f}', (p.get_x() + p.get_width() / 2, height), ha='center', va='bottom') plt.tight_layout() plt.show()
问题原因
- 当使用
hue参数时,countplot会为每个X类别生成对应hue分组的条形,若某个hue分组在某个X类别下没有数据,对应的条形高度为0,原代码未过滤这种情况,导致生成0%的标注。 - 原代码中计算百分比使用的是整个数据集的总数,而非当前X类别下的样本总数,这会导致百分比计算不符合实际分组占比的预期。
修复方案
- 遍历
countplot.patches时,跳过高度为0的条形,避免生成0%的标注。 - 按X类别分组计算样本总数,确保百分比是当前X类别下各
hue分组的占比(更符合双变量分析的逻辑)。
修复后的完整代码:
import seaborn as sns import matplotlib.pyplot as plt categorical_var = ['Complains', 'Charge Amount', 'Age Group', 'Tariff Plan', 'Status'] numerical_var = ['Call Failures', 'Subscription Length', 'Seconds of Use', 'Frequency of use', 'Frequency of SMS', 'Distinct Called Numbers','Customer Value'] def bi_var_analysis(category, target_variables): fig, axes = plt.subplots(2, 3, figsize=(18, 10)) axes = axes.flatten() # 预计算每个X类别的样本总数 category_counts = df[category].value_counts() for i, col in enumerate(target_variables): if col in categorical_var: countplot = sns.countplot(x=df[category], ax=axes[i], hue=df[col]) axes[i].set_title(f'{col} based on {category}') for p in countplot.patches: height = p.get_height() # 跳过高度为0的条形,不生成标注 if height == 0: continue # 获取当前条形对应的X类别标签 x_idx = int(p.get_x() // p.get_width()) x_label = axes[i].get_xticklabels()[x_idx].get_text() # 使用当前X类别的总数计算百分比 category_total = category_counts[x_label] percentage = '{:.1f}%'.format(100 * height / category_total) x = p.get_x() + p.get_width() / 2 y = height axes[i].annotate(percentage, (x, y), ha='center', va='bottom') else: barplot = sns.barplot(x=df[category], y=df[col], ax=axes[i]) axes[i].set_title(f'{category} vs Average {col}') axes[i].set_xlabel(category) axes[i].set_ylabel(col) for p in barplot.patches: height = p.get_height() axes[i].annotate(f'{height:.2f}', (p.get_x() + p.get_width() / 2, height), ha='center', va='bottom') plt.tight_layout() plt.show()
关键修改点
- 添加
if height == 0: continue过滤无数据的条形,消除0标注。 - 用
category_counts = df[category].value_counts()预计算每个X类别的样本数,替换原有的全局总数,使百分比计算更合理。 - 通过
x_idx = int(p.get_x() // p.get_width())获取当前条形对应的X类别索引,进而得到该类别的总数。
内容的提问来源于stack exchange,提问作者Vishal Shaji
相关产品推荐
相关产品推荐

