Data Tidak Seimbang: Menangani Kasus Fraud dan Churn yang Langka
Blog/Tips & Trik/Data Tidak Seimbang: Menangani Kasus Fraud dan Churn yang Langka

Data Tidak Seimbang: Menangani Kasus Fraud dan Churn yang Langka

BimaBima
·8 Oktober 2025·12 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

Data tidak seimbang terjadi waktu satu kelas target jauh lebih sedikit dari yang lain, misalnya fraud yang cuma 0,4% dari total transaksi. Akurasi jadi metrik yang menyesatkan di kasus ini, ganti pakai precision, recall, F1, atau PR-AUC. Tiga cara utama nanganinnya: setel class_weight biar model ngasih bobot lebih ke kelas langka, oversampling pakai SMOTE, atau undersampling kelas mayoritas. Yang paling murah dan sering cukup: class_weight='balanced' plus geser ambang keputusan sesuai biaya bisnisnya.

Kalau cuma 0,4% transaksi yang fraud, model yang bilang "semuanya aman" langsung dapat akurasi 99,6%. Modelnya nggak berguna sama sekali, tapi angkanya kelihatan bagus.

Ini masalah inti data tidak seimbang. Kelas yang kamu peduliin justru yang paling jarang muncul.

Kasusnya ada di mana-mana. Fraud kartu kredit, churn pelanggan, klaim asuransi palsu, deteksi penyakit langka, dan gagal bayar kredit. Semua punya pola yang sama: kejadian penting itu jarang.

Aku pakai dataset transaksi digital dengan 180.000 baris di sini. Dari jumlah itu, 742 ditandai sebagai fraud. Rasionya 1 banding 242.

Apa itu data tidak seimbang?

Data tidak seimbang adalah kondisi di mana jumlah baris antar kelas target beda jauh. Kelas yang banyak disebut mayoritas, yang sedikit disebut minoritas. Ketimpangan mulai jadi masalah nyata kalau rasionya lewat 1 banding 10, dan jadi masalah serius di atas 1 banding 100.

Kenapa model kesulitan? Sebagian besar algoritma dilatih buat meminimalkan kesalahan total. Kalau kelas minoritas cuma 0,4%, model bisa nurunin kesalahan total hampir maksimal cuma dengan selalu nebak kelas mayoritas.

Model itu nggak salah secara matematika. Dia cuma dikasih tujuan yang nggak sesuai kebutuhan bisnisnya.

Kenapa akurasi jadi menyesatkan?

Berikut hasil model regresi logistik tanpa penanganan apa pun di dataset fraud tadi:

MetrikNilaiArtinya
Akurasi0,996Terlihat sempurna
Precision (kelas fraud)0,71Dari yang ditandai fraud, 71% benar
Recall (kelas fraud)0,08Cuma 8% fraud yang ketangkep
F1-score (kelas fraud)0,14Hasil gabungan yang jelek

Recall 0,08 artinya dari 149 fraud di data test, model cuma nangkep 12. Sisanya 137 lolos.

Akurasi 99,6% nutupin fakta itu sepenuhnya.

Metrik yang harus kamu pakai

Recall ngukur berapa persen kasus positif yang berhasil ketangkep. Ini yang paling penting di deteksi fraud dan penyakit, di mana kelewatan itu mahal.

Precision ngukur dari semua yang ditandai positif, berapa persen yang benar. Penting kalau tiap peringatan palsu punya biaya, misalnya tim yang harus manual review.

F1-score gabungan keduanya lewat rata-rata harmonik. Pilihan default kalau kamu butuh satu angka.

PR-AUC ngukur performa di seluruh ambang keputusan. Buat data yang sangat timpang, ini lebih informatif dari ROC-AUC. ROC-AUC bisa kelihatan tinggi walaupun modelnya jelek buat kelas minoritas, soalnya dia dipengaruhi jumlah negatif yang banyak.

from sklearn.metrics import classification_report, average_precision_score

print(classification_report(y_test, y_pred, digits=3))
print("PR-AUC:", average_precision_score(y_test, y_prob).round(3))

Biasakan cetak classification_report lengkap, bukan cuma skor akurasi.

Cara 1: setel class_weight

Cara paling murah dan paling sering aku sarankan buat dicoba pertama. Kamu nggak ngubah datanya sama sekali, cuma ngasih tau model bahwa salah nebak kelas minoritas itu lebih mahal.

from sklearn.linear_model import LogisticRegression

