# REPEATED MEASURE ANALYSIS DENGAN R
# TUGAS BIOSTATISTIKA INTERMEDIATE
# NAMA  : FERI FEBRIAN ARISTIA
# NIM   : 2611018048
# Rpubs : ferifebrian
# Data simulasi berdasarkan Ding et al. (2024), PLOS Global Public Health
# Desain: 3 treatment x 3 waktu; outcome = berat badan (kg)

# 0. PAKET & PENGATURAN ------------------------------------------------------
# Paket yang belum terpasang akan dipasang otomatis (butuh internet saat pertama kali)
paket <- c("dplyr", "tidyr", "ggplot2", "afex", "emmeans", "rstatix", "car",
           "effectsize", "lme4", "lmerTest", "pbkrtest", "performance", "ggpubr")
baru <- paket[!paket %in% rownames(installed.packages())]
if (length(baru) > 0) install.packages(baru)

suppressPackageStartupMessages({
  library(dplyr); library(tidyr); library(ggplot2); library(afex)
  library(emmeans); library(rstatix); library(car); library(effectsize)
  library(lme4); library(lmerTest); library(performance); library(ggpubr)
})
options(contrasts = c("contr.sum", "contr.poly"))
afex_options(emmeans_model = "multivariate")
theme_set(theme_bw(base_size = 12))

# 1. DATA -------------------------------------------------------------------
# Skrip membaca file CSV di working directory (cek dengan getwd()).
# Kolom yang dibutuhkan: id, treatment, weight_baseline, weight_6m, weight_2y
# Nilai treatment: "Control", "Basic MCP", "Full MCP".
# Jika file CSV tidak ditemukan, data simulasi dibuat otomatis (seed tetap) dan
# disimpan sebagai CSV tersebut, sehingga seluruh skrip tetap bisa dijalankan.
file_data <- "Data_Simulasi_Berat_Badan_MCP_3x3.csv"

if (file.exists(file_data)) {
  dat_wide <- read.csv(file_data, stringsAsFactors = FALSE)
  message("Data dibaca dari: ", file_data)
} else {
  message("File ", file_data, " tidak ditemukan -> membuat data simulasi.")
  set.seed(2024)
  n_per <- 40
  grp   <- c("Control", "Basic MCP", "Full MCP")
  # Perubahan rerata berat badan (kg) terhadap baseline: 6 bulan, 2 tahun
  # (nilai ASUMSI untuk simulasi; ganti dengan data/parameter Anda bila perlu)
  delta <- rbind("Control"   = c(0.2,  0.5),
                 "Basic MCP" = c(-1.5, -1.2),
                 "Full MCP"  = c(-3.0, -2.5))
  dat_wide <- bind_rows(lapply(seq_along(grp), function(g) {
    b0 <- rnorm(n_per, 0, 8)                 # perbedaan berat badan dasar antarpasien
    b1 <- rnorm(n_per, 0, 1.2)               # perbedaan respons antarpasien
    data.frame(
      id              = sprintf("S%03d", (g - 1) * n_per + seq_len(n_per)),
      treatment       = grp[g],
      weight_baseline = round(78 + b0 + rnorm(n_per, 0, 1.0), 1),
      weight_6m       = round(78 + delta[g, 1] + b0 + b1 + rnorm(n_per, 0, 1.0), 1),
      weight_2y       = round(78 + delta[g, 2] + b0 + 1.5 * b1 + rnorm(n_per, 0, 1.2), 1)
    )
  }))
  write.csv(dat_wide, file_data, row.names = FALSE)
}
## Data dibaca dari: Data_Simulasi_Berat_Badan_MCP_3x3.csv
# Pemeriksaan struktur data
kol_perlu <- c("id", "treatment", "weight_baseline", "weight_6m", "weight_2y")
stopifnot(all(kol_perlu %in% names(dat_wide)))
print(colSums(is.na(dat_wide[, kol_perlu])))
##              id       treatment weight_baseline       weight_6m       weight_2y 
##               0               0               0               0               0
dat_wide$treatment <- factor(dat_wide$treatment,
  levels = c("Control", "Basic MCP", "Full MCP"))
stopifnot(!any(is.na(dat_wide$treatment)))   # berhenti jika ada label treatment yang tidak cocok
dat_wide$id <- factor(dat_wide$id)

