# =============================================================================
# Nama              : Syalmitha Auralia Nur
# NIM               : 2611018004
# Sumber Rujukan    : Rossen et al. (2021)
# Link DOI          : https://doi.org/10.1186/s12966-021-01193-w
# REPEATED MEASURE ANALYSIS DENGAN R
# Intervensi Pemantauan Langkah terhadap Kadar HbA1c
#
# DATA RANCANGAN
# Kelompok   : Kontrol, Langkah, Langkah+Konseling
# Responden  : 90 orang (30 per kelompok)
# Pengukuran : M0, M6, M12, M18, M24
# Satuan     : HbA1c (mmol/mol)
# =============================================================================
# 0. PAKET & PENGATURAN
# Instal sekali apabila ada paket yang belum tersedia:
# install.packages(c("dplyr", "tidyr", "ggplot2", "afex",
#                    "emmeans", "rstatix", "car", "effectsize",
#                    "lme4", "lmerTest", "performance", "ggpubr"))

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))


# Pengaturan grafik R Markdown untuk output Word

if (requireNamespace("knitr", quietly = TRUE)) {
  knitr::opts_chunk$set(
    dev = "jpeg",
    fig.width = 8,
    fig.height = 6,
    dpi = 160,
    fig.path = "figure-hba1c/",
    echo = TRUE,
    message = FALSE
  )
}


# Lokasi data

setwd("C:/Users/HP/Downloads")

getwd()
## [1] "C:/Users/HP/Downloads"
list.files(pattern = "\\.csv$")
## [1] "data_balita 2.csv"                                  
## [2] "data_balita.csv"                                    
## [3] "data_hba1c_diabetes_long.csv"                       
## [4] "data_hba1c_diabetes_wide.csv"                       
## [5] "data_tds_hipertensi_long.csv"                       
## [6] "data_tds_hipertensi_wide.csv"                       
## [7] "mahasiswa-S2-Kesehatan-Masyarakat-angkatan-2026.csv"
# Folder hasil analisis

folder_hasil <- "C:/Users/HP/Downloads/hasil_analisis_HbA1c"

if (!dir.exists(folder_hasil)) {
  dir.create(
    folder_hasil,
    recursive = TRUE,
    showWarnings = FALSE
  )
}

stopifnot(dir.exists(folder_hasil))
# 1. MEMANGGIL DATA (FORMAT WIDE DAN LONG)
n_per <- 30

kel_lab <- c(
  "Kontrol",
  "Langkah",
  "Langkah+Konseling"
)

bulan <- c(0, 6, 12, 18, 24)


# Memanggil data wide

file_wide <- "C:/Users/HP/Downloads/data_hba1c_diabetes_wide.csv"

if (!file.exists(file_wide)) {
  stop("File CSV tidak ditemukan. Periksa nama file di Downloads.")
}


# Menentukan pemisah CSV

baris_awal <- readLines(
  file_wide,
  n = 1,
  warn = FALSE
)

pemisah <- if (
  grepl(";", baris_awal, fixed = TRUE)
) ";" else ","


dat_wide <- read.table(
  file_wide,
  header = TRUE,
  sep = pemisah,
  dec = ".",
  fileEncoding = "UTF-8-BOM",
  stringsAsFactors = FALSE,
  check.names = FALSE
)


# Pengaturan tipe data

dat_wide$id <- factor(dat_wide$id)

dat_wide$kelompok <- factor(
  dat_wide$kelompok,
  levels = kel_lab
)

dat_wide$jk <- factor(
  dat_wide$jk,
  levels = c("L", "P")
)

dat_wide$diagnosis <- factor(
  dat_wide$diagnosis,
  levels = c("Prediabetes", "DM tipe 2")
)

kolom_ukur <- paste0("HbA1c_M", bulan)

dat_wide[kolom_ukur] <- lapply(
  dat_wide[kolom_ukur],
  as.numeric
)


# Mengubah data wide menjadi long

dat_long <- dat_wide |>
  pivot_longer(
    all_of(kolom_ukur),
    names_to = "waktu",
    values_to = "hba1c"
  ) |>
  mutate(
    waktu = factor(
      waktu,
      levels = kolom_ukur,
      labels = paste0("M", bulan)
    ),
    bulan = as.numeric(
      sub("M", "", as.character(waktu))
    ),
    bulan6 = bulan / 6
  )


# Pemeriksaan struktur data

stopifnot(
  nrow(dat_wide) == 90,
  nrow(dat_long) == 450,
  n_distinct(dat_long$id) == 90,
  all(table(dat_long$id) == 5),
  !anyNA(dat_long$hba1c),
  all(table(dat_wide$kelompok) == 30)
)

