15 GEE, Continuous Outcome: Beat the Blues

15.1 Packages

15.1.1 CRAN

library(tidyverse)    # all things tidy
library(pander)       # nice looking genderal tabulations
library(furniture)    # nice table1() descriptives
library(texreg)       # Convert Regression Output to LaTeX or HTML Tables
library(psych)        # contains some useful functions, like headTail

library(interactions)
library(performance)
library(lme4)         # Linear, generalized linear, & nonlinear mixed models

library(corrplot)     # Vizualize correlation matrix
library(gee)          # Genderalized Estimation Equation Solver
library(geepack)      # Genderalized Estimation Equation Package 
library(MuMIn)        # Multi-Model Inference (caluclate QIC)

library(HSAUR)        # package with the dataset

15.1.2 GitHub

Helper extract functions for exponentiation of parameters form generalized regression models within a texreg table of model parameters.

# remotes::install_github("sarbearschwartz/texreghelpr") # first time

library(texreghelpr)

15.2 Background

This dataset was used as an example in Chapter 11 of “A Handbook of Statistical Analysis using R” by Brian S. Everitt and Torsten Hothorn. The authors include this data set in their HSAUR package on CRAN.

The Background

“These data resulted from a clinical trial of an interactive multimedia program called ‘Beat the Blues’ designed to deliver cognitive behavioral therapy (CBT) to depressed patients via a computer terminal. Full details are given here: Proudfoot et. al (2003), but in essence Beat the Blues is an interactive program using multimedia techniques, in particular video vignettes.”

“In a randomized controlled trial of the program, patients with depression recruited in primary care were randomized to either the Beating the Blues program or to”Treatment as Usual” (TAU). Patients randomized to the BEat the Blues also received pharmacology and/or general practice (GP) support and practical/social help,offered as part of treatment as usual, with the exception of any face-to-face counseling or psychological intervention. Patients allocated to TAU received whatever treatment their GP prescribed. The latter included, besides any medication, discussion of problems with GP, provisions of practical/social help, referral to a counselor, referral to a practice nurse, referral to mental health professionals, or further physical examination.

The Research Question

Net of other factors (use of antidepressants and length of the current episode), does the Beat-the-Blues program results in better depression trajectories over treatment as usual?

The Data

The variables are as follows:

  • drug did the patient take anti-depressant drugs (No or Yes)
  • length the length of the current episode of depression, a factor with levels:
    • “<6m” less than six months
    • “>6m” more than six months
  • treatment treatment group, a factor with levels:
    • “TAU” treatment as usual
    • “BtheB” Beat the Blues
  • bdi.pre Beck Depression Inventory II, before treatment
  • bdi.2m Beck Depression Inventory II, after 2 months
  • bdi.4m Beck Depression Inventory II, after 4 months
  • bdi.6m Beck Depression Inventory II, after 6 months
  • bdi.8m Beck Depression Inventory II, after 8 months

15.2.1 Read in the data

data(BtheB, package = "HSAUR")

BtheB %>% 
  psych::headTail()
    drug length treatment bdi.pre bdi.2m bdi.4m bdi.6m bdi.8m
1     No    >6m       TAU      29      2      2   <NA>   <NA>
2    Yes    >6m     BtheB      32     16     24     17     20
3    Yes    <6m       TAU      25     20   <NA>   <NA>   <NA>
4     No    >6m     BtheB      21     17     16     10      9
... <NA>   <NA>      <NA>     ...    ...    ...    ...    ...
97   Yes    <6m       TAU      28   <NA>   <NA>   <NA>   <NA>
98    No    >6m     BtheB      11     22      9     11     11
99    No    <6m       TAU      13      5      5      0      6
100  Yes    <6m       TAU      43   <NA>   <NA>   <NA>   <NA>

15.2.2 Wide Format

Tidy up the dataset

WIDE format * One line per person * Good for descriptive tables & correlation matrices

btb_wide <- BtheB %>% 
  dplyr::mutate(id = row_number()) %>%           # create a new variable to ID participants
  dplyr::select(id, treatment,                    # specify that ID variable is first
                drug, length,
                bdi.pre, bdi.2m, bdi.4m, bdi.6m, bdi.8m)
btb_wide %>% 
  psych::headTail(top = 10, bottom = 10) 
     id treatment drug length bdi.pre bdi.2m bdi.4m bdi.6m bdi.8m
1     1       TAU   No    >6m      29      2      2   <NA>   <NA>
2     2     BtheB  Yes    >6m      32     16     24     17     20
3     3       TAU  Yes    <6m      25     20   <NA>   <NA>   <NA>
4     4     BtheB   No    >6m      21     17     16     10      9
5     5     BtheB  Yes    >6m      26     23   <NA>   <NA>   <NA>
6     6     BtheB  Yes    <6m       7      0      0      0      0
7     7       TAU  Yes    <6m      17      7      7      3      7
8     8       TAU   No    >6m      20     20     21     19     13
9     9     BtheB  Yes    <6m      18     13     14     20     11
10   10     BtheB  Yes    >6m      20      5      5      8     12
... ...      <NA> <NA>   <NA>     ...    ...    ...    ...    ...
91   91       TAU   No    <6m      16   <NA>   <NA>   <NA>   <NA>
92   92     BtheB  Yes    <6m      30     26     28   <NA>   <NA>
93   93     BtheB  Yes    <6m      17      8      7     12   <NA>
94   94     BtheB   No    >6m      19      4      3      3      3
95   95     BtheB   No    >6m      16     11      4      2      3
96   96     BtheB  Yes    >6m      16     16     10     10      8
97   97       TAU  Yes    <6m      28   <NA>   <NA>   <NA>   <NA>
98   98     BtheB   No    >6m      11     22      9     11     11
99   99       TAU   No    <6m      13      5      5      0      6
100 100       TAU  Yes    <6m      43   <NA>   <NA>   <NA>   <NA>

