=============================================================================

MDD Eye-Tracking Temporal Course Analysis

=============================================================================

PURPOSE:

Compare the temporal dynamics of attentional bias (emotional vs neutral gaze)

between MDD patients and healthy controls (HC) across three facial expressions

(fear, happy, sad) and three trial types, using cluster-based permutation tests.

ANALYSIS OVERVIEW:

1. Load raw gaze data (one row = one gaze sample at 500 Hz)

2. Slide a 200 ms window across each trial to compute attentional bias

at each time point -> produces a time series per subject

3. Plot group-average time series in a 3x3 grid

4. For each of the 9 (expression x trial_type) cells, run a cluster-based

permutation test asking: “Do MDD and HC differ in their bias time series?”

=============================================================================

library(tidyverse)
## ── 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.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ 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(ggplot2)

# =============================================================================
# SECTION 1 — Load data
# =============================================================================
# Reads the merged CSV that contains every gaze sample for every subject.
# Key columns used downstream:
#   subject_id   : unique participant identifier
#   sub_type     : diagnostic group (MDD / HC)
#   trial_type   : condition of the trial (3 levels)
#   expression   : facial expression shown (fear / happy / sad)
#   Ntrial       : trial number within subject
#   count        : time in ms since stimulus onset (the x-axis of the analysis)
#   AOI_tem_emo  : which Area of Interest was fixated
#                  1 = neutral face region, 2 = emotional face region

data_folder <- "C:\\Users\\quent\\Desktop\\fmri eye\\data"
sample_rate <- 500   # Hz — used to convert rows to milliseconds (1 row = 2 ms)

raw <- read_csv(file.path(data_folder, "all_subjects_full.csv")) %>%
  select(subject_id, sub_type, trial_type, expression, Ntrial, count, AOI_tem_emo) %>%
  mutate(
    subject    = as.factor(subject_id),
    group      = as.factor(sub_type),
    trial_type = as.factor(trial_type),
    expression = as.factor(expression),
    trial      = as.integer(Ntrial),
    time_ms    = as.numeric(count),
    AOI        = as.factor(AOI_tem_emo)
  )
## Rows: 19801492 Columns: 20
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (7): subject_id, sub_type, expression, trial_type, L_stim, R_stim, emo_...
## dbl (13): count, Time, x_val, y_val, Tstart, Ntrial, dot_pos, LorR, cue_pos,...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# =============================================================================
# SECTION 2 — Sliding-window epoching
# =============================================================================
# Why sliding windows?
#   A single gaze sample is noisy. Averaging over a short window (200 ms) gives
#   a stable estimate of where the participant was looking at each moment while
#   still preserving fine temporal resolution via the small step size.
#
# Parameters:
#   window_rows = 100  ->  200 ms window  (100 rows × 2 ms/row)
#   step_rows   = 1    ->  step of 1 row  = 2 ms between consecutive epoch centres
#
# For each window position the function computes:
#   emotional  : number of samples landing on the emotional AOI (AOI == 2)
#   neutral    : number of samples landing on the neutral AOI  (AOI == 1)
#   bias       : emotional - neutral  (positive = more time on emotional face)
#   epoch_center_ms : mean time_ms of the window  (used as the x-axis label)

window_rows <- 100   # 200 ms at 500 Hz
step_rows   <- 1     # step size in rows (2 ms)

# Add a within-trial row index so windows are defined in sample space, not ms
raw_indexed <- raw %>%
  group_by(subject, group, expression, trial) %>%
  arrange(time_ms, .by_group = TRUE) %>%
  mutate(row_idx = row_number()) %>%
  ungroup()

# Find the shortest trial across all subjects so every window start is valid
n_rows_per_trial <- raw_indexed %>%
  group_by(subject, expression, trial) %>%
  summarise(n = n(), .groups = "drop") %>%
  pull(n) %>% min()

# Vector of window start positions (1, 2, 3, ... up to last valid start)
epoch_starts <- seq(1, n_rows_per_trial - window_rows + 1, by = step_rows)

# Compute one epoch (one row per subject x trial x condition) for a given start
compute_epoch <- function(start) {
  raw_indexed %>%
    filter(row_idx >= start, row_idx < start + window_rows) %>%
    group_by(subject, group, trial_type, expression, trial) %>%
    summarise(
      epoch_center_ms = mean(time_ms, na.rm = TRUE),
      emotional = sum(AOI == 1, na.rm = TRUE),
      neutral   = sum(AOI == 2, na.rm = TRUE),
      bias      = emotional - neutral,
      .groups   = "drop"
    ) %>%
    mutate(epoch_start_row = start)
}

