7  Kerangka Predictive Analytics

Tujuan pembelajaran. Setelah menyelesaikan bab ini, kita akan mampu: 1. Membedakan pertanyaan prediktif dari pertanyaan deskriptif dan merumuskannya secara operasional. 2. Menjelaskan alur kerja pemodelan prediktif—dari formulasi masalah hingga evaluasi. 3. Membagi data menjadi train, validation, dan test dengan benar, serta memahami mengapa tiga pembagian diperlukan. 4. Menerapkan cross-validation untuk estimasi performa yang lebih stabil. 5. Menjelaskan bias-variance tradeoff dan mengenali tanda-tanda overfitting maupun underfitting. 6. Mengenali dan menghindari data leakage—kesalahan paling berbahaya dalam pemodelan prediktif. 7. Membangun baseline model sederhana yang menjadi pembanding untuk semua model di bab-bab selanjutnya.

7.1 Dari “Mengapa” ke “Siapa”

Di akhir Bagian I, kita berhenti pada sebuah batas yang jelas. Diagnosis kita mengatakan: “Pelanggan bulanan baru berisiko tinggi.” Itu adalah kesimpulan tingkat populasi—benar untuk kelompok, tetapi tidak cukup untuk tindakan.

VP Customer Success tidak bisa menghubungi “segmen pelanggan bulanan baru”. Ia harus menghubungi individu—dan ia tidak punya anggaran untuk menghubungi semuanya. Ia membutuhkan jawaban atas pertanyaan yang berbeda:

“Dari 1.800 pelanggan di segmen itu, mana 200 yang paling mungkin churn bulan ini?”

Perhatikan pergeseran pertanyaannya:

Bagian I Bagian II
“Segmen mana yang berisiko?” “Pelanggan mana yang akan churn?”
Tingkat populasi Tingkat individu
Rata-rata kelompok Probabilitas per pelanggan
Deskriptif/diagnostik Prediktif

Inilah wilayah predictive analytics: menggunakan pola yang telah kita identifikasi pada data historis untuk memperkirakan hasil di masa depan untuk individu tertentu.

Important

Prediksi bukan ramalan. Model prediktif tidak “melihat masa depan”. Ia menemukan pola dalam data historis dan mengasumsikan bahwa pola itu bertahan. Jika dunia berubah secara fundamental—kompetitor baru, resesi, perubahan regulasi—prediksi bisa gagal. Karena itu, model prediktif selalu merupakan kondisional: prediksi valid jika kondisi yang melatihnya masih berlaku.

7.2 Formulasi Masalah: Sebelum Menyentuh Kode

Kesalahan paling umum dalam pemodelan prediktif bukanlah algoritma yang salah, melainkan masalah yang salah dirumuskan. Sebelum membuka notebook, jawab lima pertanyaan berikut secara eksplisit.

7.2.1 1. Apa yang Diprediksi? (Target)

Untuk kasus kita, targetnya jelas: Churn (biner: Yes/No). Tetapi ini adalah pilihan yang perlu dijelaskan—mengapa biner, bukan kontinu?

Jenis target Contoh Algoritma yang sesuai
Biner Churn / tidak churn Klasifikasi: logistic regression, random forest, gradient boosting
Multikelas Jenis kontrak yang akan dipilih Klasifikasi multikelas
Kontinu Nilai tagihan bulanan berikutnya Regresi: linear regression, gradient boosting regressor
Hitungan Jumlah bulan sebelum churn Regresi Poisson, survival analysis
Waktu Kapan pelanggan akan churn Survival analysis (Bab 10)

Target biner memungkinkan kita membangun model klasifikasi—fokus Bab 9. Tetapi penting dicatat: target biner menyederhanakan realitas. Pelanggan tidak “churn” pada satu titik; mereka melalui proses. Bab 10 akan memperkenalkan pendekatan yang lebih kaya (survival analysis) sebagai pelengkap.

7.2.2 2. Untuk Siapa Prediksi Ini? (Audiens)

Prediksi untuk tim retensi berbeda dari prediksi untuk direksi:

Audiens Butuh apa Bentuk keluaran
Tim retensi operasional Daftar pelanggan untuk dihubungi minggu ini Skor probabilitas + ranking
Manajer retensi Segmentasi risiko untuk alokasi anggaran Distribusi skor, segmen risiko
Direksi Dampak finansial dan tren Agregat, forecast, confidence interval

Buku ini fokus pada keluaran skor probabilitas per pelanggan—yang dapat diturunkan menjadi ketiganya.

