Dari semua teknik hemat memori di Section ini, dtype category adalah yang paling agresif — dan satu-satunya yang bisa memperburuk keadaan bila dipakai di kolom yang salah. Kuncinya satu angka: kardinalitas, yaitu jumlah nilai unik dibanding jumlah baris. Kolom teks berulang seperti kota dengan lima nilai unik pada 100.000 baris sangat menguntungkan; kolom ID yang hampir unik justru membengkak setelah dikonversi. Chapter ini menghitung penghematannya dengan memory_usage(deep=True), membongkar anatomi category yang membuat penghematan itu terjadi, dan menetapkan ambang kapan konversi layak dilakukan.
Menghitung Penghematan pada Data Nyata #
Skrip berikut memakai tabel transaksi yang berisi dua kolom teks berulang (kota, produk) dan satu kolom teks hampir unik (id_pesanan) sebagai pembanding:
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 100_000
df = pd.DataFrame(
{
"kota": rng.choice(["Jakarta", "Bandung", "Surabaya", "Medan", "Semarang"], size=n),
"produk": rng.choice(
["Beras", "Minyak Goreng", "Gula Pasir", "Kopi", "Telur", "Tepung Terigu", "Sabun", "Sarden"],
size=n,
),
"id_pesanan": [f"TRX-{i:06d}" for i in range(n)],
"jumlah": rng.integers(1, 11, size=n),
"harga": rng.choice([15000, 25000, 40000, 55000, 70000], size=n),
}
)
df["total"] = df["jumlah"] * df["harga"]
# 1. Peta memori sebelum konversi
mu = df.memory_usage(deep=True)
print(mu.to_string())
print("total:", f"{int(mu.sum()):,}", "byte")
print()
# 2. Konversi dua kolom berulang ke category
df["kota"] = df["kota"].astype("category")
df["produk"] = df["produk"].astype("category")
mu2 = df.memory_usage(deep=True)
print("total sesudah:", f"{int(mu2.sum()):,}", "byte")
print("rasio:", round(int(mu.sum()) / int(mu2.sum()), 1))
print()
# 3. Anatomi category: daftar kategori + kode integer
s = df["kota"]
print("kategori:", s.cat.categories.tolist())
print("dtype kode:", s.cat.codes.dtype)
print("kode 4 baris pertama:", s.cat.codes.head(4).tolist())
print()
# 4. Kardinalitas menentukan ukuran kode
for banyak in [5, 200, 70_000]:
s_k = pd.Series([f"nilai-{i % banyak}" for i in range(n)]).astype("category")
print(f"{banyak:>6,} kategori -> kode {s_k.cat.codes.dtype}, memori {int(s_k.memory_usage(deep=True)):,} byte")
print()
# 5. Gotcha: teks hampir unik justru lebih boros
print("id str :", f"{int(df['id_pesanan'].memory_usage(deep=True)):,}", "byte")
print("id category :", f"{int(df['id_pesanan'].astype('category').memory_usage(deep=True)):,}", "byte")
print()
# 6. Analisis tetap berjalan seperti biasa
print("value_counts kota:", df["kota"].value_counts().to_dict())
print("groupby sum per kota:", df.groupby("kota")["total"].sum().astype(int).to_dict())Output yang Dihasilkan:
Index 132
kota 1499235
produk 1563307
id_pesanan 1800000
jumlah 800000
harga 800000
total 800000
total: 7,262,674 byte
total sesudah: 4,400,334 byte
rasio: 1.7
kategori: ['Bandung', 'Jakarta', 'Medan', 'Semarang', 'Surabaya']
dtype kode: int8
kode 4 baris pertama: [1, 2, 2, 4]
5 kategori -> kode int8, memori 100,208 byte
200 kategori -> kode int16, memori 203,447 byte
70,000 kategori -> kode int32, memori 1,727,772 byte
id str : 1,800,132 byte
id category : 2,212,632 byte
value_counts kota: {'Medan': 20268, 'Jakarta': 19981, 'Bandung': 19980, 'Surabaya': 19907, 'Semarang': 19864}
groupby sum per kota: {'Bandung': 4481275000, 'Jakarta': 4482480000, 'Medan': 4560765000, 'Semarang': 4459625000, 'Surabaya': 4515020000}Mari kita telaah baris-baris pentingnya:
- Baris 21-23 (garis dasar): dua kolom teks berulang mendominasi peta memori —
kota1.499.235 byte danproduk1.563.307 byte — sementaraid_pesananmenyita 1.800.000 byte. Total tabel 7.262.674 byte. - Baris 27-31 (konversi selektif): hanya dua kolom berulang yang dikonversi; total turun menjadi 4.400.334 byte (rasio 1.7 untuk tabel utuh). Kolom
id_pesanansengaja tidak disentuh — bagian ke-5 skrip menjelaskan alasannya. - Baris 35-38 (anatomi
category): kolomcategoryhanya menyimpan dua hal — daftar kategori terurut (['Bandung', 'Jakarta', 'Medan', 'Semarang', 'Surabaya']) dan satu kode integer per baris (dtypeint8, kode 4 baris pertama[1, 2, 2, 4]). Inilah sumber penghematan: teks utuh disimpan sekali, bukan seratus ribu kali. - Baris 42-44 (kardinalitas menentukan dtype kode): 5 kategori memakai kode
int8(100.208 byte), 200 kategori naik keint16(203.447 byte), dan 70.000 kategori butuhint32(1.727.772 byte). Kode tumbuh bertahap, tetapi daftar kategorinya juga ikut membesar. - Baris 48-49 (gotcha teks hampir unik):
id_pesanansebagaistrmemakan 1.800.132 byte; sebagaicategorymalah 2.212.632 byte — lebih besar, karena pandas menyimpan seluruh teks unik di daftar kategori plus kodeint32per baris. Konversi ini murni merugikan. - Baris 53-54 (analisis tetap jalan):
value_counts()dangroupby()...sum()pada kolomcategorymenghasilkan angka yang sama dengan versistr—Medan20.268 kemunculan, total per kota 4,4-4,6 miliar. Hemat memori tidak menuntut perubahan cara analisis.
Ambang Kapan Konversi Layak #
Kardinalitas (nunique vs panjang) | Contoh kolom | Keputusan |
|---|---|---|
| < 1% (5-10 dari 100.000) | kota, status, produk | konversi, penghematan terbesar |
| 1-10% | kelurahan, kode_pos | konversi, hitung dulu hasilnya |
| > 50% | nama_pelanggan, id_pesanan | biarkan str |
| 100% unik | kolom ID | jangan konversi — memori malah naik |
Aturan perhitungannya sederhana: penghematan per baris adalah (ukuran teks + 8 byte pointer) dikurangi ukuran kode (1-4 byte), lalu ditambah biaya sekali simpan daftar kategori. Makin rendah kardinalitas dan makin pendek teksnya, makin besar rasionya.
Dengan dtype yang tepat di setiap kolom, kita sudah menekan memori dari dua sisi: angka lewat downcast dan teks lewat category. Chapter terakhir Section ini memakai semua alat itu untuk menjawab pertanyaan yang lebih besar: sampai batas mana pandas sanggup, dan pilihan apa yang tersedia ketika data benar-benar melebihi kemampuannya.
Referensi resmi untuk pendalaman: panduan Categorical data , pandas.Categorical , dan pandas.Series.cat.codes .