Apa yang Abbas Pelajari Bulan Ini

Selama delapan bulan terakhir, Abbas sudah mahir menganalisis dan memvisualisasikan data. Bulan 9 adalah babak baru: membuat mesin yang belajar dari data. Machine Learning bukan sihir, melainkan perpanjangan logis dari statistik yang sudah Abbas kuasai. Perbedaannya adalah fokus: statistik menjelaskan, ML memprediksi.

Konteks Karier: Sebagai Data Analyst, Abbas tidak harus menjadi ML Engineer. Namun memahami cara kerja model, cara mengevaluasinya, dan cara menginterpretasikan hasilnya adalah nilai tambah yang membedakan analyst level menengah dari level senior.

Kompetensi yang Dibangun

  • Membedakan pendekatan Machine Learning dari statistik inferensial tradisional dan memilih yang tepat sesuai pertanyaan bisnis
  • Memahami perbedaan supervised learning dan unsupervised learning beserta contoh penggunaan nyata masing-masing
  • Melakukan train/test split dengan benar dan menjelaskan mengapa pemisahan data itu krusial untuk model yang jujur
  • Membangun model regresi linear dengan scikit-learn: fit, predict, dan interpretasi koefisien
  • Mengevaluasi kualitas model regresi menggunakan MSE, RMSE, dan R-squared
  • Membangun model klasifikasi dasar: Logistic Regression, Decision Tree, dan K-Nearest Neighbors
  • Membaca dan menginterpretasikan confusion matrix beserta metrik turunannya: precision, recall, dan F1-score
  • Mengenali overfitting dan underfitting secara visual dan mengambil langkah awal untuk mengatasinya
Tips Belajar Bulan Ini: Jangan menghafal rumus. Fokus pada intuisi: "Apa yang model ini coba lakukan?" dan "Bagaimana Abbas tahu model ini bekerja dengan baik?" Kode scikit-learn hanya 5 baris, yang sulit adalah memahami hasilnya secara bermakna.
Minggu 1
Konsep ML dan Regresi Linear
ML vs statistik, supervised/unsupervised, train/test split, prediksi nilai kontinu
Minggu 2
Klasifikasi Dasar
Logistic Regression, Decision Tree, KNN, dan cara membaca confusion matrix
Minggu 3
Feature Engineering dan Preprocessing
Scaling, encoding kategorikal, handling missing values sebelum ML
Minggu 4
Proyek P5: Prediksi ML
End-to-end ML pipeline dari data mentah hingga model yang bisa diinterpretasikan

Minggu 1: Konsep ML dan Regresi Linear

Minggu pertama membangun fondasi konseptual sebelum Abbas menulis satu baris kode ML pun. Memahami mengapa jauh lebih penting dari bagaimana, terutama di awal perjalanan ML.

Hari 1
Machine Learning vs Statistik Tradisional
Baca: perbedaan mendasar antara analisis deskriptif, inferensial, dan prediktif. Buat diagram sederhana di buku catatan Abbas.
Eksplorasi: identifikasi 5 pertanyaan bisnis nyata di sekitar Abbas, lalu tentukan mana yang lebih cocok dijawab statistik dan mana oleh ML.
Tonton: video StatQuest "Machine Learning Fundamentals" (20 menit) lalu tulis 3 insight utama dengan kata-kata Abbas sendiri.
Diskusi: tanyakan ke seseorang, "Menurut kamu ML itu apa?" lalu bandingkan jawabannya dengan pemahaman Abbas hari ini.
# Contoh sederhana: statistik vs ML
import numpy as np

# Statistik: menghitung rata-rata (menjelaskan masa lalu)
nilai_ujian = [75, 80, 65, 90, 85, 70, 88, 72]
rata_rata = np.mean(nilai_ujian)
print(f"Rata-rata nilai kelas: {rata_rata:.1f}")

# ML: memprediksi nilai berdasarkan jam belajar (prediksi masa depan)
# (ini preview -- nanti Abbas akan belajar cara lengkapnya)
jam_belajar = np.array([2, 4, 3, 6, 5, 3, 5.5, 2.5])
# Korelasi dulu, sebelum masuk ke model
korelasi = np.corrcoef(jam_belajar, nilai_ujian)[0, 1]
print(f"Korelasi jam belajar vs nilai: {korelasi:.3f}")
print("Pertanyaan ML: jika Abbas belajar 7 jam, berapa nilainya?")
Refleksi: Abbas sudah menggunakan korelasi sejak Bulan 2. Apa bedanya korelasi (statistik) dengan prediksi (ML)? Apakah keduanya saling menggantikan, atau justru saling melengkapi?
Hari 2
Supervised vs Unsupervised Learning
Baca: definisi supervised learning (ada label/target) dan unsupervised learning (tidak ada label). Catat 3 contoh nyata masing-masing.
Kategorikan: ambil 10 kasus ML yang Abbas temukan di berita atau media sosial, lalu tentukan masuk supervised atau unsupervised.
Praktik eksplorasi: load dataset iris dari scikit-learn, lihat strukturnya, dan identifikasi mana yang jadi fitur dan mana yang jadi label.
from sklearn.datasets import load_iris
import pandas as pd

# Load dataset klasik untuk belajar
iris = load_iris()

# Lihat struktur data
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target
df['species'] = df['target'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})

print("Bentuk data:", df.shape)
print("\n5 baris pertama:")
print(df.head())

print("\nJumlah data per spesies:")
print(df['species'].value_counts())

# Fitur (X): yang digunakan untuk prediksi
# Label (y): yang ingin diprediksi
print("\nFitur yang tersedia:", iris.feature_names)
print("Label yang diprediksi: jenis spesies bunga")
Supervised Learning: Model belajar dari pasangan input-output (X, y). Contoh: prediksi harga rumah berdasarkan luas dan lokasi.

Unsupervised Learning: Model menemukan pola dari data tanpa label. Contoh: mengelompokkan pelanggan berdasarkan perilaku belanja tanpa tahu dulu kategorinya.
Hari 3
Train/Test Split dan Overfitting
Pahami konsep: kenapa model tidak boleh dievaluasi pada data yang sama dengan data yang dipakai untuk melatihnya. Buat analogi sendiri (misal: kunci jawaban ujian).
Praktik: lakukan train/test split dengan rasio 80:20 dan 70:30. Bandingkan dan diskusikan trade-off masing-masing.
Buka simulasi overfitting di ../simulasi/overfitting.html dan eksplorasi apa yang terjadi saat model terlalu kompleks.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import pandas as pd

iris = load_iris()
X = iris.data   # fitur: 4 kolom ukuran bunga
y = iris.target # label: jenis spesies

# Split data: 80% untuk training, 20% untuk testing
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,      # 20% untuk test
    random_state=42,    # agar hasil reproduktif
    stratify=y          # pastikan proporsi kelas seimbang
)

print(f"Total data: {len(X)}")
print(f"Data training: {len(X_train)} ({len(X_train)/len(X)*100:.0f}%)")
print(f"Data testing:  {len(X_test)} ({len(X_test)/len(X)*100:.0f}%)")

