Skip to main content

15  Simple Regression


Suppose we have some bivariate data: \(\hat{x}_{i}, \hat{y}_{i}\). In the previous chapter, we summarized the strength of association between two variables using correlation. Correlation tells us how strongly \(\hat{x}\) and \(\hat{y}\) move together, but not by how much \(\hat{y}\) changes per unit change in \(\hat{x}\). Regression answers that question by fitting a line through the data.

This chapter asks two questions.

ImportantKey Definition

A fitted value \(\hat{y}_{i}^{\mathrm{fit}}\) is the model’s prediction for observation \(i\). The superscript \(\mathrm{fit}\) marks a fitted value, while \(\hat{y}_{i}\) is an observed outcome.

The sample residual is the gap between actual and fitted, \(\hat{e}_{i} = \hat{y}_{i} - \hat{y}_{i}^{\mathrm{fit}}\).

Ordinary least squares (OLS) fits a model by minimizing the sum of squared residuals, \(\sum_{i=1}^{n} \hat{e}_{i}^{2}\).

We square the residuals so that positive and negative errors do not cancel, and so that big misses are penalized more than small ones.

15.1 Simple Linear Regression

This refers to fitting a linear model to bivariate data.

Model and Objective

A regression line goes through the cloud of points. To calculate it, we need some definitions first.

ImportantKey Definition

Our simple linear model is \[ \hat{y}_{i}=b_{0}+b_{1} \hat{x}_{i}+\hat{e}_{i}, \] where \(b_{0}\) and \(b_{1}\) are the coefficients, and \(\hat{x}_{i}, \hat{y}_{i}\) are the data for observation \(i\).

The residual \(\hat{e}_{i}\) is the vertical gap between observation \(i\)’s actual value of \(\hat{y}_{i}\) and the model’s prediction for that observation: \[\hat{e}_{i}=\hat{y}_{i}-(b_{0}+b_{1}\hat{x}_{i}).\] During minimization, \(\hat{e}_{i}\) depends on the trial coefficients \(b_{0}, b_{1}\); the final residuals use the fitted coefficients \(\hat{b}_{0}, \hat{b}_{1}\). For the linear model, the OLS problem is \[\begin{aligned} \min_{b_{0}, b_{1}} \sum_{i=1}^{n} \left( \hat{e}_{i} \right)^2 &= \min_{b_{0}, b_{1}} \sum_{i=1}^{n} \left( \hat{y}_{i} - [b_{0}+b_{1} \hat{x}_{i}] \right)^2. \end{aligned}\] We could find the best-fitting coefficients numerically, by trying out different combinations of \((b_{0}, b_{1})\). You can do that in this example, which can help you understand what is happening. This problem has an analytic solution, however, which means we know that the optimal OLS slope and intercept are \[\hat{b}_{1} = \frac{\hat{c}_{XY}}{\hat{v}_{X}}, \qquad \hat{b}_{0} = \hat{m}_{Y} - \hat{b}_{1} \hat{m}_{X}.\] The computer uses this math to find the coefficients quickly. For example, the code below fits a line to two cardinal variables and checks the coefficients with these formulas.

Code
## MAIN EXAMPLE
# Lab data: 3294 US workers, with hourly wages in 1980 dollars
# Pull two columns from Wages1 and rename for compact code below
data(Wages1, package='Ecdat')
xy_hat <- Wages1[, c('school', 'wage')]
colnames(xy_hat) <- c('x_hat', 'y_hat') # x_hat=years of schooling, y_hat=hourly wage

# Run a Simple Regression
reg <- lm(y_hat ~ x_hat, data=xy_hat)
coef(reg)
## (Intercept)       x_hat 
##  -0.7225066   0.5571617
# The intercept of about -0.72 is a negative hourly wage.

# Manual verification
x_hat <- xy_hat[, 'x_hat']
y_hat <- xy_hat[, 'y_hat']
b1 <- cov( x_hat, y_hat)/var(x_hat)
b1
## [1] 0.5571617
b0 <- mean(y_hat) - b1*mean(x_hat)
b0
## [1] -0.7225066

The least-squares slope \(\hat{b}_{1}\) seeks to answer “how much does the fitted value \(\hat{y}^{\mathrm{fit}}\) change per one-unit change in \(\hat{x}\)?” When the data are not known to be linearly related, the formula shows the slope is generally just the covariance divided by the variance of \(\hat{x}\). As such, \(\hat{b}_{1}\) is generally interpreted as a summary statistic for associations, just like the correlation. In fact, \(\hat{b}_{1} = \hat{r}_{XY} \cdot \hat{s}_{Y}/\hat{s}_{X}\), so the simple regression slope is a rescaled version of the Pearson correlation statistic.

Code
# Slope
coef(reg)[2]
##     x_hat 
## 0.5571617
# Pearson Correlation
cor(xy_hat$x_hat, xy_hat$y_hat)
## [1] 0.2824925
# Slope from correlation: R_{XY} * S_Y / S_X
cor(xy_hat$x_hat, xy_hat$y_hat) * sd(xy_hat$y_hat)/sd(xy_hat$x_hat)
## [1] 0.5571617

The intercept guarantees the fitted line passes through the point of means \((\hat{m}_{X}, \hat{m}_{Y})\). To see why, add \(\hat{b}_{1}\hat{m}_{X}\) to both sides of the intercept formula to get \(\hat{m}_{Y} = \hat{b}_{0} + \hat{b}_{1}\hat{m}_{X}\). This says that the line’s height at \(\hat{m}_{X}\) is exactly \(\hat{m}_{Y}\).

Derive the OLS intercept and slope by taking the partial derivative of the objective with respect to each coefficient and setting it to zero.

Start with the intercept. By the chain rule, the derivative of each squared term with respect to \(b_{0}\) is \(-2\) times the residual: \[\begin{aligned} 0 &= \frac{\partial}{\partial b_{0}} \sum_{i=1}^{n} \left( \hat{y}_{i} - [b_{0}+b_{1} \hat{x}_{i}] \right)^2 \\ &= \sum_{i=1}^{n} -2\left( \hat{y}_{i} - [b_{0}+b_{1} \hat{x}_{i}] \right). \end{aligned}\] Divide both sides by \(-2n\), and use the means \(\hat{m}_{X} = \sum_{i=1}^{n}\hat{x}_{i}/n\) and \(\hat{m}_{Y} = \sum_{i=1}^{n}\hat{y}_{i}/n\): \[\begin{aligned} 0 &= \frac{1}{n}\sum_{i=1}^{n} \hat{y}_{i} - b_{0} - b_{1} \frac{1}{n}\sum_{i=1}^{n} \hat{x}_{i} \\ &= \hat{m}_{Y} - b_{0} - b_{1} \hat{m}_{X} \\ \Rightarrow \hat{b}_{0} &= \hat{m}_{Y} - \hat{b}_{1}\hat{m}_{X}. \end{aligned}\]

