{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LOWESS Smoother\n",
    "\n",
    "This notebook introduces the LOWESS smoother in the `nonparametric` package. LOWESS performs weighted local linear fits.\n",
    "\n",
    "We generated some non-linear data and perform a LOWESS fit, then compute a 95% confidence interval around the LOWESS fit by performing bootstrap resampling."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pylab\n",
    "import seaborn as sns\n",
    "import statsmodels.api as sm\n",
    "\n",
    "sns.set_style(\"darkgrid\")\n",
    "pylab.rc(\"figure\", figsize=(16, 8))\n",
    "pylab.rc(\"font\", size=14)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Seed for consistency\n",
    "np.random.seed(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate data looking like cosine\n",
    "x = np.random.uniform(0, 4 * np.pi, size=200)\n",
    "y = np.cos(x) + np.random.random(size=len(x))\n",
    "\n",
    "# Compute a lowess smoothing of the data\n",
    "smoothed = sm.nonparametric.lowess(exog=x, endog=y, frac=0.2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Plot the fit line\n",
    "fig, ax = pylab.subplots()\n",
    "\n",
    "ax.scatter(x, y)\n",
    "ax.plot(smoothed[:, 0], smoothed[:, 1], c=\"k\")\n",
    "pylab.autoscale(enable=True, axis=\"x\", tight=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Confidence interval\n",
    "\n",
    "Now that we have performed a fit, we may want to know how precise it is. Bootstrap resampling gives one\n",
    "way of estimating confidence intervals around a LOWESS fit by recomputing the LOWESS fit for a large number\n",
    "of random resamplings from our data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Now create a bootstrap confidence interval around the a LOWESS fit\n",
    "\n",
    "\n",
    "def lowess_with_confidence_bounds(\n",
    "    x, y, eval_x, N=200, conf_interval=0.95, lowess_kw=None\n",
    "):\n",
    "    \"\"\"\n",
    "    Perform Lowess regression and determine a confidence interval by bootstrap resampling\n",
    "    \"\"\"\n",
    "    # Lowess smoothing\n",
    "    smoothed = sm.nonparametric.lowess(exog=x, endog=y, xvals=eval_x, **lowess_kw)\n",
    "\n",
    "    # Perform bootstrap resamplings of the data\n",
    "    # and  evaluate the smoothing at a fixed set of points\n",
    "    smoothed_values = np.empty((N, len(eval_x)))\n",
    "    for i in range(N):\n",
    "        sample = np.random.choice(len(x), len(x), replace=True)\n",
    "        sampled_x = x[sample]\n",
    "        sampled_y = y[sample]\n",
    "\n",
    "        smoothed_values[i] = sm.nonparametric.lowess(\n",
    "            exog=sampled_x, endog=sampled_y, xvals=eval_x, **lowess_kw\n",
    "        )\n",
    "\n",
    "    # Get the confidence interval\n",
    "    sorted_values = np.sort(smoothed_values, axis=0)\n",
    "    bound = int(N * (1 - conf_interval) / 2)\n",
    "    bottom = sorted_values[bound - 1]\n",
    "    top = sorted_values[-bound]\n",
    "\n",
    "    return smoothed, bottom, top\n",
    "\n",
    "\n",
    "# Compute the 95% confidence interval\n",
    "eval_x = np.linspace(0, 4 * np.pi, 31)\n",
    "smoothed, bottom, top = lowess_with_confidence_bounds(\n",
    "    x, y, eval_x, lowess_kw={\"frac\": 0.1}\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Plot the confidence interval and fit\n",
    "fig, ax = pylab.subplots()\n",
    "ax.scatter(x, y)\n",
    "ax.plot(eval_x, smoothed, c=\"k\")\n",
    "ax.fill_between(eval_x, bottom, top, alpha=0.5, color=\"b\")\n",
    "pylab.autoscale(enable=True, axis=\"x\", tight=True)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.8.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
