OSCR

Changes in Aperiodic (1/f Slope) Activity During a Picture-Word Interference Task: Effects of Congruency and Sequence Manipulations.

Code ↔ Paper

2 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 2 matches
  1. [1] § Method › Cluster‐Based Permutation Analyses (FWER‐Corrected) ↔ code_afterR1.Rmd, lines 1193–1254 · score 0.57 · sign flip, cluster statistic, FWER, permutation
  2. [2] § Method › Exploratory Analyses (No Correction for Multiple Comparisons) ↔ code_afterR1.Rmd, lines 1193–1254 · score 0.56 · global stimulus induced, Baseline correction, threshold, post, interactions

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R Markdown · 1,689 lines · 61 KB · no license · 2 matches

  1. ---
  2. title: "Code to reproduce the spectral slope analyses from the project (section 3.2.3)."
  3. output: html_document
  4. date: "2024-04-04"
  5. ---
  6. Load libraries
  7. ```{r setup, include=FALSE}
  8. knitr::opts_chunk$set(echo = TRUE)
  9. setwd("C:/Users/patry/Documents/Paranauka/Projekty/Illinois/virginia/r1/")
  10. rm(list = ls())
  11. options(scipen = 999)
  12. library(plyr)
  13. library(tidyverse)
  14. library(psych)
  15. library(stringr)
  16. library(data.table)
  17. library(readxl)
  18. library(eegUtils)
  19. # Time intervals
  20. time_intervals <- c(-160, 0, 160, 320, 480, 640, 800, 960, 1120, 1280)
  21. # Define the correct order of the levels
  22. time_chunk_levels <- c("[-160,0]", "(0,160]", "(160,320]", "(320,480]", "(480,640]", "(640,800]", "(800,960]", "(960,1120]", "(1120,1280]")
  23. # Shading for plots
  24. shading_data <- data.frame(
  25. xmin = time_intervals[-length(time_intervals)], # All but the last element
  26. xmax = time_intervals[-1], # All but the first element
  27. ymin = -Inf, # Extend shading to the bottom
  28. ymax = Inf, # Extend shading to the top
  29. brightness = c(0.0, 0.6,0.1, 0.6,0.1, 0.6,0.1, 0.6,0.1) # Different brightness values
  30. )
  31. ```
  32. Raw data vizualization
  33. ```{r}
  34. load("data_raw.RData")
  35. # Collapse across non-focal dimensions (mean within cells)
  36. reg_pcongr2 = data.table::dcast(setDT(reg), ID + channel + time + PrevCongr + Congr ~ ., value.var = c("value"), mean, na.rm = TRUE)
  37. setnames(reg_pcongr2, old = ".", new = "value")
  38. # Harmonize factors & keep only levels of interest
  39. reg_pcongr2$Congr <- factor(reg_pcongr2$Congr, levels = c("Con", "Inc"))
  40. reg_pcongr2$PrevCongr <- factor(reg_pcongr2$PrevCongr, levels = c("pCon", "pInc"))
  41. reg_pcongr3 = reg_pcongr2[reg_pcongr2$channel %in% c("Fz", "Cz", "Pz", "Oz")]
  42. reg_pcongr3$channel <- factor(reg_pcongr3$channel, levels = c("Fz", "Cz", "Pz", "Oz"))
  43. # Define ROIs
  44. reg_pcongr2 <- reg_pcongr2 %>%
  45. mutate(
  46. ROI = case_when(
  47. channel %in% c("F1","F2","Fz") ~ "Frontal",
  48. channel %in% c("FC1","FC2","FCz") ~ "Fronto-Central",
  49. channel %in% c("C1","C2","Cz") ~ "Central",
  50. channel %in% c("CP1","CP2","CPz") ~ "Centro-Parietal",
  51. channel %in% c("P1","P2","Pz") ~ "Parietal",
  52. channel %in% c("O1","O2","Oz") ~ "Occipital",
  53. TRUE ~ NA_character_ # for any channels not listed
  54. )
  55. )
  56. reg_pcongr4 <- reg_pcongr2 %>%
  57. group_by(ID, time, PrevCongr, Congr, ROI) %>%
  58. summarise(value = mean(value, na.rm = TRUE)) %>%
  59. ungroup()
  60. reg_pcongr4$ROI <- factor(reg_pcongr4$ROI, levels = c("Frontal", "Fronto-Central", "Central", "Centro-Parietal", "Parietal", "Occipital"))
  61. reg_pcongr4 = na.omit(reg_pcongr4)
  62. ### Plot (midline electrodes)
  63. ggplot(reg_pcongr3, aes(x = time, y = value, color = Congr, linetype = PrevCongr)) +
  64. facet_wrap(channel ~ ., ncol = 1) +
  65. # Mean lines only
  66. stat_summary(fun = "mean", geom = "line", size = .2) +
  67. # Manual color: Con = blue, Inc = red
  68. scale_color_manual(values = c("Con" = "blue", "Inc" = "red")) +
  69. # Theme and layout
  70. theme_bw(base_size = 14) +
  71. theme(
  72. panel.grid = element_blank(),
  73. strip.background = element_rect(fill = "white"),
  74. strip.text = element_text(size = 12, face = "bold"),
  75. axis.title = element_text(size = 12),
  76. axis.text = element_text(size = 10),
  77. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  78. legend.position = "top"
  79. ) +
  80. # Reference lines
  81. #geom_hline(yintercept = 0, linetype = "dashed") +
  82. geom_vline(xintercept = 0, linetype = "dashed") +
  83. #geom_vline(xintercept = 500, linetype = "dashed", color = "blue") +
  84. # Axes and labels
  85. coord_cartesian(ylim = c(-1.5,-.5)) +
  86. labs(
  87. x = "Time (ms)",
  88. y = expression("Spectral Slope Values [a.u.]"),
  89. color = "Congruency", # Current trial
  90. linetype = "Previous Congr." # Previous trial
  91. ) +
  92. ggtitle("")
  93. # ggsave(file.path(folder_name, "raw_overview.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  94. # ggsave(file.path(folder_name, "raw_overview.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  95. ### Plot (electrode averages)
  96. ggplot(reg_pcongr4[reg_pcongr4$ROI %in% c("Frontal", "Central", "Occipital"),], aes(x = time, y = value, color = Congr, linetype = PrevCongr)) +
  97. facet_wrap(ROI ~ ., ncol = 1) +
  98. # Mean lines only
  99. stat_summary(fun = "mean", geom = "line", size = .2) +
  100. # Manual color: Con = blue, Inc = red
  101. scale_color_manual(values = c("Con" = "blue", "Inc" = "red")) +
  102. # Theme and layout
  103. theme_bw(base_size = 14) +
  104. theme(
  105. panel.grid = element_blank(),
  106. strip.background = element_rect(fill = "white"),
  107. strip.text = element_text(size = 12, face = "bold"),
  108. axis.title = element_text(size = 12),
  109. axis.text = element_text(size = 10),
  110. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  111. legend.position = "top"
  112. ) +
  113. # Reference lines
  114. #geom_hline(yintercept = 0, linetype = "dashed") +
  115. geom_vline(xintercept = 0, linetype = "dashed") +
  116. #geom_vline(xintercept = 500, linetype = "dashed", color = "blue") +
  117. # Axes and labels
  118. coord_cartesian(ylim = c(-1.5,-.5)) +
  119. labs(
  120. x = "Time (ms)",
  121. y = expression("Spectral Slope Values [a.u.]"),
  122. color = "Congruency", # Current trial
  123. linetype = "Previous Congr." # Previous trial
  124. ) +
  125. ggtitle("")
  126. # ggsave(file.path(folder_name, "raw_overview_av.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  127. # ggsave(file.path(folder_name, "raw_overview_av.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  128. rm(reg, reg_pcongr2, reg_pcongr3, reg_pcongr4)
  129. ```
  130. Create data sets and rename conditions
  131. ```{r}
  132. load("data_Pca.RData")
  133. reg <- scores
  134. #Rename and collapse across conditions
  135. # Create a named vector for bin labels
  136. bin_labels <- c(
  137. "Novel-RSI1000-Changed-pCon-Con",
  138. "Novel-RSI1000-Changed-pCon-Inc",
  139. "Novel-RSI1000-Changed-pInc-Con",
  140. "Novel-RSI1000-Changed-pInc-Inc",
  141. "Novel-RSI1000-Repeated-pCon-Con",
  142. "Novel-RSI1000-Repeated-pCon-Inc",
  143. "Novel-RSI1000-Repeated-pInc-Con",
  144. "Novel-RSI1000-Repeated-pInc-Inc",
  145. "Novel-RSI2000-Changed-pCon-Con",
  146. "Novel-RSI2000-Changed-pCon-Inc",
  147. "Novel-RSI2000-Changed-pInc-Con",
  148. "Novel-RSI2000-Changed-pInc-Inc",
  149. "Novel-RSI2000-Repeated-pCon-Con",
  150. "Novel-RSI2000-Repeated-pCon-Inc",
  151. "Novel-RSI2000-Repeated-pInc-Con",
  152. "Novel-RSI2000-Repeated-pInc-Inc",
  153. "Novel-RSI3000-Changed-pCon-Con",
  154. "Novel-RSI3000-Changed-pCon-Inc",
  155. "Novel-RSI3000-Changed-pInc-Con",
  156. "Novel-RSI3000-Changed-pInc-Inc",
  157. "Novel-RSI3000-Repeated-pCon-Con",
  158. "Novel-RSI3000-Repeated-pCon-Inc",
  159. "Novel-RSI3000-Repeated-pInc-Con",
  160. "Novel-RSI3000-Repeated-pInc-Inc",
  161. "Novel-RSI5000-Changed-pCon-Con",
  162. "Novel-RSI5000-Changed-pCon-Inc",
  163. "Novel-RSI5000-Changed-pInc-Con",
  164. "Novel-RSI5000-Changed-pInc-Inc",
  165. "Novel-RSI5000-Repeated-pCon-Con",
  166. "Novel-RSI5000-Repeated-pCon-Inc",
  167. "Novel-RSI5000-Repeated-pInc-Con",
  168. "Novel-RSI5000-Repeated-pInc-Inc",
  169. "Frequent-RSI1000-Changed-pCon-Con",
  170. "Frequent-RSI1000-Changed-pCon-Inc",
  171. "Frequent-RSI1000-Changed-pInc-Con",
  172. "Frequent-RSI1000-Changed-pInc-Inc",
  173. "Frequent-RSI1000-Repeated-pCon-Con",
  174. "Frequent-RSI1000-Repeated-pCon-Inc",
  175. "Frequent-RSI1000-Repeated-pInc-Con",
  176. "Frequent-RSI1000-Repeated-pInc-Inc",
  177. "Frequent-RSI2000-Changed-pCon-Con",
  178. "Frequent-RSI2000-Changed-pCon-Inc",
  179. "Frequent-RSI2000-Changed-pInc-Con",
  180. "Frequent-RSI2000-Changed-pInc-Inc",
  181. "Frequent-RSI2000-Repeated-pCon-Con",
  182. "Frequent-RSI2000-Repeated-pCon-Inc",
  183. "Frequent-RSI2000-Repeated-pInc-Con",
  184. "Frequent-RSI2000-Repeated-pInc-Inc",
  185. "Frequent-RSI3000-Changed-pCon-Con",
  186. "Frequent-RSI3000-Changed-pCon-Inc",
  187. "Frequent-RSI3000-Changed-pInc-Con",
  188. "Frequent-RSI3000-Changed-pInc-Inc",
  189. "Frequent-RSI3000-Repeated-pCon-Con",
  190. "Frequent-RSI3000-Repeated-pCon-Inc",
  191. "Frequent-RSI3000-Repeated-pInc-Con",
  192. "Frequent-RSI3000-Repeated-pInc-Inc",
  193. "Frequent-RSI5000-Changed-pCon-Con",
  194. "Frequent-RSI5000-Changed-pCon-Inc",
  195. "Frequent-RSI5000-Changed-pInc-Con",
  196. "Frequent-RSI5000-Changed-pInc-Inc",
  197. "Frequent-RSI5000-Repeated-pCon-Con",
  198. "Frequent-RSI5000-Repeated-pCon-Inc",
  199. "Frequent-RSI5000-Repeated-pInc-Con",
  200. "Frequent-RSI5000-Repeated-pInc-Inc"
  201. )
  202. names(bin_labels) <- as.character(1:64)
  203. # First, ensure the Bin column is numeric for matching
  204. reg$Bin <- as.numeric(as.character(reg$Bin))
  205. # Now, replace the numeric Bins with their corresponding labels
  206. reg$BinLabel <- bin_labels[as.character(reg$Bin)]
  207. # Separate the 'BinLabel' column into new columns
  208. reg <- separate(reg, col = BinLabel, into = c("StimulusRep", "RSI", "CategoryRep", "PrevCongr", "Congr"), sep = "-")
  209. #Reorder columns
  210. reg <- reg[,c("ID", "time", "Bin", "StimulusRep", "CategoryRep", "RSI", "PrevCongr", "Congr", "MR1", "MR2", "MR3", "MR4", "MR5")]
  211. #Narrow down the time window for analyses
  212. reg <- reg[reg$time %in% c(-160:1280),]
  213. #To the long format
  214. reg <- melt(reg, id.vars = c("ID", "time", "Bin", "StimulusRep", "CategoryRep", "RSI", "PrevCongr", "Congr"))
  215. #Factor order
  216. rotFit$variance_explained_percent
  217. unique(reg$variable)
  218. # Get the factor names ordered by variance explained
  219. ordered_factors <- names(sort(rotFit$variance_explained_percent, decreasing = TRUE))
  220. # Reorder reg$variable based on the ordered factors
  221. reg$variable <- factor(reg$variable, levels = ordered_factors)
  222. # Verify the order
  223. levels(reg$variable)
  224. # Rename levels of reg$variable
  225. levels(reg$variable) <- c("OCCIPITAL (MR3)", "FRONTAL (MR2)", "CENTRAL (MR1)", "TEMPORAL L (MR5)", "TEMPORAL R (MR4)")
  226. #Exclude factors with too small variance
  227. reg <- reg[!reg$variable %in% c("TEMPORAL L (MR5)", "TEMPORAL R (MR4)"),]
  228. reg <- droplevels(reg)
  229. # Reorder factor levels
  230. reg$variable <- factor(reg$variable, levels = c("FRONTAL (MR2)", "CENTRAL (MR1)", "OCCIPITAL (MR3)"))
  231. # Verify the new order
  232. levels(reg$variable)
  233. # Drop the last two elements
  234. variance_explained <- rotFit$variance_explained[1:3]
  235. # Rename the variables
  236. names(variance_explained) <- c("CENTRAL", "FRONTAL", "OCCIPITAL")
  237. # Reorder them: frontal, central, occipital
  238. variance_explained <- variance_explained[c("FRONTAL", "CENTRAL", "OCCIPITAL")]
  239. # Format the reordered variance explained as a text string for the caption
  240. caption_text <- paste(
  241. "Variance Explained:",
  242. paste(names(variance_explained), sprintf("%.2f%%", variance_explained), collapse = ", ")
  243. )
  244. ```
  245. Info for stats & plots
  246. ```{r}
  247. reg$value_to_stats = reg$value
  248. ```
  249. Global Changes in Spectral Slope Across Time Windows and Scalp Regions
  250. ```{r}
  251. ### Average across bins
  252. reg_av = data.table::dcast(setDT(reg), ID + time + variable ~ ., value.var = c("value_to_stats"), mean, na.rm = TRUE)
  253. setnames(reg_av, old = ".", new = "value")
  254. reg_av$time_chunk <- cut(reg_av$time, breaks = time_intervals, include.lowest = TRUE, ordered_result = TRUE, dig.lab = 5)
  255. reg_av$time_chunk <- factor(reg_av$time_chunk, levels = time_chunk_levels, ordered = FALSE)
  256. ### Statistics
  257. dat = reg_av
  258. dat = dat[complete.cases(dat$time_chunk)]
  259. dat <- setDT(dcast(dat, ID + variable + time_chunk ~ ., mean, value.var = c("value"), na.rm = TRUE))
  260. dat = data.table(droplevels(dat))
  261. dat = dat[complete.cases(dat$time_chunk)]
  262. # Create an empty data.table to store the t-test results
  263. pvals <- data.table(
  264. variable = character(), time_chunk = character(),
  265. t.value = numeric(), p.value = numeric(), cohen_d = numeric()
  266. )
  267. # Get unique levels
  268. unique_time <- unique(dat$time_chunk)
  269. unique_var <- unique(dat$variable)
  270. for (tpnt in unique_time) {
  271. for (var in unique_var) {
  272. subset_data <- dat[variable == var & time_chunk %in% c(tpnt, "[-160,0]")]
  273. # Get paired data
  274. baseline <- subset_data[time_chunk == "[-160,0]"][order(ID)]$.
  275. tp_data <- subset_data[time_chunk == tpnt][order(ID)]$.
  276. # Ensure same IDs are used for pairing
  277. common_ids <- intersect(
  278. subset_data[time_chunk == "[-160,0]"]$ID,
  279. subset_data[time_chunk == tpnt]$ID
  280. )
  281. baseline <- subset_data[time_chunk == "[-160,0]" & ID %in% common_ids][order(ID)]$.
  282. tp_data <- subset_data[time_chunk == tpnt & ID %in% common_ids][order(ID)]$.
  283. # Paired t-test
  284. t_test_result <- t.test(tp_data, baseline,
  285. alternative = "two.sided", paired = TRUE, conf.level = 0.95)
  286. # Cohen's d for paired samples
  287. diff_scores <- tp_data - baseline
  288. cohen_d <- mean(diff_scores) / sd(diff_scores)
  289. # Append results
  290. pvals <- rbind(
  291. pvals,
  292. data.table(
  293. variable = var, time_chunk = tpnt,
  294. t.value = t_test_result$statistic,
  295. p.value = t_test_result$p.value,
  296. cohen_d = cohen_d
  297. )
  298. )
  299. }
  300. }
  301. # Significance flags
  302. pvals$crit1 <- ifelse(pvals$p.value <= .05, 1, NA)
  303. pvals$crit2 <- ifelse(pvals$p.value <= .01, 1, NA)
  304. # Convert time_chunk to a factor with the specified levels
  305. pvals$time_chunk <- factor(pvals$time_chunk, levels = time_chunk_levels)
  306. pvals$variable <- factor(pvals$variable, levels = c("FRONTAL (MR2)", "CENTRAL (MR1)", "OCCIPITAL (MR3)"))
  307. writexl::write_xlsx(pvals, file.path("ttest_results.xlsx"))
  308. ### Plot with stats
  309. # Ensure shading_data$brightness is a factor
  310. shading_data$brightness <- as.factor(shading_data$brightness)
  311. ggplot(reg_av, aes(x = time, y = value)) +
  312. # Facet by variable
  313. facet_wrap(. ~ variable, ncol = 1, drop = FALSE) +
  314. # Add shaded regions
  315. geom_rect(data = shading_data,
  316. aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
  317. fill = factor(brightness)),
  318. alpha = 0.5, inherit.aes = FALSE, show.legend = FALSE) +
  319. scale_fill_manual(values = scales::alpha(
  320. c("lightgrey", "lightgrey", "grey", "lightgrey", "grey",
  321. "lightgrey", "grey", "lightgrey", "grey", "lightgrey", "grey"), 0.5)) +
  322. # Mean ± SE ribbon and mean line
  323. stat_summary(fun.data = mean_se, geom = "ribbon", alpha = .15, fill = "grey50") +
  324. stat_summary(fun = "mean", geom = "line", size = 1, color = "black") +
  325. # APA-style theme
  326. theme_bw(base_size = 12) +
  327. theme(
  328. panel.grid = element_blank(),
  329. strip.background = element_rect(fill = "white"),
  330. strip.text = element_text(size = 12, face = "bold"),
  331. axis.title = element_text(size = 12),
  332. axis.text = element_text(size = 10),
  333. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  334. legend.position = "none"
  335. ) +
  336. # Axes and labels
  337. geom_hline(yintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  338. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  339. labs(
  340. x = "Time (ms)",
  341. y = expression("Spectral Slope Values [a.u.]"),
  342. title = ""
  343. ) +
  344. # Horizontal lines for significant time points (black for crit1)
  345. geom_segment(
  346. data = reg_av %>%
  347. inner_join(pvals[pvals$crit1 == 1], by = c("time_chunk", "variable")),
  348. aes(x = time - 20, xend = time + 20,
  349. y = min(value, na.rm = TRUE) - 0.1,
  350. yend = min(value, na.rm = TRUE) - 0.1),
  351. inherit.aes = FALSE, color = "black", size = 1.5
  352. ) +
  353. # Horizontal lines for significant time points (dark green for crit2)
  354. geom_segment(
  355. data = reg_av %>%
  356. inner_join(pvals[pvals$crit2 == 1], by = c("time_chunk", "variable")),
  357. aes(x = time - 20, xend = time + 20,
  358. y = min(value, na.rm = TRUE) - 0.1,
  359. yend = min(value, na.rm = TRUE) - 0.1),
  360. inherit.aes = FALSE, color = "darkgreen", size = 1.5
  361. ) +
  362. # Manual y-axis range
  363. coord_cartesian(ylim = c(min(reg_av$value, na.rm = TRUE) - 0.3, NA))
  364. # ggsave(file.path(folder_name, "global_ttest.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  365. # ggsave(file.path(folder_name, "global_ttest.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  366. ```
  367. Repeated-Measures ANOVA Assessing the Effects of Experimental Manipulation on Spectral Slope across Time Windows and Scalp Regions
  368. ```{r}
  369. ### Average across bins
  370. reg_pcongr2 = data.table::dcast(setDT(reg), ID + variable + time + PrevCongr + Congr ~ ., value.var = c("value_to_stats"), mean, na.rm = TRUE)
  371. setnames(reg_pcongr2, old = ".", new = "value")
  372. reg_pcongr2$Congr <- factor(reg_pcongr2$Congr, levels = c("Con", "Inc"))
  373. reg_pcongr2$PrevCongr <- factor(reg_pcongr2$PrevCongr, levels = c("pCon", "pInc"))
  374. reg_pcongr2$time_chunk <- cut(reg_pcongr2$time, breaks = time_intervals, include.lowest = TRUE, ordered_result = TRUE, dig.lab = 5)
  375. reg_pcongr2$time_chunk <- factor(reg_pcongr2$time_chunk, levels = time_chunk_levels, ordered = FALSE)
  376. ### Statistics
  377. dat = reg_pcongr2
  378. dat = data.table(droplevels(dat))
  379. dat = setDT(dat) %>% dcast(ID + variable + time_chunk + PrevCongr + Congr ~ ., mean, value.var = c("value"), na.rm = TRUE)
  380. dat = dat[complete.cases(dat$time_chunk)]
  381. dat$ID = as.factor(dat$ID)
  382. colnames(dat)[6] <- "value"
  383. # Create an empty data.table to store the ANOVA results
  384. pvals <- data.table(
  385. time_chunk = numeric(), variable = character(),
  386. Congr_F = numeric(), Congr_p = numeric(), Congr_eta2p = numeric(),
  387. PrevCongr_F = numeric(), PrevCongr_p = numeric(), PrevCongr_eta2p = numeric(),
  388. PrevCongrCongr_F = numeric(), PrevCongrCongr_p = numeric(), PrevCongrCongr_eta2p = numeric()
  389. )
  390. # Get unique levels
  391. unique_variables <- unique(dat$variable)
  392. unique_time <- droplevels(unique(dat$time_chunk)[-1])
  393. for (tpnt in unique_time) {
  394. for (var in unique_variables) {
  395. subset_data <- dat %>% filter(time_chunk == tpnt, variable == var)
  396. result <- summary(stats::aov(data = subset_data, value ~ PrevCongr * Congr + Error(ID/(PrevCongr * Congr))))
  397. # Congr
  398. congr_ss_effect <- result$`Error: ID:Congr`[[1]]$`Sum Sq`[1]
  399. congr_ss_error <- result$`Error: ID:Congr`[[1]]$`Sum Sq`[2]
  400. congr_f <- result$`Error: ID:Congr`[[1]]$`F value`[1]
  401. congr_p <- result$`Error: ID:Congr`[[1]]$`Pr(>F)`[1]
  402. congr_eta2p <- congr_ss_effect / (congr_ss_effect + congr_ss_error)
  403. # PrevCongr
  404. prevcongr_ss_effect <- result$`Error: ID:PrevCongr`[[1]]$`Sum Sq`[1]
  405. prevcongr_ss_error <- result$`Error: ID:PrevCongr`[[1]]$`Sum Sq`[2]
  406. prevcongr_f <- result$`Error: ID:PrevCongr`[[1]]$`F value`[1]
  407. prevcongr_p <- result$`Error: ID:PrevCongr`[[1]]$`Pr(>F)`[1]
  408. prevcongr_eta2p <- prevcongr_ss_effect / (prevcongr_ss_effect + prevcongr_ss_error)
  409. # Interaction
  410. inter_ss_effect <- result$`Error: ID:PrevCongr:Congr`[[1]]$`Sum Sq`[1]
  411. inter_ss_error <- result$`Error: ID:PrevCongr:Congr`[[1]]$`Sum Sq`[2]
  412. prevcongrcongr_f <- result$`Error: ID:PrevCongr:Congr`[[1]]$`F value`[1]
  413. prevcongrcongr_p <- result$`Error: ID:PrevCongr:Congr`[[1]]$`Pr(>F)`[1]
  414. prevcongrcongr_eta2p <- inter_ss_effect / (inter_ss_effect + inter_ss_error)
  415. # Append to results table
  416. pvals <- rbind(
  417. pvals,
  418. data.table(
  419. time_chunk = tpnt, variable = var,
  420. Congr_F = congr_f, Congr_p = congr_p, Congr_eta2p = congr_eta2p,
  421. PrevCongr_F = prevcongr_f, PrevCongr_p = prevcongr_p, PrevCongr_eta2p = prevcongr_eta2p,
  422. PrevCongrCongr_F = prevcongrcongr_f, PrevCongrCongr_p = prevcongrcongr_p, PrevCongrCongr_eta2p = prevcongrcongr_eta2p
  423. )
  424. )
  425. }
  426. }
  427. # Add significance flags
  428. pvals$Congr_crit1 <- ifelse(pvals$Congr_p <= .05, 1, NA)
  429. pvals$Congr_crit2 <- ifelse(pvals$Congr_p <= .01, 1, NA)
  430. pvals$PrevCongr_crit1 <- ifelse(pvals$PrevCongr_p <= .05, 1, NA)
  431. pvals$PrevCongr_crit2 <- ifelse(pvals$PrevCongr_p <= .01, 1, NA)
  432. pvals$PrevCongrCongr_crit1 <- ifelse(pvals$PrevCongrCongr_p <= .05, 1, NA)
  433. pvals$PrevCongrCongr_crit2 <- ifelse(pvals$PrevCongrCongr_p <= .01, 1, NA)
  434. # Convert time_chunk to a factor with the specified levels
  435. pvals$time_chunk <- factor(pvals$time_chunk, levels = time_chunk_levels, ordered = FALSE)
  436. pvals$variable <- factor(pvals$variable, levels = c("FRONTAL (MR2)", "CENTRAL (MR1)", "OCCIPITAL (MR3)"))
  437. writexl::write_xlsx(pvals, file.path("anova_results.xlsx"))
  438. ###DIFFERENCE WAVES TO PLOT
  439. ###Congr
  440. # Calculate difference between Incongruent and Congruent
  441. aov_congr <- reg_pcongr2 %>%
  442. pivot_wider(names_from = Congr, values_from = value) %>%
  443. mutate(value = Inc - Con) %>%
  444. select(time, time_chunk, variable, value)
  445. ggplot(aov_congr, aes(x = time, y = value)) +
  446. facet_wrap(. ~ variable, ncol = 1, drop = FALSE) +
  447. # Add shaded regions
  448. geom_rect(data = shading_data,
  449. aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
  450. fill = factor(brightness)),
  451. alpha = 0.5, inherit.aes = FALSE, show.legend = FALSE) +
  452. scale_fill_manual(values = scales::alpha(
  453. c("lightgrey", "lightgrey", "grey", "lightgrey", "grey",
  454. "lightgrey", "grey", "lightgrey", "grey", "lightgrey", "grey"), 0.5)) +
  455. # Mean ± SE ribbon and mean line
  456. stat_summary(fun.data = mean_se, geom = "ribbon", alpha = .1, fill = "grey50") +
  457. stat_summary(fun = "mean", geom = "line", size = 1.2, color = "black") +
  458. # Theme and layout
  459. theme_bw(base_size = 14) +
  460. theme(
  461. panel.grid = element_blank(),
  462. strip.background = element_rect(fill = "white"),
  463. strip.text = element_text(size = 12, face = "bold"),
  464. axis.title = element_text(size = 12),
  465. axis.text = element_text(size = 10),
  466. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  467. legend.position = "none"
  468. ) +
  469. # Axes and labels
  470. geom_hline(yintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  471. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  472. coord_cartesian(ylim = c(-.12, .1)) +
  473. labs(
  474. x = "Time (ms)",
  475. y = expression("Spectral Slope Difference [a.u.]")
  476. ) +
  477. #ggtitle("Wavelet: Difference in 1/f slope (Incongruent – Congruent)") +
  478. # Horizontal lines for significant time points (black for crit1)
  479. geom_segment(
  480. data = aov_congr %>%
  481. inner_join(pvals[pvals$Congr_crit1 == 1], by = c("variable", "time_chunk")),
  482. aes(x = time - 20, xend = time + 20,
  483. y = -.1,
  484. yend = -.1),
  485. inherit.aes = FALSE, color = "black", size = 1.5
  486. ) +
  487. # Horizontal lines for significant time points (dark green for crit2)
  488. geom_segment(
  489. data = aov_congr %>%
  490. inner_join(pvals[pvals$Congr_crit2 == 1], by = c("variable", "time_chunk")),
  491. aes(x = time - 20, xend = time + 20,
  492. y = -.12,
  493. yend = - .12),
  494. inherit.aes = FALSE, color = "darkgreen", size = 1.5
  495. )
  496. # ggsave(file.path(folder_name, "anova_congr.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  497. # ggsave(file.path(folder_name, "anova_congr.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  498. ###Previous Congr
  499. # Calculate difference between Incongruent and Congruent
  500. aov_pcongr <- reg_pcongr2 %>%
  501. pivot_wider(names_from = PrevCongr, values_from = value) %>%
  502. mutate(value = pInc - pCon) %>%
  503. select(time, time_chunk, variable, value)
  504. ggplot(aov_pcongr, aes(x = time, y = value)) +
  505. facet_wrap(. ~ variable, ncol = 1, drop = FALSE) +
  506. # Add shaded regions
  507. geom_rect(data = shading_data,
  508. aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
  509. fill = factor(brightness)),
  510. alpha = 0.5, inherit.aes = FALSE, show.legend = FALSE) +
  511. scale_fill_manual(values = scales::alpha(
  512. c("lightgrey", "lightgrey", "grey", "lightgrey", "grey",
  513. "lightgrey", "grey", "lightgrey", "grey", "lightgrey", "grey"), 0.5)) +
  514. # Mean ± SE ribbon and mean line
  515. stat_summary(fun.data = mean_se, geom = "ribbon", alpha = .1, fill = "grey50") +
  516. stat_summary(fun = "mean", geom = "line", size = 1.2, color = "black") +
  517. # Theme and layout
  518. theme_bw(base_size = 14) +
  519. theme(
  520. panel.grid = element_blank(),
  521. strip.background = element_rect(fill = "white"),
  522. strip.text = element_text(size = 12, face = "bold"),
  523. axis.title = element_text(size = 12),
  524. axis.text = element_text(size = 10),
  525. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  526. legend.position = "none"
  527. ) +
  528. # Axes and labels
  529. geom_hline(yintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  530. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  531. coord_cartesian(ylim = c(-.12, .1)) +
  532. labs(
  533. x = "Time (ms)",
  534. y = expression("Spectral Slope Difference [a.u.]")
  535. ) +
  536. #ggtitle("Wavelet: Difference in 1/f slope (Incongruent – Congruent)") +
  537. # Horizontal lines for significant time points (black for crit1)
  538. geom_segment(
  539. data = aov_pcongr %>%
  540. inner_join(pvals[pvals$PrevCongr_crit1 == 1], by = c("variable", "time_chunk")),
  541. aes(x = time - 20, xend = time + 20,
  542. y = -.1,
  543. yend = -.1),
  544. inherit.aes = FALSE, color = "black", size = 1.5
  545. ) +
  546. # Horizontal lines for significant time points (dark green for crit2)
  547. geom_segment(
  548. data = aov_pcongr %>%
  549. inner_join(pvals[pvals$PrevCongr_crit2 == 1], by = c("variable", "time_chunk")),
  550. aes(x = time - 20, xend = time + 20,
  551. y = -.12,
  552. yend = - .12),
  553. inherit.aes = FALSE, color = "darkgreen", size = 1.5
  554. )
  555. # ggsave(file.path(folder_name, "anova_pcongr.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  556. # ggsave(file.path(folder_name, "anova_pcongr.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  557. ###Interaction
  558. aov_pcongr_by_congr <- reg_pcongr2 %>%
  559. pivot_wider(names_from = PrevCongr, values_from = value) %>%
  560. mutate(value = pInc - pCon) %>%
  561. select(time, time_chunk, variable, Congr, value)
  562. ggplot(aov_pcongr_by_congr, aes(x = time, y = value, color = Congr)) +
  563. facet_wrap(. ~ variable, ncol = 1, drop = FALSE) +
  564. # Add shaded regions
  565. geom_rect(data = shading_data,
  566. aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
  567. fill = factor(brightness)),
  568. alpha = 0.5, inherit.aes = FALSE, show.legend = FALSE) +
  569. scale_fill_manual(values = scales::alpha(
  570. c("lightgrey", "lightgrey", "grey", "lightgrey", "grey",
  571. "lightgrey", "grey", "lightgrey", "grey", "lightgrey", "grey"), 0.5)) +
  572. # Mean ± SE and mean line per Congr level
  573. stat_summary(fun.data = mean_se, geom = "ribbon", aes(fill = Congr), alpha = .1, color = NA) +
  574. stat_summary(fun = mean, geom = "line", size = 1.2) +
  575. # Color mapping
  576. scale_color_manual(values = c("Con" = "blue", "Inc" = "red")) +
  577. # Theme
  578. theme_bw(base_size = 14) +
  579. theme(
  580. panel.grid = element_blank(),
  581. strip.background = element_rect(fill = "white"),
  582. strip.text = element_text(size = 12, face = "bold"),
  583. axis.title = element_text(size = 12),
  584. axis.text = element_text(size = 10),
  585. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  586. legend.position = "none"
  587. ) +
  588. # Axes and labels
  589. geom_hline(yintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  590. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  591. coord_cartesian(ylim = c(-.12, .1)) +
  592. labs(
  593. x = "Time (ms)",
  594. y = expression("Spectral Slope Difference [a.u.]"),
  595. color = "Congruency"
  596. ) +
  597. # Significance for crit1
  598. geom_segment(
  599. data = aov_pcongr_by_congr %>%
  600. inner_join(pvals[pvals$PrevCongrCongr_crit1 == 1], by = c("variable", "time_chunk")),
  601. aes(x = time - 20, xend = time + 20, y = -.1, yend = -.1),
  602. inherit.aes = FALSE, color = "black", size = 1.5
  603. ) +
  604. # Significance for crit2
  605. geom_segment(
  606. data = aov_pcongr_by_congr %>%
  607. inner_join(pvals[pvals$PrevCongrCongr_crit2 == 1], by = c("variable", "time_chunk")),
  608. aes(x = time - 20, xend = time + 20, y = -.12, yend = -.12),
  609. inherit.aes = FALSE, color = "darkgreen", size = 1.5
  610. )
  611. # ggsave(file.path(folder_name, "anova_pcongrcongr.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  612. # ggsave(file.path(folder_name, "anova_pcongrcongr.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  613. aov_pcongr_by_congr1 <- reg_pcongr2 %>%
  614. pivot_wider(names_from = Congr, values_from = value) %>%
  615. mutate(value = Inc - Con) %>%
  616. select(time, time_chunk, variable, PrevCongr, value)
  617. ggplot(aov_pcongr_by_congr1, aes(x = time, y = value, color = PrevCongr)) +
  618. facet_wrap(. ~ variable, ncol = 1, drop = FALSE) +
  619. # Add shaded regions
  620. geom_rect(data = shading_data,
  621. aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
  622. fill = factor(brightness)),
  623. alpha = 0.5, inherit.aes = FALSE, show.legend = FALSE) +
  624. scale_fill_manual(values = scales::alpha(
  625. c("lightgrey", "lightgrey", "grey", "lightgrey", "grey",
  626. "lightgrey", "grey", "lightgrey", "grey", "lightgrey", "grey"), 0.5)) +
  627. # Mean ± SE and mean line per Congr level
  628. stat_summary(fun.data = mean_se, geom = "ribbon", aes(fill = PrevCongr), alpha = .1, color = NA) +
  629. stat_summary(fun = mean, geom = "line", size = 1.2) +
  630. # Color mapping
  631. scale_color_manual(values = c("pCon" = "blue", "pInc" = "red")) +
  632. # Theme
  633. theme_bw(base_size = 14) +
  634. theme(
  635. panel.grid = element_blank(),
  636. strip.background = element_rect(fill = "white"),
  637. strip.text = element_text(size = 12, face = "bold"),
  638. axis.title = element_text(size = 12),
  639. axis.text = element_text(size = 10),
  640. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  641. legend.position = "none"
  642. ) +
  643. # Axes and labels
  644. geom_hline(yintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  645. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  646. coord_cartesian(ylim = c(-.12, .1)) +
  647. labs(
  648. x = "Time (ms)",
  649. y = expression("Spectral Slope Difference [a.u.]"),
  650. color = "Congruency"
  651. ) +
  652. # Significance for crit1
  653. geom_segment(
  654. data = aov_pcongr_by_congr %>%
  655. inner_join(pvals[pvals$PrevCongrCongr_crit1 == 1], by = c("variable", "time_chunk")),
  656. aes(x = time - 20, xend = time + 20, y = -.1, yend = -.1),
  657. inherit.aes = FALSE, color = "black", size = 1.5
  658. ) +
  659. # Significance for crit2
  660. geom_segment(
  661. data = aov_pcongr_by_congr %>%
  662. inner_join(pvals[pvals$PrevCongrCongr_crit2 == 1], by = c("variable", "time_chunk")),
  663. aes(x = time - 20, xend = time + 20, y = -.12, yend = -.12),
  664. inherit.aes = FALSE, color = "darkgreen", size = 1.5
  665. )
  666. # ggsave(file.path(folder_name, "anova_pcongrcongr1.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  667. # ggsave(file.path(folder_name, "anova_pcongrcongr1.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  668. ###Interaction combined
  669. head(aov_pcongr_by_congr)
  670. head(aov_pcongr_by_congr1)
  671. colnames(aov_pcongr_by_congr)[4] <- "Cond"
  672. colnames(aov_pcongr_by_congr1)[4] <- "Cond"
  673. aov_pcongr_by_congr2 <- rbind(aov_pcongr_by_congr, aov_pcongr_by_congr1)
  674. ggplot(aov_pcongr_by_congr2, aes(x = time, y = value, color = Cond)) +
  675. facet_wrap(. ~ variable, ncol = 1, drop = FALSE) +
  676. # Add shaded regions
  677. geom_rect(data = shading_data,
  678. aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
  679. fill = factor(brightness)),
  680. alpha = 0.5, inherit.aes = FALSE, show.legend = FALSE) +
  681. scale_fill_manual(values = scales::alpha(
  682. c("lightgrey", "lightgrey", "grey", "lightgrey", "grey",
  683. "lightgrey", "grey", "lightgrey", "grey", "lightgrey", "grey"), 0.5)) +
  684. # Mean ± SE and mean line per Congr level
  685. stat_summary(fun.data = mean_se, geom = "ribbon", aes(fill = Cond), alpha = .1, color = NA) +
  686. stat_summary(fun = mean, geom = "line", size = 1.2) +
  687. # Color mapping
  688. scale_color_manual(values = c("pCon" = "#56B4E9", "pInc" = "#0072B2", "Con" = "#E69F00", "Inc" = "#D55E00")) +
  689. # Theme
  690. theme_bw(base_size = 14) +
  691. theme(
  692. panel.grid = element_blank(),
  693. strip.background = element_rect(fill = "white"),
  694. strip.text = element_text(size = 12, face = "bold"),
  695. axis.title = element_text(size = 12),
  696. axis.text = element_text(size = 10),
  697. plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
  698. legend.position = "none"
  699. ) +
  700. # Axes and labels
  701. geom_hline(yintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  702. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 0.3) +
  703. coord_cartesian(ylim = c(-.12, .1)) +
  704. labs(
  705. x = "Time (ms)",
  706. y = expression("Spectral Slope Difference [a.u.]"),
  707. color = "Congruency"
  708. ) +
  709. # Significance for crit1
  710. geom_segment(
  711. data = aov_pcongr_by_congr %>%
  712. inner_join(pvals[pvals$PrevCongrCongr_crit1 == 1], by = c("variable", "time_chunk")),
  713. aes(x = time - 20, xend = time + 20, y = -.1, yend = -.1),
  714. inherit.aes = FALSE, color = "black", size = 1.5
  715. ) +
  716. # Significance for crit2
  717. geom_segment(
  718. data = aov_pcongr_by_congr %>%
  719. inner_join(pvals[pvals$PrevCongrCongr_crit2 == 1], by = c("variable", "time_chunk")),
  720. aes(x = time - 20, xend = time + 20, y = -.12, yend = -.12),
  721. inherit.aes = FALSE, color = "darkgreen", size = 1.5
  722. )
  723. # ggsave(file.path(folder_name, "anova_pcongrcongr2.jpg"), width = 8, height = 15, dpi = 600, scale = .5)
  724. # ggsave(file.path(folder_name, "anova_pcongrcongr2.pdf"), width = 8, height = 15, dpi = 600, scale = .5)
  725. ```
  726. Follow-ups for rmANOVA
  727. ```{r}
  728. #pCon vs pInc within Congr level, for selected ROI × time bin
  729. unique(time_chunk_levels) # sanity-check available bins
  730. #Boxplot to show distribution
  731. plot_box_all_conditions <- function(dat_roi,
  732. ylab = "Spectral Slope [a.u.]",
  733. ylim = c()) {
  734. dat_roi$Cond <- interaction(dat_roi$Congr, dat_roi$PrevCongr,
  735. sep = "-",
  736. lex.order = TRUE)
  737. dat_roi$Cond <- factor(dat_roi$Cond,
  738. levels = c("Con-pCon",
  739. "Con-pInc",
  740. "Inc-pCon",
  741. "Inc-pInc"))
  742. ggplot(dat_roi, aes(x = Cond, y = value, fill = Congr)) +
  743. facet_grid(. ~ variable + time_chunk) +
  744. geom_boxplot(
  745. width = .6,
  746. alpha = .6,
  747. outlier.shape = NA
  748. ) +
  749. geom_jitter(
  750. width = .08,
  751. alpha = .35,
  752. size = 1.5
  753. ) +
  754. stat_summary(
  755. fun = mean,
  756. geom = "point",
  757. size = 3.5,
  758. shape = 21,
  759. fill = "white",
  760. color = "black"
  761. ) +
  762. scale_fill_manual(values = c("Con" = "red", "Inc" = "blue")) +
  763. theme_bw(base_size = 14) +
  764. theme(
  765. panel.grid = element_blank(),
  766. strip.background = element_rect(fill = "white"),
  767. strip.text = element_text(size = 12, face = "bold"),
  768. axis.title = element_text(size = 12),
  769. axis.text = element_text(size = 10)
  770. ) +
  771. labs(
  772. x = "Condition (Congruency × Previous Congruency)",
  773. y = ylab
  774. ) +
  775. coord_cartesian(ylim = ylim)
  776. }
  777. # ==============================================================================
  778. # 1) FRONTAL (MR2), time_chunk = (160,320]
  779. # ==============================================================================
  780. # -- Select the ROI × time bin -------------------------------------------------
  781. dat1 <- subset(dat, time_chunk == "(160,320]" & variable == "FRONTAL (MR2)")
  782. stopifnot(nrow(dat1) > 0)
  783. # -- Quick 2×2 cell means (Con/Inc × pCon/pInc) --------------------------------
  784. means_2x2 <- data.table::dcast(
  785. data.table::as.data.table(dat1),
  786. Congr + PrevCongr ~ .,
  787. value.var = "value",
  788. fun.aggregate = mean, na.rm = TRUE
  789. )
  790. data.table::setnames(means_2x2, ".", "mean")
  791. print(means_2x2)
  792. # -- Paired t-tests: pCon vs pInc within Congr = "Con" -------------------------
  793. w_con <- dat1[dat1$Congr == "Con", c("ID","PrevCongr","value")]
  794. w_con <- tidyr::pivot_wider(w_con, names_from = PrevCongr, values_from = value)
  795. w_con <- subset(w_con, !is.na(pCon) & !is.na(pInc)) # ensure the same IDs contribute to both levels
  796. w_con <- w_con[order(w_con$ID), ]
  797. t.test(w_con$pInc, w_con$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  798. # -- Paired t-tests: pCon vs pInc within Congr = "Inc" -------------------------
  799. w_inc <- dat1[dat1$Congr == "Inc", c("ID","PrevCongr","value")]
  800. w_inc <- tidyr::pivot_wider(w_inc, names_from = PrevCongr, values_from = value)
  801. w_inc <- subset(w_inc, !is.na(pCon) & !is.na(pInc))
  802. w_inc <- w_inc[order(w_inc$ID), ]
  803. t.test(w_inc$pInc, w_inc$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  804. # -- Plot (within-subject SE via Rmisc::summarySEwithin) ----------------------
  805. library(Rmisc) # summarySEwithin
  806. dat1$PrevCongr <- factor(dat1$PrevCongr, levels = c("pCon","pInc"))
  807. dat1$Congr <- factor(dat1$Congr, levels = c("Con","Inc"))
  808. summary_dat <- summarySEwithin(
  809. dat1,
  810. measurevar = "value",
  811. withinvars = c("variable","time_chunk","PrevCongr","Congr"),
  812. idvar = "ID"
  813. )
  814. ggplot(summary_dat, aes(x = PrevCongr, y = value, color = Congr, group = Congr)) +
  815. facet_grid(. ~ variable + time_chunk) +
  816. geom_line(position = position_dodge(0.2), size = 0.8) +
  817. geom_point(position = position_dodge(0.2), size = 3) +
  818. geom_errorbar(aes(ymin = value - se, ymax = value + se),
  819. width = 0.1, position = position_dodge(0.2)) +
  820. scale_color_manual(values = c("Con" = "red", "Inc" = "blue")) +
  821. theme_bw(base_size = 14) +
  822. theme(
  823. panel.grid = element_blank(),
  824. strip.background = element_rect(fill = "white"),
  825. strip.text = element_text(size = 12, face = "bold"),
  826. axis.title = element_text(size = 12),
  827. axis.text = element_text(size = 10),
  828. plot.title = element_text(size = 14, face = "bold", hjust = 0.5)
  829. ) +
  830. labs(x = "Previous Congruency", y = "Spectral Slope [a.u.]", title = NULL) +
  831. coord_cartesian(ylim = c(-1.35, -0.68))
  832. # ggsave(file.path(folder_name, "ttest_frontal_160.jpg"), width = 7, height = 6, dpi = 600, scale = .5)
  833. # ggsave(file.path(folder_name, "ttest_frontal_160.pdf"), width = 7, height = 6, dpi = 600, scale = .5)
  834. plot_box_all_conditions(dat1)
  835. # ==============================================================================
  836. # 2) CENTRAL (MR1), time_chunk = (160,320]
  837. # ==============================================================================
  838. dat1 <- subset(dat, time_chunk == "(160,320]" & variable == "CENTRAL (MR1)")
  839. stopifnot(nrow(dat1) > 0)
  840. means_2x2 <- data.table::dcast(
  841. data.table::as.data.table(dat1),
  842. Congr + PrevCongr ~ .,
  843. value.var = "value",
  844. fun.aggregate = mean, na.rm = TRUE
  845. )
  846. data.table::setnames(means_2x2, ".", "mean")
  847. print(means_2x2)
  848. w_con <- dat1[dat1$Congr == "Con", c("ID","PrevCongr","value")]
  849. w_con <- tidyr::pivot_wider(w_con, names_from = PrevCongr, values_from = value)
  850. w_con <- subset(w_con, !is.na(pCon) & !is.na(pInc))
  851. w_con <- w_con[order(w_con$ID), ]
  852. t.test(w_con$pInc, w_con$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  853. w_inc <- dat1[dat1$Congr == "Inc", c("ID","PrevCongr","value")]
  854. w_inc <- tidyr::pivot_wider(w_inc, names_from = PrevCongr, values_from = value)
  855. w_inc <- subset(w_inc, !is.na(pCon) & !is.na(pInc))
  856. w_inc <- w_inc[order(w_inc$ID), ]
  857. t.test(w_inc$pInc, w_inc$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  858. library(Rmisc)
  859. dat1$PrevCongr <- factor(dat1$PrevCongr, levels = c("pCon","pInc"))
  860. dat1$Congr <- factor(dat1$Congr, levels = c("Con","Inc"))
  861. summary_dat <- summarySEwithin(dat1, "value",
  862. withinvars = c("variable","time_chunk","PrevCongr","Congr"),
  863. idvar = "ID")
  864. ggplot(summary_dat, aes(x = PrevCongr, y = value, color = Congr, group = Congr)) +
  865. facet_grid(. ~ variable + time_chunk) +
  866. geom_line(position = position_dodge(0.2), size = 0.8) +
  867. geom_point(position = position_dodge(0.2), size = 3) +
  868. geom_errorbar(aes(ymin = value - se, ymax = value + se),
  869. width = 0.1, position = position_dodge(0.2)) +
  870. scale_color_manual(values = c("Con" = "red", "Inc" = "blue")) +
  871. theme_bw(base_size = 14) +
  872. theme(
  873. panel.grid = element_blank(),
  874. strip.background = element_rect(fill = "white"),
  875. strip.text = element_text(size = 12, face = "bold"),
  876. axis.title = element_text(size = 12),
  877. axis.text = element_text(size = 10),
  878. plot.title = element_text(size = 14, face = "bold", hjust = 0.5)
  879. ) +
  880. labs(x = "Previous Congruency", y = "Spectral Slope [a.u.]", title = NULL) +
  881. coord_cartesian(ylim = c(-1.35, -0.68))
  882. # ggsave(file.path(folder_name, "ttest_central_160.jpg"), width = 7, height = 6, dpi = 600, scale = .5)
  883. # ggsave(file.path(folder_name, "ttest_central_160.pdf"), width = 7, height = 6, dpi = 600, scale = .5)
  884. plot_box_all_conditions(dat1)
  885. # ==============================================================================
  886. # 3) OCCIPITAL (MR3), time_chunk = (480,640]
  887. # ==============================================================================
  888. dat1 <- subset(dat, time_chunk == "(480,640]" & variable == "OCCIPITAL (MR3)")
  889. stopifnot(nrow(dat1) > 0)
  890. means_2x2 <- data.table::dcast(
  891. data.table::as.data.table(dat1),
  892. Congr + PrevCongr ~ .,
  893. value.var = "value",
  894. fun.aggregate = mean, na.rm = TRUE
  895. )
  896. data.table::setnames(means_2x2, ".", "mean")
  897. print(means_2x2)
  898. w_con <- dat1[dat1$Congr == "Con", c("ID","PrevCongr","value")]
  899. w_con <- tidyr::pivot_wider(w_con, names_from = PrevCongr, values_from = value)
  900. w_con <- subset(w_con, !is.na(pCon) & !is.na(pInc))
  901. w_con <- w_con[order(w_con$ID), ]
  902. t.test(w_con$pInc, w_con$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  903. w_inc <- dat1[dat1$Congr == "Inc", c("ID","PrevCongr","value")]
  904. w_inc <- tidyr::pivot_wider(w_inc, names_from = PrevCongr, values_from = value)
  905. w_inc <- subset(w_inc, !is.na(pCon) & !is.na(pInc))
  906. w_inc <- w_inc[order(w_inc$ID), ]
  907. t.test(w_inc$pInc, w_inc$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  908. library(Rmisc)
  909. dat1$PrevCongr <- factor(dat1$PrevCongr, levels = c("pCon","pInc"))
  910. dat1$Congr <- factor(dat1$Congr, levels = c("Con","Inc"))
  911. summary_dat <- summarySEwithin(dat1, "value",
  912. withinvars = c("variable","time_chunk","PrevCongr","Congr"),
  913. idvar = "ID")
  914. ggplot(summary_dat, aes(x = PrevCongr, y = value, color = Congr, group = Congr)) +
  915. facet_grid(. ~ variable + time_chunk) +
  916. geom_line(position = position_dodge(0.2), size = 0.8) +
  917. geom_point(position = position_dodge(0.2), size = 3) +
  918. geom_errorbar(aes(ymin = value - se, ymax = value + se),
  919. width = 0.1, position = position_dodge(0.2)) +
  920. scale_color_manual(values = c("Con" = "red", "Inc" = "blue")) +
  921. theme_bw(base_size = 14) +
  922. theme(
  923. panel.grid = element_blank(),
  924. strip.background = element_rect(fill = "white"),
  925. strip.text = element_text(size = 12, face = "bold"),
  926. axis.title = element_text(size = 12),
  927. axis.text = element_text(size = 10),
  928. plot.title = element_text(size = 14, face = "bold", hjust = 0.5)
  929. ) +
  930. labs(x = "Previous Congruency", y = "Spectral Slope [a.u.]", title = NULL) +
  931. coord_cartesian(ylim = c(-1.35, -0.68))
  932. # ggsave(file.path(folder_name, "ttest_occ_480.jpg"), width = 7, height = 6, dpi = 600, scale = .5)
  933. # ggsave(file.path(folder_name, "ttest_occ_480.pdf"), width = 7, height = 6, dpi = 600, scale = .5)
  934. plot_box_all_conditions(dat1)
  935. # ==============================================================================
  936. # 4) FRONTAL (MR2), time_chunk = (960,1120]
  937. # ==============================================================================
  938. dat1 <- subset(dat, time_chunk == "(960,1120]" & variable == "FRONTAL (MR2)")
  939. stopifnot(nrow(dat1) > 0)
  940. means_2x2 <- data.table::dcast(
  941. data.table::as.data.table(dat1),
  942. Congr + PrevCongr ~ .,
  943. value.var = "value",
  944. fun.aggregate = mean, na.rm = TRUE
  945. )
  946. data.table::setnames(means_2x2, ".", "mean")
  947. print(means_2x2)
  948. w_con <- dat1[dat1$Congr == "Con", c("ID","PrevCongr","value")]
  949. w_con <- tidyr::pivot_wider(w_con, names_from = PrevCongr, values_from = value)
  950. w_con <- subset(w_con, !is.na(pCon) & !is.na(pInc))
  951. w_con <- w_con[order(w_con$ID), ]
  952. t.test(w_con$pInc, w_con$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  953. w_inc <- dat1[dat1$Congr == "Inc", c("ID","PrevCongr","value")]
  954. w_inc <- tidyr::pivot_wider(w_inc, names_from = PrevCongr, values_from = value)
  955. w_inc <- subset(w_inc, !is.na(pCon) & !is.na(pInc))
  956. w_inc <- w_inc[order(w_inc$ID), ]
  957. t.test(w_inc$pInc, w_inc$pCon, paired = TRUE, alternative = "two.sided", conf.level = 0.95)
  958. library(Rmisc)
  959. dat1$PrevCongr <- factor(dat1$PrevCongr, levels = c("pCon","pInc"))
  960. dat1$Congr <- factor(dat1$Congr, levels = c("Con","Inc"))
  961. summary_dat <- summarySEwithin(dat1, "value",
  962. withinvars = c("variable","time_chunk","PrevCongr","Congr"),
  963. idvar = "ID")
  964. ggplot(summary_dat, aes(x = PrevCongr, y = value, color = Congr, group = Congr)) +
  965. facet_grid(. ~ variable + time_chunk) +
  966. geom_line(position = position_dodge(0.2), size = 0.8) +
  967. geom_point(position = position_dodge(0.2), size = 3) +
  968. geom_errorbar(aes(ymin = value - se, ymax = value + se),
  969. width = 0.1, position = position_dodge(0.2)) +
  970. scale_color_manual(values = c("Con" = "red", "Inc" = "blue")) +
  971. theme_bw(base_size = 14) +
  972. theme(
  973. panel.grid = element_blank(),
  974. strip.background = element_rect(fill = "white"),
  975. strip.text = element_text(size = 12, face = "bold"),
  976. axis.title = element_text(size = 12),
  977. axis.text = element_text(size = 10),
  978. plot.title = element_text(size = 14, face = "bold", hjust = 0.5)
  979. ) +
  980. labs(x = "Previous Congruency", y = "Spectral Slope [a.u.]", title = NULL) +
  981. coord_cartesian(ylim = c(-1.35, -0.68))
  982. # ggsave(file.path(folder_name, "ttest_frontal_960.jpg"), width = 7, height = 6, dpi = 600, scale = .5)
  983. # ggsave(file.path(folder_name, "ttest_frontal_960.pdf"), width = 7, height = 6, dpi = 600, scale = .5)
  984. plot_box_all_conditions(dat1)
  985. ```
  986. Cluster-based permutations for global & experimental effects with figures
  987. ```{r}
  988. ## =========================================================
  989. ## Unified Z-based cluster permutation pipeline + plotting
  990. ## For:
  991. ## A) Global stimulus-induced change (baseline-corrected; time>=0 vs time<0)
  992. ## B) 2×2 effects (Congr, PrevCongr, Interaction) via within-subject contrasts
  993. ##
  994. ## Cluster permutation:
  995. ## - Observed statistic: Z(t) = (obs_mean(t) - perm_mu(t)) / perm_sd(t)
  996. ## - Permutations: sign-flip across subjects (within-subject scheme)
  997. ## - Cluster-forming threshold: |Z| >= 1.6449
  998. ## - Cluster statistic: max |Z| within cluster (max intensity)
  999. ## - FWER control: null = max cluster-stat across time per permutation
  1000. ##
  1001. ## Outputs:
  1002. ## - Cluster table (t_start/t_end, max|Z|, mean Z, p_cluster, sig)
  1003. ## - Plots: Z(t) time course + shaded significant clusters (pFWER <= .05)
  1004. ## =========================================================
  1005. library(data.table)
  1006. library(ggplot2)
  1007. library(patchwork)
  1008. ## =========================================================
  1009. ## 0) GLOBAL SETTINGS (edit here)
  1010. ## =========================================================
  1011. Z_THR <- 1.6449
  1012. N_PERM <- 1000
  1013. SEED <- 123
  1014. POST_RANGE <- 0:1277
  1015. TIME_RANGE <- 0:1277
  1016. COMP_ORDER <- c("FRONTAL (MR2)", "CENTRAL (MR1)", "OCCIPITAL (MR3)")
  1017. ## =========================================================
  1018. ## 1) CORE HELPERS: clustering + permutation Z
  1019. ## =========================================================
  1020. clusters_maxint_1d <- function(stat, thr = Z_THR) {
  1021. above <- abs(stat) >= thr
  1022. if (!any(above)) return(list(idx = list(), cl_stat = numeric(0)))
  1023. r <- rle(above)
  1024. ends <- cumsum(r$lengths)
  1025. starts <- ends - r$lengths + 1
  1026. keep <- which(r$values)
  1027. idx <- lapply(keep, function(k) starts[k]:ends[k])
  1028. cl_stat <- vapply(idx, function(ix) max(abs(stat[ix]), na.rm = TRUE), numeric(1))
  1029. list(idx = idx, cl_stat = cl_stat)
  1030. }
  1031. perm_cluster_maxint_1d_Z <- function(X, n_perm = N_PERM, seed = SEED,
  1032. z_thr = Z_THR, keep_zperm = FALSE) {
  1033. # X: N × T matrix (subjects × time) of values (baseline-corrected or contrasts)
  1034. set.seed(seed)
  1035. N <- nrow(X)
  1036. Tt <- ncol(X)
  1037. # Observed mean map
  1038. obs_mean <- colMeans(X, na.rm = TRUE)
  1039. # Permuted mean maps (n_perm × T)
  1040. perm_means <- matrix(NA_real_, nrow = n_perm, ncol = Tt)
  1041. for (p in seq_len(n_perm)) {
  1042. signs <- sample(c(-1, 1), size = N, replace = TRUE)
  1043. perm_means[p, ] <- colMeans(X * signs, na.rm = TRUE)
  1044. }
  1045. # Pointwise standardization parameters from permutations
  1046. perm_mu <- colMeans(perm_means, na.rm = TRUE)
  1047. perm_sd <- apply(perm_means, 2, sd, na.rm = TRUE)
  1048. perm_sd[perm_sd == 0 | !is.finite(perm_sd)] <- NA_real_
  1049. # Observed Z map
  1050. z_obs <- (obs_mean - perm_mu) / perm_sd
  1051. # Optional permuted Z maps (for diagnostics / pointwise descriptive p(t))
  1052. z_perm <- NULL
  1053. if (isTRUE(keep_zperm)) {
  1054. z_perm <- sweep(perm_means, 2, perm_mu, "-")
  1055. z_perm <- sweep(z_perm, 2, perm_sd, "/")
  1056. }
  1057. # Observed clusters + stats
  1058. cl_obs <- clusters_maxint_1d(z_obs, thr = z_thr)
  1059. # Null distribution: max cluster stat per permutation
  1060. max_null <- numeric(n_perm)
  1061. for (p in seq_len(n_perm)) {
  1062. zp <- if (!is.null(z_perm)) z_perm[p, ] else (perm_means[p, ] - perm_mu) / perm_sd
  1063. cl_p <- clusters_maxint_1d(zp, thr = z_thr)
  1064. max_null[p] <- if (length(cl_p$cl_stat)) max(cl_p$cl_stat) else 0
  1065. }
  1066. # Cluster p-values (FWER-controlled)
  1067. p_cluster <- if (length(cl_obs$cl_stat)) {
  1068. vapply(cl_obs$cl_stat, function(v) (sum(max_null >= v) + 1) / (n_perm + 1), numeric(1))
  1069. } else numeric(0)
  1070. list(
  1071. z_obs = z_obs,
  1072. z_perm = z_perm,
  1073. clusters = cl_obs$idx,
  1074. cluster_stat = cl_obs$cl_stat,
  1075. cluster_p = p_cluster,
  1076. max_null = max_null,
  1077. z_thr = z_thr,
  1078. n_perm = n_perm,
  1079. seed = seed
  1080. )
  1081. }
  1082. summarize_clusters <- function(time_num, res, variable, effect = NA_character_) {
  1083. if (length(res$clusters) == 0) {
  1084. return(data.table(
  1085. variable = variable,
  1086. effect = effect,
  1087. cluster = integer(),
  1088. t_start = integer(),
  1089. t_end = integer(),
  1090. cl_maxabs_z = numeric(),
  1091. mean_z = numeric(),
  1092. mean_abs_z = numeric(),
  1093. p_cluster = numeric(),
  1094. sig = logical()
  1095. ))
  1096. }
  1097. out <- data.table(
  1098. variable = variable,
  1099. effect = effect,
  1100. cluster = seq_along(res$clusters),
  1101. t_start = time_num[vapply(res$clusters, min, integer(1))],
  1102. t_end = time_num[vapply(res$clusters, max, integer(1))],
  1103. cl_maxabs_z = res$cluster_stat,
  1104. mean_z = vapply(res$clusters, function(ix) mean(res$z_obs[ix], na.rm = TRUE), numeric(1)),
  1105. mean_abs_z = vapply(res$clusters, function(ix) mean(abs(res$z_obs[ix]), na.rm = TRUE), numeric(1)),
  1106. p_cluster = res$cluster_p
  1107. )
  1108. out[, sig := (p_cluster <= 0.05)]
  1109. out[]
  1110. }
  1111. plot_z_timecourse_with_clusters <- function(time_num, z_obs, clusters, cluster_p,
  1112. z_thr = Z_THR,
  1113. title = NULL,
  1114. subtitle = NULL) {
  1115. df <- data.table(time = time_num, z = as.numeric(z_obs))
  1116. shade <- NULL
  1117. if (length(clusters) > 0) {
  1118. sig_idx <- which(cluster_p <= 0.05)
  1119. if (length(sig_idx) > 0) {
  1120. shade <- rbindlist(lapply(sig_idx, function(k) {
  1121. ix <- clusters[[k]]
  1122. data.table(xmin = time_num[min(ix)], xmax = time_num[max(ix)])
  1123. }))
  1124. }
  1125. }
  1126. ggplot(df, aes(x = time, y = z)) +
  1127. { if (!is.null(shade)) geom_rect(
  1128. data = shade,
  1129. aes(xmin = xmin, xmax = xmax, ymin = -Inf, ymax = Inf),
  1130. inherit.aes = FALSE,
  1131. alpha = .15
  1132. )
  1133. } +
  1134. geom_hline(yintercept = c(-z_thr, z_thr), linetype = "dotted") +
  1135. geom_hline(yintercept = 0, linetype = "solid") +
  1136. geom_line(linewidth = 1) +
  1137. theme_bw(base_size = 14) +
  1138. theme(panel.grid = element_blank()) +
  1139. labs(x = "Time (ms)", y = "Observed Z(t)", title = title, subtitle = subtitle)
  1140. }
  1141. ## =========================================================
  1142. ## 2) A) GLOBAL: baseline-corrected post-stimulus change
  1143. ## Input: reg_av must have columns ID, time, variable, value
  1144. ## =========================================================
  1145. run_global_baselinecorr_Z <- function(reg_av,
  1146. post_time_range = POST_RANGE,
  1147. n_perm = N_PERM,
  1148. seed = SEED,
  1149. z_thr = Z_THR) {
  1150. DT <- as.data.table(reg_av)
  1151. DT[, time := as.integer(time)]
  1152. setorder(DT, ID, variable, time)
  1153. # Baseline per subject × variable (time < 0)
  1154. BL <- DT[time < 0, .(baseline = mean(value, na.rm = TRUE)), by = .(ID, variable)]
  1155. # Post-stimulus only
  1156. POST <- DT[time >= 0]
  1157. POST <- merge(POST, BL, by = c("ID", "variable"), all.x = TRUE)
  1158. # Baseline-corrected values
  1159. POST[, value_bc := value - baseline]
  1160. # Restrict post-stim time range
  1161. if (!is.null(post_time_range)) POST <- POST[time %in% post_time_range]
  1162. vars <- unique(POST$variable)
  1163. # Cluster table
  1164. cluster_table <- rbindlist(lapply(vars, function(v) {
  1165. tmp <- POST[variable == v, .(ID, time, val = value_bc)]
  1166. wide <- dcast(tmp, ID ~ time, value.var = "val")
  1167. time_cols <- setdiff(names(wide), "ID")
  1168. time_num <- as.integer(time_cols)
  1169. o <- order(time_num)
  1170. time_cols <- time_cols[o]
  1171. time_num <- time_num[o]
  1172. X <- as.matrix(wide[, ..time_cols])
  1173. res <- perm_cluster_maxint_1d_Z(X, n_perm = n_perm, seed = seed, z_thr = z_thr, keep_zperm = FALSE)
  1174. summarize_clusters(time_num, res, variable = v, effect = "global_bc")
  1175. }), use.names = TRUE, fill = TRUE)
  1176. # Z curves for plotting
  1177. z_curves <- lapply(vars, function(v) {
  1178. tmp <- POST[variable == v, .(ID, time, val = value_bc)]
  1179. wide <- dcast(tmp, ID ~ time, value.var = "val")
  1180. time_cols <- setdiff(names(wide), "ID")
  1181. time_num <- as.integer(time_cols)
  1182. o <- order(time_num)
  1183. time_cols <- time_cols[o]
  1184. time_num <- time_num[o]
  1185. X <- as.matrix(wide[, ..time_cols])
  1186. res <- perm_cluster_maxint_1d_Z(X, n_perm = n_perm, seed = seed, z_thr = z_thr, keep_zperm = FALSE)
  1187. list(variable = v, time = time_num, res = res)
  1188. })
  1189. names(z_curves) <- vars
  1190. list(
  1191. POST = POST,
  1192. cluster_table = cluster_table,
  1193. z_curves = z_curves,
  1194. settings = list(n_perm = n_perm, seed = seed, z_thr = z_thr)
  1195. )
  1196. }
  1197. ## =========================================================
  1198. ## 3) B) 2×2 EFFECTS: Congr / PrevCongr / Interaction
  1199. ## Input: reg_pcongr2 must have columns:
  1200. ## ID, variable, time, PrevCongr, Congr, value
  1201. ## =========================================================
  1202. run_2x2_effects_Z <- function(reg_pcongr2,
  1203. time_range = TIME_RANGE,
  1204. n_perm = N_PERM,
  1205. seed = SEED,
  1206. z_thr = Z_THR) {
  1207. DT <- as.data.table(reg_pcongr2)
  1208. DT[, Congr := factor(Congr, levels = c("Con","Inc"))]
  1209. DT[, PrevCongr := factor(PrevCongr, levels = c("pCon","pInc"))]
  1210. DT[, time := as.integer(time)]
  1211. setorder(DT, ID, variable, time, PrevCongr, Congr)
  1212. if (!is.null(time_range)) DT <- DT[time %in% time_range]
  1213. # Wide 2×2 cells
  1214. W <- dcast(
  1215. DT,
  1216. ID + variable + time ~ PrevCongr + Congr,
  1217. value.var = "value"
  1218. )
  1219. required_cols <- c("pCon_Con","pCon_Inc","pInc_Con","pInc_Inc")
  1220. miss <- setdiff(required_cols, names(W))
  1221. if (length(miss) > 0) stop("Missing condition columns after dcast: ", paste(miss, collapse = ", "))
  1222. # Within-subject contrasts
  1223. EFF <- W[, .(
  1224. eff_Congr = 0.5 * ((pCon_Inc - pCon_Con) + (pInc_Inc - pInc_Con)),
  1225. eff_Prev = 0.5 * ((pInc_Con - pCon_Con) + (pInc_Inc - pCon_Inc)),
  1226. eff_Int = (pCon_Inc - pCon_Con) - (pInc_Inc - pInc_Con)
  1227. ), by = .(ID, variable, time)]
  1228. effects <- c("eff_Congr","eff_Prev","eff_Int")
  1229. vars <- unique(EFF$variable)
  1230. # Cluster table
  1231. cluster_table <- rbindlist(lapply(vars, function(v) {
  1232. rbindlist(lapply(effects, function(eff) {
  1233. tmp <- EFF[variable == v, .(ID, time, val = get(eff))]
  1234. wide <- dcast(tmp, ID ~ time, value.var = "val")
  1235. time_cols <- setdiff(names(wide), "ID")
  1236. time_num <- as.integer(time_cols)
  1237. o <- order(time_num)
  1238. time_cols <- time_cols[o]
  1239. time_num <- time_num[o]
  1240. X <- as.matrix(wide[, ..time_cols])
  1241. res <- perm_cluster_maxint_1d_Z(X, n_perm = n_perm, seed = seed, z_thr = z_thr, keep_zperm = FALSE)
  1242. summarize_clusters(time_num, res, variable = v, effect = eff)
  1243. }))
  1244. }), use.names = TRUE, fill = TRUE)
  1245. # Z curves for plotting
  1246. z_curves <- list()
  1247. for (v in vars) {
  1248. for (eff in effects) {
  1249. tmp <- EFF[variable == v, .(ID, time, val = get(eff))]
  1250. wide <- dcast(tmp, ID ~ time, value.var = "val")
  1251. time_cols <- setdiff(names(wide), "ID")
  1252. time_num <- as.integer(time_cols)
  1253. o <- order(time_num)
  1254. time_cols <- time_cols[o]
  1255. time_num <- time_num[o]
  1256. X <- as.matrix(wide[, ..time_cols])
  1257. res <- perm_cluster_maxint_1d_Z(X, n_perm = n_perm, seed = seed, z_thr = z_thr, keep_zperm = FALSE)
  1258. key <- paste(v, eff, sep = " | ")
  1259. z_curves[[key]] <- list(variable = v, effect = eff, time = time_num, res = res)
  1260. }
  1261. }
  1262. list(
  1263. EFF = EFF,
  1264. cluster_table = cluster_table,
  1265. z_curves = z_curves,
  1266. settings = list(n_perm = n_perm, seed = seed, z_thr = z_thr)
  1267. )
  1268. }
  1269. ## =========================================================
  1270. ## 4) FIGURE BUILDERS
  1271. ## =========================================================
  1272. make_effect_3panel_Z <- function(effects_out,
  1273. effect = c("eff_Congr","eff_Prev","eff_Int"),
  1274. components = COMP_ORDER,
  1275. z_thr = NULL,
  1276. title = NULL) {
  1277. effect <- match.arg(effect)
  1278. if (is.null(z_thr)) z_thr <- effects_out$settings$z_thr
  1279. if (is.null(title)) title <- paste0("Observed Z(t): ", effect, " (shaded clusters pFWER ≤ .05)")
  1280. panels <- lapply(components, function(v) {
  1281. key <- paste(v, effect, sep = " | ")
  1282. if (!key %in% names(effects_out$z_curves)) stop("Missing z_curve for: ", key)
  1283. item <- effects_out$z_curves[[key]]
  1284. plot_z_timecourse_with_clusters(
  1285. time_num = item$time,
  1286. z_obs = item$res$z_obs,
  1287. clusters = item$res$clusters,
  1288. cluster_p = item$res$cluster_p,
  1289. z_thr = z_thr,
  1290. title = v,
  1291. subtitle = NULL
  1292. ) +
  1293. theme(
  1294. plot.title = element_text(size = 12, face = "bold"),
  1295. axis.title.x = element_text(size = 11),
  1296. axis.title.y = element_text(size = 11)
  1297. )
  1298. })
  1299. wrap_plots(panels, ncol = 1) +
  1300. plot_annotation(title = title) &
  1301. theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
  1302. }
  1303. make_global_3panel_Z <- function(global_out,
  1304. components = COMP_ORDER,
  1305. z_thr = NULL,
  1306. title = NULL) {
  1307. if (is.null(z_thr)) z_thr <- global_out$settings$z_thr
  1308. if (is.null(title)) title <- "Global stimulus-induced changes vs baseline (Observed Z(t); shaded clusters pFWER ≤ .05)"
  1309. panels <- lapply(components, function(v) {
  1310. if (!v %in% names(global_out$z_curves)) stop("Missing global z_curve for: ", v)
  1311. item <- global_out$z_curves[[v]]
  1312. plot_z_timecourse_with_clusters(
  1313. time_num = item$time,
  1314. z_obs = item$res$z_obs,
  1315. clusters = item$res$clusters,
  1316. cluster_p = item$res$cluster_p,
  1317. z_thr = z_thr,
  1318. title = v,
  1319. subtitle = NULL
  1320. ) +
  1321. theme(
  1322. plot.title = element_text(size = 12, face = "bold"),
  1323. axis.title.x = element_text(size = 11),
  1324. axis.title.y = element_text(size = 11)
  1325. )
  1326. })
  1327. wrap_plots(panels, ncol = 1) +
  1328. plot_annotation(title = title) &
  1329. theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
  1330. }
  1331. ## =========================================================
  1332. ## 5) RUN PIPELINES
  1333. ## =========================================================
  1334. # ---- A) Global (baseline-corrected) ----
  1335. global_out <- run_global_baselinecorr_Z(
  1336. reg_av,
  1337. post_time_range = POST_RANGE,
  1338. n_perm = N_PERM,
  1339. seed = SEED,
  1340. z_thr = Z_THR
  1341. )
  1342. global_out$cluster_table[]
  1343. global_out$cluster_table[sig == TRUE]
  1344. # ---- B) 2×2 effects ----
  1345. effects_out <- run_2x2_effects_Z(
  1346. reg_pcongr2,
  1347. time_range = TIME_RANGE,
  1348. n_perm = N_PERM,
  1349. seed = SEED,
  1350. z_thr = Z_THR
  1351. )
  1352. effects_out$cluster_table[]
  1353. effects_out$cluster_table[sig == TRUE]
  1354. ## =========================================================
  1355. ## 6) MAKE THE 3-PANEL FIGURES
  1356. ## =========================================================
  1357. fig_global_3panel <- make_global_3panel_Z(
  1358. global_out,
  1359. components = COMP_ORDER,
  1360. title = "Global post-stimulus deviations from baseline (Observed Z(t); shaded clusters pFWER ≤ .05)"
  1361. )
  1362. fig_congr_3panel <- make_effect_3panel_Z(
  1363. effects_out,
  1364. effect = "eff_Congr",
  1365. components = COMP_ORDER,
  1366. title = "Main effect of Congruency (Observed Z(t); shaded clusters pFWER ≤ .05)"
  1367. )
  1368. fig_prev_3panel <- make_effect_3panel_Z(
  1369. effects_out,
  1370. effect = "eff_Prev",
  1371. components = COMP_ORDER,
  1372. title = "Main effect of Previous Congruency (Observed Z(t); shaded clusters pFWER ≤ .05)"
  1373. )
  1374. fig_int_3panel <- make_effect_3panel_Z(
  1375. effects_out,
  1376. effect = "eff_Int",
  1377. components = COMP_ORDER,
  1378. title = "Congruency × Previous Congruency (Observed Z(t); shaded clusters pFWER ≤ .05)"
  1379. )
  1380. fig_global_3panel
  1381. ggsave("fit_global.pdf", width = 6, height = 15, dpi = 600, scale = .5)
  1382. fig_congr_3panel
  1383. ggsave("fit_congr.pdf", width = 6, height = 15, dpi = 600, scale = .5)
  1384. fig_prev_3panel
  1385. ggsave("fit_prev.pdf", width = 6, height = 15, dpi = 600, scale = .5)
  1386. fig_int_3panel
  1387. ggsave("fit_int.pdf", width = 6, height = 15, dpi = 600, scale = .5)
  1388. ```