model = LogisticRegression(
    class_weight="balanced",
    max_iter=1000,
    random_state=42
)
model.fit(X_train, y_train)

Parameter class_weight="balanced" otomatis ngasih bobot berbanding terbalik dengan frekuensi kelas. Kelas yang jumlahnya 242 kali lebih sedikit dapat bobot 242 kali lebih besar.

Kamu juga bisa setel manual kalau mau kontrol lebih:

model = LogisticRegression(class_weight={0: 1, 1: 50}, max_iter=1000)

Bobot yang lebih kecil dari rasio sebenernya kadang ngasih keseimbangan precision dan recall yang lebih baik. Coba beberapa nilai, bandingin hasilnya.

Parameter ini tersedia di hampir semua model klasifikasi scikit-learn: LogisticRegression, RandomForestClassifier, SVC, dan DecisionTreeClassifier. Buat XGBoost namanya scale_pos_weight.

Cara 2: oversampling dengan SMOTE

SMOTE bikin baris sintetis buat kelas minoritas. Caranya: ambil satu titik minoritas, cari tetangga terdekatnya yang juga minoritas, lalu bikin titik baru di antara keduanya.

Ini beda dari sekadar nyalin baris yang ada. Titik baru posisinya di antara dua titik asli, jadi variasinya lebih kaya.

from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.ensemble import RandomForestClassifier

pipe = ImbPipeline([
    ("smote", SMOTE(sampling_strategy=0.1, k_neighbors=5, random_state=42)),
    ("model", RandomForestClassifier(n_estimators=200, random_state=42))
])

pipe.fit(X_train, y_train)

SMOTE ada di paket imbalanced-learn, bukan scikit-learn bawaan. Instal dengan pip install imbalanced-learn. Dokumentasi lengkapnya di situs resmi imbalanced-learn.

Perhatiin sampling_strategy=0.1. Itu artinya kelas minoritas dibikin jadi 10% dari jumlah mayoritas, bukan disamain 50:50. Nyamain penuh sering bikin model kebanyakan nandain positif.

Yang paling penting soal SMOTE: pakai ImbPipeline dari imbalanced-learn, jangan pipeline biasa. Pipeline khusus ini mastiin SMOTE cuma diterapin di data training tiap fold, nggak di data validasi.

Kalau kamu jalanin SMOTE ke seluruh data sebelum split, baris sintetis dari data training bisa mirip banget sama baris di data test. Skornya jadi tinggi palsu.

Kapan SMOTE nggak cocok

SMOTE bikin titik baru dengan menginterpolasi antar tetangga. Kalau kelas minoritas kamu sebarannya nyebar dan nggak ngelompok, titik sintetisnya bisa jatuh di wilayah yang sebenernya kelas mayoritas.

Ini sering kejadian di data fraud, di mana pola penipuannya bermacam-macam dan nggak berkumpul di satu area.

SMOTE juga nggak bekerja mulus di kolom kategorik. Interpolasi antara "Jakarta" dan "Surabaya" nggak ada artinya. Buat data campuran, ada varian SMOTENC yang nangani kolom kategorik secara terpisah.

Cara 3: undersampling kelas mayoritas

Kebalikan dari oversampling. Kamu buang sebagian baris kelas mayoritas biar rasionya lebih seimbang.

from imblearn.under_sampling import RandomUnderSampler

pipe = ImbPipeline([
    ("under", RandomUnderSampler(sampling_strategy=0.2, random_state=42)),
    ("model", RandomForestClassifier(n_estimators=200, random_state=42))
])

Kelebihannya: pelatihan jadi jauh lebih cepat soalnya datanya lebih sedikit.

Kekurangannya: kamu buang informasi. Kalau kelas mayoritas kamu cuma 5.000 baris, buang setengahnya itu kerugian nyata.

Undersampling masuk akal kalau data mayoritas kamu jumlahnya ratusan ribu ke atas. Di dataset 180.000 baris tadi, buang sebagian besar transaksi normal masih nyisain puluhan ribu contoh yang cukup.

Cara 4: geser ambang keputusan

Ini yang paling sering dilupain, padahal sering paling efektif.

Model klasifikasi sebenernya ngasih probabilitas, bukan label. Fungsi predict() otomatis pakai ambang 0,5. Nggak ada alasan ambang itu harus 0,5 buat kasus kamu.

import numpy as np
from sklearn.metrics import precision_recall_curve

