Dua nilai pendapatan 38 dan 45 juta pada chapter sebelumnya telah memperlihatkan daya rusak pencilan terhadap mean. Pencilan (outlier) adalah nilai yang jauh terpisah dari mayoritas data, dan keberadaannya memengaruhi hampir semua tahap persiapan: imputasi mean menjadi bias, scaling termasuk StandardScaler ikut tergeser, dan model berbasis jarak bisa salah fokus. Namun tindakan refleks yang paling berbahaya justru menghapusnya begitu saja — karena dalam banyak domain, nilai ekstrem tersebut bukan galat, melainkan kenyataan yang harus dipelajari model. Chapter ini membahas dua aturan deteksi klasik (IQR dan z-score), membandingkan hasilnya pada data yang sama, lalu menutup dengan strategi penanganan yang dipilih secara sadar, bukan otomatis.
Apa itu Pencilan dan Mengapa Konteks Menentukan #
Definisi pencilan bersifat relatif terhadap konteks, dan penilaian konteks tidak bisa didelegasikan ke rumus. Gaji direktur utama Rp450 juta per bulan adalah pencilan pada data karyawan staf, tetapi nilai wajar pada data jajaran direksi. Transaksi Rp4,8 miliar pada rekening pribadi adalah pencilan pada data transaksi ritel — dan justru itulah sinyal yang ingin ditangkap sistem deteksi penipuan, bukan dibuang. Usia tercatat 300 tahun hampir pasti galat input; harga rumah Rp3,45 miliar di pasar rata-rata Rp850 juta adalah segmen pasar yang sah. Rumus deteksi hanya menandai nilai yang jauh; keputusan apakah nilai itu sah harus datang dari pemahaman domain — dan keputusan itulah yang menentukan pilihan strategi penanganan.
Dua Aturan Deteksi Klasik #
Aturan IQR memakai rentang antar-kuartil yang sudah kita kenal dari describe(): batas bawah Q1 - 1,5 × IQR dan batas atas Q3 + 1,5 × IQR. Karena kuartil hanya membaca posisi data terurut, satu atau dua nilai ekstrem tidak menggeser kuartil — aturan ini tahan pencilan (robust). Aturan z-score menghitung berapa simpangan baku nilai menjauh dari rata-rata, lalu menandai yang melewati ±3. Kelemahan z-score justru terletak pada bahan bakunya: pencilan ikut menggelembungkan mean dan simpangan baku, sehingga ambangnya ikut naik dan pencilan “menyembunyikan dirinya sendiri” — efek yang disebut masking. Boxplot menggambarkan aturan IQR secara visual: batas kotak adalah kuartil dan titik di luar garis sumbu adalah nilai yang ditandai, sehingga grafik ini menjadi pemeriksaan cepat pertama sebelum menghitung apa pun.
| Metode | Ambang | Kekuatan | Kelemahan |
|---|---|---|---|
| Aturan IQR | [Q1 - 1,5×IQR, Q3 + 1,5×IQR] | Tahan pencilan; cocok data miring | Ambang 1,5 konvensi, bukan kebenaran mutlak |
| Z-score | melewati ±3 | Intuitif untuk sebaran normal | Mean/std tergeser pencilan (masking) |
| Boxplot | visual IQR | Terlihat sekilas per kolom | Sulit untuk data multidimensi |
Contoh Kode: Deteksi Tanpa Penghapusan #
Contoh berikut membangun 40 harga rumah pasar menengah lalu menanam tiga harga segmen mewah. Kedua aturan deteksi diterapkan dan hasilnya dicetak berdampingan — contoh ini sengaja tidak menghapus satu baris pun.
# Deteksi pencilan pada data harga rumah sintetis dengan dua metode:
# aturan IQR dan z-score, lalu efek transformasi log dan pembatasan (cap).
# Contoh ini HANYA MENANDAI pencilan — tidak ada baris yang dihapus.
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 40
harga = rng.normal(loc=850, scale=110, size=n).round(1).tolist()
# Tanam 3 pencilan pada posisi tetap (rumah mewah di pasar menengah).
harga[5] = 2380.0
harga[21] = 2950.0
harga[33] = 3450.0
df = pd.DataFrame({"harga_juta": harga})
# --- 1) Deteksi dengan aturan IQR ---
q1 = df["harga_juta"].quantile(0.25)
q3 = df["harga_juta"].quantile(0.75)
iqr = q3 - q1
batas_bawah = q1 - 1.5 * iqr
batas_atas = q3 + 1.5 * iqr
mask_iqr = (df["harga_juta"] < batas_bawah) | (df["harga_juta"] > batas_atas)
# --- 2) Deteksi dengan z-score ---
rata = df["harga_juta"].mean()
simpangan = df["harga_juta"].std()
z = (df["harga_juta"] - rata) / simpangan
mask_z = z.abs() > 3
print("== Statistik deteksi ==")
print(f"Q1 = {q1:.1f} Q3 = {q3:.1f} IQR = {iqr:.1f}")
print(f"batas IQR : [{batas_bawah:.1f}, {batas_atas:.1f}]")
print(f"mean = {rata:.1f} std = {simpangan:.1f}")
print(f"batas z-score atas (mean + 3 std) : {rata + 3 * simpangan:.1f}")
print("\n== Baris yang ditandai aturan IQR ==")
print(df.loc[mask_iqr].assign(z_skor=z[mask_iqr].round(2)).to_string())
print("\n== Baris yang ditandai z-score (>|3|) ==")
print(df.loc[mask_z].assign(z_skor=z[mask_z].round(2)).to_string())
print("\n== Jumlah baris yang ditandai ==")
print("IQR :", int(mask_iqr.sum()))
print("z-score :", int(mask_z.sum()))
# --- 3) Alternatif tanpa menghapus: transformasi log dan pembatasan ---
harga_log = np.log(df["harga_juta"])
harga_dipatok = df["harga_juta"].clip(upper=batas_atas)
skew_sebelum = df["harga_juta"].skew()
skew_sesudah = harga_log.skew()
median_harga = df["harga_juta"].median()
print("\n== Efek transformasi log dan pembatasan (cap) ==")
print(f"median harga : {median_harga:.1f}")
print(f"rasio maksimum / median : {df['harga_juta'].max() / median_harga:.2f}")
print(f"kemiringan (skew) sebelum log : {skew_sebelum:.3f}")
print(f"kemiringan (skew) sesudah log : {skew_sesudah:.3f}")
print(f"nilai maksimum sebelum log : {df['harga_juta'].max():.1f}")
print(f"nilai maksimum sesudah log : {harga_log.max():.3f}")
print(f"nilai maksimum setelah dipatok : {harga_dipatok.max():.1f}")Output yang Dihasilkan:
== Statistik deteksi ==
Q1 = 809.8 Q3 = 938.4 IQR = 128.6
batas IQR : [617.0, 1131.2]
mean = 1017.7 std = 570.1
batas z-score atas (mean + 3 std) : 2727.9
== Baris yang ditandai aturan IQR ==
harga_juta z_skor
5 2380.0 2.39
21 2950.0 3.39
33 3450.0 4.27
== Baris yang ditandai z-score (>|3|) ==
harga_juta z_skor
21 2950.0 3.39
33 3450.0 4.27
== Jumlah baris yang ditandai ==
IQR : 3
z-score : 2
== Efek transformasi log dan pembatasan (cap) ==
median harga : 886.9
rasio maksimum / median : 3.89
kemiringan (skew) sebelum log : 3.476
kemiringan (skew) sesudah log : 2.928
nilai maksimum sebelum log : 3450.0
nilai maksimum sesudah log : 8.146
nilai maksimum setelah dipatok : 1131.2Bedah Kode Baris-per-Baris #
- Baris 8–16 (data sintetis): empat puluh harga “normal” diambil dari sebaran normal berseed 42, lalu tiga harga mewah ditanam pada indeks tetap. Dengan menanam pencilan secara sadar, kita tahu jawaban benarnya — tiga baris — sehingga kualitas kedua metode bisa dinilai.
- Baris 21–26 (aturan IQR): kuartil dibaca lewat
quantile(), batas dihitung dengan faktor 1,5, dan hasilnya adalah mask boolean — seriTrue/Falseper baris, bukan data yang sudah dimodifikasi. - Baris 29–32 (z-score): z dihitung manual sebagai selisih terhadap mean dibagi simpangan baku, lalu
mask_zmenandai nilai dengan nilai mutlak melebihi 3. Perhitungan manual ini sengaja ditampilkan agar rumusnya terlihat, bukan tersembunyi di dalam pustaka. - Baris 40–44 (tabel baris tertandai):
df.loc[mask]menyaring baris yang ditandai,.assign()menambahkan kolom z-score sebagai konteks. Bandingkan kedua tabel pada output: inilah inti perbandingannya. - Baris 51–52 (log dan cap): dua alternatif tanpa penghapusan dihitung namun tidak menggantikan data asli — hasilnya hanya untuk diamati.
clip(upper=batas_atas)membatasi nilai maksimum pada ambang IQR. - Baris 54–65 (efek terukur): kemiringan (skewness) sebelum dan sesudah log, rasio maksimum terhadap median, serta nilai maksimum setelah transformasi — tiga angka yang menggambarkan seberapa besar ekor sebaran diredam.
Membaca Hasil: Efek Masking pada z-score #
Aturan IQR menandai tepat tiga baris yang kita tanam. Z-score hanya menandai dua: harga 2.950 dan 3.450. Harga 2.380 lolos karena ambangnya sendiri telah dinaikkan oleh para pencilan — mean tergeser ke 1.017,7 dan simpangan baku membengkak menjadi 570,1, sehingga batas atas mean + 3σ mencapai 2.727,9. Dengan kata lain, semakin ekstrem pencilannya, semakin lebar ambang z-score, dan semakin mudah pencilan menyelinap — efek masking yang menjelaskan mengapa IQR biasanya menjadi pilihan pertama untuk data miring. Bagian ketiga memperlihatkan dua jalur penanganan tanpa penghapusan: transformasi log menurunkan kemiringan dari 3,476 ke 2,928 dan menekan nilai maksimum dari 3.450 menjadi 8,146 di ruang log. Perhatikan bahwa penurunannya moderat karena rasio maksimum/median hanya 3,89 — transformasi log memberi dampak besar ketika nilai-nilainya terpisah berorde magnitudo (pendapatan Rp5 juta hingga Rp5 miliar, misalnya), bukan sekadar empat kali lipat. Pembatasan (cap) bekerja langsung: nilai maksimum jatuh ke ambang IQR 1.131,2 tanpa satu baris pun dibuang.
Strategi Penanganan: Memilih secara Sadar #
Empat pilihan tersedia setelah pencilan ditandai. Mempertahankan adalah pilihan sah ketika nilai ekstrem adalah kenyataan domain — distribusi kekayaan dan durasi kunjungan memang ber ekor panjang, dan model perlu mengenalinya. Membatasi (cap/winsorize) menukar nilai ekstrem dengan ambang seperti pada contoh; model tetap menerima sinyal “nilai ini tinggi” tanpa koordinat yang menarik seluruh perhitungan. Transformasi log meredam ekor sekaligus membuat sebaran lebih simetris untuk model linier. Menghapus hanya pantas dengan justifikasi eksplisit: nilai terbukti galat entri (usia 300, harga negatif), atau analisis memang khusus mempelajari populasi tertentu — dan setiap penghapusan perlu dicatat berapa baris serta alasannya.
Pencilan yang dibiarkan atau dibatasi tetap meninggalkan satu masalah: fitur dengan skala dan sebaran berbeda membuat model berbasis jarak dan gradient descent bekerja tidak seimbang. Chapter berikutnya, Skala Fitur dengan Scaling dan Normalisasi, menuntaskan masalah tersebut — termasuk satu scaler yang khusus tahan pencilan.
Referensi Resmi #
- scikit-learn — Outlier detection — panduan resmi deteksi anomali dan pencilan.
- pandas — DataFrame.skew — dokumentasi pengukuran kemiringan sebaran.
- pandas — Working with missing data — rujukan lanjutan interaksi pencilan dan nilai hilang.