---
jupyter: decanal
---
# Integrasi & Proyek Akhir {#sec-integrasi}
::: {.callout-note appearance="simple"}
**Tujuan pembelajaran.** Setelah menyelesaikan bab ini, kita akan mampu:
1. Menjalankan **proyek decision analytics end-to-end** dari masalah bisnis hingga rekomendasi kebijakan.
2. Mengintegrasikan **prediksi** (Bagian II) dengan **optimasi** (Bagian III) dalam satu alur kerja.
3. Menggunakan **simulasi** untuk mengevaluasi keputusan di bawah ketidakpastian.
4. Menyusun **dashboard eksekutif** dan **laporan rekomendasi** yang dapat digunakan pengambil keputusan.
5. Merancang **rencana deployment** dan **monitoring** untuk model yang diimplementasikan.
6. Merefleksikan perjalanan analitis dari data ke keputusan—dan batasannya.
:::
## Skenario: Rapat Direksi Telko Nusantara
Bayangkan kita telah menyelesaikan analisis dari Bab 1–17. Sekarang, Direktur Utama memanggil kita ke **rapat direksi triwulanan**. Agenda rapat: **strategi analytics untuk 12 bulan ke depan**.
Direktur Utama membuka dengan:
> *"Kita telah menginvestasikan waktu dan sumber daya untuk membangun kapabilitas analytics. Sekarang saya ingin tahu: **apa yang telah kita pelajari, apa yang harus kita lakukan, dan berapa nilainya?** Beri saya rekomendasi yang dapat saya bawa ke pemegang saham."*
kita memiliki **satu jam** di rapat direksi. kita tidak bisa membahas 18 bab—kita harus **menyaring** semua analisis menjadi **tiga slide**:
1. **Apa yang kita ketahui** (descriptive + diagnostic + predictive).
2. **Apa yang harus kita lakukan** (prescriptive).
3. **Berapa nilainya** (simulation + expected value).
Ini adalah **ujian sesungguhnya** dari seluruh buku: bukan apakah kita bisa membuat model, tetapi apakah kita bisa **mengubah model menjadi keputusan**.
::: {.callout-important}
**Ini bukan latihan akademis.** Dalam pekerjaan nyata, analis yang baik bukan yang membuat model paling canggih—tetapi yang **paling efektif mengomunikasikan** insight dan rekomendasi. Bab ini menguji kemampuan itu.
:::
## Ringkasan Perjalanan: Dari Data ke Keputusan
Sebelum masuk ke analisis baru, mari kita **rekap** apa yang telah kita bangun:
| Bagian | Bab | Kemampuan | Output |
|---|---|---|---|
| **I** | 1–2 | Wrangling | Dataset bersih |
| **I** | 3–4 | Statistik & visualisasi | Insight deskriptif |
| **I** | 5–6 | Diagnosis & storytelling | Diagnosis akar masalah |
| **II** | 7–9 | Klasifikasi | Probabilitas churn per pelanggan |
| **II** | 10 | Time series & survival | Forecast & hazard model |
| **II** | 11 | Unsupervised | Segmen & association rules |
| **II** | 12 | Monte Carlo | Distribusi ketidakpastian |
| **III** | 13 | Decision framework | Payoff table & EVPI |
| **III** | 14 | Linear programming | Alokasi optimal |
| **III** | 15 | Integer/binary programming | Keputusan diskrit |
| **III** | 16 | Network & multi-objective | Aliran & Pareto |
| **III** | 17 | Simulation-optimization | Keputusan robust |
**Sekarang kita akan merangkai semuanya dalam satu alur.**
## Fase 1: Business Understanding
### Pertanyaan Strategis
Direksi memiliki **tiga pertanyaan** yang harus dijawab:
1. **Churn**: Berapa pelanggan yang akan churn tahun depan, dan bagaimana kita menguranginya?
2. **Retensi**: Dengan anggaran terbatas, pelanggan mana yang harus diintervensi—dan dengan intervensi apa?
3. **Dampak**: Berapa nilai finansial dari rekomendasi ini—dan seberapa yakin kita?
### Definisi Keberhasilan
Sebelum analisis, kita tetapkan **metrik keberhasilan**:
| Metrik | Target | Cara ukur |
|---|---|---|
| **Churn rate** | Turun dari 26,5% menjadi <20% | Proporsi pelanggan churn dalam 12 bulan |
| **Revenue retention** | Menyelamatkan ≥Rp 3 miliar | Total LTV pelanggan yang berhasil dipertahankan |
| **ROI program** | ≥3× | (Net benefit) / (Biaya intervensi) |
| **Coverage** | ≥1.500 pelanggan | Jumlah pelanggan yang diintervensi |
## Fase 2: Descriptive & Diagnostic — Mengingat Kembali
Dari Bab 3–6, kita telah mengetahui:
```{python}
#| label: setup
import sys
sys.path.insert(0, ".")
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
from decanal.utils import load_churn_clean, load_retail
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score, precision_score, recall_score
from pulp import (
LpProblem, LpVariable, LpMaximize, LpStatus, lpSum, value, PULP_CBC_CMD, LpBinary,
)
np.random.seed(42)
df = load_churn_clean()
```
```{python}
#| label: descriptive-recap
# Rangkuman deskriptif
n_total = len(df)
n_churn = (df["Churn"] == "Yes").sum()
churn_rate = n_churn / n_total * 100
avg_monthly = df["MonthlyCharges"].mean()
print("=== RINGKASAN DESKRIPTIF ===")
print(f"Total pelanggan : {n_total:,}")
print(f"Churn rate : {churn_rate:.1f}% ({n_churn:,} pelanggan)")
print(f"Tagihan bulanan rata-rata: Rp {avg_monthly:,.0f}")
print(f"Estimasi revenue hilang tahunan: "
f"Rp {n_churn * avg_monthly * 12 / 1e9:.2f} miliar")
# Diagnosis kunci
print("\n=== DIAGNOSIS KUNCI ===")
churn_by_contract = df.groupby("Contract")["Churn"].apply(
lambda s: (s == "Yes").mean() * 100
).round(1)
print("Churn rate per kontrak:")
for k, v in churn_by_contract.items():
print(f" {k:15s}: {v}%")
# Segmen berisiko
high_risk = df[(df["Contract"] == "Month-to-month") & (df["tenure"] <= 12)]
print(f"\nSegmen 'Bulanan Baru' (bulanan + tenure ≤12 bln):")
print(f" Jumlah pelanggan: {len(high_risk):,}")
print(f" Churn rate : {(high_risk['Churn']=='Yes').mean()*100:.1f}%")
print(f" Kontribusi churn: {(high_risk['Churn']=='Yes').sum()/n_churn*100:.1f}% dari total churn")
```
**Insight yang disampaikan ke direksi:**
> *"Churn rate kita 26,5%—sekitar 1 dari 4 pelanggan berhenti setiap tahun. Namun churn ini **sangat terkonsentrasi**: pelanggan kontrak bulanan dengan tenure ≤12 bulan memiliki churn rate ~53%, dan menyumbang **hampir separuh** dari total churn meskipun hanya ~20% dari basis pelanggan."*
## Fase 3: Predictive — Model Churn
Dari Bab 9, kita membangun model klasifikasi. Untuk proyek ini, kita menggunakan **Gradient Boosting**—model dengan performa terbaik dari perbandingan di Bab 9.
```{python}
#| label: predictive-model
# Siapkan fitur & target
y = (df["Churn"] == "Yes").astype(int)
X = df.drop(columns=["Churn"])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Preprocessing
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),
])
# Model final
final_model = Pipeline([
("prep", preprocessor),
("clf", GradientBoostingClassifier(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42
)),
])
final_model.fit(X_train, y_train)
# Evaluasi
y_prob_test = final_model.predict_proba(X_test)[:, 1]
y_pred_test = (y_prob_test >= 0.5).astype(int)
print("=== PERFORMA MODEL FINAL ===")
print(f"Test AUC : {roc_auc_score(y_test, y_prob_test):.3f}")
print(f"Precision : {precision_score(y_test, y_pred_test):.3f}")
print(f"Recall : {recall_score(y_test, y_pred_test):.3f}")
```
```{python}
#| label: score-all
# Skor untuk SEMUA pelanggan (dasar untuk optimasi)
df["prob_churn_pred"] = final_model.predict_proba(X)[:, 1]
df["ltv_estimate"] = df["MonthlyCharges"] * 12 # annualized, sederhana
df["expected_loss"] = df["prob_churn_pred"] * df["ltv_estimate"]
print(f"Total expected loss tahunan: Rp {df['expected_loss'].sum()/1e9:.2f} miliar")
print(f"Pelanggan dengan prob churn > 0.5: {(df['prob_churn_pred'] > 0.5).sum():,}")
```
**Insight untuk direksi:**
> *"Model kami mencapai **AUC 0,88**—artinya model mampu membedakan pelanggan berisiko tinggi dari rendah dengan sangat baik. Namun **perhatikan:** model memprediksi bahwa **sekitar 1.900 pelanggan** memiliki probabilitas churn >50%, dengan total expected loss **Rp 2,9 miliar** tahun ini."*
## Fase 4: Prescriptive — Optimasi Alokasi
Sekarang kita gunakan **binary programming** (Bab 15) untuk memilih pelanggan yang akan diintervensi.
```{python}
#| label: intervention-params
# Parameter intervensi
# Tiga opsi: email, diskon, kontrak tahunan
BIAYA_EMAIL = 20_000
EFEKTIVITAS_EMAIL = 0.10
BIAYA_DISKON = 200_000
EFEKTIVITAS_DISKON = 0.40
# Anggaran dan kapasitas
BUDGET = 200_000_000 # Rp 200 juta
CAPACITY = 1_500 # 1.500 pelanggan
# Hitung net benefit per pelanggan untuk masing-masing intervensi
df["expected_benefit_email"] = (
df["prob_churn_pred"] * EFEKTIVITAS_EMAIL * df["ltv_estimate"] - BIAYA_EMAIL
)
df["expected_benefit_diskon"] = (
df["prob_churn_pred"] * EFEKTIVITAS_DISKON * df["ltv_estimate"] - BIAYA_DISKON
)
# Hanya pertimbangkan pelanggan dengan benefit positif (email)
kandidat = df[df["expected_benefit_email"] > 0].reset_index(drop=True)
print(f"Pelanggan kandidat (benefit email positif): {len(kandidat)}")
print(f"Pelanggan dengan benefit diskon positif : "
f"{(df['expected_benefit_diskon'] > 0).sum()}")
```
```{python}
#| label: diagnose-integration
print("=== DIAGNOSTIK SEBELUM OPTIMASI ===")
print(f"Total pelanggan : {len(df):,}")
print(f"Pelanggan prob_churn > 0.4: {(df['prob_churn_pred'] > 0.4).sum()}")
print(f"Pelanggan benefit_email > 0: {(df['expected_benefit_email'] > 0).sum()}")
print(f"Pelanggan benefit_diskon > 0: {(df['expected_benefit_diskon'] > 0).sum()}")
# Kandidat dengan filter yang akan dipakai
kandidat_check = df[
(df["prob_churn_pred"] > 0.4) &
(df["expected_benefit_email"] > 0)
]
print(f"\nKandidat setelah filter : {len(kandidat_check)}")
print(f"Statistik prob_churn : min={df['prob_churn_pred'].min():.3f}, "
f"mean={df['prob_churn_pred'].mean():.3f}, max={df['prob_churn_pred'].max():.3f}")
print(f"Statistik benefit_email : min={df['expected_benefit_email'].min():,.0f}, "
f"max={df['expected_benefit_email'].max():,.0f}")
# Cek manual apakah ada pelanggan yang memenuhi filter
if len(kandidat_check) == 0:
print("\n⚠️ KANDIDAT KOSONG — inilah penyebab utama!")
print(" Filter terlalu ketat. Coba turunkan threshold.")
```
```{python}
#| label: prescriptive-lp
from pulp import LpStatus, value
# Filter adaptif: jika kandidat kosong, turunkan threshold
THRESHOLD_CHURN = 0.4
kandidat = df[
(df["prob_churn_pred"] > THRESHOLD_CHURN) &
(df["expected_benefit_email"] > 0)
].reset_index(drop=True)
# Fallback: jika kosong, turunkan threshold
if len(kandidat) == 0:
THRESHOLD_CHURN = 0.3
kandidat = df[
(df["prob_churn_pred"] > THRESHOLD_CHURN) &
(df["expected_benefit_email"] > 0)
].reset_index(drop=True)
print(f"⚠️ Threshold diturunkan ke {THRESHOLD_CHURN} → kandidat: {len(kandidat)}")
# Fallback terakhir: ambil top-N berdasarkan prob_churn
if len(kandidat) == 0:
kandidat = df.nlargest(500, "prob_churn_pred").reset_index(drop=True)
print(f"⚠️ Fallback: top-500 pelanggan berdasarkan prob_churn")
print(f"Kandidat final: {len(kandidat)} pelanggan")
# ============================================================
# Model Binary Programming
# ============================================================
model = LpProblem("alokasi_integrasi", LpMaximize)
x_email = LpVariable.dicts("email", kandidat.index.tolist(), cat=LpBinary)
x_diskon = LpVariable.dicts("diskon", kandidat.index.tolist(), cat=LpBinary)
model += lpSum(
kandidat.loc[i, "expected_benefit_email"] * x_email[i] +
kandidat.loc[i, "expected_benefit_diskon"] * x_diskon[i]
for i in kandidat.index
), "Total_Net_Benefit"
for i in kandidat.index:
model += x_email[i] + x_diskon[i] <= 1, f"One_{i}"
model += lpSum(
BIAYA_EMAIL * x_email[i] + BIAYA_DISKON * x_diskon[i]
for i in kandidat.index
) <= BUDGET, "Anggaran"
model += lpSum(
x_email[i] + x_diskon[i] for i in kandidat.index
) <= CAPACITY, "Kapasitas"
model.solve(PULP_CBC_CMD(msg=False, timeLimit=60))
# ============================================================
# Penanganan Hasil yang Robust
# ============================================================
status = LpStatus[model.status]
print(f"\n=== HASIL OPTIMASI ===")
print(f"Status solver : {status}")
print(f"Jumlah variabel : {len(model.variables())}")
print(f"Jumlah kendala : {len(model.constraints)}")
if status == "Optimal" and len(kandidat) > 0:
# --- Hitung total benefit secara manual (paling robust) ---
selected_email_idx = [i for i in kandidat.index
if value(x_email[i]) is not None and value(x_email[i]) > 0.5]
selected_diskon_idx = [i for i in kandidat.index
if value(x_diskon[i]) is not None and value(x_diskon[i]) > 0.5]
total_benefit = (
kandidat.loc[selected_email_idx, "expected_benefit_email"].sum() +
kandidat.loc[selected_diskon_idx, "expected_benefit_diskon"].sum()
)
n_email = len(selected_email_idx)
n_diskon = len(selected_diskon_idx)
total_cost = n_email * BIAYA_EMAIL + n_diskon * BIAYA_DISKON
print(f"Total net benefit: Rp {total_benefit:,.0f}")
print(f"Total biaya : Rp {total_cost:,.0f}")
if total_cost > 0:
print(f"ROI : {total_benefit/total_cost:.2f}×")
print(f"\nAlokasi intervensi:")
print(f" Email : {n_email} pelanggan")
print(f" Diskon : {n_diskon} pelanggan")
print(f" Total : {n_email + n_diskon} pelanggan")
else:
print(f"\n⚠️ Solusi tidak optimal atau kandidat kosong.")
print(f" Kandidat: {len(kandidat)}")
print(f" Status : {status}")
# Set default untuk digunakan downstream
total_benefit = 0
n_email = 0
n_diskon = 0
total_cost = 0
print(f" → Menggunakan default (tidak ada intervensi)")
```
::: {.callout-warning}
**Integer programming dengan ribuan variabel bisa gagal diselesaikan.** Dengan
7.000 pelanggan kandidat dan 2 variabel binary per pelanggan (14.000 variabel),
CBC solver bisa kehabisan waktu. **Strategi praktis:**
1. **Filter kandidat** — hanya pelanggan dengan `prob_churn_pred > 0.4` dan
`expected_benefit > 0`. Ini mengurangi variabel dari 14.000 menjadi ~3.000.
2. **Tambahkan timeout** — `PULP_CBC_CMD(timeLimit=60)`. Jika solver tidak
selesai, gunakan solusi terbaik yang ditemukan (status mungkin "Not Solved",
tetapi `value()` masih bisa mengembalikan solusi incumbent).
3. **Selalu periksa status** sebelum mengakses `value()`. Status `Optimal`
menjamin solusi terbaik; status lain memerlukan penanganan khusus.
4. **Untuk masalah sangat besar**, gunakan solver komersial (Gurobi, CPLEX)
atau LP-relaxation + heuristic.
:::
**Insight untuk direksi:**
> *"Dengan anggaran **Rp 200 juta**, model kami merekomendasikan: **X pelanggan menerima email** (biaya rendah, efektivitas moderat) dan **Y pelanggan menerima diskon** (biaya tinggi, efektivitas tinggi). Total expected net benefit: **Rp Z miliar**, dengan ROI **W×**—artinya setiap Rp 1 yang diinvestasikan menghasilkan Rp W manfaat."*
## Fase 5: Simulation — Evaluasi di Bawah Ketidakpastian
Rekomendasi di atas **optimal di satu skenario**—tetapi parameter **tidak pasti**. Kita gunakan simulation-optimization (Bab 17) untuk mengevaluasi **robustness** dari rekomendasi ini.
```{python}
#| label: simulation
# Ambil alokasi optimal dari LP
selected_email = [i for i in kandidat.index if value(x_email[i]) > 0.5]
selected_diskon = [i for i in kandidat.index if value(x_diskon[i]) > 0.5]
# Simulasi: bangkitkan skenario probabilitas churn yang berbeda
N_SIM = 500
np.random.seed(42)
# Standard error untuk probabilitas churn (dari model, ~5-10%)
churn_se = 0.10
simulated_benefits = []
for _ in range(N_SIM):
# Bangkitkan probabilitas churn aktual dari distribusi sekitar prediksi
p_actual = np.clip(
np.random.normal(kandidat["prob_churn_pred"], churn_se), 0.01, 0.99
)
# Hitung benefit aktual untuk email
benefit_email = sum(
p_actual[i] * EFEKTIVITAS_EMAIL * kandidat.loc[i, "ltv_estimate"] - BIAYA_EMAIL
for i in selected_email
)
# Hitung benefit aktual untuk diskon
benefit_diskon = sum(
p_actual[i] * EFEKTIVITAS_DISKON * kandidat.loc[i, "ltv_estimate"] - BIAYA_DISKON
for i in selected_diskon
)
simulated_benefits.append(benefit_email + benefit_diskon)
simulated_benefits = np.array(simulated_benefits)
print("=== EVALUASI DI BAWAH KETIDAKPASTIAN ===")
print(f"Mean benefit : Rp {simulated_benefits.mean():,.0f}")
print(f"Std deviasi : Rp {simulated_benefits.std():,.0f}")
print(f"P5 (worst) : Rp {np.percentile(simulated_benefits, 5):,.0f}")
print(f"P95 (best) : Rp {np.percentile(simulated_benefits, 95):,.0f}")
print(f"Prob(rugi) : {(simulated_benefits < 0).mean()*100:.1f}%")
```
```{python}
#| label: fig-simulation-result
#| fig-cap: "Distribusi net benefit dari rekomendasi di bawah ketidakpastian. Sebagian besar skenario positif, tetapi ada ekor kiri yang perlu diwaspadai."
#| fig-width: 9
#| fig-height: 4.5
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.hist(simulated_benefits / 1e6, bins=40, color="#4C72B0", edgecolor="white", alpha=0.8)
ax.axvline(simulated_benefits.mean() / 1e6, color="red", linestyle="--",
label=f"Mean: Rp {simulated_benefits.mean()/1e6:.1f}M")
ax.axvline(np.percentile(simulated_benefits, 5) / 1e6, color="orange", linestyle=":",
label=f"P5: Rp {np.percentile(simulated_benefits, 5)/1e6:.1f}M")
ax.axvline(0, color="black", linestyle="-", alpha=0.5)
ax.set_xlabel("Net benefit (Rp juta)")
ax.set_ylabel("Frekuensi")
ax.set_title("Evaluasi Rekomendasi di Bawah Ketidakpastian")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
```
**Insight untuk direksi:**
> *"Rekomendasi kami memiliki **expected net benefit Rp X miliar**, tetapi **rentangnya** dari Rp Y (P5) hingga Rp Z (P95). Ada **W% kemungkinan** program ini merugi—risiko yang perlu diantisipasi dengan **cadangan anggaran** atau **intervensi tambahan**."*
## Fase 6: Executive Dashboard
Sekarang kita rangkum semuanya dalam **satu dashboard** untuk direksi:
```{python}
print("n_email :", n_email)
print("n_diskon :", n_diskon)
print("BIAYA_EMAIL :", BIAYA_EMAIL)
print("BIAYA_DISKON:", BIAYA_DISKON)
print(f"Values untuk pie: [{n_email * BIAYA_EMAIL}, {n_diskon * BIAYA_DISKON}]")
```
```{python}
#| label: fig-exec-dashboard
#| fig-cap: "Dashboard eksekutif: ringkasan analitik dan rekomendasi untuk rapat direksi."
#| fig-width: 12
#| fig-height: 6
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# ============================================================
# Guard: pastikan n_email dan n_diskon valid
# ============================================================
n_email_safe = int(n_email) if n_email is not None else 0
n_diskon_safe = int(n_diskon) if n_diskon is not None else 0
# Jika optimasi gagal (keduanya 0), gunakan fallback demonstratif
# berdasarkan filter sederhana: pelanggan dengan prob_churn > 0.5
if n_email_safe == 0 and n_diskon_safe == 0:
print("⚠️ Optimasi tidak menghasilkan alokasi — menggunakan fallback demonstratif")
high_risk = df[df["prob_churn_pred"] > 0.5]
n_email_safe = min(int(len(high_risk) * 0.6), 400) # 60% via email
n_diskon_safe = min(int(len(high_risk) * 0.1), 50) # 10% via diskon
print(f" Fallback: {n_email_safe} email, {n_diskon_safe} diskon")
# Hitung nilai untuk pie
email_budget = n_email_safe * BIAYA_EMAIL
diskon_budget = n_diskon_safe * BIAYA_DISKON
# ============================================================
# Dashboard
# ============================================================
fig = make_subplots(
rows=2, cols=2,
subplot_titles=(
"Churn Rate per Kontrak",
"Distribusi Probabilitas Churn",
f"Alokasi Anggaran Retensi (Total: Rp {(email_budget + diskon_budget)/1e6:.0f}M)",
"Distribusi Net Benefit (Simulasi)",
),
specs=[
[{"type": "bar"}, {"type": "histogram"}],
[{"type": "domain"}, {"type": "histogram"}],
],
)
# Panel 1: Churn rate per kontrak
churn_data = churn_by_contract.sort_values()
fig.add_trace(
go.Bar(x=churn_data.values, y=churn_data.index, orientation="h",
marker_color="#DD8452", text=churn_data.round(1),
texttemplate="%{text}%", textposition="outside", showlegend=False),
row=1, col=1,
)
# Panel 2: Distribusi probabilitas churn
fig.add_trace(
go.Histogram(x=df["prob_churn_pred"], nbinsx=30,
marker_color="#4C72B0", showlegend=False),
row=1, col=2,
)
# Panel 3: Alokasi anggaran — dengan guard
if email_budget + diskon_budget > 0:
fig.add_trace(
go.Pie(
labels=[f"Email ({n_email_safe})", f"Diskon ({n_diskon_safe})"],
values=[email_budget, diskon_budget],
marker={"colors": ["#55A868", "#DD8452"]},
textinfo="label+percent",
textposition="inside",
showlegend=False,
hovertemplate="<b>%{label}</b><br>Rp %{value:,.0f}<br>%{percent}<extra></extra>",
),
row=2, col=1,
)
else:
# Placeholder jika tidak ada anggaran
fig.add_trace(
go.Pie(labels=["Tidak ada alokasi"], values=[1],
marker={"colors": ["#CCCCCC"]},
textinfo="label", showlegend=False),
row=2, col=1,
)
# Panel 4: Distribusi net benefit
fig.add_trace(
go.Histogram(x=simulated_benefits / 1e6, nbinsx=40,
marker_color="#8172B3", showlegend=False),
row=2, col=2,
)
fig.update_xaxes(title_text="Churn rate (%)", row=1, col=1)
fig.update_xaxes(title_text="Probabilitas churn", row=1, col=2)
fig.update_xaxes(title_text="Net benefit (Rp juta)", row=2, col=2)
fig.update_layout(
height=600, width=1200,
title_text="Dashboard Eksekutif — Telko Nusantara Analytics Strategy",
margin=dict(l=40, r=40, t=80, b=40),
)
fig.show()
```
::: {.callout-tip}
**Dashboard harus tahan terhadap hasil kosong.** Dalam praktik, model optimasi
bisa gagal karena berbagai alasan—kandidat kosong, timeout, atau parameter yang
tidak realistis. Dashboard eksekutif **tidak boleh** menampilkan panel kosong
tanpa penjelasan.
**Praktik yang baik:**
1. Guard semua variabel dengan `if x is not None`.
2. Sediakan **placeholder** yang jelas ("Tidak ada alokasi") alih-alih panel kosong.
3. Tampilkan **diagnostik** di judul panel (mis. total anggaran) agar pembaca
langsung tahu apakah ada data.
4. Untuk demonstrasi buku, gunakan **fallback** berbasis aturan sederhana jika
optimasi gagal—tetapi beri label jelas bahwa itu fallback.
:::
## Fase 7: Rekomendasi untuk Direksi
### Tiga Rekomendasi Prioritas
| Prioritas | Rekomendasi | Target | Estimasi Dampak |
|---|---|---|---|
| **1** | Jalankan program intervensi berbasis model pada **X pelanggan** (email + diskon) | Pelanggan dengan probabilitas churn tertinggi | **Rp Z miliar** net benefit |
| **2** | Fokuskan **program onboarding** pada 12 bulan pertama | Semua pelanggan baru | Pengurangan churn dini 15–20% |
| **3** | Bangun **pipeline monitoring** untuk model—retrain setiap kuartal | Infrastruktur analitik | Model tetap akurat |
### Satu Halaman Eksekutif
> **Kepada:** Direksi Telko Nusantara
> **Dari:** Tim Analytics
> **Perihal:** Rekomendasi Strategi Retensi Berbasis Model
>
> **Temuan utama:** Churn 26,5% terkonsentrasi pada pelanggan bulanan baru. Model prediktif mengidentifikasi **X pelanggan** dengan probabilitas churn >50%, dengan expected loss **Rp 2,9 miliar**.
>
> **Rekomendasi:** Alokasikan **Rp 200 juta** untuk program intervensi pada **X pelanggan**. Expected net benefit **Rp Z miliar**, dengan ROI **W×**. Dalam 95% skenario simulasi, net benefit positif.
>
> **Risiko:** Probabilitas merugi 5% (P5 = Rp Y miliar). Mitigasi: cadangan anggaran Rp 20 juta dan monitoring kuartalan.
>
> **Keputusan yang diminta:** Persetujuan anggaran Rp 200 juta untuk pilot 6 bulan, dengan evaluasi sebelum scaling.
## Fase 8: Deployment & Monitoring
Rekomendasi tidak berhenti di presentasi—harus **diimplementasikan** dan **dipantau**.
### Rencana Deployment
| Tahap | Aktivitas | Timeline | Pemilik |
|---|---|---|---|
| **1** | Pilot pada 300 pelanggan | Bulan 1–2 | Tim retensi |
| **2** | Evaluasi A/B test | Bulan 3 | Analytics |
| **3** | Scaling jika efektif | Bulan 4–6 | Operasional |
| **4** | Full deployment | Bulan 7–12 | Gabungan |
### Monitoring Model
Model prediktif akan **menurun performanya** seiring waktu—karena perilaku pelanggan berubah, kompetitor baru muncul, atau kondisi ekonomi bergeser. Ini disebut **model drift**.
**Metrik yang harus dipantau:**
| Metrik | Target | Frekuensi | Tindakan jika melanggar |
|---|---|---|---|
| **AUC** | ≥0,85 | Bulanan | Retrain model |
| **Calibration error** | <0,05 | Bulanan | Rekalibrasi |
| **Churn rate aktual** | Sesuai prediksi | Kuartalan | Investigasi |
| **ROI intervensi** | ≥3× | Kuartalan | Sesuaikan parameter |
::: {.callout-tip}
**Prinsip MLOps: model bukan proyek sekali jadi.** Model prediktif adalah **produk hidup** yang harus:
- **Di-retrain** secara berkala (mis. setiap kuartal).
- **Dimonitor** performanya terhadap data baru.
- **Diperbarui** ketika ada perubahan struktural (mis. kompetitor baru).
Tanpa monitoring, model yang awalnya akurat bisa menjadi **tidak berguna** dalam 6–12 bulan—dan keputusan yang didasarkan padanya bisa menjadi **lebih buruk** daripada keputusan tanpa model.
:::
## Latihan Terbimbing
**Latihan 18.1 — Presentasi eksekutif.**
Buat presentasi **5 slide** untuk direksi berdasarkan analisis di bab ini. Slide harus mencakup: (a) masalah, (b) temuan utama, (c) rekomendasi, (d) risiko, (e) keputusan yang diminta. Setiap slide maksimal **satu grafik dan satu pesan**.
**Latihan 18.2 — Skenario alternatif.**
Apa yang terjadi jika anggaran dipotong menjadi **Rp 100 juta**? Selesaikan LP, hitung net benefit, dan bandingkan dengan skenario Rp 200 juta. Apakah **ROI berubah**? Apakah **coverage berubah**? Mana yang lebih penting untuk direksi?
**Latihan 18.3 — Analisis sensitivitas parameter.**
Variasikan efektivitas intervensi dari 0,20 hingga 0,60. Plot **net benefit** vs **efektivitas**. Pada efektivitas berapa **net benefit menjadi nol** (break-even)? Apa implikasi untuk desain program intervensi?
**Latihan 18.4 — Model drift simulation.**
Simulasikan **model drift**: asumsikan probabilitas churn aktual berbeda dari prediksi (mis. +10%, +20%). Berapa **degradasi AUC** dan **net benefit**? Pada tingkat drift berapa model perlu di-retrain?
**Latihan 18.5 — Integrasi dengan data retail.**
Gunakan dataset retail untuk memodelkan **penjualan perangkat** (proxy untuk cross-sell). Bagaimana alokasi anggaran berubah jika kita mempertimbangkan **nilai tambahan** dari penjualan perangkat ke pelanggan yang dipertahankan?
**Latihan 18.6 — Satu halaman eksekutif.**
Tulis **satu halaman** (300 kata) ringkasan eksekutif untuk direksi. Harus mencakup: (a) temuan, (b) rekomendasi, (c) dampak finansial, (d) risiko, (e) keputusan yang diminta. Gunakan **bahasa non-teknis**—jangan sebut "AUC" atau "LP", sebut "akurasi model" atau "optimasi anggaran".
::: {.callout-note collapse="true"}
**Petunjuk Latihan 18.1.** Untuk slide presentasi, gunakan prinsip dari Bab 6:
- **Satu slide, satu pesan.**
- **Grafik mendukung pesan**, bukan sebaliknya.
- **Angka besar, teks sedikit.**
- **Rekomendasi spesifik**, bukan "perlu dipertimbangkan".
Jika kita menggunakan Quarto, kita bisa membuat slide dengan `format: revealjs`.
:::
## Kotak "Jembatan": Dari Proyek ke Praktik
::: {.callout-important}
**Jembatan.** Proyek ini bukan akhir—ia adalah **awal dari praktik**:
1. **Setiap proyek menghasilkan pertanyaan baru.** Setelah mengimplementasikan retensi, muncul pertanyaan: *"Apakah pelanggan yang dipertahankan tetap loyal setelah 1 tahun?"* Ini membutuhkan **analisis lanjutan**—survival analysis (Bab 10) atau cohort analysis (Bab 5).
2. **Setiap model memiliki batas.** Model churn kita memprediksi **bulan depan**, bukan **tahun depan**. Setiap prediksi jangka panjang membutuhkan **model berbeda** dengan horizon yang sesuai.
3. **Setiap keputusan memiliki konsekuensi etis.** Apakah adil memberikan diskon hanya kepada pelanggan berisiko tinggi? Bagaimana dengan pelanggan setia yang tidak pernah mendapat diskon? Ini adalah pertanyaan **etika** yang tidak bisa dijawab oleh model—tetapi harus dipertimbangkan oleh pengambil keputusan.
**Decision analytics bukan tentang menemukan jawaban yang benar—ia tentang membuat keputusan yang lebih baik dengan informasi yang tersedia, dan terus belajar dari hasilnya.**
:::
## Refleksi Akhir: Perjalanan kita
kita telah menyelesaikan perjalanan dari **data mentah** hingga **rekomendasi kebijakan**. Mari kita lihat kembali apa yang telah kita kuasai:
**Keterampilan Teknis:**
- Python untuk analisis data (`pandas`, `numpy`, `matplotlib`, `seaborn`, `plotly`)
- Wrangling dan pembersihan data
- Statistik deskriptif dan inferensial
- Visualisasi data yang efektif
- Pemodelan prediktif (regresi, klasifikasi, time series, clustering)
- Simulasi Monte Carlo
- Optimasi linear, integer, dan nonlinear
- Simulation-optimization
- Deployment dan monitoring model
**Keterampilan Analitis:**
- Merumuskan masalah bisnis menjadi pertanyaan analitis
- Memilih teknik yang tepat untuk setiap masalah
- Menafsirkan hasil dalam konteks bisnis
- Mengukur ketidakpastian dan risiko
- Mengevaluasi trade-off antar tujuan
- Mengomunikasikan insight kepada audiens non-teknis
**Keterampilan Keputusan:**
- Membangun payoff table dan decision tree
- Menghitung expected value dan risk
- Mengukur nilai informasi
- Membuat rekomendasi yang dapat dieksekusi
- Merancang deployment dan monitoring
## Rangkuman
Dalam bab ini kita telah:
- **Menjalankan proyek decision analytics end-to-end**—dari masalah bisnis hingga rekomendasi kebijakan.
- **Mengintegrasikan prediksi** (Bab 9) dengan **optimasi** (Bab 15) dan **simulasi** (Bab 17) dalam satu alur kerja.
- **Menggunakan binary programming** untuk memilih intervensi optimal per pelanggan.
- **Mengevaluasi keputusan di bawah ketidakpastian** dengan simulasi Monte Carlo.
- **Merancang dashboard eksekutif** yang merangkum analisis dalam empat panel.
- **Menyusun rekomendasi** dengan estimasi dampak, ROI, dan risiko.
- **Merancang rencana deployment** dengan pilot, A/B test, dan scaling.
- **Memahami pentingnya monitoring model** dan deteksi drift.
- **Merefleksikan perjalanan analitis** dari data ke keputusan—dan batasannya.
## Penutup: Setelah Buku Ini
Buku ini berakhir, tetapi perjalanan analytics kita baru dimulai. Beberapa saran untuk melanjutkan:
1. **Kerjakan proyek nyata.** Ambil dataset dari pekerjaan atau komunitas, dan terapkan alur kerja dari buku ini. Pengalaman nyata mengajarkan hal yang tidak bisa diajarkan buku.
2. **Pelajari lebih dalam.** Setiap bab memiliki topik yang bisa diperdalam—misalnya, SHAP values untuk interpretasi model, causal inference untuk pertanyaan kausal, atau reinforcement learning untuk keputusan berurutan.
3. **Bangun portofolio.** Setiap proyek yang kita kerjakan—termasuk proyek di Bab 18—adalah bukti keterampilan kita. Dokumentasikan dengan baik di GitHub atau blog.
4. **Ajarkan.** Cara terbaik untuk benar-benar memahami sesuatu adalah mengajarkannya. Gunakan buku ini sebagai bahan untuk mengajar—dan kita akan menemukan celah dalam pemahaman kita sendiri.
5. **Tetap skeptis.** Analisis yang baik selalu mempertanyakan asumsi, menguji robustness, dan mengakui batasan. Jangan pernah percaya model—percayai **proses** yang membangun dan memvalidasinya.
---
> *"Essentially, all models are wrong, but some are useful."*
> — George E. P. Box
>
> *"The goal is to make better decisions, not perfect models."*
> — Prinsip decision analytics
**Selamat berkarya.**