y_prob = model.predict_proba(X_test)[:, 1]
prec, rec, ambang = precision_recall_curve(y_test, y_prob)

f1 = 2 * (prec * rec) / (prec + rec + 1e-9)
ambang_terbaik = ambang[np.argmax(f1[:-1])]

y_pred = (y_prob >= ambang_terbaik).astype(int)
print("Ambang terbaik:", round(ambang_terbaik, 3))

Di dataset fraud tadi, ambang optimalnya keluar di 0,083. Bukan 0,5.

Yang perlu kamu pikirin: ambang optimal itu tergantung biaya bisnis, bukan cuma F1. Kalau satu fraud yang lolos rugi Rp 2,4 juta dan satu peringatan palsu makan 10 menit waktu tim, hitung ambang yang minimalin total biaya, bukan yang maksimalin F1.

Contoh kasus: deteksi fraud transaksi digital

Ini perbandingan lengkap keempat cara di dataset 180.000 transaksi dengan 742 fraud. Modelnya random forest, evaluasi pakai 5-fold stratified cross-validation.

CaraPrecisionRecallF1PR-AUC
Tanpa penanganan0,710,080,140,32
class_weight balanced0,440,610,510,49
SMOTE 0,10,520,580,550,52
Undersampling 0,20,290,740,420,45
class_weight + geser ambang0,580,630,600,49

Baris terakhir menang di F1. Dan itu cara yang paling murah: satu parameter plus satu langkah kalibrasi ambang.

Perhatiin juga baris undersampling. Recall-nya paling tinggi (0,74) tapi precision-nya jeblok (0,29). Artinya dari 100 transaksi yang ditandai fraud, cuma 29 yang beneran fraud.

Mana yang kamu pilih tergantung biaya. Kalau tim review kamu cuma 2 orang, precision 0,29 bikin mereka tenggelam. Kalau kerugian per fraud sangat besar dan tim review cukup, recall tinggi lebih masuk akal.

Hitungan biaya di kasus ini

Anggap kerugian rata-rata per fraud yang lolos Rp 2,4 juta, dan biaya review manual satu peringatan Rp 25.000. Data test berisi 36.000 transaksi dengan 149 fraud.

CaraFraud lolosPeringatan palsuTotal biaya
Tanpa penanganan1375Rp 328,9 juta
class_weight + geser ambang5568Rp 133,7 juta
Undersampling39270Rp 100,4 juta

Begitu dihitung dalam rupiah, undersampling malah paling murah. Padahal F1-nya paling rendah di antara tiga itu.

Ini alasan kenapa aku selalu minta angka biaya sebelum milih model. Metrik statistik nggak tau berapa harga satu kesalahan.

Kesalahan umum

Pakai akurasi sebagai metrik utama. Di data 1 banding 242, akurasi hampir nggak bergerak apa pun yang kamu lakukan. Pakai F1 atau PR-AUC.

Resampling sebelum split data. Kesalahan paling fatal. Baris sintetis dari SMOTE bisa nyebar ke data test, bikin skor kelihatan bagus tanpa dasar. Selalu resampling di dalam pipeline.

Resampling data test. Data test harus mencerminkan distribusi asli. Kalau kamu seimbangin data test, kamu ngukur performa di dunia yang nggak ada.

Pakai cross-validation biasa. Dengan kelas minoritas 0,4%, ada fold yang bisa nggak kebagian contoh positif sama sekali. Pakai StratifiedKFold yang jaga proporsi kelas di tiap fold.

Langsung lompat ke SMOTE. Coba class_weight dulu. Sering cukup, dan nggak nambah kerumitan.

Nggak periksa kualitas label. Di data fraud, kelas minoritas sering nggak lengkap. Fraud yang nggak pernah ketahuan tercatat sebagai transaksi normal. Kalau labelnya sendiri meragukan, semua teknik di atas nggak nolong. Baca soal data quality sebelum masuk ke pemodelan.

Lupa cek ulang setelah model jalan. Pola fraud berubah. Model yang bagus enam bulan lalu bisa jadi kelewatan sekarang. Pantau recall bulanan.

Urutan yang aku sarankan

  1. Cek rasio kelasnya dulu dengan y.value_counts(normalize=True)
  2. Ganti metrik dari akurasi ke F1 dan PR-AUC
  3. Pakai StratifiedKFold buat cross-validation
  4. Coba class_weight="balanced"
  5. Geser ambang keputusan pakai kurva precision-recall
  6. Kalau masih kurang, baru coba SMOTE atau undersampling
  7. Hitung total biaya dalam rupiah, pilih berdasarkan itu

