2014-07-21 24 views
31

Ho notato prestazioni molto scarse durante l'utilizzo di iterrows da panda.Il problema ha problemi di prestazioni?

È qualcosa che viene sperimentato da altri? È specifico per iterrows e questa funzione dovrebbe essere evitata per i dati di una certa dimensione (sto lavorando con 2-3 milioni di righe)?

This discussion su GitHub mi ha portato a credere che sia causato quando si mischiano i dtypes nel dataframe, tuttavia il semplice esempio qui sotto mostra che c'è anche quando si usa un dtype (float64). Questa operazione richiede 36 secondi sulla mia macchina:

import pandas as pd 
import numpy as np 
import time 

s1 = np.random.randn(2000000) 
s2 = np.random.randn(2000000) 
dfa = pd.DataFrame({'s1': s1, 's2': s2}) 

start = time.time() 
i=0 
for rowindex, row in dfa.iterrows(): 
    i+=1 
end = time.time() 
print end - start 

Perché le operazioni vettoriali sono applicate in modo molto più rapido? Immagino che ci debba essere qualche iterazione riga per riga anche lì.

Non riesco a capire come non usare iterrows nel mio caso (questo salverò per una domanda futura). Pertanto mi piacerebbe sentire se tu fossi stato in grado di evitare questa iterazione. Sto facendo calcoli basati su dati in datafram separati. Grazie!

--- Edit: semplificata versione di ciò che voglio correre è stato aggiunto al di sotto ---

import pandas as pd 
import numpy as np 

#%% Create the original tables 
t1 = {'letter':['a','b'], 
     'number1':[50,-10]} 

t2 = {'letter':['a','a','b','b'], 
     'number2':[0.2,0.5,0.1,0.4]} 

table1 = pd.DataFrame(t1) 
table2 = pd.DataFrame(t2) 

#%% Create the body of the new table 
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0]) 

#%% Iterate through filtering relevant data, optimizing, returning info 
for row_index, row in table1.iterrows(): 
    t2info = table2[table2.letter == row['letter']].reset_index() 
    table3.ix[row_index,] = optimize(t2info,row['number1']) 

#%% Define optimization 
def optimize(t2info, t1info): 
    calculation = [] 
    for index, r in t2info.iterrows(): 
     calculation.append(r['number2']*t1info) 
    maxrow = calculation.index(max(calculation)) 
    return t2info.ix[maxrow] 
+4

'' apply'' NON è vettorizzato. '' iterrows'' è ancora peggio dato che racchiude tutto (cio 'il diff diff con '' apply''). Dovresti usare solo '' iterrows'' in pochissime situazioni. IMHO mai. Mostra ciò che stai facendo con '' iterrows''. – Jeff

+2

Il problema a cui ci si è collegati invece ha a che fare con la boxe di un '' DatetimeIndex'' in '' Timestamp'' (è stato implementato in python space), e questo è stato molto migliorato in master. – Jeff

+1

Vedere questo problema per una discussione più completa: https://github.com/pydata/pandas/issues/7194. – Jeff

risposta

63

In generale, iterrows dovrebbe essere utilizzato solo in casi molto molto specifici. Questo è l'ordine generale di precedenza per l'esecuzione di varie operazioni:

1) vectorization 
2) using a custom cython routine 
3) apply 
    a) reductions that can be performed in cython 
    b) iteration in python space 
4) itertuples 
5) iterrows 
6) updating an empty frame (e.g. using loc one-row-at-a-time) 

Utilizzando un personalizzato Cython routine è di solito troppo complicato, quindi cerchiamo di saltare che per ora.

1) La vettorizzazione è SEMPRE SEMPRE la prima e migliore scelta. Tuttavia, si tratta di una piccola serie di casi che non possono essere vettorizzati in modi ovvi (principalmente con una ricorrenza). Più avanti su una cornice piuttosto piccola, potrebbe essere più veloce fare altri metodi.

3) Applica implica può può essere fatto da un iteratore nello spazio cython (questo è fatto internamente in panda) (questo è un) caso.

Questo dipende da cosa sta succedendo all'interno dell'espressione apply. per esempio. df.apply(lambda x: np.sum(x)) verrà eseguito abbastanza rapidamente (ovviamente df.sum(1) è ancora meglio). Comunque qualcosa del genere: df.apply(lambda x: x['b'] + 1) verrà eseguito nello spazio python, e di conseguenza è più lento.

4) itertuples Non box i dati in una serie, proprio restituisce come una tupla

5) iterrows FA box i dati in una serie. A meno che tu non ne abbia davvero bisogno, usa un altro metodo.

