Gibt es eine eingebaute Zahl, um Ausreißer von einer Liste abzulehnen?

100

Gibt es eine eingebaute Nummer, um so etwas wie das Folgende zu tun? Nehmen Sie also eine Liste dund geben Sie eine Liste zurück, filtered_din der alle äußeren Elemente entfernt wurden, basierend auf einer angenommenen Verteilung der Punkte in 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]

Ich sage 'so etwas wie', weil die Funktion möglicherweise unterschiedliche Verteilungen (Poisson, Gauß usw.) und unterschiedliche Ausreißerschwellen innerhalb dieser Verteilungen (wie die mhier verwendeten) zulässt .

aaren
quelle
Verwandte Themen : Kann scipy.stats offensichtliche Ausreißer identifizieren und maskieren? Diese Frage scheint sich jedoch mit komplexeren Situationen zu befassen. Für die einfache Aufgabe, die Sie beschrieben haben, scheint ein externes Paket übertrieben zu sein.
Sven Marnach
Ich dachte, dass es angesichts der Anzahl der eingebauten Funktionen in der Hauptbibliothek seltsam war, dass es nichts gab, was dies tun könnte. Es scheint ziemlich üblich zu sein, mit rohen, verrauschten Daten zu tun.
Aaren

Antworten:

102

Diese Methode ist fast identisch mit Ihrer, nur mehr Numpyste (funktioniert auch nur bei Numpy-Arrays):

def reject_outliers(data, m=2):
    return data[abs(data - np.mean(data)) < m * np.std(data)]
Eumiro
quelle
3
Diese Methode funktioniert gut genug, wenn sie mausreichend groß ist (z. B. m=6), aber für kleine Werte mleidet dies unter dem Mittelwert, dass die Varianz keine robusten Schätzer sind.
Benjamin Bannier
29
Das ist aber nicht wirklich eine Beschwerde über die Methode, sondern eine Beschwerde über die vage Vorstellung eines "Ausreißers"
Eelco Hoogendoorn
Wie wählst du ein m?
John Ktejik
1
Ich habe das nicht zum Laufen gebracht. Ich erhalte immer wieder einen Fehler, der Daten zurückgibt [abs (data - np.mean (data)) <m * np.std (data)] TypeError: Nur ganzzahlige skalare Arrays können in einen skalaren Index konvertiert werden, oder es friert nur mein Programm ein
john ktejik
@johnktejik data arg muss ein numpy Array sein.
Sander van Leeuwen
181

Im Umgang mit Ausreißern ist es wichtig, Schätzer so robust wie möglich einzusetzen. Der Mittelwert einer Verteilung wird durch Ausreißer verzerrt, aber z. B. ist der Median viel geringer.

Aufbauend auf Eumiros Antwort:

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]

Hier habe ich den Mittelwert durch den robusteren Median und die Standardabweichung durch den absoluten Medianabstand zum Median ersetzt. Ich habe dann die Abstände um ihren (erneuten) Medianwert skaliert, so dass dies mauf einer vernünftigen relativen Skala liegt.

Beachten Sie, dass die data[s<m]Syntax dataein Numpy-Array sein muss , damit die Syntax funktioniert .

Benjamin Bannier
quelle
5
itl.nist.gov/div898/handbook/eda/section3/eda35h.htm Dies ist im Grunde der modifizierte Z-Score, auf den hier verwiesen wird, jedoch mit einem anderen Schwellenwert. Wenn meine Mathematik stimmt, empfehlen sie ein m von 3.5 / .6745 ~= 5.189(sie multiplizieren smit .6745 und geben ein mvon 3.5 an ... nehmen auch abs(s)). Kann jemand die Wahl von m erklären? Oder ist es etwas, das Sie anhand Ihres speziellen Datensatzes identifizieren werden?
Charlie G
2
@BenjaminBannier: Können Sie bitte eine konkrete Erklärung für die Auswahl eines Wertes für mflauschige Aussagen wie "Zusammenspiel von Reinheit und Effizienz" geben?
stackoverflowuser2010
1
@ stackoverflowuser2010: Wie ich bereits sagte, hängt dies von Ihren spezifischen Anforderungen ab, dh davon, wie sauber wir die Probe signalisieren müssen (falsch positive Ergebnisse) oder wie viele Signalmessungen wir uns leisten können, um das Signal sauber zu halten (falsch negative Ergebnisse). . Eine spezifische Beispielbewertung für einen bestimmten Anwendungsfall finden Sie beispielsweise unter desy.de/~blist/notes/whyeffpur.ps.gz .
Benjamin Bannier
2
Ich erhalte die folgende Fehlermeldung, wenn ich die Funktion mit einer Liste von Floats aufrufe:TypeError: only integer scalar arrays can be converted to a scalar index
Vasilis
2
@Charlie, wenn Sie sich die Abbildung itl.nist.gov/div898/handbook/eda/section3/eda356.htm#MAD ansehen, werden Sie feststellen , dass Sie beim Umgang mit Normalverteilung (was in der Tat nicht der Fall ist, die benötigen würden modifizierte Z-Scores) mit SD = 1 haben Sie MAD ~ 0,68, was den Skalierungsfaktor erklärt. Die Wahl von m = 3,5 impliziert daher, dass Sie 0,05% der Daten verwerfen möchten.
Fato39
13

