是否有一个内置的numpy来拒绝列表中的离群值


100

是否有内置的numpy来执行以下操作?也就是说,获取一个列表d并返回一个列表,filtered_d其中根据中假定的点的某些分布,删除了所有外围元素d

import numpy as np

def reject_outliers(data):
    m = 2
    u = np.mean(data)
    s = np.std(data)
    filtered = [e for e in data if (u - 2 * s < e < u + 2 * s)]
    return filtered

>>> d = [2,4,5,1,6,5,40]
>>> filtered_d = reject_outliers(d)
>>> print filtered_d
[2,4,5,1,6,5]

我之所以说“类似”,是因为该函数可能允许变化的分布(泊松,高斯等)和这些分布内的异常阈值(如m我在这里使用的)。


相关:scipy.stats是否可以识别并掩盖明显的异常值?,尽管这个问题似乎可以解决更复杂的情况。对于您描述的简单任务,外部软件包似乎是过大的。
Sven Marnach

我在想,考虑到主要numpy库中内置的数量,奇怪的是没有东西可以做。原始的,嘈杂的数据似乎很普通。
aaren 2012年

Answers:


103

此方法与您的方法几乎相同,只是更多的numpyst(也适用于numpy数组):

def reject_outliers(data, m=2):
    return data[abs(data - np.mean(data)) < m * np.std(data)]

3
如果该方法m足够大(例如m=6),则该方法足够好,但是对于较小的值,m其均值方差不是鲁棒的估计量。
本杰明·班尼尔

30
但这并不是对方法的真正抱怨,而是对“异常值”的模糊概念的抱怨
Eelco Hoogendoorn 2014年

您如何选择m?
约翰·克特吉克

1
我没有得到这个工作。我不断收到错误返回数据[abs(data-np.mean(data))<m * np.std(data)] TypeError:只有整数标量数组可以转换为标量索引,否则只会冻结我的程序
john ktejik

@johnktejik数据arg必须是一个numpy数组。
桑德·范·吕文

181

处理离群值时,重要的一点是应尝试使用估计值尽可能可靠。分布的平均值将受到异常值的影响,但例如中位数会小得多。

以eumiro的答案为基础:

def reject_outliers(data, m = 2.):
    d = np.abs(data - np.median(data))
    mdev = np.median(d)
    s = d/mdev if mdev else 0.
    return data[s<m]

在这里,我用更可靠的中位数代替了均值,并用中位数与中位数的绝对距离代替了标准偏差。然后,我用距离(再次)的中值来缩放距离,以使其m处于合理的相对范围内。

请注意,要使data[s<m]语法起作用,data必须是一个numpy数组。


5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm,这基本上是此处引用的修改后的Z评分,但阈值不同。如果我的数学正确,他们建议使用m的值3.5 / .6745 ~= 5.189(将它们乘以s.6745并指定m3.5的值...也取abs(s))。有人可以解释m的选择吗?还是可以从特定数据集中识别出的东西?
查理G

2
@BenjaminBannier:您能为选择一个值m而不是诸如“纯度和效率的相互作用”这样的蓬松陈述提供一些具体的解释吗?
stackoverflowuser2010

1
@ stackoverflowuser2010:就像我说的那样,这取决于您的特定要求,即我们需要将信号采样多干净(假阳性),或者我们可以抛弃多少信号测量来保持信号干净(假阴性) 。至于针对特定用例的特定示例评估,请参见例如desy.de/~blist/notes/whyeffpur.ps.gz
本杰明·班尼尔

2
当我使用浮点数列表调用该函数时,出现以下错误:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis '18

2
@Charlie,如果您查看图itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD,您会发现在处理正态分布时(实际上并非如此,您需要SD = 1的修改的z分数),则MAD〜0.68,这说明了比例因子。因此,选择m = 3.5意味着您要舍弃0.05%的数据。
Fato39

13

本杰明·班尼尔(Benjamin Bannier)的答案会在距离中位数的距离中位数为0时产生直通,因此我发现此修改版本对下面示例中给出的情况更有帮助。

def reject_outliers_2(data, m=2.):
    d = np.abs(data - np.median(data))
    mdev = np.median(d)
    s = d / (mdev if mdev else 1.)
    return data[s < m]

