Erkennen und Ausschließen von Ausreißern im Pandas-Datenrahmen


197

Ich habe einen Pandas-Datenrahmen mit wenigen Spalten.

Jetzt weiß ich, dass bestimmte Zeilen Ausreißer sind, die auf einem bestimmten Spaltenwert basieren.

Zum Beispiel

Die Spalte 'Vol' enthält alle Werte 12xxund ein Wert ist 4000(Ausreißer).

Jetzt möchte ich die Zeilen ausschließen, die eine solche VolSpalte haben.

Im Wesentlichen muss ich den Datenrahmen so filtern, dass wir alle Zeilen auswählen, in denen die Werte einer bestimmten Spalte beispielsweise innerhalb von 3 Standardabweichungen vom Mittelwert liegen.

Was ist ein eleganter Weg, um dies zu erreichen?

Antworten:


213

Wenn Sie mehrere Spalten in Ihrem Datenrahmen haben und alle Zeilen mit Ausreißern in mindestens einer Spalte entfernen möchten, würde der folgende Ausdruck dies auf einmal tun.

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

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

Beschreibung:

  • Für jede Spalte wird zunächst der Z-Score jedes Werts in der Spalte relativ zum Spaltenmittelwert und zur Standardabweichung berechnet.
  • Dann wird der absolute Z-Score genommen, da die Richtung keine Rolle spielt, nur wenn sie unter dem Schwellenwert liegt.
  • all (Achse = 1) stellt sicher, dass für jede Zeile alle Spalten die Einschränkung erfüllen.
  • Schließlich wird das Ergebnis dieser Bedingung verwendet, um den Datenrahmen zu indizieren.

6
Können Sie erklären, was dieser Code tut? Und vielleicht eine Idee geben, wie ich alle Zeilen entfernen kann, die einen Ausreißer in einer einzelnen angegebenen Spalte haben? Wäre hilfreich. Vielen Dank.
Samthebrand

17
Für jede Spalte wird zunächst der Z-Score jedes Werts in der Spalte relativ zum Spaltenmittelwert und zur Standardabweichung berechnet. Dann wird das Absolut des Z-Scores genommen, da die Richtung keine Rolle spielt, nur wenn sie unter dem Schwellenwert liegt. .all (Achse = 1) stellt sicher, dass für jede Zeile alle Spalten die Einschränkung erfüllen. Schließlich wird das Ergebnis dieser Bedingung verwendet, um den Datenrahmen zu indizieren.
Rafaelvalle

4
Wie würden Sie mit der Situation umgehen, wenn die Spalten Nullen / Nans enthalten? Wie können wir sie ignorieren lassen?
Asimo

6
Wie gehen wir mit str-Spalten für diese Lösung um? Wenn einige der Spalten nicht numerisch sind und wir Ausreißer basierend auf allen numerischen Spalten entfernen möchten.
ssp

6
Erhaltener Fehler: "TypeError: nicht unterstützte Operandentypen für /: 'str' und 'int'"
sak

142

Verwenden booleanSie die Indizierung wie innumpy.array

df = pd.DataFrame({'Data':np.random.normal(size=200)})
# example dataset of normally distributed data. 

df[np.abs(df.Data-df.Data.mean()) <= (3*df.Data.std())]
# keep only the ones that are within +3 to -3 standard deviations in the column 'Data'.

df[~(np.abs(df.Data-df.Data.mean()) > (3*df.Data.std()))]
# or if you prefer the other way around

Für eine Serie ist es ähnlich:

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

6
Es ist auch ein DataFrame.abs()FYIDataFrame.clip()
Jeff

7
Im Fall von clip()Jeff werden die Umrisse nicht entfernt: df.SOME_DATA.clip(-3std,+3std)Weisen Sie die Umrisse entweder + 3std oder -3std zu
CT Zhu

1
Das ist fast das gleiche, @AMM
CT Zhu

