# =============================================================================
# Nama : Chiesa Safira Yoviana Hefny
# NIM : 2611018002
# =============================================================================
# REPEATED MEASURE ANALYSIS DENGAN R
# Contoh terapan: Intervensi diet dan olahraga terhadap gula darah puasa (GDP)
# pada pasien diabetes (DATA SIMULASI, dibaca dari file Excel)
#
# Isi:
# 0. Paket & pengaturan
# 1. Impor data Excel (format lebar -> panjang)
# 2. Eksplorasi data: statistik deskriptif, profile plot, spaghetti plot
# 3. Repeated Measure ANOVA satu arah (within-subject: Waktu)
# 3a. Uji asumsi: outlier, normalitas, sfierisitas (Mauchly)
# 3b. ANOVA + koreksi Greenhouse-Geisser / Huynh-Feldt
# 3c. Pendekatan multivariat (MANOVA) sebagai pembanding
# 3d. Post hoc berpasangan & kontras polinomial (tren)
# 3e. Alternatif nonparametrik: uji Friedman
# 4. Mixed Design ANOVA (between: Kelompok x within: Waktu)
# 4a. Uji asumsi: outlier, normalitas, Levene, Box's M, Mauchly
# 4b. ANOVA campuran + ukuran efek
# 4c. Analisis efek sederhana (simple effects) & post hoc
# 4d. Kontras interaksi (perubahan dari baseline antarkelompok)
# 5. Pembanding: Linear Mixed Model (LMM)
# 6. Menyimpan data & ringkasan hasil
# =============================================================================
# 0. PAKET & PENGATURAN
# Jalankan sekali bila paket belum terpasang:
# install.packages(c("tidyverse", "readxl", "afex", "emmeans", "rstatix", "car",
# "effectsize", "lme4", "lmerTest", "performance", "ggpubr"))
suppressPackageStartupMessages({
library(readxl) # membaca file Excel
library(dplyr) # manipulasi data
library(tidyr) # format panjang <-> lebar
library(ggplot2) # grafik
library(afex) # ANOVA within/mixed (tipe III, koreksi GG/HF, MANOVA)
library(emmeans) # rerata marginal, efek sederhana, post hoc, kontras
library(rstatix) # uji asumsi yang ramah pipe (outlier, Shapiro, Levene, Box's M)
library(car) # leveneTest, Anova
library(effectsize) # eta kuadrat parsial, omega kuadrat
library(lme4) # linear mixed model
library(lmerTest) # uji F/t dengan derajat bebas Satterthwaite / Kenward-Roger
})
options(contrasts = c("contr.sum", "contr.poly")) # kontras jumlah-nol untuk SS tipe III
afex_options(emmeans_model = "multivariate") # post hoc memakai model multivariat (tahan terhadap non-sfierisitas)
theme_set(theme_bw(base_size = 12))
# 1. IMPOR DATA EXCEL
# Skenario: 90 pasien diabetes diacak ke tiga kelompok (n = 30 per kelompok):
# - Kontrol : edukasi standar
# - Diet : diet terkontrol
# - Diet+Olahraga : diet terkontrol + olahraga terstruktur
# Gula darah puasa (GDP, mg/dL) diukur 4 kali: GDP_W0 (baseline) s.d. GDP_W3.
#
# Pastikan file "data_gdp_diabetes.xlsx" berada di working directory, atau
# ganti 'file_data' dengan path lengkap, mis. "C:/Users/nama/Documents/data_gdp_diabetes.xlsx".
# Cek working directory dengan getwd(); ubah dengan setwd("folder_anda").
file_data <- "data_gdp_diabetes.xlsx"
sheet_dat <- "Data"
kel_lab <- c("Kontrol", "Diet", "Diet+Olahraga")
minggu <- c(0, 4, 8, 12) # jarak waktu pengukuran W0-W3 (minggu); ubah sesuai desain
kol_gdp <- paste0("GDP_W", 0:3) # nama kolom GDP di Excel
dat_wide <- read_excel(file_data, sheet = sheet_dat) |>
as.data.frame()
# Cek struktur & data hilang sebelum lanjut
str(dat_wide)
## 'data.frame': 90 obs. of 8 variables:
## $ id : chr "P001" "P002" "P003" "P004" ...
## $ kelompok: chr "Kontrol" "Kontrol" "Kontrol" "Kontrol" ...
## $ usia : num 59 48 65 47 46 60 43 59 48 40 ...
## $ jk : chr "P" "P" "P" "L" ...
## $ GDP_W0 : num 167 191 143 216 202 178 176 190 188 180 ...
## $ GDP_W1 : num 165 195 156 225 202 185 180 186 183 174 ...
## $ GDP_W2 : num 163 191 145 220 200 185 187 192 182 185 ...
## $ GDP_W3 : num 159 197 152 227 192 175 176 195 185 182 ...
colSums(is.na(dat_wide))
## id kelompok usia jk GDP_W0 GDP_W1 GDP_W2 GDP_W3
## 0 0 0 0 0 0 0 0
dat_wide$kelompok <- factor(dat_wide$kelompok, levels = kel_lab)
dat_wide$jk <- factor(dat_wide$jk)
dat_wide$id <- factor(dat_wide$id)
# Format panjang (satu baris = satu pengukuran), dibutuhkan afex/rstatix/lme4
dat_long <- dat_wide |>
pivot_longer(all_of(kol_gdp), names_to = "waktu", values_to = "gdp") |>
mutate(waktu = factor(waktu, levels = kol_gdp,
labels = paste0("W", 0:3)),
minggu = minggu[as.integer(waktu)])
head(dat_wide)
## id kelompok usia jk GDP_W0 GDP_W1 GDP_W2 GDP_W3
## 1 P001 Kontrol 59 P 167 165 163 159
## 2 P002 Kontrol 48 P 191 195 191 197
## 3 P003 Kontrol 65 P 143 156 145 152
## 4 P004 Kontrol 47 L 216 225 220 227
## 5 P005 Kontrol 46 P 202 202 200 192
## 6 P006 Kontrol 60 P 178 185 185 175
head(dat_long)
## # A tibble: 6 × 7
## id kelompok usia jk waktu gdp minggu
## <fct> <fct> <dbl> <fct> <fct> <dbl> <dbl>
## 1 P001 Kontrol 59 P W0 167 0
## 2 P001 Kontrol 59 P W1 165 4
## 3 P001 Kontrol 59 P W2 163 8
## 4 P001 Kontrol 59 P W3 159 12
## 5 P002 Kontrol 48 P W0 191 0
## 6 P002 Kontrol 48 P W1 195 4
str(dat_long)
## tibble [360 × 7] (S3: tbl_df/tbl/data.frame)
## $ id : Factor w/ 90 levels "P001","P002",..: 1 1 1 1 2 2 2 2 3 3 ...
## $ kelompok: Factor w/ 3 levels "Kontrol","Diet",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ usia : num [1:360] 59 59 59 59 48 48 48 48 65 65 ...
## $ jk : Factor w/ 2 levels "L","P": 2 2 2 2 2 2 2 2 2 2 ...
## $ waktu : Factor w/ 4 levels "W0","W1","W2",..: 1 2 3 4 1 2 3 4 1 2 ...
## $ gdp : num [1:360] 167 165 163 159 191 195 191 197 143 156 ...
## $ minggu : num [1:360] 0 4 8 12 0 4 8 12 0 4 ...
# 2. EKSPLORASI DATA
desk <- dat_long |>
group_by(kelompok, waktu) |>
get_summary_stats(gdp, type = "mean_sd")
desk
## # A tibble: 12 × 6
## kelompok waktu variable n mean sd
## <fct> <fct> <fct> <dbl> <dbl> <dbl>
## 1 Kontrol W0 gdp 30 188. 19.2
## 2 Kontrol W1 gdp 30 187. 17.4
## 3 Kontrol W2 gdp 30 188. 17.6
## 4 Kontrol W3 gdp 30 189. 18.4
## 5 Diet W0 gdp 30 195 23.0
## 6 Diet W1 gdp 30 186. 22.3
## 7 Diet W2 gdp 30 183. 22.6
## 8 Diet W3 gdp 30 177. 24.5
## 9 Diet+Olahraga W0 gdp 30 185. 21.9
## 10 Diet+Olahraga W1 gdp 30 175. 22.2
## 11 Diet+Olahraga W2 gdp 30 168. 22.6
## 12 Diet+Olahraga W3 gdp 30 162. 22.4
# Matriks kovarians & korelasi antarwaktu (seluruh subjek)
# -> memberi gambaran awal apakah sfierisitas masuk akal
S <- cov(dat_wide[, kol_gdp])
R <- cor(dat_wide[, kol_gdp])
round(S, 1); round(R, 2)
## GDP_W0 GDP_W1 GDP_W2 GDP_W3
## GDP_W0 467.7 436.5 436.4 447.8
## GDP_W1 436.5 448.9 452.0 473.1
## GDP_W2 436.4 452.0 504.5 518.4
## GDP_W3 447.8 473.1 518.4 584.9
## GDP_W0 GDP_W1 GDP_W2 GDP_W3
## GDP_W0 1.00 0.95 0.90 0.86
## GDP_W1 0.95 1.00 0.95 0.92
## GDP_W2 0.90 0.95 1.00 0.95
## GDP_W3 0.86 0.92 0.95 1.00
# Varians selisih antarpasangan waktu (inti asumsi sfierisitas)
pasangan <- combn(kol_gdp, 2)
var_selisih <- apply(pasangan, 2, function(p) var(dat_wide[[p[1]]] - dat_wide[[p[2]]]))
names(var_selisih) <- apply(pasangan, 2, paste, collapse = " - ")
round(var_selisih, 1)
## GDP_W0 - GDP_W1 GDP_W0 - GDP_W2 GDP_W0 - GDP_W3 GDP_W1 - GDP_W2 GDP_W1 - GDP_W3
## 43.7 99.4 157.0 49.4 87.7
## GDP_W2 - GDP_W3
## 52.6
# Profile plot: rerata +/- 95% CI per kelompok
p_profil <- ggplot(dat_long, aes(minggu, gdp, colour = kelompok, group = kelompok)) +
stat_summary(fun = mean, geom = "line", linewidth = 1) +
stat_summary(fun = mean, geom = "point", size = 2.5) +
stat_summary(fun.data = mean_cl_normal, geom = "errorbar", width = .6) +
scale_x_continuous(breaks = minggu) +
labs(x = "Minggu ke-", y = "Gula darah puasa (mg/dL)", colour = "Kelompok",
title = "Profil rerata GDP (± 95% CI)") +
theme(legend.position = "bottom")
p_profil
## Warning: Computation failed in `stat_summary()`.
## Caused by error in `fun.data()`:
## ! The package "Hmisc" is required.

