异常值的检测与处理(附python代码及实例)
异常值的检查与处理
异常值概述
异常值简介
异常值,也叫离群值(outlier),是指在数值上与其余数据有距离的观测值,或者简单地说,它是超出范围的值。
举个例子。
无异常值的数据 | 有异常值的数据 | |
---|---|---|
数据 | 1,2,3,3,4,5,4 | 1,2,3,3,4,5,400 |
Mean | 3.142 | 59.714 |
Median | 3 | 3 |
SD | 1.345 | 150.057 |
可以看到,具有异常值的数据集具有显著不同的平均值和标准差。平均值由3.14直接飙升至59.71,标准差也由1.345飙升到150.057,这些会对数据总体的估计产生巨大的影响。
再举一个真实的例子。一家拥有50名员工的公司中,45人的月薪为6000元,5名高级员工的月薪为10万元。如果你计算公司员工的平均月薪是14500元,这将给你一个错误的结论(大多数员工的工资低于145000元)。但如果你看工资中位数,它是6000元,这比平均水平更合理。因此,中位数比平均值更合适。这里可以看到异常值的影响。(下图非常非常贴切,Mean和Median本来很接近,但Outlier的出现,让Mean‘变心’了。)
导致异常值的原因
- 数据输入错误
人为错误(例如在数据收集、记录或输入期间引起的错误)可能导致数据中的异常值。 - 测量误差
这是异常值最常见的来源。这类异常是由于所使用的测量仪器出现故障造成的。 - 自然的异常值:
当一个异常值不是人为造成的(由于错误),它就是一个自然异常值。大多数真实世界的数据都属于这一类
异常值检查
有很多种检测异常值的方法:1.假设检验、2.z分数法(Z-score method)、3.稳健z分数(Robust Z-score)、4.四分位距法(IQR METHOD)、5.截尾处理(Winsorization method(Percentile Capping))、6.DBSCAN聚类(DBSCAN Clustering)、7.孤立森林(Isolation Forest)、8.可视化数据(Visualizing the data)。
【代码示例使用到的数据集链接】:
house-prices-advanced-regression-techniques数据集: https://www.kaggle.com/competitions/house-prices-advanced-regression-techniques/data
titanic数据集:https://www.kaggle.com/competitions/titanic/data
Cost of living数据集:https://www.kaggle.com/datasets/andytran11996/cost-of-living
1.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
=
max
∣
X
i
−
X
‾
∣
S
D
G_{calculated} = \frac{\max|X_{i} - \overline{X}|}{SD}
Gcalculated=SDmax∣Xi−X∣
其中
X
‾
\overline{X}
X和
S
D
SD
SD分别代表样本均值和样本标准差。
G
c
r
i
t
i
c
a
l
=
(
N
−
1
)
N
(
t
α
/
(
2
N
)
,
N
−
2
)
2
N
−
2
+
(
t
α
/
(
2
N
)
,
N
−
2
)
2
G_{critical} = \frac{(N-1)}{\sqrt{N}} \sqrt{\frac{(t_{\alpha /(2N),N-2})^{2}}{N-2+(t_{\alpha /(2N),N-2})^{2}}}
Gcritical=N(N−1)N−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
x = np.array([12,13,14,19,21,23])
y = np.array([12,13,14,19,21,23,45])
def grubbs_test(x):
n = len(x)
mean_x = np.mean(x)
sd_x = np.std(x)
numerator = max(abs(x-mean_x))
g_calculated = numerator/sd_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("从Grubbs_test中我们观察到计算值小于临界值,接受零假设,得出结论:不存在异常值")
else:
print("从Grubbs_test中我们观察到计算值大于临界值,拒绝零假设,得出结论:存在一个异常值")
grubbs_test(x)
grubbs_test(y)
# 输出结果:
# Grubbs Calculated Value: 1.4274928542926593
# Grubbs Critical Value: 1.887145117792422
# 从Grubbs_test中我们观察到计算值小于临界值,接受零假设,得出结论:不存在异常值
# Grubbs Calculated Value: 2.2765147221587774
# Grubbs Critical Value: 2.019968507680656
# 从Grubbs_test中我们观察到计算值大于临界值,拒绝零假设,得出结论:存在一个异常值
2.Z分数法(Z-score method)
使用Z分数法,我们可以找出距离平均值有多少个标准差值
上图为正态曲线下面积及标准差所占面积。
- 68%的数据点位于 +1 或 -1个标准差之间
- 95%的数据点位于 +2 或 -2个标准差之间
- 99.7%的数据点位于 +3 或 -3个标准差之间
z-score公式:
Z
s
c
o
r
e
=
X
−
M
e
a
n
S
D
Zscore = \frac{X-Mean}{SD}
Zscore=SDX−Mean
如果一个数据点的Z分数大于3(因为
±
3
σ
\pm 3 \sigma
±3σ覆盖了99.7%的面积),则表明该数据值与其他值有较大差异,视为异常值。
import pandas as pd
import numpy as np
train = pd.read_csv('../house-prices-advanced-regression-techniques/train.csv')
out=[]
def Zscore_outlier(df):
m = np.mean(df)
sd = np.std(df)
for i in df:
z = (i-m)/sd
if np.abs(z) > 3:
out.append(i)
print("Outliers:",out)
Zscore_outlier(train['LotArea'])
# 输出:
# Outliers: [50271, 159000, 215245, 164660, 53107, 70761, 53227, 46589, 115149, 53504, 45600, 63887, 57200]
3.稳健z分数(Robust Z-score)
也被称为中位数绝对偏差法。它类似于Z-score方法,只是参数有所变化。由于平均值和标准差受异常值的影响很大,因此我们使用中位数和中位数的绝对偏差来改变这个参数。
稳健z分数公式:
R
.
Z
.
s
c
o
r
e
=
0.675
∗
(
X
i
−
M
e
d
i
a
n
)
M
A
D
R.Z.score = \frac{0.675 * (X_i - Median)}{MAD}
R.Z.score=MAD0.675∗(Xi−Median)
其中
M
A
D
=
m
e
d
i
a
n
(
∣
X
i
−
M
e
d
i
a
n
∣
)
MAD=median(| X_{i}-Median|)
MAD=median(∣Xi−Median∣)
假设x服从标准正态分布。MAD会收敛于半正态分布的中位数,也就是正态分布的75%百分位,并且N(0.75)≃0.6745。
import pandas as pd
import numpy as np
train = pd.read_csv('../house-prices-advanced-regression-techniques/train.csv')
out=[]
def ZRscore_outlier(df):
med = np.median(df)
ma = stats.median_absolute_deviation(df)
for i in df:
z = (0.6745*(i-med))/ (np.median(ma))
if np.abs(z) > 3:
out.append(i)
print("Outliers:",out)
ZRscore_outlier(train['LotArea'])
# output:
# Outliers: [50271, 31770, 22950, 25419, 159000, 39104, 215245, 164660, 53107, 34650, 70761, 53227, 40094, 32668, 25095, 46589, 26178, 115149, 53504, 28698, 45600, 25286, 27650, 24090, 25000, 29959, 23257, 35760, 35133, 32463, 24682, 23595, 36500, 63887, 25339, 57200, 26142]
4.四分位距法(IQR METHOD)
在这种方法中,我们使用四分位范围(IQR)来检测异常值。IQR告诉我们数据集的变化。任何超出-1.5 x IQR到1.5 x IQR范围的值都被视为异常值。
- Q1 代表数据的下四分位数
- Q2 代表数据的中位数
- Q3 代表数据的上四分位数
- (Q1–1.5 *IQR) 代表数据的最小值的下界,(Q3+1.5 *IQR)代表数据的最大值的上界
import pandas as pd
import numpy as np
train = pd.read_csv('../house-prices-advanced-regression-techniques/train.csv')
out=[]
def iqr_outliers(df):
q1 = df.quantile(0.25)
q3 = df.quantile(0.75)
iqr = q3-q1
Lower_tail = q1 - 1.5 * iqr
Upper_tail = q3 + 1.5 * iqr
for i in df:
if i > Upper_tail or i < Lower_tail:
out.append(i)
print("Outliers:",out)
iqr_outliers(train['LotArea'])
# output:
# Outliers: [50271, 19900, 21000, 21453, 19378, 31770, 22950, 25419, 159000, 19296, 39104, 19138, 18386, 215245, 164660, 20431, 18800, 53107, 34650, 22420, 21750, 70761, 53227, 40094, 32668, 21872, 21780, 25095, 46589, 20896, 18450, 21535, 26178, 115149, 21695, 53504, 21384, 28698, 45600, 17920, 25286, 27650, 24090, 25000, 1300, 21286, 1477, 21750, 29959, 18000, 23257, 17755, 35760, 18030, 35133, 32463, 18890, 24682, 23595, 17871, 36500, 63887, 20781, 25339, 57200, 20544, 19690, 21930, 26142]
5.截尾处理(Winsorization method(Percentile Capping))
该方法类似于IQR法。如果一个值超过了第99个百分位数的值,并且低于给定值的第1个百分位数,则被视为异常值。
import pandas as pd
import numpy as np
train = pd.read_csv('../input/titanic/train.csv')
out=[]
def Winsorization_outliers(df):
q1 = np.percentile(df , 1)
q3 = np.percentile(df , 99)
for i in df:
if i > q3 or i < q1:
out.append(i)
print("Outliers:",out)
Winsorization_outliers(train['Fare'])
6.DBSCAN聚类(DBSCAN Clustering)
DBSCAN是一种基于密度的聚类算法,它将数据集划分为高密度区域的子组,并将稀疏区域聚类识别为异常值。DBSCAN在多元离群值检测中具有最佳的检测效果。
DBSCAN需要两个参数:
1.epsilon: 一个距离参数,定义搜索附近邻居的半径。
2.k:形成集群所需的最小点数。
核心点 —— 在其半径内至少有最小数量的其他点(minPts)的点。
边界点 —— 一个点在核心点的半径内,但小于其自身半径内其他点(minPts)的最小数量。
噪声点 —— 既不是核心点也不是边界点的点
import pandas as pd
from sklearn.cluster import DBSCAN
train = pd.read_csv('../input/titanic/train.csv')
def DB_outliers(df):
outlier_detection = DBSCAN(eps = 2, metric='euclidean', min_samples = 5)
clusters = outlier_detection.fit_predict(df.values.reshape(-1,1))
data = pd.DataFrame()
data['cluster'] = clusters
print(data['cluster'].value_counts().sort_values(ascending=False))
DB_outliers(train['Fare'])
# output:
# 0 705
# 2 50
# 4 36
#-1 32
# 6 15
# 1 12
# 7 8
# 5 7
# 8 7
# 9 7
# 3 6
# 10 6
# Name: cluster, dtype: int64
7.孤立森林(Isolation Forest)
它是一种聚类算法,属于集成决策树家族,在原理上类似于随机森林。
- 它将数据点分类为异常值和非异常值,并适用于非常高维的数据。
- 该方法基于决策树,分离出异常值。
- 如果结果是-1,这意味着这个特定的数据点是一个异常值。如果结果为1,则意味着该数据点不是异常值。
from sklearn.ensemble import IsolationForest
import numpy as np
import pandas as pd
train = pd.read_csv('../input/titanic/train.csv')
train['Fare'].fillna(train[train.Pclass==3]['Fare'].median(),inplace=True)
def Iso_outliers(df):
iso = IsolationForest( behaviour = 'new', random_state = 1, contamination= 'auto')
preds = iso.fit_predict(df.values.reshape(-1,1))
data = pd.DataFrame()
data['cluster'] = preds
print(data['cluster'].value_counts().sort_values(ascending=False))
Iso_outliers(train['Fare'])
# output
# /opt/conda/lib/python3.7/site-packages/sklearn/ensemble/_iforest.py:255: FutureWarning: 'behaviour' is deprecated in 0.22 and will be removed in 0.24. You should not pass or set this parameter.
# FutureWarning
# 1 706
# -1 185
# Name: cluster, dtype: int64
8.可视化数据(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
train = pd.read_csv('../input/titanic/train.csv')
def Box_plots(df):
plt.figure(figsize=(10, 4))
plt.title("Box Plot")
sns.boxplot(df)
plt.show()
Box_plots(train['Age'])
def hist_plots(df):
plt.figure(figsize=(10, 4))
plt.hist(df)
plt.title("Histogram Plot")
plt.show()
hist_plots(train['Age'])
def scatter_plots(df1,df2):
fig, ax = plt.subplots(figsize=(10,4))
ax.scatter(df1,df2)
ax.set_xlabel('Age')
ax.set_ylabel('Fare')
plt.title("Scatter Plot")
plt.show()
scatter_plots(train['Age'],train['Fare'])
def dist_plots(df):
plt.figure(figsize=(10, 4))
sns.distplot(df)
plt.title("Distribution plot")
sns.despine()
plt.show()
dist_plots(train['Fare'])
def qq_plots(df):
plt.figure(figsize=(10, 4))
qqplot(df,line='s')
plt.title("Normal QQPlot")
plt.show()
qq_plots(train['Fare'])
异常值处理
在检测到异常值后,我们应该删除/处理异常值,因为它是一个沉默的杀手 。
- 离群值严重影响数据集的均值和标准差。这些可能在统计上给出错误的结果。
- 它增加了误差方差,降低了统计检验的力量。
- 如果异常值是非随机分布的,它们会降低正态性。
- 大多数机器学习算法在异常值存在时不能很好地工作。因此,检测和去除异常值是很有必要的。
- 它们还会影响回归、方差分析和其他统计模型假设的基本假设。
常用的异常值处理方法有:删除值、改变值、插补法和分组处理。
1.删除异常值
如果由于数据输入错误、数据处理错误或异常值观测值非常小,我们会删除异常值。我们还可以在两端使用修剪来去除异常值。但是当数据集很小的时候,删除观测结果并不是一个好主意。
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
train = pd.read_csv('../input/cost-of-living/cost-of-living-2018.csv')
sns.boxplot(train['Cost of Living Index'])
plt.title("Box Plot before outlier removing")
plt.show()
def drop_outliers(df, field_name):
iqr = 1.5 * (np.percentile(df[field_name], 75) - np.percentile(df[field_name], 25))
df.drop(df[df[field_name] > (iqr + np.percentile(df[field_name], 75))].index, inplace=True)
df.drop(df[df[field_name] < (np.percentile(df[field_name], 25) - iqr)].index, inplace=True)
drop_outliers(train, 'Cost of Living Index')
sns.boxplot(train['Cost of Living Index'])
plt.title("Box Plot after outlier removing")
plt.show()
2.转换值
转换变量也可以消除异常值。这些转换后的值减少了由极值引起的变化。
- 范围缩放
- 对数变换
- 立方根归一化
- Box-Cox转换
这些技术将数据集中的值转换为更小的值。如果数据有很多极端值或倾斜,此方法有助于使您的数据正常。但是这些技巧并不总是给你最好的结果。从这些方法中不会丢失数据。在所有这些方法中,box-cox变换给出了最好的结果。
#Scalling
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
from sklearn import preprocessing
train = pd.read_csv('../input/cost-of-living/cost-of-living-2018.csv')
plt.hist(train['Cost of Living Index'])
plt.title("Histogram before Scalling")
plt.show()
scaler = preprocessing.StandardScaler()
train['Cost of Living Index'] = scaler.fit_transform(train['Cost of Living Index'].values.reshape(-1,1))
plt.hist(train['Cost of Living Index'])
plt.title("Histogram after Scalling")
plt.show()
#Log Transformation
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
train = pd.read_csv('../input/cost-of-living/cost-of-living-2018.csv')
sns.distplot(train['Cost of Living Index'])
plt.title("Distribution plot before Log transformation")
sns.despine()
plt.show()
train['Cost of Living Index'] = np.log(train['Cost of Living Index'])
sns.distplot(train['Cost of Living Index'])
plt.title("Distribution plot after Log transformation")
sns.despine()
plt.show()
#cube root Transformation
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
train = pd.read_csv('../input/titanic/train.csv')
plt.hist(train['Age'])
plt.title("Histogram before cube root Transformation")
plt.show()
train['Age'] = (train['Age']**(1/3))
plt.hist(train['Age'])
plt.title("Histogram after cube root Transformation")
plt.show()
#Box-transformation
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import scipy
train = pd.read_csv('../input/cost-of-living/cost-of-living-2018.csv')
sns.boxplot(train['Rent Index'])
plt.title("Box Plot before outlier removing")
plt.show()
train['Rent Index'],fitted_lambda= scipy.stats.boxcox(train['Rent Index'] ,lmbda=None)
sns.boxplot(train['Rent Index'])
plt.title("Box Plot after outlier removing")
plt.show()
3.插补法
像缺失值的归责一样,我们也可以归责异常值。在这种方法中,我们可以使用平均值、中位数、零值。由于我们进行了输入,所以没有丢失数据。这里的中值是合适的,因为它不受异常值的影响。
#mean imputation
import pandas as pd
import numpy as np
train = pd.read_csv('../input/titanic/train.csv')
sns.boxplot(train['Age'])
plt.title("Box Plot before mean imputation")
plt.show()
q1 = train['Age'].quantile(0.25)
q3 = train['Age'].quantile(0.75)
iqr = q3-q1
Lower_tail = q1 - 1.5 * iqr
Upper_tail = q3 + 1.5 * iqr
m = np.mean(train['Age'])
for i in train['Age']:
if i > Upper_tail or i < Lower_tail:
train['Age'] = train['Age'].replace(i, m)
sns.boxplot(train['Age'])
plt.title("Box Plot after mean imputation")
plt.show()
#median imputation
import pandas as pd
import numpy as np
train = pd.read_csv('../input/titanic/train.csv')
sns.boxplot(train['Age'])
plt.title("Box Plot before median imputation")
plt.show()
q1 = train['Age'].quantile(0.25)
q3 = train['Age'].quantile(0.75)
iqr = q3-q1
Lower_tail = q1 - 1.5 * iqr
Upper_tail = q3 + 1.5 * iqr
med = np.median(train['Age'])
for i in train['Age']:
if i > Upper_tail or i < Lower_tail:
train['Age'] = train['Age'].replace(i, med)
sns.boxplot(train['Age'])
plt.title("Box Plot after median imputation")
plt.show()
#Zero value imputation
import pandas as pd
import numpy as np
train = pd.read_csv('../input/titanic/train.csv')
sns.boxplot(train['Age'])
plt.title("Box Plot before Zero value imputation")
plt.show()
q1 = train['Age'].quantile(0.25)
q3 = train['Age'].quantile(0.75)
iqr = q3-q1
Lower_tail = q1 - 1.5 * iqr
Upper_tail = q3 + 1.5 * iqr
for i in train['Age']:
if i > Upper_tail or i < Lower_tail:
train['Age'] = train['Age'].replace(i, 0)
sns.boxplot(train['Age'])
plt.title("Box Plot after Zero value imputation")
plt.show()
4.分组处理
在统计模型中,如果离群值较多且数据集较小,则应将它们分开处理。其中一种方法是将两个组视为两个不同的组,为两个组建立单独的模型,然后结合输出。但是,当数据集很大时,这种技术是繁琐的。
搬运自kaggle讨论区,很好的一篇关于数值型数据异常值的检测与处理总结。
https://www.kaggle.com/code/nareshbhat/outlier-the-silent-killer/notebook
更多推荐
所有评论(0)