3、探索性数据分析(EDA)与可视化:使用 Matplotlib/Seaborn 绘制箱线图、直方图、散点图,初步识别异常点
探索性数据分析(EDA)是异常检测流程中不可或缺的起点。在正式建模之前,通过可视化手段直观观察数据分布、离散程度与变量间关系,能够快速定位潜在的异常值、数据倾斜或结构断裂。本章将系统讲解如何使用 Matplotlib 与 Seaborn 绘制三种核心图表——箱线图、直方图、散点图,并基于图形特征初步识别异常点。
核心原则:EDA 阶段的异常识别不依赖复杂算法,而是通过视觉模式发现“与众不同”的数据点。这些初步发现将直接指导后续的统计检验与模型选择。
3.1 箱线图:基于四分位距的异常点识别
箱线图(Box Plot)通过五数概括(最小值、第一四分位数 Q1、中位数、第三四分位数 Q3、最大值)展示数据分布,并利用四分位距(IQR = Q3 - Q1)定义异常点边界:
- 下边界:Q1 - 1.5 × IQR
- 上边界:Q3 + 1.5 × IQR
- 温和异常点:落在 1.5×IQR 与 3×IQR 之间的点
- 极端异常点:超过 3×IQR 的点
关键参数:Seaborn 的
boxplot() 默认使用 1.5×IQR 作为 whisker 长度,可通过 whis 参数调整灵敏度。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 生成示例数据(含异常点)
np.random.seed(42)
data = np.random.normal(50, 10, 200)
data = np.append(data, [120, 130, 5, 3]) # 注入异常点
df = pd.DataFrame({'value': data})
# 绘制箱线图
plt.figure(figsize=(8, 4))
sns.boxplot(x=df['value'], color='#3498db', linewidth=1.5)
plt.title('箱线图:识别异常点(IQR 方法)', fontsize=14, color='#2c3e50')
plt.xlabel('数值')
plt.tight_layout()
plt.show()
箱线图将自动标记超出 whisker 范围的点为异常点(通常以菱形点显示)。上例中,120、130、5、3 均被识别为异常值。
3.2 直方图:分布形态与尾部异常
直方图(Histogram)通过分箱展示数据频率分布,帮助识别:
- 偏态分布:长尾方向可能包含异常点
- 多峰分布:可能隐含不同子群体或数据污染
- 孤立箱:远离主体分布的孤立柱条
# 绘制直方图 + 核密度估计
plt.figure(figsize=(8, 4))
sns.histplot(df['value'], bins=30, kde=True, color='#2ecc71', edgecolor='#27ae60', alpha=0.7)
plt.title('直方图与 KDE:观察分布尾部', fontsize=14, color='#2c3e50')
plt.xlabel('数值')
plt.ylabel('频数')
plt.tight_layout()
plt.show()
注意:直方图的分箱数量(bins)会显著影响异常点的视觉呈现。bins 过少会掩盖尾部细节,bins 过多则可能将正常波动误判为异常。建议使用
sturges 或 fd 规则自动选择。
3.3 散点图:变量关系与离群点
散点图(Scatter Plot)用于展示两个连续变量之间的关系,异常点表现为:
- 远离主体点云:在 x 或 y 方向上显著偏离
- 破坏线性趋势:打破整体相关模式的点
- 高杠杆点:在 x 方向极端但 y 方向符合趋势的点
# 生成双变量数据(含异常点)
np.random.seed(42)
x = np.random.normal(50, 10, 200)
y = 2 * x + np.random.normal(0, 15, 200)
x = np.append(x, [100, 5, 55, 48])
y = np.append(y, [250, -20, 180, 60]) # 注入不同类型异常
df_scatter = pd.DataFrame({'feature_x': x, 'feature_y': y})
# 绘制散点图
plt.figure(figsize=(7, 5))
sns.scatterplot(data=df_scatter, x='feature_x', y='feature_y',
color='#9b59b6', edgecolor='#8e44ad', s=50, alpha=0.7)
plt.title('散点图:识别离群点与高杠杆点', fontsize=14, color='#2c3e50')
plt.xlabel('特征 X')
plt.ylabel('特征 Y')
plt.tight_layout()
plt.show()
进阶技巧:在散点图上叠加
sns.regplot() 的回归线,可以更清晰地识别偏离线性趋势的异常点。同时,结合 hue 参数按类别着色,有助于发现分组内的局部异常。
3.4 三种图表的对比与选择策略
| 图表类型 | 适用场景 | 异常点识别依据 | 优势 | 局限性 |
|---|---|---|---|---|
| 箱线图 | 单变量分布,多组对比 | IQR 边界外 | 标准化判定,不受分布形态影响 | 无法识别局部密度异常 |
| 直方图 | 单变量分布形态 | 尾部孤立箱、偏态 | 直观展示整体分布 | 对分箱数量敏感 |
| 散点图 | 双变量关系 | 偏离点云或趋势线 | 可识别关系型异常 | 高维数据需降维 |
实践建议:在 EDA 阶段,建议按以下顺序使用三种图表:
1. 先对每个特征绘制箱线图,快速筛选异常值;
2. 对疑似异常的特征绘制直方图,确认分布尾部情况;
3. 对关键变量对绘制散点图,检查关系型异常。
1. 先对每个特征绘制箱线图,快速筛选异常值;
2. 对疑似异常的特征绘制直方图,确认分布尾部情况;
3. 对关键变量对绘制散点图,检查关系型异常。
3.5 综合案例:使用 EDA 初步识别异常点
以下代码整合三种图表,对一个包含多变量的小型数据集进行 EDA 异常识别:
# 综合 EDA 异常识别流程
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# 加载示例数据(可使用自己的数据替换)
np.random.seed(42)
df_demo = pd.DataFrame({
'age': np.random.randint(18, 70, 500),
'income': np.random.lognormal(mean=10, sigma=0.5, size=500),
'spending_score': np.random.randint(1, 100, 500)
})
# 注入异常点
df_demo.loc[0, 'income'] = 500000
df_demo.loc[1, 'age'] = 150
df_demo.loc[2, 'spending_score'] = 200
# 创建画布
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 箱线图
sns.boxplot(data=df_demo[['age', 'income', 'spending_score']], ax=axes[0,0])
axes[0,0].set_title('箱线图:多变量异常点', fontsize=12)
# 直方图
sns.histplot(df_demo['income'], bins=50, kde=True, ax=axes[0,1], color='#2ecc71')
axes[0,1].set_title('收入分布直方图', fontsize=12)
# 散点图
sns.scatterplot(data=df_demo, x='age', y='spending_score', ax=axes[1,0], color='#9b59b6')
axes[1,0].set_title('年龄 vs 消费评分', fontsize=12)
# 散点图 2
sns.scatterplot(data=df_demo, x='income', y='spending_score', ax=axes[1,1], color='#1abc9c')
axes[1,1].set_title('收入 vs 消费评分', fontsize=12)
plt.tight_layout()
plt.show()
注意事项:EDA 阶段的异常识别是初步的、探索性的。被标记为异常的点不一定都是“错误”数据——它们可能代表稀有但真实的业务场景(如高净值客户、极端天气事件)。务必结合业务背景进行二次确认。
通过本章的学习,你已经掌握了使用箱线图、直方图和散点图进行初步异常点识别的核心方法。这些可视化工具将为后续的统计检验和机器学习异常检测算法提供重要的数据洞察基础。