Chapter sebelumnya menggeser ambang keputusan satu per satu dan menonton precision-recall bergerak saling bertukar. Kurva ROC (Receiver Operating Characteristic) membawa cara pandang itu ke bentuk penuh: bukan dua ambang, melainkan seluruh ambang sekaligus digambarkan sebagai satu garis, sehingga kualitas model terlihat utuh tanpa harus memilih titik kerja lebih dulu. Dari kurva ini lahir AUC (Area Under the Curve), angka tunggal bebas-ambang yang menjadi salah satu pembanding model paling populer. Pada chapter ini kita hitung TPR dan FPR secara manual, gambar kurvanya dengan roc_curve, tafsirkan AUC, lalu menguji kapan kurva ROC justru harus digantikan kurva precision-recall.

TPR dan FPR pada Beberapa Ambang #

Kurva ROC dibangun dari dua laju yang sudah kita kenal. TPR (true positive rate) adalah nama lain recall — proporsi kasus positif yang tertangkap, TP/(TP+FN). FPR (false positive rate) adalah cerminannya di kelas negatif — proporsi kasus negatif yang salah diberi alarm, FP/(FP+TN). Kita hitung keduanya pada 10 member dengan skor risiko churn dan label aktual yang sudah diketahui:

python
probabilitas = [0.95, 0.90, 0.85, 0.70, 0.55, 0.45, 0.30, 0.25, 0.15, 0.05]
aktual = [1, 1, 0, 1, 0, 1, 0, 0, 0, 0]

total_positif = sum(aktual)
total_negatif = len(aktual) - total_positif
print("Total positif %d, total negatif %d" % (total_positif, total_negatif))

for ambang in [0.8, 0.6, 0.4, 0.2]:
    tp = fp = fn = tn = 0
    for p, a in zip(probabilitas, aktual):
        if p >= ambang and a == 1:
            tp += 1
        elif p >= ambang and a == 0:
            fp += 1
        elif p < ambang and a == 1:
            fn += 1
        else:
            tn += 1
    tpr = tp / (tp + fn)
    fpr = fp / (fp + tn)
    print("Ambang %.1f -> TP %d, FP %d, FN %d, TN %d | TPR %.2f, FPR %.2f"
          % (ambang, tp, fp, fn, tn, tpr, fpr))

Output yang Dihasilkan:

text
Total positif 4, total negatif 6
Ambang 0.8 -> TP 2, FP 1, FN 2, TN 5 | TPR 0.50, FPR 0.17
Ambang 0.6 -> TP 3, FP 1, FN 1, TN 5 | TPR 0.75, FPR 0.17
Ambang 0.4 -> TP 4, FP 2, FN 0, TN 4 | TPR 1.00, FPR 0.33
Ambang 0.2 -> TP 4, FP 4, FN 0, TN 2 | TPR 1.00, FPR 0.67

Mari kita telaah pola yang muncul:

  1. Ambang 0.8: hanya tiga member ber skor tinggi yang diberi alarm — dua positif dan satu negatif. TPR 0.50, FPR 0.17: separuh kasus positif tertangkap dengan satu alarm palsu.
  2. Ambang 0.6: satu positif tambahan (skor 0.70) ikut tertangkap tanpa membawa negatif baru — TPR naik ke 0.75 sementara FPR tetap. Inilah kenaikan gratis yang dicari setiap model: menaikkan tangkapan tanpa menambah alarm palsu.
  3. Ambang 0.4 dan 0.2: TPR mentok di 1.00 (semua positif tertangkap sejak 0.4), tetapi FPR terus merambat naik ke 0.33 lalu 0.67 karena ambang yang makin rendah menyeret member negatif ikut terbawa alarm.
  4. Benang merahnya: setiap ambang menghasilkan satu pasangan (FPR, TPR). Kurva ROC adalah garis yang menghubungkan semua pasangan itu — sumbu tegak TPR, sumbu mendatar FPR.

Kurva ROC dan AUC dengan roc_curve #

Fungsi roc_curve scikit-learn menjalankan seluruh perhitungan tadi untuk semua ambang yang berbeda hasilnya sekaligus, dan auc menjumlahkan luas daerah di bawah kurvanya:

python
import numpy as np
from sklearn.metrics import roc_curve, auc

probabilitas = np.array([0.95, 0.90, 0.85, 0.70, 0.55, 0.45, 0.30, 0.25, 0.15, 0.05])
aktual = np.array([1, 1, 0, 1, 0, 1, 0, 0, 0, 0])

