{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Linear Data Simulation and Fit\n",
    "This notebook creates linear data with noise and fits it. The residuals of the fit and their distribution is also displayed. \n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    "This code generates a line $f(x)= m\\times x+b$, from X_MIN to X_MAX with a random number added from a gaussian distribution with zero mean.\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    "Uses packages\n",
    "1. [numpy][np]\n",
    "1. [scipy][sp]\n",
    "1. [matplotlib][plt] \n",
    "2. [ipwidgets][ip]\n",
    "3. [random][rd]\n",
    "4. [types][types]\n",
    "5. [math][math]\n",
    "<br/>\n",
    "<hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    "\n",
    "Example output :\n",
    "<br/>\n",
    "__Output of interact_with_plot()__\n",
    "<table>\n",
    "<tr>\n",
    "<th>\n",
    "Linear Plot With Small Noise and Negative Slope\n",
    "</th>\n",
    "<th>\n",
    "Linear Plot With Large Noise and Positive Slope\n",
    "</th>\n",
    "</tr>\n",
    "<tr>\n",
    "<td>\n",
    "<img src=\"./Python_Introduction_Images/example_plot_3.png\"/>\n",
    "</td>\n",
    "<td>\n",
    "<img src=\"./Python_Introduction_Images/example_plot_4.png\" />\n",
    "</td>\n",
    "</table>\n",
    "<br/>\n",
    "__Output of interact_with_residuals()__\n",
    "<table>\n",
    "<tr>\n",
    "<th>\n",
    "Linear Plot And Residuals With No Noise and Positive Slope\n",
    "</th>\n",
    "<th>\n",
    "Linear Plot With  Noise and Positive Slope\n",
    "</th>\n",
    "</tr>\n",
    "<tr>\n",
    "<td>\n",
    "<img src=\"./Python_Introduction_Images/example_plot_1.png\"/>\n",
    "</td>\n",
    "<td>\n",
    "<img src=\"./Python_Introduction_Images/example_plot_2.png\" />\n",
    "</td>\n",
    "</table>\n",
    "\n",
    "[np]:http://docs.scipy.org/doc/numpy/reference/?v=20160706100716\n",
    "[sp]:http://docs.scipy.org/doc/scipy/reference/?v=20160706100716\n",
    "[plt]:http://matplotlib.org/contents.html?v=20160706100716\n",
    "[rd]:https://docs.python.org/2.7/library/random.html?highlight=random#module-random\n",
    "[ip]:http://ipywidgets.readthedocs.io/en/latest/\n",
    "[types]:https://docs.python.org/2/library/types.html\n",
    "[math]:https://docs.python.org/2/library/math.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import needed libraries\n",
    "import numpy as np\n",
    "import scipy.optimize as so\n",
    "import matplotlib.pyplot as plt\n",
    "from ipywidgets import *\n",
    "import random\n",
    "from types import *\n",
    "import math\n",
    "\n",
    "# Define Constants\n",
    "# Constants that determine the span of the line in the x-axis\n",
    "X_MIN=-10\n",
    "X_MAX=10\n",
    "ListType=list\n",
    "\n",
    "# Define Functions\n",
    "\n",
    "# Define a function that finds the optimized least squared fit to a function\n",
    "def fit(function,xdata,ydata,a0):\n",
    "    \"Fit returns a least square fit \"\n",
    "    error_function=lambda a, xdata, ydata:function(a,xdata)-ydata\n",
    "    a,success=so.leastsq(error_function, a0,args=(xdata,ydata))\n",
    "    return a\n",
    "\n",
    "# Define a linear function\n",
    "def line_function(a,x):\n",
    "    \"line function (y=a[1]x+a[0])\"\n",
    "    return a[1]*x+a[0]\n",
    "\n",
    "# Define a function that finds residuals given a fit function and fit parameters and an original data set\n",
    "def find_residuals(fit_function,fit_parameters,x_data,y_data):\n",
    "    \"\"\"Returns the residuals for a fit\"\"\"\n",
    "    if type(x_data) in [np.ndarray,ListType]:\n",
    "        output=map(lambda x:fit_function(fit_parameters,x),x_data)\n",
    "        if type(y_data) is not ListType:\n",
    "            raise \n",
    "        output=[f_x-y_data[index] for index,f_x in enumerate(output)]\n",
    "    elif type(x_data) is FloatType:\n",
    "        output=fit_function(fit_parameters,x_data)-y_data\n",
    "    else:\n",
    "        output=None\n",
    "    return output\n",
    "\n",
    "# Define a function to plot a line and a fit through that line\n",
    "def plot_line(noise_magnitude,number_points,slope,intercept):\n",
    "    \"A function to plot a line with noise\"\n",
    "    data_list=np.linspace(X_MIN,X_MAX,number_points)\n",
    "    y_data=[slope*x+intercept+random.gauss(0,noise_magnitude) for x in data_list]\n",
    "    results=fit(line_function,data_list,y_data,[1,0])\n",
    "    y_fit=[line_function(results,x) for x in data_list]\n",
    "    #plot the data\n",
    "    plt.plot(data_list,y_data,'ob')\n",
    "    #plot the fit\n",
    "    plt.plot(data_list,y_fit,'r-',linewidth=5)\n",
    "    ax=plt.gca()\n",
    "    ax.set_ylim(-300,300)\n",
    "    ax.set_title('y = {0:3.2f} x + {1:3.2f}'.format(results[1],results[0]))\n",
    "    plt.show()\n",
    "\n",
    "# Define a plotting function that shows a line, a fit through that line, the residuals of the fit and a histogram \n",
    "# of those residuals\n",
    "def plot_residuals(noise_magnitude,number_points,slope,intercept):\n",
    "    \"A function to plot a line with noise and the residuals of that fit including a histogram of those residuals\"\n",
    "    data_list=np.linspace(X_MIN,X_MAX,number_points)\n",
    "    y_data=[slope*x+intercept+random.gauss(0,noise_magnitude) for x in data_list]\n",
    "    results=fit(line_function,data_list,y_data,[1,0])\n",
    "    y_fit=[line_function(results,x) for x in data_list]\n",
    "    #plot the data\n",
    "    # Comment this line to change the plot layout\n",
    "    fig, (ax0, ax1, ax2) = plt.subplots(nrows=3)\n",
    "    \n",
    "    # Uncomment these lines to change the laytout\n",
    "#     fig = plt.figure()\n",
    "#     ax0 = plt.subplot(221)\n",
    "#     ax1 = plt.subplot(223)\n",
    "#     ax2 = plt.subplot(122)\n",
    "\n",
    "    ax0.plot(data_list,y_data,'ob')\n",
    "    # plot the fit\n",
    "    ax0.plot(data_list,y_fit,'r-',linewidth=5)\n",
    "    ax0.set_ylim(-300,300)\n",
    "    ax0.set_title('y = {0:3.2f} x + {1:3.2f}'.format(results[1],results[0]))\n",
    "    # find the residuals\n",
    "    residuals=find_residuals(line_function,results,data_list,y_data)\n",
    "    # plot the residuals\n",
    "    ax1.plot(data_list,residuals,'r^')\n",
    "    ax1.set_ylim(-100,100)\n",
    "    # plot a histogram of the residuals\n",
    "    ax2.hist(residuals,bins=int(math.floor(math.sqrt(number_points))))\n",
    "    ax2.set_ylim(0,100)\n",
    "    ax2.set_xlim(-200,200)\n",
    "    # set the plot titles\n",
    "    ax1.set_title('Residuals')\n",
    "    ax2.set_title('Residual Distrubution')\n",
    "    # display\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "    \n",
    "\n",
    "# define scripts calling these create interactive plots \n",
    "def interact_with_plot():\n",
    "    %matplotlib inline\n",
    "    interact(plot_line,noise_magnitude=(0,100,1),number_points=(10,1000,10),slope=(-30,30,.1),intercept=(-200,200,1))\n",
    "\n",
    "# Test the find_residuals function\n",
    "def residual_script():\n",
    "    data_list=np.linspace(X_MIN,X_MAX,1000)\n",
    "    y_data=[5*x+10+random.gauss(0,5) for x in data_list]\n",
    "    results=fit(line_function,data_list,y_data,[1,0])\n",
    "    print(find_residuals(line_function,results,data_list,y_data))\n",
    "    \n",
    "def interact_with_residuals():\n",
    "    %matplotlib inline\n",
    "    interact(plot_residuals,noise_magnitude=(0,100,1),\n",
    "             number_points=(10,1000,10),slope=(-30,30,.1),intercept=(-200,200,1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 18216 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "#%matplotlib notebook\n",
    "plot_residuals(10,1000,5,7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c3eddc5bdfcd4eb081274965b1b8c679",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=50, description='noise_magnitude'), IntSlider(value=500, description='nu…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "interact_with_plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "bd8aea9314524785889442e0582036b3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=50, description='noise_magnitude'), IntSlider(value=500, description='nu…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "interact_with_residuals()"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "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.7.0"
  },
  "widgets": {
   "state": {
    "acdb614d44284278a97aabcefd60237e": {
     "views": [
      {
       "cell_index": 3
      }
     ]
    },
    "ca480343e65e4b6badbea83f809c7aad": {
     "views": [
      {
       "cell_index": 4
      }
     ]
    }
   },
   "version": "1.2.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