15.2.3 Long Format

Restructure to long format

LONG FORMAT * One line per observation * Good for plots and modeling

btb_long <- btb_wide %>% 
  tidyr::pivot_longer(cols = c(bdi.2m, bdi.4m, bdi.6m, bdi.8m),  # all existing variables (not quoted)
                      names_to = "month", 
                      names_pattern = "bdi.(.)m",
                      values_to = "bdi") %>% 
  dplyr::mutate(month = as.numeric(month)) %>% 
  dplyr::filter(complete.cases(id, bdi, treatment, month)) %>% 
  dplyr::arrange(id, month) %>% 
  dplyr::select(id, treatment, drug, length, bdi.pre, month, bdi)
btb_long %>% 
  psych::headTail(top = 10, bottom = 10) 
    id treatment drug length bdi.pre month bdi
1    1       TAU   No    >6m      29     2   2
2    1       TAU   No    >6m      29     4   2
3    2     BtheB  Yes    >6m      32     2  16
4    2     BtheB  Yes    >6m      32     4  24
5    2     BtheB  Yes    >6m      32     6  17
6    2     BtheB  Yes    >6m      32     8  20
7    3       TAU  Yes    <6m      25     2  20
8    4     BtheB   No    >6m      21     2  17
9    4     BtheB   No    >6m      21     4  16
10   4     BtheB   No    >6m      21     6  10
11 ...      <NA> <NA>   <NA>     ...   ... ...
12  96     BtheB  Yes    >6m      16     6  10
13  96     BtheB  Yes    >6m      16     8   8
14  98     BtheB   No    >6m      11     2  22
15  98     BtheB   No    >6m      11     4   9
16  98     BtheB   No    >6m      11     6  11
17  98     BtheB   No    >6m      11     8  11
18  99       TAU   No    <6m      13     2   5
19  99       TAU   No    <6m      13     4   5
20  99       TAU   No    <6m      13     6   0
21  99       TAU   No    <6m      13     8   6

15.3 Exploratory Data Analysis

15.3.1 Visualize: Person-profile Plots

Create spaghetti plots of the raw, observed data

btb_long %>% 
  ggplot(aes(x = month,
             y = bdi)) +
  geom_point() +
  geom_line(aes(group = id), 
            size = 1, 
            alpha = 0.3) +
  geom_smooth(method = "lm") +
  theme_bw() +
  facet_grid(.~ treatment) +
  labs(title = "BtheB - Observed Data Across Time with LM Smoother",
       subtitle = "Seperate by Treatment")

btb_long %>% 
  ggplot(aes(x = month,
             y = bdi)) +
  geom_point() +
  geom_line(aes(group = id), 
            size = 1, 
            alpha = 0.3) +
  geom_smooth(method = "lm") +
  facet_grid(drug~ treatment, labeller = label_both) +
  theme_bw() +
  labs(title = "BtheB - Observed Data Across Time with LM Smoother",
       subtitle = "Seperate by Treatment & Antidepressant Use")

btb_long %>% 
  ggplot(aes(x = month,
             y = bdi)) +
  geom_point() +
  geom_line(aes(group = id,
                color = length), 
            size = 1, 
            alpha = 0.3) +
  geom_smooth(aes(color = length),
              method = "lm",
              size = 1.25,
              se = FALSE) +
  facet_grid(drug~ treatment, labeller = label_both) +
  theme_bw() +
  labs(title = "BtheB - Observed Data Across Time with LM Smoother",
       subtitle = "Seperate by Treatment, Antidepressant Use, & Length of Current Episode")

btb_long %>% 
  ggplot(aes(x = month,
             y = bdi,
             color = treatment,
             fill = treatment)) +
  geom_smooth(method = "lm") +
  theme_bw() +
  labs(title = "BtheB - Predictions from TWO Seperate Single Simple Linear Models (lm)",
       subtitle = "Assumes Independence of Repeated Measures")

15.3.2 Calculate the Observed Correlation Structure

bdi_corr <- btb_wide %>% 
  dplyr::select(starts_with("bdi")) %>% 
  stats::cor(use="pairwise.complete.obs")

bdi_corr
          bdi.pre    bdi.2m    bdi.4m    bdi.6m    bdi.8m
bdi.pre 1.0000000 0.6142207 0.5691248 0.5077286 0.3835090
bdi.2m  0.6142207 1.0000000 0.7903346 0.7849188 0.7038158
bdi.4m  0.5691248 0.7903346 1.0000000 0.8166591 0.7220149
bdi.6m  0.5077286 0.7849188 0.8166591 1.0000000 0.8107773
bdi.8m  0.3835090 0.7038158 0.7220149 0.8107773 1.0000000

15.3.3 Plot the correlation matrix to get a better feel for the pattern

bdi_corr %>% 
  corrplot::corrplot.mixed(upper = "ellipse")

15.4 Multiple Regression (OLS)

This ignores any correlation between repeated measures on the same individual and treats all observations as independent.

15.4.1 Fit the models

btb_lm_1 <- stats::lm(bdi ~ bdi.pre + length + drug + treatment + month,
                    data = btb_long)

btb_lm_2 <- stats::lm(bdi ~ bdi.pre + length + drug + treatment*month,
                    data = btb_long)

btb_lm_3 <- stats::lm(bdi ~ bdi.pre + length + drug + treatment + drug*month,
                    data = btb_long)

btb_lm_4 <- stats::lm(bdi ~ bdi.pre + length + drug*treatment*month,
                    data = btb_long)

15.4.2 Parameter Estimates Table

texreg::knitreg(list(btb_lm_1, btb_lm_2, btb_lm_3, btb_lm_4),
                label = "lm",
                caption = "OLS")
OLS
  Model 1 Model 2 Model 3 Model 4
(Intercept) 7.88*** 7.77*** 7.21*** 7.33**
  (1.78) (2.08) (2.03) (2.30)
bdi.pre 0.57*** 0.57*** 0.57*** 0.56***
  (0.05) (0.05) (0.05) (0.05)
length>6m 1.75 1.75 1.78 1.86
  (1.11) (1.11) (1.11) (1.10)
drugYes -3.55** -3.55** -2.10 -2.00
  (1.14) (1.15) (2.39) (3.75)
treatmentBtheB -3.35** -3.13 -3.36** -3.31
  (1.10) (2.36) (1.10) (3.13)
month -0.96*** -0.93** -0.82** -0.60
  (0.23) (0.34) (0.31) (0.40)
treatmentBtheB:month   -0.05   -0.56
    (0.47)   (0.63)
drugYes:month     -0.32 -1.02
      (0.47) (0.73)
drugYes:treatmentBtheB       -0.23
        (4.92)
drugYes:treatmentBtheB:month       1.31
        (0.98)
R2 0.40 0.40 0.40 0.42
Adj. R2 0.39 0.38 0.39 0.40
Num. obs. 280 280 280 280
***p < 0.001; **p < 0.01; *p < 0.05

15.4.3 Plot the model predictions

interactions::interact_plot(model = btb_lm_1,
                            pred = month,
                            modx = treatment,
                            main.title = "LM with 95% CI",
                            interval = TRUE)                 # default = 95% confidence interval

interactions::interact_plot(model = btb_lm_1,
                            pred = month,
                            modx = treatment,
                            main.title = "LM with 68% CI (M +/- SEM)",
                            interval = TRUE,
                            int.width = 0.68)  # change to 68% CI, which is +/- 1 SEM

interactions::interact_plot(model = btb_lm_1,
                            pred = month,
                            modx = treatment,
                            interval = TRUE,
                            colors = rep("black", times = 3),
                            x.label = "Month",
                            y.label = "Predicted BDI",
                            legend.main    = "Baseline BDI:") +
  theme_bw() +
  theme(legend.position = "bottom",
        legend.key.width = unit(2, "cm")) +
  labs(title    = "BtheB - Predictions from the LM model (95% CI)",
       subtitle = "Trajectory for a person with BL depression < 6 months and randomized to TAU") 

effects::Effect(focal.predictors = c("treatment", "month"), 
                mod = btb_lm_1) %>% 
  data.frame %>% 
  dplyr::mutate(treatment = fct_reorder2(treatment, month, fit)) %>%  
  ggplot(aes(x = month,
             y = fit)) +
  geom_line(aes(color = treatment)) +
  geom_ribbon(aes(ymin = lower,            # 95% confidence intervals
                  ymax = upper,
                  fill = treatment), 
              alpha = 0.3) +
  geom_ribbon(aes(ymin = fit - se,         # Mean +/- 1 standard error for the mean (SEM)
                  ymax = fit + se,
                  fill = treatment), 
              alpha = 0.3) +
  theme_bw() +
  labs(title = "BtheB - Predictions from a Single Linear Model (lm)",
       subtitle = "Assumes Independence of Repeated Measures") +
  theme(legend.position = c(1, 1),
        legend.justification = c(1.1, 1.1),
        legend.background = element_rect(color = "black"))

15.5 Multilevel Models (MLM)

15.5.1 Fit the models

btb_lmer_RI   <- lmerTest::lmer(bdi ~ bdi.pre + length + drug + treatment + month + (1 | id), 
                            data = btb_long, 
                            REML = TRUE)

btb_lmer_RIAS <- lmerTest::lmer(bdi ~ bdi.pre + length + drug + treatment + month + (month | id), 
                            data = btb_long, 
                            REML = TRUE,
                            control = lmerControl(optimizer = "Nelder_Mead"))

15.5.2 Parameter Estimates Table

texreg::knitreg(list(btb_lm_1, btb_lmer_RI, btb_lmer_RIAS), 
                custom.model.names = c("OLS", "MLM-RI", "MLM-RIAS"),
                label = "mlm",
                caption = "LM vs. MLM")
LM vs. MLM
  OLS MLM-RI MLM-RIAS
(Intercept) 7.88*** 5.92* 5.94**
  (1.78) (2.31) (2.30)
bdi.pre 0.57*** 0.64*** 0.64***
  (0.05) (0.08) (0.08)
length>6m 1.75 0.24 0.10
  (1.11) (1.68) (1.67)
drugYes -3.55** -2.79 -2.89
  (1.14) (1.77) (1.76)
