Existe um numpy embutido para rejeitar outliers de uma lista

101

Existe um constrangimento interno para fazer algo como o seguinte? Ou seja, pegue uma lista de retorne uma lista filtered_dcom todos os elementos remotos removidos com base em alguma distribuição presumida dos pontos em 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]

Digo 'algo como' porque a função pode permitir distribuições variáveis ​​(poisson, gaussiana, etc.) e limites de valores discrepantes dentro dessas distribuições (como o mque usei aqui).

Aaren
fonte
Relacionado: O scipy.stats pode identificar e mascarar outliers óbvios? , embora essa questão pareça lidar com situações mais complexas. Para a tarefa simples que você descreveu, um pacote externo parece um exagero.
Sven Marnach
Eu estava pensando que, dado o número de builtins na biblioteca principal entorpecida, era estranho que não houvesse nada para fazer isso. Parece uma coisa bastante comum de se fazer com dados brutos e barulhentos.
aaren

Respostas:

104

Este método é quase idêntico ao seu, só que mais numpyst (funcionando também apenas em matrizes numpy):

def reject_outliers(data, m=2):
    return data[abs(data - np.mean(data)) < m * np.std(data)]
eumiro
fonte
3
Esse método funciona bem se mfor suficientemente grande (por exemplo m=6), mas para pequenos valores misso sofre da média a variância não sendo estimadores robustos.
Benjamin Bannier
30
isso não é realmente uma reclamação sobre o método, mas uma reclamação sobre a vaga noção de um 'outlier'
Eelco Hoogendoorn
como você escolhe um m?
john ktejik
1
Eu não fiz isso funcionar. Eu continuo recebendo dados de retorno de erro [abs (dados - np.mean (dados)) <m * np.std (dados)] TypeError: somente matrizes escalares inteiras podem ser convertidas em um índice escalar OU apenas congela meu programa
john ktejik
@johnktejik data arg precisa ser um array numpy.
Sander van Leeuwen
181

Algo importante ao lidar com outliers é que se deve tentar usar estimadores o mais robustos possível. A média de uma distribuição será influenciada por outliers, mas, por exemplo, a mediana será muito menor.

Com base na resposta de 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]

Aqui, substituí a média pela mediana mais robusta e o desvio padrão pela distância absoluta da mediana até a mediana. Em seguida, dimensionei as distâncias por seus (novamente) valores medianos de modo que mfiquem em uma escala relativa razoável.

Observe que para a data[s<m]sintaxe funcionar, datadeve ser uma matriz numpy.

Benjamin Bannier
fonte
5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm é basicamente o Z-score modificado referenciado aqui, mas com um limite diferente. Se minha matemática estiver certa, eles recomendam um m de 3.5 / .6745 ~= 5.189(eles multiplicam spor 0,6745 e especificam um mde 3,5 ... também leva abs(s)). Alguém pode explicar a escolha de m? Ou é algo que você identificará em seu conjunto de dados específico?
Charlie G,
2
@BenjaminBannier: Você poderia fornecer alguma explicação concreta para a escolha de um valor em mvez de afirmações fofas como "interação de pureza e eficiência"?
stackoverflowuser2010
1
@ stackoverflowuser2010: Como eu disse, isso depende de seus requisitos específicos, ou seja, quão limpo precisamos para a amostra do sinal (falsos positivos) ou quantas medições de sinal podemos descartar para manter o sinal limpo (falsos negativos) . Quanto a uma avaliação de exemplo específica para um determinado caso de uso, consulte, por exemplo, desy.de/~blist/notes/whyeffpur.ps.gz .
Benjamin Bannier
2
Recebo o seguinte erro quando chamo a função com uma lista de flutuadores:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis
2
@Charlie, se você olhar a figura itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD , você verá que ao lidar com a distribuição normal (o que na verdade não é o caso, você precisaria do escores z modificados) com SD = 1, você tem MAD ~ 0,68, o que explica o fator de escala. A escolha de m = 3,5, portanto, implica que você deseja descartar 0,05% dos dados.
Fato39 de
13

A resposta de Benjamin Bannier fornece uma passagem quando a mediana das distâncias da mediana é 0, portanto, achei essa versão modificada um pouco mais útil para os casos fornecidos no exemplo abaixo.

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]

Exemplo:

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

Dá:

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

Com base no Benjamin, usando pandas.Seriese substituindo MAD por 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]

Por exemplo, se você definir iq_range=0.6, os percentis do intervalo interquartil se tornará :, 0.20 <--> 0.80portanto, mais outliers serão incluídos.

Ankostis
fonte
4

Uma alternativa é fazer uma estimativa robusta do desvio padrão (assumindo estatísticas gaussianas). Procurando calculadoras online, vejo que o percentil 90% corresponde a 1,2815σ e 95% é 1,645σ ( http://vassarstats.net/tabs.html?#z )

Como um exemplo simples:

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)

O resultado que obtenho é:

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

Que está próximo do valor esperado de 2.

Se quisermos remover pontos acima / abaixo de 5 desvios padrão (com 1000 pontos, esperaríamos 1 valor> 3 desvios padrão):

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

Que dá:

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

Não tenho ideia de qual abordagem é a mais eficiente / robusta

Chris
fonte
3

Eu gostaria de fornecer dois métodos nesta resposta, solução baseada em "pontuação z" e solução baseada em "IQR".

O código fornecido nesta resposta funciona tanto em numpymatriz dim única quanto em matriz múltipla numpy.

Vamos importar alguns módulos primeiro.

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

método baseado em pontuação z

Este método testará se o número está fora dos três desvios padrão. Com base nesta regra, se o valor for outlier, o método retornará verdadeiro, caso contrário, retornará falso.

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

Método baseado em IQR

Este método testará se o valor é menor q1 - 1.5 * iqrou maior que q3 + 1.5 * iqr, que é semelhante ao método de plotagem do 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)

Finalmente, se você quiser filtrar os outliers, use um numpyseletor.

Tenha um bom dia.

Perdas Don
fonte
3

Considere que todos os métodos acima falham quando o desvio padrão fica muito grande devido a grandes valores discrepantes.

( Simalar, pois a média de cálculo falha e deveria, em vez disso, calcular a mediana. No entanto, a média é "mais propensa a um erro como o stdDv". )

Você pode tentar aplicar iterativamente seu algoritmo ou filtrar usando o intervalo interquartil: (aqui, "fator" está relacionado a um intervalo * sigma, mas apenas quando seus dados seguem uma distribuição gaussiana)

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
fonte
Desculpe, esqueci que já existe uma sugestão de IQR acima. Devo deixar esta resposta mesmo assim devido ao código mais curto ou excluí-lo?
K. Foe
1

Eu queria fazer algo semelhante, exceto definir o número como NaN em vez de removê-lo dos dados, já que se você removê-lo, você altera o comprimento, o que pode atrapalhar a plotagem (ou seja, se você apenas remover outliers de uma coluna em uma tabela , mas você precisa que ele permaneça o mesmo que as outras colunas para que possa plotá-las uma contra a outra).

Para fazer isso, usei as funções de mascaramento do 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
fonte
Você também pode np.clip-los para os valores mínimo e máximo permitidos para manter as dimensões.
Andi R
0

se você quiser obter a posição do índice dos outliers idx_list, ele será retornado.

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
fonte
0

Para um conjunto de imagens (cada imagem tem 3 dimensões), onde eu queria rejeitar outliers para cada pixel que usei:

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)

Então, é possível calcular a média:

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

(Eu uso para subtração de fundo)

ron653
fonte