ANCOVA -- Notes and R Code

This post covers my notes of ANCOVA methods using R from the book “Discovering Statistics using R (2012)” by Andy Field. Most code and text are directly copied from the book. All the credit goes to him.

0a. What is ANCOVA?

ANCOVA extends ANOVA by including covariates into the analysis. Covariates mean continuous variables that are not part of the main experimental manipulation but have an influence on the dependent variable.

There are two reasons for including covariates:

0b. Assumptions

It is a part of my another post Assumptions of statistics methods.

Independence of the covariate and treatment effect

For example, anxiety and depression are closely correlated (anxious people tend to be depressed) so if you wanted to compare an anxious group of people against a non-anxious group on some task, the chances are that the anxious group would also be more depressed than the non-anxious group. You might think that by adding depression as a covariate into the analysis you can look at the ‘pure’ effect of anxiety, but you can’t.

Homogeneity of regression slopes

For example, if there’s a positive relationship between the covariate and the outcome in one group, we assume that there is a positive relationship in all of the other groups too.

If you have violated the assumption of homogeneity of regression slopes, or if the variability in regression slopes is an interesting hypothesis in itself, then you can explicitly model this variation using multilevel linear models (see Chapter 19).

1. Enter data

viagraData <- read.delim("../assets/Rdata/ViagraCovariate.dat", header = TRUE)
viagraData$dose <- factor(viagraData$dose, levels = c(1:3),
    labels = c("Placebo", "Low Dose", "High Dose"))
str(viagraData)
## 'data.frame':	30 obs. of  3 variables:
##  $ dose         : Factor w/ 3 levels "Placebo","Low Dose",..: 1 1 1 1 1 1 1 1 1 2 ...
##  $ libido       : int  3 2 5 2 2 2 7 2 4 7 ...
##  $ partnerLibido: int  4 1 5 1 2 2 7 4 5 5 ...

2. Explore your data

Self-test 1

Use R to find out the means and standard deviations of both the participant’s libido and the partner’s libido in the three groups.

library(pastecs)
options(digits=2)  # round output to 2 digits
by(viagraData$libido, viagraData$dose, stat.desc, basic=F)
## viagraData$dose: Placebo
##       median         mean      SE.mean CI.mean.0.95          var 
##         2.00         3.22         0.60         1.37         3.19 
##      std.dev     coef.var 
##         1.79         0.55 
## -------------------------------------------------------- 
## viagraData$dose: Low Dose
##       median         mean      SE.mean CI.mean.0.95          var 
##         4.50         4.88         0.52         1.22         2.12 
##      std.dev     coef.var 
##         1.46         0.30 
## -------------------------------------------------------- 
## viagraData$dose: High Dose
##       median         mean      SE.mean CI.mean.0.95          var 
##         4.00         4.85         0.59         1.28         4.47 
##      std.dev     coef.var 
##         2.12         0.44
by(viagraData$partnerLibido, viagraData$dose, stat.desc, basic=F)
## viagraData$dose: Placebo
##       median         mean      SE.mean CI.mean.0.95          var 
##         4.00         3.44         0.69         1.59         4.28 
##      std.dev     coef.var 
##         2.07         0.60 
## -------------------------------------------------------- 
## viagraData$dose: Low Dose
##       median         mean      SE.mean CI.mean.0.95          var 
##         2.50         3.12         0.61         1.44         2.98 
##      std.dev     coef.var 
##         1.73         0.55 
## -------------------------------------------------------- 
## viagraData$dose: High Dose
##       median         mean      SE.mean CI.mean.0.95          var 
##         2.00         2.00         0.45         0.99         2.67 
##      std.dev     coef.var 
##         1.63         0.82

Use basic=F to remove some desciptives that don’t interest us.

Self-test 2

Use ggplot2 to produce boxplots for the Viagra data. Try to re-create Figure 11.4.

The data are currently in wide format, but we need them in long format, so we create a new datafile called restructuredData that has the data in the correct format using the melt() function from the reshape2 package