7.2.3 3. Kapan Prediksi Dibutuhkan? (Waktu)

Prediksi churn untuk bulan depan berbeda dari prediksi untuk tahun depan:

  • Horizon pendek (1 bulan): Model dapat menggunakan fitur yang lebih spesifik dan terbaru (mis. jumlah keluhan terakhir).
  • Horizon panjang (12 bulan): Model harus lebih umum; banyak fitur spesifik menjadi noise.

Model prediksi churn biasanya memiliki horizon 30–90 hari—cukup lama untuk intervensi, cukup pendek agar prediksi tetap relevan.

7.2.4 4. Apa Konsekuensi Kesalahan? (Cost Matrix)

Setiap model membuat dua jenis kesalahan:

Aktual: Churn Aktual: Tidak Churn
Prediksi: Churn True Positive (TP) ✅ False Positive (FP) ❌
Prediksi: Tidak Churn False Negative (FN) ❌ True Negative (TN) ✅
  • False Positive: kita menghubungi pelanggan yang tidak akan churn. Biaya: waktu tim retensi, potensi mengganggu pelanggan.
  • False Negative: kita melewatkan pelanggan yang akan churn. Biaya: kehilangan revenue, kehilangan pelanggan.

Dalam bisnis nyata, FN biasanya jauh lebih mahal dari FP—kehilangan pelanggan seharga LTV jauh lebih besar dari biaya satu panggilan telepon. Ini akan memengaruhi metrik yang kita optimalkan (Bab 9) dan threshold yang kita pilih untuk keputusan.

7.2.5 5. Apa Baseline yang Harus Dikalahkan? (Baseline)

Sebelum membangun model apa pun, kita perlu baseline—prediksi paling sederhana yang menjadi pembanding:

  • Baseline trivial: prediksi semua pelanggan sebagai “tidak churn”. Akurasi ~73,5% (karena 73,5% pelanggan tidak churn), tetapi tidak berguna—kita tidak menangkap satu pun pelanggan yang churn.
  • Baseline naif: prediksi “churn” jika Contract == "Month-to-month". Churn rate prediksi ~43%, akurasi tidak sebaik baseline trivial, tetapi jauh lebih berguna untuk retensi.

Model yang baik harus secara signifikan mengalahkan baseline naif dalam metrik yang relevan—bukan hanya dalam akurasi.

Tip

Aturan penting. Sebelum membangun model apa pun, tuliskan baseline yang akan kita kalahkan. Ini mencegah kita membangun model canggih yang sebenarnya lebih buruk dari aturan sederhana. Banyak proyek gagal karena tidak pernah menetapkan baseline.

7.3 Alur Kerja Pemodelan Prediktif

Proses pemodelan prediktif bukan satu langkah, melainkan siklus iteratif yang dikenal sebagai CRISP-DM (Cross-Industry Standard Process for Data Mining):

Code
flowchart LR
    A[1. Business<br/>Understanding] --> B[2. Data<br/>Understanding]
    B --> C[3. Data<br/>Preparation]
    C --> D[4. Modeling]
    D --> E[5. Evaluation]
    E --> F[6. Deployment]
    E -.->|Tidak memuaskan| C
    E -.->|Data kurang| B
    F -.->|Monitor & iterasi| A

flowchart LR
    A[1. Business<br/>Understanding] --> B[2. Data<br/>Understanding]
    B --> C[3. Data<br/>Preparation]
    C --> D[4. Modeling]
    D --> E[5. Evaluation]
    E --> F[6. Deployment]
    E -.->|Tidak memuaskan| C
    E -.->|Data kurang| B
    F -.->|Monitor & iterasi| A

Kita telah menyelesaikan tahap 1–3 di Bagian I (business understanding melalui SCQA, data understanding & preparation melalui Bab 1–2). Bab 7–9 akan fokus pada tahap 4–5 (modeling dan evaluation); tahap 6 (deployment) akan disinggung di Bab 18.

7.4 Membagi Data: Train, Validation, Test

Ini adalah keputusan paling penting dalam pemodelan—dan salah satu yang paling sering salah dilakukan.

7.4.1 Mengapa Kita Tidak Bisa Mengukur Performa pada Data Latih?

Jika kita melatih model pada seluruh dataset dan mengukur performanya pada dataset yang sama, kita akan mendapat gambaran yang terlalu optimis. Model menghafal data latih—termasuk noise dan outlier—sehingga performanya pada data latih bisa jauh lebih baik daripada performa pada data baru.

