对于异常值的检测

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对于异常值的检测相关的知识,希望对你有一定的参考价值。

参考技术A

离群点,是一个数据对象,它显著不同于其他数据对象,与其他数据分布有较为显著的不同。有时也称非离群点为“正常数据”,离群点为“异常数据”。

离群点跟噪声数据不一样,噪声是被观测变量的随机误差或方差。一般而言,噪声在数据分析(包括离群点分析)中不是令人感兴趣的,需要在数据预处理中剔除的,减少对后续模型预估的影响,增加精度。

离群点检测是有意义的,因为怀疑产生它们的分布不同于产生其他数据的分布。因此,在离群点检测时,重要的是搞清楚是哪种外力产生的离群点。

常见的异常成因:

通常,在其余数据上做各种假设,并且证明检测到的离群点显著违反了这些假设。如统计学中的假设检验,基于小概率原理,对原假设进行判断。一般检测离群点,是人工进行筛选,剔除不可信的数据,例如对于房屋数据,面积上万,卧室数量过百等情况。而在面对大量的数据时,人工方法耗时耗力,因此,才有如下的方法进行离群点检测。

统计学方法是基于模型的方法,即为数据创建一个模型,并且根据对象拟合模型的情况来评估它们。大部分用于离群点检测的统计学方法都是构建一个概率分布模型,并考虑对象有多大可能符合该模型。

离群点的概率定义:离群点是一个对象,关于数据的概率分布模型,它具有低概率。这种情况的前提是必须知道数据集服从什么分布,如果估计错误就造成了重尾分布。

a. 参数法:

当数据服从正太分布的假设时在正态分布的假定下,u±3σ区域包含99.7%的数据,u±2σ包含95.4%的数据,u±1σ包含68.3%的数据。其区域外的数据视为离群点。

当数据是非正态分布时,可以使用切比雪夫不等式,它对任何分布形状的数据都适用。根据 切比雪夫不等式 ,至少有(1-1/k 2 )的数据落在±k个标准差之内。所以,有以下结论:

计算得到:通过绘制箱线图可以直观地找到离群点,或者通过计算四分位数极差(IQR)定义为Q3-Q1。比Q1小1.5倍的IQR或者比Q3大1.5倍的IQR的任何对象都视为离群点,因为Q1-1.5IQR和Q3+1.5IQR之间的区域包含了99.3%的对象。

涉及两个或多个属性或变量的数据称为多元数据。核心思想是把多元离群点检测任务转换成一元离群点检测问题。

- 卡方统计量的多元离群点检测 :正态分布的假定下,卡方统计量也可以用来捕获多元离群点,对象 ,卡方统计量是: , 是 在第i维上的值, 是所有对象在第i维上的均值,而n是维度。如果对象的卡方统计量很大,则该对象是离群点。

b. 非参数法:

构造直方图
为了构造一个好的直方图,用户必须指定直方图的类型和其他参数(箱数、等宽or等深)。最简单的方法是,如果该对象落入直方图的一个箱中,则该对象被看做正常的,否则被认为是离群点。也可以使用直方图赋予每个对象一个离群点得分,比如对象的离群点得分为该对象落入的箱的容积的倒数。但这个方法很难选择一个较好的直方图参数。

注意
传统的观点都认为孤立点是一个单独的点,然而很多的现实情况是异常事件具有一定的时间和空间的局部性,这种局部性会产生一个小的簇.这时候离群点(孤立点)实际上是一个小簇(图下图的C1和C3)。

一个对象是异常的,如果它远离大部分点。这种方法比统计学方法更一般、更容易使用,因为确定数据集的有意义的邻近性度量比确定它的统计分布更容易。不依赖统计检验,将基于邻近度的离群点看作是那些没有“足够多“邻居的对象。这里的邻居是用 邻近度(距离) 来定义的。最常用的距离是绝对距离(曼哈顿)和欧氏距离等等。

一个对象的离群点得分由到它的k-最近邻的距离给定。离群点得分对k的取值高度敏感。如果k太小,则少量的邻近离群点可能导致离群点较少;如果K太大,则点数少于k的簇中所有的对象可能都成了离群点,导致离群点过多。为了使该方案对于k的选取更具有鲁棒性,可以使用k个最近邻的平均距离。

