2014-12-11 17 views
8

Ho un oggetto DataFrame relativamente grande (circa un milione di righe, centinaia di colonne) e mi piacerebbe ritagliare i valori anomali in ogni colonna per gruppo. Per "valori anomali di clip per ogni colonna per gruppo" intendo: calcolare i quantili del 5% e del 95% per ogni colonna in un gruppo e valori di clip al di fuori di questo intervallo quantile.Modo più rapido per rimuovere i valori anomali per gruppo in grandi panda DataFrame

Ecco il setup Attualmente sto usando:

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) 

e poi, con la mia dataframe chiamato features e indicizzato da DATE, posso fare

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

Questo funziona, tranne che è molto lento, presumibilmente a causa delle chiamate nidificate di apply: una per ciascun gruppo e quindi una per ogni colonna di ciascun gruppo. Ho provato a eliminare il secondo apply calcolando quantili per tutte le colonne contemporaneamente, ma mi sono bloccato cercando di limitare ogni colonna con un valore diverso. C'è un modo più veloce per eseguire questa procedura?

risposta

7

C'è un winsorize function in scipy.stats.mstats che si potrebbe considerare l'utilizzo. Si noti, tuttavia, che restituisce valori leggermente diversi rispetto 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 

utilizzando mstats.winsorize invece di winsorize_series è forse (a seconda della N, M, P) ~ 1,5 volte più velocemente:

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

Grazie, questo è un buon puntatore, non mi rendevo conto che scipy aveva una funzione 'winsorize'. Tuttavia, presumo che si otterrebbe una maggiore accelerazione se esiste un modo per eseguire l'operazione in blocco sul DataFrame senza dover operare colonna per colonna, in modo simile a come si potrebbe standardizzare o normalizzare in blocco, ad esempio http: // stackoverflow.com/questions/12525722/normalize-data-in-pandas –

+0

Ci sono lo stesso numero di date in ogni gruppo? – unutbu

+0

il gruppo per operazione è per data, quindi ogni gruppo ha solo una data. Intendi chiedere se ciascun gruppo ha lo stesso numero di righe? La risposta è no, ogni data può (e in genere lo fa) avere un numero diverso di righe. –

1

Ho trovato un modo piuttosto semplice per farlo funzionare, utilizzando il metodo di trasformazione in panda.

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

Un buon modo per avvicinarsi a questo è con la vettorizzazione. E per questo, mi piace usare 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 

Per confronto, ho avvolto la funzione da scipy in una funzione:

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

Ma, come si può vedere, anche se la mia funzione è piuttosto veloce, la sua ancora lontano dalla realizzazione 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 

Se siete interessati a saperne di più su accelerare codice panda, vorrei suggerire Optimization Pandas for speed e From Python to Numpy.

Problemi correlati