Analoginya: seorang siswa yang belajar dengan menghafal seluruh jawaban soal tahun lalu akan tampak jenius ketika diuji dengan soal yang sama, tetapi mungkin gagal ketika diuji dengan soal baru. Kita ingin model yang belajar pola, bukan menghafal data.

7.4.2 Mengapa Tiga Pembagian, Bukan Dua?

Pembagian umum adalah train (60–80%), validation (10–20%), test (10–20%). Masing-masing memiliki peran yang berbeda:

Set Fungsi Kapan dipakai
Train Melatih model Selama proses modeling
Validation Memilih model & hyperparameter Selama proses modeling
Test Estimasi performa final Hanya sekali, di akhir

Kesalahan klasik: menggunakan test set untuk “mencoba beberapa model dan memilih yang terbaik”. Jika kita melakukannya, test set bukan lagi test set—ia menjadi validation set kedua, dan estimasi performa kita menjadi bias optimis.

Warning

Aturan emas test set. Test set hanya boleh disentuh satu kali, di akhir, untuk mengukur performa final. Jika kita melihat hasil test set dan kembali mengubah model, kita telah melakukan test set leakage—estimasi performa kita tidak akan bertahan ketika model digunakan di dunia nyata.

7.4.3 Menerapkan Pembagian Data

Code
import sys
sys.path.insert(0, ".")
import pandas as pd
import numpy as np

from decanal.utils import load_churn_clean

df = load_churn_clean()

# Konvensi buku ini: random_state=42 di mana-mana
RANDOM_STATE = 42
TARGET = "Churn"

# Pisahkan fitur (X) dan target (y)
X = df.drop(columns=[TARGET])
y = df[TARGET]

print(f"Fitur : {X.shape[1]} kolom")
print(f"Target: {y.name} (distribusi: {dict(y.value_counts())})")
Fitur : 22 kolom
Target: Churn (distribusi: {'No': np.int64(5174), 'Yes': np.int64(1869)})
Code
from sklearn.model_selection import train_test_split

# Langkah 1: pisahkan test set (20%)
X_temp, X_test, y_temp, y_test = train_test_split(
    X, y,
    test_size=0.20,
    random_state=RANDOM_STATE,
    stratify=y,     # ← penting! lihat catatan di bawah
)

# Langkah 2: dari sisa 80%, pisahkan validation set (25% dari 80% = 20% dari total)
X_train, X_val, y_train, y_val = train_test_split(
    X_temp, y_temp,
    test_size=0.25,
    random_state=RANDOM_STATE,
    stratify=y_temp,
)

print(f"Train set     : {len(X_train):,} ({len(X_train)/len(X):.0%})")
print(f"Validation set: {len(X_val):,} ({len(X_val)/len(X):.0%})")
print(f"Test set      : {len(X_test):,} ({len(X_test)/len(X):.0%})")
Train set     : 4,225 (60%)
Validation set: 1,409 (20%)
Test set      : 1,409 (20%)
Important

Parameter stratify=y. Tanpa stratifikasi, pembagian acak bisa menghasilkan distribusi kelas yang tidak seimbang antara train dan test—misalnya, train memiliki 25% churn tetapi test memiliki 30%. Dengan stratify=y, proporsi kelas dipertahankan di semua subset. Ini wajib untuk dataset dengan kelas tidak seimbang seperti Telco (26,5% churn).

7.4.4 Verifikasi: Apakah Distribusi Konsisten?

Code
def churn_rate(s: pd.Series) -> str:
    return f"{(s == 'Yes').mean() * 100:.1f}%"

pd.DataFrame({
    "set": ["train", "validation", "test"],
    "n": [len(y_train), len(y_val), len(y_test)],
    "churn_rate": [churn_rate(y_train), churn_rate(y_val), churn_rate(y_test)],
})
set n churn_rate
0 train 4225 26.5%
1 validation 1409 26.5%
2 test 1409 26.5%

Jika churn rate di ketiga set kira-kira sama (~26,5%), pembagian berhasil. Jika tidak, ada masalah dengan stratifikasi.

7.5 Cross-Validation: Estimasi yang Lebih Stabil

Pembagian train/validation tunggal memiliki kelemahan: hasilnya bergantung pada pembagian acak tertentu. Jika kita mengulang dengan random_state berbeda, hasilnya bisa berubah beberapa persen—terutama jika dataset kecil.

K-fold cross-validation mengatasi ini dengan membagi data latih menjadi K bagian, melatih model K kali (masing-masing menggunakan K-1 bagian sebagai training dan 1 bagian sebagai validation), lalu merata-ratakan hasilnya.