从基于密度的观点来说,离群点是在低密度区域中的对象。一个对象的离群点得分是该对象周围密度的逆。基于密度的离群点检测与基于邻近度的离群点检测密切相关,因为密度通常用邻近度定义。

定义密度
一种常用的定义密度的方法是,定义密度为到k个最近邻的平均距离的倒数 。如果该距离小,则密度高,反之亦然。

另一种密度定义是使用DBSCAN聚类算法使用的密度定义,即一个对象周围的密度等于该对象指定距离d内对象的个数。 需要小心的选择d,如果d太小,则许多正常点可能具有低密度,从而离群点较多。如果d太大,则许多离群点可能具有与正常点类似的密度(和离群点得分)无法区分。 使用任何密度定义检测离群点具有与基于邻近度的离群点方案类似的特点和局限性。特殊地,当数据包含不同密度的区域时,它们不能正确的识别离群点。

定义相对密度
为了正确的识别这种数据集中的离群点,我们需要与对象邻域相关的密度概念,也就是定义相对密度。常见的有两种方法:
(1)使用基于SNN密度的聚类算法使用的方法;
(2)用点x的密度与它的最近邻y的平均密度之比作为相对密度。使用相对密度的离群点检测( 局部离群点要素LOF技术 ):

一种利用聚类检测离群点的方法是丢弃远离其他簇的小簇。这个方法可以和其他任何聚类技术一起使用,但是需要最小簇大小和小簇与其他簇之间距离的阈值。这种方案对簇个数的选择高度敏感。使用这个方案很难将离群点得分附加到对象上。

一种更系统的方法,首先聚类所有的点,对某个待测点评估它属于某一簇的程度。(基于原型的聚类可用离中心点的距离来评估,对具有目标函数(例如kmeans法时的簇的误差平方和)的聚类技术,该得分反映删除对象后目标函数的改进),如果删去此点能显著地改善此项目标函数,则可以将该点定位为孤立点。

基于聚类的离群点:一个对象是基于聚类的离群点,如果该对象不强属于任何簇。离群点对初始聚类的影响:如果通过聚类检测离群点,则由于离群点影响聚类,存在一个问题:结构是否有效。为了处理该问题,可以使用如下方法:

对象是否被认为是离群点可能依赖于簇的个数(如k很大时的噪声簇)。该问题也没有简单的答案。一种策略是对于不同的簇个数重复该分析。另一种方法是找出大量小簇,其想法是(1)较小的簇倾向于更加凝聚,(2)如果存在大量小簇时一个对象是离群点,则它多半是一个真正的离群点。不利的一面是一组离群点可能形成小簇而逃避检测。

根据已有训练集检测新样本是否异常

异常检测根据原始数据集的不同可分为两类:
novelty detection: 训练集中没有异常样本
outlier detection: 训练集中有异常样本

异常样本:
数量少,比较分散

novelty detection和outlier detection的区别:

Sklearn异常检测模型一览

5.1 奇异点检测(Novelty Detection)
奇异点检测,就是判断待测样本到底是不是在原来数据的概率分布内。概率学上认为,所有的数据都有它的隐藏的分布模式,这种分布模式可以由概率模型来具象化。

5.1 离群点检测(Outlier Detection)
不同与奇异点检测是,现在我们没有一个干净的训练集(训练集中也有噪声样本)。下面介绍的三种离群点检测算法其实也都可以用于奇异点检测。

如果我们认为,可达密度小的目标样本点就是异常点,这样未尝不可。但是,LOF算法更进一步。

LOF可以用来判断经纬度的异常。

使用python进行异常值(outlier)检测实战:KMeans + PCA + IsolationForest + SVM + EllipticEnvelope

文章引用: 数据挖掘:数据清洗——异常值处理

Pythonic 检测一维观测数据中异常值的方法

【中文标题】Pythonic 检测一维观测数据中异常值的方法【英文标题】:Pythonic way of detecting outliers in one dimensional observation data 【发布时间】:2014-04-16 17:44:47 【问题描述】:

对于给定的数据,我想将异常值(由 95% 置信水平或 95% 分位数函数或任何所需的值定义)设置为 nan 值。以下是我现在正在使用的数据和代码。如果有人能进一步解释我,我会很高兴。

import numpy as np, matplotlib.pyplot as plt

data = np.random.rand(1000)+5.0

plt.plot(data)
plt.xlabel('observation number')
plt.ylabel('recorded value')
plt.show()

【问题讨论】:

