Kelemahan performa paling sering ditemui di kode pandas bukan pada library-nya, melainkan pada kebiasaan menulis for di atas DataFrame. Pola seperti for _, baris in df.iterrows() membuat pandas mengemas setiap baris menjadi objek Series baru, lalu menjalankan logika kita di level Python satu baris demi satu baris — biayanya tumbuh linear dan terasa sekali pada data ratusan ribu baris. Chapter ini membandingkan empat cara menghitung kolom yang sama pada 100.000 baris transaksi, membuktikan hasilnya identik, dan menunjukkan bahwa versi vectorized menyelesaikan pekerjaan itu tanpa satu pun pemanggilan Python per baris.
Empat Cara, Satu Hasil #
Kita ambil tugas klasik: menghitung total = jumlah × harga untuk seluruh baris. Empat pendekatan berikut menghasilkan nilai yang sama persis, tetapi karakter biayanya berbeda total. Perhatikan terutama baris yang mencetak jumlah pemanggilan Python:
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 100_000
df = pd.DataFrame(
{
"jumlah": rng.integers(1, 11, size=n),
"harga": rng.choice([15000, 25000, 40000, 55000, 70000], size=n),
}
)
df["total"] = df["jumlah"] * df["harga"]
# 1. iterrows: tiap baris diemas menjadi Series (mahal)
hasil_iterrows = []
for _, baris in df.iterrows():
hasil_iterrows.append(baris["jumlah"] * baris["harga"])
# 2. itertuples: tiap baris menjadi namedtuple (lebih ringan, tetap Python)
hasil_itertuples = []
for baris in df.itertuples():
hasil_itertuples.append(baris.jumlah * baris.harga)
# 3. apply(axis=1): lambda tetap dipanggil satu kali per baris
hasil_apply = df.apply(lambda r: r["jumlah"] * r["harga"], axis=1)
# 4. vectorized: satu pemanggilan C untuk seluruh kolom
hasil_vector = df["jumlah"] * df["harga"]
print("banyak baris:", f"{n:,}")
print("pemanggilan Python dengan loop:", f"{n:,}")
print("pemanggilan Python versi vectorized: 0")
print("iterrows == kolom total:", np.array_equal(hasil_iterrows, df["total"].to_numpy()))
print("itertuples == kolom total:", np.array_equal(hasil_itertuples, df["total"].to_numpy()))
print("apply == kolom total:", hasil_apply.equals(df["total"]))
print("3 hasil pertama:", hasil_vector.head(3).tolist())Output yang Dihasilkan:
banyak baris: 100,000
pemanggilan Python dengan loop: 100,000
pemanggilan Python versi vectorized: 0
iterrows == kolom total: True
itertuples == kolom total: True
apply == kolom total: True
3 hasil pertama: [55000, 560000, 280000]Mari kita telaah baris-baris pentingnya:
- Baris 14-17 (
for _, baris in df.iterrows()):iterrows()mengemas tiap baris menjadi Series, sehingga pandas melakukan pekerjaan pembuatan objek sebanyak 100.000 kali sebelum logika kita dijalankan. Versi ini paling lambat. - Baris 19-22 (
for baris in df.itertuples()):itertuples()mengemas tiap baris menjadi namedtuple yang lebih ringan, dan aksesnya per atribut (baris.jumlah). Pada data sama, pola ini selesai ordo puluhan kali lebih cepat daripadaiterrows()— namun tetap satu pemanggilan Python per baris. - Baris 24-25 (
df.apply(lambda r: ..., axis=1)):apply()denganaxis=1terlihat seperti API pandas, tetapi lambda-nya tetap dipanggil 100.000 kali; pada pengukuran kami pola ini justru termasuk yang paling lambat karena ada biaya pembungkus per baris di atas loop itu sendiri. - Baris 27-28 (
df["jumlah"] * df["harga"]): perkalian vectorized dijalankan rutinitas NumPy level C satu kali untuk semua baris; tidak ada objek per baris yang dibuat. Versi ini ordo ratusan kali lebih cepat daripadaiterrows()pada tabel yang sama. - Baris 30-35 (pemeriksaan hasil): ketiga versi loop dibandingkan terhadap kolom
totalyang dihitung vectorized — semuanyaTrue. Nilai3 hasil pertamamenegaskan hasilnya identik, jadi mengganti loop tidak mengubah hasil, hanya mengubah biaya.
Kapan Loop Masih Dapat Diterima #
| Situasi | Rekomendasi |
|---|---|
| Aritmetika antar kolom | perkalian/penjumlahan langsung antar kolom |
| Label kondisional per baris | np.where() atau np.select() |
| Transformasi teks | accessor .str atau .map() per kolom |
| Statistik bergulir / kumulatif | rolling(), expanding(), cumsum() |
| Logika rumus yang tak punya padanan vectorized | itertuples() sebagai loop terakhir |
| Interaksi eksternal per baris (API, database) | loop memang tak terhindarkan; kurangi jumlah panggilan |
Tabel di atas memperlihatkan pola umumnya: sebelum menulis loop, cari dulu padanan vectorized-nya; kalau benar-benar tidak ada, pilih bentuk loop paling ringan (itertuples()), dan sederhanakan isi loop seminimal mungkin. Mengambil seluruh kolom yang dibutuhkan sebagai Series di luar loop — bukan mengambil sel per baris — juga memangkas biaya indexing berulang.
Chapter berikutnya melanjutkan pola vectorized ke sisi seleksi data: boolean masking untuk filter, penggabungan kondisi, dan pengisian nilai berdasarkan mask.
Referensi resmi untuk pendalaman: panduan Enhancing performance , pandas.DataFrame.iterrows , dan pandas.DataFrame.itertuples .