# Spaghetti plot: lintasan tiap pasien
p_spag <- ggplot(dat_long, aes(minggu, gdp, group = id)) +
geom_line(alpha = .3) +
stat_summary(aes(group = kelompok), fun = mean, geom = "line",
colour = "firebrick", linewidth = 1.2) +
facet_wrap(~ kelompok) +
scale_x_continuous(breaks = minggu) +
labs(x = "Minggu ke-", y = "GDP (mg/dL)", title = "Lintasan individu dan rerata kelompok")
p_spag

# 3. REPEATED MEASURE ANOVA SATU ARAH
# Pertanyaan: apakah GDP berubah selama pengukuran pada kelompok Diet+Olahraga?
d1 <- droplevels(filter(dat_long, kelompok == "Diet+Olahraga"))
d1w <- filter(dat_wide, kelompok == "Diet+Olahraga")
## 3a. Uji asumsi -------------------------------------------------------------
# (i) Outlier per waktu (ekstrem = di luar Q1-3IQR / Q3+3IQR)
d1 |> group_by(waktu) |> identify_outliers(gdp)
## [1] waktu id kelompok usia jk gdp minggu
## [8] is.outlier is.extreme
## <0 rows> (or 0-length row.names)
# (ii) Normalitas per waktu (Shapiro-Wilk) dan Q-Q plot
d1 |> group_by(waktu) |> shapiro_test(gdp)
## # A tibble: 4 × 4
## waktu variable statistic p
## <fct> <chr> <dbl> <dbl>
## 1 W0 gdp 0.986 0.949
## 2 W1 gdp 0.985 0.945
## 3 W2 gdp 0.974 0.652
## 4 W3 gdp 0.982 0.870
ggpubr::ggqqplot(d1, "gdp", facet.by = "waktu")