Code
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 3))
for fold in range(5):
    for i in range(5):
        color = "#DD8452" if i == fold else "#4C72B0"
        ax.barh(fold, 1, left=i, color=color, edgecolor="white")
    ax.text(5.1, fold, f"iterasi {fold+1}", va="center", fontsize=9)

ax.set_yticks(range(5))
ax.set_yticklabels([f"Fold {i+1}" for i in range(5)])
ax.set_xticks(np.arange(0.5, 5.5, 1))
ax.set_xticklabels([f"Bagian {i+1}" for i in range(5)])
ax.set_xlim(0, 6)
ax.set_title("5-Fold Cross-Validation")
ax.invert_yaxis()
plt.tight_layout()
plt.show()

Ilustrasi 5-fold cross-validation: model dilatih 5 kali, setiap kali dengan subset validasi yang berbeda.

Oranye = validation pada iterasi tersebut. Biru = training. Model dilatih 5 kali, setiap kali subset validasi berbeda. Rata-rata performa dari 5 iterasi lebih stabil daripada satu pembagian tunggal.

Code
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer

# Siapkan preprocessing dasar
numeric_features = X.select_dtypes(include=["int64", "float64"]).columns.tolist()
categorical_features = X.select_dtypes(include=["object"]).columns.tolist()

preprocessor = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]), numeric_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore")),
    ]), categorical_features),
])

# Pipeline: preprocessing + model
baseline_pipeline = Pipeline([
    ("prep", preprocessor),
    ("clf", LogisticRegression(max_iter=1000, random_state=RANDOM_STATE)),
])

# 5-fold CV pada train set
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
scores = cross_val_score(baseline_pipeline, X_train, y_train,
                          cv=cv, scoring="roc_auc")

print(f"ROC-AUC per fold: {[f'{s:.3f}' for s in scores]}")
print(f"Rata-rata       : {scores.mean():.3f} ± {scores.std():.3f}")
ROC-AUC per fold: ['0.837', '0.868', '0.834', '0.858', '0.853']
Rata-rata       : 0.850 ± 0.013
Tip

Mengapa ROC-AUC dan bukan akurasi? Karena kelas kita tidak seimbang (26,5% churn). Model yang selalu memprediksi “tidak churn” mendapat akurasi 73,5% tetapi ROC-AUC 0,5 (setara tebakan acak). ROC-AUC mengukur kemampuan membedakan antara kelas positif dan negatif—jauh lebih informatif untuk keputusan. Detail metrik akan dibahas di Bab 9.

7.6 Bias-Variance Tradeoff: Inti dari Semua Pemodelan

Bias-variance tradeoff adalah konsep paling fundamental dalam machine learning—dan salah satu yang paling sering disalahpahami. Ia menjelaskan mengapa model bisa gagal dengan dua cara yang berbeda.

7.6.1 Dua Jenis Kesalahan

Bayangkan kita membangun model untuk memprediksi churn. Kesalahan model dapat diuraikan menjadi tiga komponen:

\[\text{Total Error} = \text{Bias}^2 + \text{Variance} + \text{Irreducible Error}\]

Komponen Arti Penyebab
Bias Seberapa jauh prediksi rata-rata model dari nilai sebenarnya Model terlalu sederhana—tidak cukup fleksibel menangkap pola
Variance Seberapa banyak prediksi model berubah jika data latih berubah Model terlalu kompleks—menghafal noise dalam data latih
Irreducible Error Noise yang tidak bisa dihilangkan Batas fundamental dari data—tidak bisa dikurangi oleh model apa pun

7.6.2 Underfitting vs Overfitting

Underfitting Overfitting
Bias Tinggi Rendah
Variance Rendah Tinggi
Performa train Buruk Sangat baik
Performa test Buruk Buruk
Penyebab Model terlalu sederhana Model terlalu kompleks
Solusi Model lebih kompleks, fitur lebih banyak Regularisasi, lebih banyak data, model lebih sederhana

Analogi. Seorang siswa yang hanya membaca satu halaman buku akan gagal ujian karena tidak cukup belajar (underfitting). Seorang siswa yang menghafal seluruh buku tetapi tidak memahami konsep akan gagal ujian dengan soal baru karena terlalu menghafal (overfitting). Yang kita inginkan adalah siswa yang memahami pola—cukup belajar untuk menangkap konsep, tetapi tidak terlalu terikat pada detail spesifik.

7.6.3 Demonstrasi: Overfitting pada Decision Tree

