Was ist der effizienteste Weg, Datenrahmen mit Pandas zu durchlaufen?

Lesezeit: 10 Minuten

Benutzer-Avatar
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


Benutzer-Avatar
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

Benutzer-Avatar
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

Ergebnis:

[0.5639059543609619, 0.017839908599853516, 0.005645036697387695]

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++.

Benutzer-Avatar
Fifi

Sie haben drei Möglichkeiten:

Durch Index (am einfachsten):

>>> for index in df.index:
...     print ("df[" + str(index) + "]['B']=" + str(df['B'][index]))

Mit iterrows (am meisten benutzt):

>>> for index, row in df.iterrows():
...     print ("df[" + str(index) + "]['B']=" + str(row['B']))

Mit itertuples (am schnellsten):

>>> for row in df.itertuples():
...     print ("df[" + str(row.Index) + "]['B']=" + str(row.B))

Drei Optionen zeigen etwa Folgendes an:

df[0]['B']=125
df[1]['B']=415
df[2]['B']=23
df[3]['B']=456
df[4]['B']=189
df[5]['B']=456
df[6]['B']=12

Quelle: alphons.io

  • 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


Benutzer-Avatar
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


Benutzer-Avatar
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:

http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.apply.html

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


1098500cookie-checkWas ist der effizienteste Weg, Datenrahmen mit Pandas zu durchlaufen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy