Python实现数据预处理:常见异常值检验方法解析
异常值检验方案
在数据分析和统计学中,异常值(也称为离群值)是指与数据集中其他数据点显著不同的观测值,识别和处理异常值对于确保数据分析的准确性非常重要,常见的异常值检验方法包括以下几种:
箱线图法
- 箱线图是一种基于分位数的图形方法,通过绘制数据的四分位数来检测异常值。箱线图将数据的中间 50%(即 IQR)进行可视化,并将任何超出上下四分位数的 1.5 倍 IQR 的数据点视为异常值,由于这种方法是基于数据的分布特征,因此对分布形状较为敏感,适合检测偏态分布中的异常值,然而,它可能对含有多个异常值的复杂数据不太适用。
标准差法
- 使用标准差进行异常值检测是基于数据的均值和标准差来判断数据点是否异常。通常,超出均值 ±3 倍标准差范围的值被认为是异常值,这种方法假设数据是正态分布的,因此在数据服从正态分布时表现较好,但对于非正态分布的数据,可能会误判一些极端但合理的数据点为异常值,或者忽略一些真正的异常值。
Grubbs假设检验
- Grubbs 检验是一种基于假设检验的异常值检测方法,适用于检测单一异常值,它假设数据是正态分布的,并通过比较最极端的数据点与均值的距离来判断该点是否为异常值,Grubbs 检验在数据集中存在单个异常值时非常有效,但当有多个异常值存在时,它可能无法准确识别所有异常值,此外,Grubbs 检验对于大样本量的数据也可能显得不够敏感。
散点图法
- 散点图是一种直观的方法,用于识别数据集中是否存在明显的离群点,尤其在双变量或多变量数据中,散点图可以帮助快速识别异常值。
集成学习方法
- 孤立森林是一种基于集成学习的异常值检测方法,适用于高维数据,它通过随机选择特征并对数据进行分割来构建森林,然后根据数据点被孤立的程度来判断其是否为异常值,孤立森林不依赖于数据的分布,因此在处理复杂且多维的数据时表现优越,然而,由于其随机性,有时检测到的异常值可能与基于统计方法的结果有所不同。
DBSCAN聚类法
- DBSCAN是一种基于密度的聚类算法,能够识别噪声点或异常值,这些点不会被分配到任何簇中,从而被视为异常值。
这些方法各有优劣,选择合适的异常值检测方法取决于数据的性质和分析的需求,在实际应用中,通常会结合多种方法来进行异常值的检测和处理,最后以达到数据清洗的作用。
代码实现
异常数据生成
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 设置随机种子以确保可重复性
np.random.seed(42)
# 生成一个1000个数据点的正态分布数据集
data = np.random.normal(loc=50, scale=5, size=1000)
# 人为地添加一些异常值
data[100] = 80
data[200] = 85
data[300] = 90
# 创建DataFrame
df = pd.DataFrame(data, columns=['Value'])
# 仅进行数据的可视化,不标注异常值索引
plt.figure(figsize=(10, 6))
plt.plot(df['Value'], 'b.', label='Data Points')
plt.title('Data Points Visualization')
plt.xlabel('Index')
plt.ylabel('Value')
plt.grid(linestyle='--', alpha=0.7)
plt.show()
生成一个包含1000个数据点的正态分布数据集,加入了几个异常值,然后将数据点可视化为一个散点图,这个数据将为接下来的实验数据。
箱线图实现
# 绘制箱线图
plt.figure(figsize=(10, 6))
plt.boxplot(df['Value'], vert=False)
plt.title('Boxplot of Data Values')
plt.xlabel('Value')
plt.grid(linestyle='--', alpha=0.7)
plt.show()
# 计算异常值
Q1 = df['Value'].quantile(0.25)
Q3 = df['Value'].quantile(0.75)
IQR = Q3 - Q1
# 异常值定义为低于Q1 - 1.5 * IQR或高于Q3 + 1.5 * IQR的数据点
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 找出异常值及其索引
outliers = df[(df['Value'] < lower_bound) | (df['Value'] > upper_bound)]
# 输出异常值的索引及其对应的值
outliers_indices = outliers.index.tolist()
outliers_values = outliers['Value'].tolist()
print("异常值索引: ", outliers_indices)
print("异常值对应的值: ", outliers_values)
箱线图绘制:使用plt.boxplot()函数绘制数据的箱线图,可以直观地看到异常值的位置,计算异常值:通过计算四分位数(Q1和Q3)及四分位距(IQR),设定异常值的上下限,然后通过这些上下限找出异常值,执行代码后,你会看到一个箱线图以及异常值的索引和值,箱线图展示了数据的分布情况,异常值位于图中的“胡须”之外,然后你就可以根据索引对异常值进行相应处理,如果删除后就按照缺失值的方法去填补数据。
标准差实现
# 定义函数 detect_outliers,接受两个参数:DataFrame 和需要检查的列名
def detect_outliers(df, column_names):
# 初始化一个空的 Index,用于存储异常值的索引
outlier_indices = pd.Index([])
# 遍历指定的列名列表
for column_name in column_names:
# 计算当前列的均值和标准差
mean = df[column_name].mean()
std = df[column_name].std()
# 计算Z-score
z_scores = (df[column_name] - mean) / std
# 设置 Z-score 阈值,例如,选择阈值为 3,即超过3个标准差的值被认为是异常值
threshold = 3
# 找出Z-score绝对值大于阈值的行
outliers = df[abs(z_scores) > threshold]
# 将新找到的异常值索引与已有异常值索引合并
outlier_indices = outlier_indices.union(outliers.index)
# 返回所有异常值的索引列表
return outlier_indices
# 使用函数检测异常值
outlier_indices = detect_outliers(df, ['Value'])
# 获取异常值及其对应的索引和值
outliers = df.loc[outlier_indices]
# 输出异常值的索引及其对应的值
print("异常值索引: ", outlier_indices.tolist())
print("异常值对应的值: ", outliers['Value'].tolist())
# 绘制数据的散点图,并在图中标注异常值
plt.figure(figsize=(10, 6))
plt.plot(df.index, df['Value'], 'b.', label='Data Points')
plt.plot(outliers.index, outliers['Value'], 'ro', label='Outliers')
# 在异常值点上标注其索引和值
for index, value in outliers.iterrows():
plt.text(index, value['Value'] + 0.5, f'{index}',
horizontalalignment='center', color='red', fontsize=10)
# 设置图表标题和轴标签
plt.title('Scatter Plot with Outliers Highlighted', fontsize=16)
plt.xlabel('Index', fontsize=14)
plt.ylabel('Value', fontsize=14)
plt.grid(linestyle='--', alpha=0.7)
plt.legend()
plt.show()
检测异常值函数 detect_outliers:使用Z-score方法来检测异常值,Z-score超过3的值被认为是异常值,输出异常值:打印出检测到的异常值的索引和对应的值,可视化:使用散点图(plt.plot)绘制所有数据点,将异常值用红色圆点标出,并在其上方标注对应的索引。
Grubbs假设检验实现
from scipy.stats import t
# 定义 Grubbs 检验的函数 detect_outliers_grubbs
def detect_outliers_grubbs(df, column_names, alpha=0.05):
# 初始化一个空的 Index,用于存储异常值的索引
outlier_indices = pd.Index([])
# 遍历指定的列名列表
for column_name in column_names:
# 获取当前列的数据
data = df[column_name]
# 计算均值和标准差
mean = np.mean(data)
std = np.std(data, ddof=1) # 使用样本标准差,自由度为1
# 计算 Grubbs 检验的统计量
n = len(data)
t_critical = t.ppf(1 - alpha / (2 * n), n - 2)
g = (np.max(data) - mean) / std
# 设置 Grubbs 检验的阈值
threshold = ((n - 1) / np.sqrt(n)) * np.sqrt(t_critical**2 / (n - 2 + t_critical**2))
# 找出 Grubbs 检验的异常值
outliers = df[np.abs((data - mean) / std) > threshold]
# 将新找到的异常值索引与已有异常值索引合并
outlier_indices = outlier_indices.union(outliers.index)
# 返回所有异常值的索引列表
return outlier_indices
# 使用 Grubbs 检验检测异常值
outlier_indices = detect_outliers_grubbs(df, ['Value'])
# 获取异常值及其对应的索引和值
outliers = df.loc[outlier_indices]
# 输出异常值的索引及其对应的值
print("异常值索引: ", outlier_indices.tolist())
print("异常值对应的值: ", outliers['Value'].tolist())
# 绘制数据的散点图,并在图中标注异常值
plt.figure(figsize=(10, 6))
plt.plot(df.index, df['Value'], 'b.', label='Data Points')
plt.plot(outliers.index, outliers['Value'], 'ro', label='Outliers')
# 在异常值点上标注其索引和值
for index, value in outliers.iterrows():
plt.text(index, value['Value'] + 0.5, f'{index}',
horizontalalignment='center', color='red', fontsize=10)
# 设置图表标题和轴标签
plt.title('Scatter Plot with Grubbs Outliers Highlighted', fontsize=16)
plt.xlabel('Index', fontsize=14)
plt.ylabel('Value', fontsize=14)
plt.grid(linestyle='--', alpha=0.7)
plt.legend()
plt.show()
通过计算Grubbs统计量G和临界值,将超过临界值的点视为异常值,t_critical 使用Student t分布的临界值来计算Grubbs检验的阈值,这个方法提供了一种基于假设检验的统计方法来识别异常值,适用于更严格的异常值检测场景,在实际应用中,尽管不同异常值检测方法的输出结果可能有所不同(可以和前面的方法对比),但Grubbs检验在实验数据中成功地识别出了所有人为添加的异常值,显示了其在异常值检测方面的有效性和精准性。
集成学习(孤立森林)实现
from sklearn.ensemble import IsolationForest
# 使用孤立森林进行异常值检测
iso_forest = IsolationForest(contamination=0.01, random_state=42)
df['Anomaly'] = iso_forest.fit_predict(df[['Value']])
# 获取异常值的索引及其对应的值
anomalies = df[df['Anomaly'] == -1]
anomaly_indices = anomalies.index
anomaly_values = anomalies['Value']
# 打印异常值的索引和对应的值
print("异常值索引: ", list(anomaly_indices))
print("异常值对应的值: ", list(anomaly_values))
# 数据可视化
plt.figure(figsize=(10, 6))
plt.plot(df['Value'], 'b.', label='Data Points')
plt.plot(anomaly_indices, anomaly_values, 'ro', label='Outliers')
plt.title('Data Points with Anomalies Visualization')
plt.xlabel('Index')
plt.ylabel('Value')
plt.grid(linestyle='--', alpha=0.7)
# 标注异常值索引
for i, index in enumerate(anomaly_indices):
plt.text(index, anomaly_values.iloc[i] + 1, f'{index}', color='red', fontsize=9)
plt.legend()
plt.show()
孤立森林检测: 使用 IsolationForest 进行异常值检测,标记异常值为 -1,可视化: 使用 matplotlib 可视化数据,并在图中标注异常值的索引,异常值以红色散点显示,并在旁边标注其索引。
DBSCAN聚类实现
对于如何使用 DBSCAN 进行异常值检测,我之前已经撰写了一篇详细的文章,了解更多关于 DBSCAN 聚类和异常值检测的内容:密度聚类DBSCAN详解。
总结
在这里可以发现每种方法的检测结果都不完全一致,但是每种异常值检测方法的检测结果不一致是正常的,这是因为它们的原理和适用场景不同,箱线图和标准差方法依赖于数据的分布特征,适合于简单和对称分布的数据;Grubbs 假设检验适合检测单个异常值;而孤立森林则擅长处理高维和复杂的数据,选择哪种方法取决于具体的数据特征和分析需求,建议在实际应用中结合多种方法进行对比,以确保检测到的异常值更加准确和全面。