library(reshape2)
restructuredData<-melt(viagraData, id=c("dose"), measured=c("libido", "partnerLibido"))
names(restructuredData)<-c("dose", "libido_type", "libido")

# draw boxplot
library(ggplot2)
boxplot <- ggplot(restructuredData, aes(dose, libido))
boxplot + geom_boxplot() + facet_wrap(~libido_type) + labs(x="Dose", y ="Libido")

plot of chunk self_test_2 Levels of libido seem to increase for participants as the dose of Viagra increases but the opposite is true for their partners. Also, the spread of scores is more variable for the participants than their partners.

Levene’s test

library(car)
leveneTest(viagraData$libido, viagraData$dose, center = median)
## Levene's Test for Homogeneity of Variance (center = median)
##       Df F value Pr(>F)
## group  2    0.33   0.72
##       27

The output shows that Levene’s test is very non-significant, F(2, 27) = 0.33, p = .72. This means that for these data the variances are very similar.

3. Check that the covariate and any independent variables are independent

Self-test 3

Conduct an ANOVA to test whether partner’s libido (our covariate) is independent of the dose of Viagra (our independent variable).

viagraModel.1<-aov(partnerLibido ~ dose, data = viagraData)
summary(viagraModel.1)
##             Df Sum Sq Mean Sq F value Pr(>F)
## dose         2   12.8    6.38    1.98   0.16
## Residuals   27   87.1    3.23

The main effect of dose is not significant, F(2, 27) = 1.98, p = .16, which shows that the average level of partner’s libido was roughly the same in the three Viagra groups. In other words, the means for partner’s libido are not significantly different in the placebo, low- and high-dose groups. This result means that it is appropriate to use partner’s libido as a covariate in the analysis.

4. Do the ANCOVA

If we want Type I sums of squares, then we enter covariate(s) first, and the independent variable(s) second.

A wrong way, without setting contrasts

# get type I sums of squares
viagraModel.2<-aov(libido ~ partnerLibido + dose, data = viagraData)
# get Type II or III sums of squares
Anova(viagraModel.2, type = "III")  # from car package
## Anova Table (Type III tests)
## 
## Response: libido
##               Sum Sq Df F value Pr(>F)  
## (Intercept)     12.9  1    4.26  0.049 *
## partnerLibido   15.1  1    4.96  0.035 *
## dose            25.2  2    4.14  0.027 *
## Residuals       79.0 26                 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

A correct way, with setting orthogonal contrasts

To calculate Type III sums of squares properly we must specify orthogonal contrasts.

contrasts(viagraData$dose)<-cbind(c(-2,1,1), c(0,-1,1))
viagraModel<-aov(libido ~ partnerLibido + dose, data = viagraData)
Anova(viagraModel, type="III")
## Anova Table (Type III tests)
## 
## Response: libido
##               Sum Sq Df F value  Pr(>F)    
## (Intercept)     76.1  1   25.02 3.3e-05 ***
## partnerLibido   15.1  1    4.96   0.035 *  
## dose            25.2  2    4.14   0.027 *  
## Residuals       79.0 26                    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

From the output, we can find that the p-value of Intercept after setting orthogonal contrasts becomes much smaller.

Adjusted means

Based on the output in self-test 1, we can see the low- and high-dose groups have very similar means, 4.88 and 4.85, whereas the placebo group mean is much lower at 3.22. Actually we can’t interpret these group means because they have not been adjusted for the effect of the covariate.

To get the adjusted means we need to use the effect() function in the effects package.

library(effects)
# se=TRUE: show standard errors
adjustedMeans<-effect("dose", viagraModel, se=TRUE)
summary(adjustedMeans)
## 
##  dose effect
## dose
##   Placebo  Low Dose High Dose 
##       2.9       4.7       5.2 
## 
##  Lower 95 Percent Confidence Limits
## dose
##   Placebo  Low Dose High Dose 
##       1.7       3.4       4.1 
## 
##  Upper 95 Percent Confidence Limits
## dose
##   Placebo  Low Dose High Dose 
##       4.2       6.0       6.2
adjustedMeans$se
## [1] 0.60 0.62 0.50

