Có một nội trang phức tạp để từ chối các ngoại lệ khỏi danh sách


100

Có một nội trang numpy để làm điều gì đó như sau không? Đó là, lấy một danh sách dvà trả về một danh sách filtered_dvới bất kỳ phần tử bên ngoài nào đã bị loại bỏ dựa trên một số phân phối giả định của các điểm trong đó 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]

Tôi nói 'đại loại như' vì hàm có thể cho phép các phân phối khác nhau (poisson, gaussian, v.v.) và các ngưỡng ngoại lệ khác nhau trong các phân phối đó (như mtôi đã sử dụng ở đây).


Liên quan: scipy.stats có thể xác định và che giấu những điểm bất thường rõ ràng không? , mặc dù câu hỏi đó dường như giải quyết các tình huống phức tạp hơn. Đối với tác vụ đơn giản mà bạn đã mô tả, một gói bên ngoài dường như quá mức cần thiết.
Sven Marnach,

Tôi đã nghĩ rằng với số lượng nội trang trong thư viện chính, thật lạ là không có gì để làm điều này. Nó có vẻ như là một điều khá phổ biến đối với dữ liệu thô, nhiễu.
aaren

Câu trả lời:


103

Phương pháp này gần như giống với phương pháp của bạn, chỉ có nhiều numpyst hơn (cũng chỉ hoạt động trên các mảng numpy):

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

3
Phương pháp đó hoạt động đủ tốt nếu mđủ lớn (ví dụ m=6), nhưng đối với các giá trị nhỏ của phương pháp mnày có nghĩa là phương sai không phải là công cụ ước lượng mạnh mẽ.
Benjamin Bannier

30
đó không phải là thực sự là một lời phàn nàn về phương pháp mặc dù, nhưng khiếu nại về các khái niệm mơ hồ về một 'outlier'
Eelco Hoogendoorn

làm thế nào để bạn chọn một m?
john ktejik

1
Tôi đã không nhận được điều này để làm việc. Tôi tiếp tục nhận được lỗi trả về dữ liệu [abs (data - np.mean (data)) <m * np.std (data)] TypeError: chỉ các mảng vô hướng số nguyên mới có thể được chuyển đổi thành chỉ mục vô hướng HOẶC nó chỉ đóng băng chương trình của tôi
john ktejik

@johnktejik data arg cần phải là một mảng không rõ ràng.
Sander van Leeuwen

181

Một điều quan trọng khi đối phó với các ngoại lệ là người ta nên cố gắng sử dụng các công cụ ước tính càng mạnh càng tốt. Giá trị trung bình của một phân phối sẽ bị sai lệch bởi các giá trị ngoại lai nhưng ví dụ: giá trị trung bình sẽ nhỏ hơn nhiều.

Dựa trên câu trả lời của 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]

Ở đây tôi đã thay thế giá trị trung bình bằng trung vị mạnh hơn và độ lệch chuẩn bằng khoảng cách tuyệt đối trung bình đến trung vị. Sau đó, tôi chia tỷ lệ khoảng cách bằng giá trị trung bình (một lần nữa) của chúng để mnằm trên một tỷ lệ tương đối hợp lý.

Lưu ý rằng để data[s<m]cú pháp hoạt động, dataphải là một mảng numpy.


5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm về cơ bản đây là điểm Z đã sửa đổi được tham chiếu ở đây, nhưng với một ngưỡng khác. Nếu phép toán của tôi là đúng, họ đề xuất một m trong số 3.5 / .6745 ~= 5.189(họ nhân svới 0,6745 và chỉ định m3,5 ... cũng lấy abs(s)). Ai có thể giải thích sự lựa chọn của m? Hay nó là thứ mà bạn sẽ xác định được từ tập dữ liệu cụ thể của mình?
Charlie G

2
@BenjaminBannier: Bạn có thể vui lòng cung cấp một số giải thích cụ thể về việc chọn một giá trị mthay vì những câu nói phiến diện như "ảnh hưởng lẫn nhau của độ tinh khiết và hiệu quả" không?
stackoverflowuser2010

1
@ stackoverflowuser2010: Như tôi đã nói, điều này phụ thuộc vào yêu cầu cụ thể của bạn, tức là chúng ta cần báo hiệu mẫu sạch đến mức nào (dương tính giả) hoặc có bao nhiêu phép đo tín hiệu mà chúng ta có thể bỏ đi để giữ tín hiệu sạch (âm tính giả) . Đối với đánh giá ví dụ cụ thể cho một trường hợp sử dụng nhất định, hãy xem ví dụ: desy.de/~blist/notes/whyeffpur.ps.gz .
Benjamin Bannier

2
Tôi gặp lỗi sau khi gọi hàm với danh sách các float:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis

2
@Charlie, nếu bạn nhìn vào hình itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD , bạn sẽ thấy rằng khi xử lý phân phối chuẩn (thực tế không phải là trường hợp bạn cần điểm số z đã sửa đổi) với SD = 1, bạn có MAD ~ 0,68, điều này giải thích hệ số tỷ lệ. Do đó, lựa chọn m = 3,5 ngụ ý rằng bạn muốn loại bỏ 0,05% dữ liệu.
Fato39

13

Câu trả lời của Benjamin Bannier mang lại kết quả chuyển qua khi trung vị của khoảng cách từ trung vị là 0, vì vậy tôi thấy phiên bản sửa đổi này hữu ích hơn một chút đối với các trường hợp như được đưa ra trong ví dụ bên dưới.

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]

Thí dụ:

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

Cung cấp:

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

Xây dựng dựa trên Benjamin's, sử dụng pandas.Seriesvà thay thế MAD bằng 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]

Ví dụ: nếu bạn đặt iq_range=0.6, các phân vị phần trăm của phạm vi liên phần phân vị sẽ trở thành :, 0.20 <--> 0.80vì vậy sẽ có nhiều ngoại lệ hơn .


4

Một giải pháp thay thế là thực hiện một ước tính chắc chắn về độ lệch chuẩn (giả sử thống kê Gaussian). Tra cứu các máy tính trực tuyến, tôi thấy rằng phần trăm 90% tương ứng với 1,2815σ và 95% là 1,645σ ( http://vassarstats.net/tabs.html?#z )

Như một ví dụ đơn giản:

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)

Đầu ra tôi nhận được là:

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

Gần với giá trị kỳ vọng là 2.

Nếu chúng ta muốn xóa các điểm trên / dưới 5 độ lệch chuẩn (với 1000 điểm, chúng ta sẽ mong đợi 1 giá trị> 3 độ lệch chuẩn):

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

Cái nào mang lại:

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

Tôi không biết cách tiếp cận nào là hiệu quả / mạnh mẽ hơn


3

Tôi muốn cung cấp hai phương pháp trong câu trả lời này, giải pháp dựa trên "điểm z" và giải pháp dựa trên "IQR".

Mã được cung cấp trong câu trả lời này hoạt động trên cả numpymảng mờ đơn và nhiều numpymảng.

Trước tiên, hãy nhập một số mô-đun.

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

phương pháp dựa trên điểm z

Phương pháp này sẽ kiểm tra xem con số có nằm ngoài ba độ lệch chuẩn hay không. Dựa trên quy tắc này, nếu giá trị lớn hơn, phương thức sẽ trả về true, nếu không, trả về 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

Phương pháp dựa trên IQR

Phương pháp này sẽ kiểm tra nếu giá trị nhỏ hơn q1 - 1.5 * iqrhoặc lớn hơn q3 + 1.5 * iqr, tương tự như phương pháp biểu đồ của 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)

Cuối cùng, nếu bạn muốn lọc ra các ngoại lệ, hãy sử dụng một numpybộ chọn.

Chúc một ngày tốt lành.


3

Hãy xem xét rằng tất cả các phương pháp trên đều thất bại khi độ lệch chuẩn của bạn rất lớn do các giá trị ngoại lệ lớn.

( Simalar khi tính toán trung bình không thành công và đúng hơn nên tính giá trị trung bình. Mặc dù vậy, mức trung bình "dễ bị lỗi hơn như stdDv". )

Bạn có thể thử áp dụng lặp đi lặp lại thuật toán của mình hoặc bạn lọc bằng cách sử dụng phạm vi liên phần tư: (ở đây "yếu tố" liên quan đến phạm vi * sigma, nhưng chỉ khi dữ liệu của bạn tuân theo phân phối 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)

Xin lỗi, tôi đã bỏ qua rằng đã có đề xuất IQR ở trên. Tôi vẫn nên để lại câu trả lời này do mã ngắn hơn hay xóa nó?
K. Foe

1

Tôi muốn làm điều gì đó tương tự, ngoại trừ việc đặt số thành NaN thay vì xóa nó khỏi dữ liệu, vì nếu bạn xóa nó, bạn sẽ thay đổi độ dài có thể làm rối tung việc vẽ biểu đồ (tức là nếu bạn chỉ xóa số ngoại lai khỏi một cột trong bảng , nhưng bạn cần nó giữ nguyên như các cột khác để bạn có thể vẽ chúng với nhau).

Để làm như vậy, tôi đã sử dụng các chức năng tạo mặt nạ của 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

Bạn cũng có thể np.clip chúng đến các giá trị tối thiểu và tối đa được phép để giữ kích thước.
Andi R

0

nếu bạn muốn lấy vị trí chỉ mục của các giá trị ngoại lai idx_listsẽ trả lại nó.

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

Đối với một tập hợp hình ảnh (mỗi hình ảnh có 3 kích thước), nơi tôi muốn loại bỏ các giá trị ngoại lệ cho mỗi pixel mà tôi đã sử dụng:

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)

Sau đó, có thể tính giá trị trung bình:

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

(Tôi sử dụng nó cho Phép trừ nền)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.