2014-05-22 14 views
5

Ho un pannello in panda e sto cercando di calcolare la quantità di tempo che un individuo spende in ogni fase. Per dare un senso migliore di questo il mio set di dati è la seguente:Calcolare in modo efficiente la differenza di tempo di rotolamento nei panda

group  date stage 
A  2014-01-01 one 
A  2014-01-03 one  
A  2014-01-04 one  
A  2014-01-05 two  
B  2014-01-02 four  
B  2014-01-06 five  
B  2014-01-10 five  
C  2014-01-03 two  
C  2014-01-05 two  

Sto cercando di calcolare la durata palco per dare:

group  date stage dur 
    A  2014-01-01 one 0 
    A  2014-01-03 one 2 
    A  2014-01-04 one 3 
    A  2014-01-05 two 0 
    B  2014-01-02 four 0 
    B  2014-01-06 five 0 
    B  2014-01-10 five 4 
    C  2014-01-03 two 0 
    C  2014-01-05 two 2 

Il metodo che sto usando qui di seguito è estremamente lento. Qualche idea su un metodo più veloce?

df['stage_duration'] = df.groupby(['group', 'stage']).date.apply(lambda y: (y - y.iloc[0])).apply(lambda y:y/np.timedelta64(1, 'D'))) 
+0

Non è necessario applicare la finale, vedere qui: http://pandas-docs.github.io/pandas-docs -travis/timeseries.html # time-delta-conversioni, puoi semplicemente '' astype ('timedelta64 [D]') '' o dividi per '' np.timedelta64 (1, 'D') '' (sono sciatti diverso nel modo in cui girano. – Jeff

risposta

4

Basato il tuo codice (il tuo groupby/apply), sembra (nonostante il tuo esempio ... ma forse ho frainteso quello che vuoi e poi quello che Andy ha fatto sarebbe stata la migliore idea) che stai lavorando con un 'appuntamento' colonna che è un dtype datetime64 e non un dtype integer nei dati effettivi. Inoltre sembra che tu voglia calcolare il cambiamento in giorni misurato dalla prima osservazione di un dato group/stage. Penso che questo sia un insieme meglio di dati di esempio (se ho ben capito il tuo obiettivo in modo corretto):

>>> df 

    group  date stage dur 
0  A 2014-01-01 one 0 
1  A 2014-01-03 one 2 
2  A 2014-01-04 one 3 
3  A 2014-01-05 two 0 
4  B 2014-01-02 four 0 
5  B 2014-01-06 five 0 
6  B 2014-01-10 five 4 
7  C 2014-01-03 two 0 
8  C 2014-01-05 two 2 

Dato che si dovrebbe ottenere una certa velocità-up da solo modificando la applicano (come suggerisce Jeff nel suo commento) dividendo attraverso il timedelta64 in modo vectorized dopo la applicano (o si potrebbe farlo nel pagamento):

>>> df['dur'] = df.groupby(['group','stage']).date.apply(lambda x: x - x.iloc[0]) 
>>> df['dur'] /= np.timedelta64(1,'D') 
>>> df 

    group  date stage dur 
0  A 2014-01-01 one 0 
1  A 2014-01-03 one 2 
2  A 2014-01-04 one 3 
3  A 2014-01-05 two 0 
4  B 2014-01-02 four 0 
5  B 2014-01-06 five 0 
6  B 2014-01-10 five 4 
7  C 2014-01-03 two 0 
8  C 2014-01-05 two 2 

Ma si può anche evitare il groupby/apply dato i dati sono in gruppo, stage, ordine di data. La prima data per ogni raggruppamento ['group','stage'] si verifica quando il gruppo cambia o lo stage cambia. Quindi penso che si può fare qualcosa di simile alla seguente:

>>> beg = (df.group != df.group.shift(1)) | (df.stage != df.stage.shift(1)) 
>>> df['dur'] = (df['date'] - df['date'].where(beg).ffill())/np.timedelta64(1,'D') 
>>> df 

    group  date stage dur 
0  A 2014-01-01 one 0 
1  A 2014-01-03 one 2 
2  A 2014-01-04 one 3 
3  A 2014-01-05 two 0 
4  B 2014-01-02 four 0 
5  B 2014-01-06 five 0 
6  B 2014-01-10 five 4 
7  C 2014-01-03 two 0 
8  C 2014-01-05 two 2 

Spiegazione: Si noti che cosa df['date'].where(beg) crea:

>>> beg = (df.group != df.group.shift(1)) | (df.stage != df.stage.shift(1)) 
>>> df['date'].where(beg) 

0 2014-01-01 
1   NaT 
2   NaT 
3 2014-01-05 
4 2014-01-02 
5 2014-01-06 
6   NaT 
7 2014-01-03 
8   NaT 

E poi ho ffill i valori e prendere la differenza con la colonna 'data'.

Edit: Come Andy fa notare che si potrebbe anche usare transform:

>>> df['dur'] = df.date - df.groupby(['group','stage']).date.transform(lambda x: x.iloc[0]) 
>>> df['dur'] /= np.timedelta64(1,'D') 

    group  date stage dur 
0  A 2014-01-01 one 0 
1  A 2014-01-03 one 2 
2  A 2014-01-04 one 3 
3  A 2014-01-05 two 0 
4  B 2014-01-02 four 0 
5  B 2014-01-06 five 0 
6  B 2014-01-10 five 4 
7  C 2014-01-03 two 0 
8  C 2014-01-05 two 2 

Velocità: ho cronometrato i due metodo che utilizza un dataframe simile con 400.000 osservazioni:

metodo apply:

1 loops, best of 3: 18.3 s per loop 

Metodo di non applicazione:

1 loops, best of 3: 1.64 s per loop 

Quindi penso evitando la applicano potrebbe dare alcune accelerazioni significative

+0

+1 Questo probabilmente ha più senso per ciò che l'OP vuole ... Penso che potresti essere in grado di farlo in modo più efficiente usando una trasformazione. –

+0

Sì, @Andy, ho pensato a 'transform', ma almeno per 0.13.1 di solito trovo la trasformazione non più veloce di un' apply' generico, quindi non l'ho incluso. Ma aggiornerò la risposta con quella come alternativa. –

+0

Interessato a vedere se è più veloce, la mia ipotesi è che sarebbe (anche se dipenderà dalle dimensioni del gruppo - il sospetto sarà più veloce se gruppi più grandi). –

4

Penso userei diff qui:

In [11]: df.groupby('stage')['date'].diff().fillna(0) 
Out[11]: 
0 0 
1 2 
2 0 
3 0 
4 0 
5 4 
dtype: float64 

(Supponendo che gli stadi siano contigue.)

Se sono solo sottraendo la prima di ogni gruppo, utilizzare un transform :

In [21]: df['date'] - df.groupby('stage')['date'].transform(lambda x: x.iloc[0]) 
Out[21]: 
0 0 
1 2 
2 0 
3 0 
4 0 
5 4 
Name: date, dtype: int64 

Nota: questo è probabilmente molto più veloce ...

Problemi correlati