Metode IQR mengukur jarak berdasarkan kuartil; z-score mengukurnya berdasarkan rata-rata dan standard deviation. Z-score menyatakan seberapa jauh sebuah nilai dari mean, dalam satuan standard deviation (std): z = (x − mean) / std. Konvensi yang lazim menandai nilai dengan |z| > 3 sebagai outlier, dengan ambang 2 sebagai pilihan yang lebih ketat. Chapter ini menghitung z-score dengan pandas, lalu memperlihatkan kelemahan metode ini yang tidak dimiliki IQR: sensitif terhadap outlier itu sendiri.

Menghitung Z-Score #

Skrip berikut menguji aturan |z| > 3 pada kelembapan Sensor Utara selama 30 hari penuh Januari 2026, yang memuat satu kesalahan pembacaan 89.0 pada 21 Januari:

python
"""Mendeteksi outlier dengan z-score dan melihat batas keandalannya."""
import pandas as pd
import numpy as np

kalender = 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,
    dtype="float64",
    name="kelembapan",
)

mean_k = kelembapan.mean()
std_k = kelembapan.std()
z_kelep = (kelembapan - mean_k) / std_k
print("Kelembapan 30 hari: mean =", round(mean_k, 2), "std =", round(std_k, 2))
print("z-score hari 21:", round(z_kelep.loc["2026-01-21"], 2))
terflag = z_kelep.abs() > 3
print("Hari dengan |z| > 3 :", end=" ")
print(kelembapan[terflag].index.strftime("%d Jan").tolist(), "nilai", kelembapan[terflag].tolist())
print("Jumlah |z| > 2      :", int((z_kelep.abs() > 2).sum()))
print()

kalender_u = 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_u,
    name="suhu",
)

z_suhu = (suhu - suhu.mean()) / suhu.std()
print("Suhu 14 hari: mean =", round(suhu.mean(), 2), "std =", round(suhu.std(), 2))
print("z-score seluruh baris (NaN ikut menjadi NaN):")
print(z_suhu.round(2))
print("Jumlah |z| > 3 :", int((z_suhu.abs() > 3).sum()))
print("Jumlah |z| > 2 :", int((z_suhu.abs() > 2).sum()))
print("Nilai |z| > 2  :", suhu[(z_suhu.abs() > 2)].tolist())

Output yang Dihasilkan:

text
Kelembapan 30 hari: mean = 62.1 std = 5.25
z-score hari 21: 5.13
Hari dengan |z| > 3 : ['21 Jan'] nilai [89.0]
Jumlah |z| > 2      : 1

Suhu 14 hari: mean = 24.6 std = 6.97
z-score seluruh baris (NaN ikut menjadi NaN):
2026-01-01   -0.30
2026-01-02   -0.22
2026-01-03     NaN
2026-01-04   -0.09
2026-01-05    0.03
2026-01-06     NaN
2026-01-07     NaN
2026-01-08    0.13
2026-01-09    2.35
2026-01-10    0.09
2026-01-11   -1.81
2026-01-12   -0.11
2026-01-13   -0.07
2026-01-14     NaN
Freq: D, Name: suhu, dtype: float64
Jumlah |z| > 3 : 0
Jumlah |z| > 2 : 1
Nilai |z| > 2  : [41.0]

Mari kita telaah bagian-bagiannya:

  1. Baris 17-19 (mean, std, dan z): pandas menghitung std() dengan pembagi ddof=1 (standard deviation sampel) secara bawaan; hasilnya Series z-score sepanjang data asli. Perbedaan ini penting karena numpy.std() secara bawaan memakai ddof=0 dan akan memberi angka sedikit berbeda.
  2. Baris 21: z-score 21 Januari bernilai 5.13 — jauh melampaui ambang 3.
  3. Baris 22-24 (mask |z| > 3): z_kelep.abs() > 3 menghasilkan True tepat satu baris; kelembapan yang terflag hanya 89.0 pada 21 Januari, persis kesalahan sensor yang kita tanam.
  4. Baris 25: dengan ambang 2, jumlah baris yang terflag tetap satu; pada data ini kedua ambang sepakat.

Satu outlier tunggal pada 30 titik adalah kondisi ideal bagi z-score: mean 62.1 dan std 5.25 masih mewakili mayoritas data, sehingga nilai menyimpang terdeteksi jelas.

Jebakan Masking pada Data Kecil #

Bagian kedua skrip mengulang perhitungan yang sama pada suhu 14 hari yang sudah kita kenal — data kecil dengan dua outlier berlawanan arah dan empat lubang NaN. Hasilnya berbeda total: mean 24.6 tertarik naik oleh 41.0, std membengkak menjadi 6.97, dan akibatnya z-score kedua outlier ikut mengecil. Nilai 41.0 hanya mencapai z 2.35 dan 12.0 hanya -1.81, sehingga |z| > 3 tidak menandai satu baris pun, sementara |z| > 2 hanya menangkap 41.0. Fenomena ini disebut masking: outlier yang ada justru menyembunyikan dirinya dan saudaranya dengan menggelembungkan std.

Perhatikan juga kolom z-score pada output: setiap NaN pada suhu menjadi NaN pada z-score, dan perbandingan dengan NaN menghasilkan False sehingga baris kosong tidak pernah masuk mask. Perilaku ini sama dengan yang kita amati pada metode IQR, dan biasanya memang diharapkan.

IQR atau Z-Score #

Tabel berikut merangkum pertimbangan memilih di antara keduanya:

AspekIQRZ-Score
Basis perhitunganKuartil (statistik posisi)Mean dan standard deviation
Tahan terhadap outlierYaTidak — mean dan std tercemar outlier
Syarat dataCukup banyak agar kuartil stabilJumlah banyak, distribusi mendekati normal
Ambang lazim1.5 × IQR`

Rincian parameter std() termasuk ddof tersedia pada pandas.Series.std , dan ringkasan perilaku NaN tetap bisa dirujuk ke panduan Working with missing data .

Dua chapter terakhir memberi kita daftar kandidat outlier, tetapi daftar saja tidak membersihkan apa pun. Chapter berikutnya melengkapi persenjataan ini dengan clip(): membatasi nilai ekstrem ke batas tertentu alih-alih membuang barisnya.