# (iii) Sfierisitas: Mauchly (dilaporkan otomatis oleh anova_test & afex)
aov1_rs <- anova_test(data = d1, dv = gdp, wid = id, within = waktu,
effect.size = "pes")
aov1_rs # berisi: ANOVA, Mauchly's Test, koreksi GG & HF
## ANOVA Table (type III tests)
##
## $ANOVA
## Effect DFn DFd F p p<.05 pes
## 1 waktu 3 87 124.727 2.1e-31 * 0.811
##
## $`Mauchly's Test for Sphericity`
## Effect W p p<.05
## 1 waktu 0.53 0.003 *
##
## $`Sphericity Corrections`
## Effect GGe DF[GG] p[GG] p[GG]<.05 HFe DF[HF] p[HF]
## 1 waktu 0.784 2.35, 68.18 3.95e-25 * 0.857 2.57, 74.57 2.9e-27
## p[HF]<.05
## 1 *
get_anova_table(aov1_rs, correction = "auto") # auto: GG dipakai jika Mauchly p < .05
## ANOVA Table (type III tests)
##
## Effect DFn DFd F p p<.05 pes
## 1 waktu 2.35 68.18 124.727 3.95e-25 * 0.811
## 3b. ANOVA dengan afex (sumber utama laporan) -------------------------------
aov1 <- aov_ez(id = "id", dv = "gdp", data = d1, within = "waktu",
anova_table = list(es = c("ges", "pes"), correction = "GG"))
aov1 # tabel ringkas (df sudah dikoreksi GG)
## Anova Table (Type 3 tests)
##
## Response: gdp
## Effect df MSE F ges pes p.value
## 1 waktu 2.35, 68.18 28.29 124.73 *** .126 .811 <.001
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
##
## Sphericity correction method: GG
summary(aov1) # univariat tanpa koreksi + Mauchly + epsilon GG & HF
##
## Univariate Type III Repeated-Measures ANOVA Assuming Sphericity
##
## Sum Sq num Df Error SS den Df F value Pr(>F)
## (Intercept) 3579380 1 55556 29 1868.42 < 2.2e-16 ***
## waktu 8296 3 1929 87 124.73 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
##
## Mauchly Tests for Sphericity
##
## Test statistic p-value
## waktu 0.52956 0.0034797
##
##
## Greenhouse-Geisser and Huynh-Feldt Corrections
## for Departure from Sphericity
##
## GG eps Pr(>F[GG])
## waktu 0.78365 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## HF eps Pr(>F[HF])
## waktu 0.8571779 2.902781e-27
# Ukuran efek tambahan
eta_squared(aov1, partial = TRUE)
## # Effect Size for ANOVA (Type III)
##
## Parameter | Eta2 (partial) | 95% CI
## -----------------------------------------
## waktu | 0.81 | [0.75, 1.00]
##
## - One-sided CIs: upper bound fixed at [1.00].
omega_squared(aov1, partial = TRUE)
## # Effect Size for ANOVA (Type III)
##
## Parameter | Omega2 (partial) | 95% CI
## -------------------------------------------
## waktu | 0.12 | [0.02, 1.00]
##
## - One-sided CIs: upper bound fixed at [1.00].
## 3c. Pendekatan multivariat (tidak memerlukan sfierisitas) ------------------
aov1$Anova # Pillai, Wilks, Hotelling-Lawley, Roy
##
## Type III Repeated Measures MANOVA Tests: Pillai test statistic
## Df test stat approx F num Df den Df Pr(>F)
## (Intercept) 1 0.98472 1868.42 1 29 < 2.2e-16 ***
## waktu 1 0.91776 100.44 3 27 9.239e-15 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 3d. Post hoc & kontras tren ------------------------------------------------
em1 <- emmeans(aov1, ~ waktu)
em1
## waktu emmean SE df lower.CL upper.CL
## W0 185 3.99 29 177 193
## W1 175 4.06 29 167 184
## W2 168 4.12 29 160 177
## W3 162 4.08 29 154 171
##
## Confidence level used: 0.95
pairs(em1, adjust = "bonferroni") # semua pasangan waktu (6 perbandingan)
## contrast estimate SE df t.ratio p.value
## W0 - W1 9.27 0.681 29 13.601 <0.0001
## W0 - W2 16.43 1.320 29 12.443 <0.0001
## W0 - W3 22.27 1.380 29 16.130 <0.0001
## W1 - W2 7.17 1.220 29 5.871 <0.0001
## W1 - W3 13.00 1.250 29 10.398 <0.0001
## W2 - W3 5.83 1.300 29 4.472 0.0007
##
## P value adjustment: bonferroni method for 6 tests
contrast(em1, "trt.vs.ctrl", ref = 1, adjust = "holm") # tiap waktu vs baseline
## contrast estimate SE df t.ratio p.value
## W1 - W0 -9.27 0.681 29 -13.601 <0.0001
## W2 - W0 -16.43 1.320 29 -12.443 <0.0001
## W3 - W0 -22.27 1.380 29 -16.130 <0.0001
##
## P value adjustment: holm method for 3 tests
contrast(em1, "poly") # tren linear, kuadratik, kubik
## contrast estimate SE df t.ratio p.value
## linear -73.967 4.70 29 -15.746 <0.0001
## quadratic 3.433 1.44 29 2.382 0.0240
## cubic -0.767 3.45 29 -0.222 0.8256
## 3e. Alternatif nonparametrik ------------------------------------------------
friedman_test(d1, gdp ~ waktu | id)
## # A tibble: 1 × 6
## .y. n statistic df p method
## * <chr> <int> <dbl> <dbl> <dbl> <chr>
## 1 gdp 30 74.9 3 3.87e-16 Friedman test
friedman_effsize(d1, gdp ~ waktu | id) # Kendall's W
## # A tibble: 1 × 5
## .y. n effsize method magnitude
## * <chr> <int> <dbl> <chr> <ord>
## 1 gdp 30 0.832 Kendall W large
d1 |> wilcox_test(gdp ~ waktu, paired = TRUE, p.adjust.method = "bonferroni")
## # A tibble: 6 × 9
## .y. group1 group2 n1 n2 statistic p p.adj p.adj.signif
## * <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <chr>
## 1 gdp W0 W1 30 30 465 0.00000000186 1.12e-8 ****
## 2 gdp W0 W2 30 30 465 0.00000000186 1.12e-8 ****
## 3 gdp W0 W3 30 30 465 0.00000000186 1.12e-8 ****
## 4 gdp W1 W2 30 30 434 0.00000407 2.44e-5 ****
## 5 gdp W1 W3 30 30 460. 0.0000000149 8.94e-8 ****
## 6 gdp W2 W3 30 30 404. 0.000163 9.77e-4 ***
# (Opsional) ANOVA robust berbasis trimmed mean -- paket WRS2
if (requireNamespace("WRS2", quietly = TRUE)) {
print(WRS2::rmanova(d1$gdp, d1$waktu, d1$id, tr = 0.2))
}
## Call:
## WRS2::rmanova(y = d1$gdp, groups = d1$waktu, blocks = d1$id,
## tr = 0.2)
##
## Test statistic: F = 75.6972
## Degrees of freedom 1: 2.75
## Degrees of freedom 2: 46.67
## p-value: 0
# 4. MIXED DESIGN ANOVA (Kelompok [between] x Waktu [within])
# Pertanyaan: apakah pola perubahan GDP berbeda antarkelompok intervensi?
## 4a. Uji asumsi -------------------------------------------------------------
# (i) Outlier per sel
dat_long |> group_by(kelompok, waktu) |> identify_outliers(gdp)
## # A tibble: 6 × 9
## kelompok waktu id usia jk gdp minggu is.outlier is.extreme
## <fct> <fct> <fct> <dbl> <fct> <dbl> <dbl> <lgl> <lgl>
## 1 Kontrol W0 P003 65 P 143 0 TRUE FALSE
## 2 Kontrol W1 P004 47 L 225 4 TRUE FALSE
## 3 Kontrol W1 P026 45 L 228 4 TRUE FALSE
## 4 Kontrol W2 P003 65 P 145 8 TRUE FALSE
## 5 Kontrol W3 P004 47 L 227 12 TRUE FALSE
## 6 Kontrol W3 P026 45 L 228 12 TRUE FALSE
# (ii) Normalitas per sel (3 x 4 = 12 sel) dan residual model
dat_long |> group_by(kelompok, waktu) |> shapiro_test(gdp)
## # A tibble: 12 × 5
## kelompok waktu variable statistic p
## <fct> <fct> <chr> <dbl> <dbl>
## 1 Kontrol W0 gdp 0.985 0.933
## 2 Kontrol W1 gdp 0.962 0.338
## 3 Kontrol W2 gdp 0.981 0.864
## 4 Kontrol W3 gdp 0.970 0.528
## 5 Diet W0 gdp 0.969 0.521
## 6 Diet W1 gdp 0.968 0.474
## 7 Diet W2 gdp 0.970 0.536
## 8 Diet W3 gdp 0.969 0.505
## 9 Diet+Olahraga W0 gdp 0.986 0.949
## 10 Diet+Olahraga W1 gdp 0.985 0.945
## 11 Diet+Olahraga W2 gdp 0.974 0.652
## 12 Diet+Olahraga W3 gdp 0.982 0.870
ggpubr::ggqqplot(dat_long, "gdp", ggtheme = theme_bw()) +
facet_grid(waktu ~ kelompok)

