Outlier masih boleh jadi kebenaran, tetapi ada nilai yang pasti salah: umur negatif, umur 250 tahun, atau persentase diskon 120. Nilai semacam ini melanggar logika domain data, bukan sekadar jauh dari rata-rata. Chapter ini membahas pola koreksi tiga langkah untuk kasus seperti itu: deteksi dengan boolean mask, penandaan sebagai NaN, lalu pengisian ulang dengan nilai yang bisa dipertanggungjawabkan.
Menetapkan Aturan Domain Tiap Kolom #
Aturan domain adalah batas yang membuat sebuah nilai masuk akal menurut logika bisnis, bukan menurut sebaran data. Aturan ini disepakati bersama pemilik data dan berbeda antar konteks: persentase diskon memang dibatasi 0 sampai 100, sedangkan batas umur pelanggan mengikuti produk yang dijual. Tabel berikut merangkum aturan yang kita pakai sepanjang chapter ini.
| Kolom | Aturan domain | Contoh pelanggaran |
|---|---|---|
umur | 0 sampai 120 tahun | -5 dan 250 |
persen_diskon | 0 sampai 100 persen | 120 |
Pola Koreksi: Deteksi, Tandai, Isi Ulang #
Skrip berikut menerapkan pola tiga langkah pada kedua kolom sekaligus. Kita deteksi baris bermasalah, tampilkan dulu sebagai bukti, tandai nilainya sebagai NaN, lalu isi ulang dengan strategi yang berbeda per kolom.
import pandas as pd
import numpy as np
data = pd.DataFrame({
"pelanggan": ["Budi", "Siti", "Dewi", "Andi", "Rina"],
"umur": [29, -5, 34, 250, 27],
"persen_diskon": [10, 15, 120, 8, 25],
})
mask_umur = (data["umur"] < 0) | (data["umur"] > 120)
mask_diskon = data["persen_diskon"] > 100
print("Baris dengan nilai tidak valid:")
print(data[mask_umur | mask_diskon])
print()
data.loc[mask_umur, "umur"] = np.nan
data.loc[mask_diskon, "persen_diskon"] = np.nan
median_umur = data["umur"].median()
print("Median umur data yang valid :", median_umur)
data["umur"] = data["umur"].fillna(median_umur)
data["persen_diskon"] = data["persen_diskon"].fillna(0)
print()
print("Data setelah koreksi:")
print(data)
print(data.dtypes)Output yang Dihasilkan:
Baris dengan nilai tidak valid:
pelanggan umur persen_diskon
1 Siti -5 15
2 Dewi 34 120
3 Andi 250 8
Median umur data yang valid : 29.0
Data setelah koreksi:
pelanggan umur persen_diskon
0 Budi 29.0 10.0
1 Siti 29.0 15.0
2 Dewi 34.0 0.0
3 Andi 29.0 8.0
4 Rina 27.0 25.0
pelanggan str
umur float64
persen_diskon float64
dtype: objectMari kita telaah apa yang terjadi pada kode di atas:
- Baris 10-11 (
mask_umur = ...danmask_diskon = ...): dua mask boolean mendeteksi pelanggaran aturan domain; tanda kurung di sekitar tiap perbandingan padamask_umurwajib ada. - Baris 13-14 (
print(data[mask_umur | mask_diskon])): gabungan kedua mask menampilkan tiga baris bermasalah sebelum data disentuh, sehingga keputusan koreksi punya dasar yang terlihat. - Baris 17-18 (
data.loc[mask_umur, "umur"] = np.nan): nilai pelanggar ditandaiNaNmelalui satu akses.loc; pola ini adalah cara penulisan data yang benar di pandas 3.0 dengan Copy-on-Write. - Baris 20-22 (
median_umur = ...dandata["umur"] = data["umur"].fillna(median_umur)): median dihitung dari sisa data yang valid, lalu menjadi nilai pengisi; median dipilih karena tidak tergeser oleh nilai ekstrem. - Baris 23 (
data["persen_diskon"] = data["persen_diskon"].fillna(0)): kolom diskon diisi nol sesuai asumsi bisnis bahwa baris tanpa data diskon berarti tanpa diskon. - Baris 27-28: data akhir dan
data.dtypesdicetak; Siti dan Andi kini berumur29.0, dan Dewi memiliki diskon0.0.
Mengapa Ditandai NaN Terlebih Dahulu #
Mengganti umur -5 langsung dengan median memang lebih pendek, tetapi pola penandaan NaN memberi dua keuntungan. Pertama, setiap keputusan pengisian menjadi eksplisit dan terdokumentasi di kode: median untuk umur, nol untuk diskon. Kedua, jalur ini identik dengan teknik pengisian missing values pada chapter Mengisi Nilai Kosong, sehingga satu kebiasaan yang sama melayani dua masalah berbeda.
dtype Berubah Saat NaN Masuk #
Perhatikan hasil data.dtypes di akhir output: kolom umur dan persen_diskon yang semula int64 kini bertipe float64. Pandas menyimpan NaN sebagai nilai floating-point, sehingga kolom bilangan bulat otomatis naik tipe begitu ada satu pun NaN di dalamnya; akibatnya nilai tampil sebagai 29.0, bukan 29.
Jika bentuk bilangan bulat perlu dipertahankan, buat kolom dengan dtype nullable "Int64" sejak awal. Untuk referensi lengkap strategi pengisian NaN, buka user guide missing data
.
Koreksi seperti di atas menyasar nilai yang sudah kita ketahui bermasalah. Ketika data baru terus mengalir, kita butuh pemeriksaan yang dapat dijalankan ulang kapan saja; pemeriksaan sistematis itulah topik chapter Validasi Rentang Nilai.