Bagian 1 menutup dengan dua temuan yang ditabung sengaja: kemiringan nominal +14,28 dan jam tersepi di 02.00–05.59. Bagian ini mengubah kedua kebiasaan tersebut menjadi aturan keras — pendeteksi transparan yang bisa dijelaskan ke tim operasional dalam satu kalimat, tanpa satu baris model pun. Tiga aturan dipasang: z-score nominal terhadap rata-rata data, kuantil ekor 99%, dan aturan jam dini hari. Ketiganya lalu digabung menjadi skor sederhana 0–3, dan kita menghitung berapa transaksi yang tertandai. Skrip tetap tidak menyentuh kolom is_fraud — deteksi bekerja unsupervised; label audit itu baru dibuka di Bagian 4 untuk menilai seberapa bagus aturan-aturan ini sebenarnya.

Tiga Aturan dan Alasan Pemilihannya #

Mengapa tepat tiga aturan ini, bukan yang lain? z-score menjawab pertanyaan paling klasik:berapa jauh satu transaksi menyimpang dari rata-rata populasi, dalam satuan simpangan baku. Kuantil ekor menjawab pertanyaan yang lebih tahan terhadap kemiringan:berapa persen transaksi termahal yang kita mau periksa, apa pun bentuk distribusinya. Aturan jam menangkap keanehan waktu — transaksi 02.00–05.59 nyaris tidak muncul pada data Bagian 1, sehingga kehadirannya sendiri sudah bernilai sinyal. Ketiganya sengaja satu dimensi: itulah kekuatan sekaligus batasnya.

AturanPertanyaan yang dijawabAmbangKarakter
z-score nominal ≥ 3Seberapa jauh dari rata-rata?1495,2 ribuTahan derau, rapuh pada kemiringan
nominal ≥ kuantil 99%Berapa persen ekor yang disorot?730,8 ribuBebas asumsi bentuk distribusi
jam 02..05Kapan transaksi terjadi?02:00–05:59Sinyal waktu, buta nominal
python
# Deteksi statistik: aturan transparan pada nominal dan jam sebagai
# baseline sebelum model. Tiga aturan dipasang:
#   1. z-score nominal        : |z| >= 3 terhadap mean/std seluruh data.
#   2. kuantil ekor nominal   : nominal >= kuantil 99% (ekor 1% teratas).
#   3. aturan jam dini hari   : jam_transaksi 02..05 (dari eksplorasi
#                               Bagian 1, jam tersepi).
# Skor sederhana = jumlah aturan yang terpicu (0..3). Skrip TIDAK
# membaca is_fraud - deteksi unsupervised. Jalankan dari root proyek:
# python3 src/03_statistik.py

import numpy as np
import pandas as pd

df = pd.read_csv("data/transaksi.csv")
nominal = df["nominal_ribu"].to_numpy()
jam = df["jam_transaksi"].to_numpy()

# ---------- Aturan 1: z-score nominal ----------
rata = float(nominal.mean())
std = float(nominal.std())
z = (nominal - rata) / std
ambang_z = rata + 3.0 * std
aturan_z = z >= 3.0

# ---------- Aturan 2: kuantil ekor nominal ----------
q99 = float(np.quantile(nominal, 0.99))
aturan_kuantil = nominal >= q99

# ---------- Aturan 3: jam dini hari ----------
aturan_jam = (jam >= 2) & (jam <= 5)

# ---------- Tabel aturan ----------
print("== Tiga aturan statistik ==")
print(f"{'aturan':34s} {'ambang':>12s} {'tertandai':>10s}")
print(f"{'z-score nominal >= 3':34s} {ambang_z:12.1f} {int(aturan_z.sum()):10d}")
print(f"{'nominal >= kuantil 99%':34s} {q99:12.1f} {int(aturan_kuantil.sum()):10d}")
print(f"{'jam transaksi 02..05':34s} {'02:00-05:59':>12s} {int(aturan_jam.sum()):10d}")
print(f"\nnominal      : mean {rata:.1f} ribu, std {std:.1f} ribu "
      f"(std terdistorsi ekor kanan)")
print(f"z-score maks : {z.max():.1f}  (transaksi terbesar "
      f"{nominal.max():.1f} ribu)")

# ---------- Skor gabungan ----------
skor = aturan_z.astype(int) + aturan_kuantil.astype(int) + aturan_jam.astype(int)
df_skor = df[
    ["id_transaksi", "nominal_ribu", "jam_transaksi", "durasi_detik",
     "jumlah_item"]
].copy()
df_skor["skor"] = skor

print("\n== Distribusi skor gabungan (0..3) ==")
for s in range(4):
    print(f"skor {s}: {int((skor == s).sum()):5d} transaksi")
print(f"skor >= 1 (minimal satu aturan) : {int((skor >= 1).sum())} transaksi "
      f"({(skor >= 1).mean():.2%})")
print(f"skor >= 2 (dua aturan atau lebih): {int((skor >= 2).sum())} transaksi "
      f"({(skor >= 2).mean():.2%})")

