【Python实战】单变量异常值检测

异常值检测是数据预处理阶段重要的环节,这篇文章介绍下对于单变量异常值检测的常用方法,通过Python代码实现。

一、什么是异常值

  异常值是在数据集中与其他观察值有很大差距的数据点,它的存在,会对随后的计算结果产生不适当的影响,因此检测异常值并加以适当的处理是十分必要的。

二、异常值的处理

  异常值并不都是坏的,了解这一点非常重要。只是简单地从数据中删除异常值,而不考虑它们如何影响结果的话,可能会导致灾难。

“异常值不一定是坏事。这些只是与其他模式不一致的观察。但事实上异常值非常有趣。例如,如果在生物实验中,某只老鼠没有死亡而其他老鼠都死了,去了解为什么将会非常有趣。这可能会带来新的科学发现。因此,检测异常值非常重要。” —— Pierre Lafaye de Micheaux,统计师

  对于异常值,一般有如下几种处理:

  • 删除含有异常值的记录(是否删除根据实际情况考虑)
  • 将异常值视为缺失值,利用缺失值的处理方法进行处理
  • 平均值修正(前后两个观测值的平均值)
  • 不处理(直接在具有异常值的数据集上进行挖掘)

三、异常值的类型

  异常值有两种类型:单变量多变量(Univariate and Multivariate)。单变量异常值是仅由一个变量中的极值组成的数据点,而多变量异常值是至少两个变量的组合异常分数。假设您有三个不同的变量 - X,Y,Z。如果您在三维空间中绘制这些变量的图形,它们应该形成一种云。位于此云之外的所有数据点都将是多变量异常值。

  举个例子:做客户分析,发现客户的年平均收入是80万美元。但是,有两个客户的年收入是4美元和420万美元。这两个客户的年收入明显不同于其他人,那这两个观察结果将被视为异常值,并且是单变量异常值,当我们看到单变量的分布时,可以找到这些异常值。

  再举个例子:身高和体重之间的关系。我们对“身高”和“体重”有单变量和双变量分布,如下图所示。
height_weight_outliers.png
  看箱线图(box plot后面会介绍)没有任何异常值,再看散点图(scatter plot),有两个值在一个特定的身高和体重的平均值以下。可见多变量异常值是n维空间中的异常值,必须通过多维度的分布才能体现出来。

  如果对异常值不太了解,可以阅读这篇《数据探索指南》,上述部分解释也是从中摘录的。
  下面,我主要记录下单变量异常值检测的Python实现

四、常用异常检测方法

  原则上模拟数据集需要样本量足够大,这里仅是演示算法,所以就手动写了有限的样本。
  异常值的测量标准有很多,比较常见的是描述性统计法、三西格玛法(3σ法)、箱线图等:

1. 描述性统计

  基于常识或经验,假定我们认为大于10的数值是不符合常理的。

  下面用Python代码实现用描述性统计求异常值:

# -*- coding: utf-8 -*-

data = [2.78, 1.79, 4.73, 3.81, 2.78, 1.80, 4.81, 2.79, 1.78, 3.32, 10.8, 100.0]
threshold = 10


# 定义描述性统计识别异常值函数
def descriptive_statistics(data):
    return list(filter(lambda x: x > threshold, data))


outliers = descriptive_statistics(data)
print('异常值共有:{0}个,分别是:{1}'.format(len(outliers), outliers))

# 输出:异常值共有:2个,分别是:[10.8, 100.0]

2. 三西格玛(3σ)

  当数据服从正态分布时,99%的数值应该位于距离均值3个标准差之内的距离,P(|x−μ|>3σ)≤0.003,当数值超出这个距离,可以认为它是异常值。
  正态分布状况下,数值分布表:

数值分布 在数据中的占比
(μ-σ,μ+σ) 0.6827
(μ-2σ,μ+2σ) 0.9545
(μ-3σ,μ+3σ) 0.9973

注:在正态分布中σ代表标准差,μ代表均值,x=μ为图形的对称轴

  下面用Python代码实现用三西格玛求异常值:

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np

data = [2.78, 1.79, 4.73, 3.81, 2.78, 1.80, 4.81, 2.79, 1.78, 3.32, 10.8, 100.0]


