Decision Tree Induction (Pohon Keputusan)
Konsep klasifikasi terarah, entropy, information gain (ID3), gain ratio (C4.5), Gini impurity (CART), split atribut numerik, overfitting dan pruning.
Notebook lab (pratinjau)
Unduh lalu jalankan di Jupyter, VS Code, atau Google Colab.
Lab Minggu 6 — Decision Tree Induction
Tujuan lab:
- Menghitung entropy, information gain, gain ratio, dan Gini pada dataset Play Tennis.
- Melatih pohon keputusan scikit-learn dan memvisualisasikan percabangannya.
- Melatih pohon pada data churn pelanggan, mengamati overfitting, dan melakukan pruning.
[1]
import sys
sys.path.insert(0, "..")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from common.datasets import play_tennis, churn_pelanggan
df = play_tennis()
df
1. Entropy & Gini
[2]
def entropy(y):
p = pd.Series(y).value_counts(normalize=True)
return -(p * np.log2(p)).sum()
def gini(y):
p = pd.Series(y).value_counts(normalize=True)
return 1 - (p ** 2).sum()
print(f"Entropy(play) = {entropy(df.play):.3f}")
print(f"Gini(play) = {gini(df.play):.3f}")
[3]
p = np.linspace(0.001, 0.999, 200)
plt.plot(p, -(p * np.log2(p) + (1 - p) * np.log2(1 - p)), label="entropy")
plt.plot(p, 1 - p**2 - (1 - p)**2, label="gini")
plt.xlabel("proporsi kelas positif"); plt.legend(); plt.title("Ukuran impurity (2 kelas)")
plt.show()
2. Information gain, gain ratio, gini split untuk tiap atribut
[4]
def info_gain(d, a, target="play"):
n = len(d)
return entropy(d[target]) - sum(len(g) / n * entropy(g[target]) for _, g in d.groupby(a))
def gain_ratio(d, a, target="play"):
return info_gain(d, a, target) / entropy(d[a])
def gini_split(d, a, target="play"):
n = len(d)
return sum(len(g) / n * gini(g[target]) for _, g in d.groupby(a))
tabel = pd.DataFrame({a: {"gain": info_gain(df, a), "gain_ratio": gain_ratio(df, a),
"gini_split": gini_split(df, a)} for a in df.columns[:-1]}).T
tabel.sort_values("gain", ascending=False).round(3)
outlook terpilih sebagai root. Rincian per nilai:
[5]
pd.crosstab(df.outlook, df.play).assign(
entropy=lambda t: [entropy(df[df.outlook == v].play) for v in t.index])
3. Rekursi: subset Sunny dan Rain
[6]
for v in ["Sunny", "Rain"]:
sub = df[df.outlook == v].drop(columns="outlook")
gains = {a: round(info_gain(sub, a), 3) for a in sub.columns[:-1]}
print(v, "->", gains)
4. Decision tree scikit-learn pada Play Tennis
[7]
from sklearn.tree import DecisionTreeClassifier, export_text, plot_tree
X = pd.get_dummies(df.drop(columns="play"), dtype=int)
y = df["play"]
pohon = DecisionTreeClassifier(criterion="entropy", random_state=0).fit(X, y)
print(export_text(pohon, feature_names=list(X.columns)))
[8]
plt.figure(figsize=(12, 6))
plot_tree(pohon, feature_names=X.columns, class_names=pohon.classes_, filled=True, rounded=True)
plt.show()
Perhatikan: scikit-learn (CART) membuat split biner pada kolom one-hot, misalnya
outlook_Overcast <= 0.5, berbeda dari ID3 yang bercabang multi.
5. Studi kasus: prediksi churn pelanggan
[9]
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
churn = churn_pelanggan()
Xc = pd.get_dummies(churn.drop(columns="churn"), dtype=int)
yc = churn["churn"]
X_tr, X_te, y_tr, y_te = train_test_split(Xc, yc, test_size=0.3, random_state=42, stratify=yc)
print(X_tr.shape, X_te.shape, "proporsi churn:", yc.mean().round(3))
[10]
penuh = DecisionTreeClassifier(random_state=0).fit(X_tr, y_tr)
print("Pohon penuh — kedalaman:", penuh.get_depth(), "daun:", penuh.get_n_leaves())
print(f" akurasi latih = {accuracy_score(y_tr, penuh.predict(X_tr)):.3f}")
print(f" akurasi uji = {accuracy_score(y_te, penuh.predict(X_te)):.3f}")
Overfitting: akurasi vs. kedalaman
[11]
hasil = []
for d in range(1, 21):
mdl = DecisionTreeClassifier(max_depth=d, random_state=0).fit(X_tr, y_tr)
hasil.append((d, accuracy_score(y_tr, mdl.predict(X_tr)), accuracy_score(y_te, mdl.predict(X_te))))
h = pd.DataFrame(hasil, columns=["max_depth", "latih", "uji"]).set_index("max_depth")
h.plot(marker="o", title="Akurasi vs. max_depth")
plt.show()
print("max_depth terbaik (uji):", h["uji"].idxmax())
Post-pruning dengan ccp_alpha
[12]
path = penuh.cost_complexity_pruning_path(X_tr, y_tr)
alphas = path.ccp_alphas[:-1]
skor = [accuracy_score(y_te, DecisionTreeClassifier(ccp_alpha=a, random_state=0)
.fit(X_tr, y_tr).predict(X_te)) for a in alphas]
terbaik = alphas[int(np.argmax(skor))]
plt.plot(alphas, skor, marker=".")
plt.xlabel("ccp_alpha"); plt.ylabel("akurasi uji"); plt.title("Post-pruning")
plt.show()
print(f"ccp_alpha terbaik = {terbaik:.5f}, akurasi uji = {max(skor):.3f}")
[13]
final = DecisionTreeClassifier(max_depth=4, random_state=0).fit(X_tr, y_tr)
plt.figure(figsize=(18, 7))
plot_tree(final, feature_names=Xc.columns, class_names=["stay", "churn"], filled=True,
rounded=True, fontsize=8)
plt.show()
[14]
pd.Series(final.feature_importances_, index=Xc.columns).sort_values().plot(
kind="barh", title="Feature importance (max_depth=4)")
plt.show()
✍️ Coba sendiri
- Latih pohon dengan
criterion="gini"vs"entropy"(max_depth=4). Apakah akurasinya berbeda? - Tambahkan kolom
id_pelangganunik ke data Play Tennis. Bandingkan information gain dan gain ratio-nya. - Ekstrak 3 aturan IF-THEN dari pohon churn
final(gunakanexport_text). Terjemahkan ke strategi retensi. - Lengkapi
id3()diexercise.pydan bandingkan pohon yang dihasilkan dengan materi.
[15]
# 1.
[16]
# 2.
Aturan & strategi no. 3:
- ...
Selanjutnya
Kerjakan kuis dan exercise.py, lalu jalankan python check.py 6 dari folder utama.