Undgåelse af iterrows og Python-løkker
Erstat løkker, der behandler rækker én ad gangen, med vektoriserede kolonneoperationer, np.where og pd.cut for at opnå 10-100 gange højere hastighed.
Undgåelse af iterrows og Python-løkker er en gratis Pandas & NumPy Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Pandas & NumPy Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Pandas & NumPy Academy-kurset indeholder 4 lektioner i alt.
Omkostningerne ved rækkevis iteration
Pandas er bygget på NumPy, som behandler hele arrays på én gang ved hjælp af kompileret C-kode. Når du itererer række for række med for _, row in df.iterrows(), omgår du dette og falder tilbage til ren Python — hver række udtrækkes som et Series-objekt, og løkken kører i Python-fortolkeren med en omkostning, der er cirka 100-1000 gange højere end en tilsvarende vektoriseret operation. For en DataFrame med 1 million rækker kan det betyde minutter i stedet for millisekunder.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({'a': np.random.randn(10000), 'b': np.random.randn(10000)})
# Slow: iterrows loop
def loop_version(df):
result = []
for _, row in df.iterrows():
result.append(row['a'] + row['b'])
return pd.Series(result)
# Fast: vectorised
t_loop = timeit.timeit(lambda: loop_version(df), number=5)
t_vec = timeit.timeit(lambda: df['a'] + df['b'], number=500)
print(f'Loop (5 runs): {t_loop:.3f}s total')
print(f'Vectorised (500 runs): {t_vec:.3f}s total')
print(f'Speedup: ~{(t_loop/5)/(t_vec/500):.0f}x')Erstat betingede løkker med np.where
np.where(condition, value_if_true, value_if_false) er den vektoriserede pendant til et elementvist if/else. I stedet for at iterere over rækker og skrive en if-sætning skal du angive betingelsen og begge resultatværdier som arrays. np.where beregner dette i C for hele kolonnen på én gang, hvilket gør den 50-200 gange hurtigere end en tilsvarende Python-løkke for store DataFrames.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({'price': np.random.uniform(10, 100, 100000)})
# Slow: iterrows
def label_loop(df):
labels = []
for _, row in df.iterrows():
if row['price'] > 50:
labels.append('expensive')
else:
labels.append('cheap')
return labels
# Fast: np.where
def label_vectorised(df):
return np.where(df['price'] > 50, 'expensive', 'cheap')
# Verify equivalence on a small subset
assert list(label_loop(df.head(100))) == list(label_vectorised(df.head(100)))
print('Results match!')
print('Fast version result sample:', label_vectorised(df)[:5])Flere betingelser med np.select
Brug np.select(condlist, choicelist, default=) i stedet for indlejrede np.where, når du har tre eller flere betingelser. Angiv en liste med booleske arrays og en liste med de tilsvarende outputværdier. Den første matchende betingelse bestemmer outputtet; rækker uden match får værdien default. Det erstatter komplekse if/elif/else-kæder inde i løkker med et rent, vektoriseret udtryk.
import pandas as pd
import numpy as np
np.random.seed(0)
df = pd.DataFrame({'score': np.random.randint(0, 101, 20)})
conditions = [
df['score'] >= 90,
df['score'] >= 75,
df['score'] >= 60
]
choices = ['A', 'B', 'C']
df['grade'] = np.select(conditions, choices, default='F')
print(df.sort_values('score', ascending=False).head(10))Vektoriserede strengoperationer via .str
Manipulation af strenge er en almindelig kilde til langsomme løkker. Pandas-.str-accessoren indeholder vektoriserede pendanter til alle Python-strengmetoder: .str.lower(), .str.strip(), .str.replace(), .str.contains() og mange flere. Brug af .str-metoder er 10-50 gange hurtigere end at iterere over rækker og kalde Python-strengmetoder manuelt.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
names = ['Alice Smith', 'BOB JONES', ' carol ', 'Dave Brown'] * 25000
df = pd.DataFrame({'name': names})
# Slow: loop
def clean_loop(df):
return [n.strip().title() for n in df['name']]
# Fast: .str accessor
def clean_vectorised(df):
return df['name'].str.strip().str.title()
t1 = timeit.timeit(lambda: clean_loop(df), number=10)
t2 = timeit.timeit(lambda: clean_vectorised(df), number=50)
print(f'Loop (10 runs): {t1:.3f}s')
print(f'.str (50 runs): {t2:.3f}s')
print(f'Speedup: {(t1/10)/(t2/50):.0f}x')
print('Sample:', clean_vectorised(df).head(4).tolist())Brug pd.cut og pd.qcut i stedet for løkker
pd.cut() og pd.qcut() vektoriserer intervalinddeling, som ofte skrives som løkker med flere if/elif-betingelser. Hvis du er ved at skrive 'if value < 18: age_group = child elif value < 65: age_group = adult' inde i en række-løkke, skal du erstatte den med et enkelt kald til pd.cut(), som behandler hele kolonnen på én gang med C-hastighed.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({'age': np.random.randint(0, 90, 100000)})
# Slow: loop with conditionals
def categorise_loop(df):
result = []
for age in df['age']:
if age < 18:
result.append('child')
elif age < 65:
result.append('adult')
else:
result.append('senior')
return result
# Fast: pd.cut
def categorise_cut(df):
return pd.cut(df['age'], bins=[0, 18, 65, 100],
labels=['child', 'adult', 'senior'],
right=False)
assert list(categorise_loop(df.head(5))) == list(categorise_cut(df.head(5)).astype(str))
print('Fast version sample:', categorise_cut(df).head(5).tolist())apply() er ikke altid svaret
Mange vejledninger anbefaler at erstatte løkker med .apply(func), men apply er stadig en løkke på Python-niveau internt — den er kun marginalt hurtigere end iterrows og meget langsommere end ægte vektorisering. Brug kun apply, når der ikke findes et vektoriseret alternativ (f.eks. kompleks logik på tværs af flere kolonner). Hvis en indbygget Pandas- eller NumPy-funktion kan udtrykke operationen, skal du altid foretrække den frem for apply.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({'x': np.random.randn(100000)})
# These all do the same thing — compare performance
t1 = timeit.timeit(lambda: df['x'].apply(lambda v: v**2), number=20)
t2 = timeit.timeit(lambda: df['x'] ** 2, number=200)
t3 = timeit.timeit(lambda: np.square(df['x']), number=200)
print(f'apply(v**2): {t1/20*1000:.2f} ms per run')
print(f'** operator: {t2/200*1000:.2f} ms per run')
print(f'np.square(): {t3/200*1000:.2f} ms per run')Erstat groupby-løkker med agg og transform
Et almindeligt mønster er at iterere manuelt over grupper: for name, group in df.groupby('cat'): do_something(group). Det er langsomt af de samme årsager som iterrows. Erstat det med groupby().agg(), når du skal producere opsummerende statistikker, eller med groupby().transform(), når du skal føre statistikker på gruppeniveau tilbage til de oprindelige rækkeplaceringer.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({
'cat': np.random.choice(['A','B','C','D'], 100000),
'val': np.random.randn(100000)
})
# Slow: manual loop to add group mean
def slow_group_mean(df):
means = {}
for name, group in df.groupby('cat'):
means[name] = group['val'].mean()
return df['cat'].map(means)
# Fast: transform
def fast_group_mean(df):
return df.groupby('cat')['val'].transform('mean')
t1 = timeit.timeit(lambda: slow_group_mean(df), number=10)
t2 = timeit.timeit(lambda: fast_group_mean(df), number=100)
print(f'Loop: {t1/10*1000:.1f} ms')
print(f'transform: {t2/100*1000:.1f} ms')
print(f'Speedup: {(t1/10)/(t2/100):.0f}x')Hvornår iterrows kan accepteres
Selvom de er langsomme, findes der legitime anvendelser af iterrows og itertuples: udskrivning eller logging af oplysninger fra rækker (ydeevnen er irrelevant), konstruktion af API-nyttelaster, hvor hver række udløser et HTTP-kald (netværksforsinkelsen dominerer), og fejlfinding af bestemte rækker med komplekse betingelser. Hvis du har færre end 1.000 rækker, og operationen kun køres én gang, er forskellen mellem en løkke og vektorisering millisekunder — ikke kompleksiteten i koden værd.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'order_id': [101, 102, 103],
'product': ['Widget', 'Gadget', 'Doohickey'],
'amount': [29.99, 49.99, 9.99]
})
# Acceptable use: building a structured log (small data, one-time)
for _, row in df.iterrows():
print(f'Order {row["order_id"]}: {row["product"]} — ${row["amount"]:.2f}')itertuples er hurtigere end iterrows
Hvis du er nødt til at iterere over rækker i Python (fordi der ikke findes en vektoriseret pendant), skal du bruge itertuples() i stedet for iterrows(). Den returnerer hver række som en navngivet tupel i stedet for en Pandas Series og undgår dermed omkostningen ved at oprette et Series-objekt. Det gør den typisk 5-10 gange hurtigere end iterrows. Tilgå værdier med attributnotation: row.column_name. Undgå denne tilgang, hvis kolonnenavne indeholder mellemrum (de er ikke gyldige attributnavne).
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({'a': np.random.randn(10000), 'b': np.random.randn(10000)})
t1 = timeit.timeit(
lambda: [row.a + row.b for row in df.itertuples()], number=10)
t2 = timeit.timeit(
lambda: [row['a'] + row['b'] for _, row in df.iterrows()], number=10)
print(f'itertuples: {t1/10*1000:.1f} ms')
print(f'iterrows: {t2/10*1000:.1f} ms')
print(f'itertuples speedup: {t2/t1:.1f}x')Tjekliste til vektoriseringsmønstre
Stil dig selv disse spørgsmål, før du skriver en løkke:
- Er operationen elementvis på én kolonne? → Brug et aritmetisk kolonneudtryk eller en NumPy-ufunc.
- Indeholder den betinget logik? → Brug
np.where(2 betingelser) ellernp.select(3+). - Inddeler den værdier i intervaller? → Brug
pd.cutellerpd.qcut. - Anvender den strengoperationer? → Brug
.str-accessoren. - Opsummerer den inden for grupper? → Brug
groupby().agg()ellergroupby().transform().
apply() eller itertuples(), hvis ingen af ovenstående muligheder kan bruges.Eksempel på reel hastighedsforbedring: prisberegning
Her er en komplet før-og-efter-omskrivning af en almindelig forretningsberegning — anvendelse af trinvise rabatter baseret på bestillingsantal. Løkkeversionen er let at læse, men uacceptabelt langsom for store DataFrames. Den vektoriserede version med np.select giver samme resultat på en tiendedel af tiden.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
df = pd.DataFrame({
'price': np.random.uniform(10, 200, 100000),
'qty': np.random.randint(1, 500, 100000)
})
# BEFORE: loop with conditionals
def slow_discount(df):
result = []
for _, row in df.iterrows():
if row['qty'] >= 100:
disc = 0.2
elif row['qty'] >= 50:
disc = 0.1
elif row['qty'] >= 10:
disc = 0.05
else:
disc = 0.0
result.append(row['price'] * (1 - disc))
return pd.Series(result)
# AFTER: np.select
def fast_discount(df):
conditions = [df['qty'] >= 100, df['qty'] >= 50, df['qty'] >= 10]
discounts = [0.20, 0.10, 0.05]
disc = np.select(conditions, discounts, default=0.0)
return df['price'] * (1 - disc)
assert slow_discount(df.head(200)).round(4).equals(fast_discount(df.head(200)).reset_index(drop=True).round(4))
print('Both versions agree!')Hurtigt tjek
Test din forståelse af vektorisering fra denne lektion.
Opsummering af lektionen
I denne lektion har du lært, at iterrows er 100-1000 gange langsommere end vektoriserede operationer, at np.where og np.select erstatter betingede løkker, at .str-accessoren vektoriserer strengoperationer, og at groupby().transform() erstatter manuel iteration over grupper. Når du er nødt til at iterere, skal du bruge itertuples frem for iterrows for at opnå en forbedring på 5-10 gange. Nu ser vi på effektive datatyper — nedkonvertering af numeriske typer og brug af Categorical for at reducere hukommelsesforbruget med op til 70 %.
Lær Python med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 30
- Lektioner
- 120
Ofte stillede spørgsmål
Er lektionen “Undgåelse af iterrows og Python-løkker” gratis?
Ja — hele teksten til “Undgåelse af iterrows og Python-løkker” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Pandas & NumPy Academy-kurset, skal du opgradere til CoddyKit PRO. Pandas & NumPy Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Undgåelse af iterrows og Python-løkker”?
Erstat løkker, der behandler rækker én ad gangen, med vektoriserede kolonneoperationer, np.where og pd.cut for at opnå 10-100 gange højere hastighed. Du øver dig i Pandas & NumPy Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Pandas & NumPy Academy?
Der kræves ingen tidligere erfaring. Pandas & NumPy Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Undgåelse af iterrows og Python-løkker”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Pandas & NumPy Academy-lektion?
Ja. Alle Pandas & NumPy Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Profilering med timeit og memory_profiler
- Undgåelse af iterrows og Python-løkker
- Effektive datatyper til reduktion af hukommelsesforbrug
- Indlæsning i bidder af store filer