Come eseguire l'iterazione su righe in un DataFrame in Panda?
Risposta: NON * !
L'iterazione nei panda è un anti-schema, ed è qualcosa che dovresti fare solo quando hai esaurito ogni altra opzione. Non dovresti usare nessuna funzione con " iter
" nel suo nome per più di qualche migliaio di righe o dovrai abituarti a molte attese.
Vuoi stampare un DataFrame? Usa DataFrame.to_string()
.
Vuoi calcolare qualcosa? In tal caso, cerca i metodi in questo ordine (elenco modificato da qui ):
- vettorializzazione
- Routine cython
- Comprensioni elenco (
for
loop vaniglia )
DataFrame.apply()
: i) Riduzioni che possono essere eseguite in cython, ii) Iterazione nello spazio pitone
DataFrame.itertuples()
e iteritems()
DataFrame.iterrows()
iterrows
e itertuples
(entrambi ricevendo molti voti in risposta a questa domanda) dovrebbero essere usati in circostanze molto rare, come la generazione di oggetti riga / nametuples per l'elaborazione sequenziale, che è davvero l'unica cosa per cui queste funzioni sono utili.
Ricorso all'Autorità
La pagina dei documenti sull'iterazione ha un enorme riquadro rosso che dice:
L'iterazione attraverso gli oggetti Panda è generalmente lenta. In molti casi, non è necessario scorrere manualmente le righe [...].
* In realtà è un po 'più complicato di "non farlo". df.iterrows()
è la risposta corretta a questa domanda, ma "vettorializzare le tue operazioni" è la migliore. Concederò che ci sono circostanze in cui non è possibile evitare l'iterazione (ad esempio, alcune operazioni in cui il risultato dipende dal valore calcolato per la riga precedente). Tuttavia, ci vuole un po 'di familiarità con la biblioteca per sapere quando. Se non sei sicuro di aver bisogno di una soluzione iterativa, probabilmente non lo fai. PS: Per saperne di più sulla mia logica per aver scritto questa risposta, salta in fondo.
Un buon numero di operazioni e calcoli di base sono "vettorializzati" dai panda (tramite NumPy o tramite funzioni Cythonized). Ciò include aritmetica, confronti, (la maggior parte) riduzioni, rimodellamento (come il pivot), join e operazioni di groupby. Consulta la documentazione sulla funzionalità di base essenziale per trovare un metodo vettoriale adeguato per il tuo problema.
Se non esiste, sentiti libero di scrivere il tuo usando le estensioni personalizzate di cython .
La comprensione dell'elenco dovrebbe essere il prossimo punto di riferimento se 1) non è disponibile una soluzione vettoriale, 2) le prestazioni sono importanti, ma non abbastanza importanti da superare la seccatura del citonizzare il codice e 3) stai cercando di eseguire una trasformazione elementally sul tuo codice. Vi è una buona quantità di prove che suggeriscono che la comprensione dell'elenco è sufficientemente veloce (e talvolta anche più veloce) per molte attività panda comuni.
La formula è semplice,
# iterating over one column - `f` is some function that processes your data
result = [f(x) for x in df['col']]
# iterating over two columns, use `zip`
result = [f(x, y) for x, y in zip(df['col1'], df['col2'])]
# iterating over multiple columns - same data type
result = [f(row[0], ..., row[n]) for row in df[['col1', ...,'coln']].to_numpy()]
# iterating over multiple columns - differing data type
result = [f(row[0], ..., row[n]) for row in zip(df['col1'], ..., df['coln'])]
Se è possibile incapsulare la propria logica aziendale in una funzione, è possibile utilizzare una comprensione dell'elenco che la chiama. Puoi far funzionare le cose arbitrariamente complesse attraverso la semplicità e la velocità del raw Python.
Le
comprensioni dell'elenco di avvertenze presuppongono che i tuoi dati siano facili da lavorare: ciò significa che i tuoi tipi di dati sono coerenti e non hai NaN, ma questo non può sempre essere garantito.
- Il primo è più ovvio, ma quando si ha a che fare con NaN, preferire i metodi panda incorporati se esistono (perché hanno una logica di gestione dei case ad angolo molto migliore) o assicurarsi che la propria logica aziendale includa la logica di gestione NaN appropriata.
- Quando si ha a che fare con tipi di dati misti, è necessario ripetere l'iterazione
zip(df['A'], df['B'], ...)
anziché eseguire df[['A', 'B']].to_numpy()
l'upgrade implicito dei dati al tipo più comune. Ad esempio, se A è numerico e B è stringa, to_numpy()
eseguirà il cast dell'intero array su stringa, che potrebbe non essere quello desiderato. Fortunatamente zip
eseguire il ping delle colonne insieme è la soluzione più semplice a questo.
* YMMV per i motivi indicati nella sezione Avvertenze sopra.
Un esempio ovvio
Dimostriamo la differenza con un semplice esempio di aggiunta di due colonne di panda A + B
. Questa è un'operazione vettoriale, quindi sarà facile contrastare le prestazioni dei metodi discussi sopra.
Codice di benchmarking, per riferimento.
Devo dire, tuttavia, che non è sempre così secco e asciutto. A volte la risposta a "qual è il metodo migliore per un'operazione" è "dipende dai tuoi dati". Il mio consiglio è di testare diversi approcci sui dati prima di stabilirne uno.
Ulteriori letture
* I metodi stringa Pandas sono "vettorializzati", nel senso che sono specificati sulla serie ma operano su ciascun elemento. I meccanismi sottostanti sono ancora iterativi, poiché le operazioni sulle stringhe sono intrinsecamente difficili da vettorializzare.
Perché ho scritto questa risposta
Una tendenza comune che noto ai nuovi utenti è quella di porre domande nel modulo "come posso iterare sul mio df per fare X?". Mostra il codice che chiama iterrows()
mentre si fa qualcosa all'interno di un ciclo for. Ecco perché. Un nuovo utente della biblioteca che non è stato introdotto al concetto di vettorializzazione probabilmente immaginerà il codice che risolve il loro problema mentre scorre i propri dati per fare qualcosa. Non sapendo come scorrere un DataFrame, la prima cosa che fanno è Google e finire qui, a questa domanda. Vedono quindi la risposta accettata che dice loro come fare e chiudono gli occhi ed eseguono questo codice senza mai prima interrogarsi se l'iterazione non è la cosa giusta da fare.
Lo scopo di questa risposta è aiutare i nuovi utenti a capire che l'iterazione non è necessariamente la soluzione per ogni problema e che potrebbero esistere soluzioni migliori, più veloci e più idiomatiche, e che vale la pena dedicare tempo a esplorarle. Non sto cercando di iniziare una guerra di iterazione contro la vettorializzazione, ma voglio che i nuovi utenti siano informati quando sviluppano soluzioni ai loro problemi con questa libreria.