1
Wie können wir dasselbe tun, wenn unser Pandas-Datenrahmen 100 Spalten hat?
DreamerP

1
Super, danke für diese Antwort @CTZhu. @DreamerP Sie können es einfach auf den gesamten DataFrame anwenden mit : df_new = df[np.abs(df - df.mean()) <= (3 * df.std())]. Im Gegensatz zur Anwendung auf eine Reihe oder eine einzelne Spalte werden dadurch Ausreißer durch np.nanden DataFrame ersetzt und die Form beibehalten. Daher ist möglicherweise eine Interpolation erforderlich, um die fehlenden Werte zu füllen.
Scotty1

93

Für jede Ihrer Datenrahmenspalten können Sie ein Quantil erhalten mit:

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

3
Dieser Artikel gibt einen sehr guten Überblick über Ausreißerentfernungstechniken machinelearningmastery.com/…
user6903745

2
Dies könnte Ausreißer nur von der Obergrenze entfernen. Nicht von der Untergrenze?
Indolentdeveloper

1
@indolentdeveloper Sie haben Recht, invertieren Sie einfach die Ungleichung, um niedrigere Ausreißer zu entfernen, oder kombinieren Sie sie mit einem ODER-Operator.
user6903745

4
Die Idee des Kommentars war, die Antworten zu aktualisieren;). Da kann jemand diesen Punkt verfehlen.
Indolentdeveloper

@ user6903745 AND-Anweisung oder "OR"?
AB

38

Diese Antwort ähnelt der von @tanemaki, verwendet jedoch einen lambdaAusdruck anstelle von scipy stats.

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

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

So filtern Sie den DataFrame, bei dem nur EINE Spalte (z. B. 'B') innerhalb von drei Standardabweichungen liegt:

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

Hier erfahren Sie, wie Sie diesen Z-Score fortlaufend anwenden können : Rollender Z-Score für Pandas-Datenrahmen


22
#------------------------------------------------------------------------------
# accept a dataframe, remove outliers, return cleaned data in a new dataframe
# see http://www.itl.nist.gov/div898/handbook/prc/section1/prc16.htm
#------------------------------------------------------------------------------
def remove_outlier(df_in, col_name):
    q1 = df_in[col_name].quantile(0.25)
    q3 = df_in[col_name].quantile(0.75)
    iqr = q3-q1 #Interquartile range
    fence_low  = q1-1.5*iqr
    fence_high = q3+1.5*iqr
    df_out = df_in.loc[(df_in[col_name] > fence_low) & (df_in[col_name] < fence_high)]
    return df_out

Ich erhalte die Fehlermeldung "ValueError: Indizierung mit mehrdimensionalem Schlüssel nicht möglich" in Zeile "df_out = df_in.loc [(df_in [Spaltenname]> Zaun_low) & (df_in [Spaltenname] <Zaun_hoch)]" Werden Sie helfen
Imran Ahmad Ghazali

18

Für jede Serie im Datenrahmen können Sie Ausreißer verwenden betweenund quantileentfernen.

x = pd.Series(np.random.normal(size=200)) # with outliers
x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers

3
Hier wählen Sie nur Daten innerhalb des Interquartilbereichs (IQR) aus. Beachten Sie jedoch, dass es Werte außerhalb dieses Bereichs geben kann, die keine Ausreißer sind.
BCArg

2
Die Wahl von zB 0,1 und 0,9 wäre meiner Meinung nach ziemlich sicher. Die Verwendung von zwischen und solchen Quantilen ist eine hübsche Syntax.
PascalVKooten

18

Da ich keine Antwort gesehen habe, die sich mit numerischen und nicht numerischen Attributen befasst, ist hier eine ergänzende Antwort.

Möglicherweise möchten Sie die Ausreißer nur für numerische Attribute löschen (kategoriale Variablen können kaum Ausreißer sein).

Funktionsdefinition