Now the slope. The derivative of each squared term with respect to \(b_{1}\) is \(-2 \hat{x}_{i}\) times the residual: \[\begin{aligned} 0 &= \frac{\partial}{\partial b_{1}} \sum_{i=1}^{n} \left( \hat{y}_{i} - [b_{0}+b_{1} \hat{x}_{i}] \right)^2 \\ &= \sum_{i=1}^{n} -2\left( \hat{y}_{i} - [b_{0}+b_{1} \hat{x}_{i}] \right) \hat{x}_{i}. \end{aligned}\] Divide both sides by \(-2\), substitute the intercept \(b_{0} = \hat{m}_{Y} - b_{1}\hat{m}_{X}\), and split the sum in two: \[\begin{aligned} 0 &= \sum_{i=1}^{n} \left( [\hat{y}_{i} - \hat{m}_{Y}] - b_{1} [\hat{x}_{i} - \hat{m}_{X}] \right) \hat{x}_{i} \\ &= \sum_{i=1}^{n} (\hat{y}_{i} - \hat{m}_{Y}) \hat{x}_{i} - b_{1} \sum_{i=1}^{n} (\hat{x}_{i} - \hat{m}_{X}) \hat{x}_{i}. \end{aligned}\] Deviations from the mean sum to zero, so \(\sum_{i=1}^{n} (\hat{y}_{i} - \hat{m}_{Y}) \hat{m}_{X} = 0\) and \(\sum_{i=1}^{n} (\hat{x}_{i} - \hat{m}_{X}) \hat{m}_{X} = 0\). Subtracting these from the two sums replaces each trailing \(\hat{x}_{i}\) with \(\hat{x}_{i} - \hat{m}_{X}\): \[\begin{aligned} 0 &= \sum_{i=1}^{n} (\hat{x}_{i} - \hat{m}_{X})(\hat{y}_{i} - \hat{m}_{Y}) - b_{1} \sum_{i=1}^{n} (\hat{x}_{i} - \hat{m}_{X})^2 \\ \Rightarrow \hat{b}_{1} &= \frac{\sum_{i=1}^{n}(\hat{x}_{i}-\hat{m}_{X})(\hat{y}_{i}-\hat{m}_{Y})}{\sum_{i=1}^{n}(\hat{x}_{i}-\hat{m}_{X})^2}. \end{aligned}\] Dividing the top and bottom by \(n\) gives \(\hat{b}_{1} = \hat{c}_{XY}/\hat{v}_{X}\). This point is a minimum and not a maximum because each squared residual is U-shaped in the coefficients, so their sum is bowl-shaped in \((b_{0}, b_{1})\) and the only point where both derivatives are zero is the bottom of the bowl.

Predictions

Plugging the coefficients into the line gives the fitted value for observation \(i\). A prediction uses the same line at any design point \(x\). A fitted value is thus the prediction at a sample point. \[\begin{aligned} \hat{y}^{\mathrm{pred}}(x) &= \hat{b}_{0} + \hat{b}_{1} x \\ \hat{y}_{i}^{\mathrm{fit}} &= \hat{y}^{\mathrm{pred}}(\hat{x}_{i}) = \hat{b}_{0} + \hat{b}_{1} \hat{x}_{i}. \end{aligned}\]

Fitted values are useful for in-sample diagnostics: comparing \(\hat{y}_{i}^{\mathrm{fit}}\) to \(\hat{y}_{i}\) shows where the model fits well and where it misses (these are the residuals \(\hat{e}_{i}\) we minimized). Predictions at new values are useful for out-of-sample questions like “what hourly wage would the model predict for a worker with 14 years of schooling?”. Making predictions far away from your observations is risky, with no nearby data to anchor its prediction. Also note that a model can predict well and still not tell us what would happen. For example, a store’s daily umbrella sales predict its raincoat sales well, yet a discount that raises umbrella sales need not raise raincoat sales.

Code
# Find predicted values and residuals
predict(reg)
resid(reg)

# Manual verification
y_fit <- b0+b1*x_hat
y_fit

e_hat <- y_hat - y_fit
e_hat

Suppose the intercept is a value the outcome cannot take, such as a negative hourly wage. What went wrong?

Nothing went wrong with the fit. The intercept is the prediction at \(x=0\), \(\hat{b}_{0}=\hat{y}^{\mathrm{pred}}(0)\). When no observation has \(\hat{x}_{i}\) near zero, this prediction is far away from the observations. The intercept sets the height of the line so that it passes through the point of means, and need not be a good guess for the outcome at \(x=0\). The code below checks the observed range of \(\hat{x}\) and the predictions at both ends.

Code
# The intercept from above, about -0.72, is a negative hourly wage.
# Range of schooling in the sample
range(x_hat)
## [1]  3 16
# No worker in the sample has fewer than 3 years of schooling.
# So the prediction at zero years is far away from the observations.
# The intercept is not a good guess for the wage of a worker with no schooling.

# Predictions at the lowest and highest observed years of schooling
predict(reg, newdata=data.frame(x_hat=range(x_hat)))
##         1         2 
## 0.9489785 8.1920805
# Over the observed range of 3 to 16 years, every prediction is positive.

Suppose we have a dataset with \(n = 3\) observations: \(\{(1,2), (2,2.5), (3,4)\}\).

We can compute the regression coefficients and model predictions in five steps.

Step 1. Sample Means

\[\begin{aligned} \hat{m}_{X} &= \frac{1+2+3}{3} = 2 \\ \hat{m}_{Y} &= \frac{2 + 2.5 + 4}{3} = \frac{17}{6}. \end{aligned}\]

Step 2. Covariance and Variance

\[\begin{array}{c|c|c|c|c|c|c|} i & \hat{x}_{i} & \hat{y}_{i} & (\hat{x}_{i} - \hat{m}_{X}) & (\hat{y}_{i} - \hat{m}_{Y}) & (\hat{x}_{i} - \hat{m}_{X})(\hat{y}_{i} - \hat{m}_{Y}) & (\hat{x}_{i} - \hat{m}_{X})^2 \\ \hline 1 & 1 & 2 & -1 & -\tfrac{5}{6} & \tfrac{5}{6} & 1 \\ 2 & 2 & 2.5 & 0 & -\tfrac{1}{3} & 0 & 0 \\ 3 & 3 & 4 & 1 & \tfrac{7}{6} & \tfrac{7}{6} & 1 \\ \end{array}\]

\[\begin{aligned} \hat{c}_{XY} &= \sum_{i=1}^3 (\hat{x}_{i} - \hat{m}_{X})(\hat{y}_{i} - \hat{m}_{Y}) /3 = [\tfrac{5}{6} + \tfrac{7}{6}]/3 = 2/3 \\ \hat{v}_{X} &= \sum_{i=1}^3 (\hat{x}_{i} - \hat{m}_{X})^2 / 3 = [1 + 1]/3 = 2/3. \end{aligned}\]

Step 3. Slope and Intercept

\[ \hat{b}_{1} = \frac{\hat{c}_{XY}}{\hat{v}_{X}} = \frac{2/3}{2/3} = 1, \]

\[ \hat{b}_{0} = \hat{m}_{Y} - \hat{b}_{1} \hat{m}_{X} = \frac{17}{6} - 2 = \frac{5}{6} \approx 0.83. \]

\[ \hat{y}_{i}^{\mathrm{fit}} = \hat{b}_{0} + \hat{b}_{1} \hat{x}_{i} = \frac{5}{6} + \hat{x}_{i}. \]

Step 4. Fitted Values and Residuals

\[ \begin{array}{c|c|c|c|c} i & \hat{x}_{i} & \hat{y}_{i} & \hat{y}_{i}^{\mathrm{fit}} = \tfrac{5}{6} + \hat{x}_{i} & \hat{e}_{i} = \hat{y}_{i} - \hat{y}_{i}^{\mathrm{fit}} \\ \hline 1 & 1 & 2 & \tfrac{11}{6} & \tfrac{1}{6} \\ 2 & 2 & 2.5 & \tfrac{17}{6} & -\tfrac{1}{3} \\ 3 & 3 & 4 & \tfrac{23}{6} & \tfrac{1}{6} \\ \end{array} \]

Step 5: Prediction at chosen design point \(x=2.5\) \[\begin{aligned} \hat{y}^{\mathrm{pred}}(2.5) &= \frac{5}{6} + 2.5 = \frac{5}{6} + \frac{15}{6} = \frac{20}{6} = \frac{10}{3} \approx 3.3 \end{aligned}\]

Code
## Data
xy0_hat <- data.frame(
    x_hat=c(1, 2, 3),
    y_hat=c(2, 2.5, 4))

## Simple Linear Regression
reg0 <- lm(y_hat ~ x_hat, data=xy0_hat)
coef(reg0)
## (Intercept)       x_hat 
##   0.8333333   1.0000000

## Sample Predictions
predict(reg0)
##        1        2        3 
## 1.833333 2.833333 3.833333
resid(reg0)
##          1          2          3 
##  0.1666667 -0.3333333  0.1666667

## Out-of-Sample predictions
predict(reg0, newdata=data.frame(x_hat=c(2.5)))
##        1 
## 3.333333

