This tutorial uses R’s built-in mtcars data to
illustrate simple and multiple regression, diagnostics, categorical
predictors, interactions, and model comparison. Code and output appear
together when you knit this file to HTML.
To use: Open this file in RStudio and choose
Knit > Knit to HTML. Install rmarkdown
if necessary with install.packages("rmarkdown"). Analysis
uses base R; document rendering uses rmarkdown and
knitr.
data(mtcars)
cars <- mtcars
cars$transmission <- factor(cars$am, levels = c(0, 1),
labels = c("Automatic", "Manual"))
knitr::kable(head(cars[, c("mpg", "wt", "hp", "transmission")]),
caption = "Selected observations")| mpg | wt | hp | transmission | |
|---|---|---|---|---|
| Mazda RX4 | 21.0 | 2.620 | 110 | Manual |
| Mazda RX4 Wag | 21.0 | 2.875 | 110 | Manual |
| Datsun 710 | 22.8 | 2.320 | 93 | Manual |
| Hornet 4 Drive | 21.4 | 3.215 | 110 | Automatic |
| Hornet Sportabout | 18.7 | 3.440 | 175 | Automatic |
| Valiant | 18.1 | 3.460 | 105 | Automatic |
## mpg wt hp transmission
## Min. :10.40 Min. :1.513 Min. : 52.0 Automatic:19
## 1st Qu.:15.43 1st Qu.:2.581 1st Qu.: 96.5 Manual :13
## Median :19.20 Median :3.325 Median :123.0
## Mean :20.09 Mean :3.217 Mean :146.7
## 3rd Qu.:22.80 3rd Qu.:3.610 3rd Qu.:180.0
## Max. :33.90 Max. :5.424 Max. :335.0
| Variable | Meaning | Role |
|---|---|---|
| mpg | Miles per gallon | Continuous response |
| wt | Weight in thousands of pounds | Continuous predictor |
| hp | Gross horsepower | Continuous predictor |
| transmission | Automatic or manual | Categorical predictor |
Question: Is vehicle weight associated with fuel economy?
\[ mpg_i = \beta_0 + \beta_1 wt_i + \varepsilon_i. \]
plot(mpg ~ wt, data = cars, pch = 19, col = "steelblue",
xlab = "Weight (1,000 pounds)", ylab = "Fuel economy (mpg)",
main = "Vehicle Weight and Fuel Economy")## [1] -0.8676594
A downward pattern indicates a negative association. Correlation measures linear association; it does not establish causation.
##
## Call:
## lm(formula = mpg ~ wt, data = cars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4.5432 -2.3647 -0.1252 1.4096 6.8727
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 37.2851 1.8776 19.858 < 2e-16 ***
## wt -5.3445 0.5591 -9.559 1.29e-10 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.046 on 30 degrees of freedom
## Multiple R-squared: 0.7528, Adjusted R-squared: 0.7446
## F-statistic: 91.38 on 1 and 30 DF, p-value: 1.294e-10
## 2.5 % 97.5 %
## (Intercept) 33.450500 41.119753
## wt -6.486308 -4.202635
The fitted equation is
\[ \widehat{mpg} = 37.285 - 5.344 wt. \]
An additional 1,000 pounds is associated with a predicted change of -5.34 mpg. The model explains approximately 75.3% of observed mpg variation. The intercept describes a zero-weight car and has little practical meaning.
plot(mpg ~ wt, data = cars, pch = 19, col = "steelblue",
xlab = "Weight (1,000 pounds)", ylab = "Fuel economy (mpg)",
main = "Simple Regression Fit")
abline(fit_simple, col = "firebrick", lwd = 2)r <- cor(cars$wt, cars$mpg)
t_slope <- summary(fit_simple)$coefficients["wt", "t value"]
f_model <- unname(summary(fit_simple)$fstatistic["value"])
c(correlation = r, correlation_squared = r^2,
R_squared = summary(fit_simple)$r.squared)## correlation correlation_squared R_squared
## -0.8676594 0.7528328 0.7528328
## t_squared F_statistic
## 91.37533 91.37533
For simple regression with an intercept, \(R^2=r^2\) and \(F=t^2\). The overall F-test and two-sided slope t-test give the same p-value for \(H_0:\beta_1=0\).
Question: What mpg does the model predict for a car weighing 3,000 pounds?
## fit lwr upr
## 1 21.25171 20.12444 22.37899
## fit lwr upr
## 1 21.25171 14.92987 27.57355
weight_grid <- data.frame(wt = seq(min(cars$wt), max(cars$wt), length.out = 100))
mean_ci <- predict(fit_simple, newdata = weight_grid, interval = "confidence")
individual_pi <- predict(fit_simple, newdata = weight_grid, interval = "prediction")
plot(mpg ~ wt, data = cars, pch = 19, col = "gray40",
ylim = range(c(cars$mpg, individual_pi)),
xlab = "Weight (1,000 pounds)", ylab = "Fuel economy (mpg)",
main = "Confidence and Prediction Intervals")
lines(weight_grid$wt, mean_ci[, "fit"], lwd = 2)
matlines(weight_grid$wt, mean_ci[, c("lwr", "upr")],
col = "blue", lty = 2)
matlines(weight_grid$wt, individual_pi[, c("lwr", "upr")],
col = "firebrick", lty = 3)
legend("topright", c("Fitted mean", "95% confidence interval", "95% prediction interval"),
col = c("black", "blue", "firebrick"), lty = c(1, 2, 3), bty = "n")Avoid predictions far outside the observed weight range.
The standard model assumes a linear conditional mean, independent errors, constant error variance, and normal errors for conventional exact inference. Predictors need not be normally distributed.
| Plot | What to examine |
|---|---|
| Residuals vs Fitted | Curvature or systematic patterns; residuals should scatter around zero |
| Normal Q-Q | Points approximately following the reference line |
| Scale-Location | Similar residual spread across fitted values |
| Residuals vs Leverage | Observations with large residuals and high leverage |
Independence must also be assessed from the study design. Investigate influential observations rather than automatically deleting them.
## Chrysler Imperial Toyota Corolla Fiat 128 Lincoln Continental
## 0.53190563 0.25987495 0.19298580 0.07192515
## Ford Pantera L
## 0.03713611
Question: Is horsepower associated with mpg after accounting for weight?
\[ mpg_i = \beta_0 + \beta_1 wt_i + \beta_2 hp_i + \varepsilon_i. \]
##
## Call:
## lm(formula = mpg ~ wt + hp, data = cars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.941 -1.600 -0.182 1.050 5.854
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 37.22727 1.59879 23.285 < 2e-16 ***
## wt -3.87783 0.63273 -6.129 1.12e-06 ***
## hp -0.03177 0.00903 -3.519 0.00145 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.593 on 29 degrees of freedom
## Multiple R-squared: 0.8268, Adjusted R-squared: 0.8148
## F-statistic: 69.21 on 2 and 29 DF, p-value: 9.109e-12
## 2.5 % 97.5 %
## (Intercept) 33.95738245 40.49715778
## wt -5.17191604 -2.58374544
## hp -0.05024078 -0.01330512
## Analysis of Variance Table
##
## Model 1: mpg ~ wt
## Model 2: mpg ~ wt + hp
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 30 278.32
## 2 29 195.05 1 83.274 12.381 0.001451 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## fit lwr upr
## 1 21.78102 16.38174 27.18031
\[ VIF_j = \frac{1}{1-R_j^2}, \]
where \(R_j^2\) comes from regressing predictor \(j\) on the other predictors.
## [1] 0.6587479
r2_wt <- summary(lm(wt ~ hp, data = cars))$r.squared
r2_hp <- summary(lm(hp ~ wt, data = cars))$r.squared
c(wt = 1 / (1 - r2_wt), hp = 1 / (1 - r2_hp))## wt hp
## 1.766625 1.766625
Larger VIF values indicate greater inflation of coefficient variance. With two predictors, both VIF values are equal. A threshold such as 10 is a warning indicator, not an automatic deletion rule.
Question: Do transmission types differ in mean mpg after accounting for weight?
Let \(Z=0\) for automatic and \(Z=1\) for manual transmission.
\[ mpg_i = \beta_0 + \beta_1 wt_i + \beta_2 Z_i + \varepsilon_i. \]
##
## Call:
## lm(formula = mpg ~ wt + transmission, data = cars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4.5295 -2.3619 -0.1317 1.4025 6.8782
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 37.32155 3.05464 12.218 5.84e-13 ***
## wt -5.35281 0.78824 -6.791 1.87e-07 ***
## transmissionManual -0.02362 1.54565 -0.015 0.988
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.098 on 29 degrees of freedom
## Multiple R-squared: 0.7528, Adjusted R-squared: 0.7358
## F-statistic: 44.17 on 2 and 29 DF, p-value: 1.579e-09
## 2.5 % 97.5 %
## (Intercept) 31.074114 43.568989
## wt -6.964951 -3.740672
## transmissionManual -3.184815 3.137584
predict(fit_group, newdata = data.frame(
wt = c(3, 3),
transmission = factor(c("Automatic", "Manual"),
levels = levels(cars$transmission))),
interval = "confidence")## fit lwr upr
## 1 21.26312 19.35277 23.17346
## 2 21.23950 19.24208 23.23693
transmissionManual estimates manual minus automatic mean
mpg at the same weight. This model assumes equal weight slopes and a
constant adjusted group difference.
# Reusable function: draw lines within each group's observed weight range
plot_transmission <- function(model, title) {
group_colors <- c("steelblue", "firebrick")
plot(mpg ~ wt, data = cars, pch = 19,
col = group_colors[as.integer(cars$transmission)],
xlab = "Weight (1,000 pounds)", ylab = "Fuel economy (mpg)",
main = title)
for (j in seq_along(levels(cars$transmission))) {
group_name <- levels(cars$transmission)[j]
weights <- cars$wt[cars$transmission == group_name]
grid <- data.frame(
wt = seq(min(weights), max(weights), length.out = 100),
transmission = factor(rep(group_name, 100),
levels = levels(cars$transmission)))
lines(grid$wt, predict(model, newdata = grid),
col = group_colors[j], lwd = 2)
}
legend("topright", levels(cars$transmission),
col = group_colors, pch = 19, lwd = 2, bty = "n")
}
plot_transmission(fit_group, "Additive Model: Parallel Lines")\[ mpg_i = \beta_0 + \beta_1 wt_i + \beta_2 Z_i + \beta_3 wt_i Z_i + \varepsilon_i. \]
##
## Call:
## lm(formula = mpg ~ wt * transmission, data = cars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.6004 -1.5446 -0.5325 0.9012 6.0909
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 31.4161 3.0201 10.402 4.00e-11 ***
## wt -3.7859 0.7856 -4.819 4.55e-05 ***
## transmissionManual 14.8784 4.2640 3.489 0.00162 **
## wt:transmissionManual -5.2984 1.4447 -3.667 0.00102 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.591 on 28 degrees of freedom
## Multiple R-squared: 0.833, Adjusted R-squared: 0.8151
## F-statistic: 46.57 on 3 and 28 DF, p-value: 5.209e-11
## 2.5 % 97.5 %
## (Intercept) 25.229642 37.602469
## wt -5.395234 -2.176581
## transmissionManual 6.143928 23.612917
## wt:transmissionManual -8.257693 -2.339028
## Analysis of Variance Table
##
## Model 1: mpg ~ wt + transmission
## Model 2: mpg ~ wt * transmission
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 29 278.32
## 2 28 188.01 1 90.312 13.45 0.001017 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
b <- coef(fit_interaction)
c(Automatic_slope = unname(b["wt"]),
Manual_slope = unname(b["wt"] + b["wt:transmissionManual"]))## Automatic_slope Manual_slope
## -3.785908 -9.084268
The transmission main effect in this model is the difference at zero weight. Centering weight at 3 makes it the difference at 3,000 pounds:
cars$wt_centered <- cars$wt - 3
fit_centered <- lm(mpg ~ wt_centered * transmission, data = cars)
summary(fit_centered)##
## Call:
## lm(formula = mpg ~ wt_centered * transmission, data = cars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.6004 -1.5446 -0.5325 0.9012 6.0909
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 20.0583 0.8475 23.667 < 2e-16 ***
## wt_centered -3.7859 0.7856 -4.819 4.55e-05 ***
## transmissionManual -1.0167 1.3209 -0.770 0.44794
## wt_centered:transmissionManual -5.2984 1.4447 -3.667 0.00102 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.591 on 28 degrees of freedom
## Multiple R-squared: 0.833, Adjusted R-squared: 0.8151
## F-statistic: 46.57 on 3 and 28 DF, p-value: 5.209e-11
Centering changes the interpretation of the intercept and transmission main effect while preserving fitted values and group slopes.
models <- list(
Weight_only = fit_simple,
Weight_and_HP = fit_multiple,
Weight_and_transmission = fit_group,
Weight_by_transmission = fit_interaction
)
comparison <- data.frame(
Model = names(models),
R_squared = vapply(models, function(m) summary(m)$r.squared, numeric(1)),
Adjusted_R_squared = vapply(models, function(m) summary(m)$adj.r.squared, numeric(1)),
AIC = vapply(models, AIC, numeric(1)),
BIC = vapply(models, BIC, numeric(1)),
row.names = NULL
)
knitr::kable(comparison, digits = 4, caption = "Candidate model comparison")| Model | R_squared | Adjusted_R_squared | AIC | BIC |
|---|---|---|---|---|
| Weight_only | 0.7528 | 0.7446 | 166.0294 | 170.4266 |
| Weight_and_HP | 0.8268 | 0.8148 | 156.6523 | 162.5153 |
| Weight_and_transmission | 0.7528 | 0.7358 | 168.0292 | 173.8921 |
| Weight_by_transmission | 0.8330 | 0.8151 | 157.4760 | 164.8046 |
Higher adjusted \(R^2\) and lower AIC/BIC are preferred under their respective criteria. These models use the same response and observations. Partial F-tests require nested models; AIC/BIC can also compare non-nested candidates. In-sample fit does not establish prediction accuracy on new data.
mpg ~ hp and interpret its slope and 95% confidence
interval.mpg ~ wt + hp. Does adding
weight improve fit?Interpretation reminder: These observational vehicle data describe associations. Regression coefficients alone do not establish causal effects.
## R version 4.4.2 (2024-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
##
##
## locale:
## [1] LC_COLLATE=English_United States.utf8
## [2] LC_CTYPE=English_United States.utf8
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United States.utf8
##
## time zone: America/Chicago
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## loaded via a namespace (and not attached):
## [1] digest_0.6.37 R6_2.5.1 fastmap_1.2.0 xfun_0.49
## [5] cachem_1.1.0 knitr_1.49 htmltools_0.5.8.1 rmarkdown_2.29
## [9] lifecycle_1.0.5 cli_3.6.3 sass_0.4.9 jquerylib_0.1.4
## [13] compiler_4.4.2 rstudioapi_0.19.0 tools_4.4.2 evaluate_1.0.1
## [17] bslib_0.8.0 yaml_2.3.10 rlang_1.1.4 jsonlite_1.8.9