```python
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display, HTML, IFrame
from ipywidgets import interact,fixed
import pandas as pd
from mpl_toolkits import mplot3d
from matplotlib.patches import Rectangle, Circle

plt.rcParams["figure.figsize"] = [8, 8]

from numpy.linalg import norm
from numpy import cos,sin,tan,arctan,exp,log,pi,sqrt,linspace,meshgrid,arange,array

from ipykernel.pylab.backend_inline import flush_figures

from scipy.integrate import quad, dblquad, tplquad

%matplotlib inline

plt.rcParams.update({
    "figure.figsize": (6,6),
    "text.usetex": True,
    "font.family": "serif",
})

# Uncomment the one that corresponds to your Jupyter theme
plt.style.use('default')
# plt.style.use('dark_background')
# plt.style.use('fivethirtyeight')
# plt.style.use('Solarize_Light2')
```

$\newcommand{\RR}{\mathbb{R}}$
$\newcommand{\bv}[1]{\begin{bmatrix} #1 \end{bmatrix}}$
$\renewcommand{\vec}{\mathbf}$

## Review Poll

Let $C$ be a curve in $\mathbb R^2$, $f$ a continuous function, and $a, b$ scalars. What is the difference between these expressions?
$$ \text{(A) } \int_a^b f(x,y)\,dy \qquad \qquad \text{(B) } \int_C f(x,y)\,dy $$

# Lecture 18

## The Fundamental Theorem of Line Integrals

  - Objectives
    - Line integrals of (through) vector fields
    - Conservative vector fields
    - Fundamental Theorem of Line Integrals
      - Relation to path-independence
      - Why "conservative"?

  - Resources
    - [Exercises and Solutions](../exercises/L18-Exercises-Solutions.ipynb)
    - Content
      - Stewart: §16.3
      - New Strang:
        - [§6.3](https://openstax.org/books/calculus-volume-3/pages/6-3-conservative-vector-fields)
    - Practice
      - Mooculus: [Line Integrals](https://ximera.osu.edu/mooculus/calculus3/lineIntegrals/digInLineIntegrals)
    - Extras
      - CalcBLUE: [Path Independence](https://www.youtube.com/watch?v=Wgd8RA-pagQ&list=PL8erL0pXF3JYCn8Xukv0DqVIXtXJbOqdo&index=23)

## Example

Compute the following line integral
 $$\int_C (x-y)\,dx + xy\,dy$$ where $C$ is the clockwise section of the circle $x^2 + y^2 = 1$ from $(1,0)$ to $(0,1)$.

```python
x = y = np.linspace(-1,1,20)
x,y = np.meshgrid(x,y)
plt.figure(figsize=(7,7))
plt.quiver(x,y,x-y,x*y,color='gray');
for c in 'xy':
#         getattr(ax,f"set_{c}lim")([-1,1]);
    getattr(plt.gca(),f"set_{c}label")(f"${c}$",size=16)
t= np.linspace(0,1,100)
# plt.plot(2 - (2+sqrt(2))*t,sqrt(2)*t,'r',lw=3);
plt.arrow(0.01,-1,-.001,0,width=.03,color='b')
plt.plot(cos(t*3*pi/2),-sin(t*3*pi/2),'b',lw=3);
```

## Work example

A wagon 5 m away is pulled by a rope from a height 1 m above it with a force of 7 N. How much work is done pulling it the full distance?

```python
@interact(xpos=(0.,5.))
def _(xpos=0):
    fig = plt.figure(figsize = (7.5,3))
    ax = fig.gca()
    ax.plot([0+xpos,5],[0,1],'gray')
    ax.add_patch(Rectangle([-2+xpos,-1/4],2,1/2,color='r',alpha=.3))
    for xx in (-5/3,-1/3):
        ax.add_patch(Circle([xx+xpos,-1/4],1/4,color='gray',alpha=1))
    ax.set_ylim([-1,2])
    ax.set_xlim([-2.5,5])
    ax.arrow(xpos,0,(5-xpos)/norm((5-xpos,1)),1/norm((5-xpos,1)),color='b',width=.03);
    for xi in np.arange(0,xpos,1/2):
        ax.arrow(xi,0,(5-xi)/norm((5-xi,1)),1/norm((5-xi,1)),color='b',width=.03,alpha=.5);
    flush_figures();
```

    interactive(children=(FloatSlider(value=0.0, description='xpos', max=5.0), Output()), _dom_classes=('widget-in…

# Conservative Vector Fields

A vector field $\vec F$ is **conservative** if there exists a scalar field $f$ (with the same domain as $\vec F$ such that $$\vec F = \nabla f.$$

The function $f$ is called a **potential**.

### Example

The radial vector field $x \,\vec i + y \vec j$ is conservative. Its potential is $$f(x,y) = \frac{x^2 + y ^2}{2}$$

```python
x = y = linspace(-2,2,10)
x,y = meshgrid(x,y)
plt.quiver(x,y,x,y,color='gray');
```

What about the circular vector field $-y\,\vec i + x\, \vec j$?

That is, can we find a scalar field $f(x,y)$ with:

\begin{align}
f_x &= -y \\ f_y &= x
\end{align}

```python
x = y = linspace(-2,2,10)
x,y = meshgrid(x,y)
plt.quiver(x,y,-y,x,color='gray');
```

## No.

Reason: Clairaut.

$$ f_{xy} = -1 $$
$$ f_{yx} = 1 $$

# Example

Which of the following fields is conservative?

  1. $x\,\vec i$
  1. $x\,\vec j$
  1. Both.
  1. Neither.

```python
x = y = linspace(-1,1,12)
x,y = meshgrid(x,y)
fig, axs=plt.subplots(1,2,figsize=(10,5),sharey=True)
axs[0].quiver(x,y,x,0,color='gray')
axs[1].quiver(x,y,0,x,color='gray');
```

### Example

Is the following vector field conservative? $$\vec F(x,y) = \langle y e^{xy}, x e^{xy} + y^3 \rangle $$

#### Solution

Check mixed partials. $$P_y = e^{xy} + xy e^{xy} = Q_x \quad ✅ $$

So, we solve $f_x(x,y) = P(x,y) = y e^{xy}$. $$f(x,y) = e^{xy} + g(y)$$ $$f_y(x,y) = x e^{xy} + g'(y) = Q(x,y) = x e^{xy} + y^3 $$ So $g'(y) = y^3$, or $g(y) = \frac14 y^4 + C$.

$$f(x,y) = e^{xy} + \frac14 y^4 + C $$

# A line integral through a conservative vector field

What happens when we combine the last two topics: line integrals and conservative vector fields?

$$\int_C \nabla f \cdot d\vec r $$

where $C$ is a piecewise smooth, oriented curve parametrized by $\vec r(t) = \langle x(t),y(t)\rangle$ for $a\leq t \leq b$ and $f$ is a differentiable function.

$$ = \int_a^b \nabla f(\vec r(t)) \cdot \vec r'(t)\, dt $$

$$ = \int_a^b \left( \frac{\partial f}{\partial x} \frac{dx}{dt} + \frac{\partial f}{\partial y} \frac{dy}{dt} \right)\, dt $$

$$ = \int_a^b (f\circ \vec r)'(t)\, dt $$

$$ = f(\vec r(b)) - f(\vec r(a))$$

# The Fundamental Theorem of Line integrals

Let $C$ be a piecewise-smooth, oriented curve from point $(x_1,y_1)$ to point $(x_2,y_2)$ and $\vec F$ a continuous, conservative vector field with potential $f$. (That is, $\nabla f = \vec F$.)  Then, $$\int_C \vec F\cdot d\vec r = f(x_2,y_2) - f(x_1,y_1).$$

```python
@interact(b = (0, 1, .025))
def _(b = 0, s = (0,1,.05),
      r = fixed(lambda t: np.array(((1+t/4)*cos(4*pi*t),2*t - sin(8*t)/2))),
      r2 = fixed(lambda t: np.array(((1 - t)*1 + 1.25*t, t*(2 - sin(8)/2)))),
      rp = fixed(lambda t: np.array((-4*pi*(1+t/4)*sin(4*pi*t) + 1/4*cos(4*pi*t)  ,2 - 4*cos(8*t)))),
      r2p = fixed(lambda t: np.array((.25, (2 - sin(8)/2))))):

    t = np.linspace(0,1,100)
    plt.plot(*(r(t)),'-g',zorder=2)
    plt.plot(*(r(t*b)),'-g',zorder=3, lw=4)
    plt.plot(*(r2(t)),'-b',zorder=2)
    plt.plot(*(r2(t*b)),'-b',zorder=3, lw=4)

    plt.text(-.7,.45,"$C_1$",fontsize=30,color='g',zorder=2);
    plt.text(1.2,.5,"$C_2$",fontsize=30,color='b',zorder=2);
    u = np.linspace(-1.2,1.3,18)
    v = np.linspace(-1,2,18)
    x,y = np.meshgrid(u,v)
    plt.quiver(x,y,2*x,y/2,alpha = (1 - .8*s),color='gray');
    u = np.linspace(-1.2,1.3,55)
    v = np.linspace(-1,2,55)
    x,y = np.meshgrid(u,v)
    c = plt.contour(x,y,x**2 + y**2/4 - 1,alpha=s,levels=arange(-1.5,1.7,3/8))
    def Fr(t):
        X, Y = r(t)
        U, V = rp(t)
        return 2*X*U + Y/2 * V
    def Fr2(t):
        X, Y = r2(t)
        U, V = r2p(t)
        return 2*X*U + Y/2 * V
    iiint = quad(Fr, 0, b)[0]
    iiint2 = quad(Fr2, 0, b)[0]

    plt.title(f"$\\int_{{C_1}} \\mathbf{{F}} \\cdot d\\mathbf{{r}} = {iiint:.03f} \\quad\\quad\\int_{{C_2}} \\mathbf{{F}} \\cdot d\\mathbf{{r}} = {iiint2:.03f}$")
    plt.clabel(c);
#     plt.colorbar();
    flush_figures();
```

    interactive(children=(FloatSlider(value=0.0, description='b', max=1.0, step=0.025), FloatSlider(value=0.0, des…

## Theorem

The following statements are equivalent regarding a continuous vector field $\vec F$.

  - $\vec F$ is conservative.

  - $\oint_C \vec F\cdot d\vec r = 0$ on every closed curve $C$.

  - $\int_C \vec F\cdot d\vec r$ depends only on the endpoints of $C$, i.e., is path-independent.

```python
@interact(s = (0,1,.05))
def _(s = 0):
    x = y = linspace(-1,1,18)
    x,y = meshgrid(x,y)
    plt.title("$\\langle y,x \\rangle$",fontsize=20)
    plt.quiver(x,y,y,x,color='gray');
    c = plt.contour(x,y,x*y,alpha=s,levels=15)
    plt.clabel(c);
    flush_figures()
```

    interactive(children=(FloatSlider(value=0.0, description='s', max=1.0, step=0.05), Output()), _dom_classes=('w…

## Why "conservative"?

In physics, we say a force field $\vec F$ has potential $f$ if $\vec F = -\nabla f$. (Note: this does not change which fields are conservative; it is just a sign convention.)

Let $\vec r(t) $ for $t \in [a,b]$ be the position of a particle with mass $m$ moving in this force field. We compute the work done by the force in two ways.

$$ \int_C \vec F\cdot d\vec r = \int_a^b \vec F(\vec r(t))\cdot \vec r'(t)\,dt $$

using $\vec F = m \vec a = m \vec r''(t) $,

$$ = \int_a^b m\vec r''(t)\cdot \vec r'(t)\,dt $$

Here's the key step:

$$ = \int_a^b \frac{d}{dt}\left( \frac 12 m\, \vec r'(t)\cdot \vec r'(t) \right)\,dt $$

$$= \frac 12 m\, \vec r'(b)\cdot \vec r'(b) - \frac 12\, m \vec r'(a)\cdot \vec r'(a) $$

$$= \frac12m |\vec v(b)|^2 - \frac12 m|\vec v(a)|^2 $$

A second way:

$$ \int_C \vec F\cdot d\vec r = \int_C -\nabla f\cdot d\vec r $$

using FTLI,

$$ = -f(\vec r(b)) + f(\vec r(a)) $$

Putting these together we get...

#### Conservation of Energy

$$ f(\vec r(a)) + \frac12m|\vec v(a)|^2 =  f(\vec r(b)) + \frac12m|\vec v(b)|^2 $$


```python
import opt_mo
import numpy as np
import sympy as sym
import itertools

sym.init_printing()

```

```python
p_1, p_2, p_3, p_4 = sym.symbols("p_1, p_2, p_3, p_4")
q_1, q_2, q_3, q_4 = sym.symbols("q_1, q_2, q_3, q_4")

```

```python
p = (p_1, p_2, p_3, p_4)
q = (q_1, q_2, q_3, q_4)

```

```python
pi_1, pi_2, pi_3, pi_4 = sym.symbols("pi_1, pi_2, pi_3, pi_4")
pi = (pi_1, pi_2, pi_3, pi_4)

```

**Theorem 1 Proof**

As discrubed in Section 2 the utility of a memory one player against another is given by the steady states of M
multiplied by the payoffs.

```python
M = opt_mo.mem_one_match_markov_chain(player=p, opponent=q)

```

```python
ss = opt_mo.steady_states(M, pi)

```

```python
v = sym.Matrix([[ss[pi_1]], [ss[pi_2]], [ss[pi_3]], [ss[pi_4]]])

```

```python
utility = v.dot(np.array([3, 0, 5, 1]))
```

```python
expr = utility.factor()
```

```python
numerator, denominator = sym.fraction(expr)
```

```python
numerator

```

```python
numerator_elements = [[numerator.coeff(f1 * f2) * f1 * f2 for f2 in p] for f1 in p]
```

```python
flat_elements = list(itertools.chain.from_iterable(numerator_elements))
```

```python
cross_prod = sum(flat_elements) / 2
```

```python
cross_prod
```

```python
sym.latex(sum(flat_elements) / 2).replace("\\left", "").replace("\\right", "")
```

    'p_{1} p_{2} (q_{1} q_{2} - 5 q_{1} q_{4} - q_{1} - q_{2} q_{3} + 5 q_{3} q_{4} + q_{3}) + p_{1} p_{3} (- q_{1} q_{3} + q_{2} q_{3}) + p_{1} p_{4} (5 q_{1} q_{3} - 5 q_{3} q_{4}) + p_{2} p_{3} (- q_{1} q_{2} + q_{1} q_{3} + 3 q_{2} q_{4} + q_{2} - 3 q_{3} q_{4} - q_{3}) + p_{2} p_{4} (- 5 q_{1} q_{3} + 5 q_{1} q_{4} + 3 q_{2} q_{3} - 3 q_{2} q_{4} + 2 q_{3} - 2 q_{4}) + p_{3} p_{4} (- 3 q_{2} q_{3} + 3 q_{3} q_{4})'

```python
linear_expr = numerator.subs({p_2: 0, p_3: 0, p_4: 0}).coeff(p_1) * p_1
linear_expr += numerator.subs({p_1: 0, p_3: 0, p_4: 0}).coeff(p_2) * p_2
linear_expr += numerator.subs({p_1: 0, p_2: 0, p_4: 0}).coeff(p_3) * p_3
linear_expr += numerator.subs({p_1: 0, p_2: 0, p_3: 0}).coeff(p_4) * p_4
```

```python
linear_expr
```

```python
sym.latex(linear_expr).replace("\\left", "").replace("\\right", "")
```

    'p_{1} (- q_{1} q_{2} + 5 q_{1} q_{4} + q_{1}) + p_{2} (q_{2} q_{3} - q_{2} - 5 q_{3} q_{4} - q_{3} + 5 q_{4} + 1) + p_{3} (q_{1} q_{2} - q_{2} q_{3} - 3 q_{2} q_{4} - q_{2} + q_{3}) + p_{4} (- 5 q_{1} q_{4} + 3 q_{2} q_{4} + 5 q_{3} q_{4} - 5 q_{3} + 2 q_{4})'

```python
constant = numerator.subs({p_2: 0, p_3: 0, p_4: 0, p_1: 0})
```

```python
constant
```

```python
sym.latex(constant)
```

    'q_{2} - 5 q_{4} - 1'

```python
((constant + linear_expr + cross_prod) - numerator).simplify()
```

**Denominator**

```python
denominator_elements = [[denominator.coeff(f1 * f2) * f1 * f2 for f2 in p] for f1 in p]
```

```python
flat_elements = list(itertools.chain.from_iterable(denominator_elements))
```

```python
cross_prod = sum(flat_elements) / 2
```

```python
cross_prod
```

```python
sym.latex(cross_prod).replace("\\left", "").replace("\\right", "")
```

    'p_{1} p_{2} (q_{1} q_{2} - q_{1} q_{4} - q_{1} - q_{2} q_{3} + q_{3} q_{4} + q_{3}) + p_{1} p_{3} (- q_{1} q_{3} + q_{1} q_{4} + q_{2} q_{3} - q_{2} q_{4}) + p_{1} p_{4} (- q_{1} q_{2} + q_{1} q_{3} + q_{1} + q_{2} q_{4} - q_{3} q_{4} - q_{4}) + p_{2} p_{3} (- q_{1} q_{2} + q_{1} q_{3} + q_{2} q_{4} + q_{2} - q_{3} q_{4} - q_{3}) + p_{2} p_{4} (- q_{1} q_{3} + q_{1} q_{4} + q_{2} q_{3} - q_{2} q_{4}) + p_{3} p_{4} (q_{1} q_{2} - q_{1} q_{4} - q_{2} q_{3} - q_{2} + q_{3} q_{4} + q_{4})'

```python
linear_expr = denominator.subs({p_2: 0, p_3: 0, p_4: 0}).coeff(p_1) * p_1
linear_expr += denominator.subs({p_1: 0, p_3: 0, p_4: 0}).coeff(p_2) * p_2
linear_expr += denominator.subs({p_1: 0, p_2: 0, p_4: 0}).coeff(p_3) * p_3
linear_expr += denominator.subs({p_1: 0, p_2: 0, p_3: 0}).coeff(p_4) * p_4
```

```python
sym.latex(linear_expr).replace("\\left", "").replace("\\right", "")
```

    'p_{1} (- q_{1} q_{2} + q_{1} q_{4} + q_{1}) + p_{2} (q_{2} q_{3} - q_{2} - q_{3} q_{4} - q_{3} + q_{4} + 1) + p_{3} (q_{1} q_{2} - q_{2} q_{3} - q_{2} + q_{3} - q_{4}) + p_{4} (- q_{1} q_{4} + q_{2} + q_{3} q_{4} - q_{3} + q_{4} - 1)'

```python
constant = denominator.subs({p_2: 0, p_3: 0, p_4: 0, p_1: 0})
```

```python
constant
```

```python
sym.latex(constant)
```

    'q_{2} - q_{4} - 1'

```python
sym.Matrix(Q_num)
```

```python
n.collect(p_1 * p_2).collect(p_1 * p_3).collect(p_1 * p_4).collect(p_2 * p_3).collect(p_2 * p_4)
```

```python

```

```python
element = [[n.coeff(f1 * f2) * f1 * f2 for f2 in p] for f1 in p]
```

```python
flat_elements = list(itertools.chain.from_iterable(list2d))
```

```python
flat_elements
```

    <function __main__.<lambda>(l)>

```python
expr = 0
for i in range(3):
    for j in range()
    expr+= n.coeff(p[i] * p[i + 1]) * p[i] * p[i + 1]
```

```python
expr
```

```python
(n - expr).simplify()
```

```python

```


# Regression in Python

***
This is a very quick run-through of some basic statistical concepts, adapted from [Lab 4 in Harvard's CS109](https://github.com/cs109/2015lab4) course. Please feel free to try the original lab if you're feeling ambitious :-) The CS109 git repository also has the solutions if you're stuck.

* Linear Regression Models
* Prediction using linear regression

Linear regression is used to model and predict continuous outcomes with normal random errors. There are nearly an infinite number of different types of regression models and each regression model is typically defined by the distribution of the prediction errors (called "residuals") of the type of data. Logistic regression is used to model binary outcomes whereas Poisson regression is used to predict counts. In this exercise, we'll see some examples of linear regression as well as Train-test splits.

The packages we'll cover are: `statsmodels`, `seaborn`, and `scikit-learn`. While we don't explicitly teach `statsmodels` and `seaborn` in the Springboard workshop, those are great libraries to know.
***

***

```python
# special IPython command to prepare the notebook for matplotlib and other libraries
%matplotlib inline

import numpy as np
import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import sklearn

import seaborn as sns

# special matplotlib argument for improved plots
from matplotlib import rcParams
sns.set_style("whitegrid")
sns.set_context("poster")

```

***
# Part 1: Introduction to Linear Regression
### Purpose of linear regression
***

 Given a dataset containing predictor variables $X$ and outcome/response variable $Y$, linear regression can be used to:

 Build a predictive model to predict future values of $\hat{Y}$, using new data $X^*$ where $Y$ is unknown.

 Model the strength of the relationship between each independent variable $X_i$ and $Y$

 Many times, only a subset of independent variables $X_i$ will have a linear relationship with $Y$

 Need to figure out which $X_i$ contributes most information to predict $Y$

It is in many cases, the first pass prediction algorithm for continuous outcomes.

### A Brief Mathematical Recap
***

[Linear Regression](http://en.wikipedia.org/wiki/Linear_regression) is a method to model the relationship between a set of independent variables $X$ (also knowns as explanatory variables, features, predictors) and a dependent variable $Y$.  This method assumes the relationship between each predictor $X$ is **linearly** related to the dependent variable $Y$. The most basic linear regression model contains one independent variable $X$, we'll call this the simple model.

$$ Y = \beta_0 + \beta_1 X + \epsilon$$

where $\epsilon$ is considered as an unobservable random variable that adds noise to the linear relationship. In linear regression, $\epsilon$ is assumed to be normally distributed with a mean of 0. In other words, what this means is that on average, if we know $Y$, a roughly equal number of predictions $\hat{Y}$ will be above $Y$ and others will be below $Y$. That is, on average, the error is zero. The residuals, $\epsilon$ are also assumed to be "i.i.d.": independently and identically distributed. Independence means that the residuals are not correlated -- the residual from one prediction has no effect on the residual from another prediction. Correlated errors are common in time series analysis and spatial analyses.

* $\beta_0$ is the intercept of the linear model and represents the average of $Y$ when all independent variables $X$ are set to 0.

* $\beta_1$ is the slope of the line associated with the regression model and represents the average effect of a one-unit increase in $X$ on $Y$.

* Back to the simple model. The model in linear regression is the *conditional mean* of $Y$ given the values in $X$ is expressed a linear function.

$$ y = f(x) = E(Y | X = x)$$

http://www.learner.org/courses/againstallodds/about/glossary.html

* The goal is to estimate the coefficients (e.g. $\beta_0$ and $\beta_1$). We represent the estimates of the coefficients with a "hat" on top of the letter.

$$ \hat{\beta}_0, \hat{\beta}_1 $$

* Once we estimate the coefficients $\hat{\beta}_0$ and $\hat{\beta}_1$, we can use these to predict new values of $Y$ given new data $X$.

$$\hat{y} = \hat{\beta}_0 + \hat{\beta}_1 x_1$$

* Multiple linear regression is when you have more than one independent variable and the estimation involves matrices
    * $X_1$, $X_2$, $X_3$, $\ldots$

* How do you estimate the coefficients?
    * There are many ways to fit a linear regression model
    * The method called **least squares** is the most common methods
    * We will discuss least squares

$$ Y = \beta_0 + \beta_1 X_1 + \ldots + \beta_p X_p + \epsilon$$

### Estimating $\hat\beta$: Least squares
***
[Least squares](http://en.wikipedia.org/wiki/Least_squares) is a method that can estimate the coefficients of a linear model by minimizing the squared residuals:

$$ \mathscr{L} = \sum_{i=1}^N \epsilon_i^2 = \sum_{i=1}^N \left( y_i - \hat{y}_i \right)^2  = \sum_{i=1}^N \left(y_i - \left(\beta_0 + \beta_1 x_i\right)\right)^2 $$

where $N$ is the number of observations and $\epsilon$ represents a residual or error, ACTUAL - PREDICTED.

#### Estimating the intercept $\hat{\beta_0}$ for the simple linear model

We want to minimize the squared residuals and solve for $\hat{\beta_0}$ so we take the partial derivative of $\mathscr{L}$ with respect to $\hat{\beta_0}$

$
\begin{align}
\frac{\partial \mathscr{L}}{\partial \hat{\beta_0}} &= \frac{\partial}{\partial \hat{\beta_0}} \sum_{i=1}^N \epsilon^2 \\
&= \frac{\partial}{\partial \hat{\beta_0}} \sum_{i=1}^N \left( y_i - \hat{y}_i \right)^2 \\
&= \frac{\partial}{\partial \hat{\beta_0}} \sum_{i=1}^N \left( y_i - \left( \hat{\beta}_0 + \hat{\beta}_1 x_i \right) \right)^2 \\
&= -2 \sum_{i=1}^N \left( y_i - \left( \hat{\beta}_0 + \hat{\beta}_1 x_i \right) \right) \hspace{25mm} \mbox{(by chain rule)} \\
&= -2 \sum_{i=1}^N (y_i - \hat{\beta}_0 - \hat{\beta}_1 x_i) \\
&= -2 \left[ \left( \sum_{i=1}^N y_i \right) - N \hat{\beta_0} - \hat{\beta}_1 \left( \sum_{i=1}^N x_i
\right) \right] \\
& 2 \left[ N \hat{\beta}_0 + \hat{\beta}_1 \sum_{i=1}^N x_i - \sum_{i=1}^N y_i \right] = 0 \hspace{20mm} \mbox{(Set equal to 0 and solve for $\hat{\beta}_0$)} \\
& N \hat{\beta}_0 + \hat{\beta}_1 \sum_{i=1}^N x_i - \sum_{i=1}^N y_i = 0 \\
& N \hat{\beta}_0 = \sum_{i=1}^N y_i - \hat{\beta}_1 \sum_{i=1}^N x_i \\
& \hat{\beta}_0 = \frac{\sum_{i=1}^N y_i - \hat{\beta}_1 \sum_{i=1}^N x_i}{N} \\
& \hat{\beta}_0 = \frac{\sum_{i=1}^N y_i}{N} - \hat{\beta}_1 \frac{\sum_{i=1}^N x_i}{N} \\
& \boxed{\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x}}
\end{align}
$

Using this new information, we can compute the estimate for $\hat{\beta}_1$ by taking the partial derivative of $\mathscr{L}$ with respect to $\hat{\beta}_1$.

$
\begin{align}
\frac{\partial \mathscr{L}}{\partial \hat{\beta_1}} &= \frac{\partial}{\partial \hat{\beta_1}} \sum_{i=1}^N \epsilon^2 \\
&= \frac{\partial}{\partial \hat{\beta_1}} \sum_{i=1}^N \left( y_i - \hat{y}_i \right)^2 \\
&= \frac{\partial}{\partial \hat{\beta_1}} \sum_{i=1}^N \left( y_i - \left( \hat{\beta}_0 + \hat{\beta}_1 x_i \right) \right)^2 \\
&= 2 \sum_{i=1}^N \left( y_i - \left( \hat{\beta}_0 + \hat{\beta}_1 x_i \right) \right) \left( -x_i \right) \hspace{25mm}\mbox{(by chain rule)} \\
&= -2 \sum_{i=1}^N x_i \left( y_i - \hat{\beta}_0 - \hat{\beta}_1 x_i \right) \\
&= -2 \sum_{i=1}^N x_i (y_i - \hat{\beta}_0 x_i - \hat{\beta}_1 x_i^2) \\
&= -2 \sum_{i=1}^N x_i (y_i - \left( \bar{y} - \hat{\beta}_1 \bar{x} \right) x_i - \hat{\beta}_1 x_i^2) \\
&= -2 \sum_{i=1}^N (x_i y_i - \bar{y}x_i + \hat{\beta}_1\bar{x}x_i - \hat{\beta}_1 x_i^2) \\
&= -2 \left[ \sum_{i=1}^N x_i y_i - \bar{y} \sum_{i=1}^N x_i + \hat{\beta}_1\bar{x}\sum_{i=1}^N x_i - \hat{\beta}_1 \sum_{i=1}^N x_i^2 \right] \\
&= -2 \left[ \hat{\beta}_1 \left\{ \bar{x} \sum_{i=1}^N x_i - \sum_{i=1}^N x_i^2 \right\} + \left\{ \sum_{i=1}^N x_i y_i - \bar{y} \sum_{i=1}^N x_i \right\}\right] \\
& 2 \left[ \hat{\beta}_1 \left\{ \sum_{i=1}^N x_i^2 - \bar{x} \sum_{i=1}^N x_i \right\} + \left\{ \bar{y} \sum_{i=1}^N x_i - \sum_{i=1}^N x_i y_i \right\} \right] = 0 \\
& \hat{\beta}_1 = \frac{-\left( \bar{y} \sum_{i=1}^N x_i - \sum_{i=1}^N x_i y_i \right)}{\sum_{i=1}^N x_i^2 - \bar{x}\sum_{i=1}^N x_i} \\
&= \frac{\sum_{i=1}^N x_i y_i - \bar{y} \sum_{i=1}^N x_i}{\sum_{i=1}^N x_i^2 - \bar{x} \sum_{i=1}^N x_i} \\
& \boxed{\hat{\beta}_1 = \frac{\sum_{i=1}^N x_i y_i - \bar{x}\bar{y}n}{\sum_{i=1}^N x_i^2 - n \bar{x}^2}}
\end{align}
$

The solution can be written in compact matrix notation as

$$\hat\beta =  (X^T X)^{-1}X^T Y$$

We wanted to show you this in case you remember linear algebra, in order for this solution to exist we need $X^T X$ to be invertible. Of course this requires a few extra assumptions, $X$ must be full rank so that $X^T X$ is invertible, etc. Basically, $X^T X$ is full rank if all rows and columns are linearly independent. This has a loose relationship to variables and observations being independent respective. **This is important for us because this means that having redundant features in our regression models will lead to poorly fitting (and unstable) models.** We'll see an implementation of this in the extra linear regression example.

***
# Part 2: Exploratory Data Analysis for Linear Relationships

The [Boston Housing data set](https://archive.ics.uci.edu/ml/datasets/Housing) contains information about the housing values in suburbs of Boston.  This dataset was originally taken from the StatLib library which is maintained at Carnegie Mellon University and is now available on the UCI Machine Learning Repository.

## Load the Boston Housing data set from `sklearn`
***

This data set is available in the [sklearn](http://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_boston.html#sklearn.datasets.load_boston) python module which is how we will access it today.

```python
from sklearn.datasets import load_boston
import pandas as pd

boston = load_boston()
```

```python
boston.keys()
```

    dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])

```python
boston.data.shape
```

    (506, 13)

```python
# Print column names
print(boston.feature_names)
```

    ['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
     'B' 'LSTAT']

```python
# Print description of Boston housing data set
print(boston.DESCR)
```

    .. _boston_dataset:

    Boston house prices dataset
    ---------------------------

    **Data Set Characteristics:**

        :Number of Instances: 506

        :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

        :Attribute Information (in order):
            - CRIM     per capita crime rate by town
            - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
            - INDUS    proportion of non-retail business acres per town
            - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
            - NOX      nitric oxides concentration (parts per 10 million)
            - RM       average number of rooms per dwelling
            - AGE      proportion of owner-occupied units built prior to 1940
            - DIS      weighted distances to five Boston employment centres
            - RAD      index of accessibility to radial highways
            - TAX      full-value property-tax rate per $10,000
            - PTRATIO  pupil-teacher ratio by town
            - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
            - LSTAT    % lower status of the population
            - MEDV     Median value of owner-occupied homes in $1000's

        :Missing Attribute Values: None

        :Creator: Harrison, D. and Rubinfeld, D.L.

    This is a copy of UCI ML housing dataset.
    https://archive.ics.uci.edu/ml/machine-learning-databases/housing/

    This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.

    The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
    prices and the demand for clean air', J. Environ. Economics & Management,
    vol.5, 81-102, 1978.   Used in Belsley, Kuh & Welsch, 'Regression diagnostics
    ...', Wiley, 1980.   N.B. Various transformations are used in the table on
    pages 244-261 of the latter.

    The Boston house-price data has been used in many machine learning papers that address regression
    problems.

    .. topic:: References

       - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
       - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.

Now let's explore the data set itself.

```python
bos = pd.DataFrame(boston.data)
bos.head()
```

      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      0
      0.00632
      18.0
      2.31
      0.0
      0.538
      6.575
      65.2
      4.0900
      1.0
      296.0
      15.3
      396.90
      4.98

      1
      0.02731
      0.0
      7.07
      0.0
      0.469
      6.421
      78.9
      4.9671
      2.0
      242.0
      17.8
      396.90
      9.14

      2
      0.02729
      0.0
      7.07
      0.0
      0.469
      7.185
      61.1
      4.9671
      2.0
      242.0
      17.8
      392.83
      4.03

      3
      0.03237
      0.0
      2.18
      0.0
      0.458
      6.998
      45.8
      6.0622
      3.0
      222.0
      18.7
      394.63
      2.94

      4
      0.06905
      0.0
      2.18
      0.0
      0.458
      7.147
      54.2
      6.0622
      3.0
      222.0
      18.7
      396.90
      5.33

There are no column names in the DataFrame. Let's add those.

```python
bos.columns = boston.feature_names
bos.head()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      PTRATIO
      B
      LSTAT

      0
      0.00632
      18.0
      2.31
      0.0
      0.538
      6.575
      65.2
      4.0900
      1.0
      296.0
      15.3
      396.90
      4.98

      1
      0.02731
      0.0
      7.07
      0.0
      0.469
      6.421
      78.9
      4.9671
      2.0
      242.0
      17.8
      396.90
      9.14

      2
      0.02729
      0.0
      7.07
      0.0
      0.469
      7.185
      61.1
      4.9671
      2.0
      242.0
      17.8
      392.83
      4.03

      3
      0.03237
      0.0
      2.18
      0.0
      0.458
      6.998
      45.8
      6.0622
      3.0
      222.0
      18.7
      394.63
      2.94

      4
      0.06905
      0.0
      2.18
      0.0
      0.458
      7.147
      54.2
      6.0622
      3.0
      222.0
      18.7
      396.90
      5.33

Now we have a pandas DataFrame called `bos` containing all the data we want to use to predict Boston Housing prices.  Let's create a variable called `PRICE` which will contain the prices. This information is contained in the `target` data.

```python
print(boston.target.shape)
```

    (506,)

```python
bos['PRICE'] = boston.target
bos.head()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      PTRATIO
      B
      LSTAT
      PRICE

      0
      0.00632
      18.0
      2.31
      0.0
      0.538
      6.575
      65.2
      4.0900
      1.0
      296.0
      15.3
      396.90
      4.98
      24.0

      1
      0.02731
      0.0
      7.07
      0.0
      0.469
      6.421
      78.9
      4.9671
      2.0
      242.0
      17.8
      396.90
      9.14
      21.6

      2
      0.02729
      0.0
      7.07
      0.0
      0.469
      7.185
      61.1
      4.9671
      2.0
      242.0
      17.8
      392.83
      4.03
      34.7

      3
      0.03237
      0.0
      2.18
      0.0
      0.458
      6.998
      45.8
      6.0622
      3.0
      222.0
      18.7
      394.63
      2.94
      33.4

      4
      0.06905
      0.0
      2.18
      0.0
      0.458
      7.147
      54.2
      6.0622
      3.0
      222.0
      18.7
      396.90
      5.33
      36.2

## EDA and Summary Statistics
***

Let's explore this data set.  First we use `describe()` to get basic summary statistics for each of the columns.

```python
bos.describe()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      PTRATIO
      B
      LSTAT
      PRICE

      count
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000

      mean
      3.613524
      11.363636
      11.136779
      0.069170
      0.554695
      6.284634
      68.574901
      3.795043
      9.549407
      408.237154
      18.455534
      356.674032
      12.653063
      22.532806

      std
      8.601545
      23.322453
      6.860353
      0.253994
      0.115878
      0.702617
      28.148861
      2.105710
      8.707259
      168.537116
      2.164946
      91.294864
      7.141062
      9.197104

      min
      0.006320
      0.000000
      0.460000
      0.000000
      0.385000
      3.561000
      2.900000
      1.129600
      1.000000
      187.000000
      12.600000
      0.320000
      1.730000
      5.000000

      25%
      0.082045
      0.000000
      5.190000
      0.000000
      0.449000
      5.885500
      45.025000
      2.100175
      4.000000
      279.000000
      17.400000
      375.377500
      6.950000
      17.025000

      50%
      0.256510
      0.000000
      9.690000
      0.000000
      0.538000
      6.208500
      77.500000
      3.207450
      5.000000
      330.000000
      19.050000
      391.440000
      11.360000
      21.200000

      75%
      3.677083
      12.500000
      18.100000
      0.000000
      0.624000
      6.623500
      94.075000
      5.188425
      24.000000
      666.000000
      20.200000
      396.225000
      16.955000
      25.000000

      max
      88.976200
      100.000000
      27.740000
      1.000000
      0.871000
      8.780000
      100.000000
      12.126500
      24.000000
      711.000000
      22.000000
      396.900000
      37.970000
      50.000000

### Scatterplots
***

Let's look at some scatter plots for three variables: 'CRIM' (per capita crime rate), 'RM' (number of rooms) and 'PTRATIO' (pupil-to-teacher ratio in schools).

```python
plt.scatter(bos.CRIM, bos.PRICE)
plt.xlabel("Per capita crime rate by town (CRIM)")
plt.ylabel("Housing Price")
plt.title("Relationship between CRIM and Price")
```

Part 2 Checkup Exercise Set I

Exercise: What kind of relationship do you see? e.g. positive, negative?  linear? non-linear? Is there anything else strange or interesting about the data? What about outliers?

Exercise: Create scatter plots between *RM* and *PRICE*, and *PTRATIO* and *PRICE*. Label your axes appropriately using human readable labels. Tell a story about what you see.

Exercise: What are some other numeric variables of interest? Why do you think they are interesting? Plot scatterplots with these variables and *PRICE* (house price) and tell a story about what you see.

```python
# your turn: describe relationship

# There is a linear relationship between Housing price and Crime Rate per capita by town.
# The crime rate is low where the housing prices are high. However, it is worth noting that we do not have
# as many data points for the crime rate when the housing price is low.
```

```python
# your turn: scatter plot between *RM* and *PRICE*
plt.scatter(bos.RM, bos.PRICE)
plt.xlabel("Avg. Rooms per dwelling (RM)")
plt.ylabel("Housing Price")
plt.title("Relationship between RM and Price")
```

```python
# your turn: scatter plot between *PTRATIO* and *PRICE*
plt.scatter(bos.PTRATIO, bos.PRICE)
plt.xlabel("Pupil-Teacher Ratio (PTRATIO)")
plt.ylabel("Housing Price")
plt.title("Relationship between PTRATIO and Price")
```

```python
# your turn: create some other scatter plots
plt.scatter(bos.AGE, bos.CRIM)
plt.xlabel("Per Capita Crime by town (CRIM)")
plt.ylabel("Age")
plt.title("Relationship between CRIM and AGE")
```

### Scatterplots using Seaborn
***

[Seaborn](https://stanford.edu/~mwaskom/software/seaborn/) is a cool Python plotting library built on top of matplotlib. It provides convenient syntax and shortcuts for many common types of plots, along with better-looking defaults.

We can also use [seaborn regplot](https://stanford.edu/~mwaskom/software/seaborn/tutorial/regression.html#functions-to-draw-linear-regression-models) for the scatterplot above. This provides automatic linear regression fits (useful for data exploration later on). Here's one example below.

```python
sns.regplot(y="PRICE", x="RM", data=bos, fit_reg = True)
```

### Histograms
***

```python
plt.hist(np.log(bos.CRIM))
plt.title("CRIM")
plt.xlabel("Crime rate per capita")
plt.ylabel("Frequencey")
plt.show()
```

Part 2 Checkup Exercise Set II

Exercise: In the above histogram, we took the logarithm of the crime rate per capita. Repeat this histogram without taking the log. What was the purpose of taking the log? What do we gain by making this transformation? What do you now notice about this variable that is not obvious without making the transformation?

Exercise: Plot the histogram for *RM* and *PTRATIO* against each other, along with the two variables you picked in the previous section. We are looking for correlations in predictors here.

In above chart we took log to handle skewness of the data. As we can se when we plot below chart without taking log some points in CRIM variable are much larger than bulk of its data and by making this transformation we are able to standardize the input.

```python
#your turn
plt.hist(bos.CRIM)
plt.title("CRIM")
plt.xlabel("Crime rate per capita")
plt.ylabel("Frequencey")
plt.show()
```

```python
#your turn
plt.hist(np.log(bos.RM))
plt.title("RM")
plt.xlabel("Rooms per dwelling")
plt.ylabel("Frequencey")
plt.show()
```

## Part 3: Linear Regression with Boston Housing Data Example
***

Here,

$Y$ = boston housing prices (called "target" data in python, and referred to as the dependent variable or response variable)

and

$X$ = all the other features (or independent variables, predictors or explanatory variables)

which we will use to fit a linear regression model and predict Boston housing prices. We will use the least-squares method to estimate the coefficients.

We'll use two ways of fitting a linear regression. We recommend the first but the second is also powerful in its features.

### Fitting Linear Regression using `statsmodels`
***
[Statsmodels](http://statsmodels.sourceforge.net/) is a great Python library for a lot of basic and inferential statistics. It also provides basic regression functions using an R-like syntax, so it's commonly used by statisticians. While we don't cover statsmodels officially in the Data Science Intensive workshop, it's a good library to have in your toolbox. Here's a quick example of what you could do with it. The version of least-squares we will use in statsmodels is called *ordinary least-squares (OLS)*. There are many other versions of least-squares such as [partial least squares (PLS)](https://en.wikipedia.org/wiki/Partial_least_squares_regression) and [weighted least squares (WLS)](https://en.wikipedia.org/wiki/Iteratively_reweighted_least_squares).

```python
# Import regression modules
import statsmodels.api as sm
from statsmodels.formula.api import ols
```

```python
# statsmodels works nicely with pandas dataframes
# The thing inside the "quotes" is called a formula, a bit on that below
m = ols('PRICE ~ RM',bos).fit()
print(m.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                  PRICE   R-squared:                       0.484
    Model:                            OLS   Adj. R-squared:                  0.483
    Method:                 Least Squares   F-statistic:                     471.8
    Date:                Thu, 05 Dec 2019   Prob (F-statistic):           2.49e-74
    Time:                        12:20:49   Log-Likelihood:                -1673.1
    No. Observations:                 506   AIC:                             3350.
    Df Residuals:                     504   BIC:                             3359.
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    Intercept    -34.6706      2.650    -13.084      0.000     -39.877     -29.465
    RM             9.1021      0.419     21.722      0.000       8.279       9.925
    ==============================================================================
    Omnibus:                      102.585   Durbin-Watson:                   0.684
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):              612.449
    Skew:                           0.726   Prob(JB):                    1.02e-133
    Kurtosis:                       8.190   Cond. No.                         58.4
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

#### Interpreting coefficients

There is a ton of information in this output. But we'll concentrate on the coefficient table (middle table). We can interpret the `RM` coefficient (9.1021) by first noticing that the p-value (under `P>|t|`) is so small, basically zero. This means that the number of rooms, `RM`, is a statisticall significant predictor of `PRICE`. The regression coefficient for `RM` of 9.1021 means that *on average, each additional room is associated with an increase of $\$9,100$ in house price net of the other variables*. The confidence interval gives us a range of plausible values for this average change, about ($\$8,279, \$9,925$), definitely not chump change.

In general, the $\hat{\beta_i}, i > 0$ can be interpreted as the following: "A one unit increase in $x_i$ is associated with, on average, a $\hat{\beta_i}$ increase/decrease in $y$ net of all other variables."

On the other hand, the interpretation for the intercept, $\hat{\beta}_0$ is the average of $y$ given that all of the independent variables $x_i$ are 0.

####  `statsmodels` formulas
***
This formula notation will seem familiar to `R` users, but will take some getting used to for people coming from other languages or are new to statistics.

The formula gives instruction for a general structure for a regression call. For `statsmodels` (`ols` or `logit`) calls you need to have a Pandas dataframe with column names that you will add to your formula. In the below example you need a pandas data frame that includes the columns named (`Outcome`, `X1`,`X2`, ...), but you don't need to build a new dataframe for every regression. Use the same dataframe with all these things in it. The structure is very simple:

`Outcome ~ X1`

But of course we want to to be able to handle more complex models, for example multiple regression is doone like this:

`Outcome ~ X1 + X2 + X3`

In general, a formula for an OLS multiple linear regression is

`Y ~ X1 + X2 + ... + Xp`

This is the very basic structure but it should be enough to get you through the homework. Things can get much more complex. You can force statsmodels to treat variables as categorical with the `C()` function, call numpy functions to transform data such as `np.log` for extremely-skewed data, or fit a model without an intercept by including `- 1` in the formula. For a quick run-down of further uses see the `statsmodels` [help page](http://statsmodels.sourceforge.net/devel/example_formulas.html).

Let's see how our model actually fit our data. We can see below that there is a ceiling effect, we should probably look into that. Also, for large values of $Y$ we get underpredictions, most predictions are below the 45-degree gridlines.

Part 3 Checkup Exercise Set I

Exercise: Create a scatterplot between the predicted prices, available in `m.fittedvalues` (where `m` is the fitted model) and the original prices. How does the plot look? Do you notice anything interesting or weird in the plot? Comment on what you see.

```python
# your turn
true_prices = bos.PRICE
predicted_prices = m.fittedvalues

plt.scatter(true_prices, predicted_prices)
plt.title("Statmodels: Original Prices vs Predicted Prices")
plt.xlabel("Original Prices")
plt.ylabel("Predicted Prices")
plt.show()
```

### Fitting Linear Regression using `sklearn`

```python
from sklearn.linear_model import LinearRegression
X = bos.drop('PRICE', axis = 1)

# This creates a LinearRegression object
lm = LinearRegression()
lm
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

#### What can you do with a LinearRegression object?
***
Check out the scikit-learn [docs here](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html). We have listed the main functions here. Most machine learning models in scikit-learn follow this same API of fitting a model with `fit`, making predictions with `predict` and the appropriate scoring function `score` for each model.

Main functions | Description
--- | ---
`lm.fit()` | Fit a linear model
`lm.predit()` | Predict Y using the linear model with estimated coefficients
`lm.score()` | Returns the coefficient of determination (R^2). *A measure of how well observed outcomes are replicated by the model, as the proportion of total variation of outcomes explained by the model*

#### What output can you get?

```python
# Look inside lm object
lm.score
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

Output | Description
--- | ---
`lm.coef_` | Estimated coefficients
`lm.intercept_` | Estimated intercept

### Fit a linear model
***

The `lm.fit()` function estimates the coefficients the linear regression using least squares.

```python
# Use all 13 predictors to fit linear regression model
lm.fit(X, bos.PRICE)
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

Part 3 Checkup Exercise Set II

Exercise: How would you change the model to not fit an intercept term? Would you recommend not having an intercept? Why or why not? For more information on why to include or exclude an intercept, look [here](https://stats.idre.ucla.edu/other/mult-pkg/faq/general/faq-what-is-regression-through-the-origin/).

Exercise: One of the assumptions of the linear model is that the residuals must be i.i.d. (independently and identically distributed). To satisfy this, is it enough that the residuals are normally distributed? Explain your answer.

Exercise: True or false. To use linear regression, $Y$ must be normally distributed. Explain your answer.

```python
# your turn
lm.fit_intercept = False
lm.fit(X, bos.PRICE)
```

    LinearRegression(copy_X=True, fit_intercept=False, n_jobs=None, normalize=False)

### Estimated intercept and coefficients

Let's look at the estimated coefficients from the linear model using `1m.intercept_` and `lm.coef_`.

After we have fit our linear regression model using the least squares method, we want to see what are the estimates of our coefficients $\beta_0$, $\beta_1$, ..., $\beta_{13}$:

$$ \hat{\beta}_0, \hat{\beta}_1, \ldots, \hat{\beta}_{13} $$

```python
print('Estimated intercept coefficient: {}'.format(lm.intercept_))
```

    Estimated intercept coefficient: 0.0

```python
print('Number of coefficients: {}'.format(len(lm.coef_)))
```

    Number of coefficients: 13

```python
# The coefficients
pd.DataFrame({'features': X.columns, 'estimatedCoefficients': lm.coef_})[['features', 'estimatedCoefficients']]
```

      features
      estimatedCoefficients

      0
      CRIM
      -0.092897

      1
      ZN
      0.048715

      2
      INDUS
      -0.004060

      3
      CHAS
      2.853999

      4
      NOX
      -2.868436

      5
      RM
      5.928148

      6
      AGE
      -0.007269

      7
      DIS
      -0.968514

      8
      RAD
      0.171151

      9
      TAX
      -0.009396

      10
      PTRATIO
      -0.392191

      11
      B
      0.014906

      12
      LSTAT
      -0.416304

### Predict Prices

We can calculate the predicted prices ($\hat{Y}_i$) using `lm.predict`.

$$ \hat{Y}_i = \hat{\beta}_0 + \hat{\beta}_1 X_1 + \ldots \hat{\beta}_{13} X_{13} $$

```python
# first five predicted prices
lm.predict(X)[0:5]
```

    array([29.09826353, 24.50227548, 31.22742641, 29.7071035 , 29.56479572])

Part 3 Checkup Exercise Set III

Exercise: Histogram: Plot a histogram of all the predicted prices. Write a story about what you see. Describe the shape, center and spread of the distribution. Are there any outliers? What might be the reason for them? Should we do anything special with them?

Exercise: Scatterplot: Let's plot the true prices compared to the predicted prices to see they disagree (we did this with `statsmodels` before).

Exercise: We have looked at fitting a linear model in both `statsmodels` and `scikit-learn`. What are the advantages and disadvantages of each based on your exploration? Based on the information provided by both packages, what advantage does `statsmodels` provide?

```python
# your turn
predicted_prices = lm.predict(X)
plt.hist(predicted_prices)
plt.title('Predicted Prices')
plt.ylabel('Frequences')
plt.xlabel('Prices')
plt.show()
```

```python
predicted_prices.shape
```

    (506,)

We can see that majority of the predicted prices are in the range of 15-30. The shape of the distribution is like a bell curve and the entire range of prices is between 0 to 50.

```python
plt.scatter(bos.PRICE, predicted_prices)
plt.title("LinearRegression: Original Prices vs Predicted Prices")
plt.xlabel("Original Prices")
plt.ylabel("Predicted Prices")
plt.show()
```

One advantage of `statsmodel` is that it provides a ton of statistical information about the output which the scikit-learn does not provide.

### Evaluating the Model: Sum-of-Squares

The partitioning of the sum-of-squares shows the variance in the predictions explained by the model and the variance that is attributed to error.

$$TSS = ESS + RSS$$

#### Residual Sum-of-Squares (aka $RSS$)

The residual sum-of-squares is one of the basic ways of quantifying how much error exists in the fitted model. We will revisit this in a bit.

$$ RSS = \sum_{i=1}^N r_i^2 = \sum_{i=1}^N \left(y_i - \left(\beta_0 + \beta_1 x_i\right)\right)^2 $$

```python
print(np.sum((bos.PRICE - lm.predict(X)) ** 2))
```

    12228.046261044003

#### Explained Sum-of-Squares (aka $ESS$)

The explained sum-of-squares measures the variance explained by the regression model.

$$ESS = \sum_{i=1}^N \left( \hat{y}_i - \bar{y} \right)^2 = \sum_{i=1}^N \left( \left( \hat{\beta}_0 + \hat{\beta}_1 x_i \right) - \bar{y} \right)^2$$

```python
print(np.sum((lm.predict(X) - np.mean(bos.PRICE))**2))
```

    31908.78970831641

### Evaluating the Model: The Coefficient of Determination ($R^2$)

The coefficient of determination, $R^2$, tells us the percentage of the variance in the response variable $Y$ that can be explained by the linear regression model.

$$ R^2 = \frac{ESS}{TSS} $$

The $R^2$ value is one of the most common metrics that people use in describing the quality of a model, but it is important to note that *$R^2$ increases artificially as a side-effect of increasing the number of independent variables.* While $R^2$ is reported in almost all statistical packages, another metric called the *adjusted $R^2$* is also provided as it takes into account the number of variables in the model, and can sometimes even be used for non-linear regression models!

$$R_{adj}^2 = 1 - \left( 1 - R^2 \right) \frac{N - 1}{N - K - 1} = R^2 - \left( 1 - R^2 \right) \frac{K}{N - K - 1} = 1 - \frac{\frac{RSS}{DF_R}}{\frac{TSS}{DF_T}}$$

where $N$ is the number of observations, $K$ is the number of variables, $DF_R = N - K - 1$ is the degrees of freedom associated with the residual error and $DF_T = N - 1$ is the degrees of the freedom of the total error.

### Evaluating the Model: Mean Squared Error and the $F$-Statistic
***
The mean squared errors are just the *averages* of the sum-of-squares errors over their respective degrees of freedom.

$$MSR = \frac{ESS}{K}$$

$$MSE = \frac{RSS}{N-K-1}$$

**Remember:** Notation may vary across resources particularly the use of *R* and *E* in *RSS/ESS* and *MSR/MSE*. In some resources, E = explained and R = residual. In other resources, E = error and R = regression (explained). **This is a very important distinction that requires looking at the formula to determine which naming scheme is being used.**

Given the MSR and MSE, we can now determine whether or not the entire model we just fit is even statistically significant. We use an $F$-test for this. The null hypothesis is that all of the $\beta$ coefficients are zero, that is, none of them have any effect on $Y$. The alternative is that *at least one* $\beta$ coefficient is nonzero, but it doesn't tell us which one in a multiple regression:

$$H_0: \beta_i = 0, \mbox{for all $i$} \\
H_A: \beta_i > 0, \mbox{for some $i$}$$

$$F = \frac{MSR}{MSE} = \left( \frac{R^2}{1 - R^2} \right) \left( \frac{N - K - 1}{K} \right)$$

Once we compute the $F$-statistic, we can use the $F$-distribution with $N-K$ and $K-1$ degrees of degrees of freedom to get a p-value.

**Warning!** The $F$-statistic mentioned in this section is NOT the same as the F1-measure or F1-value discused in Unit 7.

Part 3 Checkup Exercise Set IV

Let's look at the relationship between `PTRATIO` and housing price.

Exercise: Try fitting a linear regression model using only the 'PTRATIO' (pupil-teacher ratio by town) and interpret the intercept and the coefficients.

Exercise: Calculate (or extract) the $R^2$ value. What does it tell you?

Exercise: Compute the $F$-statistic. What does it tell you?

Exercise: Take a close look at the $F$-statistic and the $t$-statistic for the regression coefficient. What relationship do you notice? Note that this relationship only applies in *simple* linear regression models.

```python
# your turn
X = pd.DataFrame({'PTRATIO':bos.PTRATIO})
lm.fit_intercept=True
lm.fit(X, bos.PRICE)
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

```python
lm.intercept_
```

    62.34462747483265

```python
lm.coef_
```

    array([-2.1571753])

```python
Rsquare = lm.score(X, bos.PRICE)
Rsquare #The Rquare value is not negative but is not closer to 1. It tells us that the model is performing okay.
```

    0.257847318009223

```python
N = X.shape[0]
K = X.shape[1]
fstatistic = (Rsquare/(1-Rsquare))/(N-K-1)
fstatistic
```

    0.0006893484775592698

Part 3 Checkup Exercise Set V

Fit a linear regression model using three independent variables

 'CRIM' (per capita crime rate by town)

 'RM' (average number of rooms per dwelling)

 'PTRATIO' (pupil-teacher ratio by town)

Exercise: Compute or extract the $F$-statistic. What does it tell you about the model?

Exercise: Compute or extract the $R^2$ statistic. What does it tell you about the model?

Exercise: Which variables in the model are significant in predicting house price? Write a story that interprets the coefficients.

```python
# your turn
X = pd.DataFrame({'CRIM':bos.CRIM, 'RM':bos.RM, 'PTRATIO':bos.PTRATIO})
lm.fit_intercept = True
lm.fit(X, bos.PRICE)
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

```python
Rsquare = lm.score(X, bos.PRICE)
Rsquare #The Rquare value is closer to 0.6. It tells us that the model will be able to generalize well on new data.
```

    0.5943412940723471

```python
N = X.shape[0]
K = X.shape[1]
fstatistic = (Rsquare/(1-Rsquare))/(N-K-1)
fstatistic
```

    0.002918578557887727

## Part 4: Comparing Models

During modeling, there will be times when we want to compare models to see which one is more predictive or fits the data better. There are many ways to compare models, but we will focus on two.

### The $F$-Statistic Revisited

The $F$-statistic can also be used to compare two *nested* models, that is, two models trained on the same dataset where one of the models contains a *subset* of the variables of the other model. The *full* model contains $K$ variables and the *reduced* model contains a subset of these $K$ variables. This allows us to add additional variables to a base model and then test if adding the variables helped the model fit.

$$F = \frac{\left( \frac{RSS_{reduced} - RSS_{full}}{DF_{reduced} - DF_{full}} \right)}{\left( \frac{RSS_{full}}{DF_{full}} \right)}$$

where $DF_x = N - K_x - 1$ where $K_x$ is the number of variables in model $x$.

### Akaike Information Criterion (AIC)

Another statistic for comparing two models is AIC, which is based on the likelihood function and takes into account the number of variables in the model.

$$AIC = 2 K - 2 \log_e{L}$$

where $L$ is the likelihood of the model. AIC is meaningless in the absolute sense, and is only meaningful when compared to AIC values from other models. Lower values of AIC indicate better fitting models.

`statsmodels` provides the AIC in its output.

Part 4 Checkup Exercises

Exercise: Find another variable (or two) to add to the model we built in Part 3. Compute the $F$-test comparing the two models as well as the AIC. Which model is better?

```python
# your turn
X = pd.DataFrame({'CRIM':bos.CRIM, 'RM':bos.RM, 'PTRATIO':bos.PTRATIO, 'AGE': bos.AGE})
lm.fit_intercept = True
lm.fit(X, bos.PRICE)

Rsquare = lm.score(X, bos.PRICE)
print('Rsquare: {}'.format(Rsquare))

N = X.shape[0]
K = X.shape[1]
fstatistic = (Rsquare/(1-Rsquare))/(N-K-1)
print('Fstatistic: {}'.format(fstatistic))
print('The model performance imporoves as we add more features into our training.')
```

    Rsquare: 0.6083475301004222
    Fstatistic: 0.0031003673421436534
    The model performance imporoves as we add more features into our training.

## Part 5: Evaluating the Model via Model Assumptions and Other Issues
***
Linear regression makes several assumptions. It is always best to check that these assumptions are valid after fitting a linear regression model.

**Linearity**. The dependent variable $Y$ is a linear combination of the regression coefficients and the independent variables $X$. This can be verified with a scatterplot of each $X$ vs. $Y$ and plotting correlations among $X$. Nonlinearity can sometimes be resolved by [transforming](https://onlinecourses.science.psu.edu/stat501/node/318) one or more independent variables, the dependent variable, or both. In other cases, a [generalized linear model](https://en.wikipedia.org/wiki/Generalized_linear_model) or a [nonlinear model](https://en.wikipedia.org/wiki/Nonlinear_regression) may be warranted.

**Constant standard deviation**. The SD of the dependent variable $Y$ should be constant for different values of X. We can check this by plotting each $X$ against $Y$ and verifying that there is no "funnel" shape showing data points fanning out as $X$ increases or decreases. Some techniques for dealing with non-constant variance include weighted least squares (WLS), [robust standard errors](https://en.wikipedia.org/wiki/Heteroscedasticity-consistent_standard_errors), or variance stabilizing transformations.

 **Normal distribution for errors**.  The $\epsilon$ term we discussed at the beginning are assumed to be normally distributed. This can be verified with a fitted values vs. residuals plot and verifying that there is no pattern, and with a quantile plot.
  $$ \epsilon_i \sim N(0, \sigma^2)$$
Sometimes the distributions of responses $Y$ may not be normally distributed at any given value of $X$.  e.g. skewed positively or negatively.

 **Independent errors**.  The observations are assumed to be obtained independently.

e.g. Observations across time may be correlated

There are some other issues that are important investigate with linear regression models.

**Correlated Predictors:** Care should be taken to make sure that the independent variables in a regression model are not too highly correlated. Correlated predictors typically do not majorly affect prediction, but do inflate standard errors of coefficients making interpretation unreliable. Common solutions are dropping the least important variables involved in the correlations, using regularlization, or, when many predictors are highly correlated, considering a dimension reduction technique such as principal component analysis (PCA).

**Influential Points:** Data points that have undue influence on the regression model. These points can be high leverage points or outliers. Such points are typically removed and the regression model rerun.

Part 5 Checkup Exercises

Take the reduced model from Part 3 to answer the following exercises. Take a look at [this blog post](http://mpastell.com/2013/04/19/python_regression/) for more information on using statsmodels to construct these plots.

Exercise: Construct a fitted values versus residuals plot. What does the plot tell you? Are there any violations of the model assumptions?

Exercise: Construct a quantile plot of the residuals. What does the plot tell you?

Exercise: What are some advantages and disadvantages of the fitted vs. residual and quantile plot compared to each other?

Exercise: Identify any outliers (if any) in your model and write a story describing what these outliers might represent.

Exercise: Construct a leverage plot and identify high leverage points in the model. Write a story explaining possible reasons for the high leverage points.

Exercise: Remove the outliers and high leverage points from your model and run the regression again. How do the results change?

```python
# Your turn.
plt.scatter(m.fittedvalues, m.resid)
plt.title('Fitted values vs Residuals')
plt.xlabel('Fitted values')
plt.ylabel('Residuals')
plt.show()
```

It is hard to see a close relationship between fitted values and residuals. Also there are outliers which makes it hard to construct a linear regression line.

```python
#quantile plot Residuals
stats.probplot(m.resid, dist='norm', plot=plt)
plt.show()
```

```python
#quantile plot Fitted Values
stats.probplot(m.fittedvalues, dist='norm', plot=plt)
plt.show()
```

With the quantile plots of fitted values and residuals a linear line is more apparent between similar data points and we can tell that there are very few normal distribution errors. The resuduals quantile plot has data points starting from -20 while for the fitted values they start from 0, so this plot has helped us identify the influential data points in both variables.

```python
#leverage plot
sm.graphics.plot_leverage_resid2(m)
```

Similar to the quantile plot the leverage plot is also used to identify the influential data points in our model and we can see a lot of data points towrds 0 but the range is skewed for both Residuals and Normalized residuals to 368. So these are our high leverage points.


```python
################################
########### Imports ############
################################
from sympy.utilities.lambdify import lambdify
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interactive, fixed, FloatSlider, HBox, Layout, Button, Label, Output, VBox
from IPython.display import display
import code7814 as wi
import sys
sys.path.append('../../python')
import dataPython as dp
#getting so that the halo component/total RC curve updates as bh widgets update

X=.12 #this is a currently random scale factor just to fit by eye. the model for the halo is wrong right now
def h(r,Mbh,arraysize):
    Me=X*Mbh*arraysize*wi.scale*wi.func(r)/max(wi.func(r)) #units: Msun/bh * dot *bh/dot = Msun
    return np.sqrt(Me*wi.GG/wi.rcut) #is it rc or just r???? units: km/s

def t(r,Mbh,arraysize):
    return np.sqrt((wi.d)**2
               +(wi.b)**2
               +(h(r,Mbh,arraysize))**2
               +(wi.g)**2)
img = plt.imread("A_spiral_snowflake.jpg") #import special snowflake ngc 6814, which has visual diameter about 27.6kpc\

# Define plotting function
def f(arraysize,Mbh):

    #fractions: now changing the number of dots plottd within each bracket, which changes as the slider
    #variable ("arraysize") changes

    #and trim to the first x elements of the pre-caclated radius arrays for each bracket
    arraysize=int(arraysize) #units: dot
    re = wi.rrr[:arraysize]
    anglee=wi.angle[:arraysize]

    #plot for each bracket, two equations for each bracket
    y=wi.c_y+re*np.sin(anglee) #y coordinates
    x=wi.c_x+re*np.cos(anglee) #x coordinates

    #below: changing the physical size of the plotted dots as the bh mass slider changes. the if else statement is fancy
    #but not necessary

    Mbh=1.5*Mbh #judging by eye
    if .1<=Mbh<1:
        Mbh=Mbh**1.5
    elif 1<=Mbh<2:
        Mbh=Mbh**2
    else:
        Mbh=Mbh**3 #display size of each marker

    f, (ax1, ax2) = plt.subplots(1, 2, sharey=False)
    f.set_figheight(15)
    f.set_figwidth(35)

     # Define r, for some reason this isn't working in code7814.py, so put directly here:
    data = dp.getXYdata_wXYerr('../../testing/7814/ngc7814data')
    r = np.asarray(data['xx'])

    ax1.scatter(x,y,color="r",marker='o',s=Mbh)
    ax1.imshow(img)
    ax1.axis('off')
    ax2.plot(r,h(r,Mbh,arraysize),label=("halo"),color='green')
    ax2.errorbar(r,wi.v_dat,yerr=wi.v_err1,fmt='bo',label='Data')
    ax2.plot(r,wi.b,label=("Bulge"),color='orange')
    ax2.plot(r,wi.d,label=("Disk"),color='purple')
    ax2.plot(r,wi.g,label=("Gas"),color='blue')
    ax2.plot(r,t(r,Mbh,arraysize),label=("Total Curve"),color='red')
    ax2.set_xlim([wi.minkpc, 19])
    ax2.set_ylim([0, 400])
    ax2.set_title('NGC 7814',fontsize = 40)
    ax2.set_ylabel('Velocity [km/s]',fontsize = 30)
    ax2.set_xlabel('radius [kpc]',fontsize = 30)
    ax2.legend(loc='upper right',prop={'size': 23},ncol=3)

    residuals = wi.v_dat - t(wi.r,Mbh,arraysize)
    # Determining errors
    errors = wi.v_err1**2 #second term is inclination uncertainty
    # Chi squared
    chisquared = np.sum(residuals**2/errors**2)
    #chisquared = stats.chisquare(v_dat,totalcurve(r,M,bpref,dpref,rc,rho00,gpref))
    reducedchisquared = chisquared * (1/(len(wi.r)-6))

    props = dict(boxstyle='round', facecolor='white', alpha=0.5)
    ax2.text(0.4,360,r"Reduced $\chi^2$:"+'\n'+"{:.2f}".format(reducedchisquared),bbox=props,size=30)

style = {'description_width': 'initial'}
layout = {'width':'600px'}

################################
######## Define Sliders ########
################################

#mass of each black hole
Mbh = FloatSlider(min=wi.minmass, max=wi.maxmass, step=wi.minmass,
                value=wi.start,
                description='Mass of each lil black holes [Msun]',
                readout= True,
                readout_format='.1f',
                orientation='horizontal',
                style=style, layout=layout)

#number of projected black dots slider
arraysize = FloatSlider(min=wi.Min, max=wi.Max, step=wi.stepN,
                value=wi.best_A,
                description='Number of lil black holes multiplied by scale %.0e'%wi.scale,
                readout= True,
                readout_format='.2d',
                orientation='horizontal',
                style=style, layout=layout)

def interactive_plot(f):
    interact = interactive(f, Mbh = Mbh, arraysize=arraysize,continuous_update=False)
    return interact

################################
########### Button #############
################################

# Button to revert back to Best Fit
button = Button(
    description="Best Fit",
    button_style='warning', # 'success', 'info', 'warning', 'danger' or ''
    icon='check')
out = Output()

def on_button_clicked(_):
    #display(Javascript('IPython.notebook.execute_cells_below()'))
    Mbh.value = wi.start
    arraysize.value=wi.best_A
button.on_click(on_button_clicked)

# displaying button and its output together
VBox([button,out,interactive_plot(f)])
```

    VBox(children=(Button(button_style='warning', description='Best Fit', icon='check', style=ButtonStyle()), Outp…

```python

```


# Export latex for ysprod models

```python
# Setup ipython environment
%load_ext autoreload
%autoreload 2
%matplotlib inline
# Import needed packages from positive
from positive import *
# Import other usefuls
from os.path import expanduser
from kerr import *
import pickle
from numpy import *
from scipy.io import loadmat
# Import ysprod models
with open('./ysmodels.pickle','rb') as f:
    ysfits = pickle.load(f)
#
from matplotlib.pyplot import *
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload
    (positive)>> Applying custom matplotlib settings.

## Misc

```python

z = sorted(ysfits.keys(),key= lambda X: X[0])

f = open('/Users/book/KOALA/positive_dev/docs/mvfits_paper/src/ysprod_eqns.tex','wb')
f.write('\\begin{align}\n')
for k in z:
    y = ysfits[k]
    #print '\n'
    #print k
    #print y
    f.write('   %% %s\n'%str(k))
    f.write('  \\label{eq:ys%i%i%i%i%i}\n'%k)
    f.write('  '+y.__str_latex__(precision=5,labels=[r'\sigma_{%i%i%i%i%i}'%k,[r'\kappa'],'']).replace(r'\;',r'\,').replace('e+0','\\times 10^'))
    f.write('   \\\\\n')
f.write('\\end{align}\n')
f.close()
```

## Plots

```python

#
import matplotlib as mpl
mpl.rcParams['xtick.labelsize'] = 12
mpl.rcParams['ytick.labelsize'] = mpl.rcParams['xtick.labelsize']

#
z = sorted(ysfits.keys(),key= lambda X: X[0])[::-1]
# z = [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)]
Q = 81
jf = 0.995*cos(pi*linspace(0,1,Q))

#
fig = figure( figsize=1.6*figaspect(1) )
mkr = 'o<vs*h>^o<vs*h>p*'

#
lbool = False
for i,k in enumerate(z):
    y = ysfits[k]
    ll,mm,l,m,n = k
    if (n==0) and (l==ll):
        p = plot( y.scalar_range.real,y.scalar_range.imag, ls='none', marker=mkr[i], label=k )
        p0 = p[0]
        p0.set_markeredgecolor( 0.9*array([int(p[0].get_color().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4)])/255 )
        if k is z[-1]:
            label = 'Fit'
            lbool = True
        else:
            label = None
        plot( y.eval(y.domain).real,y.eval(y.domain).imag, color='k', label=label )

#
xlabel(r'$\sigma^{R}_{ \bar{\ell} \bar{m} \ell m n}$')
ylabel(r'$\sigma^{I}_{ \bar{\ell} \bar{m} \ell m n}$')
legend()

#
gca().tick_params(direction='in')

#
axhline(0,color='k',ls='--')

#
# xlim( [-1,1] )
legend(frameon=True,edgecolor='none')

#
savefig('/Users/book/KOALA/positive_dev/docs/mvfits_paper/fig/issue2_ysprod_1.pdf',pad_inches=0, bbox_inches='tight')
```

```python

# def _ysprod14081860(j,ll,mm,lmn):
#     tbl = loadtxt('/Users/book/Desktop/swsh_fits.dat')
#     print tbl.shape
#     MM,LL,L,N,P1,P2,P3,P4,Q1,Q2,Q3,Q4,_,_,_,_ = tbl.T
#     l,m,n = lmn
#     k = (ll==LL) & (mm==MM) & (l==L) & (n==N)
#     if sum(k)!=1:
#         error('cannot find fit from Berti+ for (L,M,l,m,n) = (%i,%i,%i,%i,%)'%(L,M,l,m,n))
#     j_ = abs(j)
#     ans = (1 if ll==l else 0) + P1[k]*j_**P2[k] + P3[k]*j_**P4[k] + 1j * ( Q1[k]*j_**Q2[k] + Q3[k]*j_**Q4[k] )
#     return ans

```

```python
print MM
```

    [ 2.  2.  2. ..., -7. -7. -7.]

```python
ll,mm,l,m,n = 3,3,3,2,0
pos_jf = jf[ jf>0 ]
_sigma = _ysprod14081860(pos_jf,ll,mm,(l,m,n))
plot( _sigma.real, _sigma.imag )
```

```python
#
# z = sorted(ysfits.keys(),key= lambda X: X[0])
z = [(3, 2, 3, 2, 0)]
# z = [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)]
Q = 81
jf = 0.995*cos(pi*linspace(0,1,Q))

#
fig = figure( figsize=1.6*figaspect(1) )
mkr = 'ov'

#
for i,k in enumerate(z):
    y = ysfits[k]
    ll,mm,l,m,n = k
    p = plot( y.scalar_range.real,y.scalar_range.imag, ls='none', marker=mkr[i], label=k )
    if k is z[-1]:
        label = 'Fit'
        lbool = True
    else:
        label = None
    plot( y.eval(y.domain).real,y.eval(y.domain).imag, color='k', label=label )
    bert_fit = ysprod14081860(jf,ll,mm,(l,m,n))
    plot( bert_fit.real, bert_fit.imag, '--r', lw=2 )

#
gca().tick_params(direction='in')

#
axhline(0,color='k',ls='--')

#
xlabel(r'$\sigma^{R}_{ \bar{\ell} \bar{m} \ell m n}$')
ylabel(r'$\sigma^{I}_{ \bar{\ell} \bar{m} \ell m n}$')
legend()

#
# savefig('/Users/book/KOALA/positive_dev/docs/mvfits_paper/fig/issue2_ysprod_2.pdf',pad_inches=0, bbox_inches='tight')

```

```python
#
z = sorted(ysfits.keys(),key= lambda X: X[0])
# z = [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)]
Q = 81
jf = 0.995*cos(pi*linspace(0,1,Q))

#
fig = figure( figsize=1.6*figaspect(1) )
mkr = 'o<vs*h>^pXDo<vs*h>p*'

__y = {}
__res = {}

#
for i,k in enumerate(z):

    ll,mm,l,m,n = k

    if ll==mm:# and not ( k == (2,2,2,2,1) ):
#     if not ( k in [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)] ) :

        y = ysfits[k]
        #print lim(y.domain)
        #_y = array([ ysprod( j, ll, mm, (l,m,n) ) for j in jf ])
        #__y[k] = _y

        # --------------------------------------------------------- #
        # Berti
        y_fit = ysprod14081860( jf, ll,mm, (l,m,n) )
        res2 = 100*abs(y.scalar_range-y_fit)/abs(mean(y.scalar_range))
        # --------------------------------------------------------- #

        res = 100*abs(y.scalar_range-y.eval(y.domain))/abs(mean(y.scalar_range))
        p = plot( jf,res, ls='-', marker=mkr[i], label=k )

        mask = jf>-100 # 0.005
        p2 = plot( jf[mask],res2[mask], ls='-', marker=mkr[i], label=None, color=0.9*ones(3), alpha = 0.5, mec='k' )
        #if k is z[-1]:
        #    label = 'Fit'
        #    lbool = True
        #else:
        #    label = None
        #plot( y.eval(y.domain).real,y.eval(y.domain).imag, color='k', label=label )

#
gca().tick_params(direction='in')

#
axhline(0,color='k',ls='--')
xlim( [-1,1] )
# ylim( [1e-7,1e-1] )

#
xlabel(r'$j_f$')
ylabel(r'$\%|\Delta\sigma_{ \bar{\ell} \bar{m} \ell m n}|$')
legend(ncol=2,fontsize=16,frameon=True)
yscale('log')

#
# savefig('/Users/book/KOALA/positive_dev/docs/mvfits_paper/issue2_ysprod_3v2.pdf',pad_inches=0, bbox_inches='tight')

```

```python
# #
# z = sorted(ysfits.keys(),key= lambda X: X[0])
# # z = [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)]
# Q = 81
# jf = 0.995*cos(pi*linspace(0,1,Q))

# #
# # fig = figure( figsize=1.6*figaspect(1) )
# mkr = 'o<vs*h>^o<vs*h>p*'

# __y = {}
# __res = {}

# #
# for i,k in enumerate(z):

#     ll,mm,l,m,n = k

#     if ll==mm:# and not ( k == (2,2,2,2,1) ):
# #     if not ( k in [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)] ) :

#         y = ysfits[k]
#         #print lim(y.domain)
#         #_y = array([ ysprod( j, ll, mm, (l,m,n) ) for j in jf ])
#         #__y[k] = _y

#         #y_fit = y.eval(y.domain)
#         y_fit = ysprod14081860( jf, ll,mm, (l,m,n) ).conj()
#         res = 100*abs(y.scalar_range-y_fit)/abs(mean(y.scalar_range))

#         #res = 100*(y.scalar_range.real-y.eval(y.domain).real)/abs(mean(y.scalar_range))
#         #__res[k] = 100*res/abs(max(y.scalar_range.real))
#         figure()
#         p = plot( y_fit.real, y_fit.imag, ls='-', label='berti' )
#         p = plot( y.scalar_range.real, y.scalar_range.imag, ls='--', label='numerical' )
#         title(k)
#         legend()
# #         p = plot( jf,res, ls='-', marker=mkr[i], label=k )
#         #if k is z[-1]:
#         #    label = 'Fit'
#         #    lbool = True
#         #else:
#         #    label = None
#         #plot( y.eval(y.domain).real,y.eval(y.domain).imag, color='k', label=label )

# # #
# # gca().tick_params(direction='in')

# # #
# # axhline(0,color='k',ls='--')
# # xlim( [0.05,1] )
# # # ylim( [1e-2,1e1] )

# # #
# # xlabel(r'$j_f$')
# # ylabel(r'$\%|\Delta\sigma_{ \bar{\ell} \bar{m} \ell m n}|$')
# # legend(ncol=2,fontsize=16,frameon=True)
# # yscale('log')

# #
# # savefig('/Users/book/KOALA/positive_dev/docs/mvfits_paper/fig/issue2_ysprod_3_berti.pdf',pad_inches=0, bbox_inches='tight')

```

```python
# #
# z = sorted(ysfits.keys(),key= lambda X: X[0])
# # z = [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)]
# Q = 81
# jf = 0.995*cos(pi*linspace(0,1,Q))

# #
# fig = figure( figsize=1.6*figaspect(1) )
# mkr = 'o<vs*h>^o<vs*h>p*'

# __y = {}
# __res = {}

# #
# for i,k in enumerate(z):

#     ll,mm,l,m,n = k

#     if ll==mm:# and not ( k == (2,2,2,2,1) ):
# #     if not ( k in [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)] ) :

#         y = ysfits[k]
#         #print lim(y.domain)
#         #_y = array([ ysprod( j, ll, mm, (l,m,n) ) for j in jf ])
#         #__y[k] = _y

#         #y_fit = y.eval(y.domain)
#         y_fit = ysprod14081860( jf, ll,mm, (l,m,n) ).conj()
#         res = 100*abs(y.scalar_range-y_fit)/abs(mean(y.scalar_range))

#         #res = 100*(y.scalar_range.real-y.eval(y.domain).real)/abs(mean(y.scalar_range))
#         #__res[k] = 100*res/abs(max(y.scalar_range.real))
#         #p = plot( res.real, res.imag, ls='-', marker=mkr[i], label=k )
#         p = plot( jf,res, ls='-', marker=mkr[i], label=k )
#         #if k is z[-1]:
#         #    label = 'Fit'
#         #    lbool = True
#         #else:
#         #    label = None
#         #plot( y.eval(y.domain).real,y.eval(y.domain).imag, color='k', label=label )

# #
# gca().tick_params(direction='in')

# #
# axhline(0,color='k',ls='--')
# xlim( [0.03,1] )
# # ylim( [1e-2,1e1] )

# #
# xlabel(r'$j_f$')
# ylabel(r'$\%|\Delta\sigma_{ \bar{\ell} \bar{m} \ell m n}|$')
# legend(ncol=2,fontsize=16,frameon=True)
# yscale('log')

# #
# # savefig('/Users/book/KOALA/positive_dev/docs/mvfits_paper/fig/issue2_ysprod_3_berti.pdf',pad_inches=0, bbox_inches='tight')

```

```python
#
z = sorted(ysfits.keys(),key= lambda X: X[0])
# z = [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)]
Q = 81
jf = 0.995*cos(pi*linspace(0,1,Q))

#
fig = figure( figsize=1.6*figaspect(1) )
mkr = 'o<vs*h>^pDXo<vs*h>p*'
ax = gca()

__y = {}
__res = {}

#
for i,k in enumerate(z):

    ll,mm,l,m,n = k

    if ll!=mm:
#     if not ( k in [(3, 2, 2, 2, 0), (4, 3, 3, 3, 0)] ) :

        y = ysfits[k]
        #print lim(y.domain)
        #_y = array([ ysprod( j, ll, mm, (l,m,n) ) for j in jf ])
        #__y[k] = _y
        res = 100*abs(y.scalar_range-y.eval(y.domain))/abs(mean(y.scalar_range))

        # --------------------------------------------------------- #
        # Berti
        y_fit = -ysprod14081860( jf, ll,mm, (l,m,n) ).conj()
        if mod(l+m,2):
            y_fit = -y_fit
        res2 = 100*abs(y.scalar_range-y_fit)/abs(mean(y.scalar_range))
        # --------------------------------------------------------- #

        figure()
        plot( y_fit.real, y_fit.imag )
        plot( y.scalar_range.real, y.scalar_range.imag, '--k' )
        title(k)

        sca(ax)
        p = plot( jf,res, ls='-', marker=mkr[i], label=k )
        mask = jf>0.005
        p2 = plot( jf[mask],res2[mask], ls='-', marker=mkr[i], label=None, color=0.9*ones(3), alpha = 0.5, mec='k' )
        #if k is z[-1]:
        #    label = 'Fit'
        #    lbool = True
        #else:
        #    label = None
        #plot( y.eval(y.domain).real,y.eval(y.domain).imag, color='k', label=label )

#
gca().tick_params(direction='in')

#
axhline(0,color='k',ls='--')
xlim( [-1,1] )
ylim( [3e-4,1e3] )

#
xlabel(r'$j_f$')
ylabel(r'$\%|\Delta\sigma_{ \bar{\ell} \bar{m} \ell m n}|$')
legend(ncol=2,fontsize=16,frameon=True)
yscale('log')

#
savefig('/Users/book/KOALA/positive_dev/docs/mvfits_paper/issue2_ysprod_4v2.pdf',pad_inches=0, bbox_inches='tight')

```

```python
a = []
b = []
for k in __res:
    a += list( __res[k].imag )
    b += list( __res[k].real )
hist(a,50,histtype='step');
hist(b,50,histtype='step');
```

```python
ll,mm,l,m,n = 3,2,2,2,0
jf = 0.68
beta = 1.0 / ( 2 + ll-abs(mm) )
kappa = lambda JF: (log( 2 - JF ) / log(3))**(beta)
a = ysfits[ll,mm,l,m,n].eval(array([kappa(jf)]))[0]
b = ysprod(jf,ll,mm,(l,m,n))
print a
print b
print (abs(a-b))
```

    (0.0670185386343-0.0111024108534j)
    (0.0670158274683-0.0111007365255j)
    3.1865019782e-06

```python
print jf

```

    [  9.95000000e-01   9.94232891e-01   9.91932747e-01   9.88103115e-01
       9.82749899e-01   9.75881354e-01   9.67508071e-01   9.57642960e-01
       9.46301234e-01   9.33500379e-01   9.19260135e-01   9.03602458e-01
       8.86551492e-01   8.68133527e-01   8.48376964e-01   8.27312264e-01
       8.04971909e-01   7.81390346e-01   7.56603936e-01   7.30650897e-01
       7.03571247e-01   6.75406742e-01   6.46200808e-01   6.15998480e-01
       5.84846326e-01   5.52792382e-01   5.19886072e-01   4.86178135e-01
       4.51720547e-01   4.16566439e-01   3.80770015e-01   3.44386472e-01
       3.07471909e-01   2.70083248e-01   2.32278137e-01   1.94114870e-01
       1.55652293e-01   1.16949710e-01   7.80668002e-02   3.90635167e-02
       6.09261783e-17  -3.90635167e-02  -7.80668002e-02  -1.16949710e-01
      -1.55652293e-01  -1.94114870e-01  -2.32278137e-01  -2.70083248e-01
      -3.07471909e-01  -3.44386472e-01  -3.80770015e-01  -4.16566439e-01
      -4.51720547e-01  -4.86178135e-01  -5.19886072e-01  -5.52792382e-01
      -5.84846326e-01  -6.15998480e-01  -6.46200808e-01  -6.75406742e-01
      -7.03571247e-01  -7.30650897e-01  -7.56603936e-01  -7.81390346e-01
      -8.04971909e-01  -8.27312264e-01  -8.48376964e-01  -8.68133527e-01
      -8.86551492e-01  -9.03602458e-01  -9.19260135e-01  -9.33500379e-01
      -9.46301234e-01  -9.57642960e-01  -9.67508071e-01  -9.75881354e-01
      -9.82749899e-01  -9.88103115e-01  -9.91932747e-01  -9.94232891e-01
      -9.95000000e-01]

```python

#
z = sorted(ysfits.keys(),key= lambda X: X[0])

for k in z:
    #
    L,M,l,m,n = z
    #

```

    [(2, 2, 2, 2, 0), (2, 1, 2, 1, 0), (2, 2, 2, 2, 1), (3, 2, 3, 2, 0), (3, 3, 3, 3, 1), (3, 2, 2, 2, 1), (3, 3, 3, 3, 0), (3, 2, 2, 2, 0), (4, 3, 3, 3, 0), (4, 3, 4, 3, 0), (4, 4, 4, 4, 0), (5, 5, 5, 5, 0)]

```python

```

```python

```


# **Practice 1 Q&A: Fundamental Subspaces and Jacobian Mapping**
## **Goals for today**

---

During today practice we will:
*   Exploit a structure of linear mapping between Joint and Task spaces velocities.
*   Understand what is physical interpretation of fundamental subspaces of the manipulator Jacobian.
*   Obtain null space motion of 3 DoF plane manipulator with fixed end effector

 ## **Four Fundamental Subspaces. Recall**
---
>As we have studied on the lectures there are four fundamental subspaces accompanying any linear operator (matrix) $\mathbf{A}^{m \times n}$, namely:
>*   **Column** space (range, image): $\mathcal{C}(\mathbf{A}) \in \mathbb{R}^m$
>*   **Null** space (kernel): $\mathcal{N}(\mathbf{A}) \in \mathbb{R}^n$
>*   **Row** space: $\mathcal{R}(\mathbf{A}) = \mathcal{C}(\mathbf{A}^T)  \in \mathbb{R}^n$
>*   **Left null** space: $\mathcal{N}(\mathbf{A}^T) \in \mathbb{R}^m$
---
## **Jacobian Force Mapping**

>* In yout homework you was trying to the physical interpretation of remaining fundamental subspaces:
\begin{equation}
\boldsymbol{\tau} = \mathbf{J}(\mathbf{q})^T\mathbf{F}
\end{equation}
where:
  *   $\mathbf{F} \in \mathbb{R}^m$ task space force imposed on the end-effector
  *   $\boldsymbol{\tau} \in \mathbb{R}^n$ joint space torques (control effort in the actuators)
>

Similarly to what we did for velocities one can deduce  that:

*   The row space of $\mathbf{J}$ is the subspace $\mathcal{R}(\mathbf{J}) \in \mathbb{R}^n$ of the joint torques $\boldsymbol{\tau}$ that can balance forces applied to end-effector $\mathbf{F}$, in the given manipulator
posture $\mathbf{q}$
*   The left null space of $\mathbf{J}$ is the subspace $\mathcal{N}(\mathbf{J}^T) \in \mathbb{R}^m$ of the end-effector forces $\mathbf{F}$ that do not require any balancing joint torques $\boldsymbol{\tau}$, in the given manipulator posture $\mathbf{q}$.

Let us consider the following examples.

## **Examples: Zero Torques**
Consider the 1-DoF planar manipulator (pendulum):

The question is: Which force vector do not require any balancing torque?

It is obvious that force along pendulum linkage will not produce any torque, however let us check this fact by analyzing left null space of the Jacobian matrix.

Solution of forward kinematics:
\begin{equation}
\boldsymbol{x} =
\ell
\begin{bmatrix}
 \cos q\\
 \sin q
\end{bmatrix}
\end{equation}

Thus the Jacobian calculated as:
\begin{equation}
\mathbf{J} =
\frac{\partial \boldsymbol{x}}{\partial q}
=
\ell
\begin{bmatrix}
 -\sin q\\
 \cos q
\end{bmatrix}
\end{equation}

```
from numpy import cos, sin, zeros, sum, pi, dot, array
from scipy.linalg import null_space
q = 0
l = 1
# jacobian
jac = l*array([-sin(q), cos(q)])
F_0 = null_space([jac.transpose()])
print(F_0)
```

    [[1.]
     [0.]]

As we expected force is along the link.
Let us add the linear joint in to base:

Jacobian become:
\begin{equation}
\mathbf{J} =
\frac{\partial \boldsymbol{x}}{\partial \mathbf{q}}
=
\begin{bmatrix}
1 & -\ell_1\sin q_2\\
0 & \ell_1\cos q_2
\end{bmatrix}
\end{equation}

Let's implement Jacobian matrix:

```
def jacobian(q):
    return l*array([[1,-sin(q[1])], [0,cos(q[1])]])

q = [1,pi/2]

F_0 = null_space(jacobian(q).transpose())
print(F_0)
```

    [[3.061617e-17]
     [1.000000e+00]]

If link is in the vertical posture we don't need any balancing torques $\mathbf{\boldsymbol{\tau}}$ to overcome vertical force $\mathbf{F} = \mathbf{e}_y\lambda, \forall \lambda$.

Let's check other postures:

```
q = [1,pi/4]
F_0 = null_space(jacobian(q).transpose())
print(F_0)
```

    []

>**QUESTION**:
As you can see null space is empty, what does that phisicly mean?

>**HW EXERCISE**:
> Consider cable driven manipulator:
>

>
> with relationships between cable tensions $\boldsymbol{\tau}$ and force acting on end effector $\mathbf{F}$ given by:
>
>\begin{equation}
\mathbf{F} = \mathbf{W}\boldsymbol{\tau}
\end{equation}
>
>where: $\mathbf{W} \in \mathbb{R}^{m \times n}$ is so called wire matrix that play role of the Jacobian.
>
> Taking in to account that tension must remain positive $\tau_i >0,\forall i$ ,Do the following:
>
>* Answer what is the minimal number of actuators that supports
arbitary end effector force?
>* Formulate criteria that alow you to find cable tension do not producing any force on end effector, do it for the minimal number of cables. ( slack is not allowed), Tip: use null space $\mathcal{N}(\mathbf{W})$
>* Come up with the geometrical representation of the proposed criteria.

Google Colab notebooks/Practice Fundumental Subspaces 2020 part 1/practice_01_fss_jacobian_2.ipynb