dat_long <- dat_wide |>
  pivot_longer(c(weight_baseline, weight_6m, weight_2y),
               names_to = "time", values_to = "weight") |>
  mutate(time = factor(time,
    levels = c("weight_baseline", "weight_6m", "weight_2y"),
    labels = c("Baseline", "6 months", "2 years")))

head(dat_wide); head(dat_long)
##     id treatment weight_baseline weight_6m weight_2y
## 1 S001   Control            84.6      84.7      86.9
## 2 S002   Control            82.7      81.6      82.2
## 3 S003   Control            78.0      76.6      78.5
## 4 S004   Control            74.5      78.7      82.2
## 5 S005   Control            87.4      85.3      85.2
## 6 S006   Control            89.4      88.7      88.3
## # A tibble: 6 × 4
##   id    treatment time     weight
##   <fct> <fct>     <fct>     <dbl>
## 1 S001  Control   Baseline   84.6
## 2 S001  Control   6 months   84.7
## 3 S001  Control   2 years    86.9
## 4 S002  Control   Baseline   82.7
## 5 S002  Control   6 months   81.6
## 6 S002  Control   2 years    82.2
# 2. EKSPLORASI DATA ---------------------------------------------------------
desk <- dat_long |> group_by(treatment, time) |>
  get_summary_stats(weight, type = "mean_sd")
print(desk)
## # A tibble: 9 × 6
##   treatment time     variable     n  mean    sd
##   <fct>     <fct>    <fct>    <dbl> <dbl> <dbl>
## 1 Control   Baseline weight      40  76.4  8.58
## 2 Control   6 months weight      40  76.7  8.30
## 3 Control   2 years  weight      40  77.5  8.52
## 4 Basic MCP Baseline weight      40  78.7  6.40
## 5 Basic MCP 6 months weight      40  76.8  6.30
## 6 Basic MCP 2 years  weight      40  77.2  6.28
## 7 Full MCP  Baseline weight      40  79.4  8.50
## 8 Full MCP  6 months weight      40  76.5  8.83
## 9 Full MCP  2 years  weight      40  77.1  8.85
p_profile <- ggplot(dat_long, aes(time, weight, group = treatment, colour = treatment)) +
  stat_summary(fun = mean, geom = "line", linewidth = 1) +
  stat_summary(fun = mean, geom = "point", size = 2.5) +
  labs(x = "Time", y = "Body weight (kg)", colour = "Treatment",
       title = "Mean body weight profile")
print(p_profile)

p_spag <- ggplot(dat_long, aes(time, weight, group = id)) +
  geom_line(alpha = .25) +
  stat_summary(aes(group = treatment), fun = mean, geom = "line",
               colour = "firebrick", linewidth = 1.2) +
  facet_wrap(~ treatment) + labs(x = "Time", y = "Body weight (kg)")
print(p_spag)

# 3. REPEATED MEASURE ANOVA SATU ARAH: FULL MCP ----------------------------
d1 <- droplevels(filter(dat_long, treatment == "Full MCP"))
print(d1 |> group_by(time) |> identify_outliers(weight))
## [1] time       id         treatment  weight     is.outlier is.extreme
## <0 rows> (or 0-length row.names)
print(d1 |> group_by(time) |> shapiro_test(weight))
## # A tibble: 3 × 4
##   time     variable statistic     p
##   <fct>    <chr>        <dbl> <dbl>
## 1 Baseline weight       0.967 0.290
## 2 6 months weight       0.960 0.171
## 3 2 years  weight       0.981 0.726
print(ggpubr::ggqqplot(d1, "weight", facet.by = "time"))

aov1 <- aov_ez(id = "id", dv = "weight", data = d1, within = "time",
  anova_table = list(es = c("ges", "pes"), correction = "GG"))