# 定义3σ法则识别异常值函数
def three_sigma(data_series):
    rule = (data_series.mean() - 3 * data_series.std() > data_series) | (data_series.mean() + 3 * data_series.std() < data_series)
    index = np.arange(data_series.shape[0])[rule]
    outliers = data_series.iloc[index]
    return outliers.tolist()


data_series = pd.Series(data)
outliers = three_sigma(data_series)
print('异常值共有:{0}个,分别是:{1}'.format(len(outliers), outliers))

# 输出:异常值共有:1个,分别是:[100.0]

3. 箱线图(box plot)

  和3σ原则相比,箱线图依据实际数据绘制,真实、直观地表现出了数据分布的本来面貌,且没有对数据作任何限制性要求(3σ原则要求数据服从正态分布或近似服从正态分布)。
  其判断异常值的标准以四分位数和四分位距为基础。四分位数给出了数据分布的中心、散布和形状的某种指示,具有一定的鲁棒性,即25%的数据可以变得任意远而不会很大地扰动四分位数,所以异常值通常不能对这个标准施加影响。鉴于此,箱线图识别异常值的结果比较客观,因此在识别异常值方面具有一定的优越性。
  箱线图提供了识别异常值的一个标准,即:
  上界 = Q3 + 1.5IQR
  下界 = Q1 - 1.5IQR
  小于下界或大于上界的值即为异常值。
  其中,
  Q3称为上四分位数(75%),表示全部观察值中只有四分之一的数据取值比它大;
  Q1称为下四分位数(25%),表示全部观察值中只有四分之一的数据取值比它小;
  IQR称为四分位数差,这里就是 Q3-Q1;
  1.5其实是个参数λ,这个参数通常取1.5(类似于正态分布中的μ±λ)

  文字描述可能比较绕,下面用图片来解释下。
box_plot.png

第一四分位数 (Q1),又称“较小四分位数”,等于该样本中所有数值由小到大排列后第25%的数字。
第二四分位数 (Q2),又称“中位数”,等于该样本中所有数值由小到大排列后第50%的数字。
第三四分位数 (Q3),又称“较大四分位数”,等于该样本中所有数值由小到大排列后第75%的数字。
Q3与Q1的差距又称四分位距(InterQuartile Range,IQR)。

  四分位数的计算参见四分位数四分位数的计算

  下面用Python代码实现用箱线图求异常值:

# -*- coding: utf-8 -*-

import pandas as pd

data = [2.78, 1.79, 4.73, 3.81, 2.78, 1.80, 4.81, 2.79, 1.78, 3.32, 10.8, 100.0]


# 定义箱线图识别异常值函数
def box_plot(data_series):
    q_abnormal_low = data_series.quantile(0.25) - 1.5 * (data_series.quantile(0.75) - data_series.quantile(0.25))
    q_abnormal_up = data_series.quantile(0.75) + 1.5 * (data_series.quantile(0.75) - data_series.quantile(0.25))
    index = (data_series < q_abnormal_low) | (data_series > q_abnormal_up)
    outliers = data_series.loc[index]
    return outliers.tolist()


sorted_data = sorted(data)
data_series = pd.Series(sorted_data)
outliers = box_plot(data_series)
print('异常值共有:{0}个,分别是:{1}'.format(len(outliers), outliers))


# 输出:异常值共有:2个,分别是:[10.8, 100.0]

五、总结

  以上是最基础的几种单变量异常值检测方法,没有最好的,只有对当前数据场景最合适的。
  后期如果涉及机器学习的数据预处理,我会继续学习和研究多变量异常值的检测,相信会有更有意思的一些算法等着我去学习。


参考
异常检测的N种方法,阿里工程师都盘出来了:https://mp.weixin.qq.com/s/w7SbAHxZsmHqFtTG8ZAXNg
数据探索指南:https://www.analyticsvidhya.com/blog/2016/01/guide-data-exploration/?utm_source=outlierdetectionpyod&utm_medium=blog
Tukey method:https://en.wikipedia.org/wiki/Tukey%27s_range_test
图基(Tukey)检验:一种值得倍加推崇的检验方法:http://blog.sina.com.cn/s/blog_60be90250100eojy.html
如何从大量数据中找出异常值:https://blog.csdn.net/wangyangzhizhou/article/details/83854951
概率论与数理统计 第四版 浙江大学出版社:https://book.douban.com/subject/3165271/


  目录