Mari kita lihat overfitting secara langsung. Kita akan melatih decision tree dengan kedalaman berbeda dan membandingkan performanya pada train dan validation set.

Code
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score

depths = [1, 2, 3, 5, 7, 10, 15, 20, None]
hasil = []

for depth in depths:
    pipe = Pipeline([
        ("prep", preprocessor),
        ("clf", DecisionTreeClassifier(max_depth=depth, random_state=RANDOM_STATE)),
    ])
    pipe.fit(X_train, y_train)

    train_auc = roc_auc_score(y_train, pipe.predict_proba(X_train)[:, 1])
    val_auc = roc_auc_score(y_val, pipe.predict_proba(X_val)[:, 1])

    hasil.append({
        "max_depth": depth if depth is not None else "tanpa batas",
        "train_AUC": round(train_auc, 3),
        "val_AUC": round(val_auc, 3),
        "selisih": round(train_auc - val_auc, 3),
    })

pd.DataFrame(hasil)
max_depth train_AUC val_AUC selisih
0 1 0.734 0.715 0.019
1 2 0.796 0.780 0.016
2 3 0.829 0.808 0.020
3 5 0.858 0.814 0.044
4 7 0.897 0.790 0.108
5 10 0.962 0.750 0.213
6 15 0.998 0.678 0.319
7 20 1.000 0.657 0.343
8 tanpa batas 1.000 0.654 0.346
Code
plot_data = pd.DataFrame(hasil)
plot_data["depth_num"] = range(1, len(plot_data) + 1)

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(plot_data["depth_num"], plot_data["train_AUC"], marker="o",
        label="Train AUC", color="#DD8452", linewidth=2)
ax.plot(plot_data["depth_num"], plot_data["val_AUC"], marker="s",
        label="Validation AUC", color="#4C72B0", linewidth=2)

# Anotasi zona
ax.axvspan(0.5, 2.5, alpha=0.1, color="orange")
ax.axvspan(6.5, len(plot_data) + 0.5, alpha=0.1, color="red")
ax.text(1.5, 0.55, "Underfitting", ha="center", fontsize=9, color="darkorange")
ax.text(8, 0.85, "Overfitting", ha="center", fontsize=9, color="firebrick")

ax.set_xticks(plot_data["depth_num"])
ax.set_xticklabels(plot_data["max_depth"])
ax.set_xlabel("Kedalaman pohon (max_depth)")
ax.set_ylabel("ROC-AUC")
ax.set_title("Bias-Variance Tradeoff pada Decision Tree")
ax.legend(loc="lower right")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Figure 7.1: Bias-variance tradeoff. Model dengan kedalaman kecil underfitting (kedua kurva rendah). Model dengan kedalaman besar overfitting (train tinggi, validation menurun).

Cara membaca grafik ini:

  • Kedalaman 1–2 (underfitting): Train dan validation AUC keduanya rendah—model terlalu sederhana untuk menangkap pola.
  • Kedalaman 3–7 (zona ideal): Train dan validation AUC naik bersama—model menangkap pola tanpa menghafal noise. Titik di mana validation AUC tertinggi adalah kandidat model terbaik.
  • Kedalaman 10+ (overfitting): Train AUC terus naik menuju 1,0, tetapi validation AUC mulai menurun—model menghafal noise dalam train set, dan performanya pada data baru memburuk.
Important

Cara mendeteksi overfitting. Tanda paling jelas adalah selisih besar antara performa train dan validation. Jika train AUC 0,99 tetapi validation AUC 0,75, model kita overfitting. Selisih 5–10% masih wajar; selisih >15% adalah tanda bahaya.

Cara mendeteksi underfitting. Jika kedua performa (train dan validation) rendah dan tidak ada model sederhana yang berhasil, model kita underfitting. Solusinya bukan regularisasi—tetapi model yang lebih kompleks.

7.7 Data Leakage: Musuh yang Tidak Terlihat

Data leakage adalah kondisi ketika model tidak sengaja memiliki akses ke informasi yang tidak akan tersedia pada saat prediksi di dunia nyata. Ini adalah kesalahan paling berbahaya dalam pemodelan prediktif, karena model akan tampak sangat baik selama pengujian tetapi gagal total ketika diterapkan.

7.7.1 Contoh Data Leakage

