Come sempre in panda, attenersi a metodi vettorializzate (cioè evitando apply
) è essenziale per prestazioni e scalabilità.
L'operazione che si desidera eseguire è un po 'complicata poiché le operazioni di rotazione su oggetti groupby non sono al momento consapevoli di NaN (versione 0.18.1). Come tale, avremo bisogno di un paio di brevi righe di codice:
g1 = df.groupby(['var1'])['value'] # group values
g2 = df.fillna(0).groupby(['var1'])['value'] # fillna, then group values
s = g2.rolling(2).sum()/g1.rolling(2).count() # the actual computation
s.reset_index(level=0, drop=True).sort_index() # drop/sort index
L'idea è quella di sommare i valori nella finestra (usando sum
), contare i valori NaN (utilizzando count
) e poi dividere per trovare il significare. Questo codice ha pronunciato la seguente uscita che corrisponde al risultato desiderato:
0 NaN
1 NaN
2 2.0
3 2.0
4 2.5
5 3.0
6 3.0
7 2.0
Name: value, dtype: float64
prove su un dataframe più grande (circa 100.000 righe), il run-time era sotto 100ms, significativamente più veloce di qualsiasi metodo di applicare basati ho provato.
Può valere la pena testare i diversi approcci sui dati effettivi poiché i tempi possono essere influenzati da altri fattori come il numero di gruppi. È abbastanza certo che i calcoli vettorializzati vinceranno comunque.
L'approccio mostrato sopra funziona bene per calcoli semplici, come la media rotolante.Funzionerà per calcoli più complicati (come lo scostamento della deviazione standard), sebbene l'implementazione sia maggiormente coinvolta.
L'idea generale è esaminare ciascuna routine semplice che è veloce in panda (ad esempio sum
) e quindi riempire qualsiasi valore null con un elemento di identità (ad esempio 0
). Puoi quindi utilizzare groubpy ed eseguire l'operazione di rotazione (ad esempio .rolling(2).sum()
). L'output è quindi combinato con l'output (s) di altre operazioni.
Ad esempio, per implementare in base alla variazione rolling di NaN-aware (di cui la deviazione standard è la radice quadrata) dobbiamo trovare "la media dei quadrati meno il quadrato della media". Ecco uno schizzo di ciò che questo potrebbe sembrare:
def rolling_nanvar(df, window):
"""
Group df by 'var1' values and then calculate rolling variance,
adjusting for the number of NaN values in the window.
Note: user may wish to edit this function to control degrees of
freedom (n), depending on their overall aim.
"""
g1 = df.groupby(['var1'])['value']
g2 = df.fillna(0).groupby(['var1'])['value']
# fill missing values with 0, square values and groupby
g3 = df['value'].fillna(0).pow(2).groupby(df['var1'])
n = g1.rolling(window).count()
mean_of_squares = g3.rolling(window).sum()/n
square_of_mean = (g2.rolling(window).sum()/n)**2
variance = mean_of_squares - square_of_mean
return variance.reset_index(level=0, drop=True).sort_index()
Si noti che questa funzione non può essere numericamente stabile (squadratura potrebbe portare a overflow). panda usa Welford's algorithm internamente per mitigare questo problema.
In ogni caso, questa funzione, sebbene utilizzi diverse operazioni, è ancora molto veloce. Ecco un confronto con il metodo di applicazione basata su più concisa suggerito da Yakym Pirozhenko:
>>> df2 = pd.concat([df]*10000, ignore_index=True) # 80000 rows
>>> %timeit df2.groupby('var1')['value'].apply(\
lambda gp: gp.rolling(7, min_periods=1).apply(np.nanvar))
1 loops, best of 3: 11 s per loop
>>> %timeit rolling_nanvar(df2, 7)
10 loops, best of 3: 110 ms per loop
Vettorizzazione è 100 volte più veloce in questo caso. Ovviamente, a seconda della quantità di dati che hai, potresti voler continuare a usare apply
poiché ti consente generalità/brevità a scapito delle prestazioni.
Si prega di fornire un piccolo insieme riproducibile di codice tale che io possa giocare con informazioni simili che possiedi senza dovermi inventare da solo. – piRSquared
@piRSquared Ho appena aggiunto un esempio di codice. Grazie – Stergios