Was ist der effizienteste Weg, Datenrahmen mit Pandas zu durchlaufen?
Lesezeit: 10 Minuten
Puppet
Ich möchte meine eigenen komplexen Operationen mit Finanzdaten in Datenrahmen nacheinander ausführen.
Zum Beispiel verwende ich die folgende MSFT-CSV-Datei aus Yahoo Finanzen:
Date,Open,High,Low,Close,Volume,Adj Close
2011-10-19,27.37,27.47,27.01,27.13,42880000,27.13
2011-10-18,26.94,27.40,26.80,27.31,52487900,27.31
2011-10-17,27.11,27.42,26.85,26.98,39433400,26.98
2011-10-14,27.31,27.50,27.02,27.27,50947700,27.27
....
Ich mache dann folgendes:
#!/usr/bin/env python
from pandas import *
df = read_csv('table.csv')
for i, row in enumerate(df.values):
date = df.index[i]
open, high, low, close, adjclose = row
#now perform analysis on open/close based on date, etc..
Ist das der effizienteste Weg? Angesichts des Fokus auf Geschwindigkeit in Pandas würde ich annehmen, dass es eine spezielle Funktion geben muss, um die Werte so zu durchlaufen, dass man auch den Index abruft (möglicherweise durch einen Generator, um speichereffizient zu sein)? df.iteritems iteriert leider nur spaltenweise.
haben Sie versucht, eine Funktion zu schreiben und sie an zu übergeben df.apply()?
– nichts101
16. April 2015 um 6:16 Uhr
Wenn Sie Speichereffizienz wünschen, sollten Sie vektorisierte Operationen (mit Matrizen und Vektoren) verwenden. Aber ich kenne keine Pandas, also kann ich dir nicht sagen, ob solche Operationen dort möglich sind.
– Mike
10. August 2015 um 10:43 Uhr
Zitieren unutbuNumPy scheint vektorisierte Operationen zu unterstützen (The key to speed with NumPy arrays is to perform your operations on the whole array at once).
– Mike
10. August 2015 um 10:45 Uhr
Die Frage bezog sich speziell auf die sequentielle Iteration, wie sie im Finanzwesen sehr verbreitet ist, wo eine Vektorisierung oft nicht möglich ist. Und die akzeptierte Antwort von Nick Crawford beantwortet dies und warnt zusätzlich davor, nach Möglichkeit Vektorisierung zu verwenden.
– Puppet
31. Mai 2019 um 17:14 Uhr
Nick Crawford
Die neuesten Versionen von Pandas enthalten jetzt eine integrierte Funktion zum Iterieren über Zeilen.
for index, row in df.iterrows():
# do some logic here
Oder, wenn Sie es schneller wollen itertuples()
Der Vorschlag von unutbu, numpy-Funktionen zu verwenden, um das Iterieren über Zeilen zu vermeiden, erzeugt jedoch den schnellsten Code.
Beachten Sie, dass iterrows ist sehr langsam (es konvertiert jede Zeile in eine Reihe und kann möglicherweise Ihre Datentypen durcheinander bringen). Wenn Sie einen Iterator benötigen, verwenden Sie ihn besser itertuples
– joris
29. Juli 2015 um 15:46 Uhr
BTW itertuples gibt benannte Tupel zurück ( docs.python.org/3/library/…), sodass Sie mit row.high oder getattr(row,’high’) nach Namen auf jede Spalte zugreifen können.
– seanv507
17. April 2016 um 18:51 Uhr
Achtung, laut Strom Dokumente: “Du solltest niemals ändern etwas, über das Sie iterieren. Es ist nicht garantiert, dass dies in allen Fällen funktioniert. Abhängig von den Datentypen gibt der Iterator eine Kopie und keine Ansicht zurück, und das Schreiben darauf hat keine Auswirkung.”
– viddik13
7. Dezember 2016 um 18:50 Uhr
@joris. Ich kann dir nicht mehr zustimmen, itertuples ist etwa 100 mal dicker als iterrows.
– Ich gehe meinen Weg
7. November 2017 um 9:24 Uhr
itertuples(name=None) ist sogar noch schneller, da es normale Tupel anstelle von benannten Tupeln liefert. Siehe diesen interessanten Artikel: medium.com/swlh/…
– Ismael EL ATIFI
22. Dezember 2020 um 22:08 Uhr
Pandas basiert auf NumPy-Arrays. Der Schlüssel zur Geschwindigkeit mit NumPy-Arrays besteht darin, Ihre Operationen auf dem gesamten Array auf einmal auszuführen, niemals Zeile für Zeile oder Element für Element.
Zum Beispiel, wenn close ist ein 1-d-Array, und Sie möchten die prozentuale Änderung von Tag zu Tag,
pct_change = close[1:]/close[:-1]
Dadurch wird das gesamte Array der prozentualen Änderungen als eine Anweisung berechnet, anstatt
pct_change = []
for row in close:
pct_change.append(...)
Versuchen Sie also, die Python-Schleife zu vermeiden for i, row in enumerate(...) und denken Sie darüber nach, wie Sie Ihre Berechnungen mit Operationen für das gesamte Array (oder den Datenrahmen) als Ganzes durchführen, anstatt Zeile für Zeile.
Ich stimme zu, dass dies der beste Weg ist, und das mache ich normalerweise für einfache Operationen. In diesem Fall ist dies jedoch nicht möglich, da die resultierenden Operationen sehr komplex werden können. Insbesondere versuche ich, Handelsstrategien zu backtesten. Wenn sich der Preis beispielsweise über einen Zeitraum von 30 Tagen auf einem neuen Tief befindet, möchten wir die Aktie möglicherweise kaufen und aussteigen, wenn eine bestimmte Bedingung erfüllt ist und dies vor Ort simuliert werden muss. Dieses einfache Beispiel könnte immer noch durch Vektorisierung durchgeführt werden, aber je komplexer eine Handelsstrategie wird, desto weniger ist es möglich, Vektorisierung zu verwenden.
– Puppet
20. Oktober 2011 um 15:16 Uhr
Sie müssen die genaue Berechnung, die Sie durchführen möchten, genauer erläutern. Es ist hilfreich, den Code zuerst so zu schreiben, wie Sie können, und ihn dann zu profilieren und zu optimieren.
– unutbu
20. Oktober 2011 um 15:19 Uhr
Übrigens kann Code, der Python-Listen verwendet, für einige Berechnungen (insbesondere solche, die nicht als Operationen auf ganzen Arrays ausgedrückt werden können) schneller sein als äquivalenter Code, der numpy-Arrays verwendet.
– unutbu
20. Oktober 2011 um 15:35 Uhr
Ich stimme zu, dass die Vektorisierung nach Möglichkeit die richtige Lösung ist – manchmal ist jedoch ein iterativer Algorithmus der einzige Weg.
– Wes McKinney
21. Oktober 2011 um 16:15 Uhr
später Kommentar, aber ich habe festgestellt, dass der Versuch, eine vollständige Berechnung für eine Spalte durchzuführen, manchmal schwierig zu schreiben und zu debuggen ist. Berücksichtigen Sie Zwischenspalten für Berechnungen, die das Debuggen und Verstehen der Berechnungen erleichtern. haben festgestellt, dass selbst die komplexeste Logik auf diese Weise implementiert werden kann, während Schleifen vermieden werden.
– Joopp
22. September 2014 um 11:27 Uhr
Richard Wong
Wie bereits erwähnt, ist das Pandas-Objekt am effizientesten, wenn es das gesamte Array auf einmal verarbeitet. Aber für diejenigen, die wirklich einen Pandas DataFrame durchlaufen müssen, um etwas auszuführen, wie ich, habe ich mindestens drei Möglichkeiten gefunden, dies zu tun. Ich habe einen kurzen Test gemacht, um zu sehen, welche der drei am wenigsten Zeit in Anspruch nimmt.
t = pd.DataFrame({'a': range(0, 10000), 'b': range(10000, 20000)})
B = []
C = []
A = time.time()
for i,r in t.iterrows():
C.append((r['a'], r['b']))
B.append(time.time()-A)
C = []
A = time.time()
for ir in t.itertuples():
C.append((ir[1], ir[2]))
B.append(time.time()-A)
C = []
A = time.time()
for r in zip(t['a'], t['b']):
C.append((r[0], r[1]))
B.append(time.time()-A)
print B
Dies ist wahrscheinlich nicht die beste Methode, um den Zeitverbrauch zu messen, aber für mich ist es schnell.
Hier sind IMHO einige Vor- und Nachteile:
.iterrows(): Index- und Zeilenelemente in separaten Variablen zurückgeben, aber deutlich langsamer
.itertuples(): schneller als .iterrows(), gibt aber den Index zusammen mit Zeilenelementen zurück, ir[0] ist der Index
zip: am schnellsten, aber kein Zugriff auf den Index der Zeile
BEARBEITEN 10.11.2020
Für das, was es wert ist, hier ist ein aktualisierter Benchmark mit einigen anderen Alternativen (perf mit MacBookPro 2,4 GHz Intel Core i9 8 Kerne 32 Go 2667 MHz DDR4)
import sys
import tqdm
import time
import pandas as pd
B = []
t = pd.DataFrame({'a': range(0, 10000), 'b': range(10000, 20000)})
for _ in tqdm.tqdm(range(10)):
C = []
A = time.time()
for i,r in t.iterrows():
C.append((r['a'], r['b']))
B.append({"method": "iterrows", "time": time.time()-A})
C = []
A = time.time()
for ir in t.itertuples():
C.append((ir[1], ir[2]))
B.append({"method": "itertuples", "time": time.time()-A})
C = []
A = time.time()
for r in zip(t['a'], t['b']):
C.append((r[0], r[1]))
B.append({"method": "zip", "time": time.time()-A})
C = []
A = time.time()
for r in zip(*t.to_dict("list").values()):
C.append((r[0], r[1]))
B.append({"method": "zip + to_dict('list')", "time": time.time()-A})
C = []
A = time.time()
for r in t.to_dict("records"):
C.append((r["a"], r["b"]))
B.append({"method": "to_dict('records')", "time": time.time()-A})
A = time.time()
t.agg(tuple, axis=1).tolist()
B.append({"method": "agg", "time": time.time()-A})
A = time.time()
t.apply(tuple, axis=1).tolist()
B.append({"method": "apply", "time": time.time()-A})
print(f'Python {sys.version} on {sys.platform}')
print(f"Pandas version {pd.__version__}")
print(
pd.DataFrame(B).groupby("method").agg(["mean", "std"]).xs("time", axis=1).sort_values("mean")
)
## Output
Python 3.7.9 (default, Oct 13 2020, 10:58:24)
[Clang 12.0.0 (clang-1200.0.32.2)] on darwin
Pandas version 1.1.4
mean std
method
zip + to_dict('list') 0.002353 0.000168
zip 0.003381 0.000250
itertuples 0.007659 0.000728
to_dict('records') 0.025838 0.001458
agg 0.066391 0.007044
apply 0.067753 0.006997
iterrows 0.647215 0.019600
NB in Python 3 zip() gibt einen Iterator zurück, verwenden Sie also list(zip())
– Louis Maddox
12. Oktober 2016 um 13:33 Uhr
Könntest du nicht verwenden t.index den Index durchlaufen?
– elPastor
22. Dezember 2016 um 2:54 Uhr
Das ist toll; danke Richard. Es ist immer noch relevant mit Python 3.7+. Von 286 Sekunden mit Iterrows auf 3,62 Sekunden mit Reißverschluss. Vielen Dank
– pacta_sunt_servanda
16. Mai 2019 um 12:48 Uhr
Ich habe diesen Benchmark mit pandas.__version__ == 1.1.4, Python 3.7.9 und dem brandneuen MacBookPro 2,4 GHz Intel Core i9 8 Kerne 32 Go 2667 MHz DDR4 erneut ausgeführt, und die Ergebnisse sind sogar noch schlechter iterrows(): [0.6970570087432861, 0.008062124252319336, 0.0036787986755371094]
– Clemens Walter
10. November 2020 um 17:02 Uhr
@ClementWalter, schön!
– Richard Wong
12. November 2020 um 6:43 Uhr
Sie können die Zeilen durchlaufen, indem Sie transponieren und dann iteritems aufrufen:
for date, row in df.T.iteritems():
# do some logic here
Über die Effizienz bin ich mir in diesem Fall nicht sicher. Um die bestmögliche Leistung in einem iterativen Algorithmus zu erzielen, sollten Sie versuchen, ihn zu schreiben Cythonalso könntest du so etwas tun:
def my_algo(ndarray[object] dates, ndarray[float64_t] open,
ndarray[float64_t] low, ndarray[float64_t] high,
ndarray[float64_t] close, ndarray[float64_t] volume):
cdef:
Py_ssize_t i, n
float64_t foo
n = len(dates)
for i from 0 <= i < n:
foo = close[i] - open[i] # will be extremely fast
Ich würde empfehlen, den Algorithmus zuerst in reinem Python zu schreiben, sicherzustellen, dass er funktioniert, und zu sehen, wie schnell er ist – wenn er nicht schnell genug ist, konvertieren Sie die Dinge mit minimalem Aufwand in Cython, um etwas zu erhalten, das ungefähr so schnell ist wie handcodiertes C /C++.
Es gibt etwas, das sogar dreimal so schnell ist wie die Itertupel, siehe die Antwort oben. Was vor drei Jahren beantwortet wurde. Trotzdem war die Verwendung des Index hier etwas Neues (dies sollte nicht verwendet werden, nur die Idee ist schlicht und einfach).
– Frage an 42 steht mit der Ukraine
8. Juli um 17:46 Uhr
Gemeinschaft
Ich habe ausgecheckt iterrows nachdem er die Antwort von Nick Crawford bemerkt hatte, aber festgestellt hatte, dass sie Tupel (index, Series) liefert. Ich bin mir nicht sicher, was für Sie am besten geeignet ist, aber ich habe schließlich die verwendet itertuples Methode für mein Problem, die (index, row_value1…) Tupel liefert.
Es gibt auch iterkvdie (Spalten-, Serien-) Tupel durchläuft.
Es gibt etwas, das sogar dreimal so schnell ist wie die Itertupel, siehe die Antwort oben. Was vor drei Jahren beantwortet wurde. Trotzdem war die Verwendung des Index hier etwas Neues (dies sollte nicht verwendet werden, nur die Idee ist schlicht und einfach).
– Frage an 42 steht mit der Ukraine
8. Juli um 17:46 Uhr
smci
Als kleine Ergänzung können Sie auch eine Anwendung durchführen, wenn Sie eine komplexe Funktion haben, die Sie auf eine einzelne Spalte anwenden:
df[b] = df[a].apply(lambda col: do stuff with col here)
wahrscheinlich ist x ein verwirrender Name für den Spaltennamen und die Zeilenvariable, obwohl ich zustimme, dass die Anwendung am einfachsten ist 🙂
– Andy Hayden
17. Oktober 2013 um 6:09 Uhr
Nur um hinzuzufügen, apply kann auch auf mehrere Spalten angewendet werden: df['c'] = df[['a','b']].apply(lambda x: do stuff with x[0] and x[1] here, axis=1)
– fantastisch
16. August 2014 um 13:18 Uhr
Kann die Anwendung eine an anderer Stelle im Code definierte Funktion übernehmen? damit wir eine kompliziertere Funktion einführen können
– Benutzer308827
9. November 2014 um 15:28 Uhr
Ja, die Lambda-Funktion kann jede Art von benutzerdefinierter Funktion verwenden. Wohlgemerkt: Wenn Sie einen großen Datenrahmen haben, möchten Sie vielleicht stattdessen zu Cython zurückkehren (Python hat ein wenig Overhead, wenn es um das Aufrufen von Funktionen geht).
– Karst
18. November 2014 um 15:53 Uhr
Ich habe umbenannt x -> col. Besserer Name
– smci
5. Februar 2015 um 4:16 Uhr
10985000cookie-checkWas ist der effizienteste Weg, Datenrahmen mit Pandas zu durchlaufen?yes
haben Sie versucht, eine Funktion zu schreiben und sie an zu übergeben
df.apply()
?– nichts101
16. April 2015 um 6:16 Uhr
Wenn Sie Speichereffizienz wünschen, sollten Sie vektorisierte Operationen (mit Matrizen und Vektoren) verwenden. Aber ich kenne keine Pandas, also kann ich dir nicht sagen, ob solche Operationen dort möglich sind.
– Mike
10. August 2015 um 10:43 Uhr
Zitieren
unutbu
NumPy scheint vektorisierte Operationen zu unterstützen (The key to speed with NumPy arrays is to perform your operations on the whole array at once
).– Mike
10. August 2015 um 10:45 Uhr
Die Frage bezog sich speziell auf die sequentielle Iteration, wie sie im Finanzwesen sehr verbreitet ist, wo eine Vektorisierung oft nicht möglich ist. Und die akzeptierte Antwort von Nick Crawford beantwortet dies und warnt zusätzlich davor, nach Möglichkeit Vektorisierung zu verwenden.
– Puppet
31. Mai 2019 um 17:14 Uhr