Apakah ada builtin numpy untuk menolak pencilan dari daftar

101

Apakah ada builtin numpy untuk melakukan sesuatu seperti berikut? Yaitu, ambil daftar ddan kembalikan daftar filtered_ddengan elemen luar yang dihapus berdasarkan beberapa asumsi distribusi titik-titik di 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]

Saya mengatakan 'sesuatu seperti' karena fungsinya memungkinkan untuk berbagai distribusi (poisson, gaussian, dll.) Dan memvariasikan ambang pencilan dalam distribusi tersebut (seperti yang msaya gunakan di sini).

aaren
sumber
Terkait: Dapatkah scipy.stats mengidentifikasi dan menutupi pencilan yang jelas? , meskipun pertanyaan itu tampaknya menangani situasi yang lebih kompleks. Untuk tugas sederhana yang Anda jelaskan, paket eksternal tampaknya berlebihan.
Sven Marnach
Saya berpikir bahwa mengingat jumlah builtin di perpustakaan numpy utama, aneh bahwa tidak ada yang bisa melakukan ini. Sepertinya hal yang lumrah dilakukan dengan data mentah dan berisik.
aaren

Jawaban:

104

Metode ini hampir identik dengan milik Anda, hanya lebih banyak numpyst (juga bekerja pada array numpy saja):

def reject_outliers(data, m=2):
    return data[abs(data - np.mean(data)) < m * np.std(data)]
eumiro
sumber
3
Metode itu bekerja cukup baik jika mcukup besar (misalnya m=6), tetapi untuk nilai-nilai kecil mini menderita mean varians bukan penduga yang kuat.
Benjamin Bannier
30
itu sebenarnya bukan keluhan tentang metode ini, tetapi keluhan tentang gagasan samar tentang 'pencilan'
Eelco Hoogendoorn
bagaimana Anda memilih m?
john ktejik
1
Saya belum mendapatkan ini untuk bekerja. Saya terus mendapatkan data pengembalian kesalahan [abs (data - np.mean (data)) <m * np.std (data)] TypeError: hanya array skalar integer yang dapat dikonversi ke indeks skalar ATAU hanya membekukan program saya
john ktejik
@johnktejik data arg harus berupa larik numpy.
Sander van Leeuwen
181

Sesuatu yang penting ketika menangani pencilan adalah bahwa seseorang harus mencoba menggunakan estimator sekuat mungkin. Rata-rata distribusi akan menjadi bias oleh pencilan tetapi misalnya median akan jauh lebih sedikit.

Membangun jawaban 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]

Di sini saya mengganti mean dengan median yang lebih kuat dan deviasi standar dengan jarak absolut median ke median. Saya kemudian menskalakan jarak dengan nilai mediannya (lagi) sehingga mberada pada skala relatif yang masuk akal.

Perhatikan bahwa agar data[s<m]sintaks berfungsi, dataharus berupa array numpy.

Benjamin Bannier
sumber
5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm ini pada dasarnya adalah Z-score yang dimodifikasi yang dirujuk di sini, tetapi dengan ambang batas yang berbeda. Jika matematika saya benar, mereka merekomendasikan m dari 3.5 / .6745 ~= 5.189(mereka mengalikan sdengan 0,6745 dan menentukan an mdari 3,5 ... juga ambil abs(s)). Adakah yang bisa menjelaskan pilihan m? Atau apakah itu sesuatu yang akan Anda identifikasi dari kumpulan data tertentu Anda?
Charlie G
2
@BenjaminBannier: Bisakah Anda memberikan penjelasan konkret untuk memilih nilai mdaripada pernyataan halus seperti "interaksi antara kemurnian dan efisiensi"?
stackoverflowuser2010
1
@ stackoverflowuser2010: Seperti yang saya katakan, ini tergantung pada kebutuhan spesifik Anda, yaitu, seberapa bersih kami perlu memberi sinyal sampel (positif palsu), atau berapa banyak pengukuran sinyal yang dapat kami buang untuk menjaga sinyal tetap bersih (negatif palsu) . Adapun evaluasi contoh khusus untuk kasus penggunaan tertentu, lihat misalnya, desy.de/~blist/notes/whyeffpur.ps.gz .
Benjamin Bannier
2
Saya mendapatkan kesalahan berikut ketika saya memanggil fungsi dengan daftar pelampung:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis
2
@Charlie, jika Anda melihat pada gambar itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD , Anda akan melihat bahwa ketika berurusan dengan distribusi normal (yang pada kenyataannya tidak demikian, Anda memerlukan diubah skor-z) dengan SD = 1, Anda memiliki MAD ~ 0,68, yang menjelaskan faktor penskalaan. Oleh karena itu, pilihan m = 3,5 menyiratkan bahwa Anda ingin membuang 0,05% data.
Fato39
13

