Kurs
Viele Leute greifen zu „ .apply() “, um „Schleifen zu vermeiden“, und denken, dass es schnell und einfach ist. .apply(axis=1) In der Praxis ist das zeilenweise Durchlaufen von Listen immer noch eine Schleife auf Python-Ebene. Bei großen Datenmengen kann es langsam sein und manchmal Formen liefern, die du nicht erwartet hast. Die Lösung ist einfach: Nutze vektorisierte Pandas-/NumPy-Operationen für alltägliche Aufgaben und behalte .apply() für Logik, die wirklich mehrere Spalten braucht.
Dieser Leitfaden zeigt, wie .apply() heute funktioniert, zeigt häufige Probleme auf und bietet Drop-in-Muster, die schneller und übersichtlicher sind.
Was sind DataFrame.apply() und Series.apply()?
DataFrame.apply(func, axis=0) ruft standardmäßig für jede Spalte die Funktion „ func “ auf . Mit „ axis=1 ” wird „ func ” für jede Zeile aufgerufen . „ Series.apply(func) ” ruft „func ” für jedes Element auf (oder für die gesamte „ Series ”, je nach Signaturen in neueren Versionen).
- Zeilenweise vs. spaltenweise:
axis=1bedeutet „pro Zeile”.axis=0(Standard) heißt „pro Spalte”.
- Regeln für die Rückgabe von Formen (Pandas ≥0.23): Wenn deine Funktion pro Zeile/Spalte einen Skalar zurückgibt, bekommst du einen „
Series“. Wenn es „Series“ oder „dict“ zurückgibt, erweitert Pandas diese Schlüssel zu „DataFrame“. Wenn es eine Liste/ein Array zurückgibt, bekommst du eine Reihe von Listen, es sei denn, du setzt „result_type='expand'“.
- Strengere Fehler (pandas ≥2.0): Wenn Listenlängen jetzt nicht mehr passen, gibt's Fehlermeldungen, statt dass stillschweigend inkonsistente Ergebnisse produziert werden.
Empfohlene erste Wahl: Vektorisierte Operationen
Die meisten Aufgaben, bei denen Spalten kombiniert oder umgewandelt werden müssen, lassen sich mit vektorisierten Ausdrücken oder integrierten Methoden schneller und übersichtlicher erledigen. Das folgende Beispiel zeigt, wie man die Run-Differenz eines Baseballteams ohne „ .apply() “ berechnet.
import pandas as pd
team_stats = pd.DataFrame({
"Team": ["ARI", "ATL", "BAL"],
"League": ["NL", "NL", "AL"],
"Year": [2012, 2012, 2012],
"RunsScored": [734, 700, 712],
"RunsAllowed": [688, 600, 705],
"Wins": [81, 94, 93],
"Games": [162, 162, 162],
"Playoffs": [0, 1, 1],
})
# Vectorized: fast and idiomatic
team_stats["RunDiff"] = team_stats["RunsScored"] - team_stats["RunsAllowed"]
print(team_stats[["Team", "RunsScored", "RunsAllowed", "RunDiff"]])
Row-Wise .apply(), wenn du es wirklich brauchst
Verwende .apply(axis=1) , wenn deine Logik wirklich mehrere Spalten umfasst und nicht einfach vektorisiert werden kann (z. B. bedingte Regeln, die von mehreren Feldern abhängen).
einen abgeleiteten Wert aus mehreren Spalten berechnen
Dieses Muster berechnet einen Wert pro Zeile anhand mehrerer Eingaben. Der oben beschriebene vektorisierte Ansatz ist immer noch am besten, aber hier siehst du die richtige zeilenweise Verwendung und Optionen, die die Geschwindigkeit und Ausgabe beeinflussen.
def compute_run_diff(row):
# Treat the row as read-only; return a scalar
return row["RunsScored"] - row["RunsAllowed"]
# Row-wise apply (Python-level loop; can be slow on large data)
team_stats["RunDiff_apply"] = team_stats.apply(compute_run_diff, axis=1)
Wenn die Funktion numerisch ist und nur Roharrays braucht, überspringt man raw=True einige Pandas-Overhead-Kosten, indem ein NumPy-Array an die Funktion übergeben wird.
import numpy as np
def compute_run_diff_raw(values):
# values is a NumPy array when raw=True
# Order matches the column order we select
rs, ra = values
return rs - ra
team_stats["RunDiff_raw"] = team_stats[["RunsScored", "RunsAllowed"]].apply(
compute_run_diff_raw, axis=1, raw=True
)
Überprüfe die Funktionseingabe einmal.
Wenn ich eine neue Zeilenfunktion anschließe, drucke ich einmal eine Zeile aus, um zu checken, was übergeben wird.
def debug_row(row):
# Print the first row only
if row.name == team_stats.index[0]:
print("Example row:", row.to_dict())
return 0
_ = team_stats.apply(debug_row, axis=1)
Fallstudien: Summen, Saison-Gesamtwerte und Textkennzeichnungen
Bei Jahresstatistiken von Rays solltest du integrierte Methoden und elementweise Tools bevorzugen, wenn möglich. .apply() , wo immer möglich.
rays_by_year = pd.DataFrame(
{
"Year": [2012, 2011, 2010, 2009, 2008],
"RunsScored": [697, 707, 802, 803, 774],
"RunsAllowed": [577, 614, 649, 754, 671],
"Wins": [90, 91, 96, 84, 97],
"Playoffs": [0, 1, 1, 0, 1],
}
).set_index("Year")
Spaltensummen effizient berechnen
Verwende DataFrame.sum() anstelle von .apply(sum). Es ist schneller und klarer.
# Preferred
totals = rays_by_year.sum(axis=0)
print(totals)
# If you must use apply (not recommended here)
totals_apply = rays_by_year.apply(sum, axis=0)
die Gesamtpunktzahl einer Saison berechnen
Verwende vektorisierte Arithmetik über Spalten hinweg; nicht .apply() .
rays_by_year["TotalRuns"] = rays_by_year["RunsScored"] + rays_by_year["RunsAllowed"]
print(rays_by_year[["RunsScored", "RunsAllowed", "TotalRuns"]].head())
ein 0/1-Flag in Text umwandeln
Lieber Series.map() oder replace() für elementweise Transformationen in einer Spalte.
# Using map with a dict
rays_by_year["PlayoffsText"] = rays_by_year["Playoffs"].map({0: "No", 1: "Yes"})
# Equivalent with replace
rays_by_year["PlayoffsText2"] = rays_by_year["Playoffs"].replace({0: "No", 1: "Yes"})
Steuerung der Ausgangsform mit .apply()
Wenn deine Funktion mehr als einen Wert pro Zeile zurückgibt, musst du die Form explizit angeben. So vermeidest du Überraschungen und Probleme mit zukünftigen Versionen.
Gib eine Liste zurück und zerlege sie in Spalten.
Benutze result_type='expand' , um eine Liste/ein Array in mehrere Spalten aufzuteilen.
def wins_losses(row):
# Derive wins and losses as two outputs
wins = row["Wins"]
losses = row["Games"] - row["Wins"] if "Games" in row else np.nan
return [wins, losses]
# Example using team_stats, which has "Wins" and "Games"
expanded = team_stats.apply(wins_losses, axis=1, result_type="expand")
expanded.columns = ["WinsOut", "LossesOut"]
team_stats = pd.concat([team_stats, expanded], axis=1)
Gib eine Reihe oder ein Dict zurück, um Spalten automatisch zu benennen.
Rückgabe eines Series oder dict aus jeder Zeile zurück, um ein DataFrame mit passenden Spaltenbeschriftungen zu bekommen.
def summary_row(row):
return pd.Series(
{
"IsWinningSeason": row["Wins"] >= 90,
"RunRatio": (row["RunsScored"] / row["RunsAllowed"]),
}
)
summary = team_stats.apply(summary_row, axis=1)
team_stats = pd.concat([team_stats, summary], axis=1)
Wichtige Tipps zur Leistung
Zeilenweise .apply(axis=1) ist praktisch, aber bei großen Frames langsam, weil es deine Python-Funktion einmal pro Zeile aufruft. Diese Muster verhindern diesen Engpass.
- Verwende lieber vektorisierte Pandas-/NumPy-Operationen und eingebaute Methoden wie
sum,mean,clip,where,astypeund String-/Accessor-Methoden.
- Für numerische Zeilen-/Spaltenoperationen, die Arrays akzeptieren, übergib
raw=True, um ein NumPy-Array zu bekommen und den Overhead von pandas zu reduzieren.
- Für elementweise Arbeit an einer einzelnen Spalte solltest du
Series.map()oder vektorisierte Methoden anstelle vonDataFrame.apply(axis=1).
- Vermeide es, die angegebene Zeile/Spalte in deiner Funktion zu ändern; gib stattdessen einen neuen Wert zurück.
Wichtige Versionshinweise (pandas 2.x)
Die neuesten Pandas-Versionen haben das Verhalten rund um .apply() , damit die Ergebnisse besser vorhersehbar sind.
- Listenartige Rückgaben: Ab Version 2.0 gibt's 'ne Fehlermeldung, wenn die Listen unterschiedlich lang sind. Verwende einheitliche Längen und setze „
result_type='expand'“, wenn du mehrere Spalten haben willst.
- Ausbau der Produktion: Wenn du einen „
Series“ oder „ “ zurückgibst, wird das zu einem „DataFrame“ mitdictwird zu einem DataFrame mit diesen Schlüsseln als Spalten (stabil seit 0.23).
Series.apply()Änderungen: Das Argument „convert_dtype“ wird nicht mehr unterstützt. Wenn du gemischte Typen brauchst, wandle sie zuerst in „object“ um (zum Beispiels.astype("object").apply(fn)). Mit den neuen Signaturen kann manin mehr Situationen festlegen, obfnSkalare oder eine Serie bekommt (zum Beispielby_row).
.apply() vs. .map() vs. .applymap() vs. .agg()
Such dir die API aus, die am besten zu deiner Transformation passt.
Series.map(func_or_dict): Elementweise Transformation einer Spalte. Am besten für Suchvorgänge oder einfache Funktionen geeignet.
DataFrame.apply(func, axis=1): Logik, die mehrere Spalten braucht.
DataFrame.apply(func, axis=0): Spaltenweise Logik (jeder Input ist eine Serie, die eine Spalte darstellt).
DataFrame.applymap(func): Elementweise über jede Zelle. Verwende es sparsam; vektorisierte Methoden sind schneller.
.agg()/.transform(): Aggregationen und gruppenweise Transformationen; diese sind in Groupby-Pipelines zu bevorzugen.
Häufige Fehler und schnelle Lösungen
Das sind die Probleme, die meistens zu falschen Ergebnissen oder Verzögerungen führen, mit Möglichkeiten, sie zu beheben.
- Vergessen von axis=1 für die Zeilenlogik: Wenn deine Funktion plötzlich Spalten statt Zeilen bekommt, füge einfach„
“ axis=1 hinzu.
- Unerwartete Liste: Wenn du eine Liste/ein Array aus jeder Zeile zurückgibst, setz „
result_type='expand'“, um sie in Spalten aufzuteilen.
- Langsamer zeilenweiser Code: Ersetze sie durch vektorisierte Ausdrücke oder integrierte Funktionen; wenn das nicht geht, überleg dir, ob du „
raw=True” nutzen kannst, um den Overhead zu reduzieren.
- Die Eingabezeile ändern: Behandle Eingaben als schreibgeschützt und gib neue Werte zurück.
- Typ-Drift: Wenn deine Funktion manchmal nicht ganzzahlige Werte zurückgibt, können ganzzahlige Spalten zu Float- oder Objekttypen hochkonvertiert werden. Gieß danach mit
astype, falls nötig.
Fazit
.apply() ist ein flexibles Tool, aber es ist keine Abkürzung für die Leistung. Benutze vektorisierte Operationen für Arithmetik, Aggregation und elementweise Transformationen in einer einzelnen Spalte. Benutz „ DataFrame.apply(axis=1) “ nur, wenn du wirklich mehrere Spalten pro Zeile brauchst. Wenn du es benutzt, kontrolliere die Ausgabeform mit „ result_type “, schau dir „ raw=True “ für numerische Funktionen an und behalte „ dtypes “ im Auge. Diese Muster liefern vorhersehbare Ergebnisse auf modernen Pandas und lassen sich besser skalieren, wenn deine Datenmenge wächst.