Die Antwort von Benjamin Bannier ergibt einen Durchgang, wenn der Median der Abstände vom Median 0 ist. Daher fand ich diese modifizierte Version für Fälle, wie im folgenden Beispiel angegeben, etwas hilfreicher.

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]

Beispiel:

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

Gibt:

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

Aufbauend auf Benjamins, Verwendung pandas.Seriesund Ersetzung von MAD durch 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]

Wenn Sie beispielsweise festlegen iq_range=0.6, werden die Perzentile des Interquartilbereichs zu : 0.20 <--> 0.80, sodass mehr Ausreißer eingeschlossen werden.

Ankostis
quelle
4

Eine Alternative besteht darin, eine robuste Schätzung der Standardabweichung vorzunehmen (unter der Annahme einer Gaußschen Statistik). Wenn ich Online-Rechner nachschaue, sehe ich, dass das 90% -Perzentil 1,2815σ entspricht und das 95% -Interil 1,645σ ( http://vassarstats.net/tabs.html?#z ).

Als einfaches Beispiel:

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)

Die Ausgabe, die ich bekomme, ist:

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

Welches ist in der Nähe des erwarteten Wertes von 2.

Wenn wir Punkte über / unter 5 Standardabweichungen entfernen möchten (mit 1000 Punkten würden wir 1 Wert> 3 Standardabweichungen erwarten):

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

Welches gibt:

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

Ich habe keine Ahnung, welcher Ansatz effizienter / robuster ist

Chris
quelle
3

Ich möchte in dieser Antwort zwei Methoden angeben, eine Lösung basierend auf "z score" und eine Lösung basierend auf "IQR".

Der in dieser Antwort angegebene Code funktioniert sowohl für einzelne Dim- numpyArrays als auch für mehrere numpyArrays.

Importieren wir zunächst einige Module.

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

z Score-basierte Methode

Diese Methode prüft, ob die Anzahl außerhalb der drei Standardabweichungen liegt. Basierend auf dieser Regel gibt die Methode true zurück, wenn der Wert ein Ausreißer ist. Wenn nicht, wird false zurückgegeben.

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-basierte Methode

Diese Methode prüft, ob der Wert kleiner q1 - 1.5 * iqroder größer als ist q3 + 1.5 * iqr, was der Plotmethode von SPSS ähnlich ist.

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)

Wenn Sie die Ausreißer herausfiltern möchten, verwenden Sie einen numpySelektor.

Einen schönen Tag noch.

Verluste Don
quelle
3

Bedenken Sie, dass alle oben genannten Methoden fehlschlagen, wenn Ihre Standardabweichung aufgrund großer Ausreißer sehr groß wird.

( Simalar, da die durchschnittliche Berechnung fehlschlägt und eher den Median berechnen sollte. Der Durchschnitt ist jedoch "anfälliger für Fehler wie stdDv". )

Sie könnten versuchen, Ihren Algorithmus iterativ anzuwenden, oder Sie filtern nach dem Interquartilbereich: (hier bezieht sich "Faktor" auf einen * Sigma-Bereich, jedoch nur, wenn Ihre Daten einer Gaußschen Verteilung folgen)

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. Feind
quelle
Entschuldigung, ich habe übersehen, dass es oben bereits einen IQR-Vorschlag gibt. Sollte ich diese Antwort wegen kürzeren Codes trotzdem hinterlassen oder löschen?
K. Feind
1

Ich wollte etwas Ähnliches tun, außer die Nummer auf NaN zu setzen, anstatt sie aus den Daten zu entfernen, da Sie beim Entfernen die Länge ändern, die das Plotten durcheinander bringen kann (dh wenn Sie nur Ausreißer aus einer Spalte in einer Tabelle entfernen , aber Sie müssen es mit den anderen Spalten identisch halten, damit Sie sie gegeneinander zeichnen können.

Dazu habe ich die Maskierungsfunktionen von numpy verwendet :

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.
quelle
Sie können sie auch auf minimal und maximal zulässige Werte beschränken, um die Abmessungen beizubehalten.
Andi R
0

Wenn Sie die Indexposition der Ausreißer erhalten möchten, idx_listwird diese zurückgegeben.

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

Für eine Reihe von Bildern (jedes Bild hat 3 Dimensionen), bei denen ich Ausreißer für jedes verwendete Pixel ablehnen wollte:

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)

Dann ist es möglich, den Mittelwert zu berechnen:

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

(Ich benutze es für die Hintergrundsubtraktion)

ron653
quelle