异常值处理

异常值是远离其他数据点的数据点。换句话说,它们是一个数据集中不同寻常的值。异常值对于许多统计分析是有问题的,因为它可能让我们错过显著发现或扭曲实际结果。

一、单变量异常检测

四分位距法(箱型图)

notion imagenotion image
箱形图的上限和下限并不是数据集的最大值和最小值,而是经过一定的计算得到的,所以能检测到离群比较离谱的异常值
在 pandas 中,可以用 quantile 方法来计算指定的分位数,并结合箱型图的特性:1.5 倍四分位距离的特性来过滤异常值(可以适当调整倍数或分位数)
# 箱型图判断异常点 def box_outlier(data): df = data.copy(deep=True) out_index = [] for col in df.columns: # 对每一列分别用箱型图进行判断 Q1 = df[col].quantile(q=0.25) # 下四分位 Q3 = df[col].quantile(q=0.75) # 上四分位 low_whisker = Q1 - 1.5 * (Q3 - Q1) # 下边缘 up_whisker = Q3 + 1.5 * (Q3 - Q1) # 上边缘 # 寻找异常点,获得异常点索引值,删除索引值所在行数据 rule = (df[col] > up_whisker) | (df[col] < low_whisker) out = df[col].index[rule] out_index += out.tolist() df.drop(out_index, inplace=True) return df

标准差法(3σ 原则)

通常一组数据的标准差用 σ 表示,算术平均用 μ 表示。
对于服从正态分布的数据来说,约68.2%的数据在均值的一倍标准差之内。约有95.4%和99.7%的数据点在均值的两倍和三倍标准差以内。
3σ 原则是通过设置一个上限:μ + 3σ;一个下限:μ - 3σ;将分布在这个上下限范围之外的数据视为异常数据。
# 3sigma 原则 def treble_sigma_outlier(data): df = data.copy(deep=True) out_index = [] for col in df.columns: col_mean = df[col].mean() # 均值 col_std = df[col].std() # 标准差 low_whisker = col_mean - 3 * col_std # 下边缘 up_whisker = col_mean + 3 * col_std # 上边缘 # 寻找异常点,获得异常点索引值,删除索引值所在行数据 rule = (df[col] > up_whisker) | (df[col] < low_whisker) out = df[col].index[rule] out_index += out.tolist() df.drop(out_index, inplace=True) return df

z-score 法

Z分数是一种数学变换,它根据每个观测值与平均值的距离对其进行分类。该方法计算数组中每个值的z-score值,然后再根据设定的阈值来筛选样本,其计算公式为:
z-score 法其实与标准差法的底层逻辑一致,当设定的阈值为 [-3, 3] 时,其实就是 3σ 原则

二、多变量异常检测

聚类检测

使用聚类算法进行分簇,如果某一个簇里的样本数很少,而且簇质心和其他所有的簇都很远,那么这个簇里面的样本极有可能是异常特征样本了。
通常使用DBSCAN(密度)聚类方法进行异常值检测,它的优势是不用指定簇的数量。并且 DBSCAN 会直接给出噪点的标签(-1)。
聚类结果说明:
  1. -1:表示该样本为噪声点或孤立点。它们位于聚类的边缘或空白区域,没有足够的邻居点来形成一个稠密的聚类。
  1. 0:表示该样本为核心点。核心点是指其周围有足够多的邻居点(即其邻居点数大于等于一个阈值minPts),可以形成一个稠密的聚类。
  1. 其他正整数:表示该样本属于第几个聚类。DBSCAN将样本划分为多个稠密的聚类,每个聚类都有一个唯一的标签编号。
import numpy as np from sklearn.cluster import DBSCAN from sklearn.preprocessing import MinMaxScaler glass_x = np.array(glass).astype('float') scaler = MinMaxScaler() glass_scaled = scaler.fit_transform(glass_x) # Initiate DBSCAN dbscan = DBSCAN(eps=0.4, min_samples=10) dbscan.fit(glass_scaled) glass['outlier'] = dbscan.labels_

孤立森林

孤立森林遵循随机森林的方法,但相比之下,它检测(或叫做隔离)异常数据点。它有两个基本假设:离群值是少数样本,且它们是分布偏离的。
孤立森林通过随机选择一个特征,然后随机选择一个分割规则来分割所选特征的值来创建决策树。这个过程一直持续到达到设置的超参数值。在构建好的孤立森林中,如果树更短且对应分支样本数更少,则相应的值是异常值(少数和不寻常)。
返回的结果中异常值为:-1
⚠️
经验提示:
在对数据进行异常值识别的时候,应该分别对训练数据和预测数据进行训练预测(也就是都用 fit_predict 方法)而不是用训练数据训练一个模型后对新数据数据直接用 predict 进行预测。因为新数据和训练数据的空间分布是不一样的,所以这样识别出来的异常值是不对的。
我曾经才过这个坑,用训练数据训练了一个孤立森林模型,然后在预测的时候直接加载模型来预测,结果预测出来之后异常值和正常值的比例反过来了,标签为 1 的正常值数量看起来倒像是异常值(因为等着部署模型上线当时真的汗流浃背了~)。当时我以为是 fit_predict 方法和 predict 方法返回的标签相反(后来细想官方应该不会干这么 low 的事),结果去查了官网原文,发现是我的结果问题。排查了好久才反应过来是这个问题。
from sklearn.ensemble import IsolationForest # Initiate isolation forest isolation = IsolationForest(n_estimators=100, contamination='auto', max_features=glass.shape[1]) # Fit and predict isolation.fit(glass) outliers_predicted = isolation.predict(glass) # Address outliers in a new column glass['outlier'] = outliers_predicted
参数详解:
# n_estimators:基分类器的数量;默认100 # max_samples:用来训练基分类器的样本个数或比例; # 值有:auto,int,float; # 如果值是大于0的整数,则抽取整数个样本 # 如果值是浮点数(0到1之间的浮点数),则抽取data.shape[0] * float 个样本 # 如果值是auto,则抽取min(256, n_samples)个样本。n_samples代表所有样本数量 # contamination:数据污染程度,也就是异常值所占的比例。 # 如果值为auto,则默认这个比例和作者论文的数据污染比例一致,默认0.1 # 如果是一个float,则这个float的值应该在(0,0.5)之间 # max_features:用来训练基分类器的特征个数或比例 # 如果值是大于0的整数,则抽取整数个特征 # 如果值是浮点数(0到1之间的浮点数),则抽取data.shape[1] * float 个特征

局部异常因子 LOF

LOF是一种流行的无监督异常检测算法,它计算数据点相对于其邻居的局部密度偏差。计算完成后,密度较低的点被视为异常值。
算法预测结果中,大于某个阈值(例如2.5)的样本标记为异常点,LOF值小于等于该阈值的样本标记为正常点
from sklearn.neighbors import LocalOutlierFactor glass_x = np.array(glass).astype('float') scaler = MinMaxScaler() glass_scaled = scaler.fit_transform(glass_x) clf = LocalOutlierFactor() outliers_predicted = clf.fit_predict(glass) glass['outlier'] = outliers_predicted

三、异常值处理

  • 直接删除
    • 如果异常数据量太大了,直接删除不仅会损失大量训练样本,并且会直接改变样本分布
    • 可以适当控制阈值的区间,来减少异常样本
  • 当成空值填充
    • 用一些统计值填充
  • 不做处理

推荐阅读:

 
If you have any questions, please contact me.