1. Loading libraries
# # Uncomment below and run if these packages haven't already been installed in your version of R
# install.packages("ggplot2")
# install.packages("interactions)
# install.packages("jtools")
# install.packages("rstatix")
# install.packages("ggpubr")
# install.packages("lme4")
# install.packages("lmtest")
# install.packages("emmeans")
# load libraries
library(ggplot2) # to use ggplot for plotting
library(interactions) # to make interaction plots
library(jtools) # to use summ for summarizing models
library(rstatix) # for anova_test and get_anova_table
library(ggpubr) # for visualization, including checking qq-plots for
library(lme4) # to use lmer for mixed models (testing between and within effects)
library(lmtest) # to use bptest for testing heteroscedasticity
library(lmerTest) # to use anova for checking results
library(emmeans) # to get estimated marginal means for models
# Setting working directory path [insert your own path here]
setwd("/Users/emurbanw/OneDrive-MichiganMedicine/OneDriveDocuments/EFDC/Consulting/Jay_Kayser")
# Opening the data I created in the "Creating simulated data" chunk; making cohort a "factor" variable
data <- read.csv("sim_data.csv", header = TRUE)
data$cohort <- as.factor(data$cohort)
long <- read.csv("sim_data_long.csv", header = TRUE)
long$cohort <- as.factor(long$cohort)
2. Creating simulated data
# # I've commented this out since we only need to do this one time
#
# # Creating two dataframes for separate cohorts (cohort 1 has N = 75, cohort 2 has N = 25)
# data1 <- data.frame('ID' = 1:100, 'cohort' = 1, 'dep.1' = rnorm(n=100, mean=5.25, sd=2), 'dep.2' = rnorm(n=100, mean=3.25, sd=1), 'age' = round(rnorm(n=100, mean = 65, sd = 10)))
# data2 <- data.frame('ID' = 101:125, 'cohort' = 2, 'dep.1' = rnorm(n=25, mean=5, sd=1), 'dep.2' = rnorm(n=25, mean=2.5, sd=1), 'age' = round(rnorm(n=25, mean = 65, sd = 10)))
#
# # Combining the two dataframes
# data <- rbind(data1, data2)
#
# # Identifying factor variables
# data$cohort <- as.factor(data$cohort)
#
# # Reshaping the data into long format (necessary for lmer)
# long <- reshape(data=data,
# varying = c("dep.1", "dep.2"),
# timevar=c("time"),
# idvar=c("ID"),
# direction="long", sep=".", drop="na")
#
# # Re-ordering data by ID number
# long <- long[order(long$ID), ]
#
# # Saving the wide simulated data
# write.csv(data, "sim_data.csv", row.names = FALSE)
#
# # Saving the long simulated data
# write.csv(long, "sim_data_long.csv", row.names = FALSE)
3. Visualizing data
aggregate(dep.1 ~ cohort, data = data, mean)
## cohort dep.1
## 1 1 5.290355
## 2 2 5.225078
aggregate(dep.2 ~ cohort, data = data, mean)
## cohort dep.2
## 1 1 3.282309
## 2 2 2.333224
# This gives a plot of the mean depression score for all participants with separate lines for cohort
with(long, {interaction.plot(time, cohort, dep,
ylim = c(-1 , 10), lty = c(1, 12), lwd = 3,
ylab = "mean of dep", xlab = "time", trace.label = "cohort")
axis(side = 2, at=seq(from = -1, to = 10, by = 1), labels=seq(from = -1, to = 10, by = 1))
grid(nx = NULL, ny = 12)
})

# This gives a plot of each participant's data for the selected variables
# To view ALL participants, get rid of the [which(long$ID <=10),] bit
ggplot(long[which(long$ID <=10),]) +
aes(x = time, y = dep) +
# scale_x_discrete(breaks=c(1, 2), labels = c("pre", "post")) +
#scale_x_continuous(breaks=seq(1,2,by=1)) +
scale_x_continuous(limits=c(0.8, 2.2),breaks=c(1, 2), labels = c("pre", "post")) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
xlab("Timepoint") +
ylab("Depression score") +
facet_wrap(~ID)