Jenis Leakage Contoh Mengapa berbahaya
Target leakage Fitur TotalCharges dihitung setelah pelanggan churn (mencakup tagihan terakhir) Model akan belajar bahwa “TotalCharges tinggi = churn” padahal sebenarnya itu adalah konsekuensi churn
Temporal leakage Model menggunakan data 2024 untuk memprediksi 2023 Di dunia nyata, kita tidak punya data masa depan
Preprocessing leakage Menstandardisasi fitur pada seluruh dataset, lalu membagi train/test Statistik (mean, std) dari test bocor ke preprocessing—model tampak lebih baik daripada seharusnya
Group leakage Data pelanggan yang sama muncul di train dan test Model menghafal pelanggan tersebut, bukan pola umum

7.7.2 Contoh Temporal Leakage di Dataset Telco

Dataset Telco tidak memiliki timestamp, tetapi kita bisa membayangkan leakagenya. Misalnya:

Code
# JANGAN LAKUKAN INI — contoh anti-pattern
X_leaky = X.copy()
# Bayangkan kita punya kolom "last_complaint_date" yang hanya diisi
# SETELAH pelanggan menyatakan churn.
# X_leaky["had_complaint"] = (df["last_complaint_date"].notna()).astype(int)
# Model akan mendapat AUC 0.99 — tetapi tidak akan berguna di dunia nyata.

Cara mendeteksi leakage. Tanyakan untuk setiap fitur: “Apakah nilai fitur ini akan tersedia pada saat prediksi?” Jika tidak, fitur tersebut tidak boleh digunakan.

7.7.3 Mencegah Preprocessing Leakage

Kesalahan klasik: menstandardisasi data sebelum train/test split. Cara yang benar: preprocessing di dalam pipeline, sehingga hanya melihat train set saat fitting.

Code
# BENAR: preprocessing di dalam pipeline
# Pipeline hanya "melihat" train set saat .fit()
# Saat .predict() pada test set, scaler menggunakan statistik dari train
# (bukan dari test) — sehingga tidak ada leakage.

print("Pipeline yang kita bangun di atas:")
print("  1. SimpleImputer menghitung median dari X_train saja")
print("  2. StandardScaler menghitung mean/std dari X_train saja")
print("  3. OneHotEncoder mempelajari kategori dari X_train saja")
print()
print("→ Semua statistik diturunkan dari train, diterapkan ke test.")
print("→ Tidak ada informasi test yang 'bocor' ke preprocessing.")
Pipeline yang kita bangun di atas:
  1. SimpleImputer menghitung median dari X_train saja
  2. StandardScaler menghitung mean/std dari X_train saja
  3. OneHotEncoder mempelajari kategori dari X_train saja

→ Semua statistik diturunkan dari train, diterapkan ke test.
→ Tidak ada informasi test yang 'bocor' ke preprocessing.
Warning

Aturan praktis. Jangan pernah melakukan preprocessing (imputasi, scaling, encoding) di luar pipeline. Bahkan jika hasilnya tampak sama, ia menciptakan risiko leakage yang tidak terlihat. Pipeline scikit-learn ada persis untuk mencegah kesalahan ini—gunakan selalu.

7.8 Baseline Model: Yang Harus Dikalahkan

Dengan kerangka di atas, mari kita bangun baseline—model paling sederhana yang masuk akal. Baseline ini akan menjadi pembanding untuk model yang lebih canggih di Bab 8–9.

Code
# Latih pada train set, evaluasi pada validation set
baseline_pipeline.fit(X_train, y_train)

# Prediksi
y_train_pred = baseline_pipeline.predict_proba(X_train)[:, 1]
y_val_pred = baseline_pipeline.predict_proba(X_val)[:, 1]

# Evaluasi
from sklearn.metrics import roc_auc_score, accuracy_score

print("=== Baseline: Logistic Regression + Preprocessing ===")
print(f"Train AUC     : {roc_auc_score(y_train, y_train_pred):.3f}")
print(f"Validation AUC: {roc_auc_score(y_val, y_val_pred):.3f}")
print(f"Train accuracy: {accuracy_score(y_train, baseline_pipeline.predict(X_train)):.3f}")
print(f"Val accuracy  : {accuracy_score(y_val, baseline_pipeline.predict(X_val)):.3f}")
=== Baseline: Logistic Regression + Preprocessing ===
Train AUC     : 0.856
Validation AUC: 0.839
Train accuracy: 0.812
Val accuracy  : 0.805
Code
from sklearn.metrics import accuracy_score, roc_auc_score

# Konversi target ke biner 0/1 SEKALI di sini
y_val_bin = (y_val == "Yes").astype(int)
y_train_bin = (y_train == "Yes").astype(int)

