Seaborn:在箱线图上标记异常值——合并数据集实现示例请求
在Seaborn中为合并数据集的箱线图标记自定义异常值
当然有啦!其实Seaborn的boxplot默认就会自动绘制异常值,但如果咱们想手动控制异常值的样式、给它们加上自定义标签(比如关联的用户ID),可以结合Matplotlib来实现。我给你写一个适配你现有代码的完整示例,一步步来:
步骤1:计算异常值的判定边界
箱线图的异常值是基于**四分位距(IQR)**定义的:小于Q1 - 1.5*IQR或大于Q3 + 1.5*IQR的点就是异常值。咱们先给每个Company分组计算这个边界:
import seaborn as sns import matplotlib.pyplot as plt import pandas as pd # 你的合并数据集代码(假设数据格式是正确的) merged = group.merge(t, left_on=t['user_lower'], right_on=group['user'], how="left") # 计算每个Company的四分位数和IQR iqr_stats = merged.groupby('Company')['Total_Activities'].agg( Q1=lambda x: x.quantile(0.25), Q3=lambda x: x.quantile(0.75) ) iqr_stats['IQR'] = iqr_stats['Q3'] - iqr_stats['Q1'] iqr_stats['lower_bound'] = iqr_stats['Q1'] - 1.5 * iqr_stats['IQR'] iqr_stats['upper_bound'] = iqr_stats['Q3'] + 1.5 * iqr_stats['IQR'] # 把边界合并回原数据集,方便筛选异常值 merged_with_bounds = merged.merge(iqr_stats, on='Company') # 筛选出所有异常值 outliers = merged_with_bounds[ (merged_with_bounds['Total_Activities'] < merged_with_bounds['lower_bound']) | (merged_with_bounds['Total_Activities'] > merged_with_bounds['upper_bound']) ]
步骤2:绘制箱线图并标记异常值
接下来用你的原有代码绘制箱线图,然后叠加自定义的异常值标记(比如红色点+用户标签):
# 绘制基础箱线图 g = sns.boxplot(x="Company", y="Total_Activities", data=merged, orient="v") g.set_xticklabels(g.get_xticklabels(), rotation=90) # 用红色圆点突出异常值 plt.scatter( x=outliers['Company'].astype('category').cat.codes, # 转换为分类编码匹配x轴位置 y=outliers['Total_Activities'], color='crimson', marker='o', s=60, zorder=5, # 确保点在箱线图上方 label='Outliers' ) # 可选:给每个异常值添加标签(比如显示用户ID和活动数) for _, row in outliers.iterrows(): plt.annotate( text=f"User: {row['user']}\nActivities: {row['Total_Activities']}", xy=(row['Company'].astype('category').cat.codes, row['Total_Activities']), xytext=(8, 8), # 标签相对于点的偏移量 textcoords='offset points', fontsize=7, color='darkred', bbox=dict(facecolor='white', alpha=0.8, pad=2) # 给标签加背景框更清晰 ) plt.legend() plt.tight_layout() # 自动调整布局避免标签被截断 plt.show()
关键说明
- Seaborn默认的异常点就是用相同的IQR逻辑绘制的,但手动实现的好处是可以自定义样式(比如颜色、大小),还能添加额外上下文信息(比如用户ID),让图表更有分析价值。
- 用
cat.codes转换Company列是因为Seaborn的箱线图x轴是分类变量,对应的数值位置就是分类编码后的整数,这样能精准对应每个分组的位置。 - 如果不需要标签,只保留
plt.scatter部分即可,简化代码。
内容的提问来源于stack exchange,提问作者user3425900




