Dua chapter sebelumnya melatih mendeteksi outlier dengan IQR dan z-score, tetapi mask True/False belum mengubah data apa pun. Setelah keputusan diambil bahwa sebuah nilai ekstrem tidak boleh tetap sebesar itu, alternatif selain membuang baris adalah membatasi nilainya. Method clip() melakukan pekerjaan itu: nilai di bawah batas bawah dinaikkan ke batas, nilai di atas batas atas diturunkan ke batas, dan barisnya tetap utuh. Chapter ini mempraktikkan dua gaya pembatasan: batas domain tetap dan winsorization berbasis persentil.
Clipping dengan Batas Domain #
Gudang menyimpan kopi hijau pada spesifikasi suhu 18-25 derajat Celsius. Ketika spesifikasi semacam ini menjadi acuan, clip() dengan batas tetap memaksa seluruh pembacaan masuk rentang tersebut:
"""Membatasi nilai ekstrem dengan clip(): batas domain dan winsorization persentil."""
import pandas as pd
import numpy as np
kalender = pd.date_range("2026-01-01", periods=14, freq="D")
suhu = pd.Series(
[
22.5, 23.1, np.nan, 24.0, 24.8, np.nan, np.nan,
25.5, 41.0, 25.2, 12.0, 23.8, 24.1, np.nan,
],
index=kalender,
name="suhu",
)
terpotong = suhu.clip(lower=18, upper=25)
print("Suhu asli :", suhu.tolist())
print("Suhu clip 18-25:", terpotong.tolist())
berubah = suhu.notna() & (terpotong != suhu)
print("Jumlah nilai terpotong :", int(berubah.sum()))
print("Mean sebelum :", round(suhu.mean(), 2), "-> sesudah:", round(terpotong.mean(), 2))
print()
kalender_k = pd.date_range("2026-01-01", periods=30, freq="D")
kelembapan = pd.Series(
[
61, 62, 60, 63, 59, 62, 61, 60, 64, 62,
61, 63, 60, 59, 62, 61, 63, 60, 62, 61,
89, 62, 60, 61, 63, 60, 59, 61, 62, 60,
],
index=kalender_k,
dtype="float64",
name="kelembapan",
)
batas_bawah = kelembapan.quantile(0.05)
batas_atas = kelembapan.quantile(0.95)
menang = kelembapan.clip(lower=batas_bawah, upper=batas_atas)
print("Winsorization 5%/95%: batas =", round(batas_bawah, 2), "dan", round(batas_atas, 2))
print("Nilai yang dipotong :", kelembapan[menang != kelembapan].tolist())
print("Menjadi :", menang[menang != kelembapan].tolist())
print("Jumlah nilai berubah:", int((menang != kelembapan).sum()))
print("Mean :", round(kelembapan.mean(), 2), "->", round(menang.mean(), 2))
print("Std :", round(kelembapan.std(), 2), "->", round(menang.std(), 2))Output yang Dihasilkan:
Suhu asli : [22.5, 23.1, nan, 24.0, 24.8, nan, nan, 25.5, 41.0, 25.2, 12.0, 23.8, 24.1, nan]
Suhu clip 18-25: [22.5, 23.1, nan, 24.0, 24.8, nan, nan, 25.0, 25.0, 25.0, 18.0, 23.8, 24.1, nan]
Jumlah nilai terpotong : 4
Mean sebelum : 24.6 -> sesudah: 23.53
Winsorization 5%/95%: batas = 59.0 dan 63.55
Nilai yang dipotong : [64.0, 89.0]
Menjadi : [63.55, 63.55]
Jumlah nilai berubah: 2
Mean : 62.1 -> 61.24
Std : 5.25 -> 1.36Mari kita telaah baris demi baris:
- Baris 15 (
suhu.clip(lower=18, upper=25)): nilai25.5,41.0, dan25.2diturunkan menjadi25.0, sedangkan12.0dinaikkan menjadi18.0; keempat selNaNtetapNaNkarenaclip()tidak menyentuh kekosongan. - Baris 18-19 (hitung perubahan):
suhu.notna() & (terpotong != suhu)menghitung empat nilai berubah; tandanotna()wajib ada karenaNaN != NaNbernilaiTruedan akan menghitung kekosongan sebagai perubahan. - Baris 20 (mean sebelum-sesudah): rata-rata bergeser dari
24.6menjadi23.53— pembatasan satu sisi saja (atas) sudah menarik mean turun.
Perhatikan pelajaran penting dari output: 25.2 dan 25.5 ikut terpotong padahal keduanya bukan outlier. Batas domain adalah keputusan spesifikasi, bukan keputusan statistik — semua nilai di luar spesifikasi ditarik ke batas. Gunakan gaya ini hanya bila batasnya memang aturan keras, misalnya rentang penyimpanan yang disyaratkan, bukan sekadar nilai yang terlihat janggal.
Winsorization dengan Batas Persentil #
Winsorization membatasi nilai pada persentil data itu sendiri, lazimnya 5 persen dan 95 persen. Batasnya dihitung dengan quantile() — persentil adalah sisi lain dari kuantil yang sudah kita pakai pada metode IQR — lalu diberikan ke clip(). Bagian kedua skrip menerapkannya pada kelembapan 30 hari yang memuat kesalahan 89.0:
- Baris 35-36 (persentil 5%/95%): batas bawah
59.0dan batas atas63.55. - Baris 37 (
kelembapan.clip(...)): hanya dua nilai berubah —64.0dan89.0sama-sama menjadi63.55; batas bawah59.0kebetulan sama dengan nilai minimum, sehingga tidak ada nilai bawah yang terpotong. - Baris 42-43 (dampak): mean turun dari
62.1ke61.24, dan std anjlok dari5.25ke1.36; outlier yang tadi menggelembungkan standard deviation kini tak berpengaruh.
Persentase 5/95 bukan aturan mati; 1/99 dan 10/90 juga umum dipakai, tergantung seberapa ekstrem data boleh dibenahi. Dokumentasinya ada pada pandas.Series.clip dan pandas.DataFrame.clip untuk menerapkan batas pada banyak kolom sekaligus; perhitungan persentilnya merujuk pandas.DataFrame.quantile .
clip() atau Buang Barisnya #
| Keputusan | Pola kode | Konsekuensi |
|---|---|---|
| Winsorization | s = s.clip(lower=b1, upper=b2) | Jumlah baris tetap; ekor distribusi terpangkas |
| Buang baris | s = s[(s >= b1) & (s <= b2)] | Baris hilang dari semua kolom lain |
| Biarkan | s tanpa perubahan | Nilai sah bisa tetap dipertahankan bila bukan kesalahan |
Winsorization cocok ketika barisnya masih bernilai — satu pembacaan sensor salah tidak menghapus tanggal, status, dan kelembapan pada baris yang sama. Membuang baris lebih tepat ketika seluruh baris tidak bisa dipercaya, misalnya duplikat atau data dari sensor mati. Dan jangan lupakan opsi ketiga: nilai 25.2 yang sedikit di atas spesifikasi bisa jadi kejadian nyata yang justru perlu dilaporkan.
Kini seluruh perangkat section ini sudah lengkap: mendeteksi pola missing, mengisi, interpolasi, mengelompokkan, mendeteksi outlier, dan membatasi nilai. Chapter penutup berikutnya menyusun semuanya menjadi satu kerangka keputusan yang bisa kita pegang setiap kali data kotor datang.