# Cek distribusi kelas
import numpy as np
print("\nDistribusi kelas di training set:", np.bincount(y_train))
print("Distribusi kelas di test set:    ", np.bincount(y_test))
Tantangan: Coba ubah test_size menjadi 0.5 (50:50). Apakah Abbas menduga model akan lebih baik atau lebih buruk? Jalankan kode dan catat hasilnya. Besok Abbas akan membandingkan performa model dengan berbagai ukuran split ini.
Hari 4
Regresi Linear: Konsep dan Implementasi
Pahami: regresi linear menemukan garis lurus terbaik yang menjelaskan hubungan antara variabel input dan output kontinu.
Bangun model pertama: prediksi harga rumah berdasarkan luas bangunan menggunakan dataset sederhana buatan sendiri.
Visualisasi: plot data points dan garis regresi yang dihasilkan model untuk melihat seberapa baik garis itu merepresentasikan data.
Interpretasi koefisien: apa arti angka slope dan intercept dalam konteks nyata yang Abbas modelkan?
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# Dataset sederhana: luas (m2) vs harga (juta rupiah)
np.random.seed(42)
luas = np.random.uniform(30, 150, 80)
harga = 200 + 5.5 * luas + np.random.normal(0, 30, 80)

df = pd.DataFrame({'luas_m2': luas, 'harga_juta': harga})

# Siapkan data
X = df[['luas_m2']]  # fitur harus 2D
y = df['harga_juta']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Bangun dan latih model
model = LinearRegression()
model.fit(X_train, y_train)

# Interpretasi
print(f"Intercept (titik awal): {model.intercept_:.1f} juta")
print(f"Koefisien (slope): {model.coef_[0]:.2f} juta per m2")
print()
print("Artinya: setiap tambahan 1 m2 luas bangunan,")
print(f"harga naik sekitar Rp {model.coef_[0]:.1f} juta")

# Prediksi
prediksi = model.predict([[75]])
print(f"\nPrediksi harga rumah 75 m2: Rp {prediksi[0]:.1f} juta")
Persamaan Regresi Linear: harga = intercept + (koefisien x luas)
Intercept adalah nilai prediksi saat semua fitur bernilai nol (kadang tidak bermakna secara nyata). Koefisien menunjukkan seberapa besar perubahan output untuk setiap satu unit perubahan input.
Hari 5
Evaluasi Model Regresi: MSE, RMSE, dan R-squared
Pelajari metrik evaluasi: Mean Squared Error (MSE), Root MSE (RMSE), dan R-squared. Pahami apa yang diukur masing-masing.
Hitung metrik: gunakan model regresi dari Hari 4 dan evaluasi pada data test (bukan training).
Interpretasi R-squared: nilai 0 berarti model tidak lebih baik dari tebak rata-rata, nilai 1 berarti model sempurna. Berapa nilai model Abbas?
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# Lanjutan dari kode Hari 4
# Asumsikan model, X_test, y_test sudah ada

y_pred = model.predict(X_test)

# Hitung metrik evaluasi
mse  = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2   = r2_score(y_test, y_pred)

print("=== Evaluasi Model Regresi ===")
print(f"MSE  (Mean Squared Error):   {mse:.2f}")
print(f"RMSE (Root Mean Sq. Error):  {rmse:.2f} juta rupiah")
print(f"Rยฒ   (R-squared):            {r2:.4f}")
print()

# Interpretasi R2
if r2 >= 0.9:
    print("R2 >= 0.90: Model sangat baik")
elif r2 >= 0.7:
    print("R2 >= 0.70: Model cukup baik untuk analisis awal")
elif r2 >= 0.5:
    print("R2 >= 0.50: Model moderat, perlu perbaikan")
else:
    print("R2 < 0.50: Model lemah, perlu eksplorasi lebih")

# Lihat beberapa prediksi vs aktual
import pandas as pd
hasil = pd.DataFrame({'Aktual': y_test.values[:5],
                      'Prediksi': y_pred[:5],
                      'Selisih': y_test.values[:5] - y_pred[:5]})
print("\nSampel prediksi vs aktual:")
print(hasil.round(1))
Tantangan Akhir Minggu: Coba tambahkan satu fitur baru ke model regresi Abbas, misalnya jumlah kamar tidur. Apakah R-squared naik atau turun? Apakah RMSE mengecil? Catat hipotesis Abbas sebelum menjalankan kode, lalu bandingkan dengan hasilnya. Ini adalah cara berpikir saintifik yang benar.

Minggu 2: Klasifikasi Dasar

Regresi memprediksi angka kontinu. Klasifikasi memprediksi kategori: spam atau bukan, lulus atau tidak, produk mana yang akan dibeli. Minggu ini Abbas mempelajari tiga algoritma klasifikasi yang paling sering dipakai dalam pekerjaan analitik nyata.

Hari 1
Konsep Klasifikasi dan Logistic Regression
Pahami perbedaan regresi vs klasifikasi: output regresi adalah angka, output klasifikasi adalah kelas/kategori.
Baca tentang Logistic Regression: meski namanya "regresi", ini adalah algoritma klasifikasi. Pelajari mengapa disebut demikian.
Bangun model Logistic Regression pertama untuk memprediksi apakah seorang siswa lulus berdasarkan jam belajar dan nilai rata-rata.
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Dataset: prediksi kelulusan siswa
np.random.seed(42)
n = 200
jam_belajar    = np.random.uniform(1, 10, n)
nilai_rata_rata = np.random.uniform(40, 100, n)

# Label: lulus (1) atau tidak (0) berdasarkan kombinasi fitur
skor_gabungan = 0.4 * jam_belajar + 0.6 * (nilai_rata_rata / 10)
lulus = (skor_gabungan + np.random.normal(0, 0.5, n) > 5).astype(int)

df = pd.DataFrame({
    'jam_belajar': jam_belajar,
    'nilai_rata_rata': nilai_rata_rata,
    'lulus': lulus
})

print("Distribusi label:")
print(df['lulus'].value_counts().rename({0: 'Tidak Lulus', 1: 'Lulus'}))

X = df[['jam_belajar', 'nilai_rata_rata']]
y = df['lulus']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Scaling penting untuk Logistic Regression
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# Latih model
model_lr = LogisticRegression(random_state=42)
model_lr.fit(X_train_scaled, y_train)

akurasi = model_lr.score(X_test_scaled, y_test)
print(f"\nAkurasi Logistic Regression: {akurasi:.2%}")
Mengapa Perlu Scaling? Logistic Regression dan beberapa algoritma lain sensitif terhadap skala fitur. Jam belajar (1-10) dan nilai (40-100) punya skala berbeda. StandardScaler mengubah semua fitur ke skala yang sama (rata-rata 0, standar deviasi 1) agar tidak ada fitur yang mendominasi hanya karena angkanya lebih besar.
Hari 2
Decision Tree: Klasifikasi Berbasis Aturan
Pahami intuisi Decision Tree: model ini membuat serangkaian pertanyaan if-else secara otomatis untuk memisahkan kelas data.
Bangun Decision Tree untuk dataset kelulusan dari Hari 1. Bandingkan akurasinya dengan Logistic Regression.
Visualisasi pohon keputusan: lihat aturan yang dipelajari model secara grafis dan interpretasikan tiap cabangnya.
Eksplorasi hyperparameter: ubah max_depth dari 2 hingga 10. Catat apa yang terjadi pada akurasi training vs test.
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.model_selection import train_test_split
import pandas as pd

