Чи є вбудований нуме, щоб відхилити людей зі списку


101

Чи є вбудований нумез, щоб зробити щось на зразок наступного? Тобто, візьміть список 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 ідентифікувати та замаскувати очевидних людей? , хоча, здається, це питання стосується складніших ситуацій. Для простого завдання, яке ви описали, зовнішній пакет здається непосильним.
Свен Марнах

Я думав, що, враховуючи кількість вбудованих в основній бібліотеці, було дивно, що робити це нічого. Здається, це досить звичайна справа з необробленими, галасливими даними.
aaren

Відповіді:


104

Цей метод майже ідентичний вашому, просто більше numpyst (також працює лише над numpy-масивами):

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

3
Цей метод працює досить добре, якщо mвін досить великий (наприклад m=6), але для малих значень mцього страждає від середнього значення, дисперсія не є надійною оцінкою.
Бенджамін Баньє

30
це насправді не скарга на метод, але скарга на розпливчасте поняття "
чужий

як ти обираєш м?
John ktejik

1
Я не змусив цього працювати. Я продовжую отримувати дані про повернення помилок [abs (data - np.mean (data)) <m * np.std (data)] TypeError: тільки цілі скалярні масиви можуть бути перетворені в скалярний індекс АБО просто заморожує мою програму
john ktejik

@johnktejik data arg повинен бути нумерованим масивом.
Сандер ван Левен

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повинен бути нумерований масив.


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

1
@ stackoverflowuser2010: Як я вже говорив, це залежить від ваших конкретних вимог, тобто від того, наскільки чистим нам повинен бути зразок сигналу (помилкові позитиви), або скільки вимірювань сигналу ми можемо дозволити собі скинути, щоб зберегти сигнал чистим (помилкові негативи) . Що стосується конкретного прикладу оцінки для певного випадку використання, див., Наприклад, desy.de/~blist/notes/whyeffpur.ps.gz .
Бенджамін Баньє

2
Я отримую таку помилку, коли викликаю функцію зі списком плавців:TypeError: only integer scalar arrays can be converted to a scalar index
Василіс,

2
@Charlie, якщо ви подивитеся на рисунок itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD , ви побачите, що при роботі з нормальним розподілом (що насправді не так, вам знадобиться модифіковані z-бали) з SD = 1, у вас MAD ~ 0,68, що пояснює коефіцієнт масштабування. Отже, вибір m = 3.5 означає, що ви хочете відкинути 0,05% даних.
Фато39,

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.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,22815σ, а 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".

Код, наданий у цій відповіді, працює як на одиночному, так numpyі на множинному numpyмасиві.

Давайте спочатку імпортуємо деякі модулі.

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

z метод на основі балів

Цей метод перевірятиме, чи число не перевищує трьох стандартних відхилень. Виходячи з цього правила, якщо значення зовнішнє, метод поверне 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. Чи варто мені все-таки залишити цю відповідь через коротший код або видалити його?
К. Фое

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

(Я використовую це для віднімання фону)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.