code_afterR1.Rmd, no license · at the source

Overview

  1. Center for Mind/Brain Sciences (CIMeC), Universitá Degli Studi di Trento‐Rovereto, Trento, Italy
  2. Department of Psychology, Universitá Degli Studi di Bologna, Bologna, Italy
  3. Centre for Cognitive Science, Jagiellonian University, Kraków, Poland
  4. Department of Psychology, University of Illinois Urbana‐Champaign, Champaign, Illinois, USA
  5. Beckman Institute for Advanced Science and Technology, University of Illinois Urbana‐Champaign, Champaign, Illinois, USA
  6. School of Psychology, University of East Anglia, Norwich, UK
Journal: Psychophysiology, volume 63, issue 8, article e70376
Dates: received 17 November 2025; accepted 29 July 2026; published online 8 August 2026; in print August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1111/psyp.70376 · PMID 42568317 · PMCID PMC13451924 · OpenAlex W7201979157
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), cognitive (subfield)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Preprocessing, Evoked potentials, Complexity, fMRI & imaging
Keywords: aperiodic 1/f EEG, cognitive control, congruency effect, congruency sequence effect, neural noise, spatial distribution
MeSH: Attention*, Cerebral Cortex*, Evoked Potentials*, Executive Function*, Inhibition, Psychological*, Pattern Recognition, Visual*, Psychomotor Performance*, Adult, Electroencephalography, Female, Humans, Male, Young Adult (* major topic)
Topic: Neural and Behavioral Psychology Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: National Institute on Aging (RF1AG062666); NIA NIH HHS (RF1AG062666, RF1 AG062666)
Citations: cited by 1 paper (Europe PMC); 75 references in the paper

