Overview

Goal: model stopping distance (dist) as a function of speed (speed) with a simple linear regression.
Data: built-in cars dataset (n = 50).
We cover: model equation, two ggplot charts, one interactive Plotly chart, diagnostics, hypothesis test, and predictions.

Model (LaTeX)

The model is \[ \mathrm{dist}_i = \beta_0 + \beta_1 \,\mathrm{speed}_i + \varepsilon_i,\quad \varepsilon_i \sim \mathcal{N}(0, \sigma^2). \] Here \(\beta_0\) is the intercept, \(\beta_1\) the slope, and \(\varepsilon_i\) is random noise.

Packages and data

library(ggplot2)
library(dplyr)
library(broom)
library(plotly)

df <- cars
str(df)
## 'data.frame':    50 obs. of  2 variables:
##  $ speed: num  4 4 7 7 8 9 10 10 10 11 ...
##  $ dist : num  2 10 4 22 16 10 18 26 34 17 ...
summary(df)
##      speed           dist       
##  Min.   : 4.0   Min.   :  2.00  
##  1st Qu.:12.0   1st Qu.: 26.00  
##  Median :15.0   Median : 36.00  
##  Mean   :15.4   Mean   : 42.98  
##  3rd Qu.:19.0   3rd Qu.: 56.00  
##  Max.   :25.0   Max.   :120.00

ggplot #1: Scatter with fitted line

ggplot(df, aes(speed, dist)) +
  geom_point(alpha = 0.85) +
  geom_smooth(method = "lm", se = TRUE) +
  labs(title = "Stopping Distance vs Speed",
       x = "Speed (mph)", y = "Stopping Distance (ft)") +
  theme_minimal(base_size = 14) +
  theme(plot.title.position = "plot",
        plot.title = element_text(face = "bold"))

##Fit the model

fit <- lm(dist ~ speed, data = df)
summary(fit)
## 
## Call:
## lm(formula = dist ~ speed, data = df)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -29.069  -9.525  -2.272   9.215  43.201 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -17.5791     6.7584  -2.601   0.0123 *  
## speed         3.9324     0.4155   9.464 1.49e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 15.38 on 48 degrees of freedom
## Multiple R-squared:  0.6511, Adjusted R-squared:  0.6438 
## F-statistic: 89.57 on 1 and 48 DF,  p-value: 1.49e-12
coef(fit)
## (Intercept)       speed 
##  -17.579095    3.932409

The slope \(\hat{\beta}_1\) is the expected increase in stopping distance (ft) for a +1 mph change in speed.

ggplot #2: Residuals vs Fitted

aug <- augment(fit)
ggplot(aug, aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept = 0, linetype = "dashed") +
  labs(title = "Residuals vs Fitted",
       x = "Fitted values (predicted dist)", y = "Residuals") +
  theme_minimal(base_size = 14)

Interactive Plot (Plotly)

p <- plot_ly(df, x = ~speed, y = ~dist, type = "scatter", mode = "markers",
             hovertemplate = "speed=%{x}<br>dist=%{y}<extra></extra>")

xseq <- seq(min(df$speed), max(df$speed), length.out = 100)
pred_line <- data.frame(speed = xseq,
                        dist = predict(fit, newdata = data.frame(speed = xseq)))

p <- p %>% add_lines(data = pred_line, x = ~speed, y = ~dist, name = "Linear fit") %>%
  layout(margin = list(l = 50, r = 20, b = 45, t = 30),
         xaxis = list(title = "Speed (mph)"),
         yaxis = list(title = "Stopping Distance (ft)"),
         autosize = TRUE, height = 420)
p

Hypothesis test for the slope (LaTeX)

We test: \[ H_0:\ \beta_1 = 0 \quad \text{vs} \quad H_1:\ \beta_1 \neq 0. \] Test statistic: \[ t = \frac{\hat{\beta}_1}{\mathrm{SE}(\hat{\beta}_1)} \sim t_{n-2}\ \ \text{under } H_0. \] The p-value in summary(fit) evaluates the evidence for a nonzero slope.

Predictions: confidence vs prediction intervals

new_data <- data.frame(speed = c(10, 15, 20, 25))
conf <- predict(fit, newdata = new_data, interval = "confidence", level = 0.95)
pred <- predict(fit, newdata = new_data, interval = "prediction", level = 0.95)

tab <- data.frame(
  speed = new_data$speed,
  fit = round(conf[,"fit"], 1),
  conf_95 = paste0("[", round(conf[,"lwr"], 1), ", ", round(conf[,"upr"], 1), "]"),
  pred_95 = paste0("[", round(pred[,"lwr"], 1), ", ", round(pred[,"upr"], 1), "]")
)
knitr::kable(tab, align = "c",
             col.names = c("Speed (mph)","Mean fit","95% CI (mean)","95% PI (new)"))
Speed (mph) Mean fit 95% CI (mean) 95% PI (new)
10 21.7 [15.5, 28] [-9.8, 53.3]
15 41.4 [37, 45.8] [10.2, 72.6]
20 61.1 [55.2, 66.9] [29.6, 92.5]
25 80.7 [71.6, 89.9] [48.5, 113]

Assumptions and quick checks

  • Linearity: scatterplot looks roughly linear.
  • Constant variance: residuals vs fitted should not show a funnel shape.
  • Normality of errors: QQ-plot below.
qqnorm(aug$.resid); qqline(aug$.resid)

Limitations and extensions

  • The linear model is a simplification; braking also depends on tires, road, ABS, etc.
  • If residual patterns persist, try transformations (e.g., log(dist)) or a nonlinear model.
  • With more variables, consider multiple regression.

Appendix: full code

library(ggplot2); library(dplyr); library(broom); library(plotly)

df <- cars
fit <- lm(dist ~ speed, data = df)
aug <- augment(fit)

ggplot(df, aes(speed, dist)) +
  geom_point(alpha = 0.85) +
  geom_smooth(method="lm", se=TRUE) +
  theme_minimal(base_size = 14)

ggplot(aug, aes(.fitted, .resid)) +
  geom_point() +
  geom_hline(yintercept=0, linetype="dashed") +
  theme_minimal(base_size = 14)

p <- plot_ly(df, x=~speed, y=~dist, type="scatter", mode="markers")
xseq <- seq(min(df$speed), max(df$speed), length.out=100)
pred_line <- data.frame(speed=xseq, dist=predict(fit, newdata=data.frame(speed=xseq)))
p %>% add_lines(data=pred_line, x=~speed, y=~dist) %>%
  layout(margin=list(l=50,r=20,b=45,t=30), height=420)