Bir listeden aykırı değerleri reddetmek için uyuşmuş bir yerleşik var mı


101

Aşağıdakine benzer bir şey yapmak için uyuşmuş bir yerleşik var mı? Diğer bir deyişle, bir liste alın dve filtered_diçindeki noktaların varsayılan bazı dağılımına bağlı olarak kaldırılan tüm dış öğelerle bir liste döndürün 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]

'Benzeri bir şey' diyorum çünkü işlev, farklı dağılımlara (poisson, gauss, vb.) Ve bu dağılımlar içinde değişen aykırı değer eşiklerine izin verebilir ( mburada kullandığım gibi ).


İlgili: scipy.stats bariz aykırı değerleri belirleyip maskeleyebilir mi? Ancak bu soru daha karmaşık durumlarla ilgileniyor gibi görünüyor. Tanımladığınız basit görev için, harici bir paket fazla gözüküyor.
Sven Marnach

Ana numpy kütüphanesindeki yerleşiklerin sayısı göz önüne alındığında, bunu yapacak hiçbir şeyin olmaması garipti diye düşünüyordum. Ham, gürültülü verilerle yapılacak oldukça yaygın bir şey gibi görünüyor.
aaren

Yanıtlar:


104

Bu yöntem hemen hemen sizinkiyle aynı, sadece daha fazla numpyst (ayrıca sadece numpy dizilerinde de çalışıyor):

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

4
Bu yöntem myeterince büyükse (örn. m=6) Yeterince iyi çalışır , ancak bunun küçük değerleri miçin ortalama varyansın sağlam tahmin ediciler olmaması sorunu yaşar.
Benjamin Bannier

31
bu aslında yöntemle ilgili bir şikayet değil, ancak belirsiz bir 'aykırı' kavramı hakkında bir şikayet
Eelco Hoogendoorn

nasıl bir m seçersiniz?
john ktejik

1
Bunu işe almadım. Bir hata döndürme verisi almaya devam ediyorum [abs (data - np.mean (data)) <m * np.std (data)] TypeError: yalnızca tamsayı skaler diziler skaler dizine dönüştürülebilir VEYA programımı dondurur
john ktejik

1
@johnktejik veri argümanının uyuşmuş bir dizi olması gerekir.
Sander van Leeuwen

182

Aykırı değerlerle uğraşırken önemli olan bir şey, tahmin edicileri olabildiğince sağlam kullanmaya çalışmaktır. Bir dağılımın ortalaması, aykırı değerler tarafından önyargılı olacaktır, ancak örneğin, medyan çok daha az olacaktır.

Eumiro'nun cevabına dayanarak:

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]

Burada, ortalamayı daha sağlam medyanla ve standart sapmayı medyana olan medyan mutlak uzaklıkla değiştirdim. Daha sonra mesafeleri (tekrar) medyan değerlerine göre ölçeklendirdim, böylece mmakul bir nispi ölçekte.

İçin unutmayın data[s<m]işe sözdizimi,data bir numpy dizisi olmalıdır.


5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm Bu, temelde burada atıfta bulunulan, ancak farklı bir eşikle değiştirilmiş Z- skorudur . Matematiğim doğruysa, bir m öneriyorlar 3.5 / .6745 ~= 5.189( s.6745 ile çarpıyorlar ve m3.5 olarak belirtiyorlar ... ayrıca al abs(s)). Herhangi biri m seçimini açıklayabilir mi? Yoksa belirli veri kümenizden tanımlayacağınız bir şey mi?
Charlie G

2
@BenjaminBannier: Lütfen m"saflık ve verimlilik etkileşimi" gibi kabarık ifadeler yerine bir değer seçmek için somut bir açıklama yapabilir misiniz ?
stackoverflowuser2010

1
@ stackoverflowuser2010: Dediğim gibi, bu sizin özel gereksinimlerinize, yani örneğin ne kadar temiz sinyal vermemiz gerektiğine (yanlış pozitifler) veya sinyali temiz tutmak için kaç tane sinyal ölçümü atabileceğimize bağlıdır (yanlış negatifler) . Belirli bir kullanım durumu için belirli bir örnek değerlendirme için, bkz. Örneğin, desy.de/~blist/notes/whyeffpur.ps.gz .
Benjamin Bannier

2
Bir 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 şekline bakarsanız , normal dağılımla uğraşırken göreceksiniz (aslında bu, değiştirilmiş z puanları) SD = 1 ise, ölçekleme faktörünü açıklayan MAD ~ 0.68'e sahipsiniz. Bu nedenle m = 3,5 seçimi, verilerin% 0,05'ini atmak istediğiniz anlamına gelir.
Fato39