您更了解您的数据,但我认为 Winsorising 会比删除更好。此外,如果将这些数据设置为 nan,则必须处理它。看看np.percentile函数。 仅供参考:Detect and exclude outliers in Pandas dataframe 【参考方案1】:

按照@Martin 的建议使用np.percentile

percentiles = np.percentile(data, [2.5, 97.5])

# or =>, <= for within 95%
data[(percentiles[0]<data) & (percentiles[1]>data)]

# set the outliners to np.nan
data[(percentiles[0]>data) | (percentiles[1]<data)] = np.nan

【讨论】:

使用数据的百分位数作为异常值测试是合理的第一步,但并不理想。问题是 1)您将删除一些数据,即使它不是异常值,以及 2)异常值严重影响方差,因此影响百分位值。最常见的异常值测试使用“中值绝对偏差”,它对异常值的存在不太敏感。 @Joe Kington 如果您能使用 python 代码实现您的方式,我将不胜感激。 @Joe Kington 我看到了你回答的链接。但是,没有更简单的方法可以使其主要使用 numpy 中可用的功能 @julie - 该函数广泛使用 numpy(它需要一个 numpy 数组作为输入并输出一个 numpy 数组)。异常值测试远远超出numpy 的范围。 (numpy 本身只包含核心数据结构和一些基本操作。它故意很小。)你可以说scipy.stats 将是一个合理的异常值测试位置,但其中有很多,而且有没有单一的最佳测试。因此,目前没有单功能异常值测试。 Statsmodels 在sm.robust.mad 中有一个中值绝对偏差函数。我不确定是否有用于单变量异常值测试的工具,但在回归框架中有影响/异常值的工具。将看到添加一些用于单变量异常值检测的工具。【参考方案2】:

一个简单的解决方案也可以是,删除超出 2 个标准差(或 1.96)的东西:

import random
def outliers(tmp):
    """tmp is a list of numbers"""
    outs = []
    mean = sum(tmp)/(1.0*len(tmp))
    var = sum((tmp[i] - mean)**2 for i in range(0, len(tmp)))/(1.0*len(tmp))
    std = var**0.5
    outs = [tmp[i] for i in range(0, len(tmp)) if abs(tmp[i]-mean) > 1.96*std]
    return outs


lst = [random.randrange(-10, 55) for _ in range(40)]
print lst
print outliers(lst)

【讨论】:

这是用于 python 2 的吗? 在 python 3 中我应该用什么代替xrange python 2 中的 xrange 与 python 3 中的 range 相同。python 3 中不再有 xrange。【参考方案3】:

一维数据中异常值的检测取决于其分布

1- 正态分布

    数据值几乎均匀分布在预期范围内: 在这种情况下,您可以轻松使用包括均值在内的所有方法,例如对于正态分布数据(中心极限定理和样本均值的抽样分布)的 3 或 2 个标准差(95% 或 99.7%)的置信区间。I 是一种非常有效的方法。 可汗学院统计和概率 - 抽样分布库中对此进行了解释。

如果您想要数据点的置信区间而不是平均值,另一种方法是预测区间。

    数据值随机分布在一个范围内: 平均值可能不是数据的公平表示,因为平均值很容易受到异常值的影响(数据集中非常小的或非常大的值,不典型) 中位数是衡量数值数据集中心的另一种方法。

    中值绝对偏差 - 一种以中值距离衡量所有点与中值距离的方法 http://www.itl.nist.gov/div898/handbook/eda/section3/eda35h.htm - 有一个很好的解释,正如上面乔金顿的回答所解释的那样

2 - 对称分布:如果 z-score 计算和阈值相应地改变,中值绝对偏差也是一个很好的方法

说明: http://eurekastatistics.com/using-the-median-absolute-deviation-to-find-outliers/

3 - 非对称分布:双 MAD - 双中值绝对偏差 上面附加链接中的说明