treatmentBtheB -3.35** -2.36 -2.49
  (1.10) (1.71) (1.71)
month -0.96*** -0.71*** -0.70***
  (0.23) (0.15) (0.16)
R2 0.40    
Adj. R2 0.39    
Num. obs. 280 280 280
AIC   1882.08 1885.16
BIC   1911.16 1921.50
Log Likelihood   -933.04 -932.58
Num. groups: id   97 97
Var: id (Intercept)   51.44 50.56
Var: Residual   25.27 23.87
Var: id month     0.23
Cov: id (Intercept) month     -0.31
***p < 0.001; **p < 0.01; *p < 0.05

15.5.3 Likelihood Ratio Test

anova(btb_lmer_RI, 
      btb_lmer_RIAS, 
      refit = FALSE)
Data: btb_long
Models:
btb_lmer_RI: bdi ~ bdi.pre + length + drug + treatment + month + (1 | id)
btb_lmer_RIAS: bdi ~ bdi.pre + length + drug + treatment + month + (month | id)
              npar    AIC    BIC  logLik deviance  Chisq Df Pr(>Chisq)
btb_lmer_RI      8 1882.1 1911.2 -933.04   1866.1                     
btb_lmer_RIAS   10 1885.2 1921.5 -932.58   1865.2 0.9236  2     0.6301
performance::compare_performance(btb_lm_1, 
                                 btb_lmer_RI, 
                                 btb_lmer_RIAS,
                                 rank = TRUE)
# Comparison of Model Performance Indices

Name          |           Model |  RMSE | Sigma | AIC weights | BIC weights | Performance-Score
-----------------------------------------------------------------------------------------------
btb_lmer_RI   | lmerModLmerTest | 4.234 | 5.027 |       0.831 |       0.995 |            97.86%
btb_lmer_RIAS | lmerModLmerTest | 4.016 | 4.885 |       0.169 |       0.005 |            55.20%
btb_lm_1      |              lm | 8.560 | 8.654 |    8.42e-28 |    6.20e-27 |             0.00%

15.5.4 Plot the model predictions

interactions::interact_plot(model = btb_lmer_RI,
                            pred = month,
                            modx = treatment,
                            mod2 = drug,
                            interval = TRUE) +
  theme_bw() +
  labs(title = "BtheB - Predictions from a Multilevel Model (lmer)") +
  theme(legend.position = c(0, 0),
        legend.justification = c(-0.1, -0.1),
        legend.background = element_rect(color = "black"))

effects::Effect(c("treatment", "month", "drug"), 
                mod = btb_lmer_RI) %>% 
  data.frame %>%   
  dplyr::mutate(treatment = fct_reorder2(treatment, month, fit)) %>%
  ggplot(aes(x = month,
             y = fit)) +
  geom_line(aes(color = treatment)) +
  geom_ribbon(aes(ymin = lower,
                  ymax = upper,
                  fill = treatment), 
              alpha = 0.3) +
  theme_bw() +
  facet_grid(.~ drug, labeller = label_both) +
  labs(title = "BtheB - Predictions from a Multilevel Model (lmer)") +
  theme(legend.position = c(0, 0),
        legend.justification = c(-0.1, -0.1),
        legend.background = element_rect(color = "black"))

15.6 General Estimating Equations, GEE

15.6.1 Explore Various Correlation Structures

15.6.1.1 Fit the models - Main effects to determine correlation structure

Use the gee() function from the gee package for the results to be used in a texreg::knitreg() tables.

The output below each model is the ‘starting’ model assuming independence, so they will all be the same here.

btb_gee_in <- gee::gee(bdi ~ bdi.pre + length + drug + treatment + month, 
                       data = btb_long, 
                       id = id, 
                       family = gaussian, 
                       corstr = 'independence')
   (Intercept)        bdi.pre      length>6m        drugYes treatmentBtheB 
     7.8830747      0.5723729      1.7530800     -3.5460058     -3.3539662 
         month 
    -0.9608077 
btb_gee_ex <- gee::gee(bdi ~ bdi.pre + length + drug + treatment + month, 
                       data = btb_long, 
                       id = id, 
                       family = gaussian, 
                       corstr = 'exchangeable')
   (Intercept)        bdi.pre      length>6m        drugYes treatmentBtheB 
     7.8830747      0.5723729      1.7530800     -3.5460058     -3.3539662 
         month 
    -0.9608077 
# The AR-1 fails if any subjects have only 1 observation
# to use this one, we would need to remove participants with only 1 BDI

# btb_gee_ar <- gee(bdi ~ bdi.pre + length + drug + treatment + month,
#                  data = btb_long,
#                  id = id,
#                  family = gaussian,
#                  corstr = 'AR-M',
#                  Mv = 1)

btb_gee_un <- gee::gee(bdi ~ bdi.pre + length + drug + treatment + month, 
                       data = btb_long, 
                       id = id, 
                       family = gaussian, 
                       corstr = 'unstructured')
   (Intercept)        bdi.pre      length>6m        drugYes treatmentBtheB 
     7.8830747      0.5723729      1.7530800     -3.5460058     -3.3539662 
         month 
    -0.9608077 
summary(btb_gee_in)

 GEE:  GENERALIZED LINEAR MODELS FOR DEPENDENT DATA
 gee S-function, version 4.13 modified 98/01/27 (1998) 