Ich habe @ tanemakis Vorschlag erweitert, Daten zu verarbeiten, wenn auch nicht numerische Attribute vorhanden sind:

from scipy import stats

def drop_numerical_outliers(df, z_thresh=3):
    # Constrains will contain `True` or `False` depending on if it is a value below the threshold.
    constrains = df.select_dtypes(include=[np.number]) \
        .apply(lambda x: np.abs(stats.zscore(x)) < z_thresh, reduce=False) \
        .all(axis=1)
    # Drop (inplace) values set to be rejected
    df.drop(df.index[~constrains], inplace=True)

Verwendung

drop_numerical_outliers(df)

Beispiel

Stellen Sie sich einen Datensatz vor df mit einigen Werten zu Häusern vor: Gasse, Landkontur, Verkaufspreis, ... ZB: Datendokumentation

Zunächst möchten Sie die Daten in einem Streudiagramm (mit Z-Score Thresh = 3) visualisieren:

# Plot data before dropping those greater than z-score 3. 
# The scatterAreaVsPrice function's definition has been removed for readability's sake.
scatterAreaVsPrice(df)

Vorher - Gr Liv Area Versus SalePrice

# Drop the outliers on every attributes
drop_numerical_outliers(train_df)

# Plot the result. All outliers were dropped. Note that the red points are not
# the same outliers from the first plot, but the new computed outliers based on the new data-frame.
scatterAreaVsPrice(train_df)

After - Gr Liv Area Versus SalePrice


2
Tolle Lösung! Als Heads-up reduce=Falsewurde seit pandasVersion 0.23.0
RK1

Ersatz result_type='reduce'für reduce=False.
Ekaba Bisong

8

scipy.statshat Methoden trim1()und trimboth()die Ausreißer in einer einzigen Zeile auszuschneiden, entsprechend der Rangfolge und einem eingeführten Prozentsatz der entfernten Werte.


1
trimbothwar für mich am einfachsten.
Worte für den

6

Eine andere Möglichkeit besteht darin, Ihre Daten so zu transformieren, dass die Auswirkungen von Ausreißern gemindert werden. Sie können dies tun, indem Sie Ihre Daten gewinnen.

import pandas as pd
from scipy.stats import mstats
%matplotlib inline

test_data = pd.Series(range(30))
test_data.plot()

Originale Daten

# Truncate values to the 5th and 95th percentiles
transformed_test_data = pd.Series(mstats.winsorize(test_data, limits=[0.05, 0.05])) 
transformed_test_data.plot()

Winsorized Daten


6

Wenn Sie die Methodenverkettung mögen, können Sie Ihre boolesche Bedingung für alle numerischen Spalten wie folgt abrufen:

df.sub(df.mean()).div(df.std()).abs().lt(3)

Jeder Wert jeder Spalte wird True/Falsebasierend darauf konvertiert, ob er weniger als drei Standardabweichungen vom Mittelwert entfernt ist oder nicht.


Dies sollte le(3)seit dem Entfernen von Ausreißern sein. Auf diese Weise erhalten Sie Truefür die Ausreißer. Außerdem sollte +1 und diese Antwort höher sein
Erfan

2

Sie können eine boolesche Maske verwenden:

import pandas as pd

def remove_outliers(df, q=0.05):
    upper = df.quantile(1-q)
    lower = df.quantile(q)
    mask = (df < upper) & (df > lower)
    return mask

t = pd.DataFrame({'train': [1,1,2,3,4,5,6,7,8,9,9],
                  'y': [1,0,0,1,1,0,0,1,1,1,0]})

mask = remove_outliers(t['train'], 0.1)

print(t[mask])

Ausgabe:

   train  y
2      2  0
3      3  1
4      4  1
5      5  0
6      6  0
7      7  1
8      8  1

1

Da ich mich in einem sehr frühen Stadium meiner datenwissenschaftlichen Reise befinde, behandle ich Ausreißer mit dem folgenden Code.

