We use a simple hypothetical data set to illustrate some issues when covariate ranges do not match well. The data set consists of two treatment groups, a single response variable (Y) and a single covariate (X) Thie data file is here

Preliminaries

First, load the required packages (car)

Import covrange data file

covrange <- read.csv("../data/covrange.csv")
head(covrange,10)
#Make group a factor
covrange$group<-factor(covrange$group)

Fit full model with interaction to evaluate homogeneous slopes

All SS types produce same result for interaction

covrange.aov1 <- aov(y~group+x+group*x, data = covrange)
plot(covrange.aov1)

Anova(covrange.aov1, type = 3)
Anova Table (Type III tests)

Response: y
            Sum Sq Df F value    Pr(>F)    
(Intercept) 35.700  1 40.8864 8.885e-06 ***
group        5.016  1  5.7442   0.02911 *  
x            7.191  1  8.2357   0.01112 *  
group:x      2.445  1  2.8005   0.11367    
Residuals   13.970 16                      
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Not strong evidence for heterogenous slopes

Run simpler model

covrange.aov2 <- aov(y~group+x, data = covrange)
Anova(covrange.aov2, type = 3)
Anova Table (Type III tests)

Response: y
            Sum Sq Df  F value    Pr(>F)    
(Intercept) 99.269  1 102.8038 1.264e-08 ***
group        4.681  1   4.8472   0.04179 *  
x            5.157  1   5.3405   0.03364 *  
Residuals   16.416 17                       
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Get adjusted means

adjmeans <- effect("group", covrange.aov2, se = TRUE)
summary(adjmeans)

 group effect
group
        1         2 
 8.951958 10.367042 

 Lower 95 Percent Confidence Limits
group
       1        2 
8.130592 9.545677 

 Upper 95 Percent Confidence Limits
group
        1         2 
 9.773323 11.188408 

Use Huitema adjustment

Instead of using raw x values as covariates, Huitema suggests using the x-residuals from each group. This is done already in the data file (xres), but it can be done by fitting a lm with x as the response and group as the predictor, and using the calculated residuals. We’ll skip the comparison of slopes and just run the pooled slopes model and get adjusted means

covrange.aov3 <- aov(y~group+xres, data = covrange)
Anova(covrange.aov3, type = 3)
Anova Table (Type III tests)

Response: y
            Sum Sq Df  F value    Pr(>F)    
(Intercept) 707.28  1 732.4627 2.033e-15 ***
group        31.23  1  32.3367 2.677e-05 ***
xres          5.16  1   5.3405   0.03364 *  
Residuals    16.42 17                       
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
adjmeans <- effect("group", covrange.aov3, se = TRUE)
summary(adjmeans)

 group effect
group
     1      2 
 8.410 10.909 

 Lower 95 Percent Confidence Limits
group
        1         2 
 7.754387 10.253387 

 Upper 95 Percent Confidence Limits
group
        1         2 
 9.065613 11.564613 

Generate graph


df1<-covrange  # Copy dataframe to df1 so generic code can be used
aggregate(x = df1$x,        # Specify data column
     by = list(df1$group),       # Specify group indicator
     FUN = mean) 
p1<-ggplot(data = df1,aes(x = x,y = y,group = group, shape = group, color = group))+
 geom_point()+
 geom_smooth(method = "lm",se = FALSE)+
 geom_vline(xintercept = 3.872, linetype = "dashed", color = lf)+
 geom_segment(x = 2.803,y = 6,xend = 2.803,yend = 7, arrow = arrow(length = unit(3, "mm")),color = lc)+
 geom_segment(x = 4.94,y = 6,xend = 4.94,yend = 7, arrow = arrow(length = unit(3, "mm")),color = lc)+
 labs(x = "X", y = NULL)+
 scale_color_grey(end = 0.6)+
 theme_qk()+
 theme(
  legend.position = "none"
 )
p2<-ggplot(data = df1,aes(x = xres,y = y,group = group, shape = group, color = group))+
 geom_point()+
 geom_smooth(method = "lm",se = FALSE)+
 geom_vline(xintercept = 0, linetype = "dashed", color = lf)+
 labs(x = "X residual", y = NULL)+
 scale_color_grey(end = 0.6)+
 theme_qk()+
 theme(
  legend.position = "none"
 )
p3<-ggplot(data = df1,aes(x = x,y = y))+
 geom_point(color = sc)+
 geom_smooth(method = "lm",se = FALSE, color = sc,linetype = "dashed")+
 geom_smooth(method = "loess", color = lc, linetype = "solid", se = FALSE, span = 2)+
 labs(x = "X", y = NULL)+
 scale_color_grey(end = 0.6)+
 theme_qk()+
 theme(
  legend.position = "none"
 )
p1+p2+p3