Model:
 Link:                      Identity 
 Variance to Mean Relation: Gaussian 
 Correlation Structure:     Independent 

Call:
gee::gee(formula = bdi ~ bdi.pre + length + drug + treatment + 
    month, id = id, data = btb_long, family = gaussian, corstr = "independence")

Summary of Residuals:
         Min           1Q       Median           3Q          Max 
-24.20158432  -5.31202378   0.01101526   5.29503741  27.77789553 


Coefficients:
                 Estimate Naive S.E.   Naive z Robust S.E.  Robust z
(Intercept)     7.8830747 1.78048908  4.427477  2.19973944  3.583640
bdi.pre         0.5723729 0.05486079 10.433188  0.08853253  6.465114
length>6m       1.7530800 1.10849861  1.581490  1.41954159  1.234962
drugYes        -3.5460058 1.14469086 -3.097785  1.73069664 -2.048889
treatmentBtheB -3.3539662 1.09831939 -3.053726  1.71390982 -1.956909
month          -0.9608077 0.23263437 -4.130119  0.17688635 -5.431780

Estimated Scale Parameter:  74.8854
Number of Iterations:  1

Working Correlation
     [,1] [,2] [,3] [,4]
[1,]    1    0    0    0
[2,]    0    1    0    0
[3,]    0    0    1    0
[4,]    0    0    0    1
summary(btb_gee_ex)

 GEE:  GENERALIZED LINEAR MODELS FOR DEPENDENT DATA
 gee S-function, version 4.13 modified 98/01/27 (1998) 

Model:
 Link:                      Identity 
 Variance to Mean Relation: Gaussian 
 Correlation Structure:     Exchangeable 

Call:
gee::gee(formula = bdi ~ bdi.pre + length + drug + treatment + 
    month, id = id, data = btb_long, family = gaussian, corstr = "exchangeable")

Summary of Residuals:
        Min          1Q      Median          3Q         Max 
-25.4478843  -6.3276726  -0.8152833   4.3622258  25.4078115 


Coefficients:
                 Estimate Naive S.E.    Naive z Robust S.E.   Robust z
(Intercept)     5.8855129 2.32380381  2.5327065  2.10712166  2.7931529
bdi.pre         0.6399964 0.08033495  7.9665999  0.07931263  8.0692874
length>6m       0.2084783 1.69179766  0.1232288  1.48052530  0.1408137
drugYes        -2.7742506 1.78397557 -1.5550945  1.64824318 -1.6831561
treatmentBtheB -2.3360241 1.72621751 -1.3532617  1.66217026 -1.4054060
month          -0.7078407 0.14254124 -4.9658660  0.15394156 -4.5981134

Estimated Scale Parameter:  77.14393
Number of Iterations:  5

Working Correlation
          [,1]      [,2]      [,3]      [,4]
[1,] 1.0000000 0.6915241 0.6915241 0.6915241
[2,] 0.6915241 1.0000000 0.6915241 0.6915241
[3,] 0.6915241 0.6915241 1.0000000 0.6915241
[4,] 0.6915241 0.6915241 0.6915241 1.0000000
summary(btb_gee_un)

 GEE:  GENERALIZED LINEAR MODELS FOR DEPENDENT DATA
 gee S-function, version 4.13 modified 98/01/27 (1998) 

Model:
 Link:                      Identity 
 Variance to Mean Relation: Gaussian 
 Correlation Structure:     Unstructured 

Call:
gee::gee(formula = bdi ~ bdi.pre + length + drug + treatment + 
    month, id = id, data = btb_long, family = gaussian, corstr = "unstructured")

Summary of Residuals:
        Min          1Q      Median          3Q         Max 
-25.1527937  -6.1091139  -0.5896205   4.7316139  25.9041542 


Coefficients:
                 Estimate Naive S.E.   Naive z Robust S.E.   Robust z
(Intercept)     6.3905215 2.28769760  2.793429  2.15668950  2.9631162
bdi.pre         0.6171798 0.07744569  7.969195  0.08081777  7.6366846
length>6m       0.5834398 1.61626647  0.360980  1.46837275  0.3973377
drugYes        -2.7908835 1.69816226 -1.643473  1.63741987 -1.7044398
treatmentBtheB -2.4261698 1.64272613 -1.476917  1.65519523 -1.4657907
month          -0.7628336 0.18121518 -4.209546  0.15643591 -4.8763329

Estimated Scale Parameter:  76.40371
Number of Iterations:  5

Working Correlation
          [,1]      [,2]      [,3]      [,4]
[1,] 1.0000000 0.7069560 0.5704892 0.4714744
[2,] 0.7069560 1.0000000 0.6086188 0.4637445
[3,] 0.5704892 0.6086188 1.0000000 0.5454963
[4,] 0.4714744 0.4637445 0.5454963 1.0000000

15.6.1.2 Parameter Estimates Table

texreg::knitreg(list(btb_lm_1, 
                     btb_lmer_RI, 
                     btb_gee_in, 
                     btb_gee_ex, 
                     btb_gee_un),
                custom.model.names = c("OLS", 
                                       "MLM-RI", 
                                       "GEE-in", 
                                       "GEE-ex", 
                                       "GEE-un"),
                  label = "GEEs",
                  caption = "LM, MLM, and GEE")
LM, MLM, and GEE
  OLS MLM-RI GEE-in GEE-ex GEE-un
