2016-06-07 7 views
2

Ich habe einen Datenrahmen mit drei Säulen, t, b und h:Pandas heikel innere Spalte Logik

   t   b   h 
0   NaN  False   6 
1  6.023448  False   38 
2  12.996233  False   46 
3  2.484907  False   67 
4  5.062595  False   81 
5  4.624973  False   82 
6  3.367296  False   38 
7  3.688879  False   53 
8  6.926577  True   38 
9  14.972346  False   81 
10 14.442651  False   78 
11  3.367296  False   67 
12  5.236442  False   46 
13  5.298317  True   8 

und ich mag eine neue Spalte erzeugen, die den Wert jede Instanz von h ausbreitet für welche b==True, rückwärts, und nur bis zum nächsten solchen Fall oder das erste Auftreten von t>9.5. Der Rest gefüllt mit NaN 's. Hier ist ein Beispiel für die Ausgabe, die ich brauche:

   t   b   h  i 
0   NaN  False   6  NaN 
1  6.023448  False   38  NaN 
2  12.996233  False   46  38 
3  2.484907  False   67  38 
4  5.062595  False   81  38 
5  4.624973  False   82  38 
6  3.367296  False   38  38 
7  3.688879  False   53  38 
8  6.926577  True   38  38 
9  14.972346  False   81  NaN 
10 14.442651  False   78  8 
11  3.367296  False   67  8 
12  5.236442  False   46  8 
13  5.298317  True   8  8 

Ich möchte vermeiden, über Zeilen zu iterieren, weil ich Millionen von ihnen habe. Ich versuchte, die b==True Fälle mit where und dann fillna mit bfill Option zu erhalten, aber kann ihm nicht sagen, wann man anfängt zu füllen. Außerdem wird diese apply ‚ed zu einzelnen Gruppen werden innerhalb eines groupby, so brauche ich eine Funktion, die eine Spalte mit ihrem Argument addiert und gibt den gesamten Rahmen

def get_i(x): 
    x['i']=x['h'].where(x['b']==True).fillna(value=None,method='backfill').dropna() 
    return x 

Antwort

2

Sie können:

#create NaN where False values 
df['i'] = np.where(df.b, df.h, np.nan) 
#bfill all NaN 
df['i'] = df.i.fillna(method='bfill') 

#create NaN by condition 
a = df[::-1].groupby('i')['t'].apply(lambda x: (x > 9.5).shift().cumsum()) >= 1 
df['i'] = df.i.mask(a, np.nan) 

print (df) 
      t  b h  i 
0   NaN False 6 NaN 
1 6.023448 False 38 NaN 
2 12.996233 False 46 38.0 
3 2.484907 False 67 38.0 
4 5.062595 False 81 38.0 
5 4.624973 False 82 38.0 
6 3.367296 False 38 38.0 
7 3.688879 False 53 38.0 
8 6.926577 True 38 38.0 
9 14.972346 False 81 NaN 
10 14.442651 False 78 8.0 
11 3.367296 False 67 8.0 
12 5.236442 False 46 8.0 
13 5.298317 True 8 8.0 
+0

schöne Lösung! – MaxU

+0

schön und elegant –

+0

Vielen Dank für die Annahme. – jezrael

1

Zuerst habe ich die Reihenfolge der Datenrahmen umkehren. Das machte es für mich einfacher, aber es ist nicht notwendig:

df = df.iloc[::-1] 

Um die Instanzen zu isolieren, wo b == True, ich eine neue Spalte hinzugefügt:

df['cum_b'] = df['b'].cumsum() 

Das bedeutet, dass ich-Gruppe kann durch cum_b zu arbeite an jeder Instanz separat.

definiere ich eine Funktion, die den ersten Index, wo t > 9.5 findet und füllt Spalte i bis zu diesem Index:

def func(dfg): 
    idx = max(dfg[dfg.t > 9.5].index, default=-1) 
    dfg.loc[:, 'i'] = dfg.h.iloc[0] 
    dfg.loc[dfg.index < idx, 'i'] = np.nan 
    return dfg.i 

Beachten Sie, wie ich max und index < idx, weil ich nicht den Index des Datenrahmens zurückgesetzt hat nach dem Zurücksetzen seine Reihenfolge.

Wenn ich die Funktion anwenden, erhalte ich das gewünschte Ergebnis:

In [44]: df.groupby('cum_b').apply(func) 
Out[44]: 
cum_b  
1  13  8.0 
     12  8.0 
     11  8.0 
     10  8.0 
     9  NaN 
2  8  38.0 
     7  38.0 
     6  38.0 
     5  38.0 
     4  38.0 
     3  38.0 
     2  38.0 
     1  NaN 
     0  NaN