5 Lab: Cross-Validation and the Bootstrap
This is a modified version of the Lab: Cross-Validation and the Bootstrap section of chapter 5 from Introduction to Statistical Learning with Application in R. This version uses tidyverse techniques and methods that will allow for scalability and a more efficient data analytic pipeline.
We will need the packages loaded below.
Whenever performing analyses or processes that include randomization or resampling it is considered best practice to set the seed of your software’s random number generator. This is done in R using set.seed()
. This ensure the analyses and procedures being performed are reproducible. For instance, readers following along in the lab will be able to produce the precisely the same results as those produced in the lab. Provided they run all code in the lab in sequence from start to finish — cannot run code chucks out of order. Setting the seed should occur towards the top of an analytic script, say directly after loading necessary packages.
5.1 Validation Set Approach
We explore the use of the validation set approach in order to estimate the test error rates that result from fitting various linear models on the Auto
dataset. This dataset is from the ISLR
library. Take a moment and inspect the codebook — ?ISLR::Auto
. We will read in the data from the Auto.csv
file and process do a little processing.
We begin by using the sample_frac()
function to split the set of observations into two halves, by selecting a random subset of 196 observations out of the original 392 observations. We refer to these observations as the training set.
auto_validation <- tibble(train = auto_dat %>% sample_frac(0.5) %>% list(),
test = auto_dat %>% setdiff(train) %>% list())
Let’s keep it relatively simple and fit a simple linear regression using horsepower
to predict mpg
and polynomial regressions of up to degree 5 of horsepower
to predict mpg
.
# Setup tibble with model names and formulas
model_def <- tibble(degree = 1:10,
fmla = str_c("mpg ~ poly(horsepower, ", degree, ")"))
# Combine validation setup with model fitting info
auto_validation <- auto_validation %>%
crossing(model_def)
# Add model fits and assessment
auto_validation <- auto_validation %>%
mutate(model_fit = map2(fmla, train, lm),
test_mse = map2_dbl(model_fit, test, mse))
## # A tibble: 10 x 2
## degree test_mse
## <int> <dbl>
## 1 2 18.6
## 2 4 18.9
## 3 3 18.9
## 4 7 19.0
## 5 6 19.1
## 6 8 19.3
## 7 5 19.3
## 8 9 21.4
## 9 1 22.1
## 10 10 22.6
5.2 Leave-One-Out-Cross Validation
auto_loocv <- auto_dat %>%
crossv_loo(id = "fold") %>%
mutate(
train = map(train, as_tibble),
test = map(test, as_tibble)
)
auto_loocv <- auto_loocv %>%
crossing(model_def) %>%
mutate(
model_fit = map2(fmla, train, lm),
fold_mse = map2_dbl(model_fit, test, mse)
)
auto_loocv %>%
group_by(degree) %>%
summarise(test_mse = mean(fold_mse)) %>%
arrange(test_mse)
## # A tibble: 10 x 2
## degree test_mse
## <int> <dbl>
## 1 7 18.8
## 2 8 19.0
## 3 6 19.0
## 4 5 19.0
## 5 9 19.1
## 6 2 19.2
## 7 3 19.3
## 8 4 19.4
## 9 10 19.5
## 10 1 24.2
5.3 \(k\)-fold Cross-Validation
auto_10fold <- auto_dat %>%
crossv_kfold(10, id = "fold") %>%
mutate(
train = map(train, as_tibble),
test = map(test, as_tibble)
)
auto_10fold <- auto_10fold %>%
crossing(model_def) %>%
mutate(model_fit = map2(fmla, train, lm),
fold_mse = map2_dbl(model_fit, test, mse))
auto_10fold %>%
ggplot(aes(x = degree, y = fold_mse, color = fold)) +
geom_line()
5.4 The Bootstrap
We will we using the Portfolio
dataset from ISLR
— see ?ISLR::Portfolio
for details. We will load the dataset from the Portfolio.csv
file.
Name | Piped data |
Number of rows | 100 |
Number of columns | 2 |
_______________________ | |
Column type frequency: | |
numeric | 2 |
________________________ | |
Group variables | None |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 |
---|---|---|---|---|---|---|---|---|---|
x | 0 | 1 | -0.08 | 1.06 | -2.43 | -0.89 | -0.27 | 0.56 | 2.46 |
y | 0 | 1 | -0.10 | 1.14 | -2.73 | -0.89 | -0.23 | 0.81 | 2.57 |
5.4.1 Estimating the Accuracy of a Statistic of Interest
# Statistic of interest
alpha_fn <- function(resample_obj){
resample_obj %>%
# turn resample object into dataset
as_tibble() %>%
summarise(alpha = (var(y)-cov(x,y))/(var(x)+var(y)-2*cov(x,y))) %>%
pull(alpha)
}
# Estimate on original data
original_est <- portfolio_dat %>%
alpha_fn()
original_est
## [1] 0.5758321
# create 1000 bootstrap estimates
portfolio_boot <- portfolio_dat %>%
bootstrap(1000, id = "boot_id") %>%
mutate(alpha_boot = map_dbl(strap, alpha_fn))
Name | Piped data |
Number of rows | 1000 |
Number of columns | 1 |
_______________________ | |
Column type frequency: | |
numeric | 1 |
________________________ | |
Group variables | None |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 |
---|---|---|---|---|---|---|---|---|---|
alpha_boot | 0 | 1 | 0.58 | 0.09 | 0.32 | 0.51 | 0.57 | 0.63 | 0.86 |
# Boxplot of bootstrap estimates with estimate from original data (red X)
portfolio_boot %>%
ggplot(aes(x = "alpha_boot" , y = alpha_boot)) +
geom_boxplot() +
geom_point(aes(x = "alpha_boot", y = original_est),
shape = 4, size = 4, color = "red") +
coord_flip()
# Histogram of bootstrap estimates with estimate from original data (red dashed line)
portfolio_boot %>%
ggplot(aes(x = alpha_boot)) +
geom_histogram(bins = 25, color = "white") +
geom_vline(aes(xintercept = original_est),
color = "red", linetype = "dashed")
# Bootstrap estimate of standard error for estimator
portfolio_boot %>%
summarise(est_boot = original_est,
est_se = sd(alpha_boot))
## # A tibble: 1 x 2
## est_boot est_se
## <dbl> <dbl>
## 1 0.576 0.0881
5.4.2 Estimating the Accuracy of a Linear Regression Model
# 1000 Bootstraps, fit models, get parameter estimates
auto_boot <- auto_dat %>%
bootstrap(1000, id = "boot_id") %>%
mutate(model_fit = map(strap, lm, formula = mpg ~ horsepower),
mod_tidy = map(model_fit, broom::tidy))
# 1000 Bootstraps, fit models, get parameter estimates
auto_boot <- auto_dat %>%
modelr::bootstrap(1000, id = "boot_id") %>%
mutate(model_fit = map(strap, lm, formula = mpg ~ horsepower),
mod_tidy = map(model_fit, broom::tidy))
# Examine bootstrap coefficient estimates
auto_boot %>%
unnest(mod_tidy) %>%
group_by(term) %>%
select(term, estimate) %>%
skim_without_charts()
Name | Piped data |
Number of rows | 2000 |
Number of columns | 2 |
_______________________ | |
Column type frequency: | |
numeric | 1 |
________________________ | |
Group variables | term |
Variable type: numeric
skim_variable | term | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 |
---|---|---|---|---|---|---|---|---|---|---|
estimate | (Intercept) | 0 | 1 | 39.97 | 0.87 | 37.27 | 39.43 | 39.95 | 40.57 | 43.10 |
estimate | horsepower | 0 | 1 | -0.16 | 0.01 | -0.18 | -0.16 | -0.16 | -0.15 | -0.14 |
# Histogram of bootstrap coefficient estimates
auto_boot %>%
unnest(mod_tidy) %>%
ggplot(aes(x = estimate)) +
geom_histogram(color = "white", bins = 25) +
facet_wrap(. ~ term, scale = "free_x")
# Boxplot of bootstrap coefficient estimates
auto_boot %>%
unnest(mod_tidy) %>%
ggplot(aes(x = "", y = estimate)) +
geom_boxplot() +
facet_wrap(. ~ term, scale = "free_y")
# Estimates using original data (including SE)
auto_dat %>%
lm(mpg ~ horsepower, data = .) %>%
broom::tidy() %>%
select(-statistic, -p.value)
## # A tibble: 2 x 3
## term estimate std.error
## <chr> <dbl> <dbl>
## 1 (Intercept) 39.9 0.717
## 2 horsepower -0.158 0.00645
# Estimates se using bootstrap
auto_boot %>%
unnest(mod_tidy) %>%
group_by(term) %>%
summarise(est_se = sd(estimate))
## # A tibble: 2 x 2
## term est_se
## <chr> <dbl>
## 1 (Intercept) 0.866
## 2 horsepower 0.00743