The output shows the adjusted means (and their confidence intervals) and also the standard errors. Unlike the means in self-test 1, these adjusted means for the low-dose and high-dose groups are fairly different. In other words, when the means are adjusted for the effect of the covariate it looks very much like as dose increases, libido increases (from 2.93 in the placebo group, to 4.71 in the low-dose group and 5.15 in the high-dose group).

Self-test 4

Run a one-way ANOVA to see whether the three groups differ in their levels of libido.

anovaModel<-aov(libido ~ dose, data = viagraData)
summary(anovaModel)
##             Df Sum Sq Mean Sq F value Pr(>F)
## dose         2   16.8    8.42    2.42   0.11
## Residuals   27   94.1    3.49

This output shows (for illustrative purposes) the ANOVA table for these data when the covariate is not included. It is clear from the significance value, which is greater than .05, that Viagra seems to have no significant effect on libido. Therefore, without taking account of the libido of the participants’ partners we would have concluded that Viagra had no significant effect on libido, yet it does.

5. Compute contrasts or post hoc tests

Interpret planned contrasts

The overall ANCOVA does not tell us which means differ, so to break down the overall effect of dose we need to look at the contrasts that we specified before we created the ANCOVA model.

summary.lm(viagraModel)
## 
## Call:
## aov(formula = libido ~ partnerLibido + dose, data = viagraData)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -3.262 -0.790 -0.323  0.881  4.570 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      3.126      0.625    5.00  3.3e-05 ***
## partnerLibido    0.416      0.187    2.23   0.0348 *  
## dose1            0.668      0.240    2.79   0.0099 ** 
## dose2            0.220      0.406    0.54   0.5928    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.7 on 26 degrees of freedom
## Multiple R-squared:  0.288,	Adjusted R-squared:  0.205 
## F-statistic:  3.5 on 3 and 26 DF,  p-value: 0.0295

The first dummy variable (dose1) compares the placebo group with the low- and high-dose groups. The associated t-statistic is significant, indicating that the placebo group was significantly different from the combined mean of the Viagra groups.

The second dummy variable (dose2) compares the low- and high-dose groups. The associated t-statistic is not significant, indicating that the high-dose group did not produce a significantly higher libido than the low-dose group.

Post hoc tests

Because we want to test differences between the adjusted means, we can use only the glht() function; the pairwise.t.test() function will not test the adjusted means. As such, we are limited to using Tukey or Dunnett’s post hoc tests.

library(multcomp)
postHocs<-glht(viagraModel, linfct = mcp(dose = "Tukey"))
summary(postHocs)
## 
## 	 Simultaneous Tests for General Linear Hypotheses
## 
## Multiple Comparisons of Means: Tukey Contrasts
## 
## 
## Fit: aov(formula = libido ~ partnerLibido + dose, data = viagraData)
## 
## Linear Hypotheses:
##                           Estimate Std. Error t value Pr(>|t|)  
## Low Dose - Placebo == 0      1.786      0.849    2.10    0.109  
## High Dose - Placebo == 0     2.225      0.803    2.77    0.027 *
## High Dose - Low Dose == 0    0.439      0.811    0.54    0.852  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## (Adjusted p values reported -- single-step method)
confint(postHocs)
## 
## 	 Simultaneous Confidence Intervals
## 
## Multiple Comparisons of Means: Tukey Contrasts
## 
## 
## Fit: aov(formula = libido ~ partnerLibido + dose, data = viagraData)
## 
## Quantile = 2.5
## 95% family-wise confidence level
##  
## 
## Linear Hypotheses:
##                           Estimate lwr    upr   
## Low Dose - Placebo == 0    1.786   -0.324  3.896
## High Dose - Placebo == 0   2.225    0.231  4.219
## High Dose - Low Dose == 0  0.439   -1.576  2.454

