Есть ли встроенный numpy для отклонения выбросов из списка


101

Есть ли встроенный numpy, чтобы делать что-то вроде следующего? То есть возьмите список 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

Я подумал, что, учитывая количество встроенных функций в основной библиотеке numpy, было странно, что с этим нечего было делать. Кажется, это обычное дело с сырыми, зашумленными данными.
aaren

Ответы:


104

Этот метод почти идентичен вашему, только больше numpyst (также работает только с массивами 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

31
это на самом деле не жалоба на метод, а жалоба на расплывчатое понятие «выброса»
Eelco Hoogendoorn

как выбрать м?
john ktejik

1
Я не заставил это работать. Я все время получаю сообщение об ошибке return data [abs (data - np.mean (data)) <m * np.std (data)] TypeError: только целочисленные скалярные массивы могут быть преобразованы в скалярный индекс ИЛИ это просто замораживает мою программу
Джон ktejik

1
@johnktejik data arg должен быть массивом numpy.
Sander van Leeuwen

182

При работе с выбросами важно то, что нужно стараться использовать оценки как можно более надежные. Среднее значение распределения будет смещено из-за выбросов, но, например, медиана будет намного меньше.

Основываясь на ответе Эумиро:

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должен быть массив numpy.


5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm это в основном модифицированная Z-оценка, на которую здесь ссылаются, но с другим порогом. Если мои расчеты верны , они рекомендуют m 3.5 / .6745 ~= 5.189(они умножают sна 0,6745 и указывают m3,5 ... также берут abs(s)). Кто-нибудь может объяснить выбор м? Или это то, что вы определите в своем конкретном наборе данных?
Charlie G

2
@BenjaminBannier: Не могли бы вы дать какое-нибудь конкретное объяснение выбора значения, mа не пушистых заявлений, таких как «взаимодействие чистоты и эффективности»?
stackoverflowuser2010

1
@ 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
@Charlie, если вы посмотрите на рисунок itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD , вы увидите, что при нормальном распределении (на самом деле это не тот случай, когда вам понадобится модифицированные z-оценки) с SD = 1, у вас есть MAD ~ 0,68, что объясняет коэффициент масштабирования. Следовательно, выбор m = 3,5 означает, что вы хотите отбросить 0,05% данных.
Fato39

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)

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 стандартных отклонений (при 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. Должен ли я все равно оставить этот ответ из-за более короткого кода или удалить его?
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))

(Я использую его для вычитания фона)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.