Simple Constant Regression

Sometimes we restrict one coefficient of the simple linear model to zero, to get an even simpler model. Setting \(b_{1}=0\) removes \(\hat{x}_{i}\) from the model, so every observation gets the same fitted value. Our simple constant model is \[ \hat{y}_{i}=b_{0}+\hat{e}_{i}, \] where the coefficient \(b_{0}\) is a single number shared by every observation, and \(\hat{y}_{i}\) is the data for observation \(i\).

The constant model is useful as a baseline: it is our best prediction of \(\hat{y}_{i}\) when we ignore \(\hat{x}_{i}\), and other regressions can be judged by how much they improve on it. For the constant model, the OLS problem is \[ \min_{b_{0}} \sum_{i=1}^{n} \left( \hat{y}_{i} - b_{0} \right)^2. \] Setting the derivative to zero shows that the least-squares constant is the sample mean: \[\begin{aligned} \hat{b}_{0} &= \frac{\sum_{i=1}^{n} \hat{y}_{i}}{n} = \hat{m}_{Y}. \end{aligned}\] So every fitted value is the same, \(\hat{y}_{i}^{\mathrm{fit}} = \hat{m}_{Y}\).

Code
# Regress on a constant only: '~ 1' means an intercept and no slope
reg_const <- lm(y_hat ~ 1, data=xy_hat)
coef(reg_const)
## (Intercept) 
##    5.757585

# Same as the sample mean
mean(xy_hat[, 'y_hat'])
## [1] 5.757585

Regression Through the Origin

Setting \(b_{0}=0\) instead forces the fitted line through the point \((0, 0)\). Our simple model through the origin is \[ \hat{y}_{i}=b_{1}\hat{x}_{i}+\hat{e}_{i}, \] where the coefficient \(b_{1}\) is a slope with no intercept. This model is useful when theory says \(\hat{y}\) must be zero whenever \(\hat{x}\) is zero, such as a firm’s total cost when it has no fixed costs. Fit this model to the wage data, and compare its slope with the slope of the unrestricted line.

Minimizing \(\sum_{i=1}^{n} ( \hat{y}_{i} - b_{1}\hat{x}_{i} )^2\) gives \[\begin{aligned} \hat{b}_{1} &= \frac{\sum_{i=1}^{n} \hat{x}_{i}\hat{y}_{i}}{\sum_{i=1}^{n} \hat{x}_{i}^{2}}. \end{aligned}\] Unlike \(\hat{c}_{XY}/\hat{v}_{X}\), this slope does not subtract the means \(\hat{m}_{X}\) and \(\hat{m}_{Y}\).

Code
# Regress without an intercept: '0 +' drops b0 from the model
reg_origin <- lm(y_hat ~ 0 + x_hat, data=xy_hat)
coef(reg_origin)
##     x_hat 
## 0.4962765
# The unrestricted line already has an intercept near zero, about -0.72.
# So pinning the line to the origin changes its slope only a little.
# The slope moves from about 0.56 to about 0.50.

# Manual verification
sum(x_hat*y_hat)/sum(x_hat^2)
## [1] 0.4962765

# Scatter with the axes extended to the origin, then add all three fitted lines
plot(y_hat ~ x_hat, xy_hat, col=grey(0, .1), pch=16,
    xlim=c(0, max(x_hat)), ylim=c(0, max(y_hat)),
    main=NA,
    xlab='Years of Schooling',
    ylab='Hourly Wage (1980 Dollars)')
abline(reg, col=rgb(1, 0, 0, .8), lwd=2)
abline(reg_origin, col=rgb(1, 0, 0, .8), lwd=2, lty=2)
abline(h=coef(reg_const), col=rgb(0, 0, 1, .8), lwd=2)
legend('topleft', legend=c('Unrestricted', 'Through origin', 'Constant'),
    col=c(rgb(1, 0, 0, .8), rgb(1, 0, 0, .8), rgb(0, 0, 1, .8)),
    lwd=2, lty=c(1, 2, 1), bty='n')

When the unrestricted line already has an intercept near zero, pinning the line to the origin changes its slope only a little.

15.2 Goodness of Fit

After fitting the line, the next question is how much of the variation in \(\hat{y}\) the line actually accounts for.

ImportantKey Definition

The R-squared (or coefficient of determination) \(\hat{R}^{2}\) is the share of total variation in \(\hat{y}_{i}\) that the model explains. Using the Total, Explained, and Residual sums of squares, \[\underbrace{\sum_{i}(\hat{y}_{i}-\hat{m}_{Y})^2}_{\text{TSS}} = \underbrace{\sum_{i}(\hat{y}_{i}^{\mathrm{fit}}-\hat{m}_{Y})^2}_{\text{ESS}}+\underbrace{\sum_{i}\hat{e}_{i}^2}_{\text{RSS}},\] \[\hat{R}^{2} = \frac{\hat{\text{ESS}}}{\hat{\text{TSS}}}=1-\frac{\hat{\text{MSE}}}{\hat{v}_{Y}},\] where \(\hat{\text{MSE}}=\hat{\text{RSS}}/n\) is the mean squared error and \(\hat{v}_{Y}=\hat{\text{TSS}}/n\) is the variance of \(\hat{y}\). Equivalently, \(\hat{R}^{2}\) equals the squared Pearson correlation between the fitted values \(\hat{y}_{i}^{\mathrm{fit}}\) and the actual \(\hat{y}_{i}\); \(\hat{r}_{YY^{\mathrm{fit}}}^{2}\).

\(R^{2}\) always falls in \([0,1]\) and is useful as a single-number summary of how much of the variation in \(\hat{y}\) the linear model accounts for. A value of \(\hat{R}^{2}=1\) means perfect in-sample fit, and \(\hat{R}^{2}=0\) means the model does no better than predicting the mean. The simple constant regression predicts the mean for every observation, so every term of its \(\hat{\text{ESS}}\) is zero and its \(\hat{R}^{2}=0\). The first qualitative check should still be a plot of the line over the data, however, since \(R^{2}\) is only a summary. (Even a model with \(\hat{R}^{2}\) near \(1\) only describes how the mean of \(\hat{y}\) differs across values of \(\hat{x}\).)

Code
## MAIN EXAMPLE
# Manually Compute R2
e_hat <- resid(reg)
mse_hat  <- mean(e_hat^2)
y_hat <- xy_hat[, 'y_hat']
v_hat  <- mean((y_hat-mean(y_hat))^2)
R2 <- 1 - mse_hat/v_hat
R2
## [1] 0.07980201

# Check R2
summary(reg)$r.squared
## [1] 0.07980201

# Double Check R2
R <- cor(xy_hat[, 'y_hat'], predict(reg))
R^2
## [1] 0.07980201

We use the dataset \(\{(1,2), (2,2.5), (3,4)\}\) to compute Goodness of fit.

From before, we know that \(\hat{m}_{Y} = \frac{17}{6}\) and the fitted values and residuals from the regression are \[\begin{gathered} \hat{y}_{1}^{\mathrm{fit}} = \frac{11}{6},\qquad \hat{y}_{2}^{\mathrm{fit}} = \frac{17}{6},\qquad \hat{y}_{3}^{\mathrm{fit}} = \frac{23}{6}.\\ \hat{e}_{1} = \frac{1}{6},\qquad \hat{e}_{2} = -\frac{1}{3},\qquad \hat{e}_{3} = \frac{1}{6}. \end{gathered}\]

Step 1. Total Sum of Squares (TSS)

\[\begin{aligned} \hat{y}_{1} - \hat{m}_{Y} &= 2 - \frac{17}{6} = -\frac{5}{6}, \\ \hat{y}_{2} - \hat{m}_{Y} &= 2.5 - \frac{17}{6} = -\frac{1}{3}, \\ \hat{y}_{3} - \hat{m}_{Y} &= 4 - \frac{17}{6} = \frac{7}{6}. \end{aligned}\]