print(aov1); print(summary(aov1))
## Anova Table (Type 3 tests)
## 
## Response: weight
##   Effect          df  MSE         F  ges  pes p.value
## 1   time 1.61, 62.66 2.31 52.38 *** .021 .573   <.001
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
## 
## Sphericity correction method: GG
## 
## Univariate Type III Repeated-Measures ANOVA Assuming Sphericity
## 
##             Sum Sq num Df Error SS den Df  F value    Pr(>F)    
## (Intercept) 724071      1   8769.3     39 3220.168 < 2.2e-16 ***
## time           194      2    144.7     78   52.382 3.785e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## 
## Mauchly Tests for Sphericity
## 
##      Test statistic   p-value
## time        0.75527 0.0048295
## 
## 
## Greenhouse-Geisser and Huynh-Feldt Corrections
##  for Departure from Sphericity
## 
##       GG eps Pr(>F[GG])    
## time 0.80338  1.257e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##         HF eps  Pr(>F[HF])
## time 0.8326476 5.29292e-13
em1 <- emmeans(aov1, ~ time)
print(pairs(em1, adjust = "holm"))
##  contrast             estimate    SE df t.ratio p.value
##  Baseline - X6.months     2.93 0.277 39  10.575 <0.0001
##  Baseline - X2.years      2.38 0.371 39   6.414 <0.0001
##  X6.months - X2.years    -0.55 0.252 39  -2.184  0.0350
## 
## P value adjustment: holm method for 3 tests
print(friedman_test(d1, weight ~ time | id))
## # A tibble: 1 × 6
##   .y.        n statistic    df        p method       
## * <chr>  <int>     <dbl> <dbl>    <dbl> <chr>        
## 1 weight    40      45.0     2 1.66e-10 Friedman test
# 4. MIXED DESIGN ANOVA: TREATMENT x TIME ----------------------------------
# 4a. Asumsi
print(dat_long |> group_by(treatment, time) |> shapiro_test(weight))
## # A tibble: 9 × 5
##   treatment time     variable statistic      p
##   <fct>     <fct>    <chr>        <dbl>  <dbl>
## 1 Control   Baseline weight       0.958 0.141 
## 2 Control   6 months weight       0.960 0.168 
## 3 Control   2 years  weight       0.948 0.0629
## 4 Basic MCP Baseline weight       0.935 0.0242
## 5 Basic MCP 6 months weight       0.933 0.0209
## 6 Basic MCP 2 years  weight       0.931 0.0168
## 7 Full MCP  Baseline weight       0.967 0.290 
## 8 Full MCP  6 months weight       0.960 0.171 
## 9 Full MCP  2 years  weight       0.981 0.726
print(dat_long |> group_by(time) |> levene_test(weight ~ treatment))
## # A tibble: 3 × 5
##   time       df1   df2 statistic      p
##   <fct>    <int> <int>     <dbl>  <dbl>
## 1 Baseline     2   117      2.73 0.0693
## 2 6 months     2   117      2.55 0.0824
## 3 2 years      2   117      2.84 0.0624
print(box_m(dat_wide[, c("weight_baseline", "weight_6m", "weight_2y")], dat_wide$treatment))
## # A tibble: 1 × 4
##   statistic p.value parameter method                                            
##       <dbl>   <dbl>     <dbl> <chr>                                             
## 1      8.82   0.718        12 Box's M-test for Homogeneity of Covariance Matric…
# 4b. Mixed RM ANOVA
aov2 <- aov_ez(id = "id", dv = "weight", data = dat_long,
  between = "treatment", within = "time",
  anova_table = list(es = c("ges", "pes"), correction = "GG"))
