リストから外れ値を拒否するための派手な組み込みがありますか


100

次のようなことをするための派手な組み込みはありますか?つまり、リスト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ライブラリの組み込み関数の数を考えると、これを行うことが何もないのは奇妙だと思っていました。生のノイズの多いデータを扱うのはよくあることのようです。
アーレン2012

回答:


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この値が小さい場合、分散がロバスト推定量ではないという平均の影響を受けます。
ベンジャミンバニエ2013年

30
これは実際にはメソッドに関する不満ではありませんが、「外れ値」の漠然とした概念に関する不満です
Eelco Hoogendoorn '15

どのようにmを選びますか?
john ktejik 2017

1
私はこれを働かせていません。エラーが返され続けるdata [abs(data-np.mean(data))<m * np.std(data)] TypeError:整数のスカラー配列のみがスカラーインデックスに変換できるか、プログラムをフリーズするだけ
ジョンktejik

@johnktejikデータ引数は、数の多い配列である必要があります。
サンダーファンレーウェン2017

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 を掛けて3.5を指定しますm...もかかりますabs(s))。誰かがmの選択を説明できますか?それとも、特定のデータセットから特定するものですか?
チャーリーG

2
@BenjaminBannier:m「純粋さと効率の相互作用」のようなふわふわしたステートメントではなく、値を選択するための具体的な説明を提供できますか?
stackoverflowuser2010

1
@ stackoverflowuser2010:私が言ったように、これは特定の要件、つまり、サンプルをシグナルするためにどれだけクリーンである必要があるか(誤検知)、またはシグナルをクリーンに保つために捨てることができるシグナル測定の数(誤検知)によって異なります。 。特定のユースケースの具体的な評価例については、たとえば、desy.de/~blist/notes/whyeffpur.ps.gzを参照してください。
ベンジャミンバニエ2017年

2
私はfloatのリストで関数を呼び出すとき、私は次のエラーを取得する:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis

2
@Charlie、図itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MADを見ると、正規分布を処理しているときに表示されます(実際には必要ありません)。変更されたzスコア)、SD = 1の場合、MAD〜0.68であり、スケーリング係数を説明します。したがって、m = 3.5の選択は、データの0.05%を破棄することを意味します。
Fato39

13

ベンジャミンバニエの答えは、中央値からの距離の中央値が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

ベンジャミンに基づいて構築し、を使用してpandas.SeriesMADをIQRに置き換えます。

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」に基づくソリューションの2つの方法を提供したいと思います。

この回答で提供されるコードは、単一のdim numpy配列と複数のnumpy配列の両方で機能します。

最初にいくつかのモジュールをインポートしましょう。

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

zスコアに基づく方法

このメソッドは、数値が3つの標準偏差の範囲外かどうかをテストします。このルールに基づいて、値が外れ値の場合、メソッドは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のプロットメソッドと同様です。

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

大きな異常値のために標準偏差が非常に大きくなると、上記のすべての方法が失敗することを考慮してください。

平均計算が失敗し、中央値を計算するはずなので、同様です。ただし、平均は「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.フォー

1

数値をデータから削除するのではなくNaNに設定することを除いて、同様のことをしたかったのです。削除すると、プロットをめちゃくちゃにできる長さを変更するためです(つまり、表の1つの列から外れ値のみを削除する場合)ですが、他の列と同じにしておく必要があるため、互いにプロットできます)。

これを行うには、numpyのマスキング関数を使用しました。

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))

(私はバックグラウンド減算に使用します)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.