# (iii) Homogenitas varians antarkelompok pada TIAP waktu (Levene, median-centered)
dat_long |> group_by(waktu) |> levene_test(gdp ~ kelompok)
## # A tibble: 4 × 5
## waktu df1 df2 statistic p
## <fct> <int> <int> <dbl> <dbl>
## 1 W0 2 87 1.22 0.300
## 2 W1 2 87 1.37 0.259
## 3 W2 2 87 1.18 0.313
## 4 W3 2 87 1.55 0.219
# (iv) Homogenitas matriks kovarians antarkelompok (Box's M; uji pada alpha = .001)
box_m(dat_wide[, kol_gdp], dat_wide$kelompok)
## # A tibble: 1 × 4
## statistic p.value parameter method
## <dbl> <dbl> <dbl> <chr>
## 1 21.2 0.384 20 Box's M-test for Homogeneity of Covariance Matric…
# (v) Sfierisitas: Mauchly (dari summary model di bawah)
## 4b. ANOVA campuran ---------------------------------------------------------
aov2 <- aov_ez(id = "id", dv = "gdp", data = dat_long,
between = "kelompok", within = "waktu",
anova_table = list(es = c("ges", "pes"), correction = "GG"))
aov2
## Anova Table (Type 3 tests)
##
## Response: gdp
## Effect df MSE F ges pes p.value
## 1 kelompok 2, 87 1744.52 4.55 * .091 .095 .013
## 2 waktu 2.68, 233.49 25.19 122.45 *** .050 .585 <.001
## 3 kelompok:waktu 5.37, 233.49 25.19 37.09 *** .031 .460 <.001
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
##
## Sphericity correction method: GG
summary(aov2) # Mauchly, epsilon GG/HF, p terkoreksi
##
## Univariate Type III Repeated-Measures ANOVA Assuming Sphericity
##
## Sum Sq num Df Error SS den Df F value Pr(>F)
## (Intercept) 11919908 1 151773 87 6832.7710 < 2e-16 ***
## kelompok 15874 2 151773 87 4.5495 0.01321 *
## waktu 8280 3 5883 261 122.4521 < 2e-16 ***
## kelompok:waktu 5016 6 5883 261 37.0885 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
##
## Mauchly Tests for Sphericity
##
## Test statistic p-value
## waktu 0.82959 0.0068065
## kelompok:waktu 0.82959 0.0068065
##
##
## Greenhouse-Geisser and Huynh-Feldt Corrections
## for Departure from Sphericity
##
## GG eps Pr(>F[GG])
## waktu 0.89459 < 2.2e-16 ***
## kelompok:waktu 0.89459 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## HF eps Pr(>F[HF])
## waktu 0.9257711 4.721357e-46
## kelompok:waktu 0.9257711 3.546979e-30
aov2$Anova # uji multivariat untuk efek within & interaksi
##
## Type III Repeated Measures MANOVA Tests: Pillai test statistic
## Df test stat approx F num Df den Df Pr(>F)
## (Intercept) 1 0.98743 6832.8 1 87 < 2.2e-16 ***
## kelompok 2 0.09468 4.5 2 87 0.01321 *
## waktu 1 0.77152 95.7 3 85 < 2.2e-16 ***
## kelompok:waktu 2 0.68766 15.0 6 172 8.669e-14 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Versi rstatix (hasil identik; format ringkas untuk laporan)
aov2_rs <- anova_test(data = dat_long, dv = gdp, wid = id,
between = kelompok, within = waktu, effect.size = "pes",
type = 3)
get_anova_table(aov2_rs, correction = "GG")
## ANOVA Table (type III tests)
##
## Effect DFn DFd F p p<.05 pes
## 1 kelompok 2.00 87.00 4.550 1.30e-02 * 0.095
## 2 waktu 2.68 233.49 122.452 1.36e-44 * 0.585
## 3 kelompok:waktu 5.37 233.49 37.088 3.04e-29 * 0.460
# Ukuran efek
eta_squared(aov2, partial = TRUE)
## # Effect Size for ANOVA (Type III)
##
## Parameter | Eta2 (partial) | 95% CI
## ----------------------------------------------
## kelompok | 0.09 | [0.01, 1.00]
## waktu | 0.58 | [0.52, 1.00]
## kelompok:waktu | 0.46 | [0.38, 1.00]
##
## - One-sided CIs: upper bound fixed at [1.00].
omega_squared(aov2, partial = TRUE)
## # Effect Size for ANOVA (Type III)
##
## Parameter | Omega2 (partial) | 95% CI
## ------------------------------------------------
## kelompok | 0.07 | [0.00, 1.00]
## waktu | 0.05 | [0.01, 1.00]
## kelompok:waktu | 0.03 | [0.00, 1.00]
##
## - One-sided CIs: upper bound fixed at [1.00].
# Plot interaksi dari model
afex_plot(aov2, x = "waktu", trace = "kelompok", error = "within",
mapping = c("colour", "shape", "linetype")) +
labs(y = "GDP (mg/dL)", x = "Waktu")
## Warning: Panel(s) show a mixed within-between-design.
## Error bars do not allow comparisons across all means.
## Suppress error bars with: error = "none"