(Intercept) 7.88*** 5.92* 7.88*** 5.89** 6.39**
  (1.78) (2.31) (2.20) (2.11) (2.16)
bdi.pre 0.57*** 0.64*** 0.57*** 0.64*** 0.62***
  (0.05) (0.08) (0.09) (0.08) (0.08)
length>6m 1.75 0.24 1.75 0.21 0.58
  (1.11) (1.68) (1.42) (1.48) (1.47)
drugYes -3.55** -2.79 -3.55* -2.77 -2.79
  (1.14) (1.77) (1.73) (1.65) (1.64)
treatmentBtheB -3.35** -2.36 -3.35 -2.34 -2.43
  (1.10) (1.71) (1.71) (1.66) (1.66)
month -0.96*** -0.71*** -0.96*** -0.71*** -0.76***
  (0.23) (0.15) (0.18) (0.15) (0.16)
R2 0.40        
Adj. R2 0.39        
Num. obs. 280 280 280 280 280
AIC   1882.08      
BIC   1911.16      
Log Likelihood   -933.04      
Num. groups: id   97      
Var: id (Intercept)   51.44      
Var: Residual   25.27      
Scale     74.89 77.14 76.40
***p < 0.001; **p < 0.01; *p < 0.05

15.6.1.3 Re-Fit Models

Use the geeglm() function from the geepack package for the results to be used in a anova() table and interaction plots.

This function does NOT produce the same starting model output as gee::gee().

btb_geeglm_in <- geepack::geeglm(bdi ~ bdi.pre + length + drug + treatment + month, 
                                 data = btb_long, 
                                 id = id,
                                 wave = month,
                                 family = gaussian, 
                                 corstr = 'independence')

btb_geeglm_ex <- geepack::geeglm(bdi ~ bdi.pre + length + drug + treatment + month, 
                                 data = btb_long, 
                                 id = id, 
                                 wave = month,
                                 family = gaussian, 
                                 corstr = 'exchangeable')


btb_geeglm_ar <- geepack::geeglm(bdi ~ bdi.pre + length + drug + treatment + month, 
                                 data = btb_long, 
                                 id = id, 
                                 wave = month,
                                 family = gaussian, 
                                 corstr = 'ar1')

btb_geeglm_un <- geepack::geeglm(bdi ~ bdi.pre + length + drug + treatment + month, 
                                 data = btb_long, 
                                 id = id,
                                 wave = month,
                                 family = gaussian, 
                                 corstr = 'unstructured')

Can’t Use the Likelihood Ratio Test

The anova() function is used to compare nested models for parameters (fixed effects), not correlation structures.

anova(btb_geeglm_in, btb_geeglm_ex)
Models are identical
NULL
anova(btb_geeglm_in, btb_geeglm_ar)
Models are identical
NULL
anova(btb_geeglm_in, btb_geeglm_un)
Models are identical
NULL

15.6.1.4 Variaous QIC Measures of Fit

References:

The QIC() is one way to try to measure model fit. You can enter more than one model into a single function call.

QIC(I) based on independence model <– suggested by Pan (Biometric, March 2001), asymptotically unbiased estimator (choose the correlation stucture that produces the smallest QIC(I), p122)

MuMIn::QIC(btb_geeglm_in, 
           btb_geeglm_ex, 
           btb_geeglm_ar, 
           btb_geeglm_un, 
           typeR = FALSE) %>%  # default
  pander::pander(caption = "QIC")
QIC
  QIC
btb_geeglm_in 307
btb_geeglm_ex 296
btb_geeglm_ar 298
btb_geeglm_un 297

QIC(R) is based on quasi-likelihood of a working correlation R model, can NOT be used to select the working correlation matrix.

MuMIn::QIC(btb_geeglm_in, 
           btb_geeglm_ex, 
           btb_geeglm_ar, 
           btb_geeglm_un, 
           typeR = TRUE)    # NOT the default
                   QIC
btb_geeglm_in 306.5589
btb_geeglm_ex 304.5003
btb_geeglm_ar 304.6425
btb_geeglm_un 304.4087

QIC_U(R) approximates QIC(R), and while both are useful for variable selection, they can NOT be applied to select the working correlation matrix.

MuMIn::QICu(btb_geeglm_in, 
            btb_geeglm_ex, 
            btb_geeglm_ar, 
            btb_geeglm_un) 
                  QICu
btb_geeglm_in 292.0000
btb_geeglm_ex 283.7551
btb_geeglm_ar 285.6132
btb_geeglm_un 284.1707
MuMIn::model.sel(btb_geeglm_in, 
                 btb_geeglm_ex, 
                 btb_geeglm_ar, 
                 btb_geeglm_un, 
                 rank = "QIC")     #sorts the best to the TOP, uses QIC(I)
Model selection table 
              (Int) bdi.pre drg lng     mnt trt corstr qLik   QIC delta weight
btb_geeglm_ex 5.880  0.6402   +   + -0.7070   + exchng -140 296.3  0.00  0.450
btb_geeglm_un 6.068  0.6307   +   + -0.7061   + unstrc -140 296.6  0.32  0.382
btb_geeglm_ar 6.620  0.5956   +   + -0.7357   +    ar1 -140 298.3  2.00  0.165
btb_geeglm_in 7.883  0.5724   +   + -0.9608   + indpnd -140 306.6 10.30  0.003
Abbreviations:
 corstr: exchng = 'exchangeable', indpnd = 'independence', 
         unstrc = 'unstructured'
