2014-12-11 6 views
8

Mam stosunkowo duży obiekt DataFrame (około miliona wierszy, setki kolumn) i chciałbym przypisać wartości odstające w każdej kolumnie według grupy. Przez "obcinanie klipów dla każdej kolumny według grupy" rozumiem - oblicz kwantyle 5% i 95% dla każdej kolumny w grupie i wartości klipów poza tym zakresem kwantyli.Szybszy sposób usuwania wartości odstających przez grupę w dużych pandach DataFrame

Oto konfiguracja obecnie używam:

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) 

a potem, ze moja DataFrame nazywa features i indeksowane przez DATE mogę zrobić

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

To działa, oprócz tego, że jest to bardzo wolno, prawdopodobnie z powodu zagnieżdżonych wywołań apply: po jednej w każdej grupie, a następnie po jednej dla każdej kolumny w każdej grupie. Próbowałem pozbyć się drugiego apply, obliczając kwantyle dla wszystkich kolumn naraz, ale utknąłem próbując ustawić próg każdej kolumny przez inną wartość. Czy istnieje szybszy sposób na wykonanie tej procedury?

Odpowiedz

7

Istnieje winsorize function in scipy.stats.mstats, z którego można skorzystać. Należy jednak pamiętać, że powraca nieco różne wartości od winsorize_series:

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 

Stosując mstats.winsorize zamiast winsorize_series jest może (w zależności od n, m, p) ~ 1,5 x szybciej:

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

In [122]: %timeit result = grouped.apply(winsorize_df) 
1 loops, best of 3: 17.8 s per loop 

In [123]: %timeit mstats_result = grouped.apply(using_mstats_df) 
1 loops, best of 3: 11.2 s per loop 
+0

Dzięki, to jest dobry wskaźnik, nie zdawałem sobie sprawy, że scipy ma funkcję 'winsorize'. Zakładam jednak, że większe przyspieszenie byłoby możliwe, gdyby istniał sposób na masową operację operacji na DataFrame bez konieczności obsługi kolumn po kolumnie, podobnie jak w przypadku masowej standaryzacji lub normalizacji, np. Http: // stackoverflow.com/questions/12525722/normalize-data-in-pandas –

+0

Czy w każdej grupie jest taka sama liczba dat? – unutbu

+0

grupa według operacji jest według daty, więc każda grupa ma tylko jedną datę. Czy chcesz zapytać, czy każda grupa ma taką samą liczbę rzędów? Odpowiedź brzmi: nie, każda data może (i zazwyczaj ma) mieć inną liczbę wierszy. –

1

Znalazłem dość prosty sposób, aby to zadziałało, używając metody transformacji w pandach.

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

Dobrym sposobem podejścia do problemu jest wektoryzacja. I do tego uwielbiam używać np.where.

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 

Dla porównania, ja owinięty funkcję od scipy w funkcję:

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

Ale jak widać, mimo że moja funkcja jest dość szybko, to wciąż daleko od realizacji scipy:

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

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

Jeśli jesteś zainteresowany, aby dowiedzieć się więcej o przyspieszeniu kod pandy, chciałbym zaproponować Optimization Pandas for speed i From Python to Numpy.

Powiązane problemy