\[\begin{aligned} \hat{\text{TSS}} &= \sum_{i} (\hat{y}_{i} - \hat{m}_{Y})^2 \\ &= \left(-\frac{5}{6}\right)^2 + \left(-\frac{1}{3}\right)^2 + \left(\frac{7}{6}\right)^2 \\ &= \frac{25}{36} + \frac{1}{9} + \frac{49}{36} = \frac{78}{36} = \frac{13}{6}. \end{aligned}\]

Step 2. Explained Sum of Squares (ESS)

\[ \hat{y}_{1}^{\mathrm{fit}} - \hat{m}_{Y} = -1,\qquad \hat{y}_{2}^{\mathrm{fit}} - \hat{m}_{Y} = 0,\qquad \hat{y}_{3}^{\mathrm{fit}} - \hat{m}_{Y} = 1. \]

\[ \hat{\text{ESS}} = \sum_{i} (\hat{y}_{i}^{\mathrm{fit}} - \hat{m}_{Y})^2 = (-1)^2 + 0^2 + 1^2 = 2. \]

Step 3. Residual Sum of Squares (RSS)

\[ \hat{\text{RSS}} = \sum_{i} \hat{e}_{i}^2 = \left(\frac{1}{6}\right)^2 + \left(-\frac{1}{3}\right)^2 + \left(\frac{1}{6}\right)^2. \]

\[ \hat{\text{RSS}} = \frac{1}{36} + \frac{1}{9} + \frac{1}{36} = \frac{1}{36} + \frac{4}{36} + \frac{1}{36} = \frac{6}{36} = \frac{1}{6}. \]

Step 4. Check the Decomposition

\[ \hat{\text{ESS}} + \hat{\text{RSS}} = 2 + \frac{1}{6} = \frac{12}{6} + \frac{1}{6} = \frac{13}{6} = \hat{\text{TSS}}. \]

Step 5. Coefficient of Determination

\[ \hat{R}^{2} = \frac{\hat{\text{ESS}}}{\hat{\text{TSS}}} = \frac{2}{13/6} = \frac{12}{13} \approx 0.92 \]

(Verification) \[ 1 - \frac{\hat{\text{RSS}}}{\hat{\text{TSS}}} = 1 - \frac{1/6}{13/6} = 1 - \frac{1}{13} = \frac{12}{13}. \]

Code
# Compute R2
e0_hat <- resid(reg0)
RSS0  <- sum(e0_hat^2)
y0_hat <- xy0_hat[, 'y_hat']
TSS0  <- sum((y0_hat-mean(y0_hat))^2)
R2_0 <- 1 - RSS0/TSS0
R2_0
## [1] 0.9230769

# compare with our calculation: 12/13
12/13
## [1] 0.9230769

# compare with our intuitive benchmark: squared correlation of actuals and fitted values
cor(y0_hat, predict(reg0))^2
## [1] 0.9230769

Cross-Validation

To measure out-of-sample fit and predictive power, we average the squared errors of predictions for held-out observations (Allen 1974; Stone 1974; Hall and Racine 2015).

ImportantKey Definition

The leave-one-out cross-validation error measures how well a model predicts observations that were not used to fit it, \[\begin{aligned} \hat{\text{CV}} &= \frac{1}{n}\sum_{i=1}^{n} \left[ \hat{y}_{i} - \hat{y}^{\mathrm{pred}(-i)}(\hat{x}_{i}) \right]^2, \end{aligned}\] where \(\hat{y}^{\mathrm{pred}(-i)}(\hat{x}_{i})\) is the model’s prediction at \(\hat{x}_{i}\) from a fit that excluded observation \(i\).

\(\hat{\text{CV}}\) is useful for comparing models by how well they predict new observations, rather than the observations used to fit them. It drops one observation at a time, like the jackknife. A model that fits the noise in the sample predicts held-out observations poorly, so its \(\hat{\text{CV}}\) rises above its in-sample \(\hat{\text{MSE}}\).

The code below refits the line \(n\) times, each time without one observation, and predicts that observation’s outcome.

Code
## MAIN EXAMPLE
# Leave-one-out errors for the line
n <- nrow(xy_hat)
loo_errors <- rep(NA, n)
for(i in seq_len(n)){
    xy_i <- xy_hat[-i, ] # drop worker i
    reg_i <- lm(y_hat ~ x_hat, data=xy_i)
    # Predict worker i from the fit without worker i
    y_pred_i <- predict(reg_i, newdata=xy_hat[i, ])
    loo_errors[i] <- xy_hat[i, 'y_hat'] - y_pred_i
}

# CV
cv_hat <- mean(loo_errors^2)
cv_hat
## [1] 9.843857
# The line's CV of about 9.84 is just above its MSE of about 9.83.
# It predicts held-out workers only slightly worse than those it was fit to.

The out-of-sample R-squared replaces the fitted values in \(\hat{R}^{2}=1-\hat{\text{MSE}}/\hat{v}_{Y}\) with the held-out predictions \(\hat{y}^{\mathrm{pred}(-i)}(\hat{x}_{i})\). The mean squared error \(\hat{\text{MSE}}\) then becomes \(\hat{\text{CV}}\), and the variance \(\hat{v}_{Y}\) becomes its leave-one-out version \(\hat{v}_{Y}^{\text{CV}}\), which gives \[\begin{aligned} \hat{R}^{2}_{\text{CV}} &= 1 - \frac{\hat{\text{CV}}}{\hat{v}_{Y}^{\text{CV}}}, \\ \hat{v}_{Y}^{\text{CV}} &= \frac{1}{n}\sum_{i=1}^{n} \left[ \hat{y}_{i} - \hat{m}_{Y}^{(-i)} \right]^2, \end{aligned}\] where \(\hat{m}_{Y}^{(-i)}\) is the mean of \(\hat{y}\) without observation \(i\). Since \(\hat{v}_{Y}^{\text{CV}}\) is the \(\hat{\text{CV}}\) of the simple constant regression, that model has \(\hat{R}^{2}_{\text{CV}}=0\). Unlike \(\hat{R}^{2}\), \(\hat{R}^{2}_{\text{CV}}\) can be negative and is when a model predicts held-out observations worse than the mean of the other observations would. Unlike \(\hat{R}^{2}\), the out-of-sample version is not equivalent to the squared correlation between the observations and the predictions (\(\hat{y}_{i}\) and \(\hat{y}^{\mathrm{pred}(-i)}(\hat{x}_{i})\)).

The code below compares each observation with the mean of the other observations, which gives \(\hat{v}_{Y}^{\text{CV}}\) and then \(\hat{R}^{2}_{\text{CV}}\).

Code
# Leave-one-out errors for the mean
loo_errors_mean <- rep(NA, n)
for(i in seq_len(n)){
    xy_i <- xy_hat[-i, ] # drop worker i
    # Compare worker i with the mean of the other workers
    loo_errors_mean[i] <- xy_hat[i, 'y_hat'] - mean(xy_i[, 'y_hat'])
}

# Leave-one-out version of the variance
v_cv_hat <- mean(loo_errors_mean^2)
v_cv_hat
## [1] 10.69082
# The line's CV of about 9.84 is below this value of about 10.69.

# Out-of-sample R-squared
r2_cv <- 1 - cv_hat/v_cv_hat
# Correlation of the observations and the held-out predictions, then its square
r_cv <- cor(xy_hat[, 'y_hat'], xy_hat[, 'y_hat'] - loo_errors)
r2_gen_cv <- r_cv^2
c(R2=R2, R2_CV=r2_cv, r_CV=r_cv, R2_gen_CV=r2_gen_cv)
##         R2      R2_CV       r_CV  R2_gen_CV 
## 0.07980201 0.07922352 0.28047927 0.07866862
# R2_CV is positive, about 0.079, since CV is below the leave-one-out variance.
# Out of sample, the line predicts wages better than the other workers' mean.
# With 3294 workers, dropping one barely moves the line.
# So R2_CV is close to the in-sample R2 of about 0.080.
# The squared correlation of about 0.079 is close to both.
# The correlation itself is positive, about 0.28.

