394 Stimmen

Erkennen und Ausschließen von Ausreißern in einem Pandas DataFrame

Ich habe ein Pandas-Datenrahmen mit wenigen Spalten. Jetzt weiß ich, dass bestimmte Zeilen Ausreißer basierend auf einem bestimmten Spaltenwert sind. Zum Beispiel hat die Spalte Vol alle Werte um 12xx und ein Wert ist 4000 (Ausreißer). Ich möchte diese Zeilen ausschließen, die die Spalte Vol haben.

Also muss ich im Grunde einen Filter auf den Datenrahmen setzen, so dass wir alle Zeilen auswählen, in denen die Werte einer bestimmten Spalte innerhalb, sagen wir, 3 Standardabweichungen vom Mittelwert liegen.

Wie kann man das elegant erreichen?

428voto

tanemaki Punkte 4899

Verwenden Sie scipy.stats.zscore

Entfernen Sie alle Zeilen, die Ausreißer in mindestens einer Spalte haben

Wenn Sie mehrere Spalten in Ihrem DataFrame haben und alle Zeilen entfernen möchten, die Ausreißer in mindestens einer Spalte haben, würde der folgende Ausdruck dies auf einen Schlag erledigen:

import pandas as pd
import numpy as np
from scipy import stats

df = pd.DataFrame(np.random.randn(100, 3))

df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]

Beschreibung:

  • Für jede Spalte berechnet es zunächst den Z-Score jedes Werts in der Spalte relativ zum Spaltenmittelwert und zur Standardabweichung.
  • Dann nimmt es den absoluten Z-Score, weil die Richtung nicht wichtig ist, sondern nur ob er unterhalb des Schwellenwerts liegt.
  • ( < 3).all(axis=1) überprüft, ob für jede Zeile alle Spaltenwerte innerhalb von 3 Standardabweichungen vom Mittelwert liegen
  • Schließlich wird das Ergebnis dieser Bedingung verwendet, um das DataFrame zu indizieren.

Filtern Sie andere Spalten basierend auf einer einzelnen Spalte

Genauso wie oben, aber geben Sie eine Spalte für den zscore an, z.B. df[0], und entfernen Sie .all(axis=1).

df[np.abs(stats.zscore(df[0])) < 3]

241voto

user6903745 Punkte 5267

Für jede Ihrer Dataframe-Spalten können Sie das Quantil mit erhalten:

q = df["col"].quantile(0.99)

und dann filtern mit:

df[df["col"] < q]

Wenn Sie untere und obere Ausreißer entfernen müssen, kombinieren Sie die Bedingung mit einer UND-Anweisung:

q_low = df["col"].quantile(0.01)
q_hi  = df["col"].quantile(0.99)

df_filtered = df[(df["col"] < q_hi) & (df["col"] > q_low)]

192voto

CT Zhu Punkte 48823

Verwenden Sie die boolean-Indizierung wie in numpy.array

df = pd.DataFrame({'Data':np.random.normal(size=200)})
# Beispiel-Datensatz mit normal verteilter Daten. 

df[np.abs(df.Data-df.Data.mean()) <= (3*df.Data.std())]
# Behalten Sie nur diejenigen, die innerhalb von +3 bis -3 Standardabweichungen in der Spalte 'Data' liegen.

df[~(np.abs(df.Data-df.Data.mean()) > (3*df.Data.std()))]
# Oder wenn Sie es lieber andersherum bevorzugen

Für eine Serie ist es ähnlich:

S = pd.Series(np.random.normal(size=200))
S[~((S-S.mean()).abs() > 3*S.std())]

54voto

ascripter Punkte 4756

Bevor wir die eigentliche Frage beantworten, sollten wir eine weitere stellen, die je nach Art Ihrer Daten sehr relevant ist:

Was ist ein Ausreißer?

Stellen Sie sich die Wertreihe [3, 2, 3, 4, 999] vor (wobei die 999 scheinbar nicht hineinpasst) und analysieren Sie verschiedene Möglichkeiten der Ausreißererfassung

Z-Score

Das Problem hierbei ist, dass der betreffende Wert unsere Maße Mittelwert und Standardabweichung stark verfälscht, was zu unauffälligen z-Scores von ungefähr [-0.5, -0.5, -0.5, -0.5, 2.0] führt, wobei jeder Wert innerhalb von zwei Standardabweichungen des Mittelwerts bleibt. Ein sehr großer Ausreißer kann daher Ihre gesamte Bewertung von Ausreißern verzerren. Ich würde von diesem Ansatz abraten.

Quantile-Filter