## 4c. Efek sederhana (karena interaksi signifikan) ---------------------------
em2 <- emmeans(aov2, ~ waktu | kelompok)
# Efek WAKTU di dalam tiap kelompok (uji F gabungan per kelompok)
joint_tests(aov2, by = "kelompok")
## kelompok = Kontrol:
## model term df1 df2 F.ratio p.value
## waktu 3 87 0.713 0.5467
##
## kelompok = Diet:
## model term df1 df2 F.ratio p.value
## waktu 3 87 60.387 <0.0001
##
## kelompok = Diet+Olahraga:
## model term df1 df2 F.ratio p.value
## waktu 3 87 92.279 <0.0001
# Efek KELOMPOK pada tiap waktu
joint_tests(aov2, by = "waktu")
## waktu = W0:
## model term df1 df2 F.ratio p.value
## kelompok 2 87 1.812 0.1694
##
## waktu = W1:
## model term df1 df2 F.ratio p.value
## kelompok 2 87 2.839 0.0639
##
## waktu = W2:
## model term df1 df2 F.ratio p.value
## kelompok 2 87 7.154 0.0013
##
## waktu = W3:
## model term df1 df2 F.ratio p.value
## kelompok 2 87 10.905 <0.0001
# Post hoc: tiap waktu vs baseline di dalam tiap kelompok
contrast(em2, "trt.vs.ctrl", ref = 1, adjust = "holm")
## kelompok = Kontrol:
## contrast estimate SE df t.ratio p.value
## W1 - W0 -0.900 0.98 87 -0.918 1.0000
## W2 - W0 0.233 1.29 87 0.181 1.0000
## W3 - W0 0.800 1.39 87 0.577 1.0000
##
## kelompok = Diet:
## contrast estimate SE df t.ratio p.value
## W1 - W0 -9.133 0.98 87 -9.317 <0.0001
## W2 - W0 -12.300 1.29 87 -9.555 <0.0001
## W3 - W0 -17.700 1.39 87 -12.764 <0.0001
##
## kelompok = Diet+Olahraga:
## contrast estimate SE df t.ratio p.value
## W1 - W0 -9.267 0.98 87 -9.453 <0.0001
## W2 - W0 -16.433 1.29 87 -12.766 <0.0001
## W3 - W0 -22.267 1.39 87 -16.057 <0.0001
##
## P value adjustment: holm method for 3 tests
# Post hoc: perbandingan antarkelompok pada tiap waktu
em2b <- emmeans(aov2, ~ kelompok | waktu)
pairs(em2b, adjust = "tukey") # Tukey per waktu
## waktu = W0:
## contrast estimate SE df t.ratio p.value
## Kontrol - Diet -7.07 5.53 87 -1.277 0.4119
## Kontrol - (Diet+Olahraga) 3.23 5.53 87 0.584 0.8289
## Diet - (Diet+Olahraga) 10.30 5.53 87 1.861 0.1562
##
## waktu = W1:
## contrast estimate SE df t.ratio p.value
## Kontrol - Diet 1.17 5.36 87 0.218 0.9742
## Kontrol - (Diet+Olahraga) 11.60 5.36 87 2.164 0.0833
## Diet - (Diet+Olahraga) 10.43 5.36 87 1.946 0.1320
##
## waktu = W2:
## contrast estimate SE df t.ratio p.value
## Kontrol - Diet 5.47 5.44 87 1.006 0.5753
## Kontrol - (Diet+Olahraga) 19.90 5.44 87 3.661 0.0012
## Diet - (Diet+Olahraga) 14.43 5.44 87 2.655 0.0253
##
## waktu = W3:
## contrast estimate SE df t.ratio p.value
## Kontrol - Diet 11.43 5.65 87 2.024 0.1124
## Kontrol - (Diet+Olahraga) 26.30 5.65 87 4.657 <0.0001
## Diet - (Diet+Olahraga) 14.87 5.65 87 2.632 0.0268
##
## P value adjustment: tukey method for comparing a family of 3 estimates
## 4d. Kontras interaksi -------------------------------------------------------
# Apakah penurunan (W3 - W0) berbeda antarkelompok? -- inti pertanyaan uji klinis
em_full <- emmeans(aov2, ~ waktu * kelompok)
contrast(em_full, interaction = list(waktu = list("W3-W0" = c(-1, 0, 0, 1)),
kelompok = "pairwise"),
adjust = "holm")
## waktu_custom kelompok_pairwise estimate SE df t.ratio p.value
## W3-W0 Kontrol - Diet 18.50 1.96 87 9.433 <0.0001
## W3-W0 Kontrol - (Diet+Olahraga) 23.07 1.96 87 11.762 <0.0001
## W3-W0 Diet - (Diet+Olahraga) 4.57 1.96 87 2.329 0.0222
##
## P value adjustment: holm method for 3 tests
# Tren linear per kelompok dan perbandingannya
contrast(em2, "poly")[c(1, 4, 7)]
## contrast kelompok estimate SE df t.ratio p.value
## linear Kontrol 3.53 4.61 87 0.767 0.4453
## linear Diet -56.27 4.61 87 -12.210 <0.0001
## linear Diet+Olahraga -73.97 4.61 87 -16.051 <0.0001
tren_int <- summary(contrast(em_full, interaction = c(waktu = "poly", kelompok = "pairwise"),
adjust = "none"))
tren_lin <- subset(tren_int, waktu_poly == "linear") # apakah laju penurunan linear berbeda?
tren_lin$p.holm <- p.adjust(tren_lin$p.value, "holm") # koreksi Holm untuk 3 perbandingan
tren_lin
## waktu_poly kelompok_pairwise estimate SE df t.ratio
## 1 linear Kontrol - Diet 59.8 6.516957 87 9.176062
## 4 linear Kontrol - (Diet+Olahraga) 77.5 6.516957 87 11.892053
## 7 linear Diet - (Diet+Olahraga) 17.7 6.516957 87 2.715992
## p.value p.holm
## 1 1.957867e-14 3.915734e-14
## 4 6.254122e-20 1.876237e-19
## 7 7.969973e-03 7.969973e-03
# 5. PEMBANDING: LINEAR MIXED MODEL (LMM)
# Tidak mensyaratkan sfierisitas, menampung data hilang (MAR) dan waktu
# pengukuran yang tidak seragam.
lmm1 <- lmer(gdp ~ kelompok * waktu + (1 | id), data = dat_long, REML = TRUE)
lmm2 <- lmer(gdp ~ kelompok * waktu + (1 + minggu | id), data = dat_long, REML = TRUE)
## Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, : Model failed to converge with max|grad| = 0.00726823 (tol = 0.002, component 1)
## See ?lme4::convergence and ?lme4::troubleshooting.
anova(lmm1, lmm2, refit = FALSE) # uji rasio kemungkinan struktur acak
## Data: dat_long
## Models:
## lmm1: gdp ~ kelompok * waktu + (1 | id)
## lmm2: gdp ~ kelompok * waktu + (1 + minggu | id)
## npar AIC BIC logLik -2*log(L) Chisq Df Pr(>Chisq)
## lmm1 14 2536.0 2590.4 -1254.0 2508.0
## lmm2 16 2529.6 2591.7 -1248.8 2497.6 10.401 2 0.005515 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
anova(lmm2, ddf = "Kenward-Roger") # uji F tipe III efek tetap
## Type III Analysis of Variance Table with Kenward-Roger's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## kelompok 162.3 81.17 2 87.00 4.5428 0.01329 *
## waktu 4756.3 1585.45 3 185.37 88.3207 < 2e-16 ***
## kelompok:waktu 2843.6 473.94 6 206.40 26.3722 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
performance::icc(lmm1) # korelasi intrakelas
## # Intraclass Correlation Coefficient
##
## Adjusted ICC: 0.950
## Unadjusted ICC: 0.806
# Diagnostik residual LMM (normalitas & homogenitas)
par(mfrow = c(1, 3))
qqnorm(resid(lmm2), main = "Q-Q residual"); qqline(resid(lmm2))
qqnorm(ranef(lmm2)$id[, 1], main = "Q-Q intersep acak"); qqline(ranef(lmm2)$id[, 1])
plot(fitted(lmm2), resid(lmm2), xlab = "Nilai prediksi", ylab = "Residual",
main = "Residual vs prediksi"); abline(h = 0, lty = 2)