# ---------- Contoh transaksi tertandai ----------
print("\n== Delapan transaksi dengan skor tertinggi ==")
atas = df_skor[df_skor["skor"] >= 2].sort_values(
    ["skor", "nominal_ribu"], ascending=[False, False]
).head(8)
print(atas.to_string(index=False))

print("\n== Contoh transaksi jam dini hari ber skor 1 (lima pertama) ==")
bawah = df_skor[(df_skor["skor"] == 1) & aturan_jam].head(5)
print(bawah.to_string(index=False))

Output yang Dihasilkan:

text
== Tiga aturan statistik ==
aturan                                   ambang  tertandai
z-score nominal >= 3                     1495.2         20
nominal >= kuantil 99%                    730.8         50
jam transaksi 02..05                02:00-05:59         34

nominal      : mean 278.9 ribu, std 405.4 ribu (std terdistorsi ekor kanan)
z-score maks : 19.7  (transaksi terbesar 8268.3 ribu)

== Distribusi skor gabungan (0..3) ==
skor 0:  4916 transaksi
skor 1:    64 transaksi
skor 2:    20 transaksi
skor 3:     0 transaksi
skor >= 1 (minimal satu aturan) : 84 transaksi (1.68%)
skor >= 2 (dua aturan atau lebih): 20 transaksi (0.40%)

== Delapan transaksi dengan skor tertinggi ==
 id_transaksi  nominal_ribu  jam_transaksi  durasi_detik  jumlah_item  skor
          336        8268.3             18            89            1     2
          427        8260.1             14           112            1     2
           87        7938.6             21            96            2     2
         1534        7491.7             13           118            1     2
          370        7428.9             14            71            1     2
          519        7284.9             14            93            1     2
         1510        7248.3             15            95            1     2
          158        6756.5             19            89            1     2

== Contoh transaksi jam dini hari ber skor 1 (lima pertama) ==
 id_transaksi  nominal_ribu  jam_transaksi  durasi_detik  jumlah_item  skor
           53         254.5              4           219            3     1
          432         269.5              2           179            3     1
          606         255.0              4           135            2     1
          777         231.2              5           134            2     1
         1115         361.3              4           166            4     1

Mari kita bedah aturan demi aturan:

  1. Baris 18–23 (z-score): rata-rata 278,9 ribu dan simpangan 405,4 ribu menghasilkan ambang 1495,2 ribu — hanya 20 transaksi tertandai. Perhatikan std yang jauh lebih besar dari mean: ekor kanan nominal mendistorsi simpangan baku sehingga ambang z terangkat tinggi. z-score maksimum 19,7 (transaksi 8268,3 ribu) menegaskan betapa ekstremnya ekor tersebut.
  2. Baris 25–27 (kuantil): tanpa asumsi bentuk distribusi, kuantil 99% memotong tepat 1% data teratas pada 730,8 ribu — 50 transaksi. Aturan ini kebal terhadap kemiringan yang membuat z-score kehilangan sensitivitas.
  3. Baris 29–30 (jam): 34 transaksi jatuh pada jendela 02.00–05.59 — jam yang pada Bagian 1 nyaris kosong penghuni.
  4. Baris 43–57 (skor gabungan): skor 0..3 dibentuk dari penjumlahan tiga penanda boolean. Hasilnya bertingkat rapi: 84 transaksi (1,68%) memicu minimal satu aturan, dan hanya 20 transaksi (0,40%) yang memicu dua aturan sekaligus — tidak ada yang memicu tiga.
  5. Baris 60–68 (jendela inspeksi): kedelapan transaksi ber skor 2 semuanya ber nominal 6756–8268 ribu — kombinasi nominal ekstrem yang memicu z-score dan kuantil bersamaan. Sebaliknya, transaksi jam dini hari ber skor 1 justru bernominal kecil (231–361 ribu): anomali waktu yang sama sekali tak terlihat oleh aturan nominal.

Membaca Hasil: Harga Sebuah Transparansi #

Distribusi skor memberi tiga informasi berharga. Pertama, ambang alami bekerja: 20 transaksi skor-2 adalah kandidat anomali paling meyakinkan — nominal raksasa yang menembus dua aturan sekaligus. Kedua, zona abu-abu terukur: 64 transaksi skor-1 harus dipilah manual antara jam dini hari dan nominal tinggi. Ketiga, yang tidak tertandai tetap pertanyaan terbuka: 4916 transaksi skor-0 dinyatakan normal oleh ketiga aturan — tetapi apakah benar normal? Aturan satu dimensi buta terhadap kombinasi: transaksi bernominal sedang pada jam normal namun berdurasi 40 detik untuk 8 item (checkout kilat) lolos dari semua aturan kita. Celah inilah yang menuntut model multi-dimensi.

Aturan transparan telah memberi peta awal: 84 transaksi layak ditinjau, 20 di antaranya nyaris pasti anomali. Namun seluruh aturan tersebut melihat transaksi dari satu dimensi saja — nominal, atau jam, tidak pernah keduanya bersama rasio durasi per item. Pertanyaan berikutnya: apa yang ditemukan ketika model melihat keenam dimensi sekaligus? Pertanyaan itu dijawab oleh Bagian 3: Model Anomali.

Referensi Resmi #