Abstract

Aperiodic neural activity (1/f EEG) has been proposed to reflect the balance between excitatory and inhibitory (E/I) inputs, with steeper spectral slopes reflecting increased inhibition and flatter slopes indicating excitation. This activity is also thought to reflect the temporal coordination of neural firing, offering insights into fundamental brain dynamics. Recent studies have shown that the 1/f slope is sensitive to stimulus onset, characterized by initial inhibitory shifts followed by excitatory rebounds, which may reflect cognitive control mechanisms involved in suppressing distractions and preparing goal‐directed responses. However, previous research has relied on fixed temporal windows and insufficient control of ERP contamination, limiting our understanding of rapid control dynamics. Here we used newly developed time‐resolved analyses to study 1/f spectral slope modulation during a Picture‐Word Interference task, focusing on two canonical cognitive control markers: the Congruency Effect (CE) and Congruency Sequence Effect (CSE). Forty‐nine participants categorized pictures while ignoring congruent or incongruent words. Behaviorally, we replicated robust CE and CSE patterns. Spectral slope analyses showed that incongruent trials elicited steeper slopes—consistent with increased inhibition—particularly in frontal and central regions, reflecting conflict‐related control engagement. Moreover, CSE analyses revealed dynamic slope modulations across frontal, central, and occipital components over time, suggesting control adjustments influenced by previous trial congruency. These results provide the first within‐trial time‐resolved evidence that aperiodic 1/f EEG activity can track both immediate conflict resolution and cognitive adjustments, offering a temporally sensitive neural marker of cognitive control, albeit with effects that are small in magnitude on average.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.

