Table transaksi berisi satu juta baris terasa instan ketika kita menulis df["jumlah"] * df["harga"], tetapi melambat seketika begitu kita menggantinya dengan loop Python biasa. Perbedaan itu bukan kebetulan: setiap operasi pandas memiliki biaya memori dan biaya waktu yang bisa kita prediksi kalau kita memahami cara pandas menyimpan data. DataFrame memakai satu blok array NumPy per kolom, sehingga operasi antar kolom berjalan di level C untuk seluruh baris sekaligus, sementara operasi baris-per-baris berjalan di level Python satu item demi satu item. Pada Chapter pembuka Section ini kita mengukur kedua jenis biaya tersebut dengan memory_usage(), lalu merangkum operasi mana yang murah dan mana yang mahal.
Dua Sumber Biaya Operasi #
Biaya sebuah operasi pandas datang dari dua sumber yang berbeda. Sumber pertama adalah memori: setiap kolom disimpan sebagai satu blok array NumPy, sehingga menambah kolom berarti mengalokasikan blok baru seukuran jumlah baris, dan menyalin DataFrame berarti menggandakan seluruh blok tersebut. Sumber kedua adalah jumlah pemanggilan: operasi vectorized memanggil rutinitas C satu kali untuk semua baris, sedangkan loop Python memanggil kode kita satu kali per baris — pada satu juta baris berarti satu juta pemanggilan.
Aturan pengukurannya konsisten. Method memory_usage(deep=True) melaporkan konsumsi memori per bagian DataFrame dalam byte, termasuk index; hasilnya kita jumlahkan untuk melihat total. Parameter deep=True penting untuk kolom teks karena pandas perlu menghitung isi string, bukan hanya pointer-nya.
Mengukur Biaya dengan Kode #
Skrip berikut membangun tabel transaksi satu juta baris dari seed acak yang tetap, lalu mengukur memori pada empat titik: tabel awal, setelah kolom baru, saat salinan penuh, dan pada subset kecil:
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 1_000_000
df = pd.DataFrame(
{
"jumlah": rng.integers(1, 11, size=n),
"harga": rng.choice([15000, 25000, 40000, 55000, 70000], size=n),
}
)
# 1. Ukuran awal: tiap kolom = satu blok memori terpisah
mu = df.memory_usage(deep=True)
print("memori per bagian (byte):")
print(mu.to_string())
print("total:", f"{int(mu.sum()):,}", "byte")
print()
# 2. Operasi murah: satu pemanggilan untuk seluruh kolom
df["total"] = df["jumlah"] * df["harga"]
print("total 3 baris pertama:", df["total"].head(3).tolist())
print("memori setelah kolom total:", f"{int(df.memory_usage(deep=True).sum()):,}", "byte")
print()
# 3. Operasi mahal: salinan penuh menggandakan konsumsi memori
df2 = df.copy()
m0 = int(df.memory_usage(deep=True).sum())
m1 = int(df2.memory_usage(deep=True).sum())
print("df :", f"{m0:,}", "byte | df2:", f"{m1:,}", "byte")
print("keduanya hidup di RAM:", f"{m0 + m1:,}", "byte")
print()
# 4. Operasi baris-per-baris mahal secara waktu
print("loop setara:", f"{n:,}", "pemanggilan Python vs 1 pemanggilan C")
sub = df[df["total"] > 500000]
print("subset shape:", sub.shape, "memori:", f"{int(sub.memory_usage(deep=True).sum()):,}", "byte")
kolom = df["harga"]
print("Series harga:", f"{int(kolom.memory_usage(deep=True)):,}", "byte")Output yang Dihasilkan:
memori per bagian (byte):
Index 132
jumlah 8000000
harga 8000000
total: 16,000,132 byte
total 3 baris pertama: [70000, 200000, 385000]
memori setelah kolom total: 24,000,132 byte
df : 24,000,132 byte | df2: 24,000,132 byte
keduanya hidup di RAM: 48,000,264 byte
loop setara: 1,000,000 pemanggilan Python vs 1 pemanggilan C
subset shape: (79686, 3) memori: 2,549,952 byte
Series harga: 8,000,132 byteMari kita telaah baris-baris pentingnya:
- Baris 4-11 (pembuatan
df): dua kolomint64untuk satu juta baris. Blokjumlahdanhargamasing-masing memakan 8.000.000 byte (1.000.000 × 8 byte), sedangkan index RangeIndex hanya 132 byte — sehingga totalnya 16.000.132 byte. - Baris 20-21 (
df["total"] = df["jumlah"] * df["harga"]): perkalian antar kolom dilakukan satu pemanggilan di level C untuk semua baris. Memori naik tepat 8.000.000 byte menjadi 24.000.132 byte karena pandas mengalokasikan satu blok baru untuk kolomtotal. - Baris 26-31 (
df2 = df.copy()): salinan penuh menggandakan seluruh blok. Dua DataFrame kini hidup berdampingan di RAM dengan total 48.000.264 byte — dua kali lipat dari satu tabel. Di sinilah kebiasaancopy()tanpa keperluan memakan memori. - Baris 34-35 (pembanding jumlah pemanggilan): loop baris-per-baris pada tabel ini setara dengan 1.000.000 pemanggilan Python, sementara versi vectorized-nya hanya satu pemanggilan C. Selisih jumlah pemanggilan inilah yang menjelaskan kenapa loop jauh lebih lambat.
- Baris 36-39 (subset dan kolom):
df[df["total"] > 500000]menghasilkan tabel baru hanya 79.686 baris (2.549.952 byte), dan mengambil satu kolom menghasilkan Series sebesar blok kolom itu saja (8.000.132 byte). Memilih data lebih hemat daripada menyalin seluruh tabel.
Operasi Murah vs Operasi Mahal #
| Operasi | Biaya memori | Biaya waktu | Contoh |
|---|---|---|---|
| Mengambil satu kolom | hampir nol | cepat | df["harga"] |
| Aritmetika antar kolom | satu blok baru | cepat, satu pemanggilan C | df["a"] * df["b"] |
| Filter boolean masking | tabel hasil (sekecil subset) | cepat | df[mask] |
| Menambah kolom hasil fungsi vectorized | satu blok baru | cepat | df["total"] = ... |
df.copy() penuh | menggandakan semua blok | sedang | df2 = df.copy() |
apply(axis=1) / iterrows() | kecil | sangat lambat, Python per baris | df.apply(f, axis=1) |
Pola yang tampak di tabel: biaya memori naik saat pandas membuat objek baru, sedangkan biaya waktu naik drastis saat pekerjaan kembali ke level Python. Jadi urutan optimasi yang sehat adalah mengukur dulu, mengganti pola Python-level dengan operasi vectorized, dan baru menekan memori lewat pemilihan dtype — urutan yang kita ikuti di Section ini.
Chapter berikutnya menyerang biaya terbesar terlebih dahulu: loop Python yang menyusup ke kode pandas — beserta penggantinya yang vectorized.
Referensi resmi untuk pendalaman: pandas.DataFrame.memory_usage , panduan Enhancing performance , dan catatan rilis pandas 3.0.0 .