2015-04-04 25 views
10

Sto cercando soluzioni per accelerare una funzione che ho scritto per scorrere un dataframe panda e confrontare i valori delle colonne tra la riga corrente e la riga precedente .Il modo più veloce per confrontare riga e riga precedente in dataframe panda con milioni di righe

Ad esempio, questa è una versione semplificata del mio problema:

User Time     Col1 newcol1 newcol2 newcol3 newcol4 
0  1  6  [cat, dog, goat]  0  0  0  0 
1  1  6   [cat, sheep]  0  0  0  0 
2  1 12  [sheep, goat]  0  0  0  0 
3  2  3   [cat, lion]  0  0  0  0 
4  2  5 [fish, goat, lemur]  0  0  0  0 
5  3  9   [cat, dog]  0  0  0  0 
6  4  4   [dog, goat]  0  0  0  0 
7  4 11    [cat]  0  0  0  0 

Al momento ho una funzione che scorre e calcola i valori per 'newcol1' e 'newcol2' a seconda che il ' User 'è cambiato rispetto alla riga precedente e anche se la differenza nei valori' Time 'è maggiore di 1. Guarda anche il primo valore negli array memorizzati in' Col1 'e' Col2 'e gli aggiornamenti' newcol3 'e' newcol4 'se questi valori sono cambiati rispetto alla riga precedente.

Ecco la pseudo-codice per quello che sto facendo attualmente (da quando ho semplificato il problema non ho ancora testato questo, ma è abbastanza simile a quello che sto realmente facendo in notebook ipython):

def myJFunc(df): 
...  #initialize jnum counter 
...  jnum = 0; 
...  #loop through each row of dataframe (not including the first/zeroeth) 
...  for i in range(1,len(df)): 
...    #has user changed? 
...    if df.User.loc[i] == df.User.loc[i-1]: 
...      #has time increased by more than 1 (hour)? 
...      if abs(df.Time.loc[i]-df.Time.loc[i-1])>1: 
...        #update new columns 
...        df['newcol2'].loc[i-1] = 1; 
...        df['newcol1'].loc[i] = 1; 
...        #increase jnum 
...        jnum += 1; 
...      #has content changed? 
...      if df.Col1.loc[i][0] != df.Col1.loc[i-1][0]: 
...        #record this change 
...        df['newcol4'].loc[i-1] = [df.Col1.loc[i-1][0], df.Col2.loc[i][0]]; 
...    #different user? 
...    elif df.User.loc[i] != df.User.loc[i-1]: 
...      #update new columns 
...      df['newcol1'].loc[i] = 1; 
...      df['newcol2'].loc[i-1] = 1; 
...      #store jnum elsewhere (code not included here) and reset jnum 
...      jnum = 1; 

Ora ho bisogno di applicare questa funzione a diversi milioni di righe ed è incredibilmente lento, quindi sto cercando di capire il modo migliore per accelerarlo. Ho sentito che Cython può aumentare la velocità delle funzioni, ma non ne ho esperienza (e sono nuovo sia per i panda che per i python). È possibile passare due righe di un dataframe come argomenti alla funzione e quindi usare Cython per velocizzarlo o sarebbe necessario creare nuove colonne con valori "diff" in modo che la funzione legga e scriva solo in una riga del dataframe alla volta, per trarre beneficio dall'uso di Cython? Qualsiasi altro trucchetto di velocità sarebbe molto apprezzato!