Langkah 4 dan 5 sering udah nyelesaikan sebagian besar masalah. Nggak semua kasus butuh teknik resampling.

FAQ

Rasio berapa yang dianggap tidak seimbang?

Di bawah 1 banding 4 biasanya masih aman tanpa penanganan khusus. Antara 1 banding 10 dan 1 banding 100, model mulai berat sebelah dan perlu class_weight atau resampling. Di atas 1 banding 100, kamu wajib ganti metrik dan pikirin ambang keputusan dengan serius.

SMOTE atau class_weight, mana yang lebih baik?

Mulai dari class_weight. Dia nggak ngubah data, nggak nambah waktu pelatihan, dan tersedia di hampir semua model scikit-learn. SMOTE baru masuk akal kalau class_weight udah dicoba dan hasilnya masih kurang, terutama waktu kelas minoritas kamu ngelompok rapi dan fiturnya numerik semua.

Kenapa ROC-AUC nggak cocok buat data sangat timpang?

ROC-AUC pakai false positive rate yang penyebutnya jumlah negatif. Kalau negatifnya ratusan ribu, nambah beberapa ratus false positive hampir nggak menggeser angkanya. Akibatnya ROC-AUC bisa kelihatan tinggi padahal precision-nya jelek. PR-AUC nggak punya masalah ini soalnya dia pakai precision langsung.

Perlu resampling kalau pakai XGBoost?

Nggak selalu. XGBoost punya parameter scale_pos_weight yang fungsinya mirip class_weight. Setel nilainya kira-kira sama dengan rasio jumlah negatif dibagi positif, lalu sesuaikan dari situ. Banyak kasus fraud dan churn selesai cuma dengan parameter ini plus kalibrasi ambang.

Gimana kalau kelas minoritas cuma puluhan baris?

Di bawah sekitar 50 contoh positif, model klasifikasi biasa jadi nggak bisa diandalkan dan SMOTE juga nggak punya cukup tetangga buat interpolasi. Pertimbangkan pendekatan lain: deteksi anomali yang cuma belajar dari kelas normal, atau aturan berbasis pengetahuan domain. Ngumpulin lebih banyak label positif biasanya investasi yang lebih berguna daripada mengakali model.

Penutup

Tiga hal yang paling menentukan. Buang akurasi sebagai metrik utama, ganti ke F1 dan PR-AUC. Coba class_weight sebelum lompat ke SMOTE. Dan hitung biaya kesalahan dalam rupiah, soalnya itu yang menentukan ambang keputusan yang benar.

Coba jalanin classification_report lengkap di model klasifikasi yang lagi kamu kerjain. Kalau recall kelas minoritasnya di bawah 0,3, kamu punya masalah yang tersembunyi di balik angka akurasi.

Lanjut baca soal scaling dan normalisasi fitur dan encoding variabel kategorik buat ngerapiin tahap persiapan datanya. Buat konsep dasar metrik model, cek juga metric dan regression.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel terkait

Pandas groupby: Agregasi Data ala Pivot di Python (2026)
Tips & Trik
20 Juli 2026•9 menit baca

Pandas groupby: Agregasi Data ala Pivot di Python (2026)

groupby ngelompokin baris berdasarkan kolom, lalu ngitung ringkasan per grup kayak total atau rata-rata. Ini pivot-nya pandas.

BimaBima
Pandas to_excel: Ekspor DataFrame ke Excel (2026)
Tips & Trik
18 Juli 2026•8 menit baca

Pandas to_excel: Ekspor DataFrame ke Excel (2026)

to_excel nyimpen DataFrame pandas jadi file Excel .xlsx. Ini cara pakainya, dari satu sheet sampai banyak sheet, plus setelan yang bikin hasilnya rapi.

BimaBima
Pandas read_excel: Baca File Excel di Python
Tips & Trik
16 Juli 2026•8 menit baca

Pandas read_excel: Baca File Excel di Python

pandas read_excel baca file Excel jadi DataFrame, bisa pilih sheet tertentu dan lompatin baris judul yang berantakan. Ini cara pakai plus parameter penting dengan contoh data toko.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

© 2026 Ngulik Data. Semua hak dilindungi.

TAUTAN
BantuanHargaDatasetBlogAfiliasi
LEGAL
Syarat & KetentuanKebijakan Privasi
Ngulik Data
DatasetLeaderboardBlogStore