{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "87c4556d",
   "metadata": {},
   "source": [
    "# Lab Minggu 10 — K-Means, K-Medoids & Hierarchical Clustering\n",
    "\n",
    "**Tujuan lab:**\n",
    "1. Menjalankan K-Means langkah demi langkah dan melihat SSE turun.\n",
    "2. Menentukan k optimal dengan Elbow Method & Silhouette Score.\n",
    "3. Membandingkan K-Means vs K-Medoids saat ada outlier.\n",
    "4. Membuat dendrogram agglomerative clustering dengan berbagai linkage.\n",
    "5. Membuat profil segmen pelanggan (RFM) yang siap dipakai tim marketing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2174cade",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "sys.path.insert(0, \"..\")\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from sklearn.cluster import KMeans, AgglomerativeClustering\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.metrics import silhouette_score, silhouette_samples, adjusted_rand_score\n",
    "from scipy.cluster.hierarchy import linkage, dendrogram, fcluster\n",
    "\n",
    "from common.datasets import segmentasi_pelanggan"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa6a281e",
   "metadata": {},
   "source": [
    "## 1. K-Means langkah demi langkah (contoh materi)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c0783be",
   "metadata": {},
   "outputs": [],
   "source": [
    "X7 = np.array([[1, 1], [1.5, 2], [3, 4], [5, 7], [3.5, 5], [4.5, 5], [3.5, 4.5]])\n",
    "nama = list(\"ABCDEFG\")\n",
    "C = X7[[0, 2]].copy()   # centroid awal = A dan C\n",
    "\n",
    "for it in range(1, 4):\n",
    "    D = ((X7[:, None] - C[None]) ** 2).sum(axis=2)\n",
    "    label = D.argmin(axis=1)\n",
    "    print(f\"Iterasi {it}: klaster = {dict(zip(nama, label.tolist()))}\")\n",
    "    C_baru = np.array([X7[label == j].mean(axis=0) for j in range(2)])\n",
    "    sse = ((X7 - C_baru[label]) ** 2).sum()\n",
    "    print(f\"   centroid baru = {C_baru.round(3).tolist()}  SSE = {sse:.3f}\")\n",
    "    if np.allclose(C_baru, C):\n",
    "        print(\"   konvergen!\")\n",
    "        break\n",
    "    C = C_baru"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa36d425",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(X7[:, 0], X7[:, 1], c=label, cmap=\"coolwarm\", s=80)\n",
    "plt.scatter(C[:, 0], C[:, 1], marker=\"X\", s=200, c=\"k\")\n",
    "for n, (x, y) in zip(nama, X7):\n",
    "    plt.annotate(n, (x + 0.08, y + 0.08))\n",
    "plt.title(\"Hasil K-Means (k=2)\"); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e1c8e5c",
   "metadata": {},
   "source": [
    "## 2. Data segmentasi pelanggan (RFM)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b18baf72",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = segmentasi_pelanggan()\n",
    "fitur = [\"recency_hari\", \"frekuensi\", \"monetary_ribu\"]\n",
    "df[fitur].describe().round(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "299dbf7f",
   "metadata": {},
   "source": [
    "Skala sangat berbeda (monetary ribuan, frekuensi puluhan) → **z-score dulu**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7be38aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "Xs = StandardScaler().fit_transform(df[fitur])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6070b3fe",
   "metadata": {},
   "source": [
    "## 3. Elbow & Silhouette"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a02d204e",
   "metadata": {},
   "outputs": [],
   "source": [
    "ks = range(1, 10)\n",
    "sse = [KMeans(k, n_init=10, random_state=0).fit(Xs).inertia_ for k in ks]\n",
    "sil = [silhouette_score(Xs, KMeans(k, n_init=10, random_state=0).fit_predict(Xs)) for k in ks if k > 1]\n",
    "\n",
    "fig, ax = plt.subplots(1, 2, figsize=(11, 3.5))\n",
    "ax[0].plot(list(ks), sse, \"o-\"); ax[0].set_title(\"Elbow (SSE vs k)\"); ax[0].set_xlabel(\"k\")\n",
    "ax[1].plot(list(ks)[1:], sil, \"s-\", color=\"C1\"); ax[1].set_title(\"Silhouette vs k\"); ax[1].set_xlabel(\"k\")\n",
    "plt.tight_layout(); plt.show()\n",
    "print(\"k terbaik (silhouette):\", list(ks)[1:][int(np.argmax(sil))])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e73c4103",
   "metadata": {},
   "source": [
    "### Plot silhouette per titik untuk k = 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1a14d2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "km = KMeans(4, n_init=10, random_state=0).fit(Xs)\n",
    "s = silhouette_samples(Xs, km.labels_)\n",
    "y0 = 0\n",
    "for j in range(4):\n",
    "    sj = np.sort(s[km.labels_ == j])\n",
    "    plt.fill_betweenx(np.arange(y0, y0 + len(sj)), 0, sj, alpha=0.7, label=f\"klaster {j}\")\n",
    "    y0 += len(sj) + 10\n",
    "plt.axvline(s.mean(), color=\"k\", ls=\"--\", label=f\"rata-rata {s.mean():.2f}\")\n",
    "plt.xlabel(\"silhouette\"); plt.legend(); plt.title(\"Silhouette per titik (k=4)\"); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65710b3a",
   "metadata": {},
   "source": [
    "## 4. Profil segmen → strategi bisnis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93674dde",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"klaster\"] = km.labels_\n",
    "profil = df.groupby(\"klaster\")[fitur].mean().round(1)\n",
    "profil[\"jumlah\"] = df[\"klaster\"].value_counts().sort_index()\n",
    "profil[\"porsi_pendapatan_%\"] = (df.groupby(\"klaster\")[\"monetary_ribu\"].sum() / df[\"monetary_ribu\"].sum() * 100).round(1)\n",
    "profil"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7cf979af",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Karena data sintetis, kita bisa cek terhadap segmen asli (di dunia nyata label ini tidak ada!)\n",
    "print(\"Adjusted Rand Index vs segmen asli:\", round(adjusted_rand_score(df[\"segmen_asli\"], km.labels_), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17712546",
   "metadata": {},
   "source": [
    "## 5. K-Means vs K-Medoids dengan outlier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5653c50e",
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(0)\n",
    "A = np.vstack([rng.normal([0, 0], 0.5, (30, 2)), rng.normal([4, 4], 0.5, (30, 2)), [[25, 25], [26, 24]]])\n",
    "\n",
    "def k_medoids(X, medoid, iters=50):\n",
    "    D = np.sqrt(((X[:, None] - X[None]) ** 2).sum(axis=2))\n",
    "    for _ in range(iters):\n",
    "        lab = D[:, medoid].argmin(axis=1)\n",
    "        baru = [int(np.where(lab == j)[0][D[np.ix_(lab == j, lab == j)].sum(axis=1).argmin()]) for j in range(len(medoid))]\n",
    "        if baru == medoid:\n",
    "            break\n",
    "        medoid = baru\n",
    "    return lab, X[medoid]\n",
    "\n",
    "km2 = KMeans(2, init=A[[0, 30]], n_init=1).fit(A)\n",
    "lab_m, pusat_m = k_medoids(A, [0, 30])\n",
    "\n",
    "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n",
    "ax[0].scatter(A[:, 0], A[:, 1], c=km2.labels_, cmap=\"coolwarm\"); ax[0].scatter(*km2.cluster_centers_.T, marker=\"X\", s=200, c=\"k\")\n",
    "ax[0].set_title(\"K-Means: centroid tertarik outlier\")\n",
    "ax[1].scatter(A[:, 0], A[:, 1], c=lab_m, cmap=\"coolwarm\"); ax[1].scatter(*pusat_m.T, marker=\"X\", s=200, c=\"k\")\n",
    "ax[1].set_title(\"K-Medoids: medoid = titik nyata\")\n",
    "plt.show()\n",
    "print(\"centroid K-Means :\", km2.cluster_centers_.round(2).tolist())\n",
    "print(\"medoid           :\", pusat_m.round(2).tolist())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbad764e",
   "metadata": {},
   "source": [
    "## 6. Agglomerative clustering & dendrogram"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1caf3357",
   "metadata": {},
   "outputs": [],
   "source": [
    "sampel = Xs[rng.choice(len(Xs), 40, replace=False)]\n",
    "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n",
    "for a, metode in zip(ax, [\"single\", \"complete\", \"average\"]):\n",
    "    Z = linkage(sampel, method=metode)\n",
    "    dendrogram(Z, ax=a, no_labels=True)\n",
    "    a.set_title(f\"{metode} linkage\")\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16e83ad3",
   "metadata": {},
   "source": [
    "Perhatikan *chaining* pada single linkage (penggabungan bertangga), sedangkan complete/average lebih seimbang."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d339739",
   "metadata": {},
   "outputs": [],
   "source": [
    "for metode in [\"single\", \"complete\", \"average\", \"ward\"]:\n",
    "    lab = AgglomerativeClustering(n_clusters=4, linkage=metode).fit_predict(Xs)\n",
    "    print(f\"{metode:9s} silhouette={silhouette_score(Xs, lab):.3f}  ARI vs asli={adjusted_rand_score(df['segmen_asli'], lab):.3f}  ukuran={np.bincount(lab).tolist()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d82baade",
   "metadata": {},
   "source": [
    "## ✍️ Coba sendiri\n",
    "\n",
    "1. Ulangi K-Means contoh materi dengan centroid awal **B dan D**. Apakah hasil akhirnya sama?\n",
    "2. Jalankan `KMeans(4, n_init=1, init=\"random\")` dengan 5 `random_state` berbeda pada `Xs`. Catat SSE-nya — apa yang kamu simpulkan tentang inisialisasi?\n",
    "3. Coba clustering **tanpa** normalisasi. Fitur mana yang mendominasi? Bandingkan ARI-nya.\n",
    "4. Beri nama bisnis untuk tiap klaster di tabel profil (bagian 4) dan usulkan satu aksi marketing per klaster."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51985c0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5addac8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7395b04",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a9da44f",
   "metadata": {},
   "source": [
    "**Jawaban no. 4:**\n",
    "\n",
    "| Klaster | Nama segmen | Aksi |\n",
    "|---|---|---|\n",
    "| 0 | ... | ... |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "663fbae7",
   "metadata": {},
   "source": [
    "## Selanjutnya\n",
    "Kerjakan kuis dan `exercise.py`, lalu `python check.py 10`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
