前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >特征工程之异常值处理

特征工程之异常值处理

作者头像
用户3577892
发布2020-06-11 09:31:14
发布2020-06-11 09:31:14
2.5K10
代码可运行
举报
文章被收录于专栏:数据科学CLUB数据科学CLUB
运行总次数:0
代码可运行

离群值处理标准差法MAD法箱形图法图像对比法BOX-COX转换参考文章

离群值处理

标准差法

又称为拉依达准则(标准差法),适用于有较多组数据的时候。

工作原理:它是先假设一组检测数据只含有随机误差,对其进行计算处理得到标准偏差, 按一定概率确定一个区间,认为凡超过这个区间的误差,就不属于随机误差而是粗大误差, 含有该误差的数据应予以剔除。

标准差本身可以体现因子的离散程度,是基于因子的平均值μ而定的。在离群值处理过程中, 可通过用μ±nσ来衡量因子与平均值的距离

公式:假设有近似服从正态分布离散数据X=[x1,x2,…,xn],其均值μ与标准差σ分别为: ,

如何衡量数值是否为离群值? 将区间 , 的值视为正常值范围,在 , 外的值视为离群值。

代码语言:javascript
代码运行次数:0
运行
复制
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
代码语言:javascript
代码运行次数:0
运行
复制
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)
代码语言:javascript
代码运行次数:0
运行
复制
该列数据服从正态分布------------
均值为:164.85,标准差为:306.2894181853296
------------------------------
    value
0    1222
38   1223
39   1232

MAD法

概念:又称为绝对值差中位数法,是一种先需计算所有因子与中位数之间的距离总和来检测离群值的方法,适用大样本数据

公式:设有平稳离散数据X=[x1,x2,…,xn],其数据中位数 ;记 则正常值范围为 , ,在区间 , 外视为离群值

代码语言:javascript
代码运行次数:0
运行
复制
# 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个关键的百分数统计值组成的。

如何通过箱形图判断异常值呢? 假设下四分位值为 ,上四分位数值为 ,四分位距为 (其中 ),推导如下:

异常值截断点如下,截断点就是异常值与正常值的分界点,又称为内限: ,

温和异常值与极端异常值的分界点,又称为外限: ,

  1. 温和异常值:在内限与外限之间的值称为温和异常值,也就是说在对数据要求不是很严格的情况下,这类异常值可以当成正常值要处理。
  2. 极端异常值:在外限以外的值称为极端异常值,可考虑直接删除处理或者处理成缺失值再进行填充。
代码语言:javascript
代码运行次数:0
运行
复制
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)
代码语言:javascript
代码运行次数:0
运行
复制
>>>内限: [-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σ范围

代码语言:javascript
代码运行次数:0
运行
复制
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 = '--')
代码语言:javascript
代码运行次数:0
运行
复制
<matplotlib.lines.Line2D at 0x20b97072438>

可视化箱线图

代码语言:javascript
代码运行次数:0
运行
复制
#绘制箱型图(以内限为界)
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)
代码语言:javascript
代码运行次数:0
运行
复制
<matplotlib.collections.PathCollection at 0x20b93ef6080>

图像对比法

概念和工作原理 所谓的图像对比法是通过比较训练集和测试集对应的特征数据在某一区间是否存在较大的差距来判别这一区间的数据是不是属于异常离群值。

优缺点 优点:可以防止训练集得到的模型不适合测试集预测的模型,从而减少二者之间的误差。

应用场景及意义 意义:提高模型的可靠性和稳定性。

功能实现 构造数据,进行实验演示方法原理的应用。

代码语言:javascript
代码运行次数:0
运行
复制
# 功能实现
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] 的数据可判定为离群异常值,应在训练集和测试集中同时剔除掉,防止训练集训练的模型不适用于测试集的预测。

如果不进行剔除或其他处理,训练模型在测试集预测会存在巨大的误差。

BOX-COX转换

  • 优势: Box 和 Cox在1964年提出的Box-Cox变换可使线性回归模型满足线性性、独立性、方差齐性以及正态性的同时,又不丢失信息,此种变换称之为Box—Cox变换。 误差与y相关,不服从正态分布,于是给线性回归的最小二乘估计系数的结果带来误差 使用Box-Cox变换族一般都可以保证将数据进行成功的正态变换,但在二分变量或较少水平的等级变量的情况下,不能成功进行转换,此时,我们可以考虑使用广义线性模型,如LOGUSTICS模型、Johnson转换等。 Box-Cox变换后,残差可以更好的满足正态性、独立性等假设前提,降低了伪回归的概率

其中: 在一些情况下(P值<0.003)上述方法很难实现正态化处理,所以优先使用Box-Cox转换,但是当P值>0.003时两种方法均可,优先考虑普通的平方变换。

  • 此时的检验步骤为:先对数据进行正态性检验 -> 观察检验的P值 -> 根据P值挑选合适的box-cox转换函数
  • 常规的经济学转换方式:

log,对数转换,是使用最多的(数据必须大于0) 还有: 平方根转换 倒数转换 平方根后取倒数 平方根后再取反正弦 幂转换

使用kaggle里的 Housing Price 竞赛数据进行Box-Cox变换

代码语言:javascript
代码运行次数:0
运行
复制
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'])
代码语言:javascript
代码运行次数:0
运行
复制
<matplotlib.axes._subplots.AxesSubplot at 0x20b984a0cf8>
代码语言:javascript
代码运行次数:0
运行
复制
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()}")
代码语言:javascript
代码运行次数:0
运行
复制
Skewness of saleprice: 1.8828757597682129
Kurtosis of saleprice: 6.536281860064529
代码语言:javascript
代码运行次数:0
运行
复制
#进行Box-Cox变换
#box-cox
trains.SalePrice,lambda_=stats.boxcox(trains.SalePrice)
print(lambda_)
代码语言:javascript
代码运行次数:0
运行
复制
-0.07692391328663316
代码语言:javascript
代码运行次数:0
运行
复制
#再次观察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()}")
代码语言:javascript
代码运行次数:0
运行
复制
Skewness of saleprice: -0.008652893640830073
Kurtosis of saleprice: 0.8778702059824468

可见变换后的数据更好的满足正态性的假设前提。很可能会对ML模型的学习带来更好的效果。

另一种方法:使用boxcox1p

代码语言:javascript
代码运行次数:0
运行
复制
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)
代码语言:javascript
代码运行次数:0
运行
复制
1.6537136561634427
代码语言:javascript
代码运行次数:0
运行
复制
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()}")
代码语言:javascript
代码运行次数:0
运行
复制
Skewness of saleprice: 0.041645629894561206
Kurtosis of saleprice: 0.8381798433533953

可见使用boxcox1p()可使数据的峰度变得更小,但偏度没有boxcox()的结果小。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据科学CLUB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 离群值处理
    • 标准差法
    • MAD法
    • 箱形图法
    • 图像对比法
    • BOX-COX转换
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档