6) aggiornamento di un frame vuoto a-single-row-at-time. Ho visto questo metodo utilizzato troppo. È di gran lunga il più lento. Probabilmente è un luogo comune (e ragionevolmente veloce per alcune strutture python), ma DataFrame fa un discreto numero di controlli sull'indicizzazione, quindi sarà sempre molto lento aggiornare una riga alla volta. Molto meglio creare nuove strutture e concat.

+0

Sì, ho usato il numero 6 (e 5). Ho alcune cose da imparare. Sembra la scelta ovvia per un principiante relativo. – KieranPC

+1

Nella mia esperienza, la differenza tra 3, 4 e 5 è limitata a seconda del caso d'uso. – IanS

+2

Ho provato a verificare i tempi di esecuzione [in questo quaderno] (https://github.com/dimgold/Datathon-TAU/blob/master/7.6%20Pandas%20iterations%20test/Pandas_Iterations.ipynb). In qualche modo '' itertuples'' è più veloce di '' apply'' :( – Dimgold

6

operazioni vettoriali in Numpy e panda sono molto più veloce di operazioni scalari in vaniglia Python per diversi motivi:

tipo ammortizzato ricerca

Python è un linguaggio tipizzato in modo dinamico, quindi non c'è tempo di esecuzione in testa per ogni elemento in una matrice. Tuttavia, Numpy (e quindi i Panda) eseguono calcoli in C (spesso tramite Cython). Il tipo dell'array viene determinato solo all'inizio dell'iterazione; questo risparmio da solo è una delle più grandi vittorie.

Meglio caching

iterazione di un array di C è di cache-friendly e quindi molto veloce. Un DataFrame panda è una "tabella orientata alle colonne", il che significa che ogni colonna è in realtà solo una matrice. Pertanto le azioni native che è possibile eseguire su un DataFrame (come sommando tutti gli elementi di una colonna) avranno pochi errori di cache.

Maggiori opportunità per il parallelismo

un semplice array C possono funzionare tramite istruzioni SIMD. Alcune parti di Numpy abilitano SIMD, a seconda della CPU e del processo di installazione. I vantaggi del parallelismo non saranno così drammatici come la tipizzazione statica e il caching migliore, ma sono comunque una solida vittoria.

Morale della trama: utilizzare le operazioni vettoriali in Numpy e panda. Sono più veloci delle operazioni scalari in Python per il semplice motivo che queste operazioni sono esattamente ciò che un programmatore C avrebbe comunque scritto a mano. (Tranne che la nozione dell'array è molto più facile da leggere rispetto a cicli espliciti con istruzioni SIMD incorporate.)

3

Ecco il modo per risolvere il tuo problema. Questo è tutto vettorializzato.

In [58]: df = table1.merge(table2,on='letter') 

In [59]: df['calc'] = df['number1']*df['number2'] 

In [60]: df 
Out[60]: 
    letter number1 number2 calc 
0  a  50  0.2 10 
1  a  50  0.5 25 
2  b  -10  0.1 -1 
3  b  -10  0.4 -4 

In [61]: df.groupby('letter')['calc'].max() 
Out[61]: 
letter 
a   25 
b   -1 
Name: calc, dtype: float64 

In [62]: df.groupby('letter')['calc'].idxmax() 
Out[62]: 
letter 
a   1 
b   2 
Name: calc, dtype: int64 

In [63]: df.loc[df.groupby('letter')['calc'].idxmax()] 
Out[63]: 
    letter number1 number2 calc 
1  a  50  0.5 25 
2  b  -10  0.1 -1 
+0

Risposta molto chiara grazie. Cercherò di unirmi ma ho dei dubbi perché avrò 5 miliardi di righe (2,5 milioni * 2000). Q generale Ho creato una specifica Q. Sarei felice di vedere un'alternativa per evitare questo tavolo gigante, se ne conoscete uno: qui: http: //stackoverflow.com/questions/24875096/what-is-a -buon-way-to-avoid-using-iterrows-in-this-example – KieranPC

+0

questo non crea il prodotto cartesiano, è uno spazio compresso ed è piuttosto efficiente in termini di memoria, quello che stai facendo è un problema molto standard. provare. (la tua domanda collegata ha un soln molto simile) – Jeff

0

Un'altra opzione è quella di utilizzare to_records(), che è più veloce sia itertuples e iterrows.

Ma per il tuo caso, c'è molto spazio per altri tipi di miglioramenti.

Ecco la mia ultima versione ottimizzata

def iterthrough(): 
    ret = [] 
    grouped = table2.groupby('letter', sort=False) 
    t2info = table2.to_records() 
    for index, letter, n1 in table1.to_records(): 
     t2 = t2info[grouped.groups[letter].values] 
     # np.multiply is in general faster than "x * y" 
     maxrow = np.multiply(t2.number2, n1).argmax() 
     # `[1:]` removes the index column 
     ret.append(t2[maxrow].tolist()[1:]) 
    global table3 
    table3 = pd.DataFrame(ret, columns=('letter', 'number2')) 

test di benchmark:

-- iterrows() -- 
100 loops, best of 3: 12.7 ms per loop 
    letter number2 
0  a  0.5 
1  b  0.1 
2  c  5.0 
3  d  4.0 

-- itertuple() -- 
100 loops, best of 3: 12.3 ms per loop 

-- to_records() -- 
100 loops, best of 3: 7.29 ms per loop 

-- Use group by -- 
100 loops, best of 3: 4.07 ms per loop 
    letter number2 
1  a  0.5 
2  b  0.1 
4  c  5.0 
5  d  4.0 

-- Avoid multiplication -- 
1000 loops, best of 3: 1.39 ms per loop 
    letter number2 
0  a  0.5 
1  b  0.1 
2  c  5.0 
3  d  4.0 

codice completo:

import pandas as pd 
import numpy as np 

#%% Create the original tables 
t1 = {'letter':['a','b','c','d'], 
     'number1':[50,-10,.5,3]} 

t2 = {'letter':['a','a','b','b','c','d','c'], 
     'number2':[0.2,0.5,0.1,0.4,5,4,1]} 

table1 = pd.DataFrame(t1) 
table2 = pd.DataFrame(t2) 

#%% Create the body of the new table 
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index) 


print('\n-- iterrows() --') 

def optimize(t2info, t1info): 
    calculation = [] 
    for index, r in t2info.iterrows(): 
     calculation.append(r['number2'] * t1info) 
    maxrow_in_t2 = calculation.index(max(calculation)) 
    return t2info.loc[maxrow_in_t2] 

#%% Iterate through filtering relevant data, optimizing, returning info 
def iterthrough(): 
    for row_index, row in table1.iterrows(): 
     t2info = table2[table2.letter == row['letter']].reset_index() 
     table3.iloc[row_index,:] = optimize(t2info, row['number1']) 

%timeit iterthrough() 
print(table3) 

print('\n-- itertuple() --') 
def optimize(t2info, n1): 
    calculation = [] 
    for index, letter, n2 in t2info.itertuples(): 
     calculation.append(n2 * n1) 
    maxrow = calculation.index(max(calculation)) 
    return t2info.iloc[maxrow] 

def iterthrough(): 
    for row_index, letter, n1 in table1.itertuples(): 
     t2info = table2[table2.letter == letter] 
     table3.iloc[row_index,:] = optimize(t2info, n1) 

%timeit iterthrough() 


print('\n-- to_records() --') 
def optimize(t2info, n1): 
    calculation = [] 
    for index, letter, n2 in t2info.to_records(): 
     calculation.append(n2 * n1) 
    maxrow = calculation.index(max(calculation)) 
    return t2info.iloc[maxrow] 

def iterthrough(): 
    for row_index, letter, n1 in table1.to_records(): 
     t2info = table2[table2.letter == letter] 
     table3.iloc[row_index,:] = optimize(t2info, n1) 

%timeit iterthrough() 

print('\n-- Use group by --') 

def iterthrough(): 
    ret = [] 
    grouped = table2.groupby('letter', sort=False) 
    for index, letter, n1 in table1.to_records(): 
     t2 = table2.iloc[grouped.groups[letter]] 
     calculation = t2.number2 * n1 
     maxrow = calculation.argsort().iloc[-1] 
     ret.append(t2.iloc[maxrow]) 
    global table3 
    table3 = pd.DataFrame(ret) 

%timeit iterthrough() 
print(table3) 

print('\n-- Even Faster --') 
def iterthrough(): 
    ret = [] 
    grouped = table2.groupby('letter', sort=False) 
    t2info = table2.to_records() 
    for index, letter, n1 in table1.to_records(): 
     t2 = t2info[grouped.groups[letter].values] 
     maxrow = np.multiply(t2.number2, n1).argmax() 
     # `[1:]` removes the index column 
     ret.append(t2[maxrow].tolist()[1:]) 
    global table3 
    table3 = pd.DataFrame(ret, columns=('letter', 'number2')) 

%timeit iterthrough() 
print(table3) 

La versione finale è quasi 10 volte più veloce del codice originale. La strategia è:

  1. Utilizzare groupby per evitare il confronto ripetuto dei valori.
  2. Utilizzare to_records per accedere agli oggetti grezzi numpy.records.
  3. Non operare su DataFrame finché non sono stati compilati tutti i dati.