print(aov2); print(summary(aov2))
## Anova Table (Type 3 tests)
## 
## Response: weight
##           Effect           df    MSE         F  ges  pes p.value
## 1      treatment       2, 117 184.24      0.13 .002 .002    .875
## 2           time 1.61, 188.54   2.26 37.40 *** .006 .242   <.001
## 3 treatment:time 3.22, 188.54   2.26 22.21 *** .007 .275   <.001
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
## 
## Sphericity correction method: GG
## 
## Univariate Type III Repeated-Measures ANOVA Assuming Sphericity
## 
##                 Sum Sq num Df Error SS den Df    F value    Pr(>F)    
## (Intercept)    2154631      1  21555.8    117 11694.8496 < 2.2e-16 ***
## treatment           49      2  21555.8    117     0.1337     0.875    
## time               136      2    426.8    234    37.4044 8.027e-15 ***
## treatment:time     162      4    426.8    234    22.2104 1.471e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## 
## Mauchly Tests for Sphericity
## 
##                Test statistic    p-value
## time                  0.75886 1.1206e-07
## treatment:time        0.75886 1.1206e-07
## 
## 
## Greenhouse-Geisser and Huynh-Feldt Corrections
##  for Departure from Sphericity
## 
##                 GG eps Pr(>F[GG])    
## time           0.80571  2.053e-12 ***
## treatment:time 0.80571  5.573e-13 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##                   HF eps   Pr(>F[HF])
## time           0.8152804 1.562204e-12
## treatment:time 0.8152804 4.158068e-13
print(eta_squared(aov2, partial = TRUE))
## # Effect Size for ANOVA (Type III)
## 
## Parameter      | Eta2 (partial) |       95% CI
## ----------------------------------------------
## treatment      |       2.28e-03 | [0.00, 1.00]
## time           |           0.24 | [0.17, 1.00]
## treatment:time |           0.28 | [0.19, 1.00]
## 
## - One-sided CIs: upper bound fixed at [1.00].
# 4c. Simple effects dan post hoc
print(joint_tests(aov2, by = "treatment"))
## treatment = Control:
##  model term df1 df2 F.ratio p.value
##  time         2 117   5.967  0.0034
## 
## treatment = Basic MCP:
##  model term df1 df2 F.ratio p.value
##  time         2 117  25.292 <0.0001
## 
## treatment = Full MCP:
##  model term df1 df2 F.ratio p.value
##  time         2 117  60.238 <0.0001
print(joint_tests(aov2, by = "time"))
## time = Baseline:
##  model term df1 df2 F.ratio p.value
##  treatment    2 117   1.656  0.1954
## 
## time = X6.months:
##  model term df1 df2 F.ratio p.value
##  treatment    2 117   0.012  0.9880
## 
## time = X2.years:
##  model term df1 df2 F.ratio p.value
##  treatment    2 117   0.029  0.9713
em2 <- emmeans(aov2, ~ time | treatment)
print(pairs(em2, adjust = "holm"))
## treatment = Control:
##  contrast             estimate    SE  df t.ratio p.value
##  Baseline - X6.months   -0.312 0.272 117  -1.150  0.2524
##  Baseline - X2.years    -1.133 0.368 117  -3.075  0.0052
##  X6.months - X2.years   -0.820 0.253 117  -3.237  0.0047
## 
## treatment = Basic MCP:
##  contrast             estimate    SE  df t.ratio p.value
##  Baseline - X6.months    1.877 0.272 117   6.911 <0.0001
##  Baseline - X2.years     1.423 0.368 117   3.863  0.0004
##  X6.months - X2.years   -0.455 0.253 117  -1.796  0.0750
## 
## treatment = Full MCP:
##  contrast             estimate    SE  df t.ratio p.value
##  Baseline - X6.months    2.933 0.272 117  10.794 <0.0001
##  Baseline - X2.years     2.382 0.368 117   6.469 <0.0001
##  X6.months - X2.years   -0.550 0.253 117  -2.171  0.0319
## 
## P value adjustment: holm method for 3 tests
# 4d. Kontras perubahan dari baseline antarkelompok
dat_change <- dat_wide |>
  mutate(change_6m = weight_6m - weight_baseline,
         change_2y = weight_2y - weight_baseline)