#Outlier Treatment

def outlier_detect(df):
    for i in df.describe().columns:
        Q1=df.describe().at['25%',i]
        Q3=df.describe().at['75%',i]
        IQR=Q3 - Q1
        LTV=Q1 - 1.5 * IQR
        UTV=Q3 + 1.5 * IQR
        x=np.array(df[i])
        p=[]
        for j in x:
            if j < LTV or j>UTV:
                p.append(df[i].median())
            else:
                p.append(j)
        df[i]=p
    return df

1

Holen Sie sich das 98. und 2. Perzentil als Grenzen unserer Ausreißer

upper_limit = np.percentile(X_train.logerror.values, 98) 
lower_limit = np.percentile(X_train.logerror.values, 2) # Filter the outliers from the dataframe
data[‘target’].loc[X_train[‘target’]>upper_limit] = upper_limit data[‘target’].loc[X_train[‘target’]<lower_limit] = lower_limit

0

Es folgt ein vollständiges Beispiel mit Daten und 2 Gruppen:

Importe:

from StringIO import StringIO
import pandas as pd
#pandas config
pd.set_option('display.max_rows', 20)

Datenbeispiel mit 2 Gruppen: G1: Gruppe 1. G2: Gruppe 2:

TESTDATA = StringIO("""G1;G2;Value
1;A;1.6
1;A;5.1
1;A;7.1
1;A;8.1

1;B;21.1
1;B;22.1
1;B;24.1
1;B;30.6

2;A;40.6
2;A;51.1
2;A;52.1
2;A;60.6

2;B;80.1
2;B;70.6
2;B;90.6
2;B;85.1
""")

Lesen Sie Textdaten in den Pandas-Datenrahmen:

df = pd.read_csv(TESTDATA, sep=";")

Definieren Sie die Ausreißer mit Standardabweichungen

stds = 1.0
outliers = df[['G1', 'G2', 'Value']].groupby(['G1','G2']).transform(
           lambda group: (group - group.mean()).abs().div(group.std())) > stds

Definieren Sie gefilterte Datenwerte und die Ausreißer:

dfv = df[outliers.Value == False]
dfo = df[outliers.Value == True]

Drucken Sie das Ergebnis:

print '\n'*5, 'All values with decimal 1 are non-outliers. In the other hand, all values with 6 in the decimal are.'
print '\nDef DATA:\n%s\n\nFiltred Values with %s stds:\n%s\n\nOutliers:\n%s' %(df, stds, dfv, dfo)

0

Meine Funktion zum Löschen von Ausreißern

def drop_outliers(df, field_name):
    distance = 1.5 * (np.percentile(df[field_name], 75) - np.percentile(df[field_name], 25))
    df.drop(df[df[field_name] > distance + np.percentile(df[field_name], 75)].index, inplace=True)
    df.drop(df[df[field_name] < np.percentile(df[field_name], 25) - distance].index, inplace=True)

0

Ich ziehe es vor zu schneiden anstatt zu fallen. Das Folgende wird am 2. und 98. Pecentile befestigt.

df_list = list(df)
minPercentile = 0.02
maxPercentile = 0.98

for _ in range(numCols):
    df[df_list[_]] = df[df_list[_]].clip((df[df_list[_]].quantile(minPercentile)),(df[df_list[_]].quantile(maxPercentile)))

-2

Das Löschen und Löschen von Ausreißern ist meiner Meinung nach statistisch falsch. Dadurch unterscheiden sich die Daten von den Originaldaten. Macht Daten auch ungleich geformt und daher ist der beste Weg, die Auswirkung von Ausreißern durch Protokolltransformation der Daten zu reduzieren oder zu vermeiden. Das hat bei mir funktioniert:

np.log(data.iloc[:, :])

3
Ich kann keine Annahmen darüber treffen, warum das OP etwas unternehmen möchte.
RajeshM
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.