Bayangkan satu dataset pelanggan dengan kolom usia bernilai 23–50 dan kolom pendapatan_juta bernilai 6–95. Bagi manusia kedua kolom itu sama-sama wajar; bagi model berbasis jarak, kolom pendapatan sepenuhnya mendominasi perhitungan karena selisihnya puluhan kali lebih besar. Scaling menyatukan fitur-fitur ke skala yang sebanding sehingga setiap kolom berkontribusi secara adil — dan sebagaimana akan kita lihat, pilihan scaler yang salah pada data memuat pencilan justru memperburuk ketimpangan itu. Chapter ini membandingkan tiga scaler utama pada data yang sama, lalu menegaskan satu aturan yang tidak bisa ditawar: statistik scaling hanya boleh dipelajari dari data latih.

Mengapa Sebagian Model Membutuhkan Scaling #

Model yang menghitung jarak atau mengandalkan gradient adalah pihak yang paling menderita akibat skala tak sebanding. k-Nearest Neighbors mengukur kemiripan lewat jarak Euclidean; dengan skala mentah, selisih pendapatan 10 juta menghasilkan jarak yang sama dengan selisih usia 10 tahun — padahal maknanya jauh berbeda. SVM membangun bidang pemisah berdasarkan margin yang dihitung dalam ruang fitur; satu fitur berskala besar membuat bidang tersebut hampir sepenuhnya ditentukan oleh fitur itu saja. PCA mencari arah dengan varians terbesar; kolom berskala besar otomatis menyumbang varians terbesar sehingga komponen utamanya hanya mencerminkan skala, bukan pola. Gradient descent pada regresi dan neural network menapak lembah loss function; bila satu fitur berskala ribuan kali lainnya, langkah pembelajarinya terlalu besar di satu arah dan terlalu kecil di arah lain sehingga konvergensi lambat atau justru meleset.

Sebaliknya, model berbasis pohon — Decision Tree, Random Forest, Gradient Boosting — membangun keputusan dengan ambang per fitur (“pendapatan > 12 juta?”), sehingga pohon tidak peduli apakah nilainya ditulis dalam juta atau triliunan. Scaling pada model pohon tidak membantu dan tidak merusak; scaling menjadi wajib ketika jarak, proyeksi, atau gradient ikut bekerja.

Tiga Scaler Utama dan Rumusnya #

ScalerRumus per kolomRentang hasilKapan dipakai
StandardScaler(x − mean) / stdsekitar 0, sebaran 1Default serba-bisa; asumsi sebaran kira-kira normal
MinMaxScaler(x − min) / (max − min)tepat [0, 1]Butuh rentang pasti (jaringan saraf, citra)
RobustScaler(x − median) / IQRbebas, terpusat 0Data memuat pencilan; statistik kuartil tahan geseran

Dua nama lain sering muncul di dokumentasi. MaxAbsScaler membagi setiap kolom dengan nilai mutlak maksimumnya sehingga hasil berada dalam [−1, 1] — pilihan umum untuk data sparse karena sel bernilai nol tetap nol. Normalizer adalah sumber kebingungan klasik: transformer ini bukan scaler kolom, melainkan penskala per baris yang menormalkan setiap vektor sampel menjadi panjang satu (norma L2). Setelah Normalizer, baris [23, 60] dan [46, 120] menjadi vektor identik karena hanya rasio antar fitur dalam satu baris yang dipertahankan — berguna untuk data teks (TF-IDF) dan clustering dokumen, menyesatkan untuk fitur tabular biasa.

Contoh Kode: Tiga Scaler pada Data dengan Pencilan #

Contoh berikut menerapkan ketiga scaler pada delapan pelanggan yang sama — termasuk satu pelanggan berpendapatan 95 juta — lalu membandingkan nasib baris biasa dan baris pencilan pada setiap scaler.

python
# Perbandingan StandardScaler, MinMaxScaler, dan RobustScaler
# pada data pelanggan yang memuat satu pencilan pendapatan.
# Ditutup dengan disiplin yang benar: scaler di-fit HANYA pada data latih.

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler, RobustScaler, StandardScaler

