离群值处理标准差法MAD法箱形图法图像对比法BOX-COX转换参考文章
又称为拉依达准则(标准差法),适用于有较多组数据的时候。
工作原理:它是先假设一组检测数据只含有随机误差,对其进行计算处理得到标准偏差, 按一定概率确定一个区间,认为凡超过这个区间的误差,就不属于随机误差而是粗大误差, 含有该误差的数据应予以剔除。
标准差本身可以体现因子的离散程度,是基于因子的平均值μ而定的。在离群值处理过程中, 可通过用μ±nσ来衡量因子与平均值的距离
公式:假设有近似服从正态分布离散数据X=[x1,x2,…,xn],其均值μ与标准差σ分别为: ,
如何衡量数值是否为离群值? 将区间 , 的值视为正常值范围,在 , 外的值视为离群值。
from scipy.stats import kstest
def KsNormDetect(df):
# 计算均值
u = df['value'].mean()
# 计算标准差
std = df['value'].std()
# 计算P值
res = kstest(df, 'norm', (u, std))[1]
# 判断p值是否服从正态分布,p<=0.05 则服从正态分布,否则不服从。
if res<=0.05:
print('该列数据服从正态分布------------')
print('均值为:{},标准差为:{}'.format(u, std))
print('------------------------------')
return 1
else:
return 0
def OutlierDetection(df,ks_res):
# 计算均值
u = df['value'].mean()
# 计算标准差
std = df['value'].std()
if ks_res==1:
# 定义3σ法则识别异常值
# 识别异常值
error = df[np.abs(df['value'] - u) > 3 * std]
# 剔除异常值,保留正常的数据
data_c = df[np.abs(df['value'] - u) <= 3 * std]
# 输出异常数据
return(error)
# return data_c
else:
print('请先检测数据是否服从正态分布-----------')
return None
data = [1222, 87, 77, 92, 68, 80, 78, 84, 77, 81, 80, 80, 77, 92, 86, 76, 80, 81, 75, 77, 72, 81, 72, 84, 86, 80,
68, 77, 87, 76, 77, 78, 92, 75, 80, 78, 123, 3, 1223, 1232]
df = pd.DataFrame(data, columns=['value'])
ks_res = KsNormDetect(df)
result = OutlierDetection(df, ks_res)
print(result)
该列数据服从正态分布------------
均值为:164.85,标准差为:306.2894181853296
------------------------------
value
0 1222
38 1223
39 1232
概念:又称为绝对值差中位数法,是一种先需计算所有因子与中位数之间的距离总和来检测离群值的方法,适用大样本数据
公式:设有平稳离散数据X=[x1,x2,…,xn],其数据中位数 ;记 则正常值范围为 , ,在区间 , 外视为离群值
# MAD法
x = np.random.random(100)
number = 50
x = np.r_[x,-60,80,40,100,-100]#在后面添上,相当于padans中merge
plt.figure()
plt.subplot(211)
plt.hist(x,number)
plt.xlabel('raw') #没消除异常的时候
# plt.show()
def c_except(x,thresh=3.5):
'''
使用绝对中位差消除异常
:return:
'''
if len(x)<=1:
return
me = np.median(x)
abs = np.absolute(x-me)
abs_me = np.median(abs)
score = norm.ppf(0.75)*abs/abs_me
return score<thresh
#异常消除后
x_late = x[c_except(x)]
plt.subplot(212)
plt.hist(x_late,number)
plt.show()
MAD 的方法相对于分位数方法的一大优势即在于 MAD 方法对样本大小是不敏感也即是稳定的鲁棒的一种评价指标。
理论部分 概念:箱形图由最小值、下四分位值(25%),中位数(50%),上四分位数值(75%),最大值这5个关键的百分数统计值组成的。
如何通过箱形图判断异常值呢? 假设下四分位值为 ,上四分位数值为 ,四分位距为 (其中 ),推导如下:
异常值截断点如下,截断点就是异常值与正常值的分界点,又称为内限: ,
温和异常值与极端异常值的分界点,又称为外限: ,
import numpy as np
def boxplot(data):
# 下四分位数值、中位数,上四分位数值
Q1, median, Q3 = np.percentile(data, (25, 50, 75), interpolation='midpoint')
# 四分位距
IQR = Q3 - Q1
# 内限
inner = [Q1-1.5*IQR, Q3+1.5*IQR]
# 外限
outer = [Q1-3.0*IQR, Q3+3.0*IQR]
print('>>>内限:', inner)
print('>>>外限:', outer)
# 过滤掉极端异常值
print(len(data))
goodData = []
for value in data:
if (value < outer[1]) and (value > outer[0]):
goodData.append(value)
print(len(goodData))
return goodData
data = [0.2, 0.3, 0.15, 0.32, 1.5, 0.17, 0.28, 4.3, 0.8, 0.43, 0.67]
boxplot(data)
>>>内限: [-0.5025000000000002, 1.4775000000000003]
>>>外限: [-1.2450000000000003, 2.2200000000000006]
11
10
[0.2, 0.3, 0.15, 0.32, 1.5, 0.17, 0.28, 0.8, 0.43, 0.67]
画密度曲线,标出3σ范围
data = pd.Series(np.random.randn(10000)*100)
u = data.mean()
std = data.std()
fig = plt.figure(figsize = (15,9))
ax1 = fig.add_subplot(2,1,1)
data.plot(kind = 'kde',color = 'k',ylim = [0,0.005])
plt.axvline(u-3*std,color = 'r',linestyle = '--')
plt.axvline(u+3*std,color = 'r',linestyle = '--')
<matplotlib.lines.Line2D at 0x20b97072438>
可视化箱线图
#绘制箱型图(以内限为界)
fig = plt.figure(figsize = (15,9))
ax1 = fig.add_subplot(2,1,1)
color = dict(boxes='DarkGreen', whiskers='DarkOrange', medians='DarkBlue', caps='Gray')
data.plot.box(vert=False, grid = True,color = color,ax = ax1,label = 'sample data')
#绘制散点图
st = data.describe()
q1 = st['25%']
q3 = st['75%']
iqr = q3-q1
mi = q1 - 1.5*iqr
ma = q3 + 1.5*iqr
error = data[(data<mi)|(data>ma)]
data_c1 = data[(data>=mi)&(data<=ma)]
ax2 = fig.add_subplot(2,1,2)
plt.scatter(data_c1.index,data_c1.values,color = 'k',alpha = 0.3)
plt.scatter(error.index,error.values,color = 'r',alpha = 0.8)
<matplotlib.collections.PathCollection at 0x20b93ef6080>
概念和工作原理 所谓的图像对比法是通过比较训练集和测试集对应的特征数据在某一区间是否存在较大的差距来判别这一区间的数据是不是属于异常离群值。
优缺点 优点:可以防止训练集得到的模型不适合测试集预测的模型,从而减少二者之间的误差。
应用场景及意义 意义:提高模型的可靠性和稳定性。
功能实现 构造数据,进行实验演示方法原理的应用。
# 功能实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
# 构造一个演示数据
D1 = {'feature1':[1,4,3,4,6.3,6,7,8,8.3,10,9.5,12,11.2,14.5,17.8,15.3,17.3,17,19,18.8],
'feature2':[11,20,38,40,59,61,77,84,99,115,123,134,130,155,138,160,152,160,189,234],
'label':[1,5,9,4,12,6,17,25,19,10,31,11,13,21,15,28,35,24,19,20]}
D2= {'feature1':[1,3,3,6,5,6,7,10,9,10,13,12,16,14,15,16,14,21,19,20],
'feature2':[13,25,33,49,45,66,74,86,92,119,127,21,13,44,34,29,168,174,178,230]}
df_train = pd.DataFrame(data=D1)
df_test = pd.DataFrame(data=D2)
L = [df_train.iloc[:,1], df_test.iloc[:,1], 'train_feature2', 'test_feature2']
fig = plt.figure(figsize=(15,5))
X = list(range(df_train.shape[0]))
for i in range(2):
ax = fig.add_subplot(1,2,i+1)
ax.plot(X, L[i],label=L[i+2],color='red')
ax.legend()
ax.set_xlabel('Section')
结论: 从上面的的图形对比,明显发现在区间 [10,15] 之间训练集 feature2 和测试集 feature2 的数据差距悬殊(严重突变),因此区间 [10,15] 的数据可判定为离群异常值,应在训练集和测试集中同时剔除掉,防止训练集训练的模型不适用于测试集的预测。
如果不进行剔除或其他处理,训练模型在测试集预测会存在巨大的误差。
其中: 在一些情况下(P值<0.003)上述方法很难实现正态化处理,所以优先使用Box-Cox转换,但是当P值>0.003时两种方法均可,优先考虑普通的平方变换。
log,对数转换,是使用最多的(数据必须大于0) 还有: 平方根转换 倒数转换 平方根后取倒数 平方根后再取反正弦 幂转换
使用kaggle里的 Housing Price 竞赛数据进行Box-Cox变换
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
trains=pd.read_csv('train.csv')
tests=pd.read_csv('test.csv')
sns.distplot(trains['SalePrice'])
<matplotlib.axes._subplots.AxesSubplot at 0x20b984a0cf8>
from scipy import stats
from scipy.stats import norm, skew #for some statistics
#查看SalePrice的skewness
fig=plt.figure(figsize=(15,5))
#pic1
plt.subplot(1,2,1)
sns.distplot(trains['SalePrice'],fit=norm)
(mu,sigma)=norm.fit(trains['SalePrice'])
plt.legend(['$\mu=$ {:.2f} and $\sigma=$ {:.2f}'.format(mu,sigma)],loc='best')
plt.ylabel('Frequency')
#pic2
plt.subplot(1,2,2)
res=stats.probplot(trains['SalePrice'],plot=plt)
plt.suptitle('Before')
print(f"Skewness of saleprice: {trains['SalePrice'].skew()}")
print(f"Kurtosis of saleprice: {trains['SalePrice'].kurt()}")
Skewness of saleprice: 1.8828757597682129
Kurtosis of saleprice: 6.536281860064529
#进行Box-Cox变换
#box-cox
trains.SalePrice,lambda_=stats.boxcox(trains.SalePrice)
print(lambda_)
-0.07692391328663316
#再次观察SalePrice
fig=plt.figure(figsize=(15,5))
#pic1
plt.subplot(1,2,1)
sns.distplot(trains['SalePrice'],fit=norm)
(mu,sigma)=norm.fit(trains['SalePrice'])
plt.legend(['$\mu=$ {:.2f} and $\sigma=$ {:.2f}'.format(mu,sigma)],loc='best')
plt.ylabel('Frequency')
#pic2
plt.subplot(1,2,2)
res=stats.probplot(trains['SalePrice'],plot=plt)
plt.suptitle('After')
print(f"Skewness of saleprice: {trains['SalePrice'].skew()}")
print(f"Kurtosis of saleprice: {trains['SalePrice'].kurt()}")
Skewness of saleprice: -0.008652893640830073
Kurtosis of saleprice: 0.8778702059824468
可见变换后的数据更好的满足正态性的假设前提。很可能会对ML模型的学习带来更好的效果。
另一种方法:使用boxcox1p
from scipy.stats import boxcox_normmax
from scipy.special import boxcox1p
lambda_2=boxcox_normmax(trains.SalePrice+1)
print(lambda_2)
trains.SalePrice=boxcox1p(trains.SalePrice,lambda_2)
1.6537136561634427
fig=plt.figure(figsize=(15,5))
#pic1
plt.subplot(1,2,1)
sns.distplot(trains['SalePrice'],fit=norm)
(mu,sigma)=norm.fit(trains['SalePrice'])
plt.legend(['$\mu=$ {:.2f} and $\sigma=$ {:.2f}'.format(mu,sigma)],loc='best')
plt.ylabel('Frequency')
#pic2
plt.subplot(1,2,2)
res=stats.probplot(trains['SalePrice'],plot=plt)
plt.suptitle('After')
print(f"Skewness of saleprice: {trains['SalePrice'].skew()}")
print(f"Kurtosis of saleprice: {trains['SalePrice'].kurt()}")
Skewness of saleprice: 0.041645629894561206
Kurtosis of saleprice: 0.8381798433533953
可见使用boxcox1p()
可使数据的峰度变得更小,但偏度没有boxcox()
的结果小。