附上我的python代码供参考:

 def is_outlier_doubleMAD(self,points):
    """
    FOR ASSYMMETRIC DISTRIBUTION
    Returns : filtered array excluding the outliers

    Parameters : the actual data Points array

    Calculates median to divide data into 2 halves.(skew conditions handled)
    Then those two halves are treated as separate data with calculation same as for symmetric distribution.(first answer) 
    Only difference being , the thresholds are now the median distance of the right and left median with the actual data median
    """

    if len(points.shape) == 1:
        points = points[:,None]
    median = np.median(points, axis=0)
    medianIndex = (points.size/2)

    leftData = np.copy(points[0:medianIndex])
    rightData = np.copy(points[medianIndex:points.size])

    median1 = np.median(leftData, axis=0)
    diff1 = np.sum((leftData - median1)**2, axis=-1)
    diff1 = np.sqrt(diff1)

    median2 = np.median(rightData, axis=0)
    diff2 = np.sum((rightData - median2)**2, axis=-1)
    diff2 = np.sqrt(diff2)

    med_abs_deviation1 = max(np.median(diff1),0.000001)
    med_abs_deviation2 = max(np.median(diff2),0.000001)

    threshold1 = ((median-median1)/med_abs_deviation1)*3
    threshold2 = ((median2-median)/med_abs_deviation2)*3

    #if any threshold is 0 -> no outliers
    if threshold1==0:
        threshold1 = sys.maxint
    if threshold2==0:
        threshold2 = sys.maxint
    #multiplied by a factor so that only the outermost points are removed
    modified_z_score1 = 0.6745 * diff1 / med_abs_deviation1
    modified_z_score2 = 0.6745 * diff2 / med_abs_deviation2

    filtered1 = []
    i = 0
    for data in modified_z_score1:
        if data < threshold1:
            filtered1.append(leftData[i])
        i += 1
    i = 0
    filtered2 = []
    for data in modified_z_score2:
        if data < threshold2:
            filtered2.append(rightData[i])
        i += 1

    filtered = filtered1 + filtered2
    return filtered

【讨论】:

在 Python 3 中,它应该是 medianIndex = int(points.size/2)。此外,如果我运行代码并将阈值设置为零,它会崩溃并显示消息name 'sys' is not defined。 Laslty,函数调用中的self 永远不会被使用。 也可以使用:medianIndex = points.size//2 来避免浮动值【参考方案4】:

我已经修改了http://eurekastatistics.com/using-the-median-absolute-deviation-to-find-outliers 的代码,它给出了与 Joe Kington 相同的结果,但使用 L1 距离而不是 L2 距离,并且支持非对称分布。原始的 R 代码没有 Joe 的 0.6745 乘数,所以我还添加了它以在这个线程中保持一致性。不是 100% 确定是否有必要,但可以进行比较。

def doubleMADsfromMedian(y,thresh=3.5):
    # warning: this function does not check for NAs
    # nor does it address issues when 
    # more than 50% of your data have identical values
    m = np.median(y)
    abs_dev = np.abs(y - m)
    left_mad = np.median(abs_dev[y <= m])
    right_mad = np.median(abs_dev[y >= m])
    y_mad = left_mad * np.ones(len(y))
    y_mad[y > m] = right_mad
    modified_z_score = 0.6745 * abs_dev / y_mad
    modified_z_score[y == m] = 0
    return modified_z_score > thresh

【讨论】:

如何在多元数据上使用基于 MAD 的方法?您提到的文章很棒,但我猜它适用于单维数据。我想知道修改它以使其也适用于多变量数据的最简单方法。 对于多变量数据,没有简单的方法可以做到这一点。一种简单的方法是一次只将该方法应用于一个变量,并查看某些样本是否在任何维度上都是异常值。 @sergeyf 我们如何选择阈值?通读原帖,那里也挖不出来。 @ekta 通常和不幸的答案是用你的实际数据尝试一堆不同的阈值,看看有什么遗漏。由于这是一维的,因此您可以像 Joe Kington 的回答那样进行可视化。如果它更容易,您可以将阈值视为类似于标准偏差的数量。所以3.5很多。但我之前使用过接近 6 的数字 - 仅取决于您的数据。 @TheAG 啊,我明白你在说什么。我之所以说绝对是因为我们不在乎异常值是在右尾还是左尾。但是,如果您在乎,那么删除绝对值是有道理的!【参考方案5】:

使用percentile 的问题在于,被识别为异常值的点是您的样本量的函数。

测试异常值的方法有很多种,您应该考虑如何对它们进行分类。理想情况下,您应该使用先验信息(例如,“任何高于/低于此值的东西都是不现实的,因为......”)

然而,一个常见的、不太合理的异常值测试是根据“中值绝对偏差”删除点。