# Stack all epochs into one long table
temporal <- map_dfr(epoch_starts, compute_epoch)

# Average across trials to get one bias value per subject x group x condition x time point
subject_curve <- temporal %>%
  group_by(subject, group, trial_type, expression, epoch_center_ms) %>%
  summarise(
    bias = mean(bias, na.rm = TRUE),
    .groups = "drop"
  )

# Bin settings used for ANOVA and plot overlays
n_bins <- 10   # number of temporal bins

# Build binned data from the continuous time series
subject_curve_binned <- subject_curve %>%
  group_by(subject, group, trial_type, expression) %>%
  mutate(time_bin = ntile(epoch_center_ms, n_bins)) %>%
  ungroup() %>%
  group_by(subject, group, trial_type, expression, time_bin) %>%
  summarise(
    bias = mean(bias, na.rm = TRUE),
    bin_start_ms = min(epoch_center_ms, na.rm = TRUE),
    bin_end_ms   = max(epoch_center_ms, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(time_bin = factor(time_bin))

# Per-bin group comparison (MDD vs HC) for rectangle overlays on the plot
bin_group_tests <- subject_curve_binned %>%
  group_by(expression, trial_type, time_bin, bin_start_ms, bin_end_ms) %>%
  summarise(
    p_group_bin = {
      d <- cur_data()
      if (n_distinct(d$group) == 2) {
        stats::t.test(bias ~ group, data = d)$p.value
      } else {
        NA_real_
      }
    },
    .groups = "drop"
  )
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `p_group_bin = { ... }`.
## ℹ In group 1: `expression = F`, `trial_type = C`, `time_bin = 1`, `bin_start_ms
##   = 50.5`, `bin_end_ms = 164.5`.
## Caused by warning:
## ! `cur_data()` was deprecated in dplyr 1.1.0.
## ℹ Please use `pick()` instead.
sig_group_bins <- bin_group_tests %>%
  filter(!is.na(p_group_bin), p_group_bin < 0.05)
# =============================================================================
# SECTION 3 — Visualisation (3 x 3 grid)
# =============================================================================
# Rows    = facial expression (fear / happy / sad)
# Columns = trial type (3 levels)
# Lines   = group average ± the stat_summary ribbon; colour = MDD vs HC
# The y-axis (bias) > 0 means more gaze toward the emotional face region.

ggplot(subject_curve,
       aes(epoch_center_ms, bias, color = group)) +
  geom_rect(
    data = sig_group_bins,
    aes(xmin = bin_start_ms, xmax = bin_end_ms, ymin = -Inf, ymax = Inf),
    inherit.aes = FALSE,
    fill = "grey70",
    alpha = 0.25
  ) +
  stat_summary(fun = mean, geom = "line", linewidth = 1) +
  facet_grid(expression ~ trial_type) +
  theme_classic() +
  labs(x = "Time after stimulus onset (ms)",
       y = "Emotional - neutral gaze (samples per 200 ms window)")

# =============================================================================
# SECTION 4 — Bin-based mixed ANOVA (group × time_bin)
# =============================================================================
# WHAT IS BEING TESTED?
#   For each (expression × trial_type) cell, test:
#     - main effect of group (MDD vs HC)
#     - main effect of time_bin (temporal change across bins)
#     - group × time_bin interaction (different temporal profiles by group)
#
# DESIGN:
#   - Between-subject factor: group
#   - Within-subject factor : time_bin
#   - Dependent variable    : bias

# subject_curve_binned and n_bins are defined above and reused here.

extract_effect_stats <- function(summary_obj, effect_name) {
  for (component in summary_obj) {
    if (is.list(component) && length(component) >= 1) {
      tbl <- component[[1]]
      if (!is.null(tbl) && (is.matrix(tbl) || is.data.frame(tbl))) {
        rn <- rownames(tbl)
        p_col <- grep("Pr\\(>F\\)", colnames(tbl), value = TRUE)
        f_col <- grep("^F value$|^F$", colnames(tbl), value = TRUE)
        if (effect_name %in% rn && length(p_col) == 1) {
          p_val <- as.numeric(tbl[effect_name, p_col])
          f_val <- if (length(f_col) >= 1) as.numeric(tbl[effect_name, f_col[1]]) else NA_real_
          return(list(p = p_val, f = f_val))
        }
      }
    }
  }
  return(list(p = NA_real_, f = NA_real_))
}

run_mixed_anova <- function(expr, ttype) {
  dat <- subject_curve_binned %>%
    filter(expression == expr, trial_type == ttype)

  # Repeated-measures ANOVA with subject as random effect for time_bin
  fit <- aov(bias ~ group * time_bin + Error(subject / time_bin), data = dat)
  s   <- summary(fit)

  group_stats <- extract_effect_stats(s, "group")
  bin_stats   <- extract_effect_stats(s, "time_bin")
  int_stats   <- extract_effect_stats(s, "group:time_bin")

  list(
    fit = fit,
    summary = s,
    p_group = group_stats$p,
    p_time_bin = bin_stats$p,
    p_interaction = int_stats$p,
    f_group = group_stats$f,
    f_time_bin = bin_stats$f,
    f_interaction = int_stats$f
  )
}

# Run for all 9 (expression × trial_type) combinations
expressions <- levels(subject_curve$expression)
trial_types <- levels(subject_curve$trial_type)

anova_results <- lapply(expressions, function(expr) {
  lapply(trial_types, function(ttype) {
    run_mixed_anova(expr, ttype)
  }) %>% setNames(trial_types)
}) %>% setNames(expressions)

# Access individual results with: anova_results[["fear"]][["neutral"]]

# =============================================================================
# SECTION 5 — Print ANOVA results
# =============================================================================
# For each of the 9 cells, prints F and p for:
#   group, time_bin, and group × time_bin.

print_anova_results <- function(results, expressions, trial_types) {
  for (expr in expressions) {
    for (ttype in trial_types) {
      cat(rep("=", 70), "\n", sep = "")
      cat(sprintf("  Expression: %-10s | Trial type: %s\n", expr, ttype))
      cat(rep("=", 70), "\n", sep = "")

      res <- results[[expr]][[ttype]]

      cat(sprintf("  %-20s  %-10s  %-10s\n", "Effect", "F", "p-value"))
      cat(rep("-", 48), "\n", sep = "")
      cat(sprintf("  %-20s  %-10.4f  %-10.4f\n",
                  "group", res$f_group, res$p_group))
      cat(sprintf("  %-20s  %-10.4f  %-10.4f\n",
                  "time_bin", res$f_time_bin, res$p_time_bin))
      cat(sprintf("  %-20s  %-10.4f  %-10.4f\n",
                  "group:time_bin", res$f_interaction, res$p_interaction))

      sig_effects <- c(
        if (!is.na(res$p_group) && res$p_group < .05) "group" else NULL,
        if (!is.na(res$p_time_bin) && res$p_time_bin < .05) "time_bin" else NULL,
        if (!is.na(res$p_interaction) && res$p_interaction < .05) "group:time_bin" else NULL
      )

      if (length(sig_effects) == 0) {
        cat("  Significant effects: none (all p >= .05)\n\n")
      } else {
        cat(sprintf("  Significant effects: %s\n\n", paste(sig_effects, collapse = ", ")))
      }
    }
  }
}

print_anova_results(anova_results, expressions, trial_types)
## ======================================================================
##   Expression: F          | Trial type: C
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        1.5568      0.1237    
##   Significant effects: none (all p >= .05)
## 
## ======================================================================
##   Expression: F          | Trial type: E
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        3.1523      0.0009    
##   Significant effects: group:time_bin
## 
## ======================================================================
##   Expression: F          | Trial type: N
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        2.4129      0.0104    
##   Significant effects: group:time_bin
## 
## ======================================================================
##   Expression: H          | Trial type: C
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        0.3909      0.9398    
##   Significant effects: none (all p >= .05)
## 
## ======================================================================
##   Expression: H          | Trial type: E
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        2.0757      0.0291    
##   Significant effects: group:time_bin
## 
## ======================================================================
##   Expression: H          | Trial type: N
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        0.9373      0.4915    
##   Significant effects: none (all p >= .05)
## 
## ======================================================================
##   Expression: S          | Trial type: C
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        0.3605      0.9535    
##   Significant effects: none (all p >= .05)
## 
## ======================================================================
##   Expression: S          | Trial type: E
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        1.6499      0.0969    
##   Significant effects: none (all p >= .05)
## 
## ======================================================================
##   Expression: S          | Trial type: N
## ======================================================================
##   Effect                F           p-value   
## ------------------------------------------------
##   group                 NA          NA        
##   time_bin              NA          NA        
##   group:time_bin        3.9293      0.0001    
##   Significant effects: group:time_bin