fpr, tpr, ambang = roc_curve(aktual, probabilitas)
print("Ambang :", np.round(ambang, 2))
print("FPR    :", np.round(fpr, 2))
print("TPR    :", np.round(tpr, 2))
print("AUC    : %.3f" % auc(fpr, tpr))

Output yang Dihasilkan:

text
Ambang : [ inf 0.95 0.9  0.85 0.7  0.55 0.45 0.05]
FPR    : [0.   0.   0.   0.17 0.17 0.33 0.33 1.  ]
TPR    : [0.   0.25 0.5  0.5  0.75 0.75 1.   1.  ]
AUC    : 0.875

Mari kita baca keluaran fungsi ini dengan teliti:

  1. Urutan argumen roc_curve(aktual, probabilitas): berbeda dari fungsi-fungsi sebelumnya, argumen kedua bukan label prediksi melainkan skor probabilitas — roc_curve yang menerapkan sendiri seluruh ambangnya.
  2. Array ambang dimulai dengan inf dan melewatkan beberapa skor: titik pertama kurva adalah (0, 0) — ambang tak hingga membuat semua prediksi negatif. Nilai 0.30, 0.25, dan 0.15 tidak muncul karena titik kurva yang dihasilkannya terletak segaris dengan titik sekitarnya; parameter drop_intermediate yang aktif secara baku membuang ambang seperti itu tanpa mengubah bentuk kurva maupun AUC.
  3. AUC 0.875 dan tafsirannya sebagai peluang: AUC sama dengan peluang model memberi skor lebih tinggi pada kasus positif daripada kasus negatif yang dipilih acak. Data kita memuat 4 positif × 6 negatif = 24 pasangan; tiga pasangan tersusun salah — negatif 0.85 mengalahkan positif 0.70 dan 0.45, serta negatif 0.55 mengalahkan positif 0.45 — sehingga 21 dari 24 pasangan terurut benar: 21/24 = 0.875.
  4. Ketergantungan pada urutan, bukan ambang: seluruh perhitungan hanya memakai peringkat skor. Model yang mengalikan semua skornya dengan 10 tetap menghasilkan AUC yang sama.
Nilai AUCMakna
1.0Pemisahan sempurna: seluruh positif berperingkat di atas seluruh negatif
0.875Kasus kita: urutan cukup baik, beberapa tumpang tindih
0.5Setara tebakan acak: kurva menempel diagonal
Di bawah 0.5Lebih buruk dari acak — biasanya label tertukar; membalik prediksi justru memperbaikinya

Menggambar Kurva ROC #

Keluaran angka tadi sekarang kita gambar menjadi kurva. Blok berikut menggambar TPR terhadap FPR untuk model kita, dengan garis putus-putus diagonal sebagai pembanding tebakan acak:

python
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import roc_curve, auc

probabilitas = np.array([0.95, 0.90, 0.85, 0.70, 0.55, 0.45, 0.30, 0.25, 0.15, 0.05])
aktual = np.array([1, 1, 0, 1, 0, 1, 0, 0, 0, 0])

fpr, tpr, _ = roc_curve(aktual, probabilitas)
nilai_auc = auc(fpr, tpr)

fig, ax = plt.subplots(figsize=(5, 4.5))
ax.plot(fpr, tpr, marker="o", color="tab:blue",
        label="Model kita (AUC = %.3f)" % nilai_auc)
ax.plot([0, 1], [0, 1], linestyle="--", color="tab:gray",
        label="Tebakan acak (AUC = 0.5)")
ax.set_xlabel("False Positive Rate (FPR)")
ax.set_ylabel("True Positive Rate (TPR)")
ax.set_title("Kurva ROC pada 10 Skor Churn")
ax.set_xlim(-0.02, 1.02)
ax.set_ylim(-0.02, 1.02)
ax.grid(True)
ax.legend(loc="lower right")

plt.show()

Kurva yang tergambar berbentuk tangga: dimulai dari titik asal (0, 0), naik satu tangga setiap kali satu kasus positif lolos ambang, dan bergerak ke kanan setiap kali satu kasus negatif ikut terbawa. Titik-titik tangganya jarang karena data kita hanya 10 baris — pada data ribuan baris tangganya menyatu menjadi kurva mulus. Model kita melengkung menjauhi diagonal ke arah kiri atas: wilayah itu berarti TPR tinggi diperoleh saat FPR masih rendah, dan makin jauh lengkungannya dari diagonal, makin besar pula luas daerah — AUC — yang dilingkupinya.

