library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggpubr)
## Warning: package 'ggpubr' was built under R version 4.4.3
library(rstatix)
## Warning: package 'rstatix' was built under R version 4.4.3
##
## Attaching package: 'rstatix'
##
## The following object is masked from 'package:stats':
##
## filter
library(broom)
# Wide format
data("selfesteem2", package = "datarium")
selfesteem2
## # A tibble: 24 × 5
## id treatment t1 t2 t3
## <fct> <fct> <dbl> <dbl> <dbl>
## 1 1 ctr 83 77 69
## 2 2 ctr 97 95 88
## 3 3 ctr 93 92 89
## 4 4 ctr 92 92 89
## 5 5 ctr 77 73 68
## 6 6 ctr 72 65 63
## 7 7 ctr 92 89 79
## 8 8 ctr 92 87 81
## 9 9 ctr 95 91 84
## 10 10 ctr 92 84 81
## # ℹ 14 more rows
selfesteem2 is a built-in data set that comes with the
datarium package. It contains repeated measures of
self-esteem scores across three time points (t1,
t2, and t3) for individuals in different
treatment groups.
# Gather the columns t1, t2 and t3 into long format.
# Convert id and time into factor variables
selfesteem2 <- selfesteem2 %>%
gather(key = "time", value = "score", t1, t2, t3) %>%
convert_as_factor(id, time)
key parameter would indicate the column name containing
the variable names (t1, t2, t3).
value parameter would indicate the column name containing
the values from the grouped columns
# Inspect some random rows of the data by groups
selfesteem2 %>% sample_n_by(treatment, time, size = 1)
## # A tibble: 6 × 4
## id treatment time score
## <fct> <fct> <fct> <dbl>
## 1 4 ctr t1 92
## 2 5 ctr t2 73
## 3 2 ctr t3 88
## 4 11 Diet t1 93
## 5 3 Diet t2 91
## 6 11 Diet t3 91
The sample_n_by() function is getting
n(size) samples by specified group(treatment,
time).
selfesteem2 %>%
group_by(treatment, time) %>%
get_summary_stats()
## # A tibble: 6 × 15
## treatment time variable n min max median q1 q3 iqr mad
## <fct> <fct> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 ctr t1 score 12 72 97 92 82 92.2 10.2 2.96
## 2 ctr t2 score 12 65 95 88 76 92 16 5.93
## 3 ctr t3 score 12 62 91 81 68.8 88.2 19.5 11.9
## 4 Diet t1 score 12 74 100 90 83 91.5 8.5 4.45
## 5 Diet t2 score 12 75 99 90 84.5 92.2 7.75 5.19
## 6 Diet t3 score 12 72 97 89.5 84.8 93.5 8.75 6.67
## # ℹ 4 more variables: mean <dbl>, sd <dbl>, se <dbl>, ci <dbl>
selfesteem2 %>%
group_by(treatment, time) %>%
shapiro_test(score)
## # A tibble: 6 × 5
## treatment time variable statistic p
## <fct> <fct> <chr> <dbl> <dbl>
## 1 ctr t1 score 0.828 0.0200
## 2 ctr t2 score 0.868 0.0618
## 3 ctr t3 score 0.887 0.107
## 4 Diet t1 score 0.919 0.279
## 5 Diet t2 score 0.923 0.316
## 6 Diet t3 score 0.886 0.104
All groups appear to be normally distributed (> 0.05) from the Shapiro results except for the control treatment in t1.
ggqqplot(selfesteem2, "score", ggtheme = theme_bw()) +
facet_grid(time ~ treatment, labeller = "label_both")
# Gets the mean and standard deviation per group (treatment x time)
group_stats <- selfesteem2 %>%
group_by(treatment, time) %>%
summarise(
mean = mean(score, na.rm = TRUE),
sd = sd(score, na.rm = TRUE),
.groups = "drop"
)
group_stats
## # A tibble: 6 × 4
## treatment time mean sd
## <fct> <fct> <dbl> <dbl>
## 1 ctr t1 88 8.08
## 2 ctr t2 83.8 10.2
## 3 ctr t3 78.7 10.5
## 4 Diet t1 87.6 7.62
## 5 Diet t2 87.8 7.42
## 6 Diet t3 87.7 8.14
# Gets the x and y values for the normal curve
curve_data <- group_stats %>%
rowwise() %>% # executes per row
mutate(x = list(seq(mean - 4 * sd, mean + 4 * sd, length.out = 100))) %>%
unnest(x) %>%
mutate(y = dnorm(x, mean, sd))
curve_data
## # A tibble: 600 × 6
## treatment time mean sd x y
## <fct> <fct> <dbl> <dbl> <dbl> <dbl>
## 1 ctr t1 88 8.08 55.7 0.0000166
## 2 ctr t1 88 8.08 56.3 0.0000228
## 3 ctr t1 88 8.08 57.0 0.0000312
## 4 ctr t1 88 8.08 57.6 0.0000424
## 5 ctr t1 88 8.08 58.3 0.0000573
## 6 ctr t1 88 8.08 58.9 0.0000768
## 7 ctr t1 88 8.08 59.6 0.000102
## 8 ctr t1 88 8.08 60.3 0.000136
## 9 ctr t1 88 8.08 60.9 0.000178
## 10 ctr t1 88 8.08 61.6 0.000233
## # ℹ 590 more rows
ggplot(selfesteem2, aes(x = score)) +
geom_histogram(aes(y = ..density..), fill = "skyblue", color = "black") +
geom_line(data = curve_data, aes(x = x, y = y), color = "black", size = 1) +
facet_grid(time ~ treatment, labeller = label_both) +
theme_bw() +
labs(y = "Density", x = "Score")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
selfesteem2 %>%
mutate(group = interaction(treatment, time)) %>%
levene_test(score ~ group)
## # A tibble: 1 × 4
## df1 df2 statistic p
## <int> <int> <dbl> <dbl>
## 1 5 66 0.536 0.749
Since the p-value > 0.05, this implies that the groups have homogenuous variances.
anova_test(
data = selfesteem2, dv = score, wid = id,
within = c(treatment, time)
)
## ANOVA Table (type III tests)
##
## $ANOVA
## Effect DFn DFd F p p<.05 ges
## 1 treatment 1 11 15.541 2.00e-03 * 0.059
## 2 time 2 22 27.369 1.08e-06 * 0.049
## 3 treatment:time 2 22 30.424 4.63e-07 * 0.050
##
## $`Mauchly's Test for Sphericity`
## Effect W p p<.05
## 1 time 0.469 0.023 *
## 2 treatment:time 0.616 0.089
##
## $`Sphericity Corrections`
## Effect GGe DF[GG] p[GG] p[GG]<.05 HFe DF[HF]
## 1 time 0.653 1.31, 14.37 5.03e-05 * 0.705 1.41, 15.52
## 2 treatment:time 0.723 1.45, 15.9 1.25e-05 * 0.803 1.61, 17.66
## p[HF] p[HF]<.05
## 1 2.81e-05 *
## 2 4.82e-06 *
From the ANOVA results
Treatment: There is a significant main effect of treatment on score (p = .002). This suggests that across time points, the different treatment groups had significantly different scores.
Time: Significant main effect (p < .001), meaning the scores change significantly over time, regardless of treatment.
Treatment × Time interaction: Also significant (p < .001). This means that the pattern of change over time differs between treatment groups.
From Mauchly’s Test for Sphericity
The p-value for the treatment:time (> 0.05) indicates that sphericity is met for this interaction.
A significant two-way interaction indicates that the impact that one factor (e.g., treatment) has on the outcome variable (e.g., self-esteem score) depends on the level of the other factor (e.g., time) (and vice versa). So, you can decompose a significant two-way interaction into:
Simple main effect: run one-way model of the first variable (factor A) at each level of the second variable (factor B),
Simple pairwise comparisons: if the simple main effect is significant, run multiple pairwise comparisons to determine which groups are different.
For a non-significant two-way interaction, you need to determine whether you have any statistically significant main effects from the ANOVA output.
Reference: Repeated Measures ANOVA in R: The Ultimate Guide - Datanovia
Effect of treatment. In this example, we’ll analyze
the effect of treatment on
self-esteem score at every time point.
# Effect of treatment at each time point
one.way <- selfesteem2 %>%
group_by(time) %>%
anova_test(dv = score, wid = id, within = treatment) %>%
get_anova_table() %>%
adjust_pvalue(method = "bonferroni")
one.way
## # A tibble: 3 × 9
## time Effect DFn DFd F p `p<.05` ges p.adj
## <fct> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
## 1 t1 treatment 1 11 0.376 0.552 "" 0.000767 1
## 2 t2 treatment 1 11 9.03 0.012 "*" 0.052 0.036
## 3 t3 treatment 1 11 30.9 0.00017 "*" 0.199 0.00051
# Pairwise comparisons between treatment groups
pwc <- selfesteem2 %>%
group_by(time) %>%
pairwise_t_test(
score ~ treatment, paired = TRUE,
p.adjust.method = "bonferroni"
)
pwc
## # A tibble: 3 × 11
## time .y. group1 group2 n1 n2 statistic df p p.adj
## * <fct> <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 t1 score ctr Diet 12 12 0.613 11 0.552 0.552
## 2 t2 score ctr Diet 12 12 -3.00 11 0.012 0.012
## 3 t3 score ctr Diet 12 12 -5.56 11 0.00017 0.00017
## # ℹ 1 more variable: p.adj.signif <chr>
Considering the Bonferroni adjusted p-value (p.adj), it can be seen that the simple main effect of treatment was not significant at the time point t1 (p = 1). It becomes significant at t2 (p = 0.036) and t3 (p = 0.00051).
Pairwise comparisons show that the mean self-esteem score was significantly different between ctr and Diet group at t2 (p = 0.12) and t3 (p = 0.00017) but not at t1 (p = 0.55).
Effect of time
# Effect of time at each level of treatment
one.way2 <- selfesteem2 %>%
group_by(treatment) %>%
anova_test(dv = score, wid = id, within = time) %>%
get_anova_table() %>%
adjust_pvalue(method = "bonferroni")
one.way2
## # A tibble: 2 × 9
## treatment Effect DFn DFd F p `p<.05` ges p.adj
## <fct> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
## 1 ctr time 2 22 39.7 0.00000005 "*" 0.145 0.0000001
## 2 Diet time 2 22 0.078 0.925 "" 0.000197 1
# Pairwise comparisons between time points
pwc2 <- selfesteem2 %>%
group_by(treatment) %>%
pairwise_t_test(
score ~ time, paired = TRUE,
p.adjust.method = "bonferroni"
)
pwc2
## # A tibble: 6 × 11
## treatment .y. group1 group2 n1 n2 statistic df p p.adj
## * <fct> <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 ctr score t1 t2 12 12 4.53 11 0.000858 0.003
## 2 ctr score t1 t3 12 12 6.91 11 0.0000255 0.0000765
## 3 ctr score t2 t3 12 12 6.49 11 0.0000449 0.000135
## 4 Diet score t1 t2 12 12 -0.522 11 0.612 1
## 5 Diet score t1 t3 12 12 -0.102 11 0.921 1
## 6 Diet score t2 t3 12 12 0.283 11 0.782 1
## # ℹ 1 more variable: p.adj.signif <chr>
The effect of time is significant only for the control trial, F(2, 22) = 39.7, p < 0.0001. Pairwise comparisons show that all comparisons between time points were statistically significant for control trial.
If the interaction is not significant, you need to interpret the main effects for each of the two variables: treatment and time. A significant main effect can be followed up with pairwise comparisons.
selfesteem2 %>%
pairwise_t_test(
score ~ treatment, paired = TRUE,
p.adjust.method = "bonferroni"
)
## # A tibble: 1 × 10
## .y. group1 group2 n1 n2 statistic df p p.adj p.adj.signif
## * <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 score ctr Diet 36 36 -4.35 35 0.000113 0.000113 ***
There is a highly significant difference in score between the ctr and
Diet treatment conditions. The negative t-statistic (-4.35)
means that scores in the Diet condition were higher than in the ctr
condition
selfesteem2 %>%
pairwise_t_test(
score ~ time, paired = TRUE,
p.adjust.method = "bonferroni"
)
## # A tibble: 3 × 10
## .y. group1 group2 n1 n2 statistic df p p.adj p.adj.signif
## * <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 score t1 t2 24 24 2.86 23 0.009 0.027 *
## 2 score t1 t3 24 24 3.70 23 0.001 0.004 **
## 3 score t2 t3 24 24 3.75 23 0.001 0.003 **
There are significant differences in score across all
time points. Since this is a paired test, it assumes the same
individuals were measured at t1, t2, and t3.