Existe-t-il un système intégré numpy pour rejeter les valeurs aberrantes d'une liste

101

Y a-t-il un système intégré numpy pour faire quelque chose comme ce qui suit? Autrement dit, prenez une liste det retournez une liste filtered_davec tous les éléments périphériques supprimés en fonction d'une répartition supposée des points dans 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]

Je dis «quelque chose comme» parce que la fonction pourrait permettre des distributions variables (poisson, gaussien, etc.) et des seuils aberrants variables au sein de ces distributions (comme celui mque j'ai utilisé ici).

Aaren
la source
Connexes: scipy.stats peut-il identifier et masquer les valeurs aberrantes évidentes? , bien que cette question semble concerner des situations plus complexes. Pour la tâche simple que vous avez décrite, un package externe semble exagéré.
Sven Marnach
Je pensais qu'étant donné le nombre de fonctions intégrées dans la bibliothèque numpy principale, il était étrange qu'il n'y ait rien à faire. Cela semble être une chose assez courante à faire avec des données brutes et bruyantes.
aaren le

Réponses:

104

Cette méthode est presque identique à la vôtre, juste plus numpyst (fonctionne également sur les tableaux numpy uniquement):

def reject_outliers(data, m=2):
    return data[abs(data - np.mean(data)) < m * np.std(data)]
eumiro
la source
3
Cette méthode fonctionne assez bien si elle mest suffisamment grande (par exemple m=6), mais pour de petites valeurs de mcelle-ci souffre du fait que la variance n'est pas des estimateurs robustes.
Benjamin Bannier
30
ce n'est pas vraiment une plainte concernant la méthode, mais une plainte concernant la vague notion de `` valeur aberrante ''
Eelco Hoogendoorn
comment choisissez-vous un m?
john ktejik le
1
Je n'ai pas réussi à faire fonctionner cela. Je continue à obtenir une erreur de retour de données [abs (data - np.mean (data)) <m * np.std (data)] TypeError: seuls les tableaux scalaires entiers peuvent être convertis en un index scalaire OU il fige juste mon programme
john ktejik
@johnktejik data arg doit être un tableau numpy.
Sander van Leeuwen
181

Un élément important en ce qui concerne les valeurs aberrantes est que l'on devrait essayer d'utiliser des estimateurs aussi robustes que possible. La moyenne d'une distribution sera biaisée par les valeurs aberrantes mais, par exemple, la médiane sera beaucoup moins élevée.

S'appuyant sur la réponse d'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]

Ici, j'ai remplacé la moyenne par la médiane la plus robuste et l'écart-type par la distance médiane absolue à la médiane. J'ai ensuite mis à l'échelle les distances en fonction de leur (à nouveau) valeur médiane afin que ce msoit sur une échelle relative raisonnable.

Notez que pour que la data[s<m]syntaxe fonctionne, il datadoit s'agir d'un tableau numpy.

Benjamin Bannier
la source
5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm il s'agit essentiellement du score Z modifié référencé ici, mais avec un seuil différent. Si mes calculs sont corrects , ils recommandent un m de 3.5 / .6745 ~= 5.189(ils se multiplient spar 0,6745 et spécifient un mde 3,5 ... prennent également abs(s)). Quelqu'un peut-il expliquer le choix de m? Ou est-ce quelque chose que vous identifierez à partir de votre ensemble de données particulier?
Charlie G
2
@BenjaminBannier: Pouvez-vous s'il vous plaît fournir une explication concrète pour choisir une valeur pour mdes déclarations plutôt que duveteuses comme "l'interaction de la pureté et de l'efficacité"?
stackoverflowuser2010
1
@ stackoverflowuser2010: Comme je l'ai dit, cela dépend de vos exigences spécifiques, c'est-à-dire de la propreté dont nous avons besoin pour signaler que l'échantillon est (faux positifs), ou du nombre de mesures de signal que nous pouvons nous permettre de jeter pour garder le signal propre (faux négatifs) . Comme pour un exemple d'évaluation spécifique pour un certain cas d'utilisation, voir par exemple, desy.de/~blist/notes/whyeffpur.ps.gz .
Benjamin Bannier
2
J'obtiens l'erreur suivante lorsque j'appelle la fonction avec une liste de flotteurs:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis
2
@Charlie, si vous regardez la figure itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD , vous verrez que lorsque vous traitez avec une distribution normale (ce qui en fait n'est pas le cas, vous auriez besoin du z-scores modifiés) avec SD = 1, vous avez MAD ~ 0,68, ce qui explique le facteur d'échelle. Le choix de m = 3,5 implique donc que vous souhaitez rejeter 0,05% des données.
Fato39
13

La réponse de Benjamin Bannier donne un passage lorsque la médiane des distances par rapport à la médiane est de 0, donc j'ai trouvé cette version modifiée un peu plus utile pour les cas donnés dans l'exemple ci-dessous.

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]