# Gunakan data yang sama dari Hari 1
# Asumsikan X_train, X_test, y_train, y_test sudah ada (tanpa scaling)

# Decision Tree tidak butuh scaling
model_dt = DecisionTreeClassifier(
    max_depth=4,        # batasi kedalaman agar tidak overfit
    random_state=42
)
model_dt.fit(X_train, y_train)

akurasi_train = model_dt.score(X_train, y_train)
akurasi_test  = model_dt.score(X_test, y_test)

print(f"Akurasi Training: {akurasi_train:.2%}")
print(f"Akurasi Testing:  {akurasi_test:.2%}")
print()

# Jika akurasi training jauh lebih tinggi dari test, itu tanda overfitting
gap = akurasi_train - akurasi_test
if gap > 0.1:
    print(f"Peringatan: gap {gap:.2%} -- kemungkinan overfitting")
else:
    print(f"Gap kecil ({gap:.2%}) -- model generalisasi dengan baik")

# Lihat aturan yang dipelajari model
feature_names = ['jam_belajar', 'nilai_rata_rata']
aturan = export_text(model_dt, feature_names=feature_names)
print("\nAturan Decision Tree:")
print(aturan[:500])  # tampilkan 500 karakter pertama
Refleksi: Decision Tree yang sangat dalam (max_depth besar) bisa menghafalkan data training secara sempurna, tapi gagal di data baru. Pernahkah Abbas menemukan situasi serupa dalam belajar, di mana menghafal soal latihan tidak membantu saat soal ujian berbeda? Apa analoginya dengan konsep overfitting ini?
Hari 3
K-Nearest Neighbors (KNN): Klasifikasi Berbasis Jarak
Pahami intuisi KNN: untuk mengklasifikasikan data baru, cari K data latih yang paling mirip (terdekat), lalu ambil suara mayoritas.
Bangun model KNN dengan nilai K yang berbeda (K=3, K=5, K=11). Bandingkan akurasi masing-masing.
Plot decision boundary KNN vs Decision Tree untuk melihat secara visual bagaimana setiap model memisahkan kelas.
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
import numpy as np

# KNN butuh scaling karena berbasis jarak
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# Coba beberapa nilai K
print("Eksplorasi nilai K:")
print(f"{'K':>5} | {'Train':>8} | {'Test':>8}")
print("-" * 28)

hasil_k = []
for k in [1, 3, 5, 7, 11, 15, 21]:
    knn = KNeighborsClassifier(n_neighbors=k)
    knn.fit(X_train_scaled, y_train)

    acc_train = knn.score(X_train_scaled, y_train)
    acc_test  = knn.score(X_test_scaled, y_test)

    hasil_k.append({'K': k, 'train': acc_train, 'test': acc_test})
    print(f"{k:>5} | {acc_train:>7.2%} | {acc_test:>7.2%}")

# Temukan K terbaik berdasarkan test accuracy
import pandas as pd
df_k = pd.DataFrame(hasil_k)
k_terbaik = df_k.loc[df_k['test'].idxmax(), 'K']
print(f"\nK terbaik: {int(k_terbaik)}")
Tantangan: K=1 hampir selalu memberikan akurasi training 100%. Mengapa? Dan mengapa ini justru berbahaya? Coba jelaskan dengan kata-kata Abbas sendiri sebelum membaca referensi apapun. Tulis penjelasan itu di buku catatan.
Hari 4
Confusion Matrix dan Metrik Klasifikasi
Pahami bahwa akurasi saja tidak cukup, terutama saat data tidak seimbang (misalnya 95% kelas A, 5% kelas B).
Belajar membaca confusion matrix: True Positive, True Negative, False Positive, dan False Negative. Gambar tabelnya di buku catatan.
Hitung precision, recall, dan F1-score dari model klasifikasi yang sudah Abbas bangun. Interpretasikan artinya dalam konteks nyata.
from sklearn.metrics import (confusion_matrix, classification_report,
                              ConfusionMatrixDisplay)
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# Gunakan model Logistic Regression dari Hari 1
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s  = scaler.transform(X_test)

model = LogisticRegression(random_state=42)
model.fit(X_train_s, y_train)
y_pred = model.predict(X_test_s)

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(f"               Prediksi: Tidak Lulus | Prediksi: Lulus")
print(f"Aktual: Tidak Lulus   {cm[0,0]:>10}         {cm[0,1]:>10}")
print(f"Aktual: Lulus         {cm[1,0]:>10}         {cm[1,1]:>10}")
print()

# Laporan lengkap
print("Laporan Klasifikasi:")
print(classification_report(y_test, y_pred,
      target_names=['Tidak Lulus', 'Lulus']))

# Penjelasan metrik
tn, fp, fn, tp = cm.ravel()
precision = tp / (tp + fp)
recall    = tp / (tp + fn)
f1        = 2 * precision * recall / (precision + recall)
print(f"Precision: {precision:.2%} (dari semua yang diprediksi lulus, berapa yang benar-benar lulus)")
print(f"Recall:    {recall:.2%} (dari semua yang sebenarnya lulus, berapa yang berhasil terdeteksi)")
print(f"F1-Score:  {f1:.2%} (keseimbangan precision dan recall)")
Kapan Precision vs Recall lebih penting?
Jika biaya False Positive tinggi (misal: menuduh orang sehat sakit, lalu operasi), utamakan Precision.
Jika biaya False Negative tinggi (misal: melewatkan diagnosis kanker), utamakan Recall.
Dalam konteks bisnis, Abbas harus selalu bertanya: "Error mana yang lebih mahal bagi organisasi?"
Hari 5
Membandingkan Tiga Algoritma dan Memilih Model
Bandingkan Logistic Regression, Decision Tree, dan KNN pada dataset yang sama menggunakan metrik yang konsisten.
Buat tabel perbandingan: akurasi, precision, recall, F1, dan waktu training. Tentukan mana yang terbaik untuk kasus ini.
Diskusikan: selain akurasi, faktor apa lagi yang perlu dipertimbangkan saat memilih algoritma? (interpretabilitas, kecepatan, kebutuhan data, dll)
Simpan semua kode minggu ini ke file klasifikasi_dasar.py sebagai referensi proyek P5 nanti.
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import f1_score, precision_score, recall_score
import pandas as pd
import time

# Setup
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s  = scaler.transform(X_test)

# Definisikan model yang akan dibandingkan
kandidat = {
    'Logistic Regression': LogisticRegression(random_state=42),
    'Decision Tree (d=4)': DecisionTreeClassifier(max_depth=4, random_state=42),
    'KNN (K=7)':           KNeighborsClassifier(n_neighbors=7),
}

