python中异常值的检测和处理
前言
通常,咱们做数据挖掘的时候经常免不了会遇到异常值检测或者异常值处理等步骤,那么什么是异常值呢?如何检测数据中是否存在异常值?如何处理数据中的异常值?本文专门探究一下这些问题。
异常值又称离群点,是指那些在数据集中存在的不合理的值,需要注意的是,不合理的值是偏离正常范围的值,不是错误值。
异常值出现的原因:数据集中的异常值可能是由于传感器故障、人工录入错误或异常事件导致。
一、异常值的检测
异常值的检测通常有简单统计分析、3σ原则、箱型图、聚类等方法,以下详细对各个方法进行逐一说明。
1.1 简单统计分析
最常用的统计量是最大值和最小值,用来判断这个变量的取值是否超出合理的范围。在pandas中,一般使用describ属性就可以查看相关的统计量。
# 全国房价数据统计 df_dropna = df[~df.price.isna()].reset_index(drop=True) # 首先剔除NA值 df_dropna.price.describe()
1.2 3σ原则(Z-score Method)
3σ原则的前提假设是数据集符合正态分布(高斯分布),如下图:
观察上图容易得出以下数据分布情况:
- 数值分布在区间(μ-σ, μ+σ)中的概率为 0.6826
- 数值分布在区间(μ-2σ, μ+2σ)中的概率为 0.9545
- 数值分布在区间(μ-3σ, μ+3σ)中的概率为 0.9973
可以认为,数据存在随机误差,其取值几乎全部集中在(μ-3σ, μ+3σ)区间内,超出这个范围的可能性仅占不到0.3%,那么误差超过这个区间的值就识别为异常值了。
实际上,大部分真实的数据并不满足这一条件,我们就需要先对原始数据集进行Z-score变换,使用原始数据的均值(μ)和标准差(σ)进行数据的标准化。经过处理的数据也将服从标准正态分布,其均值为0,标准差为1,故3σ原则又被称为Z-score method。
Z = x − μ σ Z = \frac{x-μ}{σ} Z=σx−μ
经过Z-score标准化后得到符合正态分布的数据,我们就可以使用3σ原则来处理数据了。演示代码如下:
# 观察原始房价数据分布情况 import matplotlib.pyplot as plt s = df_dropna.price fig = plt.figure(figsize=(6, 6)) # 创建自定义图像 ax1 = fig.add_subplot(2,1,1) # 创建子图1 ax1.scatter(s.index, s.values) # 绘制散点图 plt.grid() # 添加网格 ax2 = fig.add_subplot(2,1,2) # 创建子图2 s.hist(bins=30, alpha=0.5, ax=ax2) # 绘制直方图 s.plot(kind='kde', secondary_y=True, ax=ax2) # 绘制密度图,使用双坐标轴 plt.grid() plt.show() # 显示自定义图像 # ######################################### # Z-score变换 _std = df_dropna.price.std() _mean = df_dropna.price.mean() df_dropna['z_price'] = df_dropna.price.map(lambda x: (x-_mean)/_std) # 3σ原则检测异常值 _mean = 0 _std = 1 mark = (_mean-3*_std>df_dropna['z_price']) | (df_dropna['z_price']>_mean+3*_std) df_dropna[mark] # 异常数据
根据实际业务需求,若数据不服从正态分布,也可以不做标准化处理,可以用远离平均值的多少倍标准差来描述(这就使Z-score方法可以适用于不同的业务场景,只是需要根据经验来确定 kσ 中的k值,这个k值就可以认为是阈值),演示代码如下:
# Z-score方法检测异常值(阈值k=3) _mean = df_dropna.price.mean() _std = df_dropna.price.std() mark = (_mean-3*_std>df_dropna.price) | (df_dropna.price>_mean+3*_std) df_dropna[mark] # 异常数据
1.3 Robust Z-score Method
该方法也被称为 中位数绝对偏差法。它类似于Z-score方法,只是参数有所变化。由于平均值和标准差受异常值的影响很大,因此我们使用中位数和中位数的绝对偏差来改变这个参数,公式如下:
R . Z . s c o r e = 0.6745 × ( x − M e d i a n ) M A D w h e r e M A D = m e d i a n ( ∣ x − M e d i a n ∣ ) R.Z.score = \frac{0.6745\times \left ( x-Median\right ) }{MAD} \\ where \quad MAD=median(∣x−Median∣) R.Z.score=MAD0.6745×(x−Median)whereMAD=median(∣x−Median∣)
假设 x x x 服从标准正态分布,那么 M A D MAD MAD 会收敛于半正态分布的中位数,即正态分布的75%百分位,并且 N ( 0.75 ) ≃ 0.6745 N(0.75)≃0.6745 N(0.75)≃0.6745。计算出稳健Z分数后,可以根据以下异常值的判定准则:
- 如果 ∣ Z ∣ ≤ 2 |Z| ≤ 2 ∣Z∣≤2 则结果满意(satisfactory)
- 如果 2 < ∣ Z ∣ < 3 2< |Z| < 3 2<∣Z∣<3 则结果可疑(questionable)
- 如果
∣
Z
∣
≥
3
|Z| ≥ 3
∣Z∣≥3 则结果不满意(unsatisfactory)
演示代码:
# R.Z.score Method检测异常值 import numpy as np import scipy # 计算R.Z.score med = np.median(df_dropna.price.values) mad = scipy.stats.median_absolute_deviation(df_dropna.price.values) r_z_score = df_dropna.price.map(lambda x: (0.6745*(x-med)) / (np.median(mad))) # 判定异常值 mark = np.abs(r_z_price.values) > 3 df_dropna[mark] # 异常数据
1.4 箱型图(四分位距法)
箱型图提供了识别异常值的标准,异常值通常被定义为小于QL-1.5IQR或大于QU+1.5IQR的数据。QL称为下四分位数,表示全部值中有四分之一的数据取值比它小;QU称为上四分位数,表示全部值中有四分之一的数据取值比它大;IQR称为四分位数间距,是上四分位数QU与下四分位数QL之差。所以此方法又称为四分位距法,如下图。
# 箱型图可视化数据 import matplotlib.pyplot as plt plt.boxplot(df_dropna.price) plt.show() # 四分位距法检测异常值 QL = df_dropna.price.quantile(0.25) QU = df_dropna.price.quantile(0.75) IQR = QU - QL mark = (df_dropna.price
1.5 截尾处理(Winsorization method(Percentile Capping))
该方法类似于IQR法。如果一个值超过了第99个百分位数的值,并且低于给定值的第1个百分位数,则被视为异常值。
演示代码如下:
# 截尾处理检测异常值 up = df_dropna.price.quantile(0.99) low = df_dropna.price.quantile(0.01) mark = (df_dropna.price up) df_dropna[mark] # 异常数据
1.6 DBSCAN聚类(DENSITY-BASED SPATIAL CLUSTERING OF APPLICATIONS WITH NOISE)
DBSCAN是一种基于密度的聚类算法,它将数据集划分为高密度区域的子组,并将稀疏区域聚类识别为异常值。集群标记-1表示该集群包含离群值,其余集群没有离群值。这种方法类似于K-means聚类。DBSCAN在多元离群值检测中具有最佳的检测效果。DBSCAN需要两个参数:
- epsilon:一个距离参数,定义搜索附近邻居的半径。
- minPts:形成集群所需的最小点数。
使用以上2个参数我们就可以把每个数据点分类成:
- 核心点 :在其半径内至少有最小数量的其他点(minPts)的点。
- 边界点 : 一个点在核心点的半径内,但小于其自身半径内其他点(minPts)的最小数量。
- 噪声点 : 既不是核心点也不是边界点的点。
如下图所示:
import pandas as pd from sklearn.cluster import DBSCAN def DBSCAN_outliers(df): # df是列数据 outlier_detection = DBSCAN(eps=2, metric='euclidean', min_samples=5) clusters = outlier_detection.fit_predict(df.values.reshape(-1,1)) return clusters
1.7 孤立随机森林聚类(ISOLATION FOREST)
它是一种聚类算法,属于集成决策树家族,在原理上类似于随机森林。
孤立随机森林的特点有以下几点:
- (1)它将数据点分类为异常值和非异常值,并适用于非常高维的数据。
- (2)该方法基于决策树,分离出异常值。
- (3)如果结果是 -1,这意味着这个特定的数据点是一个异常值。如果结果为 1,则意味着该数据点不是异常值。
演示代码:
from sklearn.ensemble import IsolationForest import numpy as np import pandas as pd def IsolationForest_outliers(df): # df是列数据 iso = IsolationForest(behaviour='new', random_state=1, contamination='auto') preds = iso.fit_predict(df.values.reshape(-1,1)) return preds
1.8 GRUBBS TEST
Grubbs’ test 是一个假设检验方法。
- 原假设 H 0 H_0 H0:数据集中无异常值。
- 备选假设
H
1
H_1
H1:数据集中有一个异常值。
其统计量被定义为:
G c a l c u l a t e d = m a x ( ∣ X i − X ˉ ∣ ) S D G_{calculated}=\frac{max(|X_i - \bar{X}|)}{SD} Gcalculated=SDmax(∣Xi−Xˉ∣)
其中 X ˉ \bar{X} Xˉ 和 SD 分别代表样本均值和样本标准差。其临界值被定义为:
G c i t i c a l = N − 1 N ( t α / ( 2 N ) , N − 2 ) 2 N − 2 + ( t α / ( 2 N ) , N − 2 ) 2 G_{citical}=\frac{N-1}{\sqrt{N}} \sqrt{\frac{(t_{\alpha/(2N),N-2})^{2}}{N-2+(t_{\alpha/(2N),N-2})^{2}}} Gcitical=N N−1N−2+(tα/(2N),N−2)2(tα/(2N),N−2)2
如果估计值 G c a l c u l a t e d G_{calculated} Gcalculated > 临界值 G c r i t i c a l G_{critical} Gcritical,拒绝原假设,备选假设成立,即数据集中有一个值是异常值。
一下是演示代码:
import numpy as np import scipy.stats as stats def grubbs_test(x): n = len(x) mean_x = np.mean(x) std_x = np.std(x) numerator = max(abs(x-mean_x)) g_calculated = numerator / std_x print("Grubbs Calculated Value:", g_calculated) t_value = stats.t.ppf(1 - 0.05 / (2 * n), n - 2) g_critical = ((n - 1) * np.sqrt(np.square(t_value))) / (np.sqrt(n) * np.sqrt(n - 2 + np.square(t_value))) print("Grubbs Critical Value:", g_critical) if g_critical > g_calculated: print("From grubbs_test we observe that calculated value is lesser than critical value, Accept null hypothesis and conclude that there is no outliers\n") else: print("From grubbs_test we observe that calculated value is greater than critical value, Reject null hypothesis and conclude that there is an outliers\n")
1.9 可视化数据(Visualizing the data)
数据可视化对于数据清理、数据挖掘、异常值和异常组的检测、趋势和集群识别等都很有用。下面是用于发现异常值的数据可视化图列表:
- Box and whisker plot (box plot) 箱线图
- Scatter plot 散点图
- Histogram 直方图
- Distribution Plot. 分布图
- QQ plot Q-Q图
import pandas as pd import seaborn as sns from matplotlib import pyplot as plt from statsmodels.graphics.gofplots import qqplot def Box_plots(s): plt.figure(figsize=(10,4)) plt.title("Box Plot") sns.boxplot(s) plt.show() def hist_plots(s): plt.figure(figsize=(10,4)) plt.hist(s) plt.title("Histogram Plot") plt.show() def scatter_plots(s1, s2): fig, ax = plt.subplots(figsize=(10,4)) ax.scatter(s1, s2) ax.set_xlabel('Age') ax.set_ylabel('Fare') plt.title("Scatter Plot") plt.show() def dist_plots(s): plt.figure(figsize=(10,4)) sns.distplot(s) plt.title("Distribution plot") sns.despine() plt.show() def qq_plots(df): plt.figure(figsize=(10,4)) qqplot(s, line='s') plt.title("Normal QQPlot") plt.show()
二、异常值的处理
异常值的存在对数据集的影响有以下方面:
- 离群值严重影响数据集的均值和标准差。这些可能在统计上给出错误的结果。
- 它增加了误差方差,降低了统计检验的力量。
- 如果异常值是非随机分布的,它们会降低正态性。
- 大多数机器学习算法在异常值存在时不能很好地工作。因此,检测和去除异常值是很有必要的。
- 它们还会影响回归、方差分析和其他统计模型假设的基本假设。
所以当我们检测到数据中存在异常值时必须做适当的处理才会使得数据挖掘工作更加准确,一般的,异常值的处理方法有:删除值、改变值、插补法和分组处理。
2.1 删除异常值
直接将含有异常值的记录删除,通常有两种策略:整条删除和成对删除。这种方法最简单简单易行,但缺点也不容忽视,一是,在观测值很少的情况下,这种删除操作会造成样本量不足;二是,直接删除、可能会对变量的原有分布造成影响,从而导致统计模型不稳定。
# 删除异常值(使用3σ原则) df_drop_outlier = df_dropna[~mark].reset_index(drop=True) # 剔除异常值后的箱型图 import matplotlib.pyplot as plt plt.boxplot(df_drop_outlier.price) plt.show()
2.2 数据变换
转换变量也可以消除异常值。这些转换后的值减少了由极值引起的变化。转换方法通常有:
- 范围缩放:Scalling
- 对数变换:Log Transformation
- 立方根归一化:Cube Root Transformation
- Box-Cox转换:Box-Cox Transformation
这些技术将数据集中的值转换为更小的值,而且不会丢失数据。如果数据有很多极端值或倾斜,数据变换有助于使您的数据正常。但是这些技巧并不总是给你最好的结果。在所有这些方法中,box-cox变换给出了最好的结果。以下代码仅仅作为演示:
# Scalling from sklearn import preprocessing scaler = preprocessing.StandardScaler() result = scaler.fit_transform(df_dropna.price.values.reshape(-1,1)) # Log transformation import numpy as np result = np.log(df_dropna.price.values) # Cube root Transformation import numpy as np result = np.cbrt(df_dropna.price.values) # Box-Cox Transformation import scipy result, maxlog = scipy.stats.boxcox(df_dropna.price.values ,lmbda=None)
2.3 统一值插补法
像缺失值的归责(imputation)一样,我们也可以归责异常值。在这种方法中,我们可以使用 平均值、中位数、零值 来对异常值进行替换。由于我们进行了输入,所以没有丢失数据。应选则合适的替换,这里提及一下,选择中值不受异常值的影响。以下代码仅仅作为演示。
# 均值替换异常值 _mean = df_dropna.price.mean() df_dropna[mark] = _mean # 中位数替换异常值 _median = df_dropna.price.median() df_dropna[mark] = _median # 0替换异常值 df_dropna[mark] = 0
2.4 回归插补法
发现两个相关的变量之间的变化模式,通过使数据拟合一个函数来平滑数据,也就是回归插补。
若是变量之间存在依赖关系,也就是 y = f ( x ) y=f(x) y=f(x),那么就可以设法求出依赖关系 f f f,再根据x来预测 y y y,这也是回归问题的实质。
实际问题中更常为见的假设是 p ( y ) = N ( f ( x ) ) p(y)=N(f(x)) p(y)=N(f(x)), N N N 为正态分布。假设 y y y 是观测值并且存在噪声数据,根据我们求出的 x x x 和 y y y 之间的依赖关系,再根据 x x x 来更新 y y y 的值,这样就能去除其中的随机噪声,这就是回归去噪的原理 。
2.5 盖帽法
也就是截尾处理,这其实也是一种插补方法。整行替换数据框里百分位数处于99%以上和1%以下的点:将99%以上的点值 = 99%的点值;小于1%的点值 = 1%的点值。
# 盖帽法处理异常值 up = df_dropna.price.quantile(0.99) low = df_dropna.price.quantile(0.01) df_dropna.loc[df_dropna.price > up, 'price'] = up df_dropna.loc[df_dropna.price
2.6 不处理
剔除和替换异常值或多或至少会对数据有负面影响,我们也可以根据该异常值的性质特点,使用更加稳健模型来修饰,然后直接在原数据集上进行数据挖掘。
参考链接
1.Outlier!!! The Silent Killer
2.数据清洗之异常值处理的常用方法
3.异常数据处理——3σ原则、箱线图
4.3sigma原则剔除异常值(极端值)
5.Python检验样本是否服从正态分布
6.Python pandas库和stats库计算偏度和峰度(附程序)
7.数据预处理——3sigma原则离群值处理
8.能力验证中稳健Z比分数的计算方法
9.六种常见变量标准化方法的优缺点和适用范围