Cursus
Veel mensen grijpen naar .apply() om “loops te vermijden” en verwachten dat het snel en eenvoudig is. In de praktijk is rijgewijs .apply(axis=1) nog steeds een loop op Python-niveau. Het kan traag zijn bij grote datasets en soms levert het vormen op die je niet verwacht. De oplossing is simpel: gebruik gevectoriseerde pandas/NumPy-bewerkingen voor veelvoorkomende taken en bewaar .apply() voor logica die echt meerdere kolommen nodig heeft.
Deze gids laat zien hoe .apply() vandaag werkt, benoemt veelgemaakte valkuilen en geeft kant-en-klare patronen die sneller en duidelijker zijn.
Wat zijn DataFrame.apply() en Series.apply()?
DataFrame.apply(func, axis=0) roept standaard func aan op elke kolom. Met axis=1 roept het func aan op elke rij. Series.apply(func) roept func aan op elk element (of op de hele Series, afhankelijk van signaturen in recente versies).
- Rijgewijs vs. kolomgewijs:
axis=1betekent “per rij.”axis=0(de standaard) betekent “per kolom.”
- Regels voor retourvorm (pandas ≥0.23): Als je functie een scalar per rij/kolom retourneert, krijg je een
Series. Als deze eenSeriesofdictretourneert, zet pandas die sleutels uit naar eenDataFrame. Als het een lijst/array retourneert, krijg je een Series van lijsten tenzij jeresult_type='expand'instelt.
- Strengere fouten (pandas ≥2.0): Ongelijke lengtes van list-achtige resultaten geven nu errors in plaats van stilzwijgend inconsistente resultaten op te leveren.
Aanbevolen eerste keuze: gevectoriseerde bewerkingen
De meeste taken waarbij je kolommen combineert of transformeert zijn sneller en duidelijker met gevectoriseerde expressies of ingebouwde methodes. Het volgende voorbeeld berekent het run-differentieel van een honkbalteam zonder .apply().
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"]])
Rijgewijze .apply() wanneer je het echt nodig hebt
Gebruik .apply(axis=1) wanneer je logica echt meerdere kolommen beslaat en niet eenvoudig te vectoriseren is (bijvoorbeeld voorwaardelijke regels die van meerdere velden afhangen).
een afgeleide waarde uit meerdere kolommen berekenen
Dit patroon berekent per rij een waarde met meerdere inputs. De gevectoriseerde aanpak hierboven heeft nog steeds de voorkeur, maar dit laat het correcte rijgewijze gebruik en opties zien die snelheid en output beïnvloeden.
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)
Wanneer de functie numeriek is en alleen ruwe arrays nodig heeft, sla je met raw=True een deel van de pandas-overhead over door je functie een NumPy-array te geven.
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
)
de invoer van de functie één keer verifiëren
Als ik een nieuwe rijfunctie aansluit, print ik één keer een rij om te bevestigen wat er wordt doorgegeven.
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)
Casestudy: sommen, seizoentotalen en tekstvlaggen
Gegeven Rays-per-jaarstatistieken, geef waar mogelijk de voorkeur aan ingebouwde methodes en elementgewijze tools boven .apply().
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")
kolomsommen efficiënt ophalen
Gebruik DataFrame.sum() in plaats van .apply(sum). Dat is sneller en duidelijker.
# 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)
totaal aantal gescoorde runs in een seizoen berekenen
Gebruik gevectoriseerde rekenkunde over kolommen; geen .apply() nodig.
rays_by_year["TotalRuns"] = rays_by_year["RunsScored"] + rays_by_year["RunsAllowed"]
print(rays_by_year[["RunsScored", "RunsAllowed", "TotalRuns"]].head())
een 0/1-vlag omzetten naar tekst
Geef de voorkeur aan Series.map() of replace() voor elementgewijze transformaties op één kolom.
# 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"})
Beheers de outputvorm van .apply()
Wanneer je functie meer dan één waarde per rij retourneert, maak de vorm expliciet. Zo voorkom je verrassingen en problemen bij toekomstige versies.
een lijst retourneren en uitvouwen naar kolommen
Gebruik result_type='expand' om een lijst/array over meerdere kolommen te splitsen.
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)
een series of dict retourneren om kolommen automatisch te benoemen
Een Series of dict per rij retourneren levert een DataFrame op met overeenkomstige kolomlabels.
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)
Prestatie-tips die ertoe doen
Rijgewijs .apply(axis=1) is handig maar traag op grote frames omdat je Python-functie één keer per rij wordt aangeroepen. Deze patronen vermijden die bottleneck.
- Geef de voorkeur aan gevectoriseerde pandas/NumPy-bewerkingen en ingebouwde methodes zoals
sum,mean,clip,where,astypeen string-/accessormethodes.
- Voor numerieke rij-/kolombewerkingen die arrays accepteren, geef
raw=Truemee om een NumPy-array te krijgen en pandas-overhead te verminderen.
- Voor elementgewijs werk aan één kolom, gebruik
Series.map()of gevectoriseerde methodes in plaats vanDataFrame.apply(axis=1).
- Voorkom mutatie van de aangeleverde rij/kolom binnen je functie; retourneer in plaats daarvan een nieuwe waarde.
Versie-opmerkingen die je moet kennen (pandas 2.x)
Recente pandas-releases hebben het gedrag rond .apply() aangescherpt, zodat resultaten voorspelbaarder zijn.
- List-achtige returns: In 2.0+ geven ongelijke lijstlengtes een error. Gebruik consistente lengtes en stel
result_type='expand'in wanneer je meerdere kolommen wilt.
- Uitvouwing van output: Een
Seriesofdictretourneren zet uit naar een DataFrame met die keys als kolommen (stabiel sinds 0.23).
Series.apply()-wijzigingen: Het argumentconvert_dtypeis verouderd. Als je gemengde types nodig hebt, cast dan eerst naarobject(bijvoorbeelds.astype("object").apply(fn)). Nieuwere signaturen laten beter regelen offnscalars of een Series ontvangt in meer contexten (bijvoorbeeldby_row).
.apply() vs. .map() vs. .applymap() vs. .agg()
Kies de API die past bij de vorm van je transformatie.
Series.map(func_or_dict): Elementgewijze transformatie op één kolom. Ideaal voor lookups of eenvoudige functies.
DataFrame.apply(func, axis=1): Rijgewijze logica die meerdere kolommen nodig heeft.
DataFrame.apply(func, axis=0): Kolomgewijze logica (elke input is een Series die een kolom voorstelt).
DataFrame.applymap(func): Elementgewijs over elke cel. Gebruik spaarzaam; gevectoriseerde methodes zijn sneller.
.agg()/.transform(): Aggregaties en groepstransformaties; geef hier de voorkeur aan in groupby-pijplijnen.
Veelgemaakte fouten en snelle oplossingen
Dit zijn de problemen die het vaakst tot onjuiste resultaten of vertragingen leiden, met manieren om ze te verhelpen.
- axis=1 vergeten voor per-rijlogica: Krijgt je functie ineens kolommen in plaats van rijen binnen, voeg dan
axis=1toe.
- Onverwachte Series van lijsten: Wanneer je per rij een lijst/array retourneert, stel
result_type='expand'in om te splitsen naar kolommen.
- Trage rijgewijze code: Vervang door gevectoriseerde expressies of ingebouwde functies; als dat niet kan, overweeg
raw=Trueom overhead te verminderen.
- De invoerrij muteren: Behandel inputs als read-only en retourneer nieuwe waarden.
- Dtype-drift: Als je functie soms niet-integers retourneert, kunnen integerkolommen opcasten naar float of object. Cast achteraf met
astypeindien nodig.
Conclusie
.apply() is een flexibel hulpmiddel, maar geen prestatiesnelkoppeling. Gebruik gevectoriseerde bewerkingen voor rekenwerk, aggregatie en elementgewijze transformaties op één kolom. Grijp naar DataFrame.apply(axis=1) alleen wanneer je logica echt meerdere kolommen per rij nodig heeft. Als je het gebruikt, beheer dan de outputvorm met result_type, overweeg raw=True voor numerieke functies en let op dtypes. Deze patronen leveren voorspelbare resultaten op in moderne pandas en schalen beter naarmate je data groeit.
