2014-12-11 5 views
8

Ich habe ein relativ großes DataFrame-Objekt (etwa eine Million Zeilen, Hunderte von Spalten), und ich möchte Ausreißer in jeder Spalte nach Gruppe ausschneiden. Mit "Clip-Ausreißer für jede Spalte nach Gruppe" meine ich - berechne die 5% - und 95% -Quantile für jede Spalte in einer Gruppe und trenne Werte außerhalb dieses Quantilbereichs.Schneller Weg zum Entfernen von Ausreißern nach Gruppe in großen Pandas DataFrame

Hier ist das Setup ich zur Zeit mit:

def winsorize_series(s): 
    q = s.quantile([0.05, 0.95]) 
    if isinstance(q, pd.Series) and len(q) == 2: 
     s[s < q.iloc[0]] = q.iloc[0] 
     s[s > q.iloc[1]] = q.iloc[1] 
    return s 

def winsorize_df(df): 
    return df.apply(winsorize_series, axis=0) 

und dann mit meinem Dataframe features und indiziert durch DATE genannt, kann ich

grouped = features.groupby(level='DATE') 
result = grouped.apply(winsorize_df) 

Dies funktioniert nicht, es sei denn, dass es sehr langsam, vermutlich aufgrund der verschachtelten Aufrufe apply: eine für jede Gruppe und dann eine für jede Spalte in jeder Gruppe. Ich versuchte, die zweite apply loszuwerden, indem ich Quantile für alle Spalten gleichzeitig berechnete, aber steckte versuchend, jede Spalte durch einen anderen Wert zu schwellen. Gibt es einen schnelleren Weg, um dieses Verfahren durchzuführen?

Antwort

7

Es gibt eine winsorize function in scipy.stats.mstats, die Sie in Betracht ziehen könnten. Beachten Sie jedoch, dass es leicht unterschiedliche Werte als winsorize_series zurück:

In [126]: winsorize_series(pd.Series(range(20), dtype='float'))[0] 
Out[126]: 0.95000000000000007 

In [127]: mstats.winsorize(pd.Series(range(20), dtype='float'), limits=[0.05, 0.05])[0] 
Out[127]: 1.0 

Mit mstats.winsorize statt winsorize_series vielleicht ist (abhängig von N, M, P) ~ 1,5x schneller:

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

def using_mstats_df(df): 
    return df.apply(using_mstats, axis=0) 

def using_mstats(s): 
    return mstats.winsorize(s, limits=[0.05, 0.05]) 

N, M, P = 10**5, 10, 10**2 
dates = pd.date_range('2001-01-01', periods=N//P, freq='D').repeat(P) 
df = pd.DataFrame(np.random.random((N, M)) 
        , index=dates) 
df.index.names = ['DATE'] 
grouped = df.groupby(level='DATE') 

+0

Danke, das ist ein guter Zeiger, ich wusste nicht, dass Scipy eine "Winsorize" -Funktion hatte. Ich gehe jedoch davon aus, dass eine substantielle Beschleunigung erreicht werden würde, wenn es eine Möglichkeit gäbe, die Operation im Bulk auf dem DataFrame auszuführen, ohne Spalte für Spalte arbeiten zu müssen, ähnlich wie man standardisieren oder normalisieren könnte, zB http: // stackoverflow.com/questions/12525722/normalize-data-in-pandas –

+0

Gibt es in jeder Gruppe die gleiche Anzahl von Daten? – unutbu

+0

Die Gruppe nach Vorgang ist nach Datum, daher hat jede Gruppe nur ein Datum. Willst du fragen, ob jede Gruppe die gleiche Anzahl von Zeilen hat? Die Antwort darauf ist nein, jedes Datum kann (und hat typischerweise) eine andere Anzahl von Zeilen. –

1

Ich fand eine ziemlich einfache Möglichkeit, dies zu tun, mit der Transform-Methode in Pandas.

from scipy.stats import mstats 

def winsorize_series(group): 
    return mstats.winsorize(group, limits=[lower_lim,upper_lim]) 

grouped = features.groupby(level='DATE') 
result = grouped.transform(winsorize_series) 
0

Guter Weg, dies zu erreichen, ist mit Vektorisierung. Und dafür liebe ich es, np.where zu verwenden.

import pandas as pd 
import numpy as np 
from scipy.stats import mstats 
import timeit 

data = pd.Series(range(20), dtype='float') 

def WinsorizeCustom(data): 
    quantiles = data.quantile([0.05, 0.95]) 
    q_05 = quantiles.loc[0.05] 
    q_95 = quantiles.loc[0.95] 

    out = np.where(data.values <= q_05,q_05, 
             np.where(data >= q_95, q_95, data) 
       ) 
    return out 

Zum Vergleich, wickelte ich die Funktion von scipy in einer Funktion:

def WinsorizeStats(data): 
    out = mstats.winsorize(data, limits=[0.05, 0.05]) 
    return out 

Aber wie Sie sehen können, obwohl meine Funktion ziemlich schnell ist, es ist immer noch weit von der Scipy Umsetzung:

%timeit WinsorizeCustom(data) 
#1000 loops, best of 3: 842 µs per loop 

%timeit WinsorizeStats(data) 
#1000 loops, best of 3: 212 µs per loop 

Wenn Sie daran interessiert sind, mehr zu lesen über Pandas Code zu beschleunigen, würde ich Optimization Pandas for speed und From Python to Numpy vorschlagen.

Verwandte Themen