Ein wesentlich robusterer Ansatz wird in dieser Antwort beschrieben, indem die unteren und oberen 1% der Daten eliminiert werden. Dies eliminiert jedoch einen festen Anteil unabhängig davon, ob diese Daten wirklich Ausreißer sind. Sie könnten viele gültige Daten verlieren und andererseits immer noch einige Ausreißer behalten, wenn mehr als 1% oder 2% Ihrer Daten Ausreißer sind.

IQR-Abstand vom Median

Eine noch robusterer Version des Quantile-Prinzips: Eliminieren Sie alle Daten, die mehr als f Mal die Interquartilsabstand vom Median der Daten entfernt sind. Das ist auch die Transformation, die beispielsweise von sklearn's RobustScaler verwendet wird. IQR und Median sind robust gegenüber Ausreißern, sodass Sie die Probleme des z-Wert-Ansatzes umgehen.

In einer Normalverteilung haben wir ungefähr iqr=1.35*s, sodass Sie einen z=3 eines z-Wert-Filters in einen f=2.22 eines iqr-Filters übersetzen würden. Dies würde die 999 im obigen Beispiel ausschließen.

Die grundlegende Annahme ist, dass zumindest die "mittlere Hälfte" Ihrer Daten gültig ist und die Verteilung gut widerspiegelt, während Sie auch durcheinander kommen, wenn Ihre Verteilung breite Schwänze und ein schmales q_25%-bis-q_75%-Intervall hat.

Fortgeschrittene statistische Methoden

Natürlich gibt es ausgefeilte mathematische Methoden wie das Peirce-Kriterium, den Grubb's Test oder den Dixon's Q-Test, um nur einige zu nennen, die auch für nicht normalverteilte Daten geeignet sind. Keiner von ihnen ist leicht umsetzbar und wird daher nicht weiter behandelt.

Code

Austausch aller Ausreißer für alle numerischen Spalten durch np.nan in einem Beispiel-Datenrahmen. Die Methode ist gegenüber allen von pandas bereitgestellten dtypes robust und kann problemlos auf Datenrahmen mit gemischten Typen angewendet werden:

import pandas as pd
import numpy as np                                     

# Beispieldaten aller dtypes in pandas (Spalte 'a' hat einen Ausreißer)         # dtype:
df = pd.DataFrame({'a': list(np.random.rand(8)) + [123456, np.nan],       # float64
                   'b': [0,1,2,3,np.nan,5,6,np.nan,8,9],                  # int64
                   'c': [np.nan] + list("qwertzuio"),                     # object
                   'd': [pd.to_datetime(_) for _ in range(10)],           # datetime64[ns]
                   'e': [pd.Timedelta(_) for _ in range(10)],             # timedelta[ns]
                   'f': [True] * 5 + [False] * 5,                         # bool
                   'g': pd.Series(list("abcbabbcaa"), dtype="category")}) # category
cols = df.select_dtypes('number').columns  # beschränkt auf a (float), b (int) und e (timedelta)
df_sub = df.loc[:, cols]

# OPTION 1: z-Wert-Filter: z-Wert < 3
lim = np.abs((df_sub - df_sub.mean()) / df_sub.std(ddof=0)) < 3

# OPTION 2: Quantile-Filter: Verwerfen der oberen und unteren 1% der Werte
lim = np.logical_and(df_sub < df_sub.quantile(0.99, numeric_only=False),
                     df_sub > df_sub.quantile(0.01, numeric_only=False))

# OPTION 3: iqr-Filter: innerhalb von 2,22 IQR (äquivalent zu z-Wert < 3)
iqr = df_sub.quantile(0.75, numeric_only=False) - df_sub.quantile(0.25, numeric_only=False)
lim = np.abs((df_sub - df_sub.median()) / iqr) < 2.22

# Ausreißer durch NaN ersetzen
df.loc[:, cols] = df_sub.where(lim, np.nan)

Um alle Zeilen zu löschen, die mindestens einen NaN-Wert enthalten:

df.dropna(subset=cols, inplace=True) # lösche Zeilen mit NaN in numerischen Spalten
# oder
df.dropna(inplace=True)  # lösche Zeilen mit NaN in jeder Spalte

Verwendung von pandas 1.3 Funktionen:

52voto

Alexander Punkte 96032

Diese Antwort ist ähnlich wie die von @tanemaki, verwendet jedoch einen lambda-Ausdruck anstelle von scipy stats.

df = pd.DataFrame(np.random.randn(100, 3), columns=list('ABC'))

standard_deviations = 3
df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < standard_deviations)
   .all(axis=1)]

Um das DataFrame zu filtern, in dem nur eine Spalte (z. B. 'B') innerhalb von drei Standardabweichungen liegt:

df[((df['B'] - df['B'].mean()) / df['B'].std()).abs() < standard_deviations]

Siehe hier, wie man diesen Z-Score auf rollende Basis anwendet: Rolling Z-score applied to pandas dataframe

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X