# Trivial: selalu prediksi "No" (0)
trivial_pred = np.zeros(len(y_val_bin), dtype=int)
trivial_accuracy = accuracy_score(y_val_bin, trivial_pred)
trivial_auc = 0.5

# Naif: prediksi "Yes" (1) jika Contract == Month-to-month
naive_pred = (X_val["Contract"] == "Month-to-month").astype(int).values
naive_accuracy = accuracy_score(y_val_bin, naive_pred)
naive_auc = roc_auc_score(y_val_bin, naive_pred)

# Baseline LogReg:
#   - predict()       → string "Yes"/"No"  → bandingkan ke "Yes" agar jadi 0/1
#   - predict_proba() → float 0–1          → langsung dipakai untuk AUC
baseline_accuracy = accuracy_score(
    y_val_bin,
    baseline_pipeline.predict(X_val) == "Yes"
)
baseline_auc = roc_auc_score(y_val_bin, y_val_pred)

pd.DataFrame({
    "Model": ["Trivial (selalu 'No')", "Naif (Contract)", "Baseline (LogReg)"],
    "Val accuracy": [
        f"{trivial_accuracy:.1%}",
        f"{naive_accuracy:.1%}",
        f"{baseline_accuracy:.1%}",
    ],
    "Val ROC-AUC": [
        f"{trivial_auc:.3f}",
        f"{naive_auc:.3f}",
        f"{baseline_auc:.3f}",
    ],
})
Model Val accuracy Val ROC-AUC
0 Trivial (selalu 'No') 73.5% 0.500
1 Naif (Contract) 64.4% 0.715
2 Baseline (LogReg) 80.5% 0.839

Perhatikan pola penting:

  • Baseline trivial memiliki akurasi tertinggi (73,5%) tetapi AUC terburuk (0,5). Ini menunjukkan bahwa akurasi bukan metrik yang tepat untuk kelas tidak seimbang.
  • Baseline naif mengalahkan trivial dalam AUC tetapi kalah dalam akurasi. Ini adalah ilustrasi sempurna bahwa metrik yang kita pilih menentukan model yang kita pilih.
  • Baseline LogReg mengalahkan keduanya dalam AUC—dan inilah titik awal yang harus dikalahkan oleh model yang lebih canggih.
Important

Peluang untuk model yang lebih baik. Baseline LogReg mencapai AUC ~0,85. Model yang lebih canggih (random forest, gradient boosting) di Bab 9 mungkin mencapai AUC 0,88–0,90. Tetapi peningkatan 3–5% ini tidak otomatis berarti lebih baik—kita harus mempertimbangkan interpretabilitas, biaya komputasi, dan kemudahan deployment. Sering kali, model yang lebih sederhana dipilih karena lebih mudah dijelaskan kepada pemangku kepentingan, meskipun performanya sedikit lebih rendah.

7.9 Latihan Terbimbing

Latihan 7.1 — Formulasi masalah. Untuk setiap skenario berikut, tentukan: (a) jenis target (biner/kontinu/multikelas), (b) metrik yang tepat, (c) baseline yang wajar. - (a) Memprediksi apakah pelanggan akan membayar tagihan tepat waktu. - (b) Memprediksi berapa banyak unit produk yang akan terjual besok. - (c) Memprediksi jenis kontrak yang akan dipilih pelanggan baru (bulanan/tahunan/dua tahun).

Latihan 7.2 — Split vs CV. Jelaskan dalam satu paragraf: mengapa kita membutuhkan keduanya—train/validation/test split dan cross-validation? Kapan menggunakan yang mana?

Latihan 7.3 — Deteksi overfitting. Hasil berikut berasal dari tiga model pada dataset yang sama:

Model Train AUC Val AUC
A 0,72 0,70
B 0,95 0,75
C 0,65 0,64

Model mana yang overfitting? Model mana yang underfitting? Model mana yang tampaknya paling seimbang? Jelaskan.

Latihan 7.4 — Identifikasi leakage. Untuk setiap fitur berikut, tentukan apakah aman digunakan untuk memprediksi churn, atau berpotensi leakage: - (a) tenure (bulan berlangganan) - (b) total_support_tickets (jumlah tiket dukungan yang dibuka selama berlangganan) - (c) last_payment_date (tanggal pembayaran terakhir) - (d) account_status (aktif/ditutup—hanya diperbarui setelah pelanggan churn) - (e) monthly_charges

Latihan 7.5 — Baseline comparison. Bangun baseline trivial kedua: prediksi “Yes” jika tenure <= 6. Bandingkan dengan baseline Contract yang sudah kita buat. Mana yang lebih baik dalam AUC? Mana yang lebih baik dalam akurasi? Mana yang akan kita pilih untuk sistem retensi, dan mengapa?