Jawaban Benjamin Bannier menghasilkan pass-through ketika median jarak dari median adalah 0, jadi saya menemukan versi modifikasi ini sedikit lebih membantu untuk kasus-kasus seperti yang diberikan dalam contoh di bawah ini.

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]

Contoh:

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

Memberikan:

[[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)
Yigal
sumber
9

Membangun dari Benjamin, menggunakan pandas.Series, dan mengganti MAD dengan 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]

Misalnya, jika Anda menyetel iq_range=0.6, persentil rentang interkuartil akan menjadi :, 0.20 <--> 0.80jadi lebih banyak pencilan akan disertakan.

ankostis
sumber
4

Alternatifnya adalah membuat estimasi yang kuat dari deviasi standar (dengan asumsi statistik Gaussian). Mencari kalkulator online, saya melihat bahwa persentil 90% sesuai dengan 1,2815σ dan 95% adalah 1,645σ ( http://vassarstats.net/tabs.html?#z )

Sebagai contoh sederhana:

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)

Output yang saya dapatkan adalah:

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

Yang mendekati nilai yang diharapkan dari 2.

Jika kita ingin menghapus poin di atas / di bawah 5 standar deviasi (dengan 1000 poin, kami mengharapkan 1 nilai> 3 standar deviasi):

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

Pemberian yang mana:

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

Saya tidak tahu pendekatan mana yang lebih efisien / kuat

Chris
sumber
3

Saya ingin memberikan dua metode dalam jawaban ini, solusi berdasarkan "skor z" dan solusi berdasarkan "IQR".

Kode yang diberikan dalam jawaban ini berfungsi pada numpylarik redup tunggal dan numpylarik ganda .

Mari impor beberapa modul terlebih dahulu.

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

z metode berbasis skor

Metode ini akan menguji apakah angka tersebut berada di luar tiga deviasi standar. Berdasarkan aturan ini, jika nilainya outlier, metode akan mengembalikan true, jika tidak, mengembalikan 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

Metode berbasis IQR

Metode ini akan menguji apakah nilainya kurang dari q1 - 1.5 * iqratau lebih besar dari q3 + 1.5 * iqryang serupa dengan metode plot 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)

Terakhir, jika Anda ingin memfilter pencilan, gunakan numpyselektor.

Semoga harimu menyenangkan.

Kerugian Don
sumber
3

Pertimbangkan bahwa semua metode di atas gagal ketika standar deviasi Anda menjadi sangat besar karena pencilan yang sangat besar.

( Simalar sebagai penghitungan rata-rata gagal dan seharusnya menghitung median. Padahal, rata-rata "lebih rentan terhadap kesalahan seperti stdDv". )

Anda dapat mencoba menerapkan algoritme Anda secara berulang atau memfilter menggunakan rentang interkuartil: (di sini "faktor" berkaitan dengan rentang * sigma, namun hanya jika data Anda mengikuti distribusi Gaussian)

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)
K. Foe
sumber
Maaf, saya lupa bahwa sudah ada saran IQR di atas. Haruskah saya tetap meninggalkan jawaban ini karena kode yang lebih pendek atau menghapusnya?
K. Foe
1

Saya ingin melakukan sesuatu yang serupa, kecuali mengatur nomor ke NaN daripada menghapusnya dari data, karena jika Anda menghapusnya, Anda mengubah panjangnya yang dapat mengacaukan plotting (yaitu jika Anda hanya menghapus pencilan dari satu kolom dalam tabel , tetapi Anda membutuhkannya untuk tetap sama dengan kolom lainnya sehingga Anda dapat memplotkannya satu sama lain).

Untuk melakukannya saya menggunakan fungsi masking 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
Alex S.
sumber
Anda juga dapat melakukan np.clip ke nilai minimum dan maksimum yang diizinkan untuk menjaga dimensinya.
Andi R
0

jika ingin mendapatkan posisi indeks maka pencilan idx_listakan mengembalikannya.

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]
Caner Erden
sumber
0

Untuk satu set gambar (setiap gambar memiliki 3 dimensi), di mana saya ingin menolak outlier untuk setiap piksel yang saya gunakan:

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)

Maka dimungkinkan untuk menghitung mean:

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

(Saya menggunakannya untuk Pengurangan Latar Belakang)

ron653
sumber