2016-05-02 15 views
7

Ich habe ein Pandas Datenframe und ich möchte das rollende Mittel einer Spalte berechnen (nach einer groupby-Klausel). Allerdings möchte ich NaNs ausschließen.pandas groupby und rolling_apply ignorieren NaNs

Zum Beispiel, wenn die groupby [2, NaN, 1] zurückgibt, sollte das Ergebnis 1,5 sein, während es derzeit NaN zurückgibt.

Ich habe folgendes versucht, aber es scheint nicht zu funktionieren:

df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: np.mean([i for i in x if i is not np.nan and i!='NaN'])) 

Wenn ich diese auch versuchen:

df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 3, lambda x: 1) 

Ich erhalte NaN in der Ausgabe so muss es etwas damit zu tun haben, wie Pandas im Hintergrund arbeitet.

Irgendwelche Ideen?

EDIT: Hier ist ein Codebeispiel mit dem, was ich versuche zu tun:

import pandas as pd 
import numpy as np 

df = pd.DataFrame({'var1' : ['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b'], 'value' : [1, 2, 3, np.nan, 2, 3, 4, 1] }) 
print df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 2, lambda x: np.mean([i for i in x if i is not np.nan and i!='NaN'])) 

Das Ergebnis ist:

:

0 NaN 
1 NaN 
2 2.0 
3 NaN 
4 2.5 
5 NaN 
6 3.0 
7 2.0 

während ich folgendes haben wollte

0 NaN 
1 NaN 
2 2.0 
3 2.0 
4 2.5 
5 3.0 
6 3.0 
7 2.0 
+1

Bitte geben Sie einen kleinen, reproduzierbaren Code an, damit ich mit ähnlichen Informationen spielen kann, ohne dass ich mich selbst um etwas kümmern muss. – piRSquared

+0

@piRSquared Ich habe gerade ein Codebeispiel hinzugefügt. Danke – Stergios

Antwort

1

Kann dieses Ergebnis Ihren Erwartungen entsprechen? Ich änderte leicht Ihre Lösung mit min_periods Parameter und rechten Filter für Nan. Hier

In [164]: df.groupby(by=['var1'])['value'].apply(pd.rolling_apply, 2, lambda x: np.mean([i for i in x if not np.isnan(i)]), min_periods=1) 
Out[164]: 
0 1.0 
1 2.0 
2 2.0 
3 2.0 
4 2.5 
5 3.0 
6 3.0 
7 2.0 
dtype: float64 
+0

Dies ist eine clevere Verwendung von 'min_period'! – IanS

1

ist eine alternative Implementierung ohne Liste Verständnis, aber es funktioniert nicht mit np.nan

means = df.groupby('var1')['value'].apply(
    lambda gp: gp.rolling(2, min_periods=1).apply(np.nanmean)) 
8

Wie immer in Pandas den ersten Eintrag der Ausgabe zu bevölkern, zu vektorisiert Methoden kleben (dh Vermeidung apply) ist essentiell für Leistung und Skalierbarkeit.

Die Operation, die Sie tun möchten, ist ein wenig fummelig, da rollierende Operationen auf groupby-Objekten derzeit nicht NaN-fähig sind (Version 0.18.1). Als solche müssen wir ein paar kurze Zeilen Code:

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 

Die Idee ist, die Werte im Fenster (mit sum) zu summieren, die NaN-Werte zählen (unter Verwendung von count) und teilen sich dann die finden bedeuten. Dieser Code gibt die folgende Ausgabe, die Ihre gewünschte Ausgabe entspricht:

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 

Testing diese auf einem größeren Datenrahmen (etwa 100.000 Zeilen), die Laufzeit unter 100ms war deutlich schneller als alle gelten basierte Methoden, die ich versuchte.

Es kann sich lohnen, die verschiedenen Ansätze Ihrer tatsächlichen Daten zu testen, da die Zeit durch andere Faktoren wie die Anzahl der Gruppen beeinflusst werden kann. Es ist jedoch ziemlich sicher, dass sich vektorisierte Berechnungen durchsetzen werden.


Der oben gezeigte Ansatz eignet sich gut für einfache Berechnungen, z. B. das Rolling Mean.Es wird für kompliziertere Berechnungen (wie das Rolling der Standardabweichung) funktionieren, obwohl die Implementierung komplizierter ist.

Die allgemeine Idee ist Blick auf jede einfache Routine, die in Pandas schnell ist (z. B. sum) und füllen Sie dann alle Null-Werte mit einem Identitätselement (z. B. 0). Sie können dann grobpy verwenden und den Walzvorgang durchführen (z. B. .rolling(2).sum()). Die Ausgabe wird dann mit den Ausgaben anderer Operationen kombiniert.

Um zum Beispiel groupby NaN-bewusste Rollabweichung (von denen Standardabweichung ist die Quadratwurzel) zu implementieren, müssen wir "den Mittelwert der Quadrate minus das Quadrat des Mittelwerts" finden. Hier ist eine Skizze, wie dies aussehen könnte:

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

Beachten Sie, dass diese Funktion nicht numerisch stabil sein kann (Quadratur zu Überlauf führen könnte). Pandas verwendet intern Welford's algorithm, um dieses Problem zu beheben.

Wie auch immer, diese Funktion, obwohl sie mehrere Operationen verwendet, ist immer noch sehr schnell. Hier ist ein Vergleich mit der prägnanten anwenden basierten Methode vorgeschlagen von 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 

Vektorisierung 100 mal in diesem Fall schneller. Natürlich können Sie, je nachdem, wie viele Daten Sie haben, an der Verwendung von apply festhalten, da dies Ihnen Allgemeingültigkeit/Kürze auf Kosten der Leistung ermöglicht.

+0

Beachten Sie, dass dies die Rolling-Methode verwendet, die nur in Pandas 18 verfügbar ist, während OP "pd.rolling_apply" verwendet, also höchstwahrscheinlich Pandas 17 oder niedriger installiert sind. – IanS

+0

@ajcr Dies löst mein derzeitiges Problem, benötigt aber ein vollständiges Neuschreiben, wenn ich andere Funktionen als den Mittelwert anwenden möchte (z. B. rollende Standardabweichung). Gibt es eine Möglichkeit, es auch für andere Funktionen nutzbar zu machen? – Stergios

+0

@Stergios: Ich werde das Problem ein wenig mehr Gedanken machen und einige weitere Richtlinien/Vorschläge zu dieser Antwort später heute/morgen hinzufügen. Es ist sicherlich wahr, dass die Verwendung von "Anwenden" manchmal die bequemste Option ist und die allgemeinste Lösung bietet (nicht alle Funktionen sind mit beschleunigten Pandas-Routinen leicht nachzuahmen). –