Models ranked by QIC(x) 

15.6.2 Final “Best” Model

15.6.2.1 Tabulate Model Parameters

texreg::knitreg(list(btb_gee_ex),
                custom.model.names = c("GEE-ex"),
                single.row = TRUE,
                caption = "Final GEE, exchangeable correlation")
Final GEE, exchangeable correlation
  GEE-ex
(Intercept) 5.89 (2.11)**
bdi.pre 0.64 (0.08)***
length>6m 0.21 (1.48)
drugYes -2.77 (1.65)
treatmentBtheB -2.34 (1.66)
month -0.71 (0.15)***
Scale 77.14
Num. obs. 280
***p < 0.001; **p < 0.01; *p < 0.05

15.6.2.2 Plot the Marginal Effects

interactions::interact_plot(model = btb_geeglm_ex,
                            pred = "month",
                            modx = "treatment")

Do not worry about confidence intervals.

expand.grid(bdi.pre = 23,
            length = "<6m",
            drug = "No",
            treatment = levels(btb_long$treatment),
            month = seq(from = 2, to = 8, by = 2)) %>%  
  dplyr::mutate(fit_in = predict(btb_geeglm_in,
                                 newdata = .,
                                 type = "response")) %>% 
  dplyr::mutate(fit_ex = predict(btb_geeglm_ex,
                                 newdata = .,
                                 type = "response")) %>% 
  dplyr::mutate(fit_ar = predict(btb_geeglm_ar,
                                 newdata = .,
                                 type = "response")) %>% 
  dplyr::mutate(fit_un = predict(btb_geeglm_un,
                                 newdata = .,
                                 type = "response")) %>% 
  tidyr::pivot_longer(cols = starts_with("fit_"),
                      names_to = "covR",
                      names_pattern = "fit_(.*)",
                      names_ptype = list(covR = "factor()"),
                      values_to = "fit") %>% 
  dplyr::mutate(covR = factor(covR, 
                              levels = c("un", "ar", "ex", "in"),
                              labels = c("Unstructured",
                                         "Auto-Regressive",
                                         "Compound Symetry",
                                         "Independence"))) %>% 
  ggplot(aes(x = month,
             y = fit,
             linetype = treatment)) +
  geom_line(alpha = 0.6) +
  theme_bw() +
  labs(title    = "BtheB - Predictions from four GEE models (geeglm)",
       x = "Month",
       y = "Predicted BDI",
       linetype = "Treatment:") +
  scale_linetype_manual(values = c("solid", "longdash")) +
  theme(legend.key.width = unit(1, "cm")) +
  facet_wrap(~ covR)

expand.grid(bdi.pre = 23,
            length = "<6m",
            drug = "No",
            treatment = levels(btb_long$treatment),
            month = seq(from = 2, to = 8, by = 2)) %>%  
  dplyr::mutate(fit_in = predict(btb_geeglm_in,
                                 newdata = .,
                                 type = "response")) %>% 
  dplyr::mutate(fit_ex = predict(btb_geeglm_ex,
                                 newdata = .,
                                 type = "response")) %>% 
  dplyr::mutate(fit_ar = predict(btb_geeglm_ar,
                                 newdata = .,
                                 type = "response")) %>% 
  dplyr::mutate(fit_un = predict(btb_geeglm_un,
                                 newdata = .,
                                 type = "response")) %>% 
  tidyr::pivot_longer(cols = starts_with("fit_"),
                      names_to = "covR",
                      names_pattern = "fit_(.*)",
                      names_ptype = list(covR = "factor()"),
                      values_to = "fit") %>% 
  dplyr::mutate(covR = factor(covR, 
                              levels = c("un", "ar", "ex", "in"),
                              labels = c("Unstructured",
                                         "Auto-Regressive",
                                         "Compound Symetry",
                                         "Independence"))) %>% 
  ggplot(aes(x = month,
             y = fit,
             color = covR,
             linetype = treatment)) +
  geom_line(alpha = 0.6) +
  theme_bw() +
  labs(title    = "BtheB - Predictions from four GEE models (geeglm)",
       x = "Month",
       y = "Predicted BDI",
       color = "Covariance Pattern:",
       linetype = "Treatment:") +
  scale_linetype_manual(values = c("solid", "longdash")) +
  scale_size_manual(values     = c(2, 1, 1, 1)) +
  scale_color_manual(values    = c("red", 
                                   "dodgerblue",
                                   "blue",
                                   "darkgreen")) +
  theme(legend.key.width = unit(1, "cm"))

15.6.3 Investigate interactions NOT with time (month)