par(mfrow = c(1, 1))
# (paket 'see' + performance::check_model(lmm2) memberi panel diagnostik lengkap)
# Simulasi 30 nilai hilang (MCAR, +/- 11% pengukuran pasca-baseline)
# untuk menunjukkan keunggulan LMM
set.seed(1)
dat_miss <- dat_long
dat_miss$gdp[sample(which(dat_miss$waktu != "W0"), 30)] <- NA
lmm_miss <- lmer(gdp ~ kelompok * waktu + (1 + minggu | id), data = dat_miss,
control = lmerControl(optimizer = "bobyqa"))
anova(lmm_miss, ddf = "Kenward-Roger") # semua pasien tetap dianalisis
## Type III Analysis of Variance Table with Kenward-Roger's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## kelompok 144.4 72.21 2 87.00 4.6196 0.0124 *
## waktu 3813.8 1271.26 3 167.67 80.9348 <2e-16 ***
## kelompok:waktu 2240.8 373.47 6 185.23 23.7495 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# RM ANOVA akan membuang seluruh pasien yang punya >= 1 nilai hilang:
n_distinct(dat_miss$id[is.na(dat_miss$gdp)])
## [1] 25
# 6. SIMPAN DATA & SESSION INFO
write.csv(dat_wide, "data_gdp_wide.csv", row.names = FALSE)
write.csv(dat_long, "data_gdp_long.csv", row.names = FALSE)
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Ventura 13.3.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Asia/Makassar
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] lmerTest_3.2-1 effectsize_1.0.3 car_3.1-5 carData_3.0-6
## [5] rstatix_1.1.0 emmeans_2.0.4 afex_1.5-1 lme4_2.0-6
## [9] Matrix_1.7-5 ggplot2_4.0.3 tidyr_1.3.2 dplyr_1.2.1
## [13] readxl_1.5.0
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 xfun_0.60 bslib_0.12.0
## [4] bayestestR_0.19.0 insight_1.5.4 lattice_0.22-9
## [7] numDeriv_2016.8-1.1 vctrs_0.7.3 tools_4.6.1
## [10] Rdpack_2.6.6 generics_0.1.4 pbkrtest_0.5.5
## [13] datawizard_1.4.0 parallel_4.6.1 tibble_3.3.1
## [16] pkgconfig_2.0.3 WRS2_1.1-7 RColorBrewer_1.1-3
## [19] S7_0.2.2 lifecycle_1.0.5 compiler_4.6.1
## [22] farver_2.1.2 stringr_1.6.0 htmltools_0.5.9
## [25] sass_0.4.10 yaml_2.3.12 Formula_1.2-6
## [28] ggpubr_1.0.0 pillar_1.11.1 nloptr_2.2.1
## [31] jquerylib_0.1.4 MASS_7.3-65 cachem_1.1.0
## [34] reformulas_0.4.4 boot_1.3-32 abind_1.4-8
## [37] nlme_3.1-169 tidyselect_1.2.1 digest_0.6.39
## [40] performance_0.18.2 mvtnorm_1.4-2 stringi_1.8.9
## [43] reshape2_1.4.5 purrr_1.2.2 labeling_0.4.3
## [46] splines_4.6.1 fastmap_1.2.0 grid_4.6.1
## [49] cli_3.6.6 magrittr_2.0.5 utf8_1.2.6
## [52] broom_1.0.13 withr_3.0.3 scales_1.4.0
## [55] backports_1.5.1 estimability_2.0.0 rmarkdown_2.31
## [58] ggsignif_0.6.4 cellranger_1.1.0 evaluate_1.0.5
## [61] knitr_1.51 parameters_0.29.3 rbibutils_2.4.1
## [64] rlang_1.3.0 Rcpp_1.1.2 glue_1.8.1
## [67] reshape_0.8.10 rstudioapi_0.19.0 minqa_1.2.8
## [70] jsonlite_2.0.0 R6_2.6.1 plyr_1.8.9