hasil = []
for nama, model in kandidat.items():
    # Decision Tree tidak butuh scaled data, tapi tidak merusak jika dipakai
    mulai = time.time()
    model.fit(X_train_s, y_train)
    durasi = time.time() - mulai

    y_pred = model.predict(X_test_s)

    hasil.append({
        'Model':     nama,
        'Akurasi':   f"{model.score(X_test_s, y_test):.2%}",
        'Precision': f"{precision_score(y_test, y_pred):.2%}",
        'Recall':    f"{recall_score(y_test, y_pred):.2%}",
        'F1-Score':  f"{f1_score(y_test, y_pred):.2%}",
        'Waktu (ms)': f"{durasi*1000:.1f}",
    })

df_hasil = pd.DataFrame(hasil)
print("=== Perbandingan Algoritma Klasifikasi ===")
print(df_hasil.to_string(index=False))
Refleksi Akhir Minggu: Jika Abbas diminta memilih satu model untuk dipresentasikan ke kepala sekolah guna memprediksi risiko siswa tidak lulus, model mana yang Abbas pilih? Bukan hanya berdasarkan angka, tetapi juga pertimbangan: apakah guru bisa memahami cara kerja model tersebut? Apakah model bisa menjelaskan alasannya kepada orang tua siswa?

Minggu 3 โ€” Feature Engineering

Abbas akan mempelajari teknik-teknik transformasi fitur agar model machine learning dapat belajar lebih efektif: encoding kategoris, scaling numerik, imputasi nilai hilang, dan seleksi fitur.

Hari 1

Encoding Variabel Kategoris

Memahami perbedaan Label Encoding dan One-Hot Encoding, serta kapan masing-masing digunakan.

  • Label Encoding untuk variabel ordinal (mis. Low/Medium/High)
  • One-Hot Encoding untuk variabel nominal (mis. kota, jenis kelamin)
  • Menghindari dummy variable trap dengan drop_first=True
import pandas as pd
from sklearn.preprocessing import LabelEncoder

df = pd.DataFrame({
    'kota': ['Jakarta', 'Bandung', 'Jakarta', 'Surabaya'],
    'grade': ['Low', 'High', 'Medium', 'High']
})

# Label Encoding untuk ordinal
le = LabelEncoder()
df['grade_enc'] = le.fit_transform(df['grade'])

# One-Hot Encoding untuk nominal
df_encoded = pd.get_dummies(df, columns=['kota'], drop_first=True)
print(df_encoded)
Challenge: Ambil dataset dari Kaggle yang memiliki kolom kategorikal campuran (ordinal dan nominal). Terapkan encoding yang tepat untuk masing-masing kolom dan jelaskan alasan pilihanmu.
Hari 2

Scaling Fitur Numerik

Normalisasi dan standarisasi agar fitur dengan skala berbeda tidak mendominasi proses pembelajaran model.

  • MinMaxScaler: skala ke rentang [0, 1], sensitif outlier
  • StandardScaler: distribusi mean=0, std=1 โ€” lebih robust
  • RobustScaler: berbasis median dan IQR, tahan outlier ekstrem
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
import numpy as np

data = np.array([[100], [200], [300], [10000]])  # ada outlier

scaler_mm = MinMaxScaler()
scaler_std = StandardScaler()
scaler_rob = RobustScaler()

print("MinMax:", scaler_mm.fit_transform(data).ravel())
print("Standard:", scaler_std.fit_transform(data).ravel())
print("Robust:", scaler_rob.fit_transform(data).ravel())
Trigger Refleksi: Kapan Abbas tidak perlu melakukan scaling sama sekali? Cari satu contoh algoritma yang tidak terpengaruh oleh perbedaan skala fitur.
Hari 3

Imputasi Nilai Hilang (Missing Value Imputation)

Menangani data kosong dengan strategi yang tepat agar model tidak bias.

  • Imputasi mean/median/modus via SimpleImputer
  • Imputasi berbasis model via KNNImputer dan IterativeImputer
  • Kapan harus menghapus baris vs mengisi nilai
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer, KNNImputer

df = pd.DataFrame({
    'usia': [25, np.nan, 35, 40, np.nan],
    'gaji': [3000, 4000, np.nan, 5000, 4500],
    'kota': ['Jakarta', 'Bandung', np.nan, 'Jakarta', 'Surabaya']
})

# Numerik: median imputation
imp_num = SimpleImputer(strategy='median')
df[['usia', 'gaji']] = imp_num.fit_transform(df[['usia', 'gaji']])

# Kategorik: modus imputation
imp_cat = SimpleImputer(strategy='most_frequent')
df[['kota']] = imp_cat.fit_transform(df[['kota']])

# KNN Imputer (lebih akurat untuk numerik)
imp_knn = KNNImputer(n_neighbors=2)
# Gunakan setelah encoding jika ada kategorikal
print(df)
Challenge: Temukan dataset publik dengan missing values di atas 20%. Bandingkan hasil prediksi model sederhana (misal LinearRegression) antara imputasi mean vs KNNImputer. Mana yang lebih baik dan mengapa?
Hari 4

Feature Selection: Filter dan Wrapper Methods

Memilih subset fitur yang paling informatif untuk meningkatkan performa model dan mengurangi overfitting.

  • Filter Methods: korelasi Pearson, chi-square, mutual information โ€” cepat, model-agnostic
  • Wrapper Methods: RFE (Recursive Feature Elimination) โ€” lebih akurat, lebih mahal komputasi
  • Embedded Methods: feature importance dari tree-based models
from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectKBest, chi2, RFE
from sklearn.linear_model import LogisticRegression
import pandas as pd

data = load_breast_cancer()
X, y = data.data, data.target
feature_names = data.feature_names

# Filter: SelectKBest dengan chi2
selector = SelectKBest(score_func=chi2, k=10)
X_new = selector.fit_transform(abs(X), y)
selected = [feature_names[i] for i in selector.get_support(indices=True)]
print("Top 10 fitur (chi2):", selected)

# Wrapper: RFE
model = LogisticRegression(max_iter=5000)
rfe = RFE(model, n_features_to_select=10)
rfe.fit(X, y)
selected_rfe = [feature_names[i] for i in range(len(feature_names)) if rfe.support_[i]]
print("Top 10 fitur (RFE):", selected_rfe)
Trigger Refleksi: Apakah selalu lebih baik memakai lebih banyak fitur? Coba latih model dengan semua fitur vs 10 fitur terpilih. Bandingkan accuracy dan waktu komputasi.
Hari 5

Pipeline: Menggabungkan Semua Langkah Preprocessing

Membuat pipeline yang bersih dan reproducible menggunakan sklearn.pipeline.Pipeline dan ColumnTransformer.

  • Menghindari data leakage saat split train/test
  • ColumnTransformer untuk preprocessing kolom berbeda secara bersamaan
  • Export pipeline ke file .pkl untuk deployment
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
import joblib

num_features = ['usia', 'gaji']
cat_features = ['kota']

num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

cat_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('encoder', OneHotEncoder(handle_unknown='ignore', drop='first'))
])

preprocessor = ColumnTransformer([
    ('num', num_pipeline, num_features),
    ('cat', cat_pipeline, cat_features)
])

full_pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', LogisticRegression())
])

