{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  },
  "colab": {
   "provenance": []
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-0000",
   "metadata": {},
   "source": [
    "# Seaborn: Statistical Visualization\n",
    "\n",
    "Get publication-quality statistical charts in one line — scatterplots with hue, boxplots, violin plots, correlation heatmaps, and pairplots with seaborn.\n",
    "\n",
    "*Part of the free [Python for Data Science](https://ramadnsyh.dev/courses/python-for-data-science) course by [Muhammad Ramadiansyah](https://ramadnsyh.dev). This notebook is generated from the interactive lesson — [read it online](https://ramadnsyh.dev/courses/python-for-data-science/seaborn).*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0001",
   "metadata": {},
   "source": [
    "Matplotlib can draw anything, but common statistical charts take a lot of\n",
    "boilerplate. **Seaborn** sits on top of matplotlib and specializes in exactly\n",
    "those charts: it understands DataFrames directly, maps columns to colors and\n",
    "styles for you, and looks great out of the box."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0002",
   "metadata": {},
   "source": [
    "> **Where these cells run**\n",
    "> \n",
    "> Seaborn isn't available in this page's browser runtime, so the `sns.` code\n",
    "> blocks below are for the **downloadable notebook or Google Colab** (seaborn is\n",
    "> preinstalled there). Two PyRunner cells near the end recreate the same looks\n",
    "> with plain matplotlib so you can still practice in the browser."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0003",
   "metadata": {},
   "source": [
    "## What seaborn adds\n",
    "\n",
    "Compare drawing a scatter plot colored by category. In matplotlib you filter\n",
    "the DataFrame per group and call `scatter` in a loop; in seaborn you just\n",
    "*name the columns*:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0004",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "tips = sns.load_dataset(\"tips\")   # a classic demo dataset: restaurant bills\n",
    "tips.head()"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0005",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# one line: x, y, and \"color by smoker status\"\n",
    "sns.scatterplot(data=tips, x=\"total_bill\", y=\"tip\", hue=\"smoker\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0006",
   "metadata": {},
   "source": [
    "That `data=` + column-names interface is seaborn's core idea. The three\n",
    "**semantic mappings** you'll use constantly:\n",
    "\n",
    "- `hue` — map a column to **color**\n",
    "- `style` — map a column to **marker shape**\n",
    "- `size` — map a column to **marker size**"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0007",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.scatterplot(data=tips, x=\"total_bill\", y=\"tip\",\n",
    "                hue=\"time\", style=\"smoker\", size=\"size\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0008",
   "metadata": {},
   "source": [
    "One call, five variables on screen. Use this power sparingly — two semantics\n",
    "per chart is usually the readability limit."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0009",
   "metadata": {},
   "source": [
    "## Distributions: histplot\n",
    "\n",
    "`sns.histplot` is matplotlib's histogram plus statistical extras — most\n",
    "usefully, an optional smoothed density curve (KDE) and per-group overlays\n",
    "with `hue`:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0010",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.histplot(data=tips, x=\"total_bill\", bins=25, kde=True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0011",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# overlay distributions per group\n",
    "sns.histplot(data=tips, x=\"total_bill\", hue=\"time\", bins=25)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0012",
   "metadata": {},
   "source": [
    "## Categories: boxplot, violinplot, countplot\n",
    "\n",
    "For a numeric variable split by category, seaborn's category plots are the\n",
    "biggest time-savers.\n",
    "\n",
    "A **boxplot** summarizes each group with its median, quartile box, whiskers,\n",
    "and outlier dots:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0013",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.boxplot(data=tips, x=\"day\", y=\"total_bill\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0014",
   "metadata": {},
   "source": [
    "A **violin plot** replaces the box with the full density shape — better when\n",
    "groups might be bimodal (two bumps), which a box would hide:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0015",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.violinplot(data=tips, x=\"day\", y=\"total_bill\", hue=\"sex\", split=True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0016",
   "metadata": {},
   "source": [
    "A **countplot** is a bar chart of frequencies — `value_counts()` as a picture:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0017",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.countplot(data=tips, x=\"day\", hue=\"smoker\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0018",
   "metadata": {},
   "source": [
    "## Correlation heatmaps\n",
    "\n",
    "A heatmap paints a matrix as colored cells. Its killer use case: the\n",
    "**correlation matrix** of your numeric columns, which shows every pairwise\n",
    "relationship at once:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0019",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "penguins = sns.load_dataset(\"penguins\")\n",
    "\n",
    "corr = penguins.select_dtypes(\"number\").corr()\n",
    "sns.heatmap(corr, annot=True, fmt=\".2f\", cmap=\"coolwarm\", vmin=-1, vmax=1)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0020",
   "metadata": {},
   "source": [
    "`annot=True` prints the numbers in each cell; `vmin/vmax=(-1, 1)` anchors the\n",
    "color scale so that white really means \"no correlation\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0021",
   "metadata": {},
   "source": [
    "## Pairplot: the one-line dataset overview\n",
    "\n",
    "`sns.pairplot` draws a grid of scatter plots for every pair of numeric columns\n",
    "(with distributions on the diagonal). It's the classic first move in\n",
    "exploratory data analysis:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0022",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.pairplot(penguins, hue=\"species\", vars=[\"bill_length_mm\", \"flipper_length_mm\", \"body_mass_g\"])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0023",
   "metadata": {},
   "source": [
    "On the penguins dataset this instantly reveals that the three species form\n",
    "separable clusters — exactly the kind of insight you want before any modeling."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0024",
   "metadata": {},
   "source": [
    "## Themes\n",
    "\n",
    "One call restyles every subsequent chart — including plain matplotlib ones,\n",
    "since seaborn just configures matplotlib underneath:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0025",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "sns.set_theme(style=\"whitegrid\", palette=\"deep\")   # do this once, at the top\n",
    "sns.boxplot(data=tips, x=\"day\", y=\"total_bill\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0026",
   "metadata": {},
   "source": [
    "Other styles worth trying: `\"darkgrid\"` (the default), `\"white\"`, `\"ticks\"`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0027",
   "metadata": {},
   "source": [
    "## In-browser practice (plain matplotlib)\n",
    "\n",
    "You can approximate seaborn's two signature moves with matplotlib. First,\n",
    "a `hue`-style scatter — loop over groups, one color per group:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0028",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "rng = np.random.default_rng(42)\n",
    "\n",
    "# synthetic \"tips\": lunch vs dinner behave differently\n",
    "groups = {\n",
    "    \"lunch\":  {\"n\": 60, \"slope\": 0.12, \"color\": \"tab:blue\"},\n",
    "    \"dinner\": {\"n\": 80, \"slope\": 0.18, \"color\": \"tab:orange\"},\n",
    "}\n",
    "\n",
    "plt.figure(figsize=(7, 4.5))\n",
    "for name, g in groups.items():\n",
    "    bill = rng.uniform(8, 55, g[\"n\"])\n",
    "    tip = g[\"slope\"] * bill + rng.normal(0, 1.0, g[\"n\"])\n",
    "    plt.scatter(bill, tip, s=25, alpha=0.75, color=g[\"color\"], label=name)\n",
    "\n",
    "plt.xlabel(\"total bill\")\n",
    "plt.ylabel(\"tip\")\n",
    "plt.title(\"Tips by meal time (hue-style scatter)\")\n",
    "plt.legend(title=\"time\")\n",
    "plt.grid(alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0029",
   "metadata": {},
   "source": [
    "And a boxplot comparing groups — matplotlib's `boxplot` takes a list of\n",
    "arrays, one per category:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0030",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "rng = np.random.default_rng(7)\n",
    "\n",
    "days = [\"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n",
    "bills = [\n",
    "    rng.normal(17, 5, 60).clip(min=3),    # weekday lunches: modest\n",
    "    rng.normal(18, 6, 40).clip(min=3),\n",
    "    rng.normal(24, 9, 90).clip(min=3),    # weekend: bigger, more spread\n",
    "    rng.normal(22, 8, 75).clip(min=3),\n",
    "]\n",
    "\n",
    "plt.figure(figsize=(7, 4.5))\n",
    "plt.boxplot(bills, tick_labels=days, patch_artist=True,\n",
    "            boxprops={\"facecolor\": \"tab:blue\", \"alpha\": 0.6})\n",
    "plt.xlabel(\"day\")\n",
    "plt.ylabel(\"total bill\")\n",
    "plt.title(\"Bill distribution by day (boxplot)\")\n",
    "plt.grid(axis=\"y\", alpha=0.3)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0031",
   "metadata": {},
   "source": [
    "Same insight as `sns.boxplot` — weekend bills run higher and spread wider —\n",
    "just with a little more code. That's the trade in a nutshell."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0032",
   "metadata": {},
   "source": [
    "### 🏋️ Exercise — Explore the penguins dataset in Colab\n",
    "\n",
    "In the downloadable notebook or Colab, load `sns.load_dataset(\"penguins\")` and\n",
    "build five charts: (1) a scatterplot of flipper length vs body mass with\n",
    "`hue=\"species\"` and `style=\"sex\"`, (2) a boxplot of bill depth per species,\n",
    "(3) a countplot of species per island, (4) a correlation heatmap of the numeric\n",
    "columns with annotations, and (5) a pairplot of three numeric columns colored\n",
    "by species. Set a whitegrid theme first. Which two variables are most strongly\n",
    "correlated?"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0033",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Your solution here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0034",
   "metadata": {},
   "source": [
    "<details><summary>✅ Show solution</summary>\n",
    "\n",
    "```python\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "sns.set_theme(style=\"whitegrid\")\n",
    "penguins = sns.load_dataset(\"penguins\")\n",
    "\n",
    "# 1. scatter with two semantics\n",
    "sns.scatterplot(data=penguins, x=\"flipper_length_mm\", y=\"body_mass_g\",\n",
    "                hue=\"species\", style=\"sex\")\n",
    "plt.show()\n",
    "\n",
    "# 2. boxplot per species\n",
    "sns.boxplot(data=penguins, x=\"species\", y=\"bill_depth_mm\")\n",
    "plt.show()\n",
    "\n",
    "# 3. species counts per island\n",
    "sns.countplot(data=penguins, x=\"island\", hue=\"species\")\n",
    "plt.show()\n",
    "\n",
    "# 4. correlation heatmap\n",
    "corr = penguins.select_dtypes(\"number\").corr()\n",
    "sns.heatmap(corr, annot=True, fmt=\".2f\", cmap=\"coolwarm\", vmin=-1, vmax=1)\n",
    "plt.show()\n",
    "\n",
    "# 5. pairplot overview\n",
    "sns.pairplot(penguins, hue=\"species\",\n",
    "             vars=[\"bill_length_mm\", \"bill_depth_mm\", \"flipper_length_mm\"])\n",
    "plt.show()\n",
    "```\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0035",
   "metadata": {},
   "source": [
    "That wraps up the data toolkit — you can now load, clean, query, and visualize\n",
    "data, which is everything you need to start the machine learning course."
   ]
  }
 ]
}