Exemple:

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

Donne:

[[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
la source
9

S'appuyant sur Benjamin, en utilisant pandas.Serieset en remplaçant MAD par 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]

Par exemple, si vous définissez iq_range=0.6, les percentiles de l'intervalle interquartile deviendraient :, 0.20 <--> 0.80donc plus de valeurs aberrantes seront incluses.

Ankostis
la source
4

Une alternative consiste à faire une estimation robuste de l'écart type (en supposant des statistiques gaussiennes). En regardant les calculatrices en ligne, je vois que le 90% centile correspond à 1,2815σ et le 95% est 1,645σ ( http://vassarstats.net/tabs.html?#z )

À titre d'exemple simple:

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)

Le résultat que j'obtiens est:

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

Ce qui est proche de la valeur attendue de 2.

Si nous voulons supprimer des points au-dessus / en dessous de 5 écarts-types (avec 1000 points, nous nous attendrions à 1 valeur> 3 écarts-types):

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

Qui donne:

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

Je n'ai aucune idée de l'approche la plus efficace / robuste

Chris
la source
3

Je voudrais fournir deux méthodes dans cette réponse, solution basée sur le "score z" et solution basée sur "IQR".

Le code fourni dans cette réponse fonctionne à la fois sur un numpytableau dim simple et un numpytableau multiple .

Importons d'abord quelques modules.

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

méthode basée sur le score z

Cette méthode testera si le nombre se situe en dehors des trois écarts types. Sur la base de cette règle, si la valeur est aberrante, la méthode retournera true, sinon, retournera 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

Méthode basée sur l'IQR

Cette méthode testera si la valeur est inférieure q1 - 1.5 * iqrou supérieure à q3 + 1.5 * iqr, ce qui est similaire à la méthode de tracé de 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)

Enfin, si vous souhaitez filtrer les valeurs aberrantes, utilisez un numpysélecteur.

Bonne journée.

Pertes Don
la source
3

Considérez que toutes les méthodes ci-dessus échouent lorsque votre écart type devient très important en raison d'énormes valeurs aberrantes.

( Simalar car l'évaluation moyenne échoue et devrait plutôt évaluer la médiane. Cependant, la moyenne est "plus sujette à une erreur telle que stdDv". )

Vous pouvez essayer d'appliquer itérativement votre algorithme ou filtrer en utilisant l'intervalle interquartile: (ici "facteur" se rapporte à un intervalle * sigma, mais uniquement lorsque vos données suivent une distribution gaussienne)

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
la source
Désolé, j'ai oublié qu'il existe déjà une suggestion IQR ci-dessus. Dois-je quand même laisser cette réponse en raison d'un code plus court ou le supprimer?
K.Foe
1

Je voulais faire quelque chose de similaire, sauf définir le nombre sur NaN plutôt que de le supprimer des données, car si vous le supprimez, vous modifiez la longueur, ce qui peut gâcher le traçage (c'est-à-dire si vous ne supprimez que les valeurs aberrantes d'une colonne dans une table , mais vous en avez besoin pour rester le même que les autres colonnes afin que vous puissiez les tracer les uns contre les autres).

Pour ce faire, j'ai utilisé les fonctions de masquage de 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
la source
Vous pouvez également les np.clip aux valeurs minimales et maximales autorisées pour conserver les dimensions.
Andi R
0

si vous souhaitez obtenir la position d'index des valeurs aberrantes, vous la renverrez 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]
Caner Erden
la source
0

Pour un ensemble d' images (chaque image a 3 dimensions), où je voulais rejeter les valeurs aberrantes pour chaque pixel que j'ai utilisé:

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)

Ensuite, il est possible de calculer la moyenne:

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

(Je l'utilise pour la soustraction de fond)

ron653
la source