{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "b9ba75db",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Cell 1 - Imports and reproducibility\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "rng = np.random.default_rng(2026)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "220e42d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Cell 2 - Define a discrete RV with PMF table\n",
    "values = np.array([0, 1, 2]) # outcomes\n",
    "probs = np.array([0.2, 0.5, 0.3]) # \n",
    "\n",
    "assert np.all(probs>=0), \"Probabilities must be nonnegative\"\n",
    "assert np.isclose(probs.sum(), 1.0), \"Probabilities must sum to 1\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5766655f",
   "metadata": {},
   "source": [
    "$E[X]=\\sum_x x\\, p(x)$\n",
    "\n",
    "$\\text{Var}(X)=E[X^2]-\\mu^2$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "473ff8a8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(np.float64(1.1),\n",
       " np.float64(0.48999999999999977),\n",
       " np.float64(0.6999999999999998))"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 3 - Anlytical E[X], Var(X)\n",
    "EX = np.sum(values*probs)\n",
    "EX2 = np.sum((values**2)*probs)\n",
    "VarX = EX2-EX**2\n",
    "SDX = np.sqrt(VarX)\n",
    "EX, VarX, SDX"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "32d1a6bf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 13848 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "(np.float64(1.0966),\n",
       " np.float64(0.4897884399999999),\n",
       " np.float64(0.6998488693996725))"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 4 - Monte Carlo Sampling of X\n",
    "# Empirical mean and variance\n",
    "N = 50_000\n",
    "X = rng.choice(values, size=N, p=probs)\n",
    "plt.plot(X[0:50], '.k')\n",
    "plt.show()\n",
    "\n",
    "eMean = X.mean()\n",
    "eVar = X.var(ddof=0)\n",
    "eSD = np.sqrt(eVar)\n",
    "eMean, eVar, eSD"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "0eb32f7f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 1, 1, 1, 2, 2, 0, 1])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X[0:9]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a2ece9c5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 22280 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Cell 5 - Running mean (LLN intuition):\n",
    "# \\bar{X}_n vs n\n",
    "rMean = np.cumsum(X) / np.arange(1,N+1)\n",
    "\n",
    "plt.plot(rMean, '.r', label=\"Running mean\")\n",
    "plt.grid()\n",
    "plt.xlim([0,500])\n",
    "plt.axhline(EX, color=\"black\", label=\"E[X]\")\n",
    "plt.xlabel(\"n\")\n",
    "plt.ylabel(\"Runing Mean\")\n",
    "plt.title(\"Running mean vs n\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "a9fde10e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>n</th>\n",
       "      <th>mean_hat</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>5</td>\n",
       "      <td>1.400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>5</td>\n",
       "      <td>1.200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>5</td>\n",
       "      <td>1.400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>5</td>\n",
       "      <td>1.200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>0.600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3995</th>\n",
       "      <td>1000</td>\n",
       "      <td>1.033</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3996</th>\n",
       "      <td>1000</td>\n",
       "      <td>1.132</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3997</th>\n",
       "      <td>1000</td>\n",
       "      <td>1.089</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3998</th>\n",
       "      <td>1000</td>\n",
       "      <td>1.129</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3999</th>\n",
       "      <td>1000</td>\n",
       "      <td>1.121</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>4000 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "         n  mean_hat\n",
       "0        5     1.400\n",
       "1        5     1.200\n",
       "2        5     1.400\n",
       "3        5     1.200\n",
       "4        5     0.600\n",
       "...    ...       ...\n",
       "3995  1000     1.033\n",
       "3996  1000     1.132\n",
       "3997  1000     1.089\n",
       "3998  1000     1.129\n",
       "3999  1000     1.121\n",
       "\n",
       "[4000 rows x 2 columns]"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 6 - How the estimator of E[X] changes with sample size\n",
    "# Cereate many repetitions for different n and store the sample mean each time\n",
    "nList = [5, 10, 20, 50, 100, 200, 500, 1000]\n",
    "R = 500 # repetitions per n\n",
    "\n",
    "rows = []\n",
    "for n in nList:\n",
    "    # draw an R x n matrix samples\n",
    "    samples = rng.choice(values, size=(R,n), p=probs)\n",
    "    means = samples.mean(axis=1)\n",
    "    for m in means:\n",
    "        rows.append({\"n\": n, \"mean_hat\": m})\n",
    "\n",
    "df = pd.DataFrame(rows)\n",
    "df "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "ccb262d0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/0c/2g7zyqyd3lj21c3yr8x3168m0000gn/T/ipykernel_42855/3468306116.py:3: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11.\n",
      "  plt.boxplot(groups, labels=[str(n) for n in nList])\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 18320 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Cell 7 - Boxplot\n",
    "groups = [df.loc[df[\"n\"] == n, \"mean_hat\"].values for n in nList]\n",
    "plt.boxplot(groups, labels=[str(n) for n in nList])\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "d82dbe57",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((np.float64(-1.6999999999999997),\n",
       "  np.float64(4.409999999999998),\n",
       "  np.float64(2.0999999999999996)),\n",
       " (np.float64(-1.7102), np.float64(4.40809596), np.float64(2.099546608199018)))"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 8 — Verify linear transform rules: Y = a + bX\n",
    "a, b = -5, 3\n",
    "Y = a + b * X\n",
    "\n",
    "# Analytical results\n",
    "EY = a + b * EX\n",
    "VarY = (b**2) * VarX\n",
    "SDY = np.sqrt(VarY)\n",
    "\n",
    "# Empirical results\n",
    "emp_EY = Y.mean()\n",
    "emp_VarY = Y.var(ddof=0)\n",
    "emp_SDY = np.sqrt(emp_VarY)\n",
    "\n",
    "(EY, VarY, SDY), (emp_EY, emp_VarY, emp_SDY)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "6a6c6d13",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 16996 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 20620 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Cell 9 — Visual check: histograms of X and Y (discrete RVs)\n",
    "plt.figure()\n",
    "plt.hist(X, bins=20, density=True)\n",
    "plt.xlabel(\"X\")\n",
    "plt.ylabel(\"Relative frequency\")\n",
    "plt.title(\"Histogram of discrete X\")\n",
    "plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(Y, bins=20, density=True)\n",
    "plt.xlabel(\"Y = a + bX\")\n",
    "plt.ylabel(\"Relative frequency\")\n",
    "plt.title(\"Histogram of transformed variable Y\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "4e058dd9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((np.float64(4.004671683485947), np.float64(1.3301885811390868)),\n",
       " (4.0, 1.3333333333333333))"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 10 (optional) — Continuous RV example: Uniform(a,b)\n",
    "# Demonstrate: P(c < X < d) as area, and mean/variance formulas (compare with simulation).\n",
    "a_u, b_u = 2.0, 6.0\n",
    "N_u = 200_000\n",
    "Xu = rng.uniform(a_u, b_u, size=N_u)\n",
    "\n",
    "# Analytical (Uniform)\n",
    "EXu = (a_u + b_u) / 2\n",
    "VarXu = (b_u - a_u)**2 / 12\n",
    "\n",
    "# Empirical\n",
    "(Xu.mean(), Xu.var(ddof=0)), (EXu, VarXu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "dba76292",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(np.float64(0.375195), 0.375)"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 11 (optional) — Probability query via simulation: P(c < X < d)\n",
    "c, d = 3.0, 4.5\n",
    "prob_hat = np.mean((Xu > c) & (Xu < d))\n",
    "\n",
    "# Analytical for uniform: (d-c)/(b-a) if [c,d] subset of [a,b]\n",
    "prob_true = (d - c) / (b_u - a_u)\n",
    "\n",
    "prob_hat, prob_true"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "872ca815",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 25040 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Cell 12 (optional) — Empirical CDF plot (works for any sample)\n",
    "# For teaching: CDF is monotone and ends at 1.\n",
    "x_sorted = np.sort(Xu)\n",
    "F_emp = np.arange(1, N_u + 1) / N_u\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(x_sorted, F_emp, linewidth=1)\n",
    "plt.xlabel(\"x\")\n",
    "plt.ylabel(\"F(x)\")\n",
    "plt.title(\"Empirical CDF from samples\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26fa621b",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1bdd8294",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85ee308a",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "737be3d6",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aac3728f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "687205ec",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(np.float64(1.09786), np.float64(0.4911234204), np.float64(0.7008019837300691))"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 4 — Monte Carlo sampling of X, empirical mean/variance\n",
    "N = 50_000\n",
    "X = rng.choice(values, size=N, p=probs)\n",
    "\n",
    "emp_mean = X.mean()\n",
    "emp_var  = X.var(ddof=0)   # population variance estimator\n",
    "emp_sd   = np.sqrt(emp_var)\n",
    "\n",
    "emp_mean, emp_var, emp_sd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "f225e4fc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 23928 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Cell 5 — Running mean (LLN intuition): \\bar{X}_n vs n\n",
    "running_mean = np.cumsum(X) / np.arange(1, N + 1)\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(running_mean, linewidth=1)\n",
    "plt.axhline(EX, linewidth=1)  # theoretical mean\n",
    "plt.xlabel(\"n\")\n",
    "plt.ylabel(\"Running mean\")\n",
    "plt.title(\"Running mean vs n (stabilizes near E[X])\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "c8ce1f25",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>n</th>\n",
       "      <th>mean_hat</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>5</td>\n",
       "      <td>0.8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>5</td>\n",
       "      <td>1.2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>5</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>5</td>\n",
       "      <td>1.2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>1.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   n  mean_hat\n",
       "0  5       0.8\n",
       "1  5       1.2\n",
       "2  5       1.0\n",
       "3  5       1.2\n",
       "4  5       1.0"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Cell 6 — How the estimator of E[X] changes with sample size\n",
    "# Create many repetitions for different n, store the sample mean each time.\n",
    "n_list = [5, 10, 20, 50, 100, 200, 500, 1000]\n",
    "R = 500  # repetitions per n (adjust for speed)\n",
    "\n",
    "rows = []\n",
    "for n in n_list:\n",
    "    # Draw an R x n matrix of samples\n",
    "    samples = rng.choice(values, size=(R, n), p=probs)\n",
    "    means = samples.mean(axis=1)\n",
    "    for m in means:\n",
    "        rows.append({\"n\": n, \"mean_hat\": m})\n",
    "\n",
    "df_means = pd.DataFrame(rows)\n",
    "df_means.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "71b37ec4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/0c/2g7zyqyd3lj21c3yr8x3168m0000gn/T/ipykernel_42855/2654198260.py:6: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11.\n",
      "  plt.boxplot(groups, labels=[str(n) for n in n_list], showfliers=False)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 25548 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Cell 7 — Boxplots of \\hat{E}[X] across sample sizes\n",
    "# This visually shows variability decreasing as n increases.\n",
    "groups = [df_means.loc[df_means[\"n\"] == n, \"mean_hat\"].values for n in n_list]\n",
    "\n",
    "plt.figure()\n",
    "plt.boxplot(groups, labels=[str(n) for n in n_list], showfliers=False)\n",
    "plt.axhline(EX, linewidth=1)  # theoretical mean\n",
    "plt.xlabel(\"Sample size n\")\n",
    "plt.ylabel(\"Sample mean (estimator of E[X])\")\n",
    "plt.title(\"Distribution of the sample mean vs sample size\")\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv (3.14.2)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