Petunjuk Latihan 7.3. Ingat definisi: - Overfitting: train tinggi, validation rendah, selisih besar. - Underfitting: train dan validation keduanya rendah. - Seimbang: train dan validation tinggi, selisih kecil.

Model B overfitting (selisih 0,20). Model C underfitting (keduanya rendah). Model A paling seimbang (selisih 0,02). Tetapi “paling seimbang” tidak berarti “terbaik”—model A mungkin masih kalah dari model yang lebih kompleks jika model itu memiliki validation AUC yang lebih tinggi.

7.10 Kotak “Jembatan”: Dari Data ke Keputusan

Important

Jembatan. Bab ini memberi kita kerangka yang akan menyertai seluruh Bagian II:

  1. Model bukan kotak hitam. Setiap model memiliki asumsi, batasan, dan kegagalan. Kerangka bias-variance mengajarkan kita kapan model perlu dibuat lebih kompleks, dan kapan perlu disederhanakan.

  2. Metrik bukan tujuan. Memaksimalkan AUC tanpa mempertimbangkan konteks bisnis adalah kesalahan. Model dengan AUC 0,85 tetapi interpretabilitas tinggi sering lebih berharga daripada model dengan AUC 0,90 tetapi tidak bisa dijelaskan ke pemangku kepentingan.

  3. Data leakage adalah ancaman nyata. Model yang tampak sempurna dalam pengujian bisa gagal total dalam produksi jika ada leakage. Selalu tanyakan: “Apakah informasi ini akan tersedia saat prediksi?”

  4. Baseline bukan formalitas. Baseline mengingatkan kita bahwa model yang canggih belum tentu lebih baik dari aturan sederhana. Banyak model machine learning yang dikembangkan dengan biaya besar akhirnya kalah dari aturan sederhana yang sudah dipakai tim bisnis selama bertahun-tahun.

Dengan kerangka ini, kita siap membangun model yang lebih canggih. Bab 8 akan fokus pada regresi—fondasi statistik untuk memodelkan hubungan antar variabel. Bab 9 akan fokus pada klasifikasi—yang secara langsung menjawab pertanyaan churn kita.

7.11 Rangkuman

Dalam bab ini kita telah:

  • Membedakan prediksi dari deskripsi, memahami bahwa prediksi menjawab pertanyaan tingkat individu sementara deskripsi menjawab tingkat populasi.
  • Merumuskan masalah prediktif dengan lima pertanyaan kunci: apa targetnya, untuk siapa, kapan, apa konsekuensi kesalahannya, dan apa baseline-nya.
  • Menerapkan CRISP-DM sebagai kerangka kerja siklus pemodelan, dengan penekanan pada iterasi antara modeling dan evaluation.
  • Membagi data menjadi train/validation/test dengan stratifikasi, memahami peran unik masing-masing subset dan aturan emas test set.
  • Menerapkan cross-validation untuk estimasi performa yang lebih stabil, dan memahami kapan CV lebih tepat daripada single split.
  • Memahami bias-variance tradeoff sebagai fondasi semua pemodelan, dan mendemonstrasikan overfitting secara langsung dengan decision tree.
  • Mengenali dan menghindari data leakage—terutama dalam preprocessing—dengan menerapkan pipeline scikit-learn.
  • Membangun baseline model (LogReg + preprocessing) yang mencapai AUC ~0,85, mengalahkan baseline trivial dan naif—dan menjadi patokan yang harus dikalahkan oleh model di bab selanjutnya.

7.12 Jembatan ke Bab 8

Kita memiliki kerangka. Sekarang kita membutuhkan model yang sesungguhnya. Bab 8—Regresi—akan memperkenalkan fondasi statistik untuk memodelkan hubungan antar variabel:

  • Regresi linear untuk target kontinu, dengan interpretasi koefisien yang jelas.
  • Regresi logistik untuk target biner—model yang baru saja kita pakai sebagai baseline, tetapi akan kita pelajari secara mendalam.
  • Diagnostik residual untuk memeriksa apakah asumsi model terpenuhi.
  • Regularisasi (Ridge, Lasso) untuk mengendalikan kompleksitas model.

Regresi bukan sekadar “model sederhana yang kalah dari random forest”. Ia adalah fondasi dari sebagian besar machine learning modern, dan pemahaman mendalamnya akan membuat kita lebih efektif dalam semua model yang lebih canggih.