这是 N 维情况的实现(来自本文的一些代码:https://github.com/joferkington/oost_paper_code/blob/master/utilities.py):

def is_outlier(points, thresh=3.5):
    """
    Returns a boolean array with True if points are outliers and False 
    otherwise.

    Parameters:
    -----------
        points : An numobservations by numdimensions array of observations
        thresh : The modified z-score to use as a threshold. Observations with
            a modified z-score (based on the median absolute deviation) greater
            than this value will be classified as outliers.

    Returns:
    --------
        mask : A numobservations-length boolean array.

    References:
    ----------
        Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and
        Handle Outliers", The ASQC Basic References in Quality Control:
        Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. 
    """
    if len(points.shape) == 1:
        points = points[:,None]
    median = np.median(points, axis=0)
    diff = np.sum((points - median)**2, axis=-1)
    diff = np.sqrt(diff)
    med_abs_deviation = np.median(diff)

    modified_z_score = 0.6745 * diff / med_abs_deviation

    return modified_z_score > thresh

这和one of my previous answers很像,不过我想详细说明一下样本量的影响。

让我们将基于百分位数的异常值检验(类似于@CTZhu 的答案)与针对各种不同样本大小的中值绝对偏差 (MAD) 检验进行比较:

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

def main():
    for num in [10, 50, 100, 1000]:
        # Generate some data
        x = np.random.normal(0, 0.5, num-3)

        # Add three outliers...
        x = np.r_[x, -3, -10, 12]
        plot(x)

    plt.show()

def mad_based_outlier(points, thresh=3.5):
    if len(points.shape) == 1:
        points = points[:,None]
    median = np.median(points, axis=0)
    diff = np.sum((points - median)**2, axis=-1)
    diff = np.sqrt(diff)
    med_abs_deviation = np.median(diff)

    modified_z_score = 0.6745 * diff / med_abs_deviation

    return modified_z_score > thresh

def percentile_based_outlier(data, threshold=95):
    diff = (100 - threshold) / 2.0
    minval, maxval = np.percentile(data, [diff, 100 - diff])
    return (data < minval) | (data > maxval)

def plot(x):
    fig, axes = plt.subplots(nrows=2)
    for ax, func in zip(axes, [percentile_based_outlier, mad_based_outlier]):
        sns.distplot(x, ax=ax, rug=True, hist=False)
        outliers = x[func(x)]
        ax.plot(outliers, np.zeros_like(outliers), 'ro', clip_on=False)

    kwargs = dict(y=0.95, x=0.05, ha='left', va='top')
    axes[0].set_title('Percentile-based Outliers', **kwargs)
    axes[1].set_title('MAD-based Outliers', **kwargs)
    fig.suptitle('Comparing Outlier Tests with n='.format(len(x)), size=14)

main()




请注意,无论样本量如何,基于 MAD 的分类器都能正常工作,而基于百分位数的分类器分类的点越多,样本量越大,无论它们是否实际上是异常值。

【讨论】:

乔,+1,这是一个很好的答案。虽然我想知道,如果 OP 的数据总是一致地受到干扰(random.rand()),或者大多数时候可能总是遵循其他分布。如果数据总是受到一致的干扰,我不确定是否使用MAD @CTZhu - 很好,特别是如果 OP 的数据是对数正态分布的。对于模糊对称分布,与正态分布的偏差应该没有太大关系,但对于强不对称分布(例如对数正态),MAD 不是一个好的选择。 (尽管你总是可以将它应用到日志空间来解决这个问题。)所有这些只是为了强调一点,你应该对你选择的任何异常值测试进行一些思考。 @JoeKington 您使用中位数的每个地方,但 diff 计算为 L2 范数(**2);中位数是最小化 L1 范数的值,而在 L2 范数中,“均值”是中心;我期待着如果你从 L1 标准的中位数开始。你有什么理由 **2 在计算 diff 时比绝对值更好吗? 在您基于 mad 的异常值检测中,您如何设置值 0.6745 和 3.5 用于什么目的,如何确定这些值?这很混乱 @JoeKington 的 PDF 论文 pdf-archive.com/2016/07/29/outlier-methods-external/… 的备用镜像

以上是关于对于异常值的检测的主要内容,如果未能解决你的问题,请参考以下文章

用于异常检测的具有缺失值的时间序列的 STL 分解

如何检测业务数据中的异常

如何判别测量数据中是不是有异常值?

异常值检测功能

MAD+异常检测

异常检测方法 二