Unngå iterrows og Python-løkker
Erstatt radvise løkker med vektoriserte kolonneoperasjoner, np.where og pd.cut for å oppnå 10–100 ganger høyere hastighet.
Unngå iterrows og Python-løkker er en gratis leksjon i Pandas & NumPy Academy på CoddyKit. Dette er leksjon 2 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Pandas & NumPy Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Pandas & NumPy Academy inneholder totalt 4 leksjoner.
Kostnaden ved iterasjon rad for rad
Pandas er bygget på NumPy, som behandler hele arrayer samtidig ved hjelp av kompilert C-kode. Når De itererer rad for rad med for _, row in df.iterrows(), omgår De dette og faller tilbake på ren Python — hver rad hentes ut som et Series-objekt, og løkken kjører i Python-tolkeren med omtrent 100–1000 ganger høyere kostnad enn en tilsvarende vektorisert operasjon. For en DataFrame med én million rader kan dette bety 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')Erstatt betingede løkker med np.where
np.where(condition, value_if_true, value_if_false) er den vektoriserte ekvivalenten til en elementvis if/else. I stedet for å iterere gjennom rader og skrive en if-setning, sender De betingelsen og begge resultatverdiene inn som arrayer. np.where beregner dette i C for hele kolonnen på én gang, noe som gjør den 50–200 ganger raskere enn 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
For tre eller flere betingelser kan De bruke np.select(condlist, choicelist, default=) i stedet for nøstede np.where-uttrykk. Send inn en liste med boolske arrayer og en liste med tilsvarende resultatverdier. Den første betingelsen som samsvarer, bestemmer resultatet; rader uten samsvar får verdien default. Dette erstatter komplekse if/elif/else-kjeder i løkker med et ryddig, vektorisert uttrykk.
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))Vektoriserte strengoperasjoner via .str
Manipulering av strenger er en vanlig kilde til trege løkker. Pandas-tilgangen .str tilbyr vektoriserte ekvivalenter for alle Python-metoder for strenger: .str.lower(), .str.strip(), .str.replace(), .str.contains() og mange flere. Bruk av .str-metoder er 10–50 ganger raskere enn å iterere gjennom rader og kalle Python-metoder for strenger 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())Bruk pd.cut og pd.qcut i stedet for løkker
pd.cut() og pd.qcut() vektoriserer inndelingsoperasjoner som ofte skrives som løkker med flere if/elif-betingelser. Hvis De oppdager at De skriver 'if value < 18: age_group = child elif value < 65: age_group = adult' inne i en radløkke, kan De erstatte dette med ett enkelt kall til pd.cut() som behandler hele kolonnen samtidig i C-hastighet.
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 alltid svaret
Mange veiledninger anbefaler å erstatte løkker med .apply(func), men apply er fortsatt en løkke på Python-nivå internt — den er bare marginalt raskere enn iterrows og mye tregere enn ekte vektorisering. Begrens bruken av apply til tilfeller der det ikke finnes et vektorisert alternativ (for eksempel kompleks logikk som involverer flere kolonner). Hvis en innebygd Pandas- eller NumPy-funksjon kan uttrykke operasjonen, bør De alltid foretrekke den fremfor 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')Erstatt groupby-løkker med agg og transform
Et vanlig mønster er å iterere manuelt gjennom grupper: for name, group in df.groupby('cat'): do_something(group). Dette er tregt av de samme grunnene som iterrows. Erstatt det med groupby().agg() for å produsere sammendragsstatistikk, eller groupby().transform() for å føre statistikk på gruppenivå tilbake til de opprinnelige radposisjonene.
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')Når iterrows kan aksepteres
Selv om iterrows er tregt, finnes det legitime bruksområder for iterrows og itertuples: utskrift eller logging av informasjon fra rader (ytelsen er irrelevant), konstruksjon av API-nyttelaster der hver rad utløser et HTTP-kall (nettverksforsinkelsen dominerer), og feilsøking av bestemte rader med komplekse betingelser. Hvis De har færre enn 1 000 rader og operasjonen kjøres én gang, er forskjellen mellom en løkke og vektorisering noen millisekunder — ikke verdt den ekstra kodekompleksiteten.
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 raskere enn iterrows
Hvis De må iterere gjennom rader i Python (fordi det ikke finnes en vektorisert ekvivalent), bør De bruke itertuples() i stedet for iterrows(). Den returnerer hver rad som en navngitt tuppel i stedet for en Pandas Series, og unngår dermed kostnadene ved å konstruere Series-objekter. Dette gjør den vanligvis 5–10 ganger raskere enn iterrows. Få tilgang til verdier med attributtnotasjon: row.column_name. Unngå dette hvis kolonnenavnene inneholder mellomrom (da er de ikke gyldige attributtnavn).
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')Sjekkliste for vektoriseringsmønstre
Still Dem disse spørsmålene før De skriver en løkke:
- Er operasjonen elementvis på én kolonne? → Bruk et aritmetisk kolonneuttrykk eller en NumPy-ufunc.
- Innebærer den betinget logikk? → Bruk
np.where(2 betingelser) ellernp.select(3 eller flere). - Deler den verdier inn i intervaller? → Bruk
pd.cutellerpd.qcut. - Utfører den strengoperasjoner? → Bruk
.str-tilgangen. - Samler den data innenfor grupper? → Bruk
groupby().agg()ellergroupby().transform().
apply() eller itertuples() hvis ingen av alternativene ovenfor passer.Eksempel på reell ytelsesforbedring: prisberegning
Her er en fullstendig refaktorering før og etter av en vanlig forretningsberegning — bruk av trinnvise rabatter basert på bestillingsantall. Løkkéversjonen er lett å lese, men uakseptabelt treg for store DataFrames. Den vektoriserte versjonen med np.select gir samme resultat på en tidel av 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!')Hurtigsjekk
Test forståelsen Deres av vektorisering fra denne leksjonen.
Oppsummering av leksjonen
I denne leksjonen har De lært at iterrows er 100–1000 ganger tregere enn vektoriserte operasjoner, at np.where og np.select erstatter betingede løkker, at .str-tilgangen vektoriserer strengoperasjoner, og at groupby().transform() erstatter manuell iterasjon gjennom grupper. Når De må iterere, bør De bruke itertuples fremfor iterrows for en forbedring på 5–10 ganger. Deretter skal vi se på effektive datatyper — nedkonvertering av numeriske typer og bruk av Categorical for å redusere minnebruken med opptil 70 %.
Lær deg Python med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 30
- Leksjoner
- 120
Ofte stilte spørsmål
Er leksjonen «Unngå iterrows og Python-løkker» gratis?
Ja – hele teksten i «Unngå iterrows og Python-løkker» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Pandas & NumPy Academy-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Pandas & NumPy Academy inneholder totalt 4 leksjoner.
Hva lærer jeg i «Unngå iterrows og Python-løkker»?
Erstatt radvise løkker med vektoriserte kolonneoperasjoner, np.where og pd.cut for å oppnå 10–100 ganger høyere hastighet. Du øver på Pandas & NumPy Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med Pandas & NumPy Academy?
Ingen tidligere erfaring er nødvendig. Pandas & NumPy Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.
Hvor lang tid tar leksjonen «Unngå iterrows og Python-løkker»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne Pandas & NumPy Academy-leksjonen?
Ja. Alle Pandas & NumPy Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- Profilering med timeit og memory_profiler
- Unngå iterrows og Python-løkker
- Effektive datatyper for redusert minnebruk
- Lesing i blokker av store filer