(Per quanto riguarda usando .loc, ho confrontato .loc, .iloc e .ix e questo era leggermente più veloce in modo che è l'unico motivo che sto utilizzando attualmente)

(Inoltre, il mio User colonna la realtà è unicode non int, che potrebbe essere problematica per raffronti rapidi)

+1

Con un milione di righe, perché non utilizzare un database dedicato che Python può facilmente connettersi a MySQL o SQLlite? I database relazionali possono eseguire query SQL complesse con logica if/then per il confronto riga a riga unito da indici. Sono progettati per scalare per milioni di file. Anche un trigger può essere impostato in modo che, con qualsiasi modifica dell'utente, le colonne specifiche possano essere aggiornate. – Parfait

risposta

10

Stavo pensando alle stesse parole di Andy, solo con groupby aggiunto, e penso che questo sia complementare alla risposta di Andy. L'aggiunta di groupby ha solo l'effetto di inserire un NaN nella prima riga ogni volta che si esegue un diff o shift. (Si noti che questo non è un tentativo di una risposta esatta, solo per abbozzare alcune tecniche di base.)

df['time_diff'] = df.groupby('User')['Time'].diff() 

df['Col1_0'] = df['Col1'].apply(lambda x: x[0]) 

df['Col1_0_prev'] = df.groupby('User')['Col1_0'].shift() 

    User Time     Col1 time_diff Col1_0 Col1_0_prev 
0  1  6  [cat, dog, goat]  NaN cat   NaN 
1  1  6   [cat, sheep]   0 cat   cat 
2  1 12  [sheep, goat]   6 sheep   cat 
3  2  3   [cat, lion]  NaN cat   NaN 
4  2  5 [fish, goat, lemur]   2 fish   cat 
5  3  9   [cat, dog]  NaN cat   NaN 
6  4  4   [dog, goat]  NaN dog   NaN 
7  4 11    [cat]   7 cat   dog 

Come un follow-up a punto di Andy sulla memorizzazione di oggetti, di notare che quello che ho fatto qui è stato quello di estrarre il primo elemento della colonna elenco (e aggiungere anche una versione spostata). Facendolo in questo modo devi solo effettuare un'estrazione costosa una volta e poi continuare a seguire i metodi panda standard.

+0

Grazie a entrambi (JohnE & @Andy), ho implementato entrambe le soluzioni, groupby ed estraendo il primo elemento di Col1 sono stati particolarmente utili, ora richiede ~ 3 minuti per essere eseguito sull'intero set di dati - molto felice! :) – AdO

0

Nel tuo problema, sembra che tu voglia scorrere attraverso la riga a coppie. La prima cosa che potreste fare è qualcosa di simile:

from itertools import tee, izip 
def pairwise(iterable): 
    "s -> (s0,s1), (s1,s2), (s2, s3), ..." 
    a, b = tee(iterable) 
    next(b, None) 
    return izip(a, b) 

for (idx1, row1), (idx2, row2) in pairwise(df.iterrows()): 
    # you stuff 

Tuttavia non è possibile modificare row1 e row2 direttamente sarà ancora bisogno di usare .loc o .iloc con gli indici.

Se iterrows è ancora troppo lento vi suggerisco di fare qualcosa di simile:

  • creare una colonna user_id da voi nomi Unicode utilizzando pd.unique (Utente) e di mappatura del nome con un dizionario a intero ids .

  • Creare un dataframe delta: a un dataframe spostato con user_id e la colonna del tempo si sottrasse il dataframe originale.

    df[[col1, ..]].shift() - df[[col1, ..]]) 
    

Se ID_utente> 0, significa che l'utente ha modificato in due righe consecutive. La colonna del tempo può essere filtrata direttamente con delta [delta ['time'> 1]] Con questo dataframe delta si registrano le modifiche in termini di righe. Puoi usarlo come maschera per aggiornare le colonne che ti servono dal tuo dataframe originale.

8

Usa pandas (costrutti) e vectorizza il tuo codice, cioè non usare per i loop, invece usa le funzioni panda/numpy.

'newcol1' e 'newcol2' a seconda che il 'utente' è cambiata dalla fila precedente e anche se la differenza di valori 'Tempo' è maggiore di 1.

calcolare questi separatamente:

df['newcol1'] = df['User'].shift() == df['User'] 
df.ix[0, 'newcol1'] = True # possibly tweak the first row?? 

df['newcol1'] = (df['Time'].shift() - df['Time']).abs() > 1 

non è chiaro per me lo scopo di Col1, ma oggetti Python generali in colonne non scala bene (non è possibile utilizzare il percorso veloce e il contenuto sono sparsi nella memoria). Il più delle volte si può farla franca con l'utilizzo di qualcos'altro ...


Cython è l'molto ultima opzione, e non necessaria nel 99% dei casi d'uso, ma vedere enhancing performance section of the docs per le punte.

Problemi correlati