rng = np.random.default_rng(42)

usia = rng.integers(23, 51, size=8)
pendapatan_juta = rng.integers(6, 18, size=8).astype(float)
pendapatan_juta[6] = 95.0  # pencilan: satu pelanggan berpendapatan sangat besar

df = pd.DataFrame({"usia": usia, "pendapatan_juta": pendapatan_juta})
kolom = ["usia", "pendapatan_juta"]
print("== Data asal ==")
print(df.to_string())

hasil = {}
for nama, scaler in [
    ("StandardScaler", StandardScaler()),
    ("MinMaxScaler", MinMaxScaler()),
    ("RobustScaler", RobustScaler()),
]:
    nilai = scaler.fit_transform(df[kolom])
    hasil[nama] = pd.DataFrame(nilai, columns=kolom).round(3)

for nama, tabel in hasil.items():
    print(f"\n== {nama} ==")
    print(tabel.to_string())

# Fokus: baris 6 (pencilan) dan baris 0 (pelanggan biasa) pada kolom pendapatan.
fokus = pd.DataFrame(
    {
        "StandardScaler": [
            hasil["StandardScaler"].loc[0, "pendapatan_juta"],
            hasil["StandardScaler"].loc[6, "pendapatan_juta"],
        ],
        "MinMaxScaler": [
            hasil["MinMaxScaler"].loc[0, "pendapatan_juta"],
            hasil["MinMaxScaler"].loc[6, "pendapatan_juta"],
        ],
        "RobustScaler": [
            hasil["RobustScaler"].loc[0, "pendapatan_juta"],
            hasil["RobustScaler"].loc[6, "pendapatan_juta"],
        ],
    },
    index=["baris 0 (biasa)", "baris 6 (pencilan)"],
)
print("\n== Kolom pendapatan_juta: baris biasa vs pencilan ==")
print(fokus.to_string())

print("\n== Rentang pendapatan 7 baris biasa setelah scaling ==")
for nama, tabel in hasil.items():
    kolom_biasa = tabel.loc[tabel.index != 6, "pendapatan_juta"]
    print(f"{nama:15s}: {kolom_biasa.min():.3f} s/d {kolom_biasa.max():.3f}")

# Disiplin: scaler hanya mempelajari statistik dari data latih.
X = df[kolom].to_numpy()
X_latih, X_uji = train_test_split(X, test_size=0.25, random_state=42)
scaler_latih = StandardScaler().fit(X_latih)
print("\n== Disiplin: fit hanya pada data latih ==")
print("mean data latih (dipakai scaler) :", np.round(scaler_latih.mean_, 3).tolist())
print("mean seluruh data (tidak dipakai) :", np.round(X.mean(axis=0), 3).tolist())
X_uji_terukur = scaler_latih.transform(X_uji)
print("data uji sebelum :", X_uji.tolist())
print("data uji sesudah :", np.round(X_uji_terukur, 3).tolist())

Output yang Dihasilkan:

text
== Data asal ==
   usia  pendapatan_juta
0    25              8.0
1    44              7.0
2    41             12.0
3    35             17.0
4    35             14.0
5    47             15.0
6    25             95.0
7    42             15.0

== StandardScaler ==
    usia  pendapatan_juta
0 -1.508           -0.542
1  0.931           -0.578
2  0.546           -0.396
3 -0.225           -0.214
4 -0.225           -0.323
5  1.316           -0.287
6 -1.508            2.627
7  0.674           -0.287

== MinMaxScaler ==
    usia  pendapatan_juta
0  0.000            0.011
1  0.864            0.000
2  0.727            0.057
3  0.455            0.114
4  0.455            0.080
5  1.000            0.091
6  0.000            1.000
7  0.773            0.091

== RobustScaler ==
   usia  pendapatan_juta
0  -1.3           -1.444
1   0.6           -1.667
2   0.3           -0.556
3  -0.3            0.556
4  -0.3           -0.111
5   0.9            0.111
6  -1.3           17.889
7   0.4            0.111

