Python: Wie iteriert man über die Zeilen eines Pandas-DataFrame?
pandas bietet mehrere Wege, ein DataFrame Zeile für Zeile durchzugehen — und der übliche Rat lautet, es nicht zu tun. Hier die Wege, was jeder davon kostet, und die beiden Verhaltensweisen die überraschen.
Die drei Wege
for idx, zeile in df.iterrows(): # eine Series pro Zeile
for zeile in df.itertuples(): # ein namedtuple pro Zeile
for name, menge in zip(df["name"], df["qty"]): # nur die noetigen Spalten
iterrows : index=0 name=ada qty=2
itertuples : index=0 name=ada qty=2
zip : name=ada qty=2
Alle drei liefern dieselben Werte. Was sie kosten, ist überhaupt nicht dasselbe.
Was es kostet
20.000 Zeilen, zwei Spalten addieren, pro einzelnem Durchlauf:
vectorised df['a'] + df['b'] : 0.044 ms
itertuples list comprehension: 7.604 ms
apply(axis=1) : 64.885 ms
iterrows : 211.528 ms
iterrows ist rund 4800-mal so teuer wie die vektorisierte Fassung.
itertuples rund 170-mal. apply(axis=1) liegt dazwischen und ist nicht die
Optimierung, für die man es oft hält — es ist eine Schleife mit zusätzlichem
Apparat.
Der Grund: Die vektorisierte Operation läuft als eine typisierte C-Schleife über zusammenhängenden Speicher. Jede zeilenweise Form baut pro Zeile ein Python-Objekt.
Warum iterrows das langsamste ist
Jede Zeile kommt als Series zurück — ein vollwertiges pandas-Objekt mit
Index, 20.000-mal neu gebaut. Schlimmer noch, die Typen gehen dabei verloren:
df.dtypes : {'name': 'str', 'qty': 'int64', 'price': 'float64'}
a row from iterrows is a Series with dtype object
Eine Zeile über Spalten verschiedener dtypes hat keinen einheitlichen dtype,
also wandelt pandas nach object. Aus deinem int64 wird ein Python-int, und
alle Annahmen über Float-Genauigkeit gehen mit.
itertuples behält sie:
itertuples keeps them : qty is int, price is float
Wenn du also schleifen musst, dann mit itertuples. Es ist schneller, es
erhält die dtypes, und der Attributzugriff liest sich besser als
zeile["spalte"].
In die Zeile zu schreiben bewirkt nichts
Das ist der Punkt, der einen einen Nachmittag kostet:
for idx, zeile in df.iterrows():
zeile["qty"] = 999
after assigning to row['qty'] in iterrows : [2, 3]
Das DataFrame ist unverändert. Die Zeile die du bekommen hast ist eine Kopie, du hast also etwas verändert, das am Ende des Durchlaufs verworfen wurde. Kein Fehler, keine Warnung.
Wenn du schreiben willst, sprich das DataFrame an:
df.loc[:, "qty"] = 999
df.loc[:, 'qty'] = 999 : [999, 999]
itertuples benennt sperrige Spalten um
Felder eines namedtuple müssen gültige Python-Bezeichner sein, alles andere wird also positionsweise ersetzt:
columns : ['my col', 'class', '3rd']
as a tuple : Pandas(Index=0, _1=1, _2=2, _3=3)
Ein Leerzeichen, ein Schlüsselwort und eine führende Ziffer — alle drei wurden
zu _1, _2, _3. Wenn deine Spaltennamen aus einer CSV stammen, die jemand
anderes erzeugt hat, wird dir das begegnen.
itertuples(name=None) : (0, 1, 2, 3) <- a plain tuple, no renaming
name=None liefert einfache Tupel, die du dann über die Position ansprichst.
Der Fehler, der dir unterwegs begegnet
Eine Series in ein if zu stecken:
bool(df['qty'] > 1) -> ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().
df['qty'] > 1 ist eine Series aus Wahrheitswerten, einer pro Zeile, und
pandas weigert sich zu raten, ob du “irgendeiner” oder “alle” gemeint hast. Die
Meldung zählt deine Möglichkeiten auf.
Was man stattdessen macht
Die meisten Zeilenschleifen sind eines von zwei Dingen. Rechnen über Spalten:
df["total"] = df["qty"] * df["price"]
[3.0, 6.0]
Oder eine bedingte Spalte, und dafür gibt es
numpy.where:
df["band"] = np.where(df["qty"] > 2, "hoch", "niedrig")
['low', 'high']
Für mehr Zweige gibt es np.select, und für wirklich zeilenabhängige Logik,
die sich nicht vektorisieren lässt, ist itertuples der ehrliche Rückfallweg.
Greif nur in Kenntnis des Preises dorthin und nicht als erstes.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).