LS0tCnRpdGxlOiAiUUsgQm94IDguMTUiCgpvdXRwdXQ6IAogaHRtbF9ub3RlYm9vazoKICB0aGVtZTogZmxhdGx5Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCldlIHVzZSBhIHNpbXBsZSBoeXBvdGhldGljYWwgZGF0YSBzZXQgdG8gaWxsdXN0cmF0ZSBzb21lIGlzc3VlcyB3aGVuIGNvdmFyaWF0ZSByYW5nZXMgZG8gbm90IG1hdGNoIHdlbGwuIFRoZSBkYXRhIHNldCBjb25zaXN0cyBvZiB0d28gdHJlYXRtZW50IGdyb3VwcywgYSBzaW5nbGUgcmVzcG9uc2UgdmFyaWFibGUgKFkpIGFuZCBhIHNpbmdsZSBjb3ZhcmlhdGUgKFgpIFRoaWUgZGF0YSBmaWxlIGlzIFtoZXJlXSguLi9kYXRhL2NvdnJhbmdlLmNzdikKCiMjIyBQcmVsaW1pbmFyaWVzCgpGaXJzdCwgbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMgKGNhcikKCmBgYHtyIGluY2x1ZGUgPSBGQUxTRSwgcmVzdWx0cyA9ICdoaWRlJywgZXJyb3IgPSBUUlVFfQpzb3VyY2UoIi4uL1IvbGlicmFyaWVzLlIiKSAgI1RoaXMgaXMgdGhlIGNvbW1vbiBsaWJyYXJ5CmxpYnJhcnkocmVnaGVscGVyKQpsaWJyYXJ5KGludGVyYWN0aW9ucykKbGlicmFyeShrYWRlcikKbGlicmFyeShlZmZlY3RzKQpgYGAKCkltcG9ydCBjb3ZyYW5nZSBkYXRhIGZpbGUKCmBgYHtyfQpjb3ZyYW5nZSA8LSByZWFkLmNzdigiLi4vZGF0YS9jb3ZyYW5nZS5jc3YiKQpoZWFkKGNvdnJhbmdlLDEwKQojTWFrZSBncm91cCBhIGZhY3Rvcgpjb3ZyYW5nZSRncm91cDwtZmFjdG9yKGNvdnJhbmdlJGdyb3VwKQpgYGAKCiMjIyBGaXQgZnVsbCBtb2RlbCB3aXRoIGludGVyYWN0aW9uIHRvIGV2YWx1YXRlIGhvbW9nZW5lb3VzIHNsb3BlcwoKQWxsIFNTIHR5cGVzIHByb2R1Y2Ugc2FtZSByZXN1bHQgZm9yIGludGVyYWN0aW9uCgpgYGB7ciB9CmNvdnJhbmdlLmFvdjEgPC0gYW92KHl+Z3JvdXAreCtncm91cCp4LCBkYXRhID0gY292cmFuZ2UpCnBsb3QoY292cmFuZ2UuYW92MSkKYGBgCgpgYGB7ciB9CkFub3ZhKGNvdnJhbmdlLmFvdjEsIHR5cGUgPSAzKQpgYGAKCk5vdCBzdHJvbmcgZXZpZGVuY2UgZm9yIGhldGVyb2dlbm91cyBzbG9wZXMKCiMjIyBSdW4gc2ltcGxlciBtb2RlbAoKYGBge3IgfQpjb3ZyYW5nZS5hb3YyIDwtIGFvdih5fmdyb3VwK3gsIGRhdGEgPSBjb3ZyYW5nZSkKQW5vdmEoY292cmFuZ2UuYW92MiwgdHlwZSA9IDMpCmBgYAoKR2V0IGFkanVzdGVkIG1lYW5zCgpgYGB7ciB9CmFkam1lYW5zIDwtIGVmZmVjdCgiZ3JvdXAiLCBjb3ZyYW5nZS5hb3YyLCBzZSA9IFRSVUUpCnN1bW1hcnkoYWRqbWVhbnMpCmBgYAoKIyMjIFVzZSBIdWl0ZW1hIGFkanVzdG1lbnQKCkluc3RlYWQgb2YgdXNpbmcgcmF3IHggdmFsdWVzIGFzIGNvdmFyaWF0ZXMsIEh1aXRlbWEgc3VnZ2VzdHMgdXNpbmcgdGhlIHgtcmVzaWR1YWxzIGZyb20gZWFjaCBncm91cC4gVGhpcyBpcyBkb25lIGFscmVhZHkgaW4gdGhlIGRhdGEgZmlsZSAoeHJlcyksIGJ1dCBpdCBjYW4gYmUgZG9uZSBieSBmaXR0aW5nIGEgbG0gd2l0aCB4IGFzIHRoZSByZXNwb25zZSBhbmQgZ3JvdXAgYXMgdGhlIHByZWRpY3RvciwgYW5kIHVzaW5nIHRoZSBjYWxjdWxhdGVkIHJlc2lkdWFscy4gV2UnbGwgc2tpcCB0aGUgY29tcGFyaXNvbiBvZiBzbG9wZXMgYW5kIGp1c3QgcnVuIHRoZSBwb29sZWQgc2xvcGVzIG1vZGVsIGFuZCBnZXQgYWRqdXN0ZWQgbWVhbnMKCmBgYHtyIH0KY292cmFuZ2UuYW92MyA8LSBhb3YoeX5ncm91cCt4cmVzLCBkYXRhID0gY292cmFuZ2UpCkFub3ZhKGNvdnJhbmdlLmFvdjMsIHR5cGUgPSAzKQphZGptZWFucyA8LSBlZmZlY3QoImdyb3VwIiwgY292cmFuZ2UuYW92Mywgc2UgPSBUUlVFKQpzdW1tYXJ5KGFkam1lYW5zKQpgYGAKCiMjIyBHZW5lcmF0ZSBncmFwaAoKYGBge3IgaW5jbHVkZSA9IEZBTFNFLCByZXN1bHRzID0gJ2hpZGUnfQpzb3VyY2UoIi4uL1IvYXBwZWFyYW5jZS5SIikgICNUaGlzIGlzIHRoZSBjb21tb24gbGlicmFyeSBvZiBncmFwaGljcyB0d2Vha3MsIGRlZmluaW5nIHRoZSBxayB0aGVtZQpgYGAKCmBgYHtyfQoKZGYxPC1jb3ZyYW5nZSAgIyBDb3B5IGRhdGFmcmFtZSB0byBkZjEgc28gZ2VuZXJpYyBjb2RlIGNhbiBiZSB1c2VkCmFnZ3JlZ2F0ZSh4ID0gZGYxJHgsICAgICAgICAjIFNwZWNpZnkgZGF0YSBjb2x1bW4KICAgICBieSA9IGxpc3QoZGYxJGdyb3VwKSwgICAgICAgIyBTcGVjaWZ5IGdyb3VwIGluZGljYXRvcgogICAgIEZVTiA9IG1lYW4pIApwMTwtZ2dwbG90KGRhdGEgPSBkZjEsYWVzKHggPSB4LHkgPSB5LGdyb3VwID0gZ3JvdXAsIHNoYXBlID0gZ3JvdXAsIGNvbG9yID0gZ3JvdXApKSsKIGdlb21fcG9pbnQoKSsKIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsc2UgPSBGQUxTRSkrCiBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAzLjg3MiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSBsZikrCiBnZW9tX3NlZ21lbnQoeCA9IDIuODAzLHkgPSA2LHhlbmQgPSAyLjgwMyx5ZW5kID0gNywgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDMsICJtbSIpKSxjb2xvciA9IGxjKSsKIGdlb21fc2VnbWVudCh4ID0gNC45NCx5ID0gNix4ZW5kID0gNC45NCx5ZW5kID0gNywgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDMsICJtbSIpKSxjb2xvciA9IGxjKSsKIGxhYnMoeCA9ICJYIiwgeSA9IE5VTEwpKwogc2NhbGVfY29sb3JfZ3JleShlbmQgPSAwLjYpKwogdGhlbWVfcWsoKSsKIHRoZW1lKAogIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIgogKQpwMjwtZ2dwbG90KGRhdGEgPSBkZjEsYWVzKHggPSB4cmVzLHkgPSB5LGdyb3VwID0gZ3JvdXAsIHNoYXBlID0gZ3JvdXAsIGNvbG9yID0gZ3JvdXApKSsKIGdlb21fcG9pbnQoKSsKIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsc2UgPSBGQUxTRSkrCiBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9IGxmKSsKIGxhYnMoeCA9ICJYIHJlc2lkdWFsIiwgeSA9IE5VTEwpKwogc2NhbGVfY29sb3JfZ3JleShlbmQgPSAwLjYpKwogdGhlbWVfcWsoKSsKIHRoZW1lKAogIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIgogKQpwMzwtZ2dwbG90KGRhdGEgPSBkZjEsYWVzKHggPSB4LHkgPSB5KSkrCiBnZW9tX3BvaW50KGNvbG9yID0gc2MpKwogZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIixzZSA9IEZBTFNFLCBjb2xvciA9IHNjLGxpbmV0eXBlID0gImRhc2hlZCIpKwogZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgY29sb3IgPSBsYywgbGluZXR5cGUgPSAic29saWQiLCBzZSA9IEZBTFNFLCBzcGFuID0gMikrCiBsYWJzKHggPSAiWCIsIHkgPSBOVUxMKSsKIHNjYWxlX2NvbG9yX2dyZXkoZW5kID0gMC42KSsKIHRoZW1lX3FrKCkrCiB0aGVtZSgKICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIKICkKcDErcDIrcDMKYGBgCg==