== Kolom pendapatan_juta: baris biasa vs pencilan ==
                    StandardScaler  MinMaxScaler  RobustScaler
baris 0 (biasa)             -0.542         0.011        -1.444
baris 6 (pencilan)           2.627         1.000        17.889

== Rentang pendapatan 7 baris biasa setelah scaling ==
StandardScaler : -0.578 s/d -0.214
MinMaxScaler   : 0.000 s/d 0.114
RobustScaler   : -1.667 s/d 0.556

== Disiplin: fit hanya pada data latih ==
mean data latih (dipakai scaler) : [33.833, 26.833]
mean seluruh data (tidak dipakai) : [36.75, 22.875]
data uji sebelum : [[44.0, 7.0], [47.0, 15.0]]
data uji sesudah : [[1.497, -0.648], [1.938, -0.387]]

Bedah Kode Baris-per-Baris #

  1. Baris 12–14 (data sintetis): usia dan pendapatan_juta diambil dari rng berseed, lalu baris keenam pendapatan ditimpa nilai 95 — pencilan yang sengaja ditanam pada posisi tetap.
  2. Baris 21–28 (loop tiga scaler): setiap scaler dibuat segar di dalam daftar, fit_transform() mempelajari statistik kolom lalu langsung menerapkannya, dan hasilnya disimpan sebagai DataFrame terpisah agar ketiganya bisa dibandingkan tanpa saling menimpa.
  3. Baris 34–53 (tabel fokus): nilai pendapatan dua baris penting — pelanggan biasa (indeks 0) dan pencilan (indeks 6) — dikumpulkan dari ketiga hasil agar perbedaan perlakuan scaler terlihat berdampingan.
  4. Baris 55–58 (rentang baris biasa): minimal dan maksimal pendapatan dihitung hanya dari tujuh baris non-pencilan. Angka ini mengungkap skala “ruang hidup” milik mayoritas data setelah transformasi.
  5. Baris 60–69 (disiplin split): train_test_split memisahkan enam baris latih dan dua baris uji, StandardScaler di-fit() hanya pada data latih, lalu transform() diterapkan ke data uji. Atribut mean_ memperlihatkan statistik yang benar-benar dipakai.

Membaca Hasil: Satu Pencilan, Tiga Nasib Berbeda #

Output memperlihatkan bagaimana satu nilai 95 juta membelokkan setiap scaler dengan caranya sendiri. StandardScaler menarik mean ke atas sehingga ketujuh pelanggan biasa bergeser menjadi nilai negatif sekitar −0,2 sampai −0,6 — bukan lagi berpusat di nol. MinMaxScaler mengalami kerusakan paling serius: seluruh baris biasa termampatkan ke rentang sempit 0,000–0,114 karena si pencilan memonopoli nilai 1,000; resolusi antar pelanggan nyaris hilang di kolom itu, tepat ketika kita paling membutuhkannya. RobustScaler mempertahankan ruang hidup mayoritas: baris biasa tetap tersebar sehat dari −1,667 sampai 0,556, sementara pencilan tetap tampak sebagai 17,889 — jelas ekstrem, tidak menyamar, dan tidak merusak pemetaan orang lain. Pelajarannya: bila data memuat pencilan dan Anda tetap ingin mayoritas data terpetakan baik, RobustScaler adalah pilihan paling aman; MinMaxScaler paling rentan terhadap satu nilai ekstrem.

Bagian terakhir output menegaskan aturan emasnya: mean data latih (33,833 dan 26,833) berbeda dari mean seluruh data (36,75 dan 22,875). Data uji yang diskalakan memakai statistik data latih itulah yang sah — nilai data uji tidak boleh ikut menentukan statistik transformasi.

Dengan fitur numerik yang sudah sebanding skala, satu kelompok fitur masih tersisa dalam bentuk yang tidak bisa dibaca model: teks kategorikal seperti kota dan jenjang pendidikan. Chapter berikutnya, Encoding Variabel Kategorikal, mengubahnya menjadi angka tanpa menanam urutan palsu.

Referensi Resmi #