在数据清洗的过程中,不仅仅需要处理NaN或零值。在实际项目中,需要进行更深入的分析,其中异常值检测是每次迭代分析都必须关注的重点之一。本文将介绍如何对一维、二维和曲线数据进行异常值剔除,使用的方法包括z-score、数据分布和多项式拟合分布等统计方法。通过本文的学习,将能够检测各种数据中的异常值,并且不仅仅是理论,还会使用Python的著名sk-learn包来自动化这一过程,让既掌握概念背后的数学知识,又能进行简单稳定的实践操作。
在数据分布中手动进行异常值剔除的最基本和最常见的方法是:使用统计量拟合模型作为多项式方程,找出所有低于某个z-score的点,移除这些异常值,重新拟合分布,并可能从第一步开始再次运行(直到所有异常值都被移除)。
# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
# 加载数据
d1 = np.loadtxt("outlier_1d.txt")
d2 = np.loadtxt("outlier_2d.txt")
d3 = np.loadtxt("outlier_curve.txt")
print(d1.shape, d2.shape)
# 绘制散点图
plt.scatter(d1, np.random.normal(7, 0.2, size=d1.size), s=1, alpha=0.5)
plt.scatter(d2[:, 0], d2[:, 1], s=5)
plt.show();
plt.plot(d3[:, 0], d3[:, 1]);
在这篇文章中,当不自动化处理时,只会使用NumPy和matplotlib库。为所有三种类型的数据准备了基于文本的数据集,因此推荐使用np.loadtxt()方法。然后,通过查看一维和二维数据集的形状,可以看到它们都有1010行(其中10行是故意添加的异常值,用于分析异常值)。
使用散点图绘制数据点,在输出中有两个图表,一个表示一维(蓝色)和二维(红色)数据集,第二个是曲线拟合。尽管从裸眼也可以看出图中的一些异常值,但这不是异常值检测的正确方法。
z-score是一种标准分数,用于检查数据点是否在范围内(在最高百分位数和较低百分位数之间)。如果阈值超过z-score,那么该数据点就是异常值。在上述代码中,也是按照同样的方法进行的:首先使用公式((X-均值)/标准差)计算z-score,其中X是每个数据点,如果想要遍历每个实例,那么循环是必需的。图表说明了一切!根据阈值,可以看到红点是异常值或不良数据,而绿点是好数据。
# 计算z-score
mean, std = np.mean(d1), np.std(d1)
z_score = np.abs((d1 - mean) / std)
threshold = 3
good = z_score < threshold
print(f"Rejection {(~good).sum()} points")
# 使用正态分布计算z-score对应的累积概率
from scipy.stats import norm
print(f"z-score of 3 corresponds to a prob of {100 * 2 * norm.sf(threshold):0.2f}%")
# 绘制散点图区分好数据和坏数据
visual_scatter = np.random.normal(size=d1.size)
plt.scatter(d1[good], visual_scatter[good], s=2, label="Good", color="#4CAF50")
plt.scatter(d1[~good], visual_scatter[~good], s=8, label="Bad", color="#F44336")
plt.legend();
在上述代码中,计算了z-score,并根据阈值来确定哪些点是异常值。还使用正态分布计算了z-score对应的累积概率,并通过散点图区分好数据和坏数据。
如果没有分布,而是有带有不确定性的数据,可以做类似的事情。以一个实际的例子来说,在一篇论文中,有x值、y值和误差(波长、通量和通量误差),想要减去平滑的背景。想要用一个简单的多项式拟合来做到这一点。不幸的是,数据中有几个发射线和宇宙射线的影响(以尖峰形式可见),这使多项式拟合产生了偏差,所以不得不移除它们。
# 拟合多项式并移除异常值
xs, ys = d3.T
p = np.polyfit(xs, ys, deg=5)
ps = np.polyval(p, xs)
plt.plot(xs, ys, ".", label="Data", ms=1)
plt.plot(xs, ps, label="Bad poly fit")
plt.legend();
在上述代码中,对数据点进行了5次多项式拟合,并在5次迭代内移除了异常值(尽管在循环结束时,有一个断点,如果异常值在第5次迭代之前被移除,将会中断循环)。
sk-learn库来拯救。查看主页面,上面列出了许多可以做异常值检测的方法。认为LOF(局部异常因子)很棒——它使用一个点到其最近的二十个邻居的距离来计算点的密度,并移除那些低密度区域的点。
# 使用sk-learn的LOF进行自动化异常值检测
from sklearn.neighbors import LocalOutlierFactor
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.005)
good = lof.fit_predict(d2) == 1
plt.scatter(d2[good, 0], d2[good, 1], s=2, label="Good", color="#4CAF50")
plt.scatter(d2[~good, 0], d2[~good, 1], s=8, label="Bad", color="#F44336")
plt.legend();