With many observations, dropping one barely moves the line, so \(\hat{R}^{2}_{\text{CV}}\) is close to the in-sample \(\hat{R}^{2}\).

We can also compare the line with the simple constant regression out of sample. The code below repeats the loop for the constant model, whose held-out prediction for each observation is the mean of the other observations.

Code
# Leave-one-out prediction errors for the constant model
loo_errors_const <- rep(NA, n)
for(i in seq_len(n)){
    xy_i <- xy_hat[-i, ] # drop worker i
    reg_const_i <- lm(y_hat ~ 1, data=xy_i)
    # Predict worker i from the fit without worker i
    y_pred_i <- predict(reg_const_i, newdata=xy_hat[i, ])
    loo_errors_const[i] <- xy_hat[i, 'y_hat'] - y_pred_i
}
cv_const_hat <- mean(loo_errors_const^2)
r2_cv_const <- 1 - cv_const_hat/v_cv_hat

# In-sample and out-of-sample fit of both models
fit_compare <- rbind(
    Line=c(MSE=mse_hat, CV=cv_hat, R2=R2, R2_CV=r2_cv),
    Constant=c(MSE=mean(resid(reg_const)^2), CV=cv_const_hat,
        R2=summary(reg_const)$r.squared, R2_CV=r2_cv_const))
round(fit_compare, 3)
##             MSE     CV   R2 R2_CV
## Line      9.832  9.844 0.08 0.079
## Constant 10.684 10.691 0.00 0.000
# The line fits better in sample, and the order holds out of sample.
# The line's CV is about 9.84, while the constant model's is about 10.69.
# Schooling explains enough of the variation in wages.
# So estimating a slope removes more error from predictions than its noise adds.

In sample, the line fits better than the constant model, as it must, since the constant model is the line with \(b_{1}=0\). Out of sample, the line does better only when \(\hat{x}\) explains enough of the variation in \(\hat{y}\) that estimating a slope removes more error from the predictions than its noise adds. The constant model’s \(\hat{\text{CV}}\) is exactly \(\hat{v}_{Y}^{\text{CV}}\), so its \(\hat{R}^{2}_{\text{CV}}\) is \(0\), the same as its in-sample \(\hat{R}^{2}\). With \(\hat{v}_{Y}\) in the denominator instead, even the constant model would score below \(0\), because each held-out prediction is the mean of the other \(n-1\) observations, while \(\hat{m}_{Y}\) also includes the observation being predicted.

Repeat the cross-validation for the \(50\) states in USArrests, the data in the figure that opens this chapter. Regress murder arrests per 100K on the urban population share, then compare \(\hat{R}^{2}\), \(\hat{R}^{2}_{\text{CV}}\), and the squared correlation of the observations and the held-out predictions.

Code
xy2_hat <- USArrests[, c('UrbanPop', 'Murder')]
colnames(xy2_hat) <- c('x_hat', 'y_hat') # x_hat=urban population share, y_hat=murder arrests per 100K
reg_usa <- lm(y_hat ~ x_hat, data=xy2_hat)

# Leave-one-out errors for the line and for the mean
n_usa <- nrow(xy2_hat)
loo_errors_usa <- rep(NA, n_usa)
loo_errors_mean_usa <- rep(NA, n_usa)
for(i in seq_len(n_usa)){
    xy2_i <- xy2_hat[-i, ] # drop state i
    reg_usa_i <- lm(y_hat ~ x_hat, data=xy2_i)
    # Predict state i from the fit without state i
    y_pred_i <- predict(reg_usa_i, newdata=xy2_hat[i, ])
    loo_errors_usa[i] <- xy2_hat[i, 'y_hat'] - y_pred_i
    # Compare state i with the mean of the other states
    loo_errors_mean_usa[i] <- xy2_hat[i, 'y_hat'] - mean(xy2_i[, 'y_hat'])
}

# In-sample error, CV, and the leave-one-out version of the variance
cv_usa_hat <- mean(loo_errors_usa^2)
v_cv_usa_hat <- mean(loo_errors_mean_usa^2)
c(MSE=mean(resid(reg_usa)^2), CV=cv_usa_hat, v_CV=v_cv_usa_hat)
##      MSE       CV     v_CV 
## 18.50107 20.13806 19.35762
# The line's CV of about 20.1 is above its MSE of about 18.5.
# It predicts held-out states worse than the states it was fit to.
# Its CV is also above the leave-one-out variance of about 19.4.
# That variance is the constant model's CV, so we would choose it over the line.

# In-sample and out-of-sample fit
r_cv_usa <- cor(xy2_hat[, 'y_hat'], xy2_hat[, 'y_hat'] - loo_errors_usa)
c(R2=summary(reg_usa)$r.squared, R2_CV=1 - cv_usa_hat/v_cv_usa_hat, r_CV=r_cv_usa, R2_gen_CV=r_cv_usa^2)
##          R2       R2_CV        r_CV   R2_gen_CV 
##  0.00484035 -0.04031697 -0.46825489  0.21926264
# R2_CV is negative, about -0.04.
# Out of sample, the line predicts worse than the mean of the other states.
# Urbanization explains very little of the variation in murder arrests.
# So estimating a slope adds more noise to the predictions than it removes.
# The squared correlation of about 0.22 looks better than the R2 of about 0.005.
# That is only because squaring hides the sign of the correlation, about -0.47.
# Dropping a state with a high murder arrest rate lowers its prediction.
# So the held-out predictions tend to move against the observations.

When \(\hat{x}\) explains very little of the variation in \(\hat{y}\), estimating a slope can add more noise to the predictions than it removes, and \(\hat{R}^{2}_{\text{CV}}\) can be negative. The squared correlation can then look better than \(\hat{R}^{2}\), but only because squaring hides the sign of the correlation. With such a line, dropping an observation with a high \(\hat{y}_{i}\) lowers the prediction for that observation, so the held-out predictions tend to move against the observations.

15.3 Variability Estimates

A regression coefficient is a statistic, so it has its own sampling distribution that we want to summarize. As in Standard Errors, we summarize that spread with the standard error: the standard deviation of the statistic’s sampling distribution. There, we computed bootstrap standard errors for the mean and the median of one variable. The slope works the same way, except that we resample whole rows of the data, so each \(\hat{x}_{i}\) stays paired with its \(\hat{y}_{i}\). For the slope, the standard error describes how much \(\hat{b}_{1}\) would change if we drew another sample of \(n\) observations. We write \(\text{SE}(B_{1})\) for the theoretical standard error of the slope estimator \(B_{1}\), and \(\hat{\text{SE}}(B_{1})\) for its estimate computed from the observed sample.