print(head(dat_wide))
##     id kelompok usia jk diagnosis HbA1c_M0 HbA1c_M6 HbA1c_M12 HbA1c_M18
## 1 P001  Kontrol   60  P DM tipe 2     66.9     62.5      61.0      60.9
## 2 P002  Kontrol   76  L DM tipe 2     55.0     53.3      59.7      55.7
## 3 P003  Kontrol   68  P DM tipe 2     63.6     51.9      55.4      52.7
## 4 P004  Kontrol   64  L DM tipe 2     43.0     51.5      57.3      46.8
## 5 P005  Kontrol   72  P DM tipe 2     56.4     47.0      52.6      52.9
## 6 P006  Kontrol   54  P DM tipe 2     55.3     56.7      58.8      57.6
##   HbA1c_M24
## 1      70.4
## 2      58.7
## 3      55.4
## 4      50.2
## 5      54.9
## 6      63.0
print(head(dat_long))
## # A tibble: 6 × 9
##   id    kelompok  usia jk    diagnosis waktu hba1c bulan bulan6
##   <fct> <fct>    <int> <fct> <fct>     <fct> <dbl> <dbl>  <dbl>
## 1 P001  Kontrol     60 P     DM tipe 2 M0     66.9     0      0
## 2 P001  Kontrol     60 P     DM tipe 2 M6     62.5     6      1
## 3 P001  Kontrol     60 P     DM tipe 2 M12    61      12      2
## 4 P001  Kontrol     60 P     DM tipe 2 M18    60.9    18      3
## 5 P001  Kontrol     60 P     DM tipe 2 M24    70.4    24      4
## 6 P002  Kontrol     76 L     DM tipe 2 M0     55       0      0
str(dat_long)
## tibble [450 × 9] (S3: tbl_df/tbl/data.frame)
##  $ id       : Factor w/ 90 levels "P001","P002",..: 1 1 1 1 1 2 2 2 2 2 ...
##  $ kelompok : Factor w/ 3 levels "Kontrol","Langkah",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ usia     : int [1:450] 60 60 60 60 60 76 76 76 76 76 ...
##  $ jk       : Factor w/ 2 levels "L","P": 2 2 2 2 2 1 1 1 1 1 ...
##  $ diagnosis: Factor w/ 2 levels "Prediabetes",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ waktu    : Factor w/ 5 levels "M0","M6","M12",..: 1 2 3 4 5 1 2 3 4 5 ...
##  $ hba1c    : num [1:450] 66.9 62.5 61 60.9 70.4 55 53.3 59.7 55.7 58.7 ...
##  $ bulan    : num [1:450] 0 6 12 18 24 0 6 12 18 24 ...
##  $ bulan6   : num [1:450] 0 1 2 3 4 0 1 2 3 4 ...
print(table(dat_long$kelompok, dat_long$waktu))
##                    
##                     M0 M6 M12 M18 M24
##   Kontrol           30 30  30  30  30
##   Langkah           30 30  30  30  30
##   Langkah+Konseling 30 30  30  30  30
# 2. EKSPLORASI DATA
# Statistik deskriptif

desk <- dat_long |>
  group_by(kelompok, waktu) |>
  get_summary_stats(
    hba1c,
    type = "mean_sd"
  )

print(desk)
## # A tibble: 15 × 6
##    kelompok          waktu variable     n  mean    sd
##    <fct>             <fct> <fct>    <dbl> <dbl> <dbl>
##  1 Kontrol           M0    hba1c       30  50.5 10.8 
##  2 Kontrol           M6    hba1c       30  50.4  9.41
##  3 Kontrol           M12   hba1c       30  51.9 10.6 
##  4 Kontrol           M18   hba1c       30  51.6 10.0 
##  5 Kontrol           M24   hba1c       30  52.6 11.2 
##  6 Langkah           M0    hba1c       30  50.3 10.5 
##  7 Langkah           M6    hba1c       30  51.4  9.11
##  8 Langkah           M12   hba1c       30  52.9  9.79
##  9 Langkah           M18   hba1c       30  53.0 10.1 
## 10 Langkah           M24   hba1c       30  57.1  9.89
## 11 Langkah+Konseling M0    hba1c       30  50.0  9.27
## 12 Langkah+Konseling M6    hba1c       30  51.2 10.3 
## 13 Langkah+Konseling M12   hba1c       30  52.6  9.74
## 14 Langkah+Konseling M18   hba1c       30  53.6 11.2 
## 15 Langkah+Konseling M24   hba1c       30  53.1 10.8
# Matriks kovarians dan korelasi antarwaktu

S <- cov(dat_wide[, kolom_ukur])

R <- cor(dat_wide[, kolom_ukur])

print(round(S, 1))
##           HbA1c_M0 HbA1c_M6 HbA1c_M12 HbA1c_M18 HbA1c_M24
## HbA1c_M0     102.1     85.3      87.8      90.6      86.8
## HbA1c_M6      85.3     90.7      85.9      89.6      88.7
## HbA1c_M12     87.8     85.9      98.7      95.2      95.8
## HbA1c_M18     90.6     89.6      95.2     107.8      98.7
## HbA1c_M24     86.8     88.7      95.8      98.7     114.7
print(round(R, 2))
##           HbA1c_M0 HbA1c_M6 HbA1c_M12 HbA1c_M18 HbA1c_M24
## HbA1c_M0      1.00     0.89      0.87      0.86      0.80
## HbA1c_M6      0.89     1.00      0.91      0.91      0.87
## HbA1c_M12     0.87     0.91      1.00      0.92      0.90
## HbA1c_M18     0.86     0.91      0.92      1.00      0.89
## HbA1c_M24     0.80     0.87      0.90      0.89      1.00
# Varians selisih antarpasangan waktu

pasangan <- combn(kolom_ukur, 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 = " - "
)

print(round(var_selisih, 1))
##   HbA1c_M0 - HbA1c_M6  HbA1c_M0 - HbA1c_M12  HbA1c_M0 - HbA1c_M18 
##                  22.2                  25.2                  28.6 
##  HbA1c_M0 - HbA1c_M24  HbA1c_M6 - HbA1c_M12  HbA1c_M6 - HbA1c_M18 
##                  43.3                  17.7                  19.3 
##  HbA1c_M6 - HbA1c_M24 HbA1c_M12 - HbA1c_M18 HbA1c_M12 - HbA1c_M24 
##                  28.0                  16.0                  21.8 
## HbA1c_M18 - HbA1c_M24 
##                  25.0
# Profile plot: rerata +/- 95% CI

rata_ci95 <- function(x) {
  
  m <- mean(x, na.rm = TRUE)
  
  n <- sum(!is.na(x))
  
  se <- sd(x, na.rm = TRUE) / sqrt(n)
  
  margin <- qt(0.975, df = n - 1) * se
  
  data.frame(
    y = m,
    ymin = m - margin,
    ymax = m + margin
  )
}


