Repository navigation
Expand file tree
/
Copy pathTutorial4.Rmd
More file actions
1025 lines (703 loc) · 45.1 KB
/
Copy pathTutorial4.Rmd
File metadata and controls
1025 lines (703 loc) · 45.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: 'Tutorial 4: Regression in R'
author: "Ziling Ma"
date: "November 20th, 2024"
output:
html_document:
toc: true
theme: united
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
solution <- TRUE
```
# 1. Linear Regression Models
## 1.1 Concept heads up
Linear Regression method is one of the most common research methods examining the linear relationship of the dependent variable $Y$ and independent variable(s) $X$.
If you want to determine the value of a continuous response variable $Y$ given the values of $p >= 1$ independent explanatory variables $X_1, X_2, . . ., X_p$. The overarching model is defined as
$$Y = \beta_0+\beta_1X_1+\beta_2X_2+\cdots+\beta_pX_p+\epsilon,$$
where $\beta_0,\cdots,\beta_p$ are the regression coefficients and we assume indepent, normally distributed residuals $\epsilon \sim N(0,\sigma)$ around the mean.
The model to be fitted is given in terms of the mean response, conditional upon a particular realization of the set of explanatory variables
$$\hat{y} = E[Y|X_1=x_1,X_2=x_2,\cdots,X_p=x_p]=\hat{\beta}_0+\hat{\beta_1}x_1+\cdots+\hat{\beta}_px_p,$$
where $\hat{\beta}_j$ represent estimates of the regression coefficients.
For the $n$ data records, the $\hat{\beta}_j$ are found as the values that minimize the sum
$$\sum^n_{i=1}[y_i-(\hat{\beta}_0+\hat{\beta_1}x_{1,i}+\cdots+\hat{\beta}_px_{p,i})]^2,$$
where $x_{j,i}$ is the observed value of individual $i$ for explanatory variable $X_j$ and $y_i$ is their response value.
The computations involved in minimizing this squared distance are made much easier by a matrix representation of the data. When dealing with $n$ multivariate observations, you can write the linear model as
$$\textbf{Y}=\textbf{X}\cdot\boldsymbol{\beta}+\boldsymbol{\epsilon},$$
where $\boldsymbol{Y}$ and $\boldsymbol{\epsilon}$ denote $n\times 1$ column matrices such that
$$
\mathbf{Y} = \begin{bmatrix}
y_1 \\
y_2 \\
\vdots \\
y_n
\end{bmatrix}
\quad \text{and} \quad
\boldsymbol{\epsilon} = \begin{bmatrix}
\epsilon_1 \\
\epsilon_2 \\
\vdots \\
\epsilon_n
\end{bmatrix}.
$$
Here, \( y_i \) and \( \epsilon_i \) refer to the response observation and random error term for the \( i \)-th individual. The quantity \(\boldsymbol{\beta}\) is a \( (p + 1) \times 1 \) column matrix of the regression coefficients, and the observed predictor data for all individuals and explanatory variables are stored in an \( n \times (p + 1) \) matrix \(\mathbf{X}\), called the design matrix:
$$
\boldsymbol{\beta} = \begin{bmatrix}
\beta_0 \\
\beta_1 \\
\vdots \\
\beta_p
\end{bmatrix}
\quad \text{and} \quad
\mathbf{X} = \begin{bmatrix}
1 & x_{1,1} & \cdots & x_{1,p} \\
1 & x_{2,1} & \cdots & x_{2,p} \\
\vdots & \vdots & \ddots & \vdots \\
1 & x_{n,1} & \cdots & x_{n,p}
\end{bmatrix}.
$$
The minimization of least square providing the estimated regression coefficient values is then found with the following calculation:
$$
\hat{\boldsymbol{\beta}} = \begin{bmatrix}
\hat{\beta}_0 \\
\hat{\beta}_1 \\
\vdots \\
\hat{\beta}_p
\end{bmatrix}
= \left( \mathbf{X}^{\top} \mathbf{X} \right)^{-1} \mathbf{X}^{\top} \mathbf{Y}
$$
## 1.2 Simple Linear regression models
### An easy example using the equation we just reviewed
You can manually estimate the $\beta_j,(j=0,1,\cdots,p)$ in `R`. As a quick demonstration, let’s say you have two predictor variables: X1 as continuous and X2 as binary. Your target regression equation is therefore $\hat{y}=\hat{\beta}_0+\hat{\beta}_1x_1+\hat{\beta}_2x_2$.
Suppose you collect the following data, where the response data, data for $X_1$, and data for $X_2$, for $n = 8$ individuals, are given in the columns $y$, $x_1$, and $x_2$, respectively.
```{r}
demo.data <- data.frame(y=c(1.55,0.42,1.29,0.73,0.76,-1.09,1.41,-0.32),
x1=c(1.13,-0.73,0.12,0.52,-0.54,-1.15,0.20,-1.09),
x2=c(1,0,1,1,0,1,0,1))
demo.data
```
<span style="color:red">Question:</span> Now, try to compute the point estimates in $\boldsymbol{\beta}$ for the linear model using the equation we deducted earlier.
### Example using function `lm()`
Let's first get sample data.
#install.packages("car") ##if this is a new package we need to install first.
```{r}
library(car) #We have to library the package every time we use
```
We are using the dataset "Prestige" * in the car package for this guide. We can use the help command to access the codebook:
```
help("Prestige")
View(Prestige)
```
<span style="color:red">Question:</span> Can you use function `lm()` to describe the relationship between the
dependent variable(prestige) and one independent variable(education)? And how can we interpret the model?
Hint: you can use summary to view the result.
<span style="color:blue">Log transformation:</span>
It's a useful practice to transform the variable to it's log form when doing the regression. You can either transform the variable beforehand or do so in the equation.
Note that in some cases the variable contains the value of 0 so we cannot directly transform them to the log-form. We can add the value of 1 to every observation and then do the transformation.
<span style="color:red">Question:</span> Use the same data, now, use log transformaton on income, and do another linear model regarding the relationship between the response variable 'prestige' and the three independent variables 'education', 'income' and 'women'. And how can we interpret the model now?
<span style="color:blue">Predicted values and Residuals:</span>
<span style="color:red">Question</span> Using regression model 2, how can you predict the value of prestige and how do you obtain the residuals?
### Regression with Categorical variables
Categorical variables play a big part in the linear regression too. Note that we only talk about where independent variables are categorical variables in this guide. When the dependent variable is a categorical variable, you may consider the alternatives of linear regression like logit regression and multinomial regression. In this dataset, `type` is a categorical variable that describes the type of occupation.
<span style="color:red">Question</span> Construct a linear model that describe the relationship between the response variable 'prestige' and three independent variables 'education', 'log(income)' and 'type'.
### Categorical variables with interaction terms
Sometimes we are interested in the interaction terms of two variables. Here is an example of interaction term with categorical variables.
<span style="color:red">Question</span> We would like to use the same independent variables as before, but now, we want to include the interation between 'type' and 'education', 'type' and 'log(income)'. Can you consider how to construct this linear model?
# 2.Assumption Diagnostics and Regression Trouble Shooting
It's a good habit to check for the assumptions of linear regressions to see whether the model is good. We will use the raw variables (without transforming) to construct a model and then we can test for the assumptions. Note that these commands, if not specifically stated, are from the 'car' package.
## 2.1 Normality Check
It's important to check whether the normality assumption holds for the model.
Now we consider the linear model
```{r}
model1 <- lm(prestige ~ education + income + type, data = Prestige)
```
<span style="color:red">Question</span> How can we test the normality assumption?
Hint: you can use the matrial from tutorial 3: ANOVA to check the normality, both from `qqPlot()` and `shapiro.test()
## 2.2 Heteroskedasticity Check
We can use the `ncvTest()` or `leveneTest()`, etc. to test for equal variances. We can also consult with the residual plots, which we will discuss later, whether the model meets the homoscedasticity assumption.
<span style="color:red">Question</span> Please use `ncvTest()` on model1 and check the result.
## 2.3 Multicollinearity Check
We want to know whether we have too many variables that have high correlation with each other.
“When there are strong linear relationships among the predictors in a regression analysis, the precision of the estimated regression coefficients in linear models declines compared to what it would have been were the predictors uncorrelated with each other” (Fox:359)
<span style="color:red">Question</span> Please use `vif()` on model1 and check the result. A GVIF > 4 suggests collinearity.
## 2.4 Residual Plots
We can also plot the residual plots on `model1`.
<span style="color:red">Question</span> Please plot the residuals plots and comment.
## 2.5 Outliers
Outlier Identification is also significant.
We can first look at the QQplot.
```{r}
model1 <- lm(prestige ~ education + income + type, data = Prestige)
qqPlot(model1,id.n=2)
```
## 2.6 Influential/High leverage points
We can look at the Influential/ high leverage points.
```{r}
#Influence Plots
influencePlot(model1)
```
# Example 1: Factors in linear regression
## Interpreting coefficients of factor variables
In the case of quantitative predictors, we’re more or less comfortable with the interpretation of the linear model coefficient as a “slope” or a “unit increase in outcome per unit increase in the covariate”. This isn’t the right interpretation for factor variables. In particular, the notion of a slope or unit change no longer makes sense when talking about a categorical variable. E.g., what does it even mean to say “unit increase in major” when studying the effect of college major on future earnings?
To understand what the coefficients really mean, let’s go to the `birthwt` data and try regressing birthweight on mother’s race and mother’s age.
```{r}
library(tidyverse)
# Load data from MASS into a tibble
# Rename variables
# Recode categorical variables
birthwt <- as_tibble(MASS::birthwt) %>%
rename(birthwt.below.2500 = low,
mother.age = age,
mother.weight = lwt,
mother.smokes = smoke,
previous.prem.labor = ptl,
hypertension = ht,
uterine.irr = ui,
physician.visits = ftv,
birthwt.grams = bwt) %>%
mutate(race = recode_factor(race, `1` = "white", `2` = "black", `3` = "other")) %>%
mutate_at(c("mother.smokes", "hypertension", "uterine.irr", "birthwt.below.2500"),
~ recode_factor(.x, `0` = "no", `1` = "yes"))
```
```{r, include=solution}
# Fit regression model
birthwt.lm <- lm(birthwt.grams ~ race + mother.age, data = birthwt)
# Regression model summary
summary(birthwt.lm)
```
Note that there are two coefficients estimated for the race variable (`raceother` and `racewhite`). What’s happening here?
When you put a factor variable into a regression, you’re allowing a __different intercept at every level of the factor__. In the present example, you’re saying that you want to model `birthwt.grams` as
<center> __Baby’s birthweight = Intercept(based on mother’s race) + β * mother’s age__ </center>
We can rewrite this more succinctly as:
\[
y=\mathrm{Intercept}_{race}+\beta\times age
\]
Essentially you’re saying that your data is broken down into 3 racial groups, and you want to model your data as having the same slope governing how birthweight changes with mother’s age, but potentially different intercepts. Here’s a picture of what’s happening.
```{r, include=solution}
# Calculate race-specific intercepts
intercepts <- c(coef(birthwt.lm)["(Intercept)"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceblack"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceother"])
lines.df <- data.frame(intercepts = intercepts,
slopes = rep(coef(birthwt.lm)["mother.age"], 3),
race = levels(birthwt$race))
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
```
How do we interpret the 2 race coefficients? For categorical variables, the interpretation is relative to the given baseline. The baseline is just whatever level comes first (here, “white”). E.g., the estimate of `raceother` means that the estimated intercept is `r round(coef(birthwt.lm)["raceother"], 2)` higher among “other” race mothers compared to black mothers. Similarly, the estimated intercept is `r round(coef(birthwt.lm)["raceblack"], 2)` higher for black mothers than white mothers.
> Another way of putting it: Among mothers of the same age, babies of black mothers are born on average weighing `r round(coef(birthwt.lm)["raceblack"], 2)`g more than babies of white mothers.
### Why is one of the levels missing in the regression?
As you’ve already noticed, there is no coefficient called “racewhite” in the estimated model. This is because this coefficient gets absorbed into the overall (Intercept) term.
Let’s peek under the hood. Using the `model.matrix()` function on our linear model object, we can get the data matrix that underlies our regression. Here are the first 20 rows.
```{r}
head(model.matrix(birthwt.lm), 20)
```
Even though we think of the regression `birthwt.grams ~ race + mother.age` as being a regression on two variables (and an intercept), it’s actually a regression on 3 variables (and an intercept). This is because the `race` variable gets represented as two dummy variables: one for `race == other` and the other for `race == black`.
Why isn’t there a column for representing the indicator of `race == white`? This gets back to our colinearity issue. By definition, we have that
<center> __racewhite + raceother + raceblack = 1 = (Intercept)__ </center>
This is because for every observation, one and only one of the race dummy variables will equal 1. Thus the group of 4 variables {racewhite, raceother, raceblack, (Intercept)} is perfectly colinear, and we can’t include all 4 of them in the model. The default behavior in `R` is to remove the dummy corresponding to the first level of the factor (here, racewhite), and to keep the rest.
## Interaction terms
Let’s go back to the regression line plot we generated above.
```{r}
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
```
We can have similar plots by using the `geom_smooth` or `stat_smooth` commands in `ggplot`. Compare the plot above to the following.
```{r}
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
stat_smooth(method = "lm", se = FALSE, fullrange = TRUE)
```
In this case we have not only race-specific intercepts, but also __race-specific slopes__. The plot above corresponds to the model:
<center> __Baby’s birthweight = Intercept(based on mother’s race) + β(based on mother’s race) * mother’s age__ </center>
We can rewrite this more succinctly as:
\[
y=\mathrm{Intercept}_{race}+\beta_{race}\times age
\]
To specify this interaction model in `R`, we use the following syntax
```{r}
birthwt.lm.interact <- lm(birthwt.grams ~ race * mother.age, data = birthwt)
summary(birthwt.lm.interact)
```
We now have new terms appearing. Terms like `raceblack:mother.age` are deviations from the baseline slope (the coefficient of `mother.age` in the model) in the same way that terms like `raceblack` are deviations from the baseline intercept. This models says that:
> On average among white mothers, every additional year of age is associated with a `r round(coef(birthwt.lm.interact)["mother.age"], 2)`g increase in the birthweight of the baby.
To get the slope for black mothers, we need to add the interaction term to the baseline.
\[
\beta_{\text{raceblack}}=\beta_{\text{racewhite}}+\beta_{\text{raceblack:mother.age}}=\text{mother.age}+\text{raceblack:mother.age}=`r coef(birthwt.lm.interact)["mother.age"]`+(`r coef(birthwt.lm.interact)["raceblack:mother.age"]`)=
`r coef(birthwt.lm.interact)["mother.age"] + coef(birthwt.lm.interact)["raceblack:mother.age"]`
\]
This slope estimate is negative, which agrees with the regression plot above.
## Is a categorical variable in a regression statistically significant?
Last class we considered modelling birthweight as a linear function of mother’s age, allowing for a race-specific intercept for each of the three race categories. This model is fit again below.
```{r}
birthwt.lm <- lm(birthwt.grams ~ race + mother.age, data = birthwt)
```
Here’s a visualization of the model fit that we wound up with. Note that while there are 3 lines shown, this is a visualization of just one model: `birthwt.grams ~ race + mother.age`. This model produces 3 lines because the coefficients of the `race` variable result in different intercepts.
```{r}
# Calculate race-specific intercepts
intercepts <- c(coef(birthwt.lm)["(Intercept)"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceblack"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceother"])
lines.df <- data.frame(intercepts = intercepts,
slopes = rep(coef(birthwt.lm)["mother.age"], 3),
race = levels(birthwt$race))
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
```
At this stage we may be interested in assessing whether the race variable is statistically significant. i.e., Does including the `race` variable significantly improve the fit of our model, or is the simpler model `birthwt.grams ~ mother.age` just as good?
Essentially, we want to know if the race-specific intercepts capture significantly more variation in the outcome (birthweight) than the single intercept model, or if allowing for different intercepts isn’t doing much more than capturing random fluctuations in the data.
Here’s a picture of the two models we’re comparing:
```{r}
library(gridExtra)
plot.complex <- ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
# Single intercept model (birthwt.grams ~ mother.age)
p <- ggplot(birthwt, aes(x = mother.age, y = birthwt.grams))
plot.simple <- p + geom_point(aes(colour = race)) + stat_smooth(method = "lm")
grid.arrange(plot.complex, plot.simple, ncol = 2)
```
To test this hypothesis, we use the anova function (not to be confused with the aov function). This function compares two nested models, accounting for their residual sums of squares (how well they fit the data) and their complexity (how many more variables are in the larger model) to assess statistical significance.
```{r}
# Fit the simpler model with mother.age as the only predictor
birthwt.lm.simple <- lm(birthwt.grams ~ mother.age, data = birthwt)
# Compare to more complex model
anova(birthwt.lm.simple, birthwt.lm)
```
This output tells us that the `race` variable is statistically significant: It is unlikely that the improvement in fit when the add the race variable is simply due to random fluctuations in the data. Thus it is important to consider `race` when modeling how birthweight depends on the mother’s age.
## Is an interaction term significant?
Assessing significance of interaction terms operates on the same principle. We once again ask whether the improvement in model fit is worth the increased complexity of our model. For instance, consider the example we saw last class, where we allowed for a race-specific slope in addition to the race-specific intercept from before.
```{r}
birthwt.lm.interact <- lm(birthwt.grams ~ race * mother.age, data = birthwt)
summary(birthwt.lm.interact)
```
Here’s a side-by-side visual comparison of the` race + mother.age` model and the `race + mother.age + race*mother.age` interaction model.
```{r}
plot.interact <- ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
stat_smooth(method = "lm", se = FALSE, fullrange = TRUE)
grid.arrange(plot.interact, plot.complex, ncol = 2)
```
So, do the lines with different slopes fit the data significantly better than the common slope model? Let’s compare the two with the `anova()` function.
```{r}
anova(birthwt.lm, birthwt.lm.interact)
```
This $p$-value turns out to not be statistically significant. So even though the estimated slopes in the interaction model look very different, our estimates are quite variable, so we don’t have enough evidence to conclude that the interaction term (different slopes) is providing significant additional explanatory power over the simpler `race + mother.age` model.
## Is my complex model signficantly better than a simpler one?
The testing strategy above applies to any two nested models. Here’s an example where we add in a few more variables and see how it compares to the `race + mother.age` model from earlier.
```{r}
birthwt.lm.complex <- lm(birthwt.grams ~ mother.smokes + physician.visits + race + mother.age, data = birthwt)
summary(birthwt.lm.complex)
```
Let’s compare to our earlier model:
```{r}
anova(birthwt.lm, birthwt.lm.complex)
```
Highly significant! This is probably due to the fact that mother’s smoking status has a tremendously high association with birthweight.
# Example 2: Summarizing stratified regressions
## Gapminder life expectancy data
```{r}
(gapminder <- read_delim("http://www.andrew.cmu.edu/user/achoulde/94842/data/gapminder_five_year.txt",
delim = "\t")) # Load data
```
## Maximum life expectancy for each continent
Before diving into a regression, let’s look at some data summaries that use some familiar functions. Here’s how we can form a table showing the maximum life expectancy on each continent each year, and the country that attained that maximum.
```{r}
gapminder %>%
group_by(continent, year) %>%
summarize(max.life.exp = max(lifeExp),
country = country[which.max(lifeExp)])
```
## Fitting a linear model for each country
We’re now going to go through an example where we get the life expectancy in 1952 and the rate of change in life expectancy over time for each country. The rate of change will be obtained by regressing `lifeExp` on `year`.
### (1) Figure out how to extract the desired information.
Let’s start with the data for a single country.
```{r}
country.name <- "Ireland" # Pick a country
gapminder.sub <- filter(gapminder, country == country.name) # Pull data for this country
gapminder.sub
```
```{r}
# Scatterplot of life exp vs year
# with a regression line overlaid
ggplot(data = gapminder.sub, mapping = aes(x = year, y = lifeExp)) +
geom_point() +
geom_smooth(method = "lm") +
ggtitle(paste("Life expectancy in", country.name))
```
We can confirm that it’s a pretty good model for other countries as well, though not for all of them
```{r, fig.height=20, fig.width=10}
ggplot(data = gapminder, aes(x = year, y = lifeExp)) +
facet_wrap( ~ country) +
geom_point() +
geom_smooth(method = "lm")
```
Now let’s fit a regression and extract the slope.
```{r}
life.exp.lm <- lm(lifeExp ~ year, data = gapminder.sub) # Fit model
coef(life.exp.lm) # Get coefficients
coef(life.exp.lm)["year"] # The slope that we wanted
coef(lm(lifeExp ~ year, data = gapminder.sub))["year"]
```
### (2) Extract information for each country
Here we’ll extract the slope in the way we practiced above, and we’ll also extract the “origin” life expectancy: the given country’s life expectancy in 1952, the first year of our data. For the purpose of plotting we’ll also want the continent information, so we’ll capture that in this call too.
```{r}
progress.df <- gapminder %>%
group_by(country) %>%
summarize(continent = continent[1],
origin = lifeExp[year == 1952],
slope = lm(lifeExp ~ year)$coef["year"])
progress.df
```
## What can we learn from this output?
Let’s summarize our findings by creating a bar chart for the origin and slope, with the bars colored by continent. We’ll do this in ggplot.
### Plotting origins coloured by continent
```{r, fig.width=15, fig.height=10}
# Reorder country factor by origin
# Construct bar chart
progress.df %>%
mutate(country = reorder(country, origin)) %>%
ggplot(aes(x = country, y = origin, fill = continent)) +
geom_bar(stat = "identity") +
theme(axis.text.x = element_text(angle = 60, vjust = 1, hjust = 1))
```
### Plotting slopes coloured by continent
```{r, fig.width=15, fig.height=10}
# Reorder country factor by slope
# Construct bar chart
progress.df %>%
mutate(country = reorder(country, slope)) %>%
ggplot(aes(x = country, y = slope, fill = continent)) +
geom_bar(stat = "identity") +
theme(axis.text.x = element_text(angle = 60, vjust = 1, hjust = 1))
```
These are very interesting plots. What can you tell from looking at them?
### Looking at per capita GDP by year
Let’s start by looking at some plots of how GDP per capita varied by year
```{r, fig.height=20, fig.width=10}
# qplot(year, gdpPercap, facets = ~ country, data = gapminder, colour = continent) +
ggplot(data = gapminder, mapping = aes(x = year, y = gdpPercap, colour = continent)) +
geom_point() +
facet_wrap(~ country, ncol=12) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
```
What if we want to rearrange the plots by continent? This can be done by changing the order of the `country` level.
```{r, fig.height=20, fig.width=10}
# First step: reorder the countries by continent
# Produce a data frame that has the countries ordered alphabetically within continent
# Arrange sorts the data according to the variable(s) provided
country.df <- gapminder %>%
group_by(country) %>%
summarize(continent = continent[1]) %>%
arrange(continent)
gapminder.ordered <- gapminder %>%
mutate(country = factor(country, levels = country.df$country))
# Let's make sure that things are now ordered correctly...
levels(gapminder.ordered$country)
ggplot(data = gapminder.ordered, mapping = aes(x = year, y = gdpPercap, colour = continent)) +
geom_point() +
facet_wrap(~ country, ncol=12) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
stat_smooth(method = "lm")
```
In the case of quantitative predictors, we’re more or less comfortable with the interpretation of the linear model coefficient as a “slope” or a “unit increase in outcome per unit increase in the covariate”. This isn’t the right interpretation for factor variables. In particular, the notion of a slope or unit change no longer makes sense when talking about a categorical variable. E.g., what does it even mean to say “unit increase in major” when studying the effect of college major on future earnings?
To understand what the coefficients really mean, let’s go to the `birthwt` data and try regressing birthweight on mother’s race and mother’s age.
```{r}
library(tidyverse)
# Load data from MASS into a tibble
# Rename variables
# Recode categorical variables
birthwt <- as_tibble(MASS::birthwt) %>%
rename(birthwt.below.2500 = low,
mother.age = age,
mother.weight = lwt,
mother.smokes = smoke,
previous.prem.labor = ptl,
hypertension = ht,
uterine.irr = ui,
physician.visits = ftv,
birthwt.grams = bwt) %>%
mutate(race = recode_factor(race, `1` = "white", `2` = "black", `3` = "other")) %>%
mutate_at(c("mother.smokes", "hypertension", "uterine.irr", "birthwt.below.2500"),
~ recode_factor(.x, `0` = "no", `1` = "yes"))
```
```{r, include=solution}
# Fit regression model
birthwt.lm <- lm(birthwt.grams ~ race + mother.age, data = birthwt)
# Regression model summary
summary(birthwt.lm)
```
Note that there are two coefficients estimated for the race variable (`raceother` and `racewhite`). What’s happening here?
When you put a factor variable into a regression, you’re allowing a __different intercept at every level of the factor__. In the present example, you’re saying that you want to model `birthwt.grams` as
<center> __Baby’s birthweight = Intercept(based on mother’s race) + β * mother’s age__ </center>
We can rewrite this more succinctly as:
\[
y=\mathrm{Intercept}_{race}+\beta\times age
\]
Essentially you’re saying that your data is broken down into 3 racial groups, and you want to model your data as having the same slope governing how birthweight changes with mother’s age, but potentially different intercepts. Here’s a picture of what’s happening.
```{r, include=solution}
# Calculate race-specific intercepts
intercepts <- c(coef(birthwt.lm)["(Intercept)"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceblack"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceother"])
lines.df <- data.frame(intercepts = intercepts,
slopes = rep(coef(birthwt.lm)["mother.age"], 3),
race = levels(birthwt$race))
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
```
How do we interpret the 2 race coefficients? For categorical variables, the interpretation is relative to the given baseline. The baseline is just whatever level comes first (here, “white”). E.g., the estimate of `raceother` means that the estimated intercept is `r round(coef(birthwt.lm)["raceother"], 2)` higher among “other” race mothers compared to black mothers. Similarly, the estimated intercept is `r round(coef(birthwt.lm)["raceblack"], 2)` higher for black mothers than white mothers.
> Another way of putting it: Among mothers of the same age, babies of black mothers are born on average weighing `r round(coef(birthwt.lm)["raceblack"], 2)`g more than babies of white mothers.
### Why is one of the levels missing in the regression?
As you’ve already noticed, there is no coefficient called “racewhite” in the estimated model. This is because this coefficient gets absorbed into the overall (Intercept) term.
Let’s peek under the hood. Using the `model.matrix()` function on our linear model object, we can get the data matrix that underlies our regression. Here are the first 20 rows.
```{r}
head(model.matrix(birthwt.lm), 20)
```
Even though we think of the regression `birthwt.grams ~ race + mother.age` as being a regression on two variables (and an intercept), it’s actually a regression on 3 variables (and an intercept). This is because the `race` variable gets represented as two dummy variables: one for `race == other` and the other for `race == black`.
Why isn’t there a column for representing the indicator of `race == white`? This gets back to our colinearity issue. By definition, we have that
<center> __racewhite + raceother + raceblack = 1 = (Intercept)__ </center>
This is because for every observation, one and only one of the race dummy variables will equal 1. Thus the group of 4 variables {racewhite, raceother, raceblack, (Intercept)} is perfectly colinear, and we can’t include all 4 of them in the model. The default behavior in `R` is to remove the dummy corresponding to the first level of the factor (here, racewhite), and to keep the rest.
## Interaction terms
Let’s go back to the regression line plot we generated above.
```{r}
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
```
We can have similar plots by using the `geom_smooth` or `stat_smooth` commands in `ggplot`. Compare the plot above to the following.
```{r}
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
stat_smooth(method = "lm", se = FALSE, fullrange = TRUE)
```
In this case we have not only race-specific intercepts, but also __race-specific slopes__. The plot above corresponds to the model:
<center> __Baby’s birthweight = Intercept(based on mother’s race) + β(based on mother’s race) * mother’s age__ </center>
We can rewrite this more succinctly as:
\[
y=\mathrm{Intercept}_{race}+\beta_{race}\times age
\]
To specify this interaction model in `R`, we use the following syntax
```{r}
birthwt.lm.interact <- lm(birthwt.grams ~ race * mother.age, data = birthwt)
summary(birthwt.lm.interact)
```
We now have new terms appearing. Terms like `raceblack:mother.age` are deviations from the baseline slope (the coefficient of `mother.age` in the model) in the same way that terms like `raceblack` are deviations from the baseline intercept. This models says that:
> On average among white mothers, every additional year of age is associated with a `r round(coef(birthwt.lm.interact)["mother.age"], 2)`g increase in the birthweight of the baby.
To get the slope for black mothers, we need to add the interaction term to the baseline.
\[
\beta_{\text{raceblack}}=\beta_{\text{racewhite}}+\beta_{\text{raceblack:mother.age}}=\text{mother.age}+\text{raceblack:mother.age}=`r coef(birthwt.lm.interact)["mother.age"]`+(`r coef(birthwt.lm.interact)["raceblack:mother.age"]`)=
`r coef(birthwt.lm.interact)["mother.age"] + coef(birthwt.lm.interact)["raceblack:mother.age"]`
\]
This slope estimate is negative, which agrees with the regression plot above.
## Is a categorical variable in a regression statistically significant?
Last class we considered modelling birthweight as a linear function of mother’s age, allowing for a race-specific intercept for each of the three race categories. This model is fit again below.
```{r}
birthwt.lm <- lm(birthwt.grams ~ race + mother.age, data = birthwt)
```
Here’s a visualization of the model fit that we wound up with. Note that while there are 3 lines shown, this is a visualization of just one model: `birthwt.grams ~ race + mother.age`. This model produces 3 lines because the coefficients of the `race` variable result in different intercepts.
```{r}
# Calculate race-specific intercepts
intercepts <- c(coef(birthwt.lm)["(Intercept)"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceblack"],
coef(birthwt.lm)["(Intercept)"] + coef(birthwt.lm)["raceother"])
lines.df <- data.frame(intercepts = intercepts,
slopes = rep(coef(birthwt.lm)["mother.age"], 3),
race = levels(birthwt$race))
ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
```
At this stage we may be interested in assessing whether the race variable is statistically significant. i.e., Does including the `race` variable significantly improve the fit of our model, or is the simpler model `birthwt.grams ~ mother.age` just as good?
Essentially, we want to know if the race-specific intercepts capture significantly more variation in the outcome (birthweight) than the single intercept model, or if allowing for different intercepts isn’t doing much more than capturing random fluctuations in the data.
Here’s a picture of the two models we’re comparing:
```{r}
library(gridExtra)
plot.complex <- ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
geom_abline(aes(intercept = intercepts,
slope = slopes,
color = race), data = lines.df)
# Single intercept model (birthwt.grams ~ mother.age)
p <- ggplot(birthwt, aes(x = mother.age, y = birthwt.grams))
plot.simple <- p + geom_point(aes(colour = race)) + stat_smooth(method = "lm")
grid.arrange(plot.complex, plot.simple, ncol = 2)
```
To test this hypothesis, we use the anova function (not to be confused with the aov function). This function compares two nested models, accounting for their residual sums of squares (how well they fit the data) and their complexity (how many more variables are in the larger model) to assess statistical significance.
```{r}
# Fit the simpler model with mother.age as the only predictor
birthwt.lm.simple <- lm(birthwt.grams ~ mother.age, data = birthwt)
# Compare to more complex model
anova(birthwt.lm.simple, birthwt.lm)
```
This output tells us that the `race` variable is statistically significant: It is unlikely that the improvement in fit when the add the race variable is simply due to random fluctuations in the data. Thus it is important to consider `race` when modeling how birthweight depends on the mother’s age.
## Is an interaction term significant?
Assessing significance of interaction terms operates on the same principle. We once again ask whether the improvement in model fit is worth the increased complexity of our model. For instance, consider the example we saw last class, where we allowed for a race-specific slope in addition to the race-specific intercept from before.
```{r}
birthwt.lm.interact <- lm(birthwt.grams ~ race * mother.age, data = birthwt)
summary(birthwt.lm.interact)
```
Here’s a side-by-side visual comparison of the` race + mother.age` model and the `race + mother.age + race*mother.age` interaction model.
```{r}
plot.interact <- ggplot(mapping = aes(x = mother.age, y = birthwt.grams, color = race), data = birthwt) +
geom_point() +
stat_smooth(method = "lm", se = FALSE, fullrange = TRUE)
grid.arrange(plot.interact, plot.complex, ncol = 2)
```
So, do the lines with different slopes fit the data significantly better than the common slope model? Let’s compare the two with the `anova()` function.
```{r}
anova(birthwt.lm, birthwt.lm.interact)
```
This $p$-value turns out to not be statistically significant. So even though the estimated slopes in the interaction model look very different, our estimates are quite variable, so we don’t have enough evidence to conclude that the interaction term (different slopes) is providing significant additional explanatory power over the simpler `race + mother.age` model.
## Is my complex model signficantly better than a simpler one?
The testing strategy above applies to any two nested models. Here’s an example where we add in a few more variables and see how it compares to the `race + mother.age` model from earlier.
```{r}
birthwt.lm.complex <- lm(birthwt.grams ~ mother.smokes + physician.visits + race + mother.age, data = birthwt)
summary(birthwt.lm.complex)
```
Let’s compare to our earlier model:
```{r}
anova(birthwt.lm, birthwt.lm.complex)
```
Highly significant! This is probably due to the fact that mother’s smoking status has a tremendously high association with birthweight.
# Analysis of Covariance
ANCOVA in R, or Analysis of Covariance, is a powerful statistical method that combines aspects of analysis of variance (ANOVA) and regression analysis. It is employed to assess group mean differences while taking into account the influence of continuous covariates. ANCOVA is particularly valuable when researchers need to control for variables that might confound the interpretation of their results. This technique allows us to determine whether there are significant differences in the means of dependent variables among various groups, even when considering the potential impact of covariates.
The main difference between ANOVA (Analysis of Variance) and ANCOVA (Analysis of Covariance) lies in how they handle and incorporate variables, as well as their primary objectives:
<span style="color:blue">1. Purpose </span>
<span style="color:red">ANOVA: </span> Compares the means of two or more groups to determine if there are statistically significant differences between them.
<span style="color:red">ANCOVA:</span> Compares the means of two or more groups while controlling for the effects of one or more continuous covariates (i.e., control variables).
<span style="color:blue"> 2. Variables Involved </span>
<span style="color:red">ANOVA: </span>
Independent variables (factors): Categorical variables that define the groups.
Dependent variable: Continuous outcome variable being compared across groups.
<span style="color:red">ANCOVA:</span>
Independent variables (factors): Categorical variables that define the groups.
Dependent variable: Continuous outcome variable being compared across groups.
Covariates: Continuous variables that are included to control for their effects, improving the accuracy of group comparisons.
<span style="color:blue"> 3. Example Scenarios </span>
<span style="color:red">ANOVA: </span> You want to test if different teaching methods (Group A, B, C) lead to different average test scores.
<span style="color:red">ANCOVA:</span> You want to test if different teaching methods (Group A, B, C) lead to different average test scores while controlling for students' prior knowledge (e.g., pre-test scores).
## Mathematical Model
### ANOVA
The ANOVA model can be expressed as:
$$
Y_{ij} = \mu + \alpha_i + \epsilon_{ij}
$$
Where:
- \( Y_{ij} \): Dependent variable (response)
- \( \mu \): Overall mean
- \( \alpha_i \): Effect of group \(i\)
- \( \epsilon_{ij} \): Random error
### ANCOVA
The ANCOVA model adds a covariate to the equation:
$$
Y_{ij} = \mu + \alpha_i + \beta X_{ij} + \epsilon_{ij}
$$
Where:
- \( X_{ij} \): Covariate
- \( \beta \): Regression coefficient for the covariate
- Other terms (\( Y_{ij} \), \( \mu \), \( \alpha_i \), and \( \epsilon_{ij} \)) are as defined in the ANOVA model.
## Assumptions
ANCOVA makes several assumptions about the data, such as:
<span style="color:red"> Linearity between the covariate and the outcome variable at each level of the grouping variable. </span>
This can be checked by creating a grouped scatter plot of the covariate and the outcome variable.
<span style="color:red"> Homogeneity of regression slopes. </span>
The slopes of the regression lines, formed by the covariate and the outcome variable, should be the same for each group. This assumption evaluates that there is no interaction between the outcome and the covariate. The plotted regression lines by groups should be parallel.
<span style="color:red"> The outcome variable should be approximately normally distributed. </span>
This can be checked using the Shapiro-Wilk test of normality on the model residuals.
<span style="color:red"> Homoscedasticity or homogeneity of residuals variance for all groups. </span>
The residuals are assumed to have a constant variance (homoscedasticity)
<span style="color:red"> No significant outliers in the groups </span>
<span style="color:blue">Related libraries</span>
```{r}
library(tidyverse)
library(ggpubr)
library(rstatix)
library(broom)
library(datarium)
```
## One-way ANCOVA
```{r}
#make this example reproducible
set.seed(10)
#create dataset
data <- data.frame(technique = rep(c("A", "B", "C"), each = 30),
current_grade = runif(90, 65, 95),
exam = c(runif(30, 80, 95), runif(30, 70, 95), runif(30, 70, 90)))
head(data)
```
Explore the data
Before we fit the ANCOVA model, we should first explore the data to gain a better
understanding of it and verify that there aren't any extreme outliers that could
skew the results.
First, we can view a summary of each variable in the dataset:
```{r}
summary(data)
```
Next, we can use the dplyr package to easily find the mean and the standard deviation of both the current grades and the exam scores for each studying technique:
```{r}
#load dplyr
library(dplyr)
data %>%
group_by(technique) %>%
summarise(mean_grade = mean(current_grade),
sd_grade = sd(current_grade),
mean_exam = mean(exam),
sd_exam = sd(exam))
```
Please plot a boxplot showing the relationship between exam grades with technique
```{r}
# ?boxplot
```
Similarly, we can also use boxplots to visualize the distribution of current grades based on studying technique:
```{r}
# ?boxplot
```
Next, we’ll fit the ANCOVA model using exam score as the response variable, studying technique as the predictor (or “treatment”) variable, and current grade as the covariate. We start with the complete model, and consider the interaction between variables.
We’ll use the Anova() function in the car package to do so, just so we can specify that we’d like to use type III sum of squares for the model, since type I sum of squares is dependent upon the order that the predictors are entered into the model
```{r}
library(car)
# ?lm
# ?Anova
```
Now let's fit a simpler model without interaction.
```{r}
# ?lm
```
### Post Hoc Tests
Although the ANCOVA results told us that studying technique had a statistically significant effect on exam scores, we need to run post hoc tests to actually find out which studying techniques differ from each other.
To do so, we can use the glht() function within the multcomp package in R to perform Tukey’s Test for multiple comparisons:
```{r}