Confusion matrix memberi kita empat angka; sekarang kita timbang empat angka itu menjadi keputusan. Dua pertanyaan paling penting dalam klasifikasi dunia nyata jarang dijawab akurasi: ketika model membunyikan alarm, seberapa bisa dipercaya alarm itu? Dan dari seluruh kasus yang benar-benar perlu perhatian, berapa yang berhasil ditemukan? Precision menjawab pertanyaan pertama, recall menjawab yang kedua, dan F1-score mencoba berpegang pada keduanya sekaligus. Pada chapter ini kita menghitung ketiganya dari empat sel confusion matrix, membaca laporan otomatis classification_report, lalu menggeser ambang keputusan untuk melihat pertukaran antara keduanya secara langsung.

Precision dan Recall dari Empat Sel #

Kedua metrik ini sama-sama bertumpu pada sel TP, tetapi pembaginya berbeda. Precision membagi TP dengan jumlah prediksi positif (TP + FP — kolom positif confusion matrix); recall membagi TP dengan jumlah kasus positif yang sebenarnya (TP + FN — baris positif confusion matrix). Kita hitung keduanya pada kasus deteksi fraud sebuah bank: dari 400 transaksi yang diperiksa, 40 di antaranya fraud, dan model menandai 90 transaksi sebagai mencurigakan:

python
tp, fp, fn, tn = 36, 54, 4, 306

precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * precision * recall / (precision + recall)
rata_aritmetik = (precision + recall) / 2

print("Precision = TP/(TP+FP) = %d/%d = %.3f" % (tp, tp + fp, precision))
print("Recall    = TP/(TP+FN) = %d/%d = %.3f" % (tp, tp + fn, recall))
print("F1        = 2*P*R/(P+R) = %.3f" % f1)
print("Rata-rata aritmetik P dan R = %.3f" % rata_aritmetik)

Output yang Dihasilkan:

text
Precision = TP/(TP+FP) = 36/90 = 0.400
Recall    = TP/(TP+FN) = 36/40 = 0.900
F1        = 2*P*R/(P+R) = 0.554
Rata-rata aritmetik P dan R = 0.650

Mari kita telaah angka-angka tersebut:

  1. Recall 0.900: dari 40 transaksi fraud, 36 tertangkap dan 4 lolos. Tim investigasi tidak akan menganggur — hampir semua kasus terdeteksi.
  2. Precision 0.400: dari 90 alarm yang dibunyikan, hanya 36 yang benar. Dua dari tiga pemberitahuan ke nasabah berakhir dengan rasa tidak nyaman tanpa dasar.
  3. Skor tinggi di satu sisi bukan berita buruk: angka ini justru profil yang disengaja untuk fraud — membiarkan kasus lolos jauh lebih mahal daripada memeriksa transaksi sah secara manual. Menghubungi nasabah sebelum transaksi besar dieksekusi dapat mencegah kerugian besar; permintaan maaf atas pemeriksaan yang keliru hanyalah biaya operasional.
  4. F1 0.554: rata-rata harmonik kedua nilai — pembahasannya menyusul di bagian berikutnya.

Kapan masing-masing menjadi prioritas? Aturan ringkasnya mengikuti biaya kesalahan:

PrioritasPertanyaan kunciContoh kasusKesalahan yang paling dihindari
Precision tinggiApa setiap alarm layak ditindak?Filter spam, rekomendasi kontenFP: email penting tercecer, pengguna pergi
Recall tinggiApa tidak ada kasus yang lolos?Deteksi fraud, skrining penyakit, prediksi churn pelanggan bernilai besarFN: kerugian finansial atau kasus terlambat ditangani
Keduanya seimbangBerapa skor tunggal yang jujur?Perbandingan model awal saat biaya belum jelasF1-score sebagai ringkasan netral

F1-Score: Rata-rata Harmonik yang Tidak Bisa Dibohongi #

Sekilas F1 tampak seperti rata-rata biasa, tetapi perhitungan kita memperlihatkan selisihnya: rata-rata aritmetik precision dan recall adalah 0.650, sedangkan F1 hanya 0.554. Rata-rata harmonik selalu condong ke nilai yang lebih kecil, sehingga satu sisi yang rendah menyeret keseluruhan turun — model dengan precision 1.0 dan recall 0.02 mendapat F1 sekitar 0.04 meski rata-rata aritmetiknya 0.51. Sifat ini yang membuat F1 sulit dibohongi: skornya hanya tinggi jika kedua komponennya sama-sama tinggi. Saat kita belum tahu biaya kesalahan mana yang lebih mahal, atau saat membandingkan banyak model secara cepat, F1 adalah ringkasan yang lebih jujur daripada akurasi maupun rata-rata aritmetik.

Membaca classification_report #

Menghitung tiap metrik satu per satu menjadi cepat melelahkan untuk banyak kelas. Fungsi classification_report merangkum semuanya dalam satu tabel; kita jalankan pada data 60 transaksi (8 fraud) dari chapter sebelumnya dengan prediksi TP=6, FP=4, FN=2, TN=48:

python
from sklearn.metrics import classification_report

# 60 transaksi: 8 fraud (1), 52 sah (0)
aktual = [1] * 8 + [0] * 52
prediksi = [1] * 6 + [0] * 2 + [1] * 4 + [0] * 48

laporan = classification_report(
    aktual, prediksi, target_names=["sah", "fraud"], digits=2
)
print(laporan)

Output yang Dihasilkan:

text
              precision    recall  f1-score   support

         sah       0.96      0.92      0.94        52
       fraud       0.60      0.75      0.67         8

    accuracy                           0.90        60
   macro avg       0.78      0.84      0.80        60