13

Benjamin Bannier'in cevabı, medyandan mesafelerin medyanı 0 olduğunda bir geçiş sağlar, bu yüzden bu değiştirilmiş versiyonu aşağıdaki örnekte verildiği gibi durumlar için biraz daha yararlı buldum.

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]

Misal:

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

Verir:

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

10

Benjamin'in üzerine inşa etmek pandas.Series, MAD kullanarak ve IQR ile değiştirmek :

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]

Örneğin, eğer ayarlarsanız iq_range=0.6, çeyrekler arası aralığın yüzdelikleri: olur 0.20 <--> 0.80, böylece daha fazla aykırı değerler dahil edilir.


4

Bir alternatif, standart sapmanın sağlam bir tahminini yapmaktır (Gauss istatistiklerini varsayarsak). Çevrimiçi hesap makinelerine baktığımda,% 90 yüzdeliğin 1.2815σ'ya karşılık geldiğini ve% 95'in 1.645σ olduğunu görüyorum ( http://vassarstats.net/tabs.html?#z )

Basit bir örnek olarak:

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)

Aldığım çıktı:

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

Beklenen 2 değerine yakın olan.

5 standart sapmanın üstündeki / altındaki noktaları kaldırmak istiyorsak (1000 nokta ile 1 değer> 3 standart sapma bekleriz):

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

Hangi verir:

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

Hangi yaklaşımın daha etkili / sağlam olduğuna dair hiçbir fikrim yok


3

Bu cevapta "z skoruna" dayalı çözüm ve "IQR" tabanlı çözüm olmak üzere iki yöntem sunmak istiyorum.

Bu cevapta verilen kod hem tek dim numpydizisi hem de çoklu numpydizi üzerinde çalışır .

Öncelikle bazı modülleri içeri aktaralım.

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

z puanına dayalı yöntem

Bu yöntem, sayının üç standart sapmanın dışında olup olmadığını test edecektir. Bu kurala göre, değer aykırı ise, yöntem true, değilse, false döndürür.

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 tabanlı yöntem

Bu yöntem, değerin SPSS'nin çizim yöntemine benzer şekilde küçük q1 - 1.5 * iqrveya büyük olup olmadığını test edecektir q3 + 1.5 * iqr.

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)

Son olarak, aykırı değerleri filtrelemek istiyorsanız, bir numpyseçici kullanın .

İyi günler.


3

Büyük aykırı değerler nedeniyle standart sapmanız çok büyüdüğünde yukarıdaki yöntemlerin hepsinin başarısız olduğunu düşünün.

( Ortalama hesaplama başarısız olduğu için Simalar daha çok medyanı hesaplamalıdır. Bununla birlikte, ortalama "stdDv gibi bir hataya daha yatkındır". )

Algoritmanızı yinelemeli olarak uygulamayı deneyebilir veya çeyrekler arası aralığı kullanarak filtreleyebilirsiniz: (burada "faktör" bir * sigma aralığı ile ilgilidir, ancak yalnızca verileriniz bir Gauss dağılımını takip ettiğinde)

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)

Üzgünüm, yukarıda zaten bir IQR önerisi olduğunu gözden kaçırdım. Daha kısa kod yüzünden bu yanıtı bırakmalı mıyım yoksa silmeli miyim?
K. Foe

1

Numarayı verilerden kaldırmak yerine NaN olarak ayarlamak dışında benzer bir şey yapmak istedim, çünkü eğer onu kaldırırsanız uzunluğu değiştirirsiniz ve bu da çizimi bozabilir (yani, tablodaki bir sütundan yalnızca aykırı değerleri kaldırıyorsanız , ancak diğer sütunlarla aynı kalması gerekir, böylece onları birbirine karşı çizebilirsiniz).

Bunu yapmak için numpy'nin maskeleme işlevlerini kullandım :

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

Ayrıca boyutları korumak için minimum ve maksimum izin verilen değerlere np.clip atabilirsiniz.
Andi R

0

Aykırı değerlerin dizin konumunu almak istiyorsanız idx_listbunu geri verecektir.

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

Kullandığım her piksel için aykırı değerleri reddetmek istediğim bir dizi görüntü için (her görüntünün 3 boyutu vardır):

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)

O zaman ortalamayı hesaplamak mümkündür:

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

(Arka Plan Çıkarma için kullanıyorum)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.