มีจำนวนในตัวเพื่อปฏิเสธค่าผิดปกติจากรายการหรือไม่


104

มีบิวด์อินเพื่อทำสิ่งต่อไปนี้หรือไม่? นั่นคือรับรายการ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

ฉันคิดว่าเมื่อพิจารณาจากจำนวนบิวด์อินในไลบรารีหลักแล้วมันแปลกที่ไม่มีอะไรให้ทำ ดูเหมือนเป็นเรื่องธรรมดาที่จะทำกับข้อมูลดิบที่มีเสียงดัง
aaren

คำตอบ:


108

วิธีนี้เกือบจะเหมือนกับของคุณเพียงแค่มีจำนวนมากกว่า (ทำงานกับอาร์เรย์ numpy เท่านั้น):

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

4
วิธีนี้ใช้ได้ผลดีพอหากmมีขนาดใหญ่เพียงพอ (เช่นm=6) แต่สำหรับค่าเล็ก ๆmนี้จะทนทุกข์ทรมานจากค่าเฉลี่ยที่ความแปรปรวนไม่ได้เป็นตัวประมาณที่มีประสิทธิภาพ
Benjamin Bannier

32
นั่นไม่ใช่การร้องเรียนเกี่ยวกับวิธีการนี้ แต่เป็นการร้องเรียนเกี่ยวกับความคิดที่คลุมเครือของ 'คนนอก'
Eelco Hoogendoorn

1
คุณจะเลือกม. ได้อย่างไร?
john ktejik

1
ฉันยังไม่ได้ทำงานนี้ ฉันได้รับข้อมูลส่งคืนข้อผิดพลาดอยู่เรื่อย ๆ [abs (data - np.mean (data)) <m * np.std (data)] TypeError: เฉพาะอาร์เรย์สเกลาร์จำนวนเต็มเท่านั้นที่สามารถแปลงเป็นดัชนีสเกลาร์ได้หรือเพียงแค่หยุดโปรแกรมของฉัน
john ktejik

1
@johnktejik data arg ต้องเป็นอาร์เรย์ numpy
Sander van Leeuwen

184

สิ่งที่สำคัญเมื่อต้องรับมือกับค่าผิดปกติคือเราควรพยายามใช้ตัวประมาณให้มีประสิทธิภาพมากที่สุด ค่าเฉลี่ยของการกระจายจะเอนเอียงโดยค่าผิดปกติ แต่เช่นค่ามัธยฐานจะน้อยกว่ามาก

จากคำตอบของ 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ต้องเป็นอาร์เรย์จำนวนนับ


5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htmโดยพื้นฐานแล้วนี่คือคะแนน Z ที่แก้ไขแล้วซึ่งอ้างอิงที่นี่ แต่มีเกณฑ์ที่แตกต่างกัน ถ้าคณิตศาสตร์ของฉันถูกต้องพวกเขาแนะนำ m ของ3.5 / .6745 ~= 5.189(พวกเขาคูณsด้วย. 6745 และระบุm3.5 ... ด้วยabs(s)) ใครช่วยอธิบายการเลือกม. หรือเป็นสิ่งที่คุณจะระบุได้จากชุดข้อมูลเฉพาะของคุณ
Charlie G

2
@BenjaminBannier: คุณช่วยให้คำอธิบายที่เป็นรูปธรรมสำหรับการเลือกค่าที่mมากกว่าคำพูดที่ดูอ่อนหวานเช่น "การมีส่วนร่วมของความบริสุทธิ์และประสิทธิภาพ" ได้ไหม
stackoverflowuser2010

2
@ stackoverflowuser2010: อย่างที่บอกไปว่าสิ่งนี้ขึ้นอยู่กับความต้องการเฉพาะของคุณนั่นคือความสะอาดที่เราต้องส่งสัญญาณให้ตัวอย่าง (ผลบวกปลอม) หรือจำนวนสัญญาณที่เราสามารถทิ้งได้เพื่อให้สัญญาณสะอาด (เชิงลบเท็จ) . ในฐานะที่เป็นสำหรับการประเมินผลตัวอย่างที่เฉพาะเจาะจงสำหรับกรณีการใช้งานบางอย่างให้ดูเช่นdesy.de/~blist/notes/whyeffpur.ps.gz
Benjamin Bannier

2
ฉันได้รับข้อผิดพลาดต่อไปนี้เมื่อฉันเรียกใช้ฟังก์ชันพร้อมรายการลอย:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis

2
@ ชาร์ลีถ้าคุณดูรูปitl.nist.gov/div898/handbook/eda/section3/eda356.htm#MADคุณจะเห็นว่าเมื่อจัดการกับการแจกแจงแบบปกติ (ซึ่งในความเป็นจริงไม่ใช่กรณีที่คุณต้องการ คะแนน z ที่แก้ไข) ด้วย SD = 1 คุณมี MAD ~ 0.68 ซึ่งอธิบายปัจจัยการปรับขนาด ดังนั้นการเลือก m = 3.5 จึงหมายความว่าคุณต้องการทิ้งข้อมูล 0.05%
Fato39

14

คำตอบของ Benjamin Bannier ให้ค่า pass-through เมื่อค่ามัธยฐานของระยะทางจากค่ามัธยฐานคือ 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)

10

การสร้างโดยใช้pandas.Seriesและแทนที่MAD ด้วย 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 ส่วนเบี่ยงเบนมาตรฐาน (โดยมี 1,000 คะแนนเราคาดหวัง 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

วิธีนี้จะทดสอบว่าตัวเลขอยู่นอกค่าเบี่ยงเบนมาตรฐานทั้งสามหรือไม่ ตามกฎนี้หากค่าผิดปกติเมธอดจะคืนค่าจริงถ้าไม่ส่งกลับเท็จ

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

พิจารณาว่าวิธีการทั้งหมดข้างต้นล้มเหลวเมื่อค่าเบี่ยงเบนมาตรฐานของคุณมีขนาดใหญ่มากเนื่องจากค่าผิดปกติมาก

( 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 :

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 ให้เป็นค่าต่ำสุดและสูงสุดที่อนุญาตเพื่อเก็บมิติไว้
Andi 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.