## Introduction

This notebook will go over a new loss function to optimize the decision of the ERUPT metric directly for both single and mutliple response case.

1. New Loss Function for single response variable

2. Building / Gridsearching the Uplift Model with new loss vs default mean-squared-error loss

3. Extending new loss function to multi-responses

### New Loss Function

Readers are encouraged to read the [example on single responses](https://github.com/Ibotta/ibotta_uplift/blob/master/examples/mr_uplift_one_response_example.ipynb) for a more thorough background on uplift models and the ERUPT metric.

To recap we start with the decision function defined as:

\begin{equation}
 \pi(x_i) =argmax \:_{t \in T} E[y_i | X=x_i, T=t]
\tag{1}
\end{equation}

This is the function that determines whether to assign a user a particular treatment for a single response we wish to maximize. It just says that we apply the treatment with the highest expected value.

To exaluate the decision function the ERUPT metric is used. It estimates the expected value of the decision function on an out-of-sample dataset. Assuming that the treatments are uniformly (all treatments have same number of observations) and randomly assigned the ERUPT metric is:

$$ERUPT(pi(x),y,t) \propto \sum_{i}^N y_i I(\pi(x_i) = t_i) \tag{2}$$

The original loss functions used in mr_uplift have used a mean squared error (MSE) function $f(x,t)$ to estimate $E[y_i | X=x_i, T=t]$ :

\begin{equation}
 MSE(y,f(x,t)) = 1/N \sum_{i}^N (y_i-f(X=x_i, T=t_i))^2
\tag{3}
\end{equation}

In theory this should produce an estimator that approximates the interaction between $x$ and $t$ neccesary to build a good decision function $\pi(x_i)$. However, in practice thie interaction effect is tricky to estimate because the it is generally small relative to main effects and noise in the response. Several researchers have developed in techniques order to estimate this (see [Zhao, Fang, Simchi-Levi](https://arxiv.org/pdf/1705.08492.pdf?source=post_page---------------------------), [Athey, Tibshirani, Wager](https://arxiv.org/pdf/1610.01271.pdf), [Hitsch, Misra](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3111957) to name a few).

The approach taken here follows an approach developed by [Weiss](https://medium.com/building-ibotta/maximizing-the-erupt-metric-for-uplift-models-f8d7e57bfdf2) and [Fernandez-Loria, et al](https://arxiv.org/pdf/2004.11532.pdf) in which it attempts to optimize the ERUPT function in $2$ directly.

In order to approximate that function we assign a 'probability' score to each treatment:

\begin{equation}
 optimizedloss(y,t,f(t,x)) =  \sum_{i}^N \sum_{t \in T}^T y_{i,t}*p(t|x_i)  =  \sum_{i}^N \sum_{t \in T}^T y_{i,t}*\frac{e^{f(X=x_i, T=t)}}{\sum_{t \in T}e^{f(X=x_i, T=t)}}
\tag{4}
\end{equation}
where $y_{i_t}$ is equal to $y_{i}$ if user $i$ recieved treatment $t$, else zero.

One major difference here compared with [Weiss](https://medium.com/building-ibotta/maximizing-the-erupt-metric-for-uplift-models-f8d7e57bfdf2) is that it explitly incorporates the treatments in the function $f()$ to be estimated. This should help regularize the estimation if the treatments have similar meta-data information. For instance; if the treatments are ordered then this model can take that information into account.

Finally, MSE is also included in the loss function to create a weighted loss between the two. Including MSE should help when the estimated optimal treatment does not equal the treatment assignment:

\begin{equation}
\alpha*optimizedloss(y,t,f(t,x)) + (1-\alpha)*MSE(y,f(x,t))
\tag{5}
\end{equation}

```python
from ggplot import *
import numpy as np
import pandas as pd

from mr_uplift.dataset.data_simulation import get_sin_uplift_data
from mr_uplift.mr_uplift import MRUplift

```

    /Users/samweiss/anaconda3/lib/python3.6/site-packages/ggplot/utils.py:81: FutureWarning: pandas.tslib is deprecated and will be removed in a future version.
    You can access Timestamp as pandas.Timestamp
      pd.tslib.Timestamp,
    /Users/samweiss/anaconda3/lib/python3.6/site-packages/ggplot/stats/smoothers.py:4: FutureWarning: The pandas.lib module is deprecated and will be removed in a future version. These are private functions and can be accessed from pandas._libs.lib instead
      from pandas.lib import Timestamp
    Using TensorFlow backend.

### Data Generating Process

In order to test the new loss function I simulated some data and compared results with a regular MSE loss. Below is the data generating process of the data. This is similar to the previous data function except that the treatment effect relationship is now in a sine function. This adds a layer of complexity for the model to estimate.

\begin{equation}
x_1  \sim runif(0,1)
\end{equation}

\begin{equation}
x_2 \sim runif(0,1)
\end{equation}

\begin{equation}
e_1 \sim rnorm(0,1)
\end{equation}

\begin{equation}
e_2 \sim rnorm(0,1)
\end{equation}

\begin{equation}
t \sim rbinom(.5)
\end{equation}

\begin{equation}
noise \sim rnorm(0,1)
\end{equation}

\begin{equation}
revenue = sin(20*x_1*t) + e_1
\end{equation}

\begin{equation}
costs = sin(20*x_2*t) + e_2
\end{equation}

\begin{equation}
profit = revenue - costs
\end{equation}

```python
np.random.seed(23)

y, x, t = get_sin_uplift_data(10000)

y = pd.DataFrame(y)
y.columns = ['revenue','cost', 'noise']
y['profit'] = y['revenue'] - y['cost']
```

```python
#Build Uplift with the default MSE Loss
uplift_model = MRUplift()
param_grid = dict(num_nodes=[8, 32], dropout=[.1,.25], activation=[
                          'relu'], num_layers=[2,3], epochs=[25], batch_size=[100])

uplift_model.fit(x, np.array(y['profit']).reshape(-1,1), t.reshape(-1,1), param_grid = param_grid, n_jobs = 1, cv = 5)

#Build Uplift with the Optimization Loss
#alpha and copy_several_times are new hyperparameters. alpha is the weight given to MSE vs optimi_loss from eqn (5)
#copy_several_times is for multiple response uplift models and is described below
uplift_model_optimized = MRUplift()
param_grid_optimized = dict(num_nodes=[8, 32], dropout=[.1,.25], activation=[
                          'relu'], num_layers=[2,3], epochs=[30], batch_size=[100],
                 copy_several_times = [None],
                 alpha = [.999,.99,.75,.5])
uplift_model_optimized.fit(x, np.array(y['profit']).reshape(-1,1), t.reshape(-1,1),
                           param_grid = param_grid_optimized, n_jobs = 1, optimized_loss = True, cv=5)

```

### Results MSE vs Optimiziation

The results below show the MSE only loss function increases profits by around ~0.04 while the new optimization results in profit score of ~0.17. This shows the new loss function can help estimate the heterogeneous effect more effectively in this example.

```python
erupt_curves_mse, distribution_mse = uplift_model.get_erupt_curves()
print('ERUPT Metric for MSE Model')
print(erupt_curves_mse)

print('Distribution of Treatments for MSE Model')
print(distribution_mse)

print('Best Parameters MSE')
print(uplift_model.best_params_net)

erupt_curves_optimized, distribution_optimized = uplift_model_optimized.get_erupt_curves()

print('ERUPT Metric for Optimized Model')
print(erupt_curves_optimized)

print('Distribution of Treatments for Optimized Model')
print(distribution_optimized)

print('Best Parameters Optimized')
print(uplift_model_optimized.best_params_net)

```

    Using Test Data Set
    ERUPT Metric for MSE Model
           mean       std response_var_names weights assignment
    0  0.044627  0.012787              var_0       1      model
    0 -0.002599  0.012609              var_0       1     random
    Distribution of Treatments for MSE Model
       num_observations  tmt weights  percent_tmt
    0              3625    1       1     0.517857
    1              3375    0       1     0.482143
    Best Parameters MSE
    {'activation': 'relu', 'batch_size': 100, 'dropout': 0.25, 'epochs': 25, 'num_layers': 3, 'num_nodes': 8}
    Using Test Data Set
    ERUPT Metric for Optimized Model
           mean       std response_var_names weights assignment
    0  0.171712  0.013539              var_0       1      model
    0 -0.010521  0.013697              var_0       1     random
    Distribution of Treatments for Optimized Model
       num_observations  tmt weights  percent_tmt
    0              4391    1       1     0.627286
    1              2609    0       1     0.372714
    Best Parameters Optimized
    {'activation': 'relu', 'alpha': 0.5, 'batch_size': 100, 'copy_several_times': None, 'dropout': 0.1, 'epochs': 30, 'num_layers': 3, 'num_nodes': 32}

### Multi-Response Optimization

See  [example on multiple_responses](https://github.com/Ibotta/ibotta_uplift/blob/master/examples/mr_uplift_multiple_response_example.ipynb) for a more detailed introduction on uplift models with multiple responses.

The general idea of uplift models with multiple response variables is to create a new variable that is a weighted sum of the responses. These weights define the relative importance responses variables. One can then calculate ERUPT metrics for a given set of weights as if it was a single response.

However, issue of extending the loss function from $(4)$ to a multi-response framework is that it only optimizes one set of weights. Ideally we'd like maximize a function that 'integrates out' the weights. Instead of doing that explitily I draw a random uniform weight for each observation-response pair and maximize the following:

\begin{equation}
w_{i,j} \sim runif(-1,1)
\end{equation}

\begin{equation}
 optimizedlossmultioutput(y,t,w,f(t,x)) = \sum_{i}^N \sum_{t \in T}^T (\sum_{j}^J w_{i,j} y_{i,t,j})*\frac{e^{\sum_{j}^J w_{i,j} f_j(X=x_i, T=t)}}{\sum_{t \in T}e^{\sum_{j}^J w_{i,j} f_j(X=x_i, T=t)}}
\tag{6}
\end{equation}

Note that the weights $w_{i,j}$ do not explicitly go into the function $f(t,x)$.

Different 'optimal treatments' will be selected due to randomness of the weights for two identical observations. Because of this I included another hyperparameter 'copy_several_times' that duplicates observations a specified number of times. I've found this to be helpful in practice

An MSE loss is also include similar to $(5)$. This has two main benefits 1) to create help estimate the function when the optimal treatment was not randomly assigned and 2) to maintain the order of response predictions. Becuase of these reasons I would not set $\alpha$ to be 1.

Below is an example of the multi-response case.

```python
uplift_model_optimized_mr = MRUplift()
param_grid = dict(num_nodes=[ 32,64, 256], dropout=[.25], activation=[
                          'relu'], num_layers=[3], epochs=[25], batch_size=[100],
                 copy_several_times = [10],
                 alpha = [.999,.99,.5])

uplift_model_optimized_mr.fit(x, y, t.reshape(-1,1),
                           param_grid = param_grid, n_jobs = 1, optimized_loss = True, cv=5)

```

```python
erupt_curves_optimized_mr, distribution_optimized_mr = uplift_model_optimized_mr.get_erupt_curves()
```

    Using Test Data Set

```python
from ggplot import *

distribution_optimized_mr['weights_1'] = [np.float(x.split(',')[0]) for x in distribution_optimized_mr['weights']]
erupt_curves_optimized_mr['weights_1'] = [np.float(x.split(',')[0]) for x in erupt_curves_optimized_mr['weights']]

ggplot(aes(x='weights_1', y='mean', group = 'assignment', colour = 'assignment'), data=erupt_curves_optimized_mr) +\
    geom_line()+\
    geom_point()+facet_wrap("response_var_names")
```


```python
### Running in Google Colab? You'll want to uncomment and run these cell once each time you start this notebook.

"""
!wget https://raw.githubusercontent.com/psheehan/CIERA-HS-Program/master/BonusProblems/Module3/M33velocities.csv
"""
```

# Galaxy Rotation Curves

We've already talked quite a bit about the rotation curve of the Solar System, but that isn't the only place where rotation curves are useful! Another place where they have frequently been used is in understanding how matter is distributed in galaxies. It turns out that the equation we were previously using

\begin{equation}
v = \sqrt{\frac{G \, M}{R}}
\end{equation}

is more broadly applicable such that $M$ is the mass $enclosed$ within radius R, or:

\begin{equation}
v(R) = \sqrt{\frac{G \, M_{enclosed}(R)}{R}}.
\end{equation}

In general terms, the mass enclosed is what you get if you add up all of the mass inside of radius R. What this means, then, is that if you can measure the rotation curve of a galaxy, then you can calculate where mass is located within the galaxy. Confused? Let's explore.

In this directory, there is a file called "M33velocities.csv" with the velocities (in km/s; second column) as a function of distance from the center of the galaxy (in kilo-parsecs). Let's start by reading in the data from that file:

```python
##Import statements?

##Read data from file. Use delimiter = ','
data = np.genfromtxt('', delimiter=',')  ##fill in the blank
velocity = data[]; radius = data[]
```

Now, make a rotation curve plot, showing velocity as a function of radius. Make sure the axes are properly labelled!

```python

```

Using numpy, calculate the mass enclosed within each radius, $R$, from the above equation and the velocity and radius data you read in above:

```python

```

Next, make a plot of the mass enclosed that you just calculated as a function of radius. From images of M33, we see that stars only go out to a radius of 7 kpc - make a line on the plot that marks this point (_Hint:_ https://matplotlib.org/api/_as_gen/matplotlib.pyplot.axvline.html).

```python

```

For comparison, use the velocities and radii of planets in the Solar System to calculate the mass enclosed as a function of radius:

```python

```

What do you notice about the behaviour of the mass of the galaxy as you go to larger and larger radii? Does the mass ever drop? Change the font color below to learn more!

If you've done your calculations correctly, you should see that even beyond the 15 kpc outer edge of the galaxy, the mass enclosed still continues to go up. What is that mass that we can't see? Dark matter! You've just uncovered one of the main pieces of evidence indicating that there is a significant amount of matter that we can't see, hidden within M33. The only way we can detect it is in how it exerts a gravitational force on other matter, through this rotation curve.


# 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 = \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} - \hat{\beta}_1 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'])

```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 House Prices dataset
    ===========================

    Notes
    ------
    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.
    http://archive.ics.uci.edu/ml/datasets/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.

    **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.
       - many more! (see http://archive.ics.uci.edu/ml/datasets/Housing)

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.593761
      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.596783
      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.647423
      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.

# your turn: describe relationship

- The relationship appears to be non-linear.  There appears to be many towns with little to no CRIM. Also there appears to be a strange occurance at a housing price of 50 w.r.t. CRIM rates.

# your turn: scatter plot between *RM* and *PRICE*

```python
plt.scatter(bos.RM, bos.PRICE)
plt.xlabel("Average number of rooms per dwelling (RM)")
plt.ylabel("Housing Price")
plt.title("Relationship between RM and Price")
```

- There appears to be a strong positive correlation between the number of rooms and the housing price. This would make sense since as the number of rooms increase, in general, so does the size of the host and as a result its price.

# your turn: scatter plot between *PTRATIO* and *PRICE*

```python
plt.scatter(bos.PTRATIO, bos.PRICE)
plt.xlabel("Pupil-teacher ratio by town (PTRATIO)")
plt.ylabel("Housing Price")
plt.title("Relationship between PTRATIO and Price")
```

- There appears to be slight negative correlation between the housing price and the number of pupils per teacher. This would make sense as a smaller percentage of families would be able to afford expensive homes. This would naturally lead to more expensive schools and thus fewer students attenting that school.

# your turn: create some other scatter plots

```python
plt.scatter(bos.AGE, bos.PRICE)
plt.xlabel("Proportion of owner-occupied units built prior to 1940 (AGE)")
plt.ylabel("Housing Price")
plt.title("Relationship between AGE and Price")
```

- It appears there is a negative correlation between price and the proportion of owner-occupied units built prior to 1940.  This means that as that as the age of the home grows the price decreases.

### 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.

# your turn

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

- The log is usful here as log scales allow a large range to be displayed without small values being compressed down into bottom of the graph. There appears to be a lot of observations in the 0-20 range. By taking the log, this range is subdivided and a bimodal trend can be seen that otherwise would have been missed.

```python
plt.hist(bos.RM)
plt.title("RM")
plt.xlabel("Average number of rooms per dwelling")
plt.ylabel("Frequencey")
plt.show()
```

```python
plt.hist(bos.PTRATIO)
plt.title("PTRATIO")
plt.xlabel("Pupil-teacher ratio by town (PTRATIO)")
plt.ylabel("Frequencey")
plt.show()
```

```python
plt.hist(bos.AGE)
plt.title("AGE")
plt.xlabel("Proportion of owner-occupied units built prior to 1940 (AGE)")
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
```

    /Users/racineda/anaconda3/lib/python3.6/site-packages/statsmodels/compat/pandas.py:56: FutureWarning: The pandas.core.datetools module is deprecated and will be removed in a future version. Please use the pandas.tseries module instead.
      from pandas.core import datetools

```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:                Tue, 05 Dec 2017   Prob (F-statistic):           2.49e-74
    Time:                        01:02:13   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.

# your turn

```python
sns.regplot(y=bos.PRICE, x=m.fittedvalues, fit_reg = True)
plt.xlabel("Predicted Price")
plt.ylabel("Original Price")
plt.title("Relationship between Predicted and Original Housing Prices")
```

- The predicted prices are very close to the original prices with some exceptions. The model is not able to fit a great model based only on number of rooms alone. Also, the it appears that an origional price of 0 would not yield a predicted price of 0 which is strange.

### Fitting Linear Regression using `sklearn`

```python
from sklearn.linear_model import LinearRegression
from sklearn import linear_model

X = bos.drop('PRICE', axis = 1)

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

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, 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.<tab>
```

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=1, 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://online.stat.psu.edu/~ajw13/stat501/SpecialTopics/Reg_thru_origin.pdf).

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.

# your turn

```python
lm_ni = LinearRegression(fit_intercept=False).fit(X, bos.PRICE)
```

- I recommend fitting with an intercept. Unless we are sure that the linear approximation of the linear regression model passes through the origin.

- To satisfy i.i.d. it is not enough to say that the residuals are normally distributed.  They also need to be uncorrelated.

- In linear regression the independent variables do not have to be normal.  The dependent variable also does not need to be normally distributed.  The usual linear regression assumes that the errors are normally distributed.

### 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: 36.491103280363404

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

    Number of coefficients: 13

```python
# The coefficients
coef = list(zip(X.columns, lm.coef_))
pd.DataFrame(coef, columns = ['features', 'estimatedCoefficients'])
```

      features
      estimatedCoefficients

      0
      CRIM
      -0.107171

      1
      ZN
      0.046395

      2
      INDUS
      0.020860

      3
      CHAS
      2.688561

      4
      NOX
      -17.795759

      5
      RM
      3.804752

      6
      AGE
      0.000751

      7
      DIS
      -1.475759

      8
      RAD
      0.305655

      9
      TAX
      -0.012329

      10
      PTRATIO
      -0.953464

      11
      B
      0.009393

      12
      LSTAT
      -0.525467

### 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([ 30.00821269,  25.0298606 ,  30.5702317 ,  28.60814055,  27.94288232])

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?

# your turn

```python
sns.distplot(lm.predict(X), axlabel="Predicted Housing Prices", bins=16)
```

- The shape appears to be normal with a slight positive skew.
- A housing price of 0 or negative does not make sense so in that regard there are outliers.

```python
sns.regplot(y=bos.PRICE, x=lm.predict(X), fit_reg = True)
plt.xlabel("Predicted Price")
plt.ylabel("Original Price")
plt.title("Relationship between Predicted and Original Housing Prices")
```

- Statsmodels is appears to be more geared towards modelling while scikit-learn seems better for machine learning.

### 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))
```

    11080.276284149868

#### 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)
```

    8.69056631064e-23

### 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.

$$MSE = \frac{ESS}{K}$$
$$MSR = \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.

# your turn

```python
lm = ols('PRICE ~ PTRATIO', bos).fit()
print(lm.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                  PRICE   R-squared:                       0.258
    Model:                            OLS   Adj. R-squared:                  0.256
    Method:                 Least Squares   F-statistic:                     175.1
    Date:                Tue, 05 Dec 2017   Prob (F-statistic):           1.61e-34
    Time:                        01:02:14   Log-Likelihood:                -1764.8
    No. Observations:                 506   AIC:                             3534.
    Df Residuals:                     504   BIC:                             3542.
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    Intercept     62.3446      3.029     20.581      0.000      56.393      68.296
    PTRATIO       -2.1572      0.163    -13.233      0.000      -2.477      -1.837
    ==============================================================================
    Omnibus:                       92.924   Durbin-Watson:                   0.725
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):              191.444
    Skew:                           1.001   Prob(JB):                     2.68e-42
    Kurtosis:                       5.252   Cond. No.                         160.
    ==============================================================================

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

- I see no relationship between the F and t statistics.

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.

# your turn

```python
lm = LinearRegression()
lm.fit(X[['CRIM','RM','PTRATIO']], bos.PRICE)
```

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

```python
y_pred = lm.predict(X[['CRIM','RM','PTRATIO']])
r_squared = sklearn.metrics.r2_score(bos.PRICE, y_pred)
print(r_squared)
```

    0.593414155114

```python
N = len(X['CRIM'])
K = 3
F = (r_squared / (1 - r_squared )) * ((N - K - 1) / K )
print(F)
```

    244.223870239

```python
lm.coef_
```

    array([-0.20210971,  7.3815667 , -1.0741868 ])

- The number of rooms is the most significant contributor to the model. As the number of rooms increase the value of the home increases. As crime and the pupil-teacher ratio by town goes down, the value of the home increases.

## 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
lm = LinearRegression()
lm = lm.fit(X[['CRIM','RM','PTRATIO','AGE']], bos.PRICE)
```

```python
y_pred = lm.predict(X[['CRIM','RM','PTRATIO','AGE']])
r_squared = sklearn.metrics.r2_score(bos.PRICE, y_pred)
print(r_squared)
```

    0.607673430691

```python
N = len(X['CRIM'])
K = 4
F = (r_squared / (1 - r_squared )) * ((N - K - 1) / K )
print(F)
```

    193.999344292

```python
resid =  bos.PRICE - y_pred
sse = sum(resid ** 2)
print(sse)
```

    16758.7376337

- The first model is better.

## 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?

# Your turn.

```python
#
# Model
#
model = ols(formula='PRICE ~ CRIM + RM + PTRATIO', data=bos)
fitted = model.fit()
print(fitted.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                  PRICE   R-squared:                       0.593
    Model:                            OLS   Adj. R-squared:                  0.591
    Method:                 Least Squares   F-statistic:                     244.2
    Date:                Tue, 05 Dec 2017   Prob (F-statistic):           1.09e-97
    Time:                        01:02:14   Log-Likelihood:                -1612.6
    No. Observations:                 506   AIC:                             3233.
    Df Residuals:                     502   BIC:                             3250.
    Df Model:                           3
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    Intercept     -3.3066      4.038     -0.819      0.413     -11.240       4.627
    CRIM          -0.2021      0.032     -6.301      0.000      -0.265      -0.139
    RM             7.3816      0.402     18.360      0.000       6.592       8.171
    PTRATIO       -1.0742      0.133     -8.081      0.000      -1.335      -0.813
    ==============================================================================
    Omnibus:                      233.249   Durbin-Watson:                   0.828
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):             2002.374
    Skew:                           1.802   Prob(JB):                         0.00
    Kurtosis:                      12.054   Cond. No.                         311.
    ==============================================================================

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

```python
plt.hist(fitted.resid, bins=20)
plt.ylabel('Count')
plt.xlabel('Normalized residuals')
```

- The residuals plot shows the error against the predicted value, and allows us to look for regions in the target where the error is greatest. The shape of the residuals can strongly inform us where OLS (ordinary least squares) is being most strongly effected by the components of our model (namely the features). Here there appears to be a positive skew to the plot which could be due to some outliers.

```python
import statsmodels.api as sm1

#
# Quantile plot of residuals.
#
sm1.qqplot(fitted.resid, line='s')
```

- If the residuals are from a normal distribution with mean 0, the points tend to fall along the reference line that has an intercept of 0 and a slope equal to the estimated standard deviation.

- The fitted vs. residual allows you to see the error as it relates to the model. A flat line tells us that a linear model is a good fit for the data. It does not tell us anything about the error however.

- The quartile plot helps us to assess whether the residuals are roughly normally distributed. If the residuals look far from normal we may be in trouble. In particular, if the residual tend to be larger in magnitude than what we would expect from the normal distribution, then our p-values and confidence intervals may be too optimisitic. i.e., we may fail to adequately account for the full variability of the data.

```python
#
# Cooks distance for high leverage.
#
influence = fitted.get_influence()
#c is the distance and p is p-value
(c, p) = influence.cooks_distance
plt.stem(np.arange(len(c)), c, markerfmt=",")
```

```python
#
# 365 is a high leverage point.
#
print(np.where(c == c.max()))
```

    (array([365]),)

```python
#
# import regressionplots.
#
from statsmodels.graphics.regressionplots import *

#
# Leverage plot.
#
plot_leverage_resid2(fitted)
```

```python
#
# Influence plot.
#
influence_plot(fitted)
```

```python
#
# Remove outliers and high leverage point(s) and replot influence.
#
drop = fitted.resid > 20
drop[365] = True
new_bos = pd.DataFrame(bos[np.invert(drop)])

#
# Regenerate the model
#
new_model = ols(formula='PRICE ~ CRIM + RM + PTRATIO', data=new_bos)
new_fitted = new_model.fit()
print(new_fitted.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                  PRICE   R-squared:                       0.718
    Model:                            OLS   Adj. R-squared:                  0.716
    Method:                 Least Squares   F-statistic:                     419.5
    Date:                Tue, 05 Dec 2017   Prob (F-statistic):          1.70e-135
    Time:                        01:03:02   Log-Likelihood:                -1479.2
    No. Observations:                 499   AIC:                             2966.
    Df Residuals:                     495   BIC:                             2983.
    Df Model:                           3
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    Intercept     -6.1637      3.296     -1.870      0.062     -12.640       0.313
    CRIM          -0.2063      0.026     -8.025      0.000      -0.257      -0.156
    RM             8.0054      0.333     24.069      0.000       7.352       8.659
    PTRATIO       -1.1537      0.107    -10.801      0.000      -1.364      -0.944
    ==============================================================================
    Omnibus:                       12.541   Durbin-Watson:                   1.097
    Prob(Omnibus):                  0.002   Jarque-Bera (JB):               24.150
    Skew:                           0.009   Prob(JB):                     5.70e-06
    Kurtosis:                       4.078   Cond. No.                         315.
    ==============================================================================

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

```python
#
# Quantile plot of residuals.
#
sm1.qqplot(new_fitted.resid, line='s')
```

```python
plt.hist(new_fitted.resid, bins=20)
plt.ylabel('Count')
plt.xlabel('Normalized residuals')
```

```python
#
# Influence plot.
#
influence_plot(new_fitted)
```

- The model fits much better after removing outliers and high leverage points.