前言
继续详细介绍缺失值处理、异常值检测、数据一致性处理。这是数据清洗的第一步,关键步骤。
上一篇因为篇幅的原因,只介绍了缺失值处理,本文介绍异常值检测。
1、异常值与离群点检测
1.1 基于统计学方法(Z-Score、IQR)
a) Z-Score:公式:Z = (X - μ) / σ
其中,X是原始值,μ是平均值,σ是标准差
原理:假设数据呈正态分布,将原始数据转换为标准正态分布。通常|Z| > 3被视为异常值。b) IQR(四分位距)方法:公式:IQR = Q3 - Q1
下界 = Q1 - 1.5 * IQR
上界 = Q3 + 1.5 * IQR
原理:利用数据的四分位数来定义异常值范围,对非正态分布数据也适用。
依旧用到了scipy.stats
,上篇已经介绍过,就不做过多的赘述了。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
# 创建包含异常值的示例数据
np.random.seed(42)
data = np.concatenate([np.random.normal(0, 1, 980), np.random.normal(5, 1, 20)])
df = pd.DataFrame({'value': data})
# Z-Score方法
z_scores = np.abs(stats.zscore(df['value']))
z_score_outliers = df[z_scores > 3]
# IQR方法
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1
iqr_outliers = df[(df['value'] < (Q1 - 1.5 * IQR)) | (df['value'] > (Q3 + 1.5 * IQR))]
# 可视化
plt.figure(figsize=(12, 6))
plt.subplot(121)
plt.hist(df['value'], bins=50, edgecolor='black') # 修正了bins的值
plt.title('Data Distribution')
plt.xlabel('Value')
plt.ylabel('Frequency') # 修正了ylabel的拼写
plt.subplot(122)
plt.boxplot(df['value'])
plt.title('Box Plot')
plt.ylabel('Value')
plt.tight_layout()
plt.show()
print(f"Z-Score method detected {len(z_score_outliers)} outliers")
print(f"IQR method detected {len(iqr_outliers)} outliers")
# 显示检测到的异常值
print("\nZ-Score Outliers:")
print(z_score_outliers)
print("\nIQR Outliers:")
print(iqr_outliers)
结果如下,
其实检测异常值的代码就6行,其余是拟合数据,画图的代码,所以说可视化是非常直观的,大家在做任何数据处理前要学会看数据,如散点图、热力图、线图等等。
2.2 基于Isolation Forest、LOF的检测
-
a) Isolation Forest孤立森林:
原理:通过随机选择特征和分割点来构建多棵隔离树,是一种用于异常检测的无监督学习算法。
核心思想:异常值在数据集中通常较少且与正常数据有显著差异,更容易在隔离树中被快速隔离。
-
算法步骤:
(1)随机选择一个特征
(2)在该特征的取值范围内随机选择一个分割点
(3)根据选定的特征和分割点,将数据集分割成两部分
(4)重复1-3,直到每个样本被隔离或达到指定深度,即每个点都位于一个叶节点中
(5)计算平均路径长度,路径较短的被视为异常点
用一句大白话说,越偏离大部队的样本,越容易被找出来(平均路径长度)。
-
b) LOF局部离群因子:
原理:比较一个点的密度与其邻居的密度,来判定是否是异常点。
核心思想:如果一个点的密度明显低于其邻居,则可能是异常点。
-
算法步骤:
(1)选择邻居数K,计算每个点的k-距离(到第k个最近邻居的距离)
(2)计算每个点的可达距离,对于每个数据点p和其邻居o,定义可达距离reach-dist_k(p, o),表示从p到o的距离,考虑到o的k-距离
(3)计算每个点的局部可达密度,基于其可达距离的倒数
(4)计算LOF值(邻居的平均局部可达密度与点自身局部可达密度的比值)
用一句大白话说,越靠近大部队的样本,样本之间的密达越大(局部可达密度)。
-
c) LOF局部离群因子和Isolation Forest孤立森林区别:
原理不同:
LOF考虑数据点的局部密度;孤立森林异常点通常会有较短的路径计算复杂度:
LOF的计算复杂度较高,因为它需要计算每个数据点与其邻近点的距离,并计算密度比值。孤立森林的计算复杂度相对较低,因为它只需要构建一棵二叉树,并计算路径长度。检测效果:
LOF对于局部异常点的检测效果较好,孤立森林对于全局异常点的检测效果较好。适用性:LOF适用于那些异常点在局部区域内密度显著不同的数据集。孤立森林适用于那些异常点在整个数据集中分布较为稀疏的数据集。
依旧用到了sklearn
,上篇已经介绍过,就不做过多的赘述了。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
from sklearn.datasets import make_blobs
# 创建包含异常值的示例数据
X, _ = make_blobs(n_samples=300, centers=1, cluster_std=0.5, random_state=0)
X = np.concatenate([X, np.random.uniform(low=-4, high=4, size=(15, 2))])
# Isolation Forest
iso_forest = IsolationForest(contamination=0.1, random_state=42)
iso_forest_labels = iso_forest.fit_predict(X)
# LOF
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.1)
lof_labels = lof.fit_predict(X)
# 可视化
plt.figure(figsize=(12, 5))
plt.subplot(121)
plt.scatter(X[:, 0], X[:, 1], c=iso_forest_labels, cmap='viridis')
plt.title('Isolation Forest')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.subplot(122)
plt.scatter(X[:, 0], X[:, 1], c=lof_labels, cmap='viridis')
plt.title('Local Outlier Factor')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.tight_layout()
plt.show()
print(f"Isolation Forest detected {sum(iso_forest_labels == -1)} outliers")
print(f"LOF detected {sum(lof_labels == -1)} outliers")
输出结果:
Isolation Forest detected 32 outliers
LOF detected 32 outliers
sklearn.ensemble.IsolationForest
孤独森林和sklearn.neighbors.LocalOutlierFactor
局部离群因子。选用哪种方法,取决于数据的特性,所以大家可以提前画图看看,这个例子适合IsolationForest
。
2、 异常值修正
以下是处理异常值的几种常用策略的核心代码示例:
删除异常值
# 假设 df 是一个包含异常值的 DataFrame
# 删除某一列中所有异常值,这里以 'A' 列为例
df = df[df['A'] < some_threshold] # 将 some_threshold 替换为适当的阈值
替换异常值
# 使用均值替换异常值
mean_value = df['A'].mean()
df['A'] = df['A'].apply(lambda x: mean_value if x > some_threshold else x)
# 使用中位数替换异常值
median_value = df['A'].median()
df['A'] = df['A'].apply(lambda x: median_value if x > some_threshold else x)
变换数据
# 对数变换,适用于正数数据
df['A'] = np.log1p(df['A']) # 使用 np.log1p 以避免 log(0) 的情况
# 对数变换后,可能需要再次处理异常值
分箱(离散化)
# 使用 pandas 的 cut 函数进行分箱
bins = [-float('inf'), some_lower_threshold, some_upper_threshold, float('inf')]
labels = ['Low', 'Medium', 'High']
df['A_binned'] = pd.cut(df['A'], bins=bins, labels=labels, right=False)
代码示例提供了处理异常值的基本方法,但在实际应用中,选择策略时需考虑数据特性、异常值产生的原因和对模型的潜在影响。