{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "55555ebb",
   "metadata": {},
   "source": [
    "# Lab Minggu 7 — Naïve Bayes & K-Nearest Neighbors\n",
    "\n",
    "**Tujuan lab:**\n",
    "1. Menghitung Naïve Bayes manual pada Play Tennis lalu mencocokkannya dengan sklearn.\n",
    "2. Memakai Gaussian NB & KNN untuk memprediksi risiko *churn* pelanggan.\n",
    "3. Membuat klasifikasi sentimen ulasan dengan Multinomial NB.\n",
    "4. Melihat pengaruh normalisasi dan nilai *k* pada KNN.\n",
    "\n",
    "Baca `lesson.md` dulu. Jalankan sel satu per satu (Shift+Enter)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32b1d418",
   "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.model_selection import train_test_split\n",
    "from sklearn.naive_bayes import CategoricalNB, GaussianNB, MultinomialNB\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.preprocessing import OrdinalEncoder, StandardScaler\n",
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "from sklearn.metrics import accuracy_score, classification_report\n",
    "\n",
    "from common.datasets import play_tennis, churn_pelanggan, ulasan_produk"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "541cfdf5",
   "metadata": {},
   "source": [
    "## 1. Naïve Bayes manual — Play Tennis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7cc14774",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = play_tennis()\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98537568",
   "metadata": {},
   "outputs": [],
   "source": [
    "prior = df[\"play\"].value_counts(normalize=True)\n",
    "prior"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "431e87ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Tabel likelihood P(nilai | kelas) per fitur (tanpa smoothing)\n",
    "for fitur in [\"outlook\", \"temperature\", \"humidity\", \"wind\"]:\n",
    "    print(pd.crosstab(df[fitur], df[\"play\"], normalize=\"columns\").round(3), \"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f38a3e71",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_baru = {\"outlook\": \"Sunny\", \"temperature\": \"Cool\", \"humidity\": \"High\", \"wind\": \"Strong\"}\n",
    "\n",
    "def skor_nb(df, x, alpha=0):\n",
    "    skor = {}\n",
    "    for kelas, grup in df.groupby(\"play\"):\n",
    "        s = len(grup) / len(df)\n",
    "        for f, v in x.items():\n",
    "            k = df[f].nunique()\n",
    "            s *= ((grup[f] == v).sum() + alpha) / (len(grup) + alpha * k)\n",
    "        skor[kelas] = s\n",
    "    total = sum(skor.values())\n",
    "    return {k: (round(v, 5), round(v / total, 3)) for k, v in skor.items()}\n",
    "\n",
    "print(\"tanpa smoothing :\", skor_nb(df, x_baru, 0))\n",
    "print(\"Laplace alpha=1 :\", skor_nb(df, x_baru, 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05754531",
   "metadata": {},
   "source": [
    "### Bandingkan dengan sklearn `CategoricalNB`\n",
    "`alpha=1` di sklearn = Laplace smoothing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bde59e1c",
   "metadata": {},
   "outputs": [],
   "source": [
    "enc = OrdinalEncoder()\n",
    "X = enc.fit_transform(df.drop(columns=\"play\"))\n",
    "y = df[\"play\"]\n",
    "nb = CategoricalNB(alpha=1).fit(X, y)\n",
    "x_enc = enc.transform(pd.DataFrame([x_baru]))\n",
    "print(dict(zip(nb.classes_, nb.predict_proba(x_enc)[0].round(3))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3435cc72",
   "metadata": {},
   "source": [
    "## 2. Gaussian NB vs KNN — prediksi churn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1c6daa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "churn = churn_pelanggan()\n",
    "fitur_num = [\"lama_langganan\", \"biaya_bulanan\", \"jumlah_keluhan\", \"jam_pemakaian\"]\n",
    "X = churn[fitur_num].values\n",
    "y = churn[\"churn\"].values\n",
    "X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)\n",
    "churn.groupby(\"churn\")[fitur_num].mean().round(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ebc0f1fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "gnb = GaussianNB().fit(X_tr, y_tr)\n",
    "print(\"Gaussian NB akurasi:\", round(accuracy_score(y_te, gnb.predict(X_te)), 3))\n",
    "print(\"mean per kelas (theta_):\\n\", pd.DataFrame(gnb.theta_, columns=fitur_num).round(1))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f751d1d4",
   "metadata": {},
   "source": [
    "### KNN tanpa vs dengan normalisasi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4980c8ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "knn_mentah = KNeighborsClassifier(n_neighbors=7).fit(X_tr, y_tr)\n",
    "sc = StandardScaler().fit(X_tr)\n",
    "knn_norm = KNeighborsClassifier(n_neighbors=7).fit(sc.transform(X_tr), y_tr)\n",
    "print(\"KNN tanpa normalisasi :\", round(accuracy_score(y_te, knn_mentah.predict(X_te)), 3))\n",
    "print(\"KNN dengan z-score    :\", round(accuracy_score(y_te, knn_norm.predict(sc.transform(X_te))), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ef636d3",
   "metadata": {},
   "source": [
    "Tanpa normalisasi, `biaya_bulanan` (ratusan ribu) mendominasi jarak."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a407d23c",
   "metadata": {},
   "source": [
    "### Pengaruh nilai k"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42a323e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "ks = range(1, 40, 2)\n",
    "acc_tr, acc_te = [], []\n",
    "Xs_tr, Xs_te = sc.transform(X_tr), sc.transform(X_te)\n",
    "for k in ks:\n",
    "    model = KNeighborsClassifier(n_neighbors=k).fit(Xs_tr, y_tr)\n",
    "    acc_tr.append(model.score(Xs_tr, y_tr))\n",
    "    acc_te.append(model.score(Xs_te, y_te))\n",
    "\n",
    "plt.plot(ks, acc_tr, \"o-\", label=\"latih\")\n",
    "plt.plot(ks, acc_te, \"s-\", label=\"uji\")\n",
    "plt.xlabel(\"k\"); plt.ylabel(\"akurasi\"); plt.legend(); plt.title(\"KNN: k vs akurasi\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ffa0bdef",
   "metadata": {},
   "source": [
    "k=1 → akurasi latih 100% (hafal data) tetapi akurasi uji lebih rendah = **overfitting**."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55e77b3e",
   "metadata": {},
   "source": [
    "## 3. Klasifikasi sentimen dengan Multinomial NB"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47981a4d",
   "metadata": {},
   "outputs": [],
   "source": [
    "ul = ulasan_produk()\n",
    "vec = CountVectorizer()\n",
    "X_teks = vec.fit_transform(ul[\"teks\"])\n",
    "print(\"Ukuran kosakata:\", len(vec.vocabulary_))\n",
    "mnb = MultinomialNB(alpha=1).fit(X_teks, ul[\"sentimen\"])\n",
    "\n",
    "uji = [\"barang bagus dan pengiriman cepat\", \"penjual lambat barang rusak\", \"harga murah tapi kualitas jelek\"]\n",
    "for t, p, pr in zip(uji, mnb.predict(vec.transform(uji)), mnb.predict_proba(vec.transform(uji))):\n",
    "    print(f\"{t:40s} -> {p:8s} {dict(zip(mnb.classes_, pr.round(2)))}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "831f4dd3",
   "metadata": {},
   "source": [
    "Kata paling \"khas\" per kelas (log-likelihood terbesar):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ff1349f",
   "metadata": {},
   "outputs": [],
   "source": [
    "kata = np.array(vec.get_feature_names_out())\n",
    "for i, kelas in enumerate(mnb.classes_):\n",
    "    top = np.argsort(mnb.feature_log_prob_[i])[::-1][:6]\n",
    "    print(kelas, \"->\", \", \".join(kata[top]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d44ff2f",
   "metadata": {},
   "source": [
    "## ✍️ Coba sendiri\n",
    "\n",
    "1. Hitung manual posterior Play Tennis untuk `X = (Rain, Mild, Normal, Weak)` dengan α = 1. Cocokkan dengan `CategoricalNB`.\n",
    "2. Tambahkan fitur kategorikal `kontrak` (one-hot) ke model KNN churn. Apakah akurasi naik?\n",
    "3. Bandingkan KNN `metric=\"manhattan\"` vs `\"euclidean\"` pada data churn yang sudah dinormalisasi.\n",
    "4. Tulis 3 ulasan buatanmu sendiri (termasuk yang ambigu) dan lihat prediksi Multinomial NB. Mengapa ulasan ambigu sulit?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "317ee7f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12cca6bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ce3b2c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e621d51b",
   "metadata": {},
   "source": [
    "**Jawaban no. 4:**\n",
    "\n",
    "- ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0e395b9",
   "metadata": {},
   "source": [
    "## Selanjutnya\n",
    "Kerjakan kuis dan `exercise.py`, lalu jalankan `python check.py 7` dari folder utama."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