This output suggests significant differences between the high-dose and placebo groups (t = 2.77, p < .05). The confidence intervals also confirm this conclusion because they do not cross zero for the comparison of the high dose and placebo groups.

Plots

aov() function automatically generates some plots that we can use to test the assumptions. We can see these graphs by executing:

plot(viagraModel)

plot of chunk residual_by_plotplot of chunk residual_by_plotplot of chunk residual_by_plotplot of chunk residual_by_plot You will actually see four graphs, but the first two are the most important. The first graph (on the left of the figure) can be used for testing homogeneity of variance. The plot we have does show funnelling (the spread of scores is wider at some points than at others), which implies that the residuals might be heteroscedastic (a bad thing). The second plot (on the right) is a Q-Q plot, which tells us about the normality of residuals in the model.

It looks like the diagonal line has not washed for several weeks and the dots are running away from the smell.

Again, this is not good news for the model. These plots suggest that a robust version of ANCOVA might be in order.

6. Check for homogeneity of regression slopes

Self-test 5

Use ggplot2 to re-create Figure 11.3.

scatter <- ggplot(viagraData, aes(partnerLibido, libido, colour = dose))
scatter + geom_point(aes(shape = dose), size = 3) + 
    geom_smooth(method = "lm", aes(fill = dose), alpha = 0.1) +
    labs(x = "Partner's Libido", y = "Participant's Libido")

plot of chunk self_test_5 The output shows the scatterplots of the relationship between partnerLibido and libido in the three groups. This scatterplot showed that although this relationship was comparable in the low-dose and placebo groups, it appeared different in the high-dose group.

Use Anova() to test homogeneity of regression slopes

To test the assumption of homogeneity of regression slopes we need to run the ANCOVA again, but include the interaction between the covariate and predictor variable.

# three different ways, giving the same result
# hoRS<-aov(libido ~ partnerLibido + dose + dose:partnerLibido, data = viagraData)
# hoRS<-aov(libido ~ partnerLibido*dose, data = viagraData)
hoRS<-update(viagraModel, .~. + partnerLibido:dose)
Anova(hoRS, type="III")  # from car package
## Anova Table (Type III tests)
## 
## Response: libido
##                    Sum Sq Df F value  Pr(>F)    
## (Intercept)          53.5  1   21.92 9.3e-05 ***
## partnerLibido        17.2  1    7.03   0.014 *  
## dose                 36.6  2    7.48   0.003 ** 
## partnerLibido:dose   20.4  2    4.18   0.028 *  
## Residuals            58.6 24                    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

The main thing in which we’re interested is the interaction term, so look at the significance value of the covariate by outcome interaction (partnerLibido:dose), if this effect is significant then the assumption of homogeneity of regression slopes has been broken.

Robust test (skipped)

Based on the previous output, a robust version of ANCOVA might be in order. However, since the book uses a different dataset, I’d like to skip this part.

Effect size

There are several ways to calculate effect sizes.

rcontrast<-function(t, df)
{
  r<-sqrt(t^2/(t^2 + df))
  print(paste("r = ", r))
}

Therefore, based on the output from Interpret planned contrasts in Step 5, the t-value of each effect is 2.23, 2.79, and 0.54. The degree of freedom is 26.

t<-c(2.23, 2.79, 0.54)
df<-26
rcontrast(t, df)
## [1] "r =  0.400695004307376" "r =  0.480007503471119"
## [3] "r =  0.105313792270809"

The output shows that the effect of the covariate (.400) and the difference between the combined dose groups and the placebo (.479) both represent medium to large effect sizes (they’re both between .4 and .5). The difference between the high- and low-dose groups (.106) was a fairly small effect.

Conclusion

I only keep the R code and some very brief interpretation of the results. To see the rationale of each method or read more description of each method, it is a good idea to read the book sections. For convenience, I have added section numbers for some methods.

Thanks for reading and feel free to correct me if I made any mistake.