4. Between-cohort comparisons
# Comparing depression ratings between the two cohorts on one timepoint
# First testing to see if variances are equal between the two cohorts at each timepoint; this determines if var.equal should be set to TRUE or FALSE
# var.equal = TRUE tells us that our test for equality of variances was supported (i.e., the test was non-significant)
# If the var.test was significant, change this to var.equal = FALSE
# paired = FALSE tells us these are independent samples
# Time point 1
var.test(data$dep.1 ~ data$cohort) # The p-value is < .05, so the variances are equal; set var.equal = FALSE in next test
##
## F test to compare two variances
##
## data: data$dep.1 by data$cohort
## F = 3.7331, num df = 99, denom df = 24, p-value = 0.0005111
## alternative hypothesis: true ratio of variances is not equal to 1
## 95 percent confidence interval:
## 1.843398 6.665013
## sample estimates:
## ratio of variances
## 3.733134
t.test(data$dep.1 ~ data$cohort, var.equal = FALSE, paired = FALSE)
##
## Welch Two Sample t-test
##
## data: data$dep.1 by data$cohort
## t = 0.24146, df = 74.063, p-value = 0.8099
## alternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0
## 95 percent confidence interval:
## -0.4733924 0.6039480
## sample estimates:
## mean in group 1 mean in group 2
## 5.290355 5.225078
# Time point 2
var.test(data$dep.2 ~ data$cohort) # The p-value is > .05, so the variances are equal; set var.equal = TRUE in next test
##
## F test to compare two variances
##
## data: data$dep.2 by data$cohort
## F = 0.64154, num df = 99, denom df = 24, p-value = 0.134
## alternative hypothesis: true ratio of variances is not equal to 1
## 95 percent confidence interval:
## 0.3167893 1.1453873
## sample estimates:
## ratio of variances
## 0.6415418
t.test(data$dep.2 ~ data$cohort, var.equal = TRUE, paired = FALSE)
##
## Two Sample t-test
##
## data: data$dep.2 by data$cohort
## t = 4.7854, df = 123, p-value = 4.797e-06
## alternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0
## 95 percent confidence interval:
## 0.5565055 1.3416639
## sample estimates:
## mean in group 1 mean in group 2
## 3.282309 2.333224
5. Within-cohort comparisons
# Assessing change over time w/ t.test (within subjects; can be done combining cohorts or run separately for each cohort; I have done both below)
# This doesn't allow a comparison of whether change over time varies as a function of being in cohort 1 or cohort 2
# Because this is paired, equality of variances is not necessary
# paired = TRUE tells us this is a paired samples t-test
# Cohort 1
t.test(data$dep.1[data$cohort==1], data$dep.2[data$cohort==1], paired = TRUE)
##
## Paired t-test
##
## data: data$dep.1[data$cohort == 1] and data$dep.2[data$cohort == 1]
## t = 10.093, df = 99, p-value < 2.2e-16
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
## 1.613296 2.402797
## sample estimates:
## mean difference
## 2.008047
# Cohort 2
t.test(data$dep.1[data$cohort==2], data$dep.2[data$cohort==2], paired = TRUE)
##
## Paired t-test
##
## data: data$dep.1[data$cohort == 2] and data$dep.2[data$cohort == 2]
## t = 9.4472, df = 24, p-value = 1.475e-09
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
## 2.260077 3.523630
## sample estimates:
## mean difference
## 2.891854
# Both cohorts together
t.test(data$dep.1, data$dep.2, paired = TRUE)
##
## Paired t-test
##
## data: data$dep.1 and data$dep.2
## t = 12.632, df = 124, p-value < 2.2e-16
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
## 1.842475 2.527142
## sample estimates:
## mean difference
## 2.184808
6. Checking ANOVA assumptions
# Identifying outliers
long %>%
group_by(time, cohort) %>%
identify_outliers(dep)
## # A tibble: 4 × 7
## cohort time ID age dep is.outlier is.extreme
## <fct> <int> <int> <int> <dbl> <lgl> <lgl>
## 1 1 1 6 62 10.7 TRUE FALSE
## 2 1 1 54 86 9.92 TRUE FALSE
## 3 1 1 86 65 -0.554 TRUE FALSE
## 4 1 2 32 71 5.60 TRUE FALSE
# Checking normality of distribution; p-values should be > .05
# Shapiro test is sensitive to sample sizes over 50, so even small deviations may lead to significant results
long %>%
group_by(time, cohort) %>%
shapiro_test(dep)
## # A tibble: 4 × 5
## cohort time variable statistic p
## <fct> <int> <chr> <dbl> <dbl>
## 1 1 1 dep 0.990 0.636
## 2 2 1 dep 0.993 0.999
## 3 1 2 dep 0.996 0.994
## 4 2 2 dep 0.949 0.234
# Checking homogeneity of variances; p-values should be > .05
long %>%
group_by(time) %>%
levene_test(dep ~ cohort)
## # A tibble: 2 × 5
## time df1 df2 statistic p
## <int> <int> <int> <dbl> <dbl>
## 1 1 1 123 6.88 0.00982
## 2 2 1 123 1.56 0.214
# Checking assumption of sphericity
# This is tested and corrected for automatically when running anova_test and get_anova_table
# Checking homogeneity of covariances; p-values should be > .001; sensitive to unequal sample sizes
box_m(long[, "dep", drop = FALSE], long$cohort)
## # A tibble: 1 × 4
## statistic p.value parameter method
## <dbl> <dbl> <dbl> <chr>
## 1 0.000548 0.981 1 Box's M-test for Homogeneity of Covariance Matric…
7. Between/Within cohort comparisons
# THERE ARE SEVERAL OPTIONS HERE, AND IT DEPENDS ON CHARACTERISTICS OF YOUR DESIGN
# For example, if you have unbalanced cohort sizes or missing data, you probably will need to run the lmer command which is more flexible
# Otherwise, if you have a very simple design you could probably stick with a repeated measures or mixed GLM.
# Often they will give you similar results in terms of rejecting or failing to reject the null hypothesis, but not always.
# In the example here, there are actually discrepancies in the significance of the cohort term, even though we don't necessarily want to interpret that anyway in the presence of an interaction.
# So for this example dataset, I recommend using model2 given the unbalanced design, presence of outliers, and flexibility of lmer.
########## Effect of time in a Repeated Measures-GLM ##########
model0 <- anova_test(data = long, dv = dep, wid = ID, within = time)
# Checking the results
get_anova_table(model0)
## ANOVA Table (type III tests)
##
## Effect DFn DFd F p p<.05 ges
## 1 time 1 124 159.567 5.06e-24 * 0.38
########## Effect of time and cohort in a Mixed-GLM ##########
model1 <- anova_test(data = long, dv = dep, wid = ID, within = time, between = cohort)
# Checking the results
get_anova_table(model1)
## ANOVA Table (type III tests)
##
## Effect DFn DFd F p p<.05 ges
## 1 cohort 1 123 5.172 2.50e-02 * 0.021
## 2 time 1 123 131.819 3.48e-21 * 0.339
## 3 cohort:time 1 123 4.289 4.00e-02 * 0.016
########## Linear mixed effects model w/ random intercept ##########
# Running the model; (1|ID) specifies that observations are nested within ID and that intercepts of the model can vary between individuals
model2 <- lmer(dep ~ time*cohort + (1|ID), data = long)
# Checking the results
anova(model2)
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## time 240.090 240.090 1 123.00 131.8191 < 2e-16 ***
## cohort 2.655 2.655 1 152.42 1.4579 0.22913
## time:cohort 7.811 7.811 1 123.00 4.2886 0.04046 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summ(model2, digits=3, confint=TRUE, part.corr = TRUE, robust=FALSE)
## MODEL INFO:
## Observations: 250
## Dependent Variable: dep
## Type: Mixed effects linear regression
##
## MODEL FIT:
## AIC = 884.128, BIC = 905.257
## Pseudo-R² (fixed effects) = 0.400
## Pseudo-R² (total) = 0.427
##
## FIXED EFFECTS:
## -------------------------------------------------------------------------
## Est. 2.5% 97.5% t val. d.f. p
## ------------------ -------- -------- -------- --------- --------- -------
## (Intercept) 7.298 6.704 7.893 24.074 152.424 0.000
## time -2.008 -2.382 -1.634 -10.521 123.000 0.000
## cohort2 0.819 -0.510 2.147 1.207 152.424 0.229
## time:cohort2 -0.884 -1.720 -0.047 -2.071 123.000 0.040
## -------------------------------------------------------------------------
##
## p values calculated using Satterthwaite d.f.
##
## RANDOM EFFECTS:
## ------------------------------------
## Group Parameter Std. Dev.
## ---------- ------------- -----------
## ID (Intercept) 0.290
## Residual 1.350
## ------------------------------------
##
## Grouping variables:
## --------------------------
## Group # groups ICC
## ------- ---------- -------
## ID 125 0.044
## --------------------------
summary(model2)
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: dep ~ time * cohort + (1 | ID)
## Data: long
##
## REML criterion at convergence: 872.1
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -4.0975 -0.5115 -0.0109 0.4929 3.8494
##
## Random effects:
## Groups Name Variance Std.Dev.
## ID (Intercept) 0.0841 0.29
## Residual 1.8214 1.35
## Number of obs: 250, groups: ID, 125
##
## Fixed effects:
## Estimate Std. Error df t value Pr(>|t|)
## (Intercept) 7.2984 0.3032 152.4240 24.074 <2e-16 ***
## time -2.0080 0.1909 123.0000 -10.521 <2e-16 ***
## cohort2 0.8185 0.6779 152.4240 1.207 0.2291
## time:cohort2 -0.8838 0.4268 123.0000 -2.071 0.0405 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr) time cohrt2
## time -0.944
## cohort2 -0.447 0.422
## time:cohrt2 0.422 -0.447 -0.944
# Looking at estimated marginal means based on the model
model2_means <- emmeans(model2, ~time*cohort)
summary(model2_means)
## time cohort emmean SE df lower.CL upper.CL
## 1 1 5.29 0.138 246 5.02 5.56
## 2 1 3.28 0.138 246 3.01 3.55
## 1 2 5.23 0.276 246 4.68 5.77
## 2 2 2.33 0.276 246 1.79 2.88
##
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
interact_plot(model2, pred = time, modx = cohort, plot.points = TRUE, partial.residuals= FALSE, interval = FALSE, facet.modx = FALSE, pred.labels = c("pre", "post"), x.label = "Timepoint", y.label = "Depression Score", legend.main = "Cohort")

########## Linear mixed effects model w/ random intercept and slope ##########
# # The below model would add that change in depression scores over time can vary between individuals
# # However, this will not work with only two timepoints, as it overparamaterizes the model by trying to estimate random intercepts AND random slopes.
# model3 <- lmer(dep ~ time*cohort + (1 + time|ID), data = long)
# anova(model3)
# summ(model3, digits=3, confint=TRUE, part.corr = TRUE, robust=FALSE)
# summary(model3)