# full_pipeline.fit(X_train, y_train)
# joblib.dump(full_pipeline, 'model_pipeline.pkl')
print("Pipeline berhasil dibuat.")
Mini Project: Abbas membangun pipeline lengkap dari raw dataset (pilih sendiri dari Kaggle) hingga prediksi: imputasi, encoding, scaling, feature selection, dan model. Simpan hasilnya dalam file .pkl.

Minggu 4 โ€” Evaluasi dan Iterasi Model

Abbas mempelajari cara mengevaluasi model secara menyeluruh: cross-validation, hyperparameter tuning, analisis error, dan iterasi perbaikan yang sistematis.

Hari 1

Cross-Validation dan Strategi Evaluasi Robust

Memahami keterbatasan single train/test split dan menggantinya dengan cross-validation untuk estimasi performa yang lebih stabil.

  • k-Fold Cross-Validation: membagi data ke k bagian, rotasi sebagai test set
  • Stratified k-Fold: menjaga proporsi kelas di tiap fold โ€” penting untuk data imbalanced
  • Membaca mean dan std dari skor CV untuk deteksi instabilitas model
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import numpy as np

X, y = load_breast_cancer(return_X_y=True)
model = RandomForestClassifier(n_estimators=100, random_state=42)

# Stratified k-Fold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='f1')

print(f"F1 per fold: {scores.round(3)}")
print(f"Mean F1: {scores.mean():.3f} (+/- {scores.std():.3f})")
Trigger Refleksi: Abbas memiliki dataset dengan 95% kelas negatif dan 5% kelas positif. Metrik apa yang paling tepat digunakan sebagai scoring di cross_val_score? Mengapa accuracy bisa menyesatkan di sini?
Hari 2

Hyperparameter Tuning: Grid Search dan Random Search

Menemukan kombinasi hyperparameter terbaik secara sistematis alih-alih trial and error manual.

  • GridSearchCV: exhaustive search โ€” tepat untuk ruang parameter kecil
  • RandomizedSearchCV: sampling acak โ€” efisien untuk ruang parameter besar
  • Menggunakan refit=True untuk langsung mendapatkan model terbaik
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from scipy.stats import randint

X, y = load_breast_cancer(return_X_y=True)

param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [None, 5, 10],
    'min_samples_split': [2, 5, 10]
}

# Grid Search (27 kombinasi x 5 fold = 135 fits)
grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid, cv=5, scoring='f1', n_jobs=-1
)
grid_search.fit(X, y)
print("Best params (Grid):", grid_search.best_params_)
print("Best F1 (Grid):", grid_search.best_score_.round(3))

# Random Search (lebih cepat untuk ruang besar)
param_dist = {
    'n_estimators': randint(50, 500),
    'max_depth': [None, 3, 5, 10, 20],
    'min_samples_split': randint(2, 20)
}
rand_search = RandomizedSearchCV(
    RandomForestClassifier(random_state=42),
    param_dist, n_iter=20, cv=5, scoring='f1', random_state=42, n_jobs=-1
)
rand_search.fit(X, y)
print("Best params (Random):", rand_search.best_params_)
Challenge: Lakukan GridSearchCV pada dataset yang Abbas pilih di Minggu 3. Bandingkan skor model default vs model setelah tuning. Berapa persen peningkatannya?
Hari 3

Analisis Error: Confusion Matrix, Learning Curve, dan Residual Plot

Mendiagnosis di mana model gagal โ€” apakah karena underfitting, overfitting, atau bias data tertentu.

  • Confusion Matrix: visualisasi detail kesalahan klasifikasi per kelas
  • Learning Curve: deteksi bias vs variance โ€” butuh lebih banyak data atau model lebih kompleks?
  • Residual Plot: untuk regresi โ€” pola residual mengindikasikan masalah model
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import learning_curve, train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import ConfusionMatrixDisplay

X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Confusion Matrix
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
ConfusionMatrixDisplay.from_estimator(model, X_test, y_test, ax=axes[0])
axes[0].set_title("Confusion Matrix")

# Learning Curve
train_sizes, train_scores, val_scores = learning_curve(
    model, X, y, cv=5, scoring='f1',
    train_sizes=np.linspace(0.1, 1.0, 10), n_jobs=-1
)
axes[1].plot(train_sizes, train_scores.mean(axis=1), label='Train F1')
axes[1].plot(train_sizes, val_scores.mean(axis=1), label='Validation F1')
axes[1].set_xlabel("Training Size")
axes[1].set_ylabel("F1 Score")
axes[1].set_title("Learning Curve")
axes[1].legend()
plt.tight_layout()
plt.savefig("eval_analysis.png", dpi=150)
Trigger Refleksi: Dari learning curve yang Abbas buat, apakah model menunjukkan high bias atau high variance? Apa tindakan konkret yang bisa diambil berdasarkan diagnosis tersebut?
Hari 4

Strategi Iterasi: Mengatasi Overfitting dan Underfitting

Membangun kebiasaan iterasi yang terstruktur: tahu kapan menambah data, memperumit model, atau menyederhanakan.

  • Underfitting: tambah fitur, perbesar model, kurangi regularisasi
  • Overfitting: regularisasi (L1/L2), dropout, pruning, tambah data, feature selection
  • Teknik augmentasi data sederhana untuk memperbesar training set
from sklearn.linear_model import LogisticRegression, Ridge
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score
import numpy as np

X, y = load_breast_cancer(return_X_y=True)

# Membandingkan tingkat regularisasi
C_values = [0.001, 0.01, 0.1, 1, 10, 100]
results = []
for C in C_values:
    model = LogisticRegression(C=C, max_iter=5000)
    score = cross_val_score(model, X, y, cv=5, scoring='f1').mean()
    results.append((C, score))
    print(f"C={C:6}: F1 = {score:.4f}")

