swiss_data <- read.table("swiss.txt", header = TRUE) # load data, header row present
str(swiss_data) # check structure of data
## 'data.frame': 47 obs. of 6 variables:
## $ Fertility : num 64.1 75.5 54.3 79.4 76.9 35 72.7 65 67.6 83.8 ...
## $ Agriculture : num 62 85.9 15.2 64.9 43.5 1.2 16.7 75.9 18.7 70.2 ...
## $ Examination : int 21 3 31 7 17 37 22 9 25 16 ...
## $ Education : int 12 2 20 3 15 53 13 9 7 7 ...
## $ Catholic : num 8.52 99.71 2.15 98.22 5.16 ...
## $ Infant.Mortality: num 16.5 15.1 10.8 20.2 20.6 18 18.9 17.8 19.5 23.6 ...
# fit model predicting Fertility from the other 5 variables
swiss_lm <- lm(Fertility ~ Agriculture + Examination + Education + Catholic + Infant.Mortality,
data = swiss_data)
summary(swiss_lm)
##
## Call:
## lm(formula = Fertility ~ Agriculture + Examination + Education +
## Catholic + Infant.Mortality, data = swiss_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.2743 -5.2617 0.5032 4.1198 15.3213
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 66.91518 10.70604 6.250 1.91e-07 ***
## Agriculture -0.17211 0.07030 -2.448 0.01873 *
## Examination -0.25801 0.25388 -1.016 0.31546
## Education -0.87094 0.18303 -4.758 2.43e-05 ***
## Catholic 0.10412 0.03526 2.953 0.00519 **
## Infant.Mortality 1.07705 0.38172 2.822 0.00734 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.165 on 41 degrees of freedom
## Multiple R-squared: 0.7067, Adjusted R-squared: 0.671
## F-statistic: 19.76 on 5 and 41 DF, p-value: 5.594e-10
n <- nrow(swiss_data) # number of observations
p <- length(coef(swiss_lm)) # number of parameters (including intercept)
h <- hatvalues(swiss_lm) # leverage values (diagonal of hat matrix)
plot(h, type = "h", xlab = "Observation Number", ylab = "Leverage",
main = "Leverage vs Observation Number")
points(h, pch = 16)
abline(h = 2 * p / n, col = "blue", lty = 2) # 2p/n threshold
abline(h = 3 * p / n, col = "red", lty = 2) # 3p/n threshold
legend("topleft", legend = c("2p/n", "3p/n"), col = c("blue", "red"), lty = 2)
which(h > 2 * p / n) # observations exceeding 2p/n
## La Vallee V. De Geneve
## 3 6
which(h > 3 * p / n) # observations exceeding 3p/n
## V. De Geneve
## 6
2p/n = 0.2553 and 3p/n = 0.383. Two provinces exceed 2p/n: La Vallee (h = 0.351) and V. De Geneve (h = 0.456). Only V. De Geneve exceeds 3p/n.
stud_del_res <- rstudent(swiss_lm) # studentized deleted (externally studentized) residuals
plot(stud_del_res, xlab = "Observation Number", ylab = "Studentized Deleted Residual",
main = "Studentized Deleted Residuals vs Observation Number")
abline(h = c(-2, 2), col = "red", lty = 2)
order(abs(stud_del_res), decreasing = TRUE)[1:3] # indices of 3 most extreme
## [1] 32 25 39
rownames(swiss_data)[order(abs(stud_del_res), decreasing = TRUE)[1:3]] # corresponding provinces
## [1] "Sierre" "Rive Gauche" "Porrentruy"
stud_del_res[order(abs(stud_del_res), decreasing = TRUE)[1:3]]
## Sierre Rive Gauche Porrentruy
## 2.445227 -2.394471 -2.367218
The two high-leverage points do not have large studentized residuals: La Vallee = 0.61, V. De Geneve = 0.04, both well within ±2. The three most extreme provinces by studentized deleted residual are Sierre (2.45), Rive Gauche (-2.39), and Porrentruy (-2.37).
cooksd <- cooks.distance(swiss_lm) # Cook's distance for each observation
plot(cooksd, type = "h", xlab = "Observation Number", ylab = "Cook's Distance",
main = "Cook's Distance vs Observation Number")
points(cooksd, pch = 16)
abline(h = 4 / n, col = "red", lty = 2) # common rule-of-thumb threshold
order(cooksd, decreasing = TRUE)[1:5]
## [1] 39 32 47 25 28
rownames(swiss_data)[order(cooksd, decreasing = TRUE)[1:5]]
## [1] "Porrentruy" "Sierre" "Neuchatel" "Rive Gauche" "Rive Droite"
cooksd[order(cooksd, decreasing = TRUE)[1:5]]
## Porrentruy Sierre Neuchatel Rive Gauche Rive Droite
## 0.2077343 0.1476237 0.1249500 0.1123648 0.1024707
Cook’s distance for the two high-leverage points is small: La Vallee = 0.034, V. De Geneve = 0.0002. The high studentized-residual points have larger Cook’s distance: Sierre = 0.148, Rive Gauche = 0.112, Porrentruy = 0.208 (the largest in the data set). The high-leverage points do not influence the fitted model more than other points — their Cook’s distance is low because their residuals are small. The points with extreme studentized residuals do influence the model more than most other points, with Porrentruy the single most influential observation overall.
library(faraway) # load package (install.packages("faraway") first if needed)
data(cheddar) # load the cheddar dataset into the environment
str(cheddar) # preliminary look at structure
## 'data.frame': 30 obs. of 4 variables:
## $ taste : num 12.3 20.9 39 47.9 5.6 25.9 37.3 21.9 18.1 21 ...
## $ Acetic: num 4.54 5.16 5.37 5.76 4.66 ...
## $ H2S : num 3.13 5.04 5.44 7.5 3.81 ...
## $ Lactic: num 0.86 1.53 1.57 1.81 0.99 1.09 1.29 1.78 1.29 1.58 ...
summary(cheddar) # preliminary summary statistics
## taste Acetic H2S Lactic
## Min. : 0.70 Min. :4.477 Min. : 2.996 Min. :0.860
## 1st Qu.:13.55 1st Qu.:5.237 1st Qu.: 3.978 1st Qu.:1.250
## Median :20.95 Median :5.425 Median : 5.329 Median :1.450
## Mean :24.53 Mean :5.498 Mean : 5.942 Mean :1.442
## 3rd Qu.:36.70 3rd Qu.:5.883 3rd Qu.: 7.575 3rd Qu.:1.667
## Max. :57.20 Max. :6.458 Max. :10.199 Max. :2.010
pairs(cheddar) # scatterplot matrix of all variables
cor(cheddar) # correlation matrix
## taste Acetic H2S Lactic
## taste 1.0000000 0.5495393 0.7557523 0.7042362
## Acetic 0.5495393 1.0000000 0.6179559 0.6037826
## H2S 0.7557523 0.6179559 1.0000000 0.6448123
## Lactic 0.7042362 0.6037826 0.6448123 1.0000000
Taste ranges from 0.7 to 57.2 with mean 24.5. All three predictors are positively correlated with taste: H2S has the strongest association (r = 0.756), followed by Lactic (r = 0.704) and Acetic (r = 0.550). The predictors are also correlated with each other (r between 0.60 and 0.65), suggesting some multicollinearity.
cheddar_lm <- lm(taste ~ Acetic + H2S + Lactic, data = cheddar) # fit full model
summary(cheddar_lm)
##
## Call:
## lm(formula = taste ~ Acetic + H2S + Lactic, data = cheddar)
##
## Residuals:
## Min 1Q Median 3Q Max
## -17.390 -6.612 -1.009 4.908 25.449
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -28.8768 19.7354 -1.463 0.15540
## Acetic 0.3277 4.4598 0.073 0.94198
## H2S 3.9118 1.2484 3.133 0.00425 **
## Lactic 19.6705 8.6291 2.280 0.03108 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.13 on 26 degrees of freedom
## Multiple R-squared: 0.6518, Adjusted R-squared: 0.6116
## F-statistic: 16.22 on 3 and 26 DF, p-value: 3.81e-06
The model is significant overall (F = 16.22, p < 0.001) and explains 65.2% of the variance in taste (adjusted R² = 0.612). H2S (p = 0.004) and Lactic (p = 0.031) are significant predictors; Acetic (p = 0.942) is not, likely due to overlap with H2S and Lactic.
vif(cheddar_lm) # variance inflation factors to check multicollinearity
## Acetic H2S Lactic
## 1.831589 1.992200 1.937912
VIFs are all below 2, so multicollinearity is not severe despite the pairwise correlations among predictors.
par(mfrow = c(2, 2))
plot(cheddar_lm) # standard diagnostic plots: residuals vs fitted, QQ, scale-location, leverage
par(mfrow = c(1, 1))
shapiro.test(residuals(cheddar_lm)) # test normality of residuals
##
## Shapiro-Wilk normality test
##
## data: residuals(cheddar_lm)
## W = 0.98021, p-value = 0.8312
h_cheddar <- hatvalues(cheddar_lm)
p_cheddar <- length(coef(cheddar_lm))
n_cheddar <- nrow(cheddar)
which(h_cheddar > 2 * p_cheddar / n_cheddar) # check for high-leverage points
## named integer(0)
cooks.distance(cheddar_lm)[order(cooks.distance(cheddar_lm), decreasing = TRUE)[1:3]] # largest Cook's D
## 15 12 30
## 0.1714653 0.1231599 0.1017165
Residuals vs fitted show no strong curvature, so linearity is reasonable. The QQ plot and Shapiro-Wilk test (W = 0.980, p = 0.831) support normality of residuals. No observation exceeds the 2p/n leverage threshold. Case 15 has the largest Cook’s distance (0.171), followed by case 12 (0.123) and case 30 (0.102), but none are large enough to be a serious concern. Overall the linear model is an appropriate fit for this data, with no major violations of the regression assumptions.