ROC versus Kurva Precision-Recall pada Data Timpang #

Kurva ROC punya satu kelemahan yang serius: FPR dibagi banyaknya kasus negatif, dan pada data timpang jumlah negatif yang besar membuat FPR tampak kecil meski alarm palsunya banyak. Kita uji efek ini pada data deteksi fraud yang timpang — 60 transaksi dengan hanya 6 fraud — memakai dua ringkasan kurva sekaligus: AUC-ROC dan average precision (luas versi kurva precision-recall):

python
import numpy as np
from sklearn.metrics import roc_auc_score, average_precision_score

rng = np.random.default_rng(42)

# 60 transaksi: 54 sah, 6 fraud (data timpang 9:1)
skor_sah = rng.uniform(0.01, 0.75, 54)
skor_fraud = np.array([0.93, 0.87, 0.80, 0.72, 0.64, 0.55])

y = np.array([0] * 54 + [1] * 6)
skor = np.concatenate([skor_sah, skor_fraud])

print("AUC-ROC           : %.3f" % roc_auc_score(y, skor))
print("Average precision : %.3f" % average_precision_score(y, skor))

Output yang Dihasilkan:

text
AUC-ROC           : 0.917
Average precision : 0.713

Mari kita bedah mengapa dua angka itu berbeda jauh untuk model yang sama:

  1. AUC-ROC 0.917 terdengar meyakinkan: kurva ROC menghitung peluang fraud diberi skor lebih tinggi daripada transaksi sah. Mayoritas transaksi sah berskor rendah sehingga peringkatnya memang kalah — angka ini otomatis tampak sehat pada data timpang.
  2. Average precision 0.713 lebih tegas: kurva precision-recall memakai precision, yang penyebutnya adalah jumlah alarm — bukan jumlah seluruh negatif. Enam fraud di tengah 54 transaksi sah membuat precision cepat runtuh begitu alarm palsu muncul, dan average precision mencatat kejatuhan itu.
  3. Kesimpulan praktisnya: pada data timpang dengan kelas positif langka, laporkan kurva precision-recall beserta average precision sebagai gambaran utama; kurva ROC tetap berguna untuk membandingkan model, tetapi angka AUC-nya yang tinggi jangan dipersembahkan sebagai bukti sistem siap pakai.

Aturan ringkas memilih kurva: ketika kelas positif langka (fraud, penyakit jarang, pelanggan berhenti di industri dengan retensi tinggi), precision dan recall adalah mata yang jujur; ketika kedua kelas berbagi porsi yang seimbang dan biaya kedua jenis kesalahan sebanding, kurva ROC dan AUC-nya bekerja dengan baik.

Rangkuman #

  • Kurva ROC menggambar TPR (sumbu tegak) terhadap FPR (sumbu mendatar) untuk seluruh ambang keputusan sekaligus; hitungan manual empat ambang memperlihatkan tiap ambang menghasilkan satu titik kurva.
  • roc_curve menerima skor probabilitas — bukan label — dan menyusun titik-titiknya sendiri; beberapa skor tidak muncul sebagai ambang karena tidak mengubah keputusan apa pun.
  • AUC adalah luas di bawah kurva ROC sekaligus peluang skor kasus positif lebih tinggi daripada kasus negatif acak: 1.0 sempurna, 0.5 setara acak, di bawah 0.5 biasanya menandakan label tertukar.
  • AUC bebas ambang — cocok untuk membandingkan model sebelum memilih titik kerja, tetapi tidak menggantikan confusion matrix saat menetapkan ambang operasional.
  • Pada data timpang, FPR yang berpenyebut besar membuat AUC-ROC tampak tinggi (0.917) sementara average precision lebih jujur (0.713); untuk kelas positif langka, utamakan kurva precision-recall.

Sepanjang enam chapter ini kita mengasah satu sisi evaluasi: angka-angka yang mengukur kualitas prediksi. Tetapi angka sebagus apa pun menjadi rapuh bila dihitung dari satu pembagian data saja — bisa jadi keberuntungan semata. Sisi kedua evaluasi adalah protokol pengukurannya yang jujur, dan fondasi protokol itu adalah memutar peran data dalam cross-validation, pokok bahasan chapter Cross-Validation .

Referensi #

  1. scikit-learn — Model evaluation: ROC dan AUC
  2. imbalanced-learn — dokumentasi resmi