The standard error is distinct from the standard deviation of \(\hat{y}\), which measures variability of observations within a single sample. From a standard error, we can build a confidence interval: a range of values consistent with our data at a chosen level. Note that values reported by your computer do not necessarily satisfy this definition. To estimate variability, we will use the data-driven methods from Resampling Intervals. (For some theoretical background, see also https://www.sagepub.com/sites/default/files/upm-binaries/21122_Chapter_21.pdf.)

Bootstrap

There are several resampling techniques. One main one is the bootstrap, which resamples with replacement for an arbitrary number of iterations. When bootstrapping a dataset with \(n\) observations, you randomly resample all \(n\) rows in your data set \(B\) times. We can then just take the percentiles of the bootstrap distribution.

Code
## MAIN EXAMPLE
# Original OLS coefficient
slope <-  coef(reg)[2]

# Number of observations
n <- nrow(xy_hat)

# Bootstrap Sampling Distribution
boot_coefs <- rep(NA, 399)
for(i in seq_along(boot_coefs)){
    b_id <- sample(n, replace=TRUE)
    xy_boot <- xy_hat[b_id, ]
    reg_b <- lm(y_hat ~ x_hat, data=xy_boot)
    slope_b <- coef(reg_b)[2]
    boot_coefs[i] <- slope_b
}

# Plot Bootstrap Sampling Distribution
# + Percentile CI
hist(boot_coefs, breaks=25,
    main=NA, border=NA,
    freq=FALSE,
    xlab=expression(hat(b)[1]))
title('Bootstrap Distribution with 95% CIs', font.main=1)
boot_ci_percentile <- quantile(boot_coefs, probs=c(.025, .975))
abline(v=boot_ci_percentile, col=rgb(0, 0, 0, .8), lwd=2)

Normal Approximation with Jackknife SE

We also use the jackknife, which loops through each row of the dataset. In each iteration of the loop, we drop that observation from the dataset and reestimate the statistic of interest. We then calculate the standard deviation of the statistic across all subsamples to get jackknife standard errors and, assuming the statistic’s sampling distribution is approximately Normal, use Normal quantiles to construct a CI.1

Code
# Jackknife Sampling Distribution for OLS Coefficient
jack_coefs <- rep(NA, n)
for(i in seq_along(jack_coefs)){
    xy_i <- xy_hat[-i, ]
    reg_i <- lm(y_hat ~ x_hat, data=xy_i)
    slope_i <- coef(reg_i)[2]
    jack_coefs[i] <- slope_i
}
jack_se <- sqrt((n - 1)/n * sum((jack_coefs - mean(jack_coefs))^2))

# Plot Estimated Sampling Distribution
# Grid spans four jackknife SEs on either side of the estimate
x <- seq(slope - 4*jack_se, slope + 4*jack_se, length.out=500)
fx <- dnorm(x, mean(jack_coefs), jack_se)
plot(x, fx, type='l', bty='n', col=rgb(0, 0, 1, .8),
    xlab=expression(hat(b)[1]))
title('Normal Approx. with 95% CIs', font.main=1)

# Normal Approx Confidence Interval
jack_ci_normal <- qnorm(c(.025, .975), slope, jack_se)
abline(v=jack_ci_normal, col=rgb(0, 0, 0, .8), lwd=2, lty=2)

# Bootstrap Quantiles
abline(v=boot_ci_percentile, col=rgb(0, 0, 0, .8), lwd=2)
legend('topright', lty=c(1, 2), lwd=2, col=rgb(0, 0, 0, .8), bty='n', cex=.75,
    title='Confidence Interval',
    legend=c('Bootstrap Percentile', 'Normal Approx.'))

In any case, a \(95\%\) confidence interval should contain the population slope in about \(95\%\) of samples. Any one sample can miss it by a fair amount.

15.4 Hypothesis Tests

We can also use resampling to test a null hypothesis, most often of “no relationship”. We are rarely interested in computing standard errors and conducting hypothesis tests for simple linear regressions in practice, but work through the ideas with two variables before moving to analyze multiple variables.

Invert a CI

One main way to conduct hypothesis tests is to examine whether a confidence interval contains a hypothesized value, as in Invert a CI. Does the slope coefficient equal \(0\)? We already have a bootstrap percentile interval for \(\hat{b}_{1}\), so we check whether it contains \(0\).

Code
# Bootstrap distribution of the slope from above, with zero added to the plot
hist(boot_coefs, breaks=25,
    main=NA, border=NA, freq=FALSE,
    xlab=expression(hat(b)[1]),
    xlim=range(c(0, boot_coefs)))
title('Bootstrap Distribution with 95% CI', font.main=1)
abline(v=boot_ci_percentile, col=rgb(0, 0, 0, .8), lwd=2)
abline(v=0, col=rgb(0, 0, 1, .8), lty=2)

Code

# Does the 95% interval contain zero?
boot_ci_percentile
##      2.5%     97.5% 
## 0.4952086 0.6248158
unname(boot_ci_percentile[1] <= 0 & boot_ci_percentile[2] >= 0)
## [1] FALSE
# The interval does not contain 0.
# So a slope of zero is not consistent with the data at the 95% level.

If the interval does not contain \(0\), a slope of zero is not consistent with the data at the \(95\%\) level.

Impose the Null

We can also compute a null distribution. We focus on the simplest: simulations that each impose the null hypothesis and re-estimate the statistic of interest. Specifically, we compute the distribution of slopes on data with randomly reshuffled outcomes (imposing the null), and compare how extreme the observed slope is. We can sample with replacement (i.e., the null bootstrap) or without (permutation), just as with the correlation statistic.

Code
## MAIN EXAMPLE
# Null Distribution for Reg Coef
null_coefs <-  rep(NA, 399)
for(b in seq_along(null_coefs)){
    # Permuted data
    xy_perm <- xy_hat
    xy_perm[, 'y_hat'] <- sample( xy_perm[, 'y_hat'], replace=FALSE) #Bootstrap: replace=T
    # Redo regression
    reg_b <- lm(y_hat ~ x_hat, data=xy_perm)
    null_coefs[b] <- coef(reg_b)[2]
}

# Null Distribution
hist(null_coefs, breaks=25,
    main=NA, border=NA, freq=FALSE,
    xlab=expression(hat(b)[1]),
    xlim=range(null_coefs, slope))
title('Null Distribution', font.main=1)
# Middle 95% of the permuted slopes
null_ci <- quantile(null_coefs, probs=c(.025, .975))
abline(v=null_ci, col=rgb(0, 0, 0, .8), lwd=2)
# Observed value
abline(v=slope, col=rgb(1, 0, 0, .8), lwd=2)

Permutations are common when testing against “no association”. From permuted data, we can calculate a \(p\)-value: the probability you would see something at least as extreme as your statistic under the null (assuming your null hypothesis was true, see \(p\)-values). We calculate the \(p\)-value directly from the null distribution. Smaller \(p\)-values suggest more evidence against the null.

Code
# Two Sided Test for P(|b1| >= |observed b1| | Null of b1=0)
F_hat <- ecdf(abs(null_coefs))
p_slope <- mean(abs(null_coefs) >= abs(slope))
p_slope
## [1] 0
plot(F_hat, xlim=range(abs(null_coefs), abs(slope)),
    xlab=expression( abs(hat(b)[1]) ),
    main=NA)
title('Null Distribution', font.main=1)
abline(v=abs(slope), col=rgb(1, 0, 0, .8), lwd=2)

Interpret the \(p\)-value computed above. It is the fraction of the null distribution at least as extreme as the observed \(\hat{b}_{1}\). Suppose the code returned \(p=0.02\): in a world where the slope is truly zero, only \(2\%\) of samples would produce a \(|\hat{b}_{1}|\) this large, so under the common \(5\%\) rule (\(0.02 < 0.05\)) we reject the null of no relationship. A larger value such as \(p=0.30\) would mean such a \(\hat{b}_{1}\) is unremarkable under the null, so we would fail to reject.

Code
# Hard decision rule at the 5% level
if(p_slope < 0.05){
    'reject the null of no relationship'
} else {
    'fail to reject the null of no relationship'
}
## [1] "reject the null of no relationship"

Suppose you have data on the urban population share and murder arrests per 100K for the \(50\) states in USArrests, the data in the figure that opens this chapter. Conduct a linear regression. Summarize the model relationship and how well it fits the data. Then construct a \(95\%\) bootstrap confidence interval for the slope coefficient. Finally, compute a permutation \(p\)-value for the null hypothesis of no relationship between urbanization and murder arrests.

Code
xy2_hat <- USArrests[, c('UrbanPop', 'Murder')]
colnames(xy2_hat) <- c('x_hat', 'y_hat') # x_hat=urban population share, y_hat=murder arrests per 100K

# Regression and goodness of fit
reg_usa <- lm(y_hat ~ x_hat, data=xy2_hat)
coef(reg_usa)
## (Intercept)       x_hat 
##  6.41594246  0.02093466
# Each extra percentage point of urban population goes with more murder arrests.
# The difference is about 0.02 arrests per 100K, on average.
summary(reg_usa)$r.squared
## [1] 0.00484035
# R-squared of about 0.005 is close to zero.
# Urbanization leaves nearly all of the variation in murder arrests unexplained.

# Bootstrap percentile CI for the slope
slope_usa <- coef(reg_usa)[2]
boot_coefs_usa <- rep(NA, 399)
for(b in seq_along(boot_coefs_usa)){
    b_id <- sample(nrow(xy2_hat), replace=TRUE)
    xy2_boot <- xy2_hat[b_id, ]
    reg_usa_b <- lm(y_hat ~ x_hat, data=xy2_boot)
    boot_coefs_usa[b] <- coef(reg_usa_b)[2]
}
quantile(boot_coefs_usa, probs=c(.025, .975))
##        2.5%       97.5% 
## -0.05757407  0.10059414
# The bootstrap interval contains zero.

# Permutation p-value, using the slope as the statistic
null_coefs_usa <- rep(NA, 399)
for(b in seq_along(null_coefs_usa)){
    xy2_perm <- xy2_hat
    xy2_perm[, 'y_hat'] <- sample(xy2_perm[, 'y_hat'], replace=FALSE)
    reg_usa_b <- lm(y_hat ~ x_hat, data=xy2_perm)
    null_coefs_usa[b] <- coef(reg_usa_b)[2]
}
# Middle 95% of the permuted slopes
quantile(null_coefs_usa, probs=c(.025, .975))
##        2.5%       97.5% 
## -0.07912115  0.07818051
# The observed slope is also inside the middle 95% of permuted slopes.
# That range runs from about -0.08 to 0.08.
mean(abs(null_coefs_usa) >= abs(slope_usa))
## [1] 0.6441103
# The p-value is large, so we fail to reject the null of no relationship.

Caveats

We could also use hard decision rules, such as “\(p < 0.05\) is a statistically significant finding”. However, just like confidence intervals, those hard decision rules can be sensitive to somewhat arbitrary choices (Why a bootstrap percentile interval instead of a jackknife Normal one? Why test at the \(95\%\) level rather than \(90\%\)?)

“No association” is the most common null hypothesis in practice, but we can also use bootstrapping to test against other specific hypothesized values of \(\beta_{1}\), the population slope. The simplest way is to check whether \(\beta_{1}\) lies inside the bootstrap interval for \(\hat{b}_{1}\). To impose the null instead, you recenter the bootstrap distribution of \(\hat{b}_{1}\) around the hypothetical value, as we did for the mean in Impose the Null.

Although two-sided hypothesis tests are most common, you can also test one-sided hypotheses.

Standardized Slopes

Software typically reports a \(t\)-value for each coefficient, which divides the coefficient by its standard error: \(\hat{t} = \frac{\hat{b}_{1}}{\hat{\text{SE}}(B_{1})}\), where \(\hat{\text{SE}}(B_{1})\) is the estimated standard error of the coefficient. For reasons we won’t go into in this book, \(\hat{t}\) is often used instead of \(\hat{b}_{1}\) as the test statistic. As a final example, we redo the permutation test with \(\hat{t}\), using the jackknife standard error from above. Each permutation recomputes both the slope and its jackknife standard error.

Code
# Create t-stat with jackknife SE
t_hat <- slope/jack_se

# Null Distribution for t-stat
# This takes a minute or two: each permutation recomputes n jackknife slopes
null_t <-  rep(NA, 399)
for(b in seq_along(null_t)){
    # Permuted data
    xy_perm <- xy_hat
    xy_perm[, 'y_hat'] <- sample( xy_perm[, 'y_hat'], replace=FALSE)
    # Redo regression
    reg_b <- lm(y_hat ~ x_hat, data=xy_perm)
    slope_b <- coef(reg_b)[2]
    # Redo Jackknife SEs
    # Each slope is cov/var, which matches lm() and is much faster inside nested loops
    x_perm <- xy_perm[, 'x_hat']
    y_perm <- xy_perm[, 'y_hat']
    jack_coefs_b <- rep(NA, nrow(xy_perm))
    for(i in seq_along(jack_coefs_b)){
        jack_coefs_b[i] <- cov(x_perm[-i], y_perm[-i])/var(x_perm[-i])
    }
    n_b <- length(jack_coefs_b)
    jack_se_b <- sqrt((n_b - 1)/n_b * sum((jack_coefs_b - mean(jack_coefs_b))^2))
    # Redo t with Jackknife SE
    null_t[b] <- slope_b/jack_se_b
}

# Null Distribution
hist(null_t, breaks=25,
    main=NA, border=NA, freq=FALSE,
    xlab=expression(hat(t)),
    xlim=range(null_t, t_hat))
title('Null Distribution of t', font.main=1)
abline(v=quantile(null_t, probs=c(.025, .975)), col=rgb(0, 0, 0, .8), lwd=2)
abline(v=t_hat, col=rgb(1, 0, 0, .8), lwd=2)

Code

# Two Sided Test for P(|t| >= |observed t| | Null of t=0), next to the slope version
p_t <- mean(abs(null_t) >= abs(t_hat))
c(slope=p_slope, t=p_t)
## slope     t 
##     0     0
# Both p-values are essentially zero.
# So both tests reject the null of no relationship between schooling and wages.

Precision and Fit

A slope can be estimated precisely, with a narrow interval and a small \(p\)-value. But \(\hat{R}^{2}\) can still be low, so the line leaves most of the variation in \(\hat{y}\) unexplained. Real data are often noisy, so a precise slope with a low \(\hat{R}^{2}\) is common. Outcomes differ across observations for many reasons other than \(\hat{x}\). The two results do not conflict, because they answer different questions.

  • Estimated association, given the model: the slope, its standard error, and the \(p\)-value take the line as given and ask how well we have estimated it.
  • How much to trust the model: goodness of fit asks how well the model accounts for the data. Unlike the standard error, it does not keep shrinking as \(n\) grows, and it is useful as a comparison between models.

More observations pin down the line more precisely, but they do not move the outcomes closer to the line. With enough observations, even a weak association has a small \(p\)-value, so a small \(p\)-value does not mean a good fit. A low \(\hat{R}^{2}\) is also not a reason to dismiss a precise slope.

To compare models, we can ask which one predicts held-out observations better. When the line has a lower \(\hat{\text{CV}}\) than the simple constant regression, we would choose the line to predict the outcome, even if it leaves most of the variation unexplained. When the constant model has the lower \(\hat{\text{CV}}\), we would choose it over the line. The sign of \(\hat{R}^{2}_{\text{CV}}\) reports the same comparison: it is positive when the line has the lower \(\hat{\text{CV}}\) and negative when the constant model does. The code below collects these results.

Code
# Wage data: the slope's 95% bootstrap interval and p-value, then R-squared
boot_ci_percentile
##      2.5%     97.5% 
## 0.4952086 0.6248158
p_slope
## [1] 0
# The slope is estimated precisely.
# Its 95% bootstrap interval runs from about 0.50 to 0.62.
# Its p-value is essentially zero.
R2
## [1] 0.07980201
# R-squared is only about 0.08.
# So the line leaves most of the variation in wages unexplained.
# Wages differ across workers for many reasons other than schooling.

# Wage data: CV of the line and of the constant model, then R2_CV
c(line=cv_hat, constant=v_cv_hat, R2_CV=r2_cv)
##        line    constant       R2_CV 
##  9.84385670 10.69082123  0.07922352
# The line has the lower CV, about 9.84 against 10.69, and R2_CV is positive.
# So we would choose the line to predict wages.
# That holds even though it leaves most of the variation unexplained.

Suppose the \(p\)-value is essentially zero but \(\hat{R}^{2}\) is low. Does \(\hat{x}\) matter?

The \(p\)-value asks whether the slope can be told apart from zero. The slope itself says how large the association is. The code below prints the \(p\)-value and compares the size of the slope with the mean of \(\hat{y}\).

Code
# The p-value, from the permutation test above
p_slope
## [1] 0
# With 3294 workers, the slope is estimated precisely.
# A slope of zero is not consistent with the data.

# Four more years of schooling, then the mean wage for scale
4*slope
##    x_hat 
## 2.228647
mean(xy_hat[, 'y_hat'])
## [1] 5.757585
# Four more years go with about 2.23 dollars more per hour, on average.
# The mean wage is about 5.76 dollars.

\(\hat{R}^{2}\) asks how much of the variation in \(\hat{y}\) the line accounts for. So \(\hat{x}\) can matter for the average \(\hat{y}\), while observations with the same \(\hat{x}\) still have very different \(\hat{y}\). The code below compares the spread of the residuals with the spread of \(\hat{y}\).

Code
# Standard deviation of the residuals and of wages
c(residuals=sqrt(mse_hat), wages=sqrt(v_hat))
## residuals     wages 
##  3.135554  3.268690
# Residuals have a standard deviation of about 3.14 dollars, wages about 3.27.
# The line accounts for about 8% of the variation in wages.
# So schooling matters for the average wage.
# But workers with the same schooling still earn very different wages.

15.5 Regression and Causation

The same caveats about “correlation is not causation” from Association is not Causation extend to regression. You may be tempted to use the term “the effect”, but that interpretation of a regression coefficient assumes the linear model is true. If you fit a line to a non-linear relationship, then you will still get back a coefficient even though there is no singular the effect: the true relationship is non-linear! Also consider a classic example, Anscombe’s Quartet (Anscombe 1973), which shows four very different datasets that give the same linear regression coefficient. Notice that you understand the problem because we used scatterplots to visualize the data.2

Code
# Anscombe's Quartet 

par(mfrow=c(2, 2))
for(i in 1:4){
    x_hat <- anscombe[, paste0('x', i)]
    y_hat <- anscombe[, paste0('y', i)]
    plot(x_hat, y_hat, ylim=c(4, 13), xlim=c(4, 20),
        pch=16, col=grey(0, .6), main=NA,
        xlab=paste0('x', i), ylab=paste0('y', i))
    reg <- lm(y_hat ~ x_hat)
    # Slope, its p-value, and R-squared for the panel title
    b <- round(coef(reg)[2], 2)
    p <- round(summary(reg)$coefficients[2, 4], 4)
    r2 <- round(summary(reg)$r.squared, 2)
    abline(reg, col=rgb(1, 0, 0, .8), lwd=2)
    title(bquote(paste('Slope=', .(b), ', p=', .(p), ', ', R^2, '=', .(r2))), font.main=1)
}

Code

## For an even better example, see `Datasaurus Dozen'
#browseURL(
#'https://bookdown.org/paul/applied-data-visualization/
#why-look-the-datasaurus-dozen.html')

It is true that linear regression “is the best linear predictor of the nonlinear regression function if the mean-squared error is used as the loss function” (Cameron and Trivedi 2005, 92). But this is not a carte-blanche justification for OLS, as the best of the bad predictors is still a bad predictor. For many economic applications, it is more helpful to think and speak of “dose response curves” instead of “the effect”.

While adding interaction terms or squared terms allows one to incorporate heterogeneity and non-linearity, they change several features of the model (most of which are not intended). Often, there are nonsensical predicted values. For example, if most of your age data are between \([23,65]\), a quadratic term can imply silly things for people aged \(10\) or \(90\).

Nonetheless, linear regression provides an important piece of quantitative information that is understood by many. All models are an approximation, and sometimes only unimportant nuances are missing from a vanilla linear model. Other times, that model can be seriously misleading. (This is especially true if you are making policy recommendations based on a universal “the effect”.) As an exploratory tool, linear regression is a good guess but one whose point estimates should not be taken too seriously (in which case, the standard errors are also much less important). Before trying to find a regression specification that makes sense for the entire dataset, explore local relationships.

15.6 Exercises

  1. Comment the script you wrote for this chapter, then restart R and check that it runs from a clean session, then check the script with AI as explained in Working with AI. Write three sentences from memory on the main statistical idea of this chapter, and ask the assistant what is wrong, vague, or missing. Finish with your own questions about whatever you found hardest.

  2. Explain in words what the \(R^2\) statistic measures. A researcher runs a regression and finds \(\hat{R}^2 = 0.95\). Does this mean the model is correctly specified or that \(X_{i}\) causes \(Y_{i}\)? Why or why not? In your answer, explain why confounding and selection bias need not lower \(\hat{R}^{2}\).

  3. Suppose you have \(n=4\) observations: \(\{(1, 3),\ (2, 5),\ (3, 6),\ (4, 10)\}\). Compute \(\hat{b}_{0}\) and \(\hat{b}_{1}\) by hand using \(\hat{b}_{1} = \hat{c}_{XY}/\hat{v}_{X}\) and \(\hat{b}_{0} = \hat{m}_{Y} - \hat{b}_{1} \hat{m}_{X}\). Then compute the fitted values \(\hat{y}_{i}^{\mathrm{fit}}\), the residuals \(\hat{e}_{i}\), and \(\hat{R}^2\).

  4. Using the mtcars dataset, regress mpg on wt with lm(). Report \(\hat{b}_{0}\), \(\hat{b}_{1}\), and \(\hat{R}^2\). Then write a bootstrap with \(B = 999\) iterations to construct a \(95\%\) percentile confidence interval for \(\beta_{1}\) and check whether it contains zero.

Further Reading

Recall

The OLS line for bivariate data has slope \(\hat{b}_{1}=\hat{c}_{XY}/\hat{v}_{X}\) and intercept \(\hat{b}_{0}=\hat{m}_{Y}-\hat{b}_{1}\hat{m}_{X}\). We measured its goodness of fit with \(\hat{R}^{2}\) in sample and with leave-one-out cross-validation out of sample. For the wage data, the line predicted held-out workers better than the constant model, and its \(\hat{R}^{2}_{\text{CV}}\) of about \(0.079\) was close to its \(\hat{R}^{2}\). For the murder data, the line predicted held-out states worse than the constant model, and its \(\hat{R}^{2}_{\text{CV}}\) was about \(-0.04\). We built confidence intervals and \(p\)-values by resampling the data with the bootstrap, the jackknife, and permutations. For the wage data, the slope’s \(p\)-value was essentially zero while \(\hat{R}^{2}\) was only about \(0.08\), since precision and fit answer different questions. Anscombe’s Quartet gave four very different datasets the same linear regression coefficients, and only the scatterplots showed the difference. The three-observation worked example \(\{(1,2),(2,2.5),(3,4)\}\) traced through every step by hand and produced slope \(\hat{b}_{1}=1\), intercept \(\hat{b}_{0}=5/6\), \(\hat{\text{TSS}}=13/6\), \(\hat{\text{RSS}}=1/6\), and \(\hat{R}^{2}=12/13\approx 0.92\).

Allen, David M. 1974. “The Relationship Between Variable Selection and Data Augmentation and a Method for Prediction.” Technometrics 16 (1): 125–27. https://doi.org/10.1080/00401706.1974.10489157.
Anscombe, F. J. 1973. “Graphs in Statistical Analysis.” The American Statistician 27 (1): 17–21. https://doi.org/10.1080/00031305.1973.10478966.
Cameron, A. C., and P. K. Trivedi. 2005. Microeconometrics: Methods and Applications. Cambridge University Press. https://books.google.de/books?id=Zf0gCwxC9ocC.
Hall, Peter G., and Jeffrey S. Racine. 2015. “Infinite Order Cross-Validated Local Polynomial Regression.” Journal of Econometrics 185 (2): 510–25. https://doi.org/10.1016/j.jeconom.2014.06.003.
Stone, M. 1974. “Cross-Validatory Choice and Assessment of Statistical Predictions.” Journal of the Royal Statistical Society: Series B (Methodological) 36 (2): 111–33. https://doi.org/10.1111/j.2517-6161.1974.tb00994.x.

  1. Recall that jackknife standard errors need to be rescaled because each jackknife resample is too similar to the next. For this reason, we also do not simply take the percentiles of the jackknife distribution. We can also calculate the standard deviation of the statistic across all bootstrap samples instead.↩︎

  2. The same principle holds when comparing two groups: http://www.stat.columbia.edu/~gelman/research/published/causal_quartet_second_revision.pdf↩︎