btb_geeglm_ex_1 <- geepack::geeglm(bdi ~ bdi.pre*length + drug + treatment + month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')

btb_geeglm_ex_2 <- geepack::geeglm(bdi ~ bdi.pre*drug + length + treatment + month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')

btb_geeglm_ex_3 <- geepack::geeglm(bdi ~  bdi.pre*treatment + length + drug + month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')
texreg::knitreg(list(btb_geeglm_ex, 
                    btb_geeglm_ex_1, 
                    btb_geeglm_ex_2,
                    btb_geeglm_ex_3),
               custom.model.names = c("None",
                                      "Length",
                                      "Antidpressant",
                                      "Treatment"),
               label = "GEE_inter1",
               caption = "GEE (exchangable): Interactions, not with Time")
GEE (exchangable): Interactions, not with Time
  None Length Antidpressant Treatment
(Intercept) 5.88** 3.24 3.37 3.62
  (2.11) (1.99) (2.68) (2.78)
bdi.pre 0.64*** 0.76*** 0.75*** 0.74***
  (0.08) (0.08) (0.12) (0.12)
length>6m 0.20 5.66 0.47 0.22
  (1.48) (3.27) (1.46) (1.48)
drugYes -2.77 -2.25 1.47 -2.76
  (1.65) (1.62) (3.20) (1.64)
treatmentBtheB -2.33 -2.42 -2.19 1.24
  (1.66) (1.63) (1.67) (3.38)
month -0.71*** -0.70*** -0.71*** -0.71***
  (0.15) (0.15) (0.15) (0.15)
bdi.pre:length>6m   -0.23    
    (0.16)    
bdi.pre:drugYes     -0.19  
      (0.17)  
bdi.pre:treatmentBtheB       -0.15
        (0.17)
Scale parameter: gamma 75.50 74.56 73.92 74.15
Scale parameter: SE 10.68 10.71 10.15 10.72
Correlation parameter: alpha 0.69 0.69 0.68 0.68
Correlation parameter: SE 0.11 0.12 0.10 0.11
Num. obs. 280 280 280 280
Num. clust. 97 97 97 97
***p < 0.001; **p < 0.01; *p < 0.05

15.6.4 Investigate interactions with time (month)

btb_geeglm_ex_11 <- geepack::geeglm(bdi ~ bdi.pre + length + drug + treatment*month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')

btb_geeglm_ex_12 <- geepack::geeglm(bdi ~ bdi.pre + length + treatment + drug*month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')

btb_geeglm_ex_13 <- geepack::geeglm(bdi ~ bdi.pre + drug + treatment +  length*month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')

btb_geeglm_ex_14 <- geepack::geeglm(bdi ~ length + drug + treatment + bdi.pre*month, 
                                   data = btb_long, 
                                   id = id, 
                                   wave = month,
                                   family = gaussian, 
                                   corstr = 'exchangeable')
texreg::knitreg(list(btb_geeglm_ex, 
                       btb_geeglm_ex_11, 
                       btb_geeglm_ex_12,
                       btb_geeglm_ex_13,
                       btb_geeglm_ex_14),
               custom.model.names = c("None",
                                      "Treatment",
                                      "Antidepressant",
                                      "Length",
                                      "BL BDI"),
               label="GEEinter2",
               caption = "GEE (exchangable): Interactions with Time")
GEE (exchangable): Interactions with Time
  None Treatment Antidepressant Length BL BDI
(Intercept) 5.88** 6.83** 6.63** 6.28** 5.09*
  (2.11) (2.22) (2.21) (2.15) (2.34)
bdi.pre 0.64*** 0.64*** 0.64*** 0.64*** 0.67***
  (0.08) (0.08) (0.08) (0.08) (0.09)
length>6m 0.20 0.25 0.16 -0.44 0.27
  (1.48) (1.49) (1.49) (1.83) (1.48)
drugYes -2.77 -2.74 -4.31* -2.79 -2.72
  (1.65) (1.66) (2.01) (1.64) (1.66)
treatmentBtheB -2.33 -4.23* -2.32 -2.31 -2.36
  (1.66) (2.00) (1.67) (1.66) (1.66)
month -0.71*** -0.95*** -0.88*** -0.80*** -0.50
  (0.15) (0.26) (0.21) (0.16) (0.35)
treatmentBtheB:month   0.47      
    (0.30)      
drugYes:month     0.38    
      (0.30)    
length>6m:month       0.16  
        (0.29)  
bdi.pre:month         -0.01
          (0.01)
Scale parameter: gamma 75.50 76.03 76.13 75.21 75.17
Scale parameter: SE 10.68 10.83 10.81 10.63 10.79
Correlation parameter: alpha 0.69 0.70 0.70 0.69 0.69
Correlation parameter: SE 0.11 0.11 0.11 0.11 0.11
Num. obs. 280 280 280 280 280
Num. clust. 97 97 97 97 97
***p < 0.001; **p < 0.01; *p < 0.05

Now only plot the significant variables for the ‘best’ model.

interactions::interact_plot(model = btb_geeglm_ex,
                            pred = "month",
                            modx = "bdi.pre")

interactions::interact_plot(model = btb_geeglm_ex,
                            pred = "month",
                            modx = "bdi.pre",
                            modx.values = c(10, 20, 30),
                            at = list(length = "<6m",
                                      drug = "No",
                                      treatment = "TAU"),
                            colors = rep("black", times = 3),
                            x.label = "Month",
                            y.label = "Predicted BDI",
                            legend.main    = "Baseline BDI:") +
  theme_bw() +
  theme(legend.position = "bottom",
        legend.key.width = unit(2, "cm")) +
  labs(title    = "BtheB - Predictions from the GEE model (exchangable)",
       subtitle = "Trajectory for a person with BL depression < 6 months and randomized to TAU") 

15.7 Conclusion

The Research Question

Does the Beat-the-Blues program

Net of other factors (use of antidepressants and length of the current episode), does the Beat-the-Blues program results in better depression trajectories over treatment as usual?

The Conclusion

There is no evidence that depression trajectories differ between participants randomized to the Beat the Blues program or the Treatment as Usual condition, after accounting for covariates and the correlation between repeated measurements.