Menguasai encoding kategori melampaui one-hot: target encoding, ordinal, dan frequency encoding, menangani high-cardinality yang meledakkan dimensi, serta workflow feature engineering ala kompetisi Kaggle untuk data tabular yang aman dari leakage.

Di episode 4 kita mengenal encoding dasar: One-Hot untuk nominal, Label untuk ordinal. Episode ini kita bongkar masalah yang tidak pernah dibahas di sana: apa yang terjadi saat kategori berjumlah ratusan ribu?
Bayangkan kolom kode_pos dengan 50.000 nilai unik, atau product_id dengan 1 juta SKU. One-Hot encoding akan meledakkan dimensi menjadi ratusan ribu kolom — lambat, rawan overfit, dan hampir tidak berguna. Untuk data tabular kompetisi seperti Kaggle, encoding lanjutan inilah yang membedakan solusi bagus dari yang biasa-biasa saja.
One-Hot ideal untuk 2-10 kategori bernilai nominal. Masalah muncul di high-cardinality:
| Jumlah kategori | One-Hot | Masalah |
|---|---|---|
| 3-10 | Ideal | Tidak ada |
| 10-50 | Mulai berat | Dimensi membesar |
| 100-1.000 | Sangat berat | Sparse, overfit |
| 10.000+ | Mustahil | Meledak, tidak praktis |
Selain dimensi, ada masalah kedua: kategori yang jarang muncul (misal 5 kemunculan dari 1 juta baris) — satu-hot untuk kategori langka hampir tidak memberi sinyal, hanya menambah noise.
Frequency encoding menggantikan setiap kategori dengan frekuensi kemunculannya: kategori yang sering muncul diberi nilai tinggi, yang langka rendah.
freq = df["kota"].value_counts(normalize=True)
df["kota_freq"] = df["kota"].map(freq)Keunggulan: satu kolom untuk kategori berapa pun banyaknya, dan langsung menangkap "popularitas". Kelemahan: dua kategori berbeda dengan frekuensi sama dianggap identik — informasi terkompresi berlebihan.
Untuk kategori yang memiliki urutan alami (rendah/sedang/tinggi, junior/mid/senior), ordinal encoding memberi angka sesuai urutan:
from sklearn.preprocessing import OrdinalEncoder
tingkat = [["junior", "mid", "senior"]]
enc = OrdinalEncoder(categories=tingkat)
X_encoded = enc.fit_transform(df[["level"]])Yang penting: jangan memakai ini untuk kategori nominal — memberi angka "acak" pada kota akan membuat model mengasumsikan urutan yang tidak ada.
Target encoding mengganti kategori dengan nilai rata-rata target untuk kategori tersebut. Contoh: untuk kolom kota, setiap nilai kota diganti dengan rata-rata harga rumah di kota itu.
means = df.groupby("kota")["harga"].mean()
df["kota_target"] = df["kota"].map(means)Kekuatannya besar — kolom kota_target mengandung sinyal prediktif langsung. Tapi inilah encoding paling berbahaya jika salah: karena nilai fitur dihitung dari target, ia sangat mudah bocor.
Warning
Target encoding menghitung statistik dari target. Jika dihitung dari seluruh data termasuk test set, model "melihat" jawaban test saat training — leakage. Solusi wajib: gunakan TargetEncoder dari category_encoders dengan cv-fold, atau tambahkan smoothing agar kategori langka ditarik ke rata-rata global.
Cara yang aman memakai library khusus:
from category_encoders import TargetEncoder
enc = TargetEncoder(cols=["kota"], smoothing=20)
X_enc = enc.fit_transform(X_train, y_train) # fit dengan y!
X_test_enc = enc.transform(X_test) # transform sajaPerhatikan: fit_transform menerima y_train karena itulah sumber informasinya — dan library ini menangani smoothing serta fold internal untuk mengurangi overfitting.
Pilihan encoding sebaiknya menyesuaikan model:
| Model | Encoding terbaik |
|---|---|
| Linear (regresi/logistic) | Frequency, target (dengan hati-hati), binning |
| kNN / distance-based | Semua harus discale; hindari one-hot dimensi besar |
| Tree-based | Bisa makan raw categorical (CatBoost), ordinal, frequency |
Menariknya, CatBoost punya dukungan kategori native: ia melakukan target encoding internal dengan mekanisme anti-leakage bawaan:
from catboost import CatBoostClassifier
model = CatBoostClassifier(cat_features=["kota", "produk"], iterations=500)
model.fit(X_train, y_train)Kalian tinggal mendeklarasikan kolom mana yang kategorikal (cat_features) — CatBoost menangani encoding-nya sendiri. Ini alasan mengapa CatBoost menjadi pilihan serius untuk dataset dengan banyak kolom kategorikal.
Praktik yang terbukti di kompetisi tabular:
from sklearn.model_selection import cross_val_score
import lightgbm as lgb
def cv_score(X, y, cat_cols=None):
model = lgb.LGBMClassifier(random_state=42)
return cross_val_score(model, X, y, cv=5, scoring="roc_auc").mean()
# Skema A: one-hot
X_a = pd.get_dummies(df[["kota", "produk"]])
print(f"One-Hot: {cv_score(X_a, y):.4f}")
# Skema B: frequency + ordinal
df["kota_freq"] = df["kota"].map(df["kota"].value_counts(normalize=True))
df["produk_freq"] = df["produk"].map(df["produk"].value_counts(normalize=True))
X_b = df[["kota_freq", "produk_freq"]]
print(f"Frequency: {cv_score(X_b, y):.4f}")Angka CV inilah yang menjadi juri — bukan intuisi.
handle_unknown yang tidak konsisten — kategori test yang tidak ada di train harus punya nilai default.Pada episode 15 ini, kalian telah menguasai encoding lanjutan untuk data kategorikal.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita masuk dimensi baru: model security & adversarial ML — data poisoning, adversarial examples, membership inference, plus strategi mitigasi, sanitasi data, dan monitoring model di produksi. Sampai jumpa di episode 16!