best_C, best_score = max(results, key=lambda x: x[1])
print(f"
C terbaik: {best_C} dengan F1 = {best_score:.4f}")
Challenge: Buat eksperimen sistematis: latih 3 model dengan kompleksitas berbeda (underfitting, pas, overfitting) pada dataset yang sama. Visualisasikan perbedaan training vs validation score ketiganya.
Hari 5

Mini Project Bulan 09: End-to-End ML Pipeline

Abbas mengintegrasikan semua yang telah dipelajari selama bulan ini dalam satu proyek utuh: dari data mentah hingga model yang dievaluasi dan siap digunakan.

  • Pilih dataset klasifikasi atau regresi dari Kaggle (minimal 1.000 baris, 8+ fitur)
  • Tahap 1: EDA singkat, identifikasi missing values dan distribusi target
  • Tahap 2: Feature engineering โ€” encoding, scaling, imputasi, seleksi fitur
  • Tahap 3: Bandingkan minimal 3 algoritma (LogReg, Decision Tree, Random Forest)
  • Tahap 4: Hyperparameter tuning pada model terbaik
  • Tahap 5: Evaluasi final dengan confusion matrix, classification report, dan learning curve
  • Tahap 6: Simpan pipeline sebagai .pkl dan tulis 1 paragraf kesimpulan
import joblib
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report
# import dataset pilihan Abbas di sini

# Contoh kerangka akhir
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
# full_pipeline.fit(X_train, y_train)
# y_pred = full_pipeline.predict(X_test)
# print(classification_report(y_test, y_pred))
# joblib.dump(full_pipeline, 'bulan09_final_model.pkl')
print("Template mini project siap. Lengkapi dengan dataset dan pipeline Abbas sendiri.")
Deliverable Akhir Bulan 09: Notebook Jupyter (.ipynb) lengkap dengan semua tahap di atas, file bulan09_final_model.pkl, dan 1 paragraf kesimpulan: apa yang paling Abbas pelajari bulan ini, dan apa yang masih ingin diperdalam di bulan berikutnya.

Buku Pendukung Bulan 09

Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow
Aurelien Geron
Referensi utama untuk semua topik Bulan 09: algoritma klasik, feature engineering, pipeline sklearn, evaluasi model, dan hyperparameter tuning. Chapter 2 dan 3 sangat relevan langsung.
Feature Engineering for Machine Learning
Alice Zheng & Amanda Casari
Buku yang fokus penuh pada rekayasa fitur: encoding, scaling, binning, interaksi fitur, dan feature crosses. Sangat cocok untuk memperdalam Minggu 3 Bulan 09.
Introduction to Statistical Learning (ISLR)
Gareth James, Daniela Witten, Trevor Hastie, Robert Tibshirani
Penjelasan matematis yang accessible untuk Linear Regression, Logistic Regression, Decision Tree, dan Random Forest. Gratis tersedia online. Sangat membantu memahami mengapa algoritma bekerja, bukan hanya cara pakainya.
Python Machine Learning
Sebastian Raschka & Vahid Mirjalili
Implementasi praktis algoritma ML dari nol dengan Python. Cocok untuk memperkuat pemahaman pipeline, evaluasi model, dan penggunaan scikit-learn secara mendalam di Bulan 09.

Tools yang Digunakan

scikit-learn
Library ML utama di Python. Menyediakan semua algoritma (LogReg, Decision Tree, Random Forest), preprocessing (scaler, imputer, encoder), pipeline, dan evaluasi dalam satu ekosistem yang konsisten.
Jupyter Notebook
Lingkungan eksplorasi interaktif untuk menulis kode, visualisasi, dan narasi dalam satu dokumen. Standar industri untuk dokumentasi eksperimen ML.
pandas
Manipulasi dan inspeksi dataframe: cek missing values, encoding manual, groupby, merge. Dipakai di setiap tahap preprocessing sebelum data masuk pipeline sklearn.
matplotlib & seaborn
Visualisasi distribusi fitur, confusion matrix, learning curve, dan residual plot. Penting untuk analisis error dan komunikasi hasil kepada tim.
joblib
Serialisasi pipeline dan model terlatih ke file .pkl. Memungkinkan model disimpan dan diload ulang tanpa perlu melatih ulang dari awal โ€” fondasi untuk deployment sederhana.
Kaggle Datasets
Sumber dataset publik untuk latihan. Abbas dapat mengunduh dataset nyata dengan berbagai kompleksitas: imbalanced classes, mixed types, missing values โ€” semua relevan untuk Bulan 09.

Tips Praktis dan Jebakan Pemula

Setelah mempelajari algoritma dan pipeline, Abbas perlu memahami kesalahan umum yang dilakukan pemula ML dan cara menghindarinya. Tips ini dikumpulkan dari pola kesalahan yang paling sering muncul di komunitas dan industri.

Jebakan 1: Data Leakage akibat Scaling Sebelum Split
Ini adalah kesalahan paling fatal dan paling sering dilakukan pemula. Jika Abbas melakukan scaler.fit(X) pada seluruh data sebelum train/test split, maka informasi distribusi data test sudah "bocor" ke training. Model akan terlihat sangat bagus di test set, tapi gagal total di data nyata. Solusi: selalu split data dulu, baru fit preprocessor pada training set saja.
# SALAH: scaler tahu tentang data test!
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)       # fit pada semua data
X_train, X_test = train_test_split(X_scaled)

# BENAR: scaler hanya belajar dari training set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # fit hanya di train
X_test_scaled  = scaler.transform(X_test)        # transform saja, tidak fit ulang
Tips 2: Selalu Mulai dari Baseline Model
Sebelum mencoba Random Forest atau algoritma kompleks, Abbas harus punya angka baseline. Untuk klasifikasi, baseline termudah adalah "selalu prediksi kelas mayoritas". Jika Random Forest hanya lebih baik 2% dari baseline, mungkin fitur yang perlu diperbaiki, bukan modelnya.
from sklearn.dummy import DummyClassifier
from sklearn.metrics import f1_score

# Baseline: selalu prediksi kelas paling sering muncul
dummy = DummyClassifier(strategy='most_frequent')
dummy.fit(X_train, y_train)
baseline_f1 = f1_score(y_test, dummy.predict(X_test), average='weighted')
print(f"Baseline F1: {baseline_f1:.3f}")
# Model Abbas harus secara signifikan mengalahkan angka ini
# Kalau tidak, cek kualitas fitur terlebih dahulu
Jebakan 3: Terlalu Percaya pada Accuracy di Data Imbalanced
Bayangkan dataset deteksi penipuan: 99% transaksi normal, 1% fraud. Model yang selalu prediksi "normal" akan mendapat accuracy 99%, padahal model tersebut sama sekali tidak berguna. Untuk kasus semacam ini, gunakan F1-score, precision, recall, atau AUC-ROC sebagai metrik utama. Accuracy hanya cocok ketika distribusi kelas cukup seimbang (paling tidak 70:30).
Tips 4: Pakai random_state Konsisten di Seluruh Eksperimen
Setiap kali Abbas menjalankan ulang kode, hasilnya bisa berbeda karena komponen acak (random forest, train/test split, dll). Tetapkan random_state=42 di semua fungsi yang menerimanya agar eksperimen bisa direproduksi dan dibandingkan dengan adil. Ini bukan takhayul angka 42, hanya konvensi yang dipakai luas di komunitas ML.
Jebakan 5: Hyperparameter Tuning Sebelum Feature Engineering Selesai
Banyak pemula langsung melakukan GridSearch saat model pertama kurang bagus. Padahal urutan yang benar: (1) perbaiki data dan fitur dulu, (2) coba beberapa algoritma, (3) baru tuning hyperparameter pada algoritma terbaik. Tuning model dengan fitur buruk tidak akan memberikan peningkatan signifikan karena batas maksimalnya memang sudah rendah.
Tips 6: Gunakan Pipeline untuk Mencegah Data Leakage Otomatis
sklearn Pipeline memastikan semua langkah preprocessing (imputer, scaler, encoder) hanya di-fit pada training data, bahkan saat menggunakan cross-validation. Ini cara paling aman dan bersih untuk membangun workflow ML karena semua operasi terkapsulasi dalam satu objek.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