p_profil <- ggplot(
  dat_long,
  aes(
    bulan,
    hba1c,
    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 = rata_ci95,
    geom = "errorbar",
    width = 0.6
  ) +
  scale_x_continuous(breaks = bulan) +
  labs(
    x = "Bulan ke-",
    y = "HbA1c (mmol/mol)",
    colour = "Kelompok",
    title = "Profil Rerata HbA1c (95% CI)"
  ) +
  theme(legend.position = "bottom")

print(p_profil)

# Spaghetti plot: lintasan tiap responden

p_spag <- ggplot(
  dat_long,
  aes(
    bulan,
    hba1c,
    group = id
  )
) +
  geom_line(alpha = 0.3) +
  stat_summary(
    aes(group = kelompok),
    fun = mean,
    geom = "line",
    colour = "firebrick",
    linewidth = 1.2
  ) +
  facet_wrap(~ kelompok) +
  scale_x_continuous(breaks = bulan) +
  labs(
    x = "Bulan ke-",
    y = "HbA1c (mmol/mol)",
    title = "Lintasan Individu dan Rerata Kelompok"
  )

print(p_spag)

# 3. REPEATED MEASURE ANOVA SATU ARAH
#    WITHIN-SUBJECT: WAKTU
# Perubahan HbA1c selama 24 bulan pada kelompok Langkah+Konseling

d1 <- droplevels(
  filter(
    dat_long,
    kelompok == "Langkah+Konseling"
  )
)

d1w <- filter(
  dat_wide,
  kelompok == "Langkah+Konseling"
)


# 3a. UJI ASUMSI
# (i) Outlier per waktu

print(
  d1 |>
    group_by(waktu) |>
    identify_outliers(hba1c)
)
##  [1] waktu      id         kelompok   usia       jk         diagnosis 
##  [7] hba1c      bulan      bulan6     is.outlier is.extreme
## <0 rows> (or 0-length row.names)
# (ii) Normalitas Shapiro-Wilk

print(
  d1 |>
    group_by(waktu) |>
    shapiro_test(hba1c)
)
## # A tibble: 5 × 4
##   waktu variable statistic        p
##   <fct> <chr>        <dbl>    <dbl>
## 1 M0    hba1c        0.849 0.000577
## 2 M6    hba1c        0.922 0.0309  
## 3 M12   hba1c        0.930 0.0485  
## 4 M18   hba1c        0.923 0.0317  
## 5 M24   hba1c        0.943 0.111
# Q-Q plot

p_qq1 <- ggpubr::ggqqplot(
  d1,
  "hba1c",
  facet.by = "waktu"
)

print(p_qq1)

# (iii) Sferisitas Mauchly

aov1_rs <- anova_test(
  data = d1,
  dv = hba1c,
  wid = id,
  within = waktu,
  effect.size = "pes"
)

print(aov1_rs)
## ANOVA Table (type III tests)
## 
## $ANOVA
##   Effect DFn DFd     F        p p<.05   pes
## 1  waktu   4 116 5.293 0.000596     * 0.154
## 
## $`Mauchly's Test for Sphericity`
##   Effect   W     p p<.05
## 1  waktu 0.6 0.123      
## 
## $`Sphericity Corrections`
##   Effect   GGe     DF[GG] p[GG] p[GG]<.05   HFe       DF[HF] p[HF] p[HF]<.05
## 1  waktu 0.775 3.1, 89.88 0.002         * 0.878 3.51, 101.86 0.001         *
print(
  get_anova_table(
    aov1_rs,
    correction = "auto"
  )
)
## ANOVA Table (type III tests)
## 
##   Effect DFn DFd     F        p p<.05   pes
## 1  waktu   4 116 5.293 0.000596     * 0.154
# 3b. REPEATED MEASURE ANOVA DENGAN AFEX
aov1 <- aov_ez(
  id = "id",
  dv = "hba1c",
  data = d1,
  within = "waktu",
  anova_table = list(
    es = c("ges", "pes"),
    correction = "GG"
  )
)

print(aov1)
## Anova Table (Type 3 tests)
## 
## Response: hba1c
##   Effect          df   MSE       F  ges  pes p.value
## 1  waktu 3.10, 89.88 15.71 5.29 ** .017 .154    .002
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
## 
## Sphericity correction method: GG
summary(aov1)
## 
## Univariate Type III Repeated-Measures ANOVA Assuming Sphericity
## 
##             Sum Sq num Df Error SS den Df  F value    Pr(>F)    
## (Intercept) 407255      1  13931.0     29 847.7795 < 2.2e-16 ***
## waktu          258      4   1411.7    116   5.2929 0.0005958 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## 
## Mauchly Tests for Sphericity
## 
##       Test statistic p-value
## waktu        0.60038  0.1234
## 
## 
## Greenhouse-Geisser and Huynh-Feldt Corrections
##  for Departure from Sphericity
## 
##        GG eps Pr(>F[GG])   
## waktu 0.77481   0.001871 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##          HF eps  Pr(>F[HF])
## waktu 0.8781361 0.001104726
# Ukuran efek

eta_squared(
  aov1,
  partial = TRUE
)
## # Effect Size for ANOVA (Type III)
## 
## Parameter | Eta2 (partial) |       95% CI
## -----------------------------------------
## waktu     |           0.15 | [0.05, 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.01 | [0.00, 1.00]
## 
## - One-sided CIs: upper bound fixed at [1.00].
# 3c. PENDEKATAN MULTIVARIAT (MANOVA)
print(aov1$Anova)
## 
## Type III Repeated Measures MANOVA Tests: Pillai test statistic
##             Df test stat approx F num Df den Df  Pr(>F)    
## (Intercept)  1   0.96692   847.78      1     29 < 2e-16 ***
## waktu        1   0.33654     3.30      4     26 0.02594 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# 3d. POST HOC DAN KONTRAS TREN
em1 <- emmeans(aov1, ~ waktu)

print(em1)
##  waktu emmean   SE df lower.CL upper.CL
##  M0      50.0 1.69 29     46.6     53.5
##  M6      51.2 1.88 29     47.3     55.0
##  M12     52.6 1.78 29     49.0     56.3
##  M18     53.6 2.04 29     49.4     57.8
##  M24     53.1 1.97 29     49.1     57.1
## 
## Confidence level used: 0.95
# Semua pasangan waktu

pairs(
  em1,
  adjust = "bonferroni"
)
##  contrast  estimate    SE df t.ratio p.value
##  M0 - M6     -1.167 0.919 29  -1.269  1.0000
##  M0 - M12    -2.600 0.992 29  -2.622  0.1379
##  M0 - M18    -3.547 1.060 29  -3.352  0.0224
##  M0 - M24    -3.083 1.200 29  -2.564  0.1579
##  M6 - M12    -1.433 0.752 29  -1.905  0.6676
##  M6 - M18    -2.380 0.767 29  -3.105  0.0423
##  M6 - M24    -1.917 0.861 29  -2.225  0.3398
##  M12 - M18   -0.947 0.704 29  -1.345  1.0000
##  M12 - M24   -0.483 0.746 29  -0.648  1.0000
##  M18 - M24    0.463 0.878 29   0.528  1.0000
## 
## P value adjustment: bonferroni method for 10 tests
# Setiap waktu vs baseline

contrast(
  em1,
  "trt.vs.ctrl",
  ref = 1,
  adjust = "holm"
)
##  contrast estimate    SE df t.ratio p.value
##  M6 - M0      1.17 0.919 29   1.269  0.2145
##  M12 - M0     2.60 0.992 29   2.622  0.0414
##  M18 - M0     3.55 1.060 29   3.352  0.0090
##  M24 - M0     3.08 1.200 29   2.564  0.0414
## 
## P value adjustment: holm method for 4 tests
# Tren polinomial

contrast(
  em1,
  "poly"
)
##  contrast  estimate   SE df t.ratio p.value
##  linear        8.55 2.62 29   3.263  0.0028
##  quadratic    -3.75 2.15 29  -1.743  0.0919
##  cubic        -1.68 1.82 29  -0.922  0.3642
##  quartic      -0.17 4.39 29  -0.039  0.9694
# 3e. ALTERNATIF NONPARAMETRIK: FRIEDMAN
friedman_test(
  d1,
  hba1c ~ waktu | id
)
## # A tibble: 1 × 6
##   .y.       n statistic    df       p method       
## * <chr> <int>     <dbl> <dbl>   <dbl> <chr>        
## 1 hba1c    30      15.4     4 0.00398 Friedman test
friedman_effsize(
  d1,
  hba1c ~ waktu | id
)
## # A tibble: 1 × 5
##   .y.       n effsize method    magnitude
## * <chr> <int>   <dbl> <chr>     <ord>    
## 1 hba1c    30   0.128 Kendall W small
# Post hoc Wilcoxon berpasangan

d1 |>
  wilcox_test(
    hba1c ~ waktu,
    paired = TRUE,
    p.adjust.method = "bonferroni"
  )
## # A tibble: 10 × 9
##    .y.   group1 group2    n1    n2 statistic       p  p.adj p.adj.signif
##  * <chr> <chr>  <chr>  <int> <int>     <dbl>   <dbl>  <dbl> <chr>       
##  1 hba1c M0     M6        30    30     162   0.150   1      ns          
##  2 hba1c M0     M12       30    30     118   0.0172  0.172  ns          
##  3 hba1c M0     M18       30    30      82.5 0.00137 0.0137 *           
##  4 hba1c M0     M24       30    30     120   0.0197  0.197  ns          
##  5 hba1c M6     M12       30    30     136.  0.0478  0.478  ns          
##  6 hba1c M6     M18       30    30     102.  0.00588 0.0588 ns          
##  7 hba1c M6     M24       30    30     100   0.00538 0.0538 ns          
##  8 hba1c M12    M18       30    30     171   0.213   1      ns          
##  9 hba1c M12    M24       30    30     190.  0.396   1      ns          
## 10 hba1c M18    M24       30    30     251   0.704   1      ns
# 4. MIXED DESIGN ANOVA
#    BETWEEN: KELOMPOK x WITHIN: WAKTU
# 4a. UJI ASUMSI
# (i) Outlier per kelompok dan waktu

dat_long |>
  group_by(kelompok, waktu) |>
  identify_outliers(hba1c)
## # A tibble: 1 × 11
##   kelompok waktu id     usia jk    diagnosis hba1c bulan bulan6 is.outlier
##   <fct>    <fct> <fct> <int> <fct> <fct>     <dbl> <dbl>  <dbl> <lgl>     
## 1 Langkah  M0    P045     77 L     DM tipe 2  75.5     0      0 TRUE      
## # ℹ 1 more variable: is.extreme <lgl>
# (ii) Normalitas per kelompok dan waktu

dat_long |>
  group_by(kelompok, waktu) |>
  shapiro_test(hba1c)
## # A tibble: 15 × 5
##    kelompok          waktu variable statistic        p
##    <fct>             <fct> <chr>        <dbl>    <dbl>
##  1 Kontrol           M0    hba1c        0.918 0.0245  
##  2 Kontrol           M6    hba1c        0.946 0.136   
##  3 Kontrol           M12   hba1c        0.933 0.0605  
##  4 Kontrol           M18   hba1c        0.946 0.128   
##  5 Kontrol           M24   hba1c        0.943 0.108   
##  6 Langkah           M0    hba1c        0.909 0.0143  
##  7 Langkah           M6    hba1c        0.925 0.0363  
##  8 Langkah           M12   hba1c        0.950 0.174   
##  9 Langkah           M18   hba1c        0.954 0.212   
## 10 Langkah           M24   hba1c        0.953 0.203   
## 11 Langkah+Konseling M0    hba1c        0.849 0.000577
## 12 Langkah+Konseling M6    hba1c        0.922 0.0309  
## 13 Langkah+Konseling M12   hba1c        0.930 0.0485  
## 14 Langkah+Konseling M18   hba1c        0.923 0.0317  
## 15 Langkah+Konseling M24   hba1c        0.943 0.111
# Q-Q Plot

p_qq2 <- ggpubr::ggqqplot(
  dat_long,
  "hba1c",
  ggtheme = theme_bw()
) +
  facet_grid(waktu ~ kelompok)

print(p_qq2)

# (iii) Homogenitas varians Levene

dat_long |>
  group_by(waktu) |>
  levene_test(hba1c ~ kelompok)
## # A tibble: 5 × 5
##   waktu   df1   df2 statistic     p
##   <fct> <int> <int>     <dbl> <dbl>
## 1 M0        2    87     0.416 0.661
## 2 M6        2    87     0.137 0.872
## 3 M12       2    87     0.401 0.671
## 4 M18       2    87     0.363 0.697
## 5 M24       2    87     0.761 0.470
# (iv) Homogenitas matriks kovarians Box's M

box_m(
  dat_wide[, kolom_ukur],
  dat_wide$kelompok
)
## # A tibble: 1 × 4
##   statistic p.value parameter method                                            
##       <dbl>   <dbl>     <dbl> <chr>                                             
## 1      38.8   0.129        30 Box's M-test for Homogeneity of Covariance Matric…
# 4b. MIXED DESIGN ANOVA
aov2 <- aov_ez(
  id = "id",
  dv = "hba1c",
  data = dat_long,
  between = "kelompok",
  within = "waktu",
  anova_table = list(
    es = c("ges", "pes"),
    correction = "GG"
  )
)

print(aov2)
## Anova Table (Type 3 tests)
## 
## Response: hba1c
##           Effect           df    MSE         F  ges  pes p.value
## 1       kelompok        2, 87 473.29      0.19 .004 .004    .830
## 2          waktu 3.33, 289.68  14.23 18.39 *** .019 .175   <.001
## 3 kelompok:waktu 6.66, 289.68  14.23   2.90 ** .006 .063    .007
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
## 
## Sphericity correction method: GG
summary(aov2)
## 
## Univariate Type III Repeated-Measures ANOVA Assuming Sphericity
## 
##                 Sum Sq num Df Error SS den Df   F value    Pr(>F)    
## (Intercept)    1223611      1    41176     87 2585.3210 < 2.2e-16 ***
## kelompok           177      2    41176     87    0.1865   0.83020    
## waktu              872      4     4122    348   18.3941 1.006e-13 ***
## kelompok:waktu     275      8     4122    348    2.9002   0.00384 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## 
## Mauchly Tests for Sphericity
## 
##                Test statistic    p-value
## waktu                 0.71585 0.00077217
## kelompok:waktu        0.71585 0.00077217
## 
## 
## Greenhouse-Geisser and Huynh-Feldt Corrections
##  for Departure from Sphericity
## 
##                 GG eps Pr(>F[GG])    
## waktu          0.83242  8.378e-12 ***
## kelompok:waktu 0.83242   0.006973 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##                   HF eps   Pr(>F[HF])
## waktu          0.8695202 3.144467e-12
## kelompok:waktu 0.8695202 6.105600e-03
print(aov2$Anova)
## 
## Type III Repeated Measures MANOVA Tests: Pillai test statistic
##                Df test stat approx F num Df den Df    Pr(>F)    
## (Intercept)     1   0.96744  2585.32      1     87 < 2.2e-16 ***
## kelompok        2   0.00427     0.19      2     87   0.83020    
## waktu           1   0.34140    10.89      4     84 3.698e-07 ***
## kelompok:waktu  2   0.20159     2.38      8    170   0.01858 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# ANOVA versi rstatix (Tipe III)

aov2_rs <- anova_test(
  data = dat_long,
  dv = hba1c,
  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  0.186 8.30e-01       0.004
## 2          waktu 3.33 289.68 18.394 8.38e-12     * 0.175
## 3 kelompok:waktu 6.66 289.68  2.900 7.00e-03     * 0.063
# Ukuran efek

eta_squared(
  aov2,
  partial = TRUE
)
## # Effect Size for ANOVA (Type III)
## 
## Parameter      | Eta2 (partial) |       95% CI
## ----------------------------------------------
## kelompok       |       4.27e-03 | [0.00, 1.00]
## waktu          |           0.17 | [0.11, 1.00]
## kelompok:waktu |           0.06 | [0.01, 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.00 | [0.00, 1.00]
## waktu          |             0.02 | [0.00, 1.00]
## kelompok:waktu |         3.91e-03 | [0.00, 1.00]
## 
## - One-sided CIs: upper bound fixed at [1.00].
# Plot interaksi model

p_afex <- afex_plot(
  aov2,
  x = "waktu",
  trace = "kelompok",
  error = "within",
  mapping = c("colour", "shape", "linetype")
) +
  labs(
    y = "HbA1c (mmol/mol)",
    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"
print(p_afex)

# 4b.1 LINE PLOT DENGAN ERROR BARS (MEAN +/- SE)
# Menghitung summary statistics untuk plot

df_summary <- dat_long %>%
  group_by(waktu, kelompok) %>%
  summarise(
    mean_hba1c = mean(hba1c, na.rm = TRUE),
    sd_hba1c = sd(hba1c, na.rm = TRUE),
    n = sum(!is.na(hba1c)),
    se_hba1c = sd_hba1c / sqrt(n),
    .groups = "drop"
  )

print(df_summary)
## # A tibble: 15 × 6
##    waktu kelompok          mean_hba1c sd_hba1c     n se_hba1c
##    <fct> <fct>                  <dbl>    <dbl> <int>    <dbl>
##  1 M0    Kontrol                 50.5    10.8     30     1.97
##  2 M0    Langkah                 50.3    10.5     30     1.92
##  3 M0    Langkah+Konseling       50.0     9.27    30     1.69
##  4 M6    Kontrol                 50.4     9.41    30     1.72
##  5 M6    Langkah                 51.4     9.11    30     1.66
##  6 M6    Langkah+Konseling       51.2    10.3     30     1.88
##  7 M12   Kontrol                 51.9    10.6     30     1.93
##  8 M12   Langkah                 52.9     9.79    30     1.79
##  9 M12   Langkah+Konseling       52.6     9.74    30     1.78
## 10 M18   Kontrol                 51.6    10.0     30     1.83
## 11 M18   Langkah                 53.0    10.1     30     1.85
## 12 M18   Langkah+Konseling       53.6    11.2     30     2.04
## 13 M24   Kontrol                 52.6    11.2     30     2.04
## 14 M24   Langkah                 57.1     9.89    30     1.81
## 15 M24   Langkah+Konseling       53.1    10.8     30     1.97
# Membuat Line Plot dengan Error Bars

p_interaksi <- ggplot(
  df_summary,
  aes(
    x = waktu,
    y = mean_hba1c,
    group = kelompok,
    color = kelompok
  )
) +
  geom_line(linewidth = 1) +
  geom_point(size = 3) +
  geom_errorbar(
    aes(
      ymin = mean_hba1c - se_hba1c,
      ymax = mean_hba1c + se_hba1c
    ),
    width = 0.1
  ) +
  scale_color_manual(
    values = c(
      "Kontrol" = "#E46726",
      "Langkah" = "#2C82C9",
      "Langkah+Konseling" = "#379467"
    )
  ) +
  labs(
    title = "Perubahan Kadar HbA1c Seiring Waktu",
    subtitle = "Interaksi antara Kelompok dan Waktu (Mixed ANOVA)",
    x = "Waktu Pengukuran",
    y = "Rata-rata HbA1c (mmol/mol)",
    color = "Kelompok"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(
      face = "bold",
      size = 14
    ),
    legend.position = "bottom"
  )

print(p_interaksi)

# 4c. ANALISIS EFEK SEDERHANA DAN POST HOC
em2 <- emmeans(
  aov2,
  ~ waktu | kelompok
)


# Efek waktu di dalam tiap kelompok

joint_tests(
  aov2,
  by = "kelompok"
)
## kelompok = Kontrol:
##  model term df1 df2 F.ratio p.value
##  waktu        4  87   1.592  0.1837
## 
## kelompok = Langkah:
##  model term df1 df2 F.ratio p.value
##  waktu        4  87  10.961 <0.0001
## 
## kelompok = Langkah+Konseling:
##  model term df1 df2 F.ratio p.value
##  waktu        4  87   3.860  0.0062
# Efek kelompok pada tiap waktu

joint_tests(
  aov2,
  by = "waktu"
)
## waktu = M0:
##  model term df1 df2 F.ratio p.value
##  kelompok     2  87   0.015  0.9852
## 
## waktu = M6:
##  model term df1 df2 F.ratio p.value
##  kelompok     2  87   0.089  0.9147
## 
## waktu = M12:
##  model term df1 df2 F.ratio p.value
##  kelompok     2  87   0.088  0.9157
## 
## waktu = M18:
##  model term df1 df2 F.ratio p.value
##  kelompok     2  87   0.267  0.7663
## 
## waktu = M24:
##  model term df1 df2 F.ratio p.value
##  kelompok     2  87   1.569  0.2141
# Post hoc tiap waktu vs baseline

contrast(
  em2,
  "trt.vs.ctrl",
  ref = 1,
  adjust = "holm"
)
## kelompok = Kontrol:
##  contrast estimate    SE df t.ratio p.value
##  M6 - M0   -0.0567 0.863 87  -0.066  0.9478
##  M12 - M0   1.3800 0.921 87   1.498  0.4130
##  M18 - M0   1.1667 0.971 87   1.202  0.4653
##  M24 - M0   2.1367 1.160 87   1.845  0.2738
## 
## kelompok = Langkah:
##  contrast estimate    SE df t.ratio p.value
##  M6 - M0    1.0900 0.863 87   1.263  0.2100
##  M12 - M0   2.5767 0.921 87   2.798  0.0190
##  M18 - M0   2.6300 0.971 87   2.709  0.0190
##  M24 - M0   6.7267 1.160 87   5.808 <0.0001
## 
## kelompok = Langkah+Konseling:
##  contrast estimate    SE df t.ratio p.value
##  M6 - M0    1.1667 0.863 87   1.352  0.1800
##  M12 - M0   2.6000 0.921 87   2.823  0.0177
##  M18 - M0   3.5467 0.971 87   3.654  0.0018
##  M24 - M0   3.0833 1.160 87   2.662  0.0185
## 
## P value adjustment: holm method for 4 tests
# Perbandingan antarkelompok pada setiap waktu

em2b <- emmeans(
  aov2,
  ~ kelompok | waktu
)

pairs(
  em2b,
  adjust = "tukey"
)
## waktu = M0:
##  contrast                      estimate   SE df t.ratio p.value
##  Kontrol - Langkah                0.147 2.64 87   0.056  0.9983
##  Kontrol - (Langkah+Konseling)    0.447 2.64 87   0.169  0.9843
##  Langkah - (Langkah+Konseling)    0.300 2.64 87   0.114  0.9929
## 
## waktu = M6:
##  contrast                      estimate   SE df t.ratio p.value
##  Kontrol - Langkah               -1.000 2.49 87  -0.402  0.9147
##  Kontrol - (Langkah+Konseling)   -0.777 2.49 87  -0.313  0.9476
##  Langkah - (Langkah+Konseling)    0.223 2.49 87   0.090  0.9956
## 
## waktu = M12:
##  contrast                      estimate   SE df t.ratio p.value
##  Kontrol - Langkah               -1.050 2.59 87  -0.405  0.9136
##  Kontrol - (Langkah+Konseling)   -0.773 2.59 87  -0.298  0.9521
##  Langkah - (Langkah+Konseling)    0.277 2.59 87   0.107  0.9937
## 
## waktu = M18:
##  contrast                      estimate   SE df t.ratio p.value
##  Kontrol - Langkah               -1.317 2.70 87  -0.487  0.8776
##  Kontrol - (Langkah+Konseling)   -1.933 2.70 87  -0.715  0.7551
##  Langkah - (Langkah+Konseling)   -0.617 2.70 87  -0.228  0.9717
## 
## waktu = M24:
##  contrast                      estimate   SE df t.ratio p.value
##  Kontrol - Langkah               -4.443 2.75 87  -1.617  0.2440
##  Kontrol - (Langkah+Konseling)   -0.500 2.75 87  -0.182  0.9819
##  Langkah - (Langkah+Konseling)    3.943 2.75 87   1.435  0.3276
## 
## P value adjustment: tukey method for comparing a family of 3 estimates
# 4d. KONTRAS INTERAKSI
# Perbedaan perubahan M24 - M0 antarkelompok

em_full <- emmeans(
  aov2,
  ~ waktu * kelompok
)

kontras_akhir <- contrast(
  em_full,
  interaction = list(
    waktu = list(
      "M24-M0" = c(-1, 0, 0, 0, 1)
    ),
    kelompok = "pairwise"
  ),
  adjust = "holm"
)

print(kontras_akhir)
##  waktu_custom kelompok_pairwise             estimate   SE df t.ratio p.value
##  M24-M0       Kontrol - Langkah               -4.590 1.64 87  -2.802  0.0188
##  M24-M0       Kontrol - (Langkah+Konseling)   -0.947 1.64 87  -0.578  0.5648
##  M24-M0       Langkah - (Langkah+Konseling)    3.643 1.64 87   2.224  0.0574
## 
## P value adjustment: holm method for 3 tests
# Tren polinomial per kelompok

tren_kelompok <- as.data.frame(
  contrast(em2, "poly")
)

subset(
  tren_kelompok,
  contrast == "linear"
)
##   contrast          kelompok  estimate       SE df  t.ratio      p.value
## 1   linear           Kontrol  5.496667 2.582513 87 2.128418 3.612998e-02
## 5   linear           Langkah 14.993333 2.582513 87 5.805715 1.023210e-07
## 9   linear Langkah+Konseling  8.546667 2.582513 87 3.309438 1.361411e-03
# Perbandingan tren linear antarkelompok

tren_int <- as.data.frame(
  summary(
    contrast(
      em_full,
      interaction = c(
        waktu = "poly",
        kelompok = "pairwise"
      ),
      adjust = "none"
    )
  )
)

tren_lin <- subset(
  tren_int,
  waktu_poly == "linear"
)

tren_lin$p.holm <- p.adjust(
  tren_lin$p.value,
  method = "holm"
)

print(tren_lin)
##   waktu_poly             kelompok_pairwise  estimate       SE df    t.ratio
## 1     linear             Kontrol - Langkah -9.496667 3.652225 87 -2.6002416
## 5     linear Kontrol - (Langkah+Konseling) -3.050000 3.652225 87 -0.8351074
## 9     linear Langkah - (Langkah+Konseling)  6.446667 3.652225 87  1.7651342
##      p.value     p.holm
## 1 0.01094446 0.03283337
## 5 0.40594470 0.40594470
## 9 0.08105026 0.16210053
# 5. PEMBANDING: LINEAR MIXED MODEL (LMM)
# Pengaturan optimizer

kontrol_lmm <- lmerControl(
  optimizer = "bobyqa",
  optCtrl = list(maxfun = 2e5)
)


# Model 1: Random intercept

lmm1 <- lmerTest::lmer(
  hba1c ~ kelompok * waktu + (1 | id),
  data = dat_long,
  REML = TRUE,
  control = kontrol_lmm
)


# Model 2: Random intercept dan random slope

lmm2 <- lmerTest::lmer(
  hba1c ~ kelompok * waktu + (1 + bulan6 | id),
  data = dat_long,
  REML = TRUE,
  control = kontrol_lmm
)


# Perbandingan struktur efek acak

anova(
  lmm1,
  lmm2,
  refit = FALSE
)
## Data: dat_long
## Models:
## lmm1: hba1c ~ kelompok * waktu + (1 | id)
## lmm2: hba1c ~ kelompok * waktu + (1 + bulan6 | id)
##      npar    AIC    BIC  logLik -2*log(L)  Chisq Df Pr(>Chisq)    
## lmm1   17 2736.3 2806.1 -1351.1    2702.3                         
## lmm2   19 2716.1 2794.1 -1339.0    2678.1 24.207  2   5.54e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Uji F Tipe III efek tetap

anova(
  lmm2,
  type = 3,
  ddf = "Satterthwaite"
)
## Type III Analysis of Variance Table with Satterthwaite's method
##                Sum Sq Mean Sq NumDF  DenDF F value    Pr(>F)    
## kelompok         3.40   1.702     2  87.00  0.1865  0.830198    
## waktu          412.91 103.228     4 229.98 11.3133 2.131e-08 ***
## kelompok:waktu 198.11  24.763     8 229.98  2.7139  0.007109 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Korelasi intrakelas

performance::icc(lmm1)
## # Intraclass Correlation Coefficient
## 
##     Adjusted ICC: 0.886
##   Unadjusted ICC: 0.862
# Pemeriksaan model singular

isSingular(lmm2)
## [1] FALSE
# Diagnostik residual LMM

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))


# Simulasi 30 nilai hilang pada pengukuran pasca-baseline

set.seed(1)

dat_miss <- dat_long

idx_miss <- sample(
  which(dat_miss$waktu != "M0"),
  30
)

dat_miss$hba1c[idx_miss] <- NA


# LMM dengan data hilang

lmm_miss <- lmerTest::lmer(
  hba1c ~ kelompok * waktu + (1 | id),
  data = dat_miss,
  REML = TRUE,
  na.action = na.omit,
  control = kontrol_lmm
)


# Uji F Tipe III pada data hilang

anova(
  lmm_miss,
  type = 3,
  ddf = "Satterthwaite"
)
## Type III Analysis of Variance Table with Satterthwaite's method
##                Sum Sq Mean Sq NumDF  DenDF F value    Pr(>F)    
## kelompok         5.27   2.635     2  87.06  0.2192  0.803617    
## waktu          866.54 216.634     4 318.45 18.0169 2.374e-13 ***
## kelompok:waktu 256.94  32.117     8 318.45  2.6711  0.007485 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Menghitung jumlah subjek dengan data hilang

n_distinct(
  dat_miss$id[is.na(dat_miss$hba1c)]
)
## [1] 25
# 6. MENYIMPAN DATA, GRAFIK, DAN RINGKASAN HASIL
folder_hasil <- "C:/Users/HP/Downloads/hasil_analisis_HbA1c"

if (!dir.exists(folder_hasil)) {
  dir.create(
    folder_hasil,
    recursive = TRUE,
    showWarnings = FALSE
  )
}

stopifnot(dir.exists(folder_hasil))


# Menyimpan data wide

write.table(
  dat_wide,
  file.path(folder_hasil, "hasil_data_wide.csv"),
  sep = ";",
  dec = ".",
  row.names = FALSE,
  quote = FALSE
)


# Menyimpan data long

write.table(
  dat_long |> select(-bulan6),
  file.path(folder_hasil, "hasil_data_long.csv"),
  sep = ";",
  dec = ".",
  row.names = FALSE,
  quote = FALSE
)


# Menyimpan statistik deskriptif

write.table(
  desk,
  file.path(folder_hasil, "statistik_deskriptif.csv"),
  sep = ";",
  dec = ".",
  row.names = FALSE,
  quote = FALSE
)


# Menyimpan summary statistics Line Plot

write.table(
  df_summary,
  file.path(folder_hasil, "summary_line_plot_HbA1c.csv"),
  sep = ";",
  dec = ".",
  row.names = FALSE,
  quote = FALSE
)


# Menyimpan grafik dalam format PDF

simpan_pdf <- function(plot, nama_file, lebar, tinggi) {
  
  lokasi <- file.path(folder_hasil, nama_file)
  
  grDevices::pdf(
    file = lokasi,
    width = lebar,
    height = tinggi,
    onefile = TRUE,
    useDingbats = FALSE
  )
  
  tryCatch(
    print(plot),
    finally = grDevices::dev.off()
  )
  
  message("Grafik tersimpan: ", lokasi)
  
  invisible(lokasi)
}


# Menyimpan profile plot

simpan_pdf(
  p_profil,
  "profile_plot_HbA1c.pdf",
  lebar = 9,
  tinggi = 5.6
)


# Menyimpan spaghetti plot

simpan_pdf(
  p_spag,
  "spaghetti_plot_HbA1c.pdf",
  lebar = 10,
  tinggi = 5.3
)


# Menyimpan Line Plot dengan Error Bars

simpan_pdf(
  p_interaksi,
  "line_plot_interaksi_HbA1c.pdf",
  lebar = 9,
  tinggi = 5.6
)


# Menyimpan plot interaksi Mixed ANOVA

simpan_pdf(
  p_afex,
  "plot_mixed_anova_HbA1c.pdf",
  lebar = 9,
  tinggi = 5.6
)


# Menyimpan Q-Q plot

simpan_pdf(
  p_qq2,
  "qqplot_normalitas_HbA1c.pdf",
  lebar = 11,
  tinggi = 8
)


# Menyimpan ringkasan hasil analisis

capture.output(
  {
    
    cat("HASIL ANALISIS DATA SIMULASI HbA1c\n\n")
    
    cat("Repeated Measure ANOVA - Langkah+Konseling\n")
    print(aov1)
    
    cat("\nMixed Design ANOVA - Semua Kelompok\n")
    print(aov2)
    
    cat("\nRingkasan Rerata dan SD\n")
    print(desk)
    
    cat("\nSummary Statistics Line Plot (Mean +/- SE)\n")
    print(df_summary)
    
    cat("\nKontras Perubahan Baseline-Akhir\n")
    print(kontras_akhir)
    
    cat("\nLinear Mixed Model\n")
    
    print(
      anova(
        lmm2,
        type = 3,
        ddf = "Satterthwaite"
      )
    )
    
  },
  
  file = file.path(
    folder_hasil,
    "ringkasan_hasil_analisis.txt"
  )
)


# Memeriksa seluruh file hasil

print(list.files(folder_hasil))
##  [1] "fig_knit_unnamed-chunk-10-1.pdf" "fig_knit_unnamed-chunk-17-1.pdf"
##  [3] "fig_knit_unnamed-chunk-18-1.pdf" "fig_knit_unnamed-chunk-20-1.pdf"
##  [5] "fig_knit_unnamed-chunk-24-1.pdf" "fig_knit_unnamed-chunk-7-1.pdf" 
##  [7] "fig_knit_unnamed-chunk-7-2.pdf"  "hasil_data_long.csv"            
##  [9] "hasil_data_wide.csv"             "line_plot_interaksi_HbA1c.pdf"  
## [11] "plot_mixed_anova_HbA1c.pdf"      "profile_plot_HbA1c.pdf"         
## [13] "profile_plot_HbA1c.png"          "qqplot_normalitas_HbA1c.pdf"    
## [15] "ringkasan_hasil_analisis.txt"    "spaghetti_plot_HbA1c.pdf"       
## [17] "spaghetti_plot_HbA1c.png"        "statistik_deskriptif.csv"       
## [19] "summary_line_plot_HbA1c.csv"
message(
  "Selesai. Hasil tersimpan di: ",
  normalizePath(folder_hasil)
)

# =============================================================================