print(anova_test(data = dat_change, dv = change_6m, between = treatment))
## ANOVA Table (type II tests)
## 
##      Effect DFn DFd      F        p p<.05   ges
## 1 treatment   2 117 37.124 3.28e-13     * 0.388
print(anova_test(data = dat_change, dv = change_2y, between = treatment))
## ANOVA Table (type II tests)
## 
##      Effect DFn DFd      F        p p<.05   ges
## 1 treatment   2 117 24.337 1.45e-09     * 0.294
print(pairwise_t_test(dat_change, change_6m ~ treatment, p.adjust.method = "holm"))
## # A tibble: 3 × 9
##   .y.       group1    group2    n1    n2        p p.signif    p.adj p.adj.signif
## * <chr>     <chr>     <chr>  <int> <int>    <dbl> <chr>       <dbl> <chr>       
## 1 change_6m Control   Basic…    40    40 9.11e- 8 ****     1.82e- 7 ****        
## 2 change_6m Control   Full …    40    40 9.48e-14 ****     2.84e-13 ****        
## 3 change_6m Basic MCP Full …    40    40 6.99e- 3 **       6.99e- 3 **
print(pairwise_t_test(dat_change, change_2y ~ treatment, p.adjust.method = "holm"))
## # A tibble: 3 × 9
##   .y.       group1    group2     n1    n2        p p.signif   p.adj p.adj.signif
## * <chr>     <chr>     <chr>   <int> <int>    <dbl> <chr>      <dbl> <chr>       
## 1 change_2y Control   Basic …    40    40 3.04e- 6 ****     6.07e-6 ****        
## 2 change_2y Control   Full M…    40    40 6.04e-10 ****     1.81e-9 ****        
## 3 change_2y Basic MCP Full M…    40    40 6.78e- 2 ns       6.78e-2 ns
# 5. PEMBANDING: LINEAR MIXED MODEL -----------------------------------------
lmm <- lmer(weight ~ treatment * time + (1 | id), data = dat_long, REML = TRUE)
print(anova(lmm, ddf = "Kenward-Roger"))
## Type III Analysis of Variance Table with Kenward-Roger's method
##                 Sum Sq Mean Sq NumDF DenDF F value    Pr(>F)    
## treatment        0.488   0.244     2   117  0.1337     0.875    
## time           136.446  68.223     2   234 37.4044 8.027e-15 ***
## treatment:time 162.041  40.510     4   234 22.2104 1.471e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
print(summary(lmm))
## Linear mixed model fit by REML. t-tests use Satterthwaite's method [
## lmerModLmerTest]
## Formula: weight ~ treatment * time + (1 | id)
##    Data: dat_long
## 
## REML criterion at convergence: 1793.4
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.56858 -0.45134  0.01732  0.48765  2.37786 
## 
## Random effects:
##  Groups   Name        Variance Std.Dev.
##  id       (Intercept) 60.805   7.798   
##  Residual              1.824   1.351   
## Number of obs: 360, groups:  id, 120
## 
## Fixed effects:
##                  Estimate Std. Error       df t value Pr(>|t|)    
## (Intercept)       77.3633     0.7154 117.0000 108.143  < 2e-16 ***
## treatment1        -0.5192     1.0117 117.0000  -0.513 0.608806    
## treatment2         0.2042     1.0117 117.0000   0.202 0.840420    
## time1              0.7967     0.1007 234.0000   7.914 9.91e-14 ***
## time2             -0.7025     0.1007 234.0000  -6.979 3.04e-11 ***
## treatment1:time1  -1.2783     0.1424 234.0000  -8.980  < 2e-16 ***
## treatment2:time1   0.3033     0.1424 234.0000   2.131 0.034151 *  
## treatment1:time2   0.5333     0.1424 234.0000   3.746 0.000226 ***
## treatment2:time2  -0.0750     0.1424 234.0000  -0.527 0.598804    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##             (Intr) trtmn1 trtmn2 time1  time2  trt1:1 trt2:1 trt1:2
## treatment1   0.000                                                 
## treatment2   0.000 -0.500                                          
## time1        0.000  0.000  0.000                                   
## time2        0.000  0.000  0.000 -0.500                            
## trtmnt1:tm1  0.000  0.000  0.000  0.000  0.000                     
## trtmnt2:tm1  0.000  0.000  0.000  0.000  0.000 -0.500              
## trtmnt1:tm2  0.000  0.000  0.000  0.000  0.000 -0.500  0.250       
## trtmnt2:tm2  0.000  0.000  0.000  0.000  0.000  0.250 -0.500 -0.500
print(performance::icc(lmm))
## # Intraclass Correlation Coefficient
## 
##     Adjusted ICC: 0.971
##   Unadjusted ICC: 0.956
# 6. SIMPAN RINGKASAN --------------------------------------------------------
write.csv(desk, "Ringkasan_Deskriptif_MCP.csv", row.names = FALSE)
message("Selesai. Ringkasan disimpan di: ", file.path(getwd(), "Ringkasan_Deskriptif_MCP.csv"))
## Selesai. Ringkasan disimpan di: C:/Users/kesba/Downloads/FERI/ARS/BIOSTATISTIKA/Feri Febrian Arisrtia/Feri Febrian Arisrtia/Ringkasan_Deskriptif_MCP.csv