weighted avg       0.91      0.90      0.90        60

Cara membaca laporan ini menjadi keterampilan harian:

  1. Satu baris per kelas: baris fraud memuat precision 0.60 (6 dari 10 alarm benar), recall 0.75 (6 dari 8 fraud tertangkap), dan F1 0.67. Kolom support mencatat jumlah baris aktual per kelas — 52 sah dan 8 fraud — angka yang wajib dicek lebih dulu untuk mengetahui seberapa timpang datanya.
  2. accuracy satu baris penuh: 0.90 di kolom tengah, mengingatkan kita pada angka yang kemarin tampak meyakinkan itu.
  3. macro avg: rata-rata sederhana kedua kelas tanpa memandang jumlah — precision (0.96+0.60)/2 = 0.78. Rata-rata ini memperlakukan kelas minoritas se penting kelas mayoritas, sehingga cocok diamati pada data timpang.
  4. weighted avg: rata-rata tertimbang support — angkanya tertarik ke kelas besar. Selisih macro dan weighted (0.78 vs 0.91 untuk precision) adalah alat ukur cepat seberapa timpang performa antar kelas.

Trade-off Precision dan Recall lewat Ambang Keputusan #

Selama ini kita diam-diam memakai ambang 0.5: prediksi positif jika probabilitas melebihi setengah. Padahal predict_proba memberi kita probabilitas mentah, dan ambang adalah kenop yang bisa kita putar. Kita pakai model LogisticRegression yang memprediksi churn 60 member toko daring dari skor risiko; 45 member menjadi data latih, 15 sisanya diuji pada dua ambang berbeda:

python
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score
from sklearn.model_selection import train_test_split

rng = np.random.default_rng(42)
skor_risiko = rng.uniform(0, 10, 60)
churn = (skor_risiko + rng.normal(0, 2.5, 60) > 5).astype(int)

X = skor_risiko.reshape(-1, 1)
y = churn
X_latih, X_uji, y_latih, y_uji = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

model = LogisticRegression()
model.fit(X_latih, y_latih)
probabilitas = model.predict_proba(X_uji)[:, 1]

for ambang in [0.5, 0.3]:
    keputusan = (probabilitas >= ambang).astype(int)
    print("Ambang %.1f -> precision %.2f, recall %.2f"
          % (ambang,
             precision_score(y_uji, keputusan, zero_division=0),
             recall_score(y_uji, keputusan)))

Output yang Dihasilkan:

text
Ambang 0.5 -> precision 0.83, recall 0.71
Ambang 0.3 -> precision 0.67, recall 0.86

Mari kita telaah mekanisme di balik pergeserannya:

  1. predict_proba(X_uji)[:, 1]: mengembalikan probabilitas kelas positif untuk tiap baris — angka kontinu antara 0 dan 1, bukan label final. Kolom [:, 1] dipilih karena kolom 0 menyimpan probabilitas kelas negatif.
  2. keputusan = (probabilitas >= ambang): perbandingan array mengubah probabilitas menjadi label 0/1 sesuai ambang — persis keputusan yang biasanya disembunyikan predict() di balik ambang 0.5.
  3. Ambang 0.5: precision 0.83 (5 dari 6 alarm benar), recall 0.71 (5 dari 7 member berisiko tertangkap). Dua member berisiko berprobabilitas di bawah 0.5 dan lolos.
  4. Ambang 0.3: recall naik ke 0.86 karena satu member berisiko berprobabilitas 0.47 kini ikut tertangkap. Harganya: dua member bertahan berprobabilitas antara 0.3-0.5 ikut terbawa sebagai alarm palsu, sehingga precision turun ke 0.67. Satu member berisiko berprobabilitas 0.27 tetap lolos di kedua ambang.

Perhatikan polanya: menurunkan ambang selalu bergerak ke arah recall naik dan precision turun; menaikkan ambang bergerak sebaliknya. Tidak ada satu ambang yang menaikkan keduanya sekaligus — model dan datanya yang menentukan kurvanya, ambang hanya memilih titik kerja kita di kurva itu.

Rangkuman #

  • Precision = TP/(TP+FP) mengukur keandalan alarm: dari yang diprediksi positif, berapa yang benar; recall = TP/(TP+FN) mengukur jangkauan: dari seluruh kasus positif, berapa yang tertangkap.
  • Demo fraud 400 transaksi menghasilkan precision 0.400 dan recall 0.900 — profil yang disengaja karena kasus lolos lebih mahal daripada pemeriksaan manual.
  • F1 adalah rata-rata harmonik kedua metrik: nilainya condong ke sisi terlemah (0.554 vs rata-rata aritmetik 0.650), sehingga hanya tinggi bila keduanya tinggi.
  • classification_report merangkum precision, recall, F1, dan support per kelas; bandingkan macro avg dengan weighted avg untuk mengintip ketimpangan performa antar kelas.
  • Ambang keputusan menggeser titik kerja model di kurva precision-recall: ambang turun menaikkan recall dan menurunkan precision — pilihan angkanya mengikuti biaya kesalahan.

Menggeser ambang satu per satu membuat kita penasaran: bagaimana peta lengkap semua kemungkinan ambang sekaligus? Peta itu ada — kurva ROC — dan luas daerah di bawahnya, AUC, menjadi salah satu angka paling sering dirujuk saat membandingkan model. Kita gambar keduanya pada chapter ROC dan AUC .

Referensi #

  1. scikit-learn — Model evaluation: precision, recall, dan F-measures
  2. imbalanced-learn — dokumentasi resmi