OSF z3qsg

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Languages: R (2)
Size: 6 files, 2 scripts
Software Heritage: not checked
Found in: “Data Availability Statement”
Holds: 2 notebooks
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: data.table (2 files), eegUtils (2 files), psych (2 files), tidyverse (2 files), ggplot2 (1 file), patchwork (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
  • 26 September 2026: the link answers (HTTP 200)
2 files
At the source: osf.io/z3qsg

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 2 scripts, each with its path and the digest of its content;
  • 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data Availability Statement

Data and code necessary to reproduce the statistical analyses (Section 3.2.3) are available at https://osf.io/z3qsg.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 3, 28 September 2026

  • Publisher: n/a → Wiley

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 6 keywords, 13 MeSH terms, 2 funders, 73 references.

Cite

This paper

Tronelli, V., Kałamała, P., Gratton, G., Fabiani, M., Gyurkovics, M., Low, K. A., Codispoti, M., & De Cesarei, A. (2026). Changes in Aperiodic (1/f Slope) Activity During a Picture-Word Interference Task: Effects of Congruency and Sequence Manipulations. Psychophysiology, 63(8), e70376. https://doi.org/10.1111/psyp.70376

BibTeX

@article{tronelli2026changes,
author = {Tronelli, Virginia and Kałamała, Patrycja and Gratton, Gabriele and Fabiani, Monica and Gyurkovics, Mate and Low, Kathy A and Codispoti, Maurizio and De Cesarei, Andrea},
title = {{Changes in Aperiodic (1/f Slope) Activity During a Picture-Word Interference Task: Effects of Congruency and Sequence Manipulations}},
journal = {Psychophysiology},
year = {2026},
month = aug,
volume = {63},
number = {8},
pages = {e70376},
publisher = {Wiley},
issn = {0048-5772},
doi = {10.1111/psyp.70376},
url = {https://doi.org/10.1111/psyp.70376},
pmid = {42568317},
pmcid = {PMC13451924}
}

RIS

TY - JOUR
AU - Tronelli, Virginia
AU - Kałamała, Patrycja
AU - Gratton, Gabriele
AU - Fabiani, Monica
AU - Gyurkovics, Mate
AU - Low, Kathy A
AU - Codispoti, Maurizio
AU - De Cesarei, Andrea
TI - Changes in Aperiodic (1/f Slope) Activity During a Picture-Word Interference Task: Effects of Congruency and Sequence Manipulations
T2 - Psychophysiology
J2 - Psychophysiology
PY - 2026
DA - 2026/08/01
VL - 63
IS - 8
SP - e70376
SN - 0048-5772
PB - Wiley
DO - 10.1111/psyp.70376
UR - https://doi.org/10.1111/psyp.70376
LA - en
ER -

CSL-JSON

{
"id": "10.1111/psyp.70376",
"type": "article-journal",
"title": "Changes in Aperiodic (1/f Slope) Activity During a Picture-Word Interference Task: Effects of Congruency and Sequence Manipulations",
"container-title": "Psychophysiology",
"author": [
{
"family": "Tronelli",
"given": "Virginia"
},
{
"family": "Kałamała",
"given": "Patrycja"
},
{
"family": "Gratton",
"given": "Gabriele"
},
{
"family": "Fabiani",
"given": "Monica"
},
{
"family": "Gyurkovics",
"given": "Mate"
},
{
"family": "Low",
"given": "Kathy A"
},
{
"family": "Codispoti",
"given": "Maurizio"
},
{
"family": "De Cesarei",
"given": "Andrea"
}
],
"container-title-short": "Psychophysiology",
"volume": "63",
"issue": "8",
"page": "e70376",
"DOI": "10.1111/psyp.70376",
"PMID": "42568317",
"PMCID": "PMC13451924",
"ISSN": "0048-5772",
"publisher": "Wiley",
"URL": "https://doi.org/10.1111/psyp.70376",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
1
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.isci.2026.116936 [code]
Beyond neural oscillations: Stress-related aperiodic activity and aperiodic-oscillatory spectral covariation.
Journal: iScience
In common: eegUtils, data.table, patchwork, 2 other tools, EEG, 5 references
[2] doi:10.1111/ejn.70255 [code]
A Systematic Review of Aperiodic Neural Activity in Clinical Investigations
Journal: n/a
In common: EEG, 9 references
[3] doi:10.1093/braincomms/fcag351 [code]
Time-resolved aperiodic dynamics in event segmentation in attention-deficit/hyperactivity disorder.
Journal: Brain communications
In common: ggplot2, tidyverse, EEG, cognitive, 6 references
[4] doi:10.1111/ejn.70543 [code]
Neural Oscillations Track Subjective and Pupillary Arousal During Naturalistic Movie Viewing.
Journal: The European journal of neuroscience
In common: EEG, cognitive, 7 references
[5] doi:10.7554/elife.108673 [code]
Adaptive behavior is guided by integrated representations of controlled and non-controlled information.
Journal: eLife
In common: EEG, 6 references
[6] doi:10.1093/cercor/bhag113 [code]
Long-term reliability and stability of parameterized resting state EEG: evidence from a five-year follow-up.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: psych, ggplot2, tidyverse, EEG, 4 references
[7] doi:10.1162/imag.a.1298 [code]
Frontoparietal control-default mode connectivity predicts TMS effects on cognitive control.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: psych, data.table, patchwork, 2 other tools, cognitive, 1 reference
[8] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: 6 references
[9] doi:10.1016/j.neuroimage.2026.122115 [code]
Midfrontal theta power relates to response speeding following frustrative nonreward.
Journal: NeuroImage
In common: psych, patchwork, ggplot2, 1 other tool, EEG, cognitive, 2 references
[10] doi:10.1126/sciadv.aea3919 [code]
Hierarchical brain dynamics supporting visual perceptual transitions.
Journal: Science advances
In common: ggplot2, cognitive, 5 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.