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, ...).