pipe = Pipeline([
    ('scaler', StandardScaler()),     # fit hanya pada training fold
    ('model', LogisticRegression())
])
scores = cross_val_score(pipe, X, y, cv=5, scoring='f1')
# Pipeline otomatis menangani fit/transform per fold dengan benar
print(f"CV F1: {scores.mean():.3f} +/- {scores.std():.3f}")
Jebakan 7: Melihat Test Set Terlalu Sering
Test set adalah "ujian final" yang idealnya hanya dilihat sekali: setelah model selesai dibangun dan di-tuning. Jika Abbas melihat test set berkali-kali untuk memperbaiki model, secara tidak sadar model menjadi "hafal" distribusi test set. Gunakan validation set atau cross-validation untuk semua keputusan pengembangan model.
Tips 8: Feature Importance Bukan Pengganti Domain Knowledge
Random Forest bisa memberi tahu fitur mana yang paling penting untuk model, tapi angka itu bisa menyesatkan jika ada fitur yang berkorelasi tinggi satu sama lain. Selalu validasi dengan domain expert: "Apakah masuk akal secara bisnis bahwa fitur ini yang paling penting?"
import pandas as pd
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

importance_df = pd.DataFrame({
    'fitur': feature_names,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)

print(importance_df.head(10).to_string(index=False))
# Selalu tanya: apakah urutan ini masuk akal secara bisnis?

Latihan Tambahan: Kasus Nyata Konteks Indonesia

Latihan berikut menggunakan konteks Indonesia agar Abbas bisa mengkoneksikan skill ML dengan permasalahan yang relevan secara lokal. Setiap latihan dirancang lebih menantang dari contoh di minggu 1-4 karena melibatkan data simulasi yang lebih realistik, keputusan metrik yang tidak trivial, dan interpretasi bisnis.

Latihan A

Prediksi Harga Kos-Kosan di Kota Besar Indonesia (Regresi End-to-End)

Abbas diminta memprediksi harga sewa bulanan kos-kosan di Jakarta, Bandung, dan Surabaya berdasarkan fitur seperti luas kamar, fasilitas, jarak ke kampus, dan tipe kos. Latihan ini melatih pipeline regresi lengkap termasuk handling fitur kategorikal dan numerik sekaligus.

import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_absolute_error, r2_score

np.random.seed(42)
n = 500

# Dataset simulasi kos-kosan Indonesia
kota      = np.random.choice(['Jakarta', 'Bandung', 'Surabaya'], n)
tipe      = np.random.choice(['Putra', 'Putri', 'Campur'], n)
fasilitas = np.random.choice(['AC+WiFi', 'WiFi saja', 'Standar'], n)
luas_m2   = np.random.normal(12, 3, n).clip(6, 25)
jarak_km  = np.random.exponential(2, n).clip(0.1, 10)
lantai    = np.random.randint(1, 5, n)

# Harga dasar berdasarkan kota (dalam ribu rupiah per bulan)
base = {'Jakarta': 1500, 'Bandung': 900, 'Surabaya': 800}
harga = np.array([base[k] for k in kota], dtype=float)
harga += luas_m2 * 50
harga -= jarak_km * 80
harga += (fasilitas == 'AC+WiFi').astype(float) * 400
harga += (fasilitas == 'WiFi saja').astype(float) * 150
harga += np.random.normal(0, 150, n)
harga = harga.clip(300, 5000)

df = pd.DataFrame({
    'kota': kota, 'tipe': tipe, 'fasilitas': fasilitas,
    'luas_m2': luas_m2, 'jarak_km': jarak_km,
    'lantai': lantai, 'harga_ribu': harga
})

X = df.drop('harga_ribu', axis=1)
y = df['harga_ribu']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

num_features = ['luas_m2', 'jarak_km', 'lantai']
cat_features = ['kota', 'tipe', 'fasilitas']

preprocessor = ColumnTransformer([
    ('num', Pipeline([
        ('imp', SimpleImputer(strategy='median')),
        ('scl', StandardScaler())
    ]), num_features),
    ('cat', OneHotEncoder(handle_unknown='ignore'), cat_features)
])

pipeline = Pipeline([
    ('prep', preprocessor),
    ('model', GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, random_state=42))
])

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

mae = mean_absolute_error(y_test, y_pred)
r2  = r2_score(y_test, y_pred)
print(f"MAE: Rp {mae:.0f} ribu/bulan")
print(f"R-squared: {r2:.3f}")

# Cek apakah error model merata antar kota (fairness check)
for kota_nama in ['Jakarta', 'Bandung', 'Surabaya']:
    mask = X_test['kota'] == kota_nama
    if mask.any():
        mae_kota = mean_absolute_error(y_test[mask], y_pred[mask])
        print(f"  MAE {kota_nama}: Rp {mae_kota:.0f} ribu")
Tantangan Abbas: Tambahkan fitur interaksi antara kota dan fasilitas secara manual sebagai kolom baru sebelum split. Apakah R-squared meningkat? Lalu buat prediksi untuk kos fiktif: Bandung, tipe Putri, AC+WiFi, 14m2, 1.5km dari kampus, lantai 2. Berapa estimasi harganya? Apakah terasa masuk akal?
Latihan B

Deteksi Transaksi Mencurigakan di Fintech Indonesia (Klasifikasi Imbalanced)

Ini mensimulasikan masalah nyata yang dihadapi startup fintech Indonesia: 98% transaksi normal, 2% berpotensi fraud. Abbas harus memilih strategi yang tepat untuk kelas yang sangat tidak seimbang. Kasus ini memaksa Abbas memikirkan metrik evaluasi secara kritis, bukan hanya menerima accuracy.

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

np.random.seed(42)
n_normal = 9800
n_fraud  = 200  # hanya 2% fraud

# Simulasi transaksi normal (pola aktivitas siang hari, jumlah wajar)
normal = pd.DataFrame({
    'jumlah_rp':        np.random.lognormal(13, 1.5, n_normal),
    'jam':              np.random.choice(range(8, 22), n_normal),
    'frekuensi_harian': np.random.poisson(2, n_normal),
    'selisih_lokasi_km': np.random.exponential(5, n_normal),
    'label': 0
})

# Simulasi fraud (jumlah besar, tengah malam, frekuensi tinggi, lokasi jauh)
fraud = pd.DataFrame({
    'jumlah_rp':        np.random.lognormal(15, 2, n_fraud),
    'jam':              np.random.choice(range(0, 6), n_fraud),
    'frekuensi_harian': np.random.poisson(15, n_fraud),
    'selisih_lokasi_km': np.random.exponential(200, n_fraud),
    'label': 1
})

df = pd.concat([normal, fraud]).sample(frac=1, random_state=42).reset_index(drop=True)
X  = df.drop('label', axis=1)
y  = df['label']

print(f"Proporsi fraud: {y.mean():.2%}")

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# Model 1: tanpa penanganan imbalanced
pipe_default = Pipeline([
    ('scl', StandardScaler()),
    ('clf', RandomForestClassifier(n_estimators=100, random_state=42))
])
pipe_default.fit(X_train, y_train)

# Model 2: dengan class_weight='balanced'
pipe_balanced = Pipeline([
    ('scl', StandardScaler()),
    ('clf', RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42))
])
pipe_balanced.fit(X_train, y_train)

print("\n--- Model Default ---")
print(classification_report(y_test, pipe_default.predict(X_test),
      target_names=['Normal', 'Fraud']))

print("--- Model Balanced ---")
print(classification_report(y_test, pipe_balanced.predict(X_test),
      target_names=['Normal', 'Fraud']))

# AUC-ROC: metrik paling tepat untuk data imbalanced
auc_d = roc_auc_score(y_test, pipe_default.predict_proba(X_test)[:,1])
auc_b = roc_auc_score(y_test, pipe_balanced.predict_proba(X_test)[:,1])
print(f"AUC-ROC Default : {auc_d:.4f}")
print(f"AUC-ROC Balanced: {auc_b:.4f}")
Tantangan Abbas: Dari output classification_report, bandingkan recall kelas Fraud antara kedua model. Recall fraud berarti: dari semua transaksi fraud yang sesungguhnya ada, berapa persen yang berhasil terdeteksi? Di industri fintech, recall fraud lebih kritis dari precision. Mengapa? Coba juga ganti threshold dari 0.5 ke 0.3 menggunakan predict_proba dan lihat bagaimana trade-off precision vs recall berubah.
Latihan C

Prediksi Siswa Berisiko Dropout (Pipeline Lengkap + Interpretasi Konteks Sekolah)

Abbas memprediksi siswa SMA mana yang berisiko tidak melanjutkan sekolah berdasarkan data akademik dan sosial. Latihan ini relevan secara personal dan melatih kemampuan membandingkan beberapa algoritma secara terstruktur dalam satu pipeline yang bersih.

import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
import warnings
warnings.filterwarnings('ignore')

np.random.seed(42)
n = 800

df = pd.DataFrame({
    'nilai_rata':         np.random.normal(72, 15, n).clip(30, 100),
    'absensi_persen':     np.random.exponential(8, n).clip(0, 60),
    'jarak_sekolah_km':  np.random.exponential(3, n).clip(0.5, 25),
    'pendapatan_ortu':    np.random.choice(['rendah', 'menengah', 'tinggi'], n,
                                            p=[0.45, 0.40, 0.15]),
    'ekskul_aktif':       np.random.randint(0, 4, n),
    'punya_hp':           np.random.choice([0, 1], n, p=[0.3, 0.7]),
    'kelas':              np.random.choice(['X', 'XI', 'XII'], n)
})

# Probabilitas dropout berdasarkan kombinasi faktor risiko
risiko = (
    df['nilai_rata'].lt(60).astype(int) * 2 +
    df['absensi_persen'].gt(20).astype(int) * 3 +
    df['pendapatan_ortu'].eq('rendah').astype(int) * 2 +
    df['jarak_sekolah_km'].gt(10).astype(int) +
    df['ekskul_aktif'].eq(0).astype(int)
)
prob_dropout = 1 / (1 + np.exp(-(risiko - 4)))
df['dropout'] = (np.random.random(n) < prob_dropout).astype(int)
print(f"Proporsi siswa dropout: {df['dropout'].mean():.2%}")

X = df.drop('dropout', axis=1)
y = df['dropout']

num_cols = ['nilai_rata', 'absensi_persen', 'jarak_sekolah_km', 'ekskul_aktif', 'punya_hp']
cat_cols = ['pendapatan_ortu', 'kelas']

preprocessor = ColumnTransformer([
    ('num', Pipeline([
        ('imp', SimpleImputer(strategy='median')),
        ('scl', StandardScaler())
    ]), num_cols),
    ('cat', Pipeline([
        ('imp', SimpleImputer(strategy='most_frequent')),
        ('enc', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
    ]), cat_cols)
])

models = {
    'Logistic Regression': LogisticRegression(max_iter=1000),
    'Decision Tree':        DecisionTreeClassifier(max_depth=5, random_state=42),
    'Random Forest':        RandomForestClassifier(n_estimators=100, random_state=42)
}

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
print(f"\n{'Model':<25} {'F1 Mean':>10} {'F1 Std':>10} {'Recall':>10}")
print("-" * 58)
for nama, clf in models.items():
    pipe = Pipeline([('prep', preprocessor), ('clf', clf)])
    f1  = cross_val_score(pipe, X, y, cv=skf, scoring='f1')
    rec = cross_val_score(pipe, X, y, cv=skf, scoring='recall')
    print(f"{nama:<25} {f1.mean():>10.3f} {f1.std():>10.3f} {rec.mean():>10.3f}")
Tantangan Abbas: Recall di sini berarti: dari semua siswa yang akan dropout, berapa persen yang berhasil terdeteksi lebih awal? Pilih model dengan recall terbaik, latih ulang pada seluruh data, dan cetak 10 siswa dengan probabilitas dropout tertinggi menggunakan predict_proba. Apa karakteristik umum siswa berisiko tinggi ini? Tulis 2-3 kalimat analisis sebagai rekomendasi program intervensi kepada pihak sekolah.
Latihan D

Simpan, Load, dan Gunakan Model untuk Prediksi Baru (Fondasi Deployment)

Membangun model saja tidak cukup. Abbas harus bisa menyimpannya dan menggunakannya kembali tanpa melatih ulang dari nol. Kemampuan ini adalah fondasi deployment ML sederhana dan dibutuhkan di hampir setiap proyek dunia nyata.

import joblib
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

# Langkah 1: Latih pipeline seperti biasa
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
feature_names = X.columns.tolist()

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model',  RandomForestClassifier(n_estimators=100, random_state=42))
])
pipeline.fit(X_train, y_train)

print("Evaluasi pada test set:")
print(classification_report(y_test, pipeline.predict(X_test),
      target_names=['Malignant', 'Benign']))

# Langkah 2: Simpan seluruh pipeline ke satu file
joblib.dump(pipeline, 'model_diagnostik.pkl')
print("Pipeline disimpan ke model_diagnostik.pkl")

# Langkah 3: Hapus dari memori, simulasi sesi baru
del pipeline

# Langkah 4: Load model yang sudah disimpan
pipeline_loaded = joblib.load('model_diagnostik.pkl')
print("Model berhasil di-load. Siap digunakan tanpa training ulang.")

# Langkah 5: Prediksi sampel baru (simulasi pasien baru datang)
sampel_baru = X_test.iloc[[0]]
prediksi    = pipeline_loaded.predict(sampel_baru)[0]
prob        = pipeline_loaded.predict_proba(sampel_baru)[0]

label_map = {0: 'Malignant (ganas)', 1: 'Benign (jinak)'}
print(f"\nHasil prediksi sampel baru:")
print(f"  Kelas          : {label_map[prediksi]}")
print(f"  Prob Malignant : {prob[0]:.2%}")
print(f"  Prob Benign    : {prob[1]:.2%}")
print(f"  Label asli     : {label_map[y_test.iloc[0]]}")
Tantangan Abbas: Gunakan pipeline yang sudah disimpan dari Latihan A (kos-kosan). Load kembali dengan joblib.load, lalu buat fungsi prediksi_kos(kota, tipe, fasilitas, luas_m2, jarak_km, lantai) yang menerima input dan mengembalikan prediksi harga dalam format "Rp X.XXX.000/bulan". Uji dengan 5 skenario kos fiktif berbeda yang Abbas tentukan sendiri. Apakah hasil prediksi terasa masuk akal dibandingkan harga kos di kota tersebut yang Abbas ketahui?