# load libraries
library(socviz)
library(juanr)
library(gapminder)
library(patchwork)
library(broom)
library(paletteer)
library(moderndive)Extra Materials: Sampling, Bootstrapping, and Prediction
Sampling in R
Now imagine that instead of having data on every American, we only have a sample of 10 Americans. Why do we have a sample? Because interviewing every American is prohibitively costly. The same way a poll works: a sample is used to estimate American public opinion.
We can pick one sample of 10 people from gss_sm using the rep_sample_n() function from moderndive:
gss_sm %>% rep_sample_n(size = 10, reps = 1)# A tibble: 10 × 33
# Groups: replicate [1]
replicate year id ballot age childs sibs degree race sex region
<int> <dbl> <dbl> <labelled> <dbl> <dbl> <lab> <fct> <fct> <fct> <fct>
1 1 2016 2409 2 66 7 11 High … Black Fema… Pacif…
2 1 2016 2610 2 47 4 1 High … White Fema… W. So…
3 1 2016 512 1 68 2 2 Bache… White Fema… New E…
4 1 2016 1097 3 39 0 1 High … White Male W. No…
5 1 2016 1917 1 47 0 2 Gradu… Black Fema… E. So…
6 1 2016 241 2 37 5 2 High … Other Male Pacif…
7 1 2016 1458 3 30 0 2 High … White Fema… Mount…
8 1 2016 1085 1 39 1 3 Lt Hi… White Male W. No…
9 1 2016 1557 3 64 0 1 High … White Fema… Middl…
10 1 2016 316 2 66 4 2 High … White Male E. No…
# ℹ 22 more variables: income16 <fct>, relig <fct>, marital <fct>, padeg <fct>,
# madeg <fct>, partyid <fct>, polviews <fct>, happy <fct>, partners <fct>,
# grass <fct>, zodiac <fct>, pres12 <labelled>, wtssall <dbl>,
# income_rc <fct>, agegrp <fct>, ageq <fct>, siblings <fct>, kids <fct>,
# religion <fct>, bigregion <fct>, partners_rc <fct>, obama <dbl>
size = size of the sample; reps = number of samples
We can then calculate the average number of kids among that sample of 10 people.
gss_sm %>% rep_sample_n(size = 10, reps = 1) %>%
summarise(avg_kids = mean(childs, na.rm = TRUE))# A tibble: 1 × 2
replicate avg_kids
<int> <dbl>
1 1 1.8
This is our sample estimate of the population parameter.
How to simulate
If we take lots of samples of size 20 \(\rightarrow\) uncertainty in a sample of 20.
gss_sm %>%
rep_sample_n(size = 20, reps = 1000)If we take lots of samples of size 100 \(\rightarrow\) uncertainty in a sample of 100:
gss_sm %>%
rep_sample_n(size = 100, reps = 1000)So to see how uncertain we should feel about gss_sm, we should take many samples that are the same size as gss_sm:
gss_sm %>%
rep_sample_n(size = 2867, reps = 1000)nrow(DATA) tells you how many observations in data object
If we have a dataset of 2,867 observations and ask R to randomly pick 2,867 observations, we’ll just get a bunch of copies of the original dataset:
gss_sm %>%
rep_sample_n(size = nrow(gss_sm), reps = 1000)Solution: sample with replacement \(\rightarrow\) once we draw an observation it goes back into the dataset, and can be sampled again!
gss_sm %>%
rep_sample_n(size = nrow(gss_sm), reps = 1000, replace = TRUE)Sampling with and without replacement
If we were sampling 4 of these delicious fruits:
fruits = c("Mango", "Pineapple", "Banana", "Blackberry")
fruits[1] "Mango" "Pineapple" "Banana" "Blackberry"
It would look like this, with and without replacement:
| Sample, no replace | Mango | Banana | Pineapple | Blackberry |
| Sample, replace | Pineapple | Pineapple | Banana | Blackberry |
Example: Polling Crime Victimization
library(juanr)
data <- crime |>
filter(pais == "Mexico")
boot_data = data |>
rep_sample_n(size = nrow(data), reps = 1000, replace = TRUE) |>
summarise(avg = mean(vic1ext, na.rm = TRUE),
standard_error = sd(vic1ext, na.rm = TRUE)) |>
mutate(low = avg - 1.96 * standard_error,
high = avg + 1.96 * standard_error)
ggplot(boot_data, aes(x = avg)) +
geom_histogram(fill = red, color = "white") +
theme_nice() +
labs(x = "Average number of Individuals who are Victims of a Crime in Mexico") +
annotate(geom = "rect",
xmin = quantile(boot_data$avg, probs = c(.025)),
xmax = quantile(boot_data$avg, probs = c(.975)),
ymin = 0, ymax = Inf,
alpha = .5, fill = yellow)
Bootstrapping Regression
We can simulate uncertainty in our estimate by bootstrapping
boot_lm_kids = gss_sm %>%
rep_sample_n(size = nrow(gss_sm), reps = 1000, replace = TRUE) %>%
nest(data = -replicate) %>%
mutate(model = data %>% map(.x = ., .f = ~lm(childs ~ age, data = .x))) %>%
mutate(results = map(model, tidy)) %>%
unnest(results) %>%
ungroup() %>%
filter(term == "age")
boot_lm_kids %>%
select(`Bootstrap` = replicate, `Coefficient` = term, Estimate = estimate) %>%
slice(1:10) %>%
knitr::kable(digits = 4)| Bootstrap | Coefficient | Estimate |
|---|---|---|
| 1 | age | 0.0345 |
| 2 | age | 0.0336 |
| 3 | age | 0.0361 |
| 4 | age | 0.0319 |
| 5 | age | 0.0379 |
| 6 | age | 0.0360 |
| 7 | age | 0.0344 |
| 8 | age | 0.0339 |
| 9 | age | 0.0356 |
| 10 | age | 0.0352 |
How much might our estimate of lm(childs ~ age) vary? Look at the standard error
# A tibble: 1 × 2
mean standard_error
<dbl> <dbl>
1 0.0346 0.00165
You might report it as .0345, +/- 2 standard errors.
We can also look at the 95% confidence interval – we are 95% “confident” that the true effect of age on the number of children a person has falls within this range. lm() will estimate the standard error of regression estimates for us using the statistical theory approach, but the results are similar to bootstrapping.
mod = lm(obama ~ sex, data = gss_sm)
tidy(mod)# A tibble: 2 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 0.576 0.0177 32.6 5.84e-182
2 sexFemale 0.0871 0.0234 3.72 2.06e- 4
We can also ask tidy for the 95% CI:
tidy(mod, conf.int = TRUE)# A tibble: 2 × 7
term estimate std.error statistic p.value conf.low conf.high
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 0.576 0.0177 32.6 5.84e-182 0.541 0.611
2 sexFemale 0.0871 0.0234 3.72 2.06e- 4 0.0412 0.133
conf.low and conf.high are the lower and upper bound of the 95% CI
Prediction (by hand)
weight_model = lm(mpg ~ wt, data = mtcars)The equation for the model is:
\(\widehat{mpg} = 37.29 - 5.34(wt)\)
We only have one explanatory variable to work with (wt), so our scenario can only rely on weight
Scenario: a car that weighs 3.25 tons \(\rightarrow\) what fuel efficiency should we expect?
To get an estimate of mpg, we simply plug in the value of “weight” we are interested in:
Estimate for weight = 3.25
\(\widehat{mpg} = 37.29 - 5.34 \times \color{red}{3.25} = 19.935\)
Prediction (in R)
First define the scenario we want a prediction for, using crossing()
weight_scenario = crossing(wt = 3.25)
weight_scenario# A tibble: 1 × 1
wt
<dbl>
1 3.25
the variables in crossing have to have the same name as the variables in the model
We can then combine our scenario with our model using augment()
augment(weight_model, newdata = weight_scenario)# A tibble: 1 × 2
wt .fitted
<dbl> <dbl>
1 3.25 19.9
augment takes the values for our scenario and plugs them into our model equation to get an estimate
We tell augment what our new scenario is using the newdata = argument
We can also look at multiple scenarios, maybe a light, medium, and heavy car:
weight_scenario = crossing(wt = c(1.5, 3, 5))
weight_scenario# A tibble: 3 × 1
wt
<dbl>
1 1.5
2 3
3 5
And then use augment to get the estimates:
augment(weight_model, newdata = weight_scenario)# A tibble: 3 × 2
wt .fitted
<dbl> <dbl>
1 1.5 29.3
2 3 21.3
3 5 10.6
Sequence of scenarios
Or we can look at a sequence of scenarios using the seq() function
Example: estimates for every weight between 2 and 6 tons, in .1 ton increments
seq_weights = crossing(wt = seq(from = 2, to = 6, by = .1))
seq_weights# A tibble: 41 × 1
wt
<dbl>
1 2
2 2.1
3 2.2
4 2.3
5 2.4
6 2.5
7 2.6
8 2.7
9 2.8
10 2.9
# ℹ 31 more rows
seq needs a starting point (from), an end point (to), and an interval (by)
And then get predictions for all these scenarios:
augment(weight_model, newdata = seq_weights)# A tibble: 41 × 2
wt .fitted
<dbl> <dbl>
1 2 26.6
2 2.1 26.1
3 2.2 25.5
4 2.3 25.0
5 2.4 24.5
6 2.5 23.9
7 2.6 23.4
8 2.7 22.9
9 2.8 22.3
10 2.9 21.8
# ℹ 31 more rows
Multiple Regression
big_model = lm(mpg ~ wt + cyl + hp + am, data = mtcars)
big_scenario = crossing(wt = 3, cyl = 4, hp = 120, am = 1)
augment(big_model, newdata = big_scenario)# A tibble: 1 × 5
wt cyl hp am .fitted
<dbl> <dbl> <dbl> <dbl> <dbl>
1 3 4 120 1 23.8
We can use seq() to see what happens when horsepower changes while everything else is left at a fixed value
varying_hp = crossing(wt = 3, cyl = 4, am = 1, hp = seq(from = 50, to = 340, by = 5))
augment(big_model, newdata = varying_hp)# A tibble: 59 × 5
wt cyl am hp .fitted
<dbl> <dbl> <dbl> <dbl> <dbl>
1 3 4 1 50 25.6
2 3 4 1 55 25.5
3 3 4 1 60 25.3
4 3 4 1 65 25.2
5 3 4 1 70 25.1
6 3 4 1 75 25.0
7 3 4 1 80 24.8
8 3 4 1 85 24.7
9 3 4 1 90 24.6
10 3 4 1 95 24.5
# ℹ 49 more rows
Visualizing predictions
We could then store our estimate, and use it for plotting
hp_pred = augment(big_model, newdata = varying_hp)
ggplot(hp_pred, aes(x = hp, y = .fitted)) + geom_point()