例:

data_points = np.array([10, 10, 10, 17, 10, 10])
print(reject_outliers(data_points))
print(reject_outliers_2(data_points))

给出:

[[10, 10, 10, 17, 10, 10]]  # 17 is not filtered
[10, 10, 10, 10, 10]  # 17 is filtered (it's distance, 7, is greater than m)

9

在Benjamin的基础上,使用pandas.Series,并用IQR替换MAD

def reject_outliers(sr, iq_range=0.5):
    pcnt = (1 - iq_range) / 2
    qlow, median, qhigh = sr.dropna().quantile([pcnt, 0.50, 1-pcnt])
    iqr = qhigh - qlow
    return sr[ (sr - median).abs() <= iqr]

例如,如果设置iq_range=0.6,则四分位数范围的百分位数将变为:0.20 <--> 0.80,因此将包含更多离群值。


4

另一种方法是对标准偏差进行可靠的估计(假设高斯统计量)。查找在线计算器,我发现90%的百分位数对应于1.2815σ,而95%的百分位数是1.645σ(http://vassarstats.net/tabs.html?#z

作为一个简单的例子:

import numpy as np

# Create some random numbers
x = np.random.normal(5, 2, 1000)

# Calculate the statistics
print("Mean= ", np.mean(x))
print("Median= ", np.median(x))
print("Max/Min=", x.max(), " ", x.min())
print("StdDev=", np.std(x))
print("90th Percentile", np.percentile(x, 90))

# Add a few large points
x[10] += 1000
x[20] += 2000
x[30] += 1500

# Recalculate the statistics
print()
print("Mean= ", np.mean(x))
print("Median= ", np.median(x))
print("Max/Min=", x.max(), " ", x.min())
print("StdDev=", np.std(x))
print("90th Percentile", np.percentile(x, 90))

# Measure the percentile intervals and then estimate Standard Deviation of the distribution, both from median to the 90th percentile and from the 10th to 90th percentile
p90 = np.percentile(x, 90)
p10 = np.percentile(x, 10)
p50 = np.median(x)
# p50 to p90 is 1.2815 sigma
rSig = (p90-p50)/1.2815
print("Robust Sigma=", rSig)

rSig = (p90-p10)/(2*1.2815)
print("Robust Sigma=", rSig)

我得到的输出是:

Mean=  4.99760520022
Median=  4.95395274981
Max/Min= 11.1226494654   -2.15388472011
Sigma= 1.976629928
90th Percentile 7.52065379649

Mean=  9.64760520022
Median=  4.95667658782
Max/Min= 2205.43861943   -2.15388472011
Sigma= 88.6263902244
90th Percentile 7.60646688694

Robust Sigma= 2.06772555531
Robust Sigma= 1.99878292462

接近预期值2。

如果要删除高于/低于5个标准偏差的点(对于1000个点,我们期望1个值> 3个标准偏差):

y = x[abs(x - p50) < rSig*5]

# Print the statistics again
print("Mean= ", np.mean(y))
print("Median= ", np.median(y))
print("Max/Min=", y.max(), " ", y.min())
print("StdDev=", np.std(y))

这使:

Mean=  4.99755359935
Median=  4.95213030447
Max/Min= 11.1226494654   -2.15388472011
StdDev= 1.97692712883

我不知道哪种方法更有效/更健壮


3

我想在此答案中提供两种方法,基于“ z分数”的解决方案和基于“ IQR”的解决方案。

此答案中提供的代码适用于单个暗numpy数组和多个numpy数组。

让我们首先导入一些模块。

import collections
import numpy as np
import scipy.stats as stat
from scipy.stats import iqr

基于z评分的方法

此方法将测试数字是否超出三个标准偏差。根据此规则,如果值离群,则该方法将返回true,否则返回false。

def sd_outlier(x, axis = None, bar = 3, side = 'both'):
    assert side in ['gt', 'lt', 'both'], 'Side should be `gt`, `lt` or `both`.'

    d_z = stat.zscore(x, axis = axis)

    if side == 'gt':
        return d_z > bar
    elif side == 'lt':
        return d_z < -bar
    elif side == 'both':
        return np.abs(d_z) > bar

基于IQR的方法

此方法将测试值是否小于q1 - 1.5 * iqr或大于q3 + 1.5 * iqr,这与SPSS的plot方法类似。

def q1(x, axis = None):
    return np.percentile(x, 25, axis = axis)

def q3(x, axis = None):
    return np.percentile(x, 75, axis = axis)

def iqr_outlier(x, axis = None, bar = 1.5, side = 'both'):
    assert side in ['gt', 'lt', 'both'], 'Side should be `gt`, `lt` or `both`.'

    d_iqr = iqr(x, axis = axis)
    d_q1 = q1(x, axis = axis)
    d_q3 = q3(x, axis = axis)
    iqr_distance = np.multiply(d_iqr, bar)

    stat_shape = list(x.shape)

    if isinstance(axis, collections.Iterable):
        for single_axis in axis:
            stat_shape[single_axis] = 1
    else:
        stat_shape[axis] = 1

    if side in ['gt', 'both']:
        upper_range = d_q3 + iqr_distance
        upper_outlier = np.greater(x - upper_range.reshape(stat_shape), 0)
    if side in ['lt', 'both']:
        lower_range = d_q1 - iqr_distance
        lower_outlier = np.less(x - lower_range.reshape(stat_shape), 0)

    if side == 'gt':
        return upper_outlier
    if side == 'lt':
        return lower_outlier
    if side == 'both':
        return np.logical_or(upper_outlier, lower_outlier)

最后,如果要滤除异常值,请使用numpy选择器。

祝你今天愉快。


3

考虑到当您的标准偏差由于巨大的异常值而变得非常大时,上述所有方法都会失败。

Simalar的平均值计算失败,应该计算中位数。尽管如此,平均值“更容易出现stdDv这样的错误”。

您可以尝试迭代应用算法,也可以使用四分位数范围进行过滤:(此处“因数”与*范围有关,但仅当数据遵循高斯分布时)

import numpy as np

def sortoutOutliers(dataIn,factor):
    quant3, quant1 = np.percentile(dataIn, [75 ,25])
    iqr = quant3 - quant1
    iqrSigma = iqr/1.34896
    medData = np.median(dataIn)
    dataOut = [ x for x in dataIn if ( (x > medData - factor* iqrSigma) and (x < medData + factor* iqrSigma) ) ] 
    return(dataOut)

抱歉,我忽略了上面已经有一个IQR建议。由于代码较短,我还是应该保留此答案还是将其删除?
K. Foe

1

我想做类似的事情,除了将数字设置为NaN而不是从数据中删除它,因为如果删除它,则更改了会弄乱绘图的长度(即,如果您仅从表的一列中删除异常值) ,但您需要使其与其他列保持相同,以便可以相互绘制图)。

为此,我使用了numpy的masking函数

def reject_outliers(data, m=2):
    stdev = np.std(data)
    mean = np.mean(data)
    maskMin = mean - stdev * m
    maskMax = mean + stdev * m
    mask = np.ma.masked_outside(data, maskMin, maskMax)
    print('Masking values outside of {} and {}'.format(maskMin, maskMax))
    return mask

您也可以将它们np.clip到最小和最大允许值以保持尺寸。
安迪R

0

如果要获取离群值的索引位置,idx_list则将其返回。

def reject_outliers(data, m = 2.):
        d = np.abs(data - np.median(data))
        mdev = np.median(d)
        s = d/mdev if mdev else 0.
        data_range = np.arange(len(data))
        idx_list = data_range[s>=m]
        return data[s<m], idx_list

data_points = np.array([8, 10, 35, 17, 73, 77])  
print(reject_outliers(data_points))

after rejection: [ 8 10 35 17], index positions of outliers: [4 5]

0

对于一组图像(每个图像都有3维),我想拒绝使用的每个像素的离群值:

mean = np.mean(imgs, axis=0)
std = np.std(imgs, axis=0)
mask = np.greater(0.5 * std + 1, np.abs(imgs - mean))
masked = np.multiply(imgs, mask)

然后可以计算平均值:

masked_mean = np.divide(np.sum(masked, axis=0), np.sum(mask, axis=0))

(我将其用于背景减法)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.