OSCR

Precision fMRI reveals that the language network exhibits adult-like left-hemispheric lateralization by 4 years of age.

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] § Methods › Do response magnitude and inter-regional correlations exhibit age-related changes? ↔ source data and code/OzernovPalchik_OBrien_source_data_code_FINAL_100325.zip/code/NCOMM_OzernovPalchik_OBrien_SI_code_091025.Rmd, lines 3219–3247 · score 0.61 · correlation strength, inter regional, pairwise comparisons, network, models, adult
  2. [2] § Methods › Do children (of different ages) show left-hemispheric bias, and is the bias adult-like? ↔ source data and code/OzernovPalchik_OBrien_source_data_code_FINAL_100325.zip/code/NCOMM_OzernovPalchik_OBrien_main_code_091025.Rmd, lines 707–791 · score 0.57 · linear regression model, predicting LI, LH bias, fit, volume, RH

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 · 3,309 lines · 112 KB · no license · 1 match

  1. ---
  2. title: "NComm_SI_Alice_power_extracted"
  3. output:
  4. html_document:
  5. toc: true
  6. toc_float: true
  7. theme: flatly
  8. date: "2025-03-10"
  9. editor_options:
  10. chunk_output_type: console
  11. ---
  12. ```{r setup, include=FALSE}
  13. knitr::opts_chunk$set(
  14. echo = TRUE,
  15. warning = FALSE,
  16. message = FALSE,
  17. fig.width = 8,
  18. fig.height = 6
  19. )
  20. ```
  21. ###########################################
  22. #SI 0
  23. ###########################################
  24. # SI-0: Set Up
  25. ## Load Required Libraries
  26. ```{r load-libraries}
  27. # Load required libraries
  28. library(lme4)
  29. library(lsmeans)
  30. library(dplyr)
  31. library(kableExtra)
  32. library(tidyr)
  33. library(MuMIn) # for r.squaredGLMM
  34. library(pwr) # for power analysis
  35. library(ggplot2)
  36. library(ggsignif)
  37. library(ggpubr)
  38. library(cowplot)
  39. library(stringr)
  40. library(brms)
  41. # Note: Ensure your working directory is set to the code/ folder
  42. # or that data files are accessible via ../data/ relative path
  43. ```
  44. ## Load Datasets
  45. ```{r load-data}
  46. # Read in datasets from ../data/ folder
  47. ds1_eff <- read.csv("../data/effect_ds1_090125.csv")
  48. ds2_eff <- read.csv("../data/effect_ds2_031025.csv")
  49. ds1_vol <- read.csv("../data/volume_ds1_031025.csv")
  50. ds2_vol <- read.csv("../data/volume_ds2_031025.csv")
  51. ds1_demo <- read.csv("../data/ds1_demo_data_031425.csv")
  52. ds2_demo <- read.csv("../data/ds2_demo_090225.csv")
  53. beh <- read.csv('../data/MNM_behavfMRI_180825.csv')
  54. cat("Data loaded successfully!\n")
  55. cat("DS1 Effect data dimensions:", dim(ds1_eff), "\n")
  56. cat("DS2 Effect data dimensions:", dim(ds2_eff), "\n")
  57. # Report unique N for each dataset
  58. cat("\nUnique subject counts:\n")
  59. cat("DS1 Effect N =", length(unique(ds1_eff$Subject)), "\n")
  60. cat("DS2 Effect N =", length(unique(ds2_eff$Subject)), "\n")
  61. cat("DS1 Volume N =", length(unique(ds1_vol$Subject)), "\n")
  62. cat("DS2 Volume N =", length(unique(ds2_vol$Subject)), "\n")
  63. cat("DS1 Demo N =", length(unique(ds1_demo$Subject)), "\n")
  64. cat("DS2 Demo N =", length(unique(ds2_demo$Subject)), "\n")
  65. cat("DS2 Beh N =", length(unique(beh$Subject)), "\n")
  66. ```
  67. ## Convert Variables to Factors
  68. ```{r factor-conversion}
  69. # Define columns to convert to factors for effect datasets
  70. cols_to_factor <- c("age", "front", "hemi", "ROI")
  71. # Convert specified columns to factors in both datasets
  72. ds1_eff[cols_to_factor] <- lapply(ds1_eff[cols_to_factor], as.factor)
  73. ds2_eff[cols_to_factor] <- lapply(ds2_eff[cols_to_factor], as.factor)
  74. ds1_vol[cols_to_factor] <- lapply(ds1_vol[cols_to_factor], as.factor)
  75. ds2_vol[cols_to_factor] <- lapply(ds2_vol[cols_to_factor], as.factor)
  76. cat("Factor conversion completed.\n")
  77. cat("Age levels in DS1:", levels(ds1_eff$age), "\n")
  78. cat("ROI levels in DS1:", length(levels(ds1_eff$ROI)), "regions\n")
  79. cat("ROI levels in DS2:", length(levels(ds2_eff$ROI)), "regions\n")
  80. ```
  81. ## ROI Name Mapping for Dataset 2
  82. ```{r roi-mapping}
  83. # Create mapping vector for ROI names
  84. roi_map <- c(
  85. LH_AntTemp = "AntTemp",
  86. LH_IFG = "IFG",
  87. LH_IFGorb = "IFGorb",
  88. LH_MFG = "MFG",
  89. LH_PostTemp = "PostTemp",
  90. RH_AntTemp = "R AntTemp",
  91. RH_IFG = "R IFG",
  92. RH_IFGorb = "R IFG orb",
  93. RH_MFG = "R MFG",
  94. RH_PostTemp = "R Post temp"
  95. )
  96. # Function to update DS2 ROI names to match DS1 format
  97. update_ds2_rois <- function(df_ds2, df_ds1) {
  98. df_ds2$ROI <- factor(
  99. roi_map[as.character(df_ds2$ROI)], # map old → new names
  100. levels = levels(df_ds1$ROI) # same order/levels as ds1
  101. )
  102. return(df_ds2)
  103. }
  104. # Apply ROI name updates
  105. ds2_vol <- update_ds2_rois(ds2_vol, ds1_vol)
  106. ds2_eff <- update_ds2_rois(ds2_eff, ds1_eff)
  107. # Verify that ROI levels match between datasets
  108. stopifnot(
  109. all(levels(ds2_vol$ROI) == levels(ds1_vol$ROI)),
  110. all(levels(ds2_eff$ROI) == levels(ds1_eff$ROI))
  111. )
  112. cat("ROI mapping completed successfully.\n")
  113. cat("ROI levels now match between datasets.\n")
  114. ```
  115. ## Combine Datasets
  116. ```{r combine-datasets}
  117. # Combine effect datasets
  118. ds1_eff$ds <- '1'
  119. ds2_eff$ds <- '2'
  120. ds_eff_combined <- bind_rows(ds1_eff, ds2_eff)
  121. ds_eff_combined$age_years <- NULL
  122. # Combine volume datasets
  123. ds1_vol$ds <- '1'
  124. ds2_vol$ds <- '2'
  125. ds_vol_combined <- bind_rows(ds1_vol, ds2_vol)
  126. ds_vol_combined$group <- NULL
  127. cat("Datasets combined successfully.\n")
  128. cat("Combined effect data dimensions:", dim(ds_eff_combined), "\n")
  129. cat("Combined volume data dimensions:", dim(ds_vol_combined), "\n")
  130. # Check for NAs in key variables
  131. cat("\nNA counts:\n")
  132. cat("Effect data NAs:", sum(is.na(ds_eff_combined$effect)), "\n")
  133. cat("Volume data NAs:", sum(is.na(ds_vol_combined$volume)), "\n")
  134. # Report unique N by age for effect data
  135. cat("\nEffect data - Unique N by age:\n")
  136. age_counts_eff <- ds_eff_combined %>%
  137. distinct(Subject, age) %>%
  138. count(age, name = "N")
  139. print(age_counts_eff)
  140. # Report unique N by age for volume data
  141. cat("\nVolume data - Unique N by age:\n")
  142. age_counts_vol <- ds_vol_combined %>%
  143. distinct(Subject, age) %>%
  144. count(age, name = "N")
  145. print(age_counts_vol)
  146. ```
  147. ## Create Right Hemisphere Subsets
  148. ```{r rh-subsets}
  149. # Dataset 1 - Right Hemisphere
  150. ds1_eff_rh <- ds1_eff %>%
  151. filter(hemi == "rh", condition == "language") %>%
  152. select(Subject, ROI, age, front, hemi, contrast) %>%
  153. mutate(dataset = "DS1")
  154. # Create age group subsets for DS1
  155. ds1_early_rh <- filter(ds1_eff_rh, age == "early")
  156. ds1_middle_rh <- filter(ds1_eff_rh, age == "middle")
  157. ds1_late_rh <- filter(ds1_eff_rh, age == "late")
  158. ds1_adult_rh <- filter(ds1_eff_rh, age == "adult")
  159. # Dataset 2 - Right Hemisphere
  160. ds2_eff_rh <- ds2_eff %>%
  161. filter(hemi == "rh", condition == "EffectSize.mentalsocialphysical") %>%
  162. select(Subject, ROI, age, front, hemi, contrast) %>%
  163. mutate(dataset = "DS2")
  164. # Create age group subsets for DS2
  165. ds2_early_rh <- filter(ds2_eff_rh, age == "early")
  166. ds2_middle_rh <- filter(ds2_eff_rh, age == "middle")
  167. ds2_late_rh <- filter(ds2_eff_rh, age == "late")
  168. ds2_adult_rh <- filter(ds2_eff_rh, age == "adult")
  169. ```
  170. ###########################################
  171. #SI 1
  172. ###########################################
  173. # SI-1.B: Behavioral performance of Dataset2
  174. ## Load and Prepare Behavioral Data
  175. ```{r behavioral-prep}
  176. # Get subjects from neural data
  177. neural_subjects <- unique(ds2_eff$Subject)
  178. # Get subjects from behavioral data
  179. behavioral_subjects <- unique(beh$Subject)
  180. # Find subjects in neural data but missing from behavioral data
  181. setdiff(neural_subjects, behavioral_subjects)
  182. # Merge datasets (only subjects with both neural and behavioral data)
  183. beh_merged <- beh %>%
  184. inner_join(ds2_eff %>% dplyr::select(Subject, age) %>% distinct(), by = "Subject")
  185. colSums(is.na(beh_merged))
  186. # Report final sample
  187. cat("Final sample size:", length(unique(beh_merged$Subject)), "subjects\n")
  188. ```
  189. ## Set Up Custom Contrasts for Age Groups
  190. ```{r age-contrasts}
  191. # # Create custom contrasts matrix for age group comparisons
  192. # custom_contrasts <- matrix(c(
  193. # 1, 0, 0, # early vs middle
  194. # -1, 1, 0, # middle vs late
  195. # 0, -1, 1, # late vs adult
  196. # 0, 0, -1 # adult baseline
  197. # ), ncol = 3, byrow = TRUE)
  198. #
  199. # rownames(custom_contrasts) <- c("early", "middle", "late", "adult")
  200. # colnames(custom_contrasts) <- c("early_vs_middle", "middle_vs_late", "late_vs_adult")
  201. #
  202. # # Apply contrasts to age factor
  203. # beh_merged$age <- as.factor(beh_merged$age)
  204. # contrasts(beh_merged$age) <- custom_contrasts
  205. #
  206. # #check contrasts
  207. # kable(custom_contrasts, caption = "Custom Age Group Contrast Matrix") %>%
  208. # kable_styling(bootstrap_options = c("striped", "hover"))
  209. ```
  210. ## SI-1B Behavioral Performance Summary
  211. ### Summary behavioral data
  212. ```{r behavioral-summary}
  213. # Create participant info dataframe
  214. participant_info <- beh_merged %>%
  215. dplyr::select(Subject, age) %>%
  216. distinct()
  217. # Aggregate behavioral performance (excluding Music condition)
  218. agg_summary <- beh_merged %>%
  219. filter(Cond != "Music") %>%
  220. mutate(CondGroup = case_when(
  221. Cond == "Foreign" ~ "Foreign",
  222. Cond %in% c("Mental", "Physical", "Social") ~ "MPS"
  223. )) %>%
  224. filter(!is.na(CondGroup)) %>%
  225. group_by(Subject, CondGroup) %>%
  226. summarise(
  227. Total = n(),
  228. Sum_Correct = sum(Correct, na.rm = TRUE),
  229. Sum_Wrong = sum(Wrong, na.rm = TRUE),
  230. Sum_Miss = sum(Miss, na.rm = TRUE),
  231. .groups = "drop"
  232. ) %>%
  233. left_join(participant_info, by = "Subject")
  234. # Calculate percent correct for each subject and condition
  235. agg_per <- agg_summary %>%
  236. mutate(Percent_Correct = (Sum_Correct / Total) * 100)
  237. # Display summary statistics
  238. summary_stats <- agg_per %>%
  239. group_by(CondGroup, age) %>%
  240. summarise(
  241. N = n(),
  242. Mean_PC = round(mean(Percent_Correct, na.rm = TRUE), 2),
  243. SD_PC = round(sd(Percent_Correct, na.rm = TRUE), 2),
  244. SE_PC = round(sd(Percent_Correct, na.rm = TRUE) / sqrt(n()), 2),
  245. .groups = "drop"
  246. )
  247. kable(summary_stats,
  248. caption = "Behavioral Performance Summary by Condition and Age Group",
  249. col.names = c("Condition", "Age", "N", "Mean %", "SD", "SE")) %>%
  250. kable_styling(bootstrap_options = c("striped", "hover"))
  251. ```
  252. ### Analysis of behavioral data
  253. ```{r behavioral-analysis}
  254. # Test age and condition effects using mixed-effects model
  255. m_beh_1 <- lmer(Percent_Correct ~ CondGroup * age + (1 | Subject), data = agg_per)
  256. # Display model summary
  257. cat("Mixed-Effects Model Results:\n")
  258. cat("============================\n")
  259. print(summary(m_beh_1))
  260. # Get estimated marginal means and all pairwise comparisons for age
  261. age_emm <- emmeans(m_beh_1, ~ age)
  262. age_contrasts <- pairs(age_emm, adjust = "tukey")
  263. print(age_contrasts)
  264. # Calculate R-squared
  265. r2_values <- r.squaredGLMM(m_beh_1)
  266. cat("\nR-squared values:\n")
  267. cat("Marginal R²:", round(r2_values[1], 3), "\n")
  268. cat("Conditional R²:", round(r2_values[2], 3), "\n")
  269. # Post-hoc comparisons for condition groups
  270. cat("Post-hoc comparisons for Condition Groups:\n")
  271. cat("==========================================\n")
  272. condition_comparisons <- emmeans(m_beh_1, pairwise ~ CondGroup, adjust = "tukey")
  273. print(condition_comparisons)
  274. # Age group comparisons - formatted as requested table
  275. cat("\n\n**Accuracy on the in-scanner task**\n")
  276. cat("====================================\n")
  277. # Get age group comparisons
  278. age_comparisons <- emmeans(m_beh_1, pairwise ~ age, adjust = "tukey")
  279. age_contrasts <- as.data.frame(age_comparisons$contrasts)
  280. # Create formatted table
  281. age_results <- age_contrasts %>%
  282. mutate(
  283. Comparison = gsub(" - ", " vs. ", contrast),
  284. B = round(estimate, 1),
  285. SE = round(SE, 2),
  286. t = round(t.ratio, 2),
  287. p = round(p.value, 2),
  288. p_formatted = case_when(
  289. p.value < 0.001 ~ "**<0.001**",
  290. p.value < 0.01 ~ paste0("**", sprintf("%.2f", p.value), "**"),
  291. p.value < 0.05 ~ paste0("**", sprintf("%.2f", p.value), "**"),
  292. TRUE ~ sprintf("%.2f", p.value)
  293. )
  294. ) %>%
  295. select(Comparison, B, SE, t, p_formatted) %>%
  296. arrange(Comparison)
  297. # Display as kable
  298. kable(age_results,
  299. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  300. caption = "**Accuracy on the in-scanner task**",
  301. escape = FALSE) %>%
  302. kable_styling(bootstrap_options = c("striped", "hover"))
  303. # Analysis within language condition only (excluding Foreign)
  304. agg_per_lang <- agg_per %>% filter(CondGroup != "Foreign")
  305. # Fit linear model for language condition
  306. lm_lang <- lm(Percent_Correct ~ age, data = agg_per_lang)
  307. cat("\n\nLanguage Condition Analysis (MPS only):\n")
  308. cat("=======================================\n")
  309. print(summary(lm_lang))
  310. # Calculate effect sizes
  311. anova_results <- anova(lm_lang)
  312. eta_squared <- anova_results$"Sum Sq"[1] / sum(anova_results$"Sum Sq")
  313. cat("\nEta-squared (effect size):", round(eta_squared, 3), "\n")
  314. ```
  315. ###########################################
  316. #SI-2: The Language Network’s Topography in Children
  317. ###########################################
  318. #SI-2B Within- and between-participant similarity of activation patterns for the Language > Control contrast.
  319. # Complete SI-2B: Within- and between-participant similarity analysis with tables
  320. ```{r}
  321. ## 1. WITHIN-PARTICIPANT CORRELATIONS
  322. # A. Whole brain within-participant correlations
  323. within_corr <- read.csv("../data/corr_analyses/alice_correlations_within.csv")
  324. # Ensure Subject column exists
  325. if("Subject" %in% names(within_corr)) {
  326. # Already has Subject column
  327. } else if("Participant" %in% names(within_corr)) {
  328. names(within_corr)[names(within_corr) == "Participant"] <- "Subject"
  329. } else {
  330. # Assume first column is Subject ID
  331. names(within_corr)[1] <- "Subject"
  332. }
  333. within_corr$group <- factor(tolower(within_corr$Group), levels = c("early", "middle", "late", "adult"))
  334. # Statistical models - whole brain within
  335. m_within_wb <- lm(Corr ~ group, data = within_corr)
  336. # Continuous age analysis (children only)
  337. cont_within_wb <- merge(within_corr, ds1_demo, by = "Subject") %>% filter(Set != 'ADULT')
  338. wb_within_cont <- lm(Corr ~ Age, data = cont_within_wb)
  339. # B. ROI-based within-participant correlations (LH language network)
  340. all_sp_corr <- read.csv("../data/corr_analyses/sp_corr_data.csv")
  341. all_sp_corr_lh <- all_sp_corr %>%
  342. filter(variable %in% c("IFG", "IFGorb", "MFG", "AntTemp", "PostTemp")) %>%
  343. group_by(Participant, group) %>%
  344. summarise(corr = mean(value, na.rm = TRUE), .groups = "drop") %>%
  345. rename(Subject = Participant)
  346. all_sp_corr_lh$group <- factor(all_sp_corr_lh$group, levels = c("early", "middle", "late", "adult"))
  347. # Statistical models - ROI within
  348. m_within_roi <- lm(corr ~ group, data = all_sp_corr_lh)
  349. # Continuous age analysis (children only)
  350. cont_within_roi <- merge(all_sp_corr_lh, ds1_demo, by = "Subject") %>%
  351. filter(group != 'adult')
  352. roi_within_cont <- lm(corr ~ Age, data = cont_within_roi)
  353. ## 2. BETWEEN-PARTICIPANT CORRELATIONS
  354. # Process between-participant data function
  355. process_between_data <- function(df, group_name) {
  356. df$r <- rowMeans(df[, -1], na.rm = TRUE)
  357. df$group <- group_name
  358. # Standardize first column name to "Subject"
  359. if("Subject" %in% names(df)) {
  360. # Already has Subject column
  361. } else if("Participant" %in% names(df)) {
  362. names(df)[names(df) == "Participant"] <- "Subject"
  363. } else if("participant" %in% names(df)) {
  364. names(df)[names(df) == "participant"] <- "Subject"
  365. } else {
  366. # Assume first column is Subject ID
  367. names(df)[1] <- "Subject"
  368. }
  369. if(group_name == "adult") df$Subject <- gsub("_", "", df$Subject)
  370. return(df[, c("Subject", "r", "group")])
  371. }
  372. # C. Whole brain between-participant correlations
  373. df1_bt2_avg <- read.csv("../data/corr_analyses/alice_correlations_between_early_2.csv")
  374. df2_bt2_avg <- read.csv("../data/corr_analyses/alice_correlations_between_middle_2_all.csv")
  375. df3_bt2_avg <- read.csv("../data/corr_analyses/alice_correlations_between_late_2.csv")
  376. df4_bt2_avg <- read.csv("../data/corr_analyses/alice_correlations_between_adult_2_updated_092823.csv")
  377. all_between_wb <- rbind(
  378. process_between_data(df1_bt2_avg, "early"),
  379. process_between_data(df2_bt2_avg, "middle"),
  380. process_between_data(df3_bt2_avg, "late"),
  381. process_between_data(df4_bt2_avg, "adult")
  382. )
  383. all_between_wb$group <- factor(all_between_wb$group, levels = c("early", "middle", "late", "adult"))
  384. # Statistical models - whole brain between
  385. m_between_wb <- lm(r ~ group, data = all_between_wb)
  386. # Continuous age analysis (children only)
  387. cont_between_wb <- merge(all_between_wb, ds1_demo, by = "Subject") %>%
  388. filter(group != 'adult')
  389. wb_between_cont <- lm(r ~ Age, data = cont_between_wb)
  390. # D. ROI-based between-participant correlations
  391. df1_roi_avg <- read.csv("../data/corr_analyses/ROI/avg_corr_matrix_early_lh.csv")
  392. df2_roi_avg <- read.csv("../data/corr_analyses/ROI/avg_corr_matrix_middle_lh.csv")
  393. df3_roi_avg <- read.csv("../data/corr_analyses/ROI/avg_corr_matrix_late_lh.csv")
  394. df4_roi_avg <- read.csv("../data/corr_analyses/ROI/avg_corr_matrix_adult_lh_updated092823.csv")
  395. all_between_roi <- rbind(
  396. process_between_data(df1_roi_avg, "early"),
  397. process_between_data(df2_roi_avg, "middle"),
  398. process_between_data(df3_roi_avg, "late"),
  399. process_between_data(df4_roi_avg, "adult")
  400. )
  401. all_between_roi$group <- factor(all_between_roi$group, levels = c("early", "middle", "late", "adult"))
  402. # Statistical models - ROI between
  403. m_between_roi <- lm(r ~ group, data = all_between_roi)
  404. # Continuous age analysis (children only)
  405. cont_between_roi <- merge(all_between_roi, ds1_demo, by = "Subject") %>%
  406. filter(group != 'adult')
  407. roi_between_cont <- lm(r ~ Age, data = cont_between_roi)
  408. ## DEBUGGING SECTION - ADD THIS TO TROUBLESHOOT
  409. # Debug function to check continuous models
  410. debug_continuous_model <- function(model, model_name, data) {
  411. cat("\n=== Debugging", model_name, "===\n")
  412. # Check if Age variable exists and its properties
  413. cat("Age variable summary:\n")
  414. if("Age" %in% names(data)) {
  415. print(summary(data$Age))
  416. cat("Age class:", class(data$Age), "\n")
  417. cat("Age range:", range(data$Age, na.rm = TRUE), "\n")
  418. cat("Any NAs in Age:", sum(is.na(data$Age)), "\n")
  419. } else {
  420. cat("WARNING: No 'Age' variable found in data!\n")
  421. cat("Available variables:", names(data), "\n")
  422. }
  423. # Check model summary
  424. cat("\nModel summary:\n")
  425. model_summary <- summary(model)
  426. print(model_summary)
  427. # Check coefficient names
  428. cat("\nCoefficient names:\n")
  429. print(rownames(model_summary$coefficients))
  430. # Try to extract Age coefficient
  431. cat("\nAge coefficient extraction:\n")
  432. if("Age" %in% rownames(model_summary$coefficients)) {
  433. age_coef <- model_summary$coefficients["Age", ]
  434. print(age_coef)
  435. } else {
  436. cat("WARNING: 'Age' not found in model coefficients!\n")
  437. }
  438. cat("========================\n\n")
  439. }
  440. # Check data merging for continuous models
  441. cat("=== CHECKING DATA MERGING ===\n")
  442. cat("ds1_demo columns:", names(ds1_demo), "\n")
  443. if("Age" %in% names(ds1_demo)) {
  444. cat("ds1_demo Age summary:", summary(ds1_demo$Age), "\n")
  445. } else {
  446. cat("WARNING: No Age column in ds1_demo!\n")
  447. }
  448. # Check each merged dataset
  449. check_merged_data <- function(merged_data, name) {
  450. cat("\n", name, ":\n")
  451. cat("Dimensions:", dim(merged_data), "\n")
  452. cat("Age column exists:", "Age" %in% names(merged_data), "\n")
  453. if("Age" %in% names(merged_data)) {
  454. cat("Age summary:", summary(merged_data$Age), "\n")
  455. }
  456. }
  457. check_merged_data(cont_within_roi, "cont_within_roi")
  458. check_merged_data(cont_within_wb, "cont_within_wb")
  459. check_merged_data(cont_between_roi, "cont_between_roi")
  460. check_merged_data(cont_between_wb, "cont_between_wb")
  461. # Run debugging for all continuous models
  462. debug_continuous_model(roi_within_cont, "ROI Within Continuous", cont_within_roi)
  463. debug_continuous_model(wb_within_cont, "WB Within Continuous", cont_within_wb)
  464. debug_continuous_model(roi_between_cont, "ROI Between Continuous", cont_between_roi)
  465. debug_continuous_model(wb_between_cont, "WB Between Continuous", cont_between_wb)
  466. # Robust coefficient extraction function
  467. extract_age_coefficient <- function(model) {
  468. model_summary <- summary(model)
  469. coef_matrix <- model_summary$coefficients
  470. # Check different possible names for Age variable
  471. age_names <- c("Age", "age", "AGE")
  472. age_row <- NULL
  473. for(name in age_names) {
  474. if(name %in% rownames(coef_matrix)) {
  475. age_row <- name
  476. break
  477. }
  478. }
  479. if(is.null(age_row)) {
  480. cat("WARNING: No Age variable found in model coefficients.\n")
  481. cat("Available coefficients:", rownames(coef_matrix), "\n")
  482. return(c(Estimate = 0, `Std. Error` = 0, `t value` = 0, `Pr(>|t|)` = 1))
  483. }
  484. return(coef_matrix[age_row, ])
  485. }
  486. ## END DEBUGGING SECTION
  487. ## 3. TABLE GENERATION FUNCTIONS
  488. # Function to format p-values with bold for significance
  489. format_p <- function(p) {
  490. ifelse(p < 0.001, "**<0.001**",
  491. ifelse(p < 0.01, paste0("**", sprintf("%.3f", p), "**"),
  492. ifelse(p < 0.05, paste0("**", sprintf("%.3f", p), "**"),
  493. sprintf("%.3f", p))))
  494. }
  495. # Function to create formatted table
  496. create_results_table <- function(continuous_model, categorical_model, title) {
  497. # Extract continuous age effect using robust method
  498. cont_coef <- extract_age_coefficient(continuous_model)
  499. # Extract pairwise comparisons vs Adult using emmeans
  500. emm_result <- emmeans(categorical_model, ~ group)
  501. contrasts_result <- pairs(emm_result, adjust = "sidak")
  502. contrast_df <- as.data.frame(contrasts_result)
  503. # Find comparisons against adult (adult should be reference)
  504. # Look for patterns like "early - adult", "middle - adult", "late - adult"
  505. early_vs_adult <- contrast_df[grepl("early.*-.*adult", contrast_df$contrast), ]
  506. middle_vs_adult <- contrast_df[grepl("middle.*-.*adult", contrast_df$contrast), ]
  507. late_vs_adult <- contrast_df[grepl("late.*-.*adult", contrast_df$contrast), ]
  508. # If no matches found, try alternative approach with manual contrasts
  509. if(nrow(early_vs_adult) == 0) {
  510. # Create custom contrasts comparing each group to adult
  511. contrast_list <- list(
  512. "early_vs_adult" = c(1, 0, 0, -1), # early - adult
  513. "middle_vs_adult" = c(0, 1, 0, -1), # middle - adult
  514. "late_vs_adult" = c(0, 0, 1, -1) # late - adult
  515. )
  516. custom_contrasts <- contrast(emm_result, contrast_list, adjust = "sidak")
  517. contrast_df <- as.data.frame(custom_contrasts)
  518. early_vs_adult <- contrast_df[1, ]
  519. middle_vs_adult <- contrast_df[2, ]
  520. late_vs_adult <- contrast_df[3, ]
  521. }
  522. # Create results dataframe
  523. results <- data.frame(
  524. Comparison = c("Age = Continuous", "Early vs. Adult", "Middle vs. Adult", "Late vs. Adult"),
  525. B = c(round(cont_coef["Estimate"], 2),
  526. round(early_vs_adult$estimate, 2),
  527. round(middle_vs_adult$estimate, 2),
  528. round(late_vs_adult$estimate, 2)),
  529. SE = c(round(cont_coef["Std. Error"], 2),
  530. round(early_vs_adult$SE, 2),
  531. round(middle_vs_adult$SE, 2),
  532. round(late_vs_adult$SE, 2)),
  533. t = c(round(cont_coef["t value"], 2),
  534. round(early_vs_adult$t.ratio, 2),
  535. round(middle_vs_adult$t.ratio, 2),
  536. round(late_vs_adult$t.ratio, 2)),
  537. p = c(format_p(cont_coef["Pr(>|t|)"]),
  538. format_p(early_vs_adult$p.value),
  539. format_p(middle_vs_adult$p.value),
  540. format_p(late_vs_adult$p.value))
  541. )
  542. # Create kable table
  543. kable(results,
  544. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  545. caption = paste0("**", title, "**"),
  546. escape = FALSE) %>%
  547. kable_styling(bootstrap_options = c("striped", "hover"))
  548. }
  549. # Alternative simple table generation (use if main function fails)
  550. create_simple_table <- function(continuous_model, categorical_model, title) {
  551. # Continuous age effect
  552. cont_summary <- summary(continuous_model)
  553. age_coef <- cont_summary$coefficients["Age", ]
  554. # Get group means and use adult as reference
  555. group_summary <- summary(categorical_model)
  556. coef_matrix <- group_summary$coefficients
  557. # Extract coefficients (adult is reference, so intercept = adult mean)
  558. # Other groups show difference from adult
  559. intercept <- coef_matrix["(Intercept)", ]
  560. early_coef <- if("groupearly" %in% rownames(coef_matrix)) coef_matrix["groupearly", ] else c(0, 0, 0, 1)
  561. middle_coef <- if("groupmiddle" %in% rownames(coef_matrix)) coef_matrix["groupmiddle", ] else c(0, 0, 0, 1)
  562. late_coef <- if("grouplate" %in% rownames(coef_matrix)) coef_matrix["grouplate", ] else c(0, 0, 0, 1)
  563. # Create results
  564. results <- data.frame(
  565. Comparison = c("Age = Continuous", "Early vs. Adult", "Middle vs. Adult", "Late vs. Adult"),
  566. B = c(round(age_coef["Estimate"], 2),
  567. round(early_coef["Estimate"], 2),
  568. round(middle_coef["Estimate"], 2),
  569. round(late_coef["Estimate"], 2)),
  570. SE = c(round(age_coef["Std. Error"], 2),
  571. round(early_coef["Std. Error"], 2),
  572. round(middle_coef["Std. Error"], 2),
  573. round(late_coef["Std. Error"], 2)),
  574. t = c(round(age_coef["t value"], 2),
  575. round(early_coef["t value"], 2),
  576. round(middle_coef["t value"], 2),
  577. round(late_coef["t value"], 2)),
  578. p = c(format_p(age_coef["Pr(>|t|)"]),
  579. format_p(early_coef["Pr(>|t|)"]),
  580. format_p(middle_coef["Pr(>|t|)"]),
  581. format_p(late_coef["Pr(>|t|)"]))
  582. )
  583. kable(results,
  584. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  585. caption = paste0("**", title, "**"),
  586. escape = FALSE) %>%
  587. kable_styling(bootstrap_options = c("striped", "hover"))
  588. }
  589. # Debug function to check contrasts (optional - remove if not needed)
  590. debug_contrasts <- function(categorical_model, model_name) {
  591. cat("\n=== Debug for", model_name, "===\n")
  592. emm_result <- emmeans(categorical_model, ~ group)
  593. cat("Estimated marginal means:\n")
  594. print(emm_result)
  595. contrasts_result <- pairs(emm_result, adjust = "sidak")
  596. cat("All pairwise contrasts:\n")
  597. print(contrasts_result)
  598. cat("========================\n")
  599. }
  600. # Run debug for all models (comment out if not needed)
  601. debug_contrasts(m_within_roi, "ROI Within")
  602. debug_contrasts(m_within_wb, "Whole Brain Within")
  603. debug_contrasts(m_between_roi, "ROI Between")
  604. debug_contrasts(m_between_wb, "Whole Brain Between")
  605. # Table A: Within-participant correlations (LH language network)
  606. table_a <- create_results_table(
  607. roi_within_cont, m_within_roi,
  608. "A. Age differences in within-participant correlations (indexing activation stability across runs) within the LH language network."
  609. )
  610. # Table B: Within-participant correlations (whole brain)
  611. cat("Generating Table B...\n")
  612. table_b <- create_results_table(
  613. wb_within_cont, m_within_wb,
  614. "B. Age differences in within-participant correlations across the brain."
  615. )
  616. # Table C: Between-participant correlations (LH language network)
  617. cat("Generating Table C...\n")
  618. table_c <- create_results_table(
  619. roi_between_cont, m_between_roi,
  620. "C. Age differences in between-participant correlations (indexing inter-individual topographic variability) within the LH language network."
  621. )
  622. # Table D: Between-participant correlations (whole brain)
  623. cat("Generating Table D...\n")
  624. table_d <- create_results_table(
  625. wb_between_cont, m_between_wb,
  626. "D. Age differences in between-participant correlations across the brain."
  627. )
  628. # Display all tables
  629. cat("## SI-2B Statistical Results Tables\n\n")
  630. print(table_a)
  631. cat("\n")
  632. print(table_b)
  633. cat("\n")
  634. print(table_c)
  635. cat("\n")
  636. print(table_d)
  637. ## 5. COMBINED FIGURE (OPTIONAL)
  638. # Prepare data for plotting
  639. prepare_plot_data <- function(within_data, between_data, within_col, between_col, within_group_col, between_group_col) {
  640. within_plot <- within_data %>%
  641. select(all_of(c(within_group_col, within_col))) %>%
  642. rename(group = !!within_group_col, Corr = !!within_col) %>%
  643. mutate(comparison = "within")
  644. between_plot <- between_data %>%
  645. select(all_of(c(between_group_col, between_col))) %>%
  646. rename(group = !!between_group_col, Corr = !!between_col) %>%
  647. mutate(comparison = "between")
  648. return(rbind(within_plot, between_plot))
  649. }
  650. # ROI data
  651. roi_plot_data <- prepare_plot_data(all_sp_corr_lh, all_between_roi, "corr", "r", "group", "group")
  652. # Whole brain data
  653. wb_plot_data <- prepare_plot_data(within_corr, all_between_wb, "Corr", "r", "group", "group")
  654. # Create plots
  655. create_corr_plot <- function(data, title) {
  656. data$combined <- paste(data$group, data$comparison, sep = "_")
  657. data$combined <- factor(data$combined,
  658. levels = c("early_within", "early_between", "middle_within", "middle_between",
  659. "late_within", "late_between", "adult_within", "adult_between"))
  660. ggbarplot(data, x = "combined", y = "Corr", fill = "comparison",
  661. add = "mean_se", palette = c("gray33", "gray70"), width = 0.6,
  662. ylab = "Correlations", ylim = c(0, 1), xlab = "", title = title) +
  663. scale_x_discrete(labels = rep(c("w/in", "b/t"), 4)) +
  664. theme_classic() + theme(text = element_text(size = 18))
  665. }
  666. roi_plot <- create_corr_plot(roi_plot_data, "A. LH Language Parcels")
  667. wb_plot <- create_corr_plot(wb_plot_data, "B. Whole Brain")
  668. cat("\n## Plots\n")
  669. print(roi_plot)
  670. print(wb_plot)
  671. ```
  672. ###########################################
  673. #SI-3: Additional Analyses Related to Language Lateralization
  674. ###########################################
  675. ## SI-3.A: Lateralization Index and Magnitude by Component (Frontal and Temporal)
  676. ### Data Preparation for Lateralization Analysis
  677. ```{r lateralization-data-prep}
  678. # Summarize volume data across ROIs by summing effects for each Subject, age, hemi, and front
  679. ds1_vol_sum <- ds1_vol %>%
  680. group_by(Subject, age, hemi, front) %>%
  681. summarise(contrast = sum(effect, na.rm = TRUE), .groups = "drop")
  682. ds2_vol_sum <- ds2_vol %>%
  683. group_by(Subject, age, hemi, front) %>%
  684. summarise(contrast = sum(effect, na.rm = TRUE), .groups = "drop")
  685. # Filter effect data for language conditions
  686. ####Wide to Long DS1
  687. # Filter effect data for language conditions
  688. ds1_eff_lang <- ds1_eff %>%
  689. filter(condition == "language")
  690. # Filter for language condition
  691. levels(ds1_eff_lang$ROI) <- gsub("R ", "", levels(ds1_eff_lang$ROI))
  692. levels(ds1_eff_lang$ROI) <- gsub("IFG orb", "IFGorb", levels(ds1_eff_lang$ROI))
  693. levels(ds1_eff_lang$ROI) <- gsub("Post temp", "PostTemp", levels(ds1_eff_lang$ROI))
  694. ## Remove num_outliers, which has some NAs
  695. ds1_eff_lang <- ds1_eff_lang %>% select(-num_outliers)
  696. ## Make sure there are no single (null) observations
  697. oneobs <- ds1_eff_lang %>%
  698. group_by(Subject) %>%
  699. summarize(n = n()) %>%
  700. filter(n==1) %>%
  701. pull(Subject)
  702. ds1_eff_lang <- ds1_eff_lang %>%
  703. filter(!Subject %in% oneobs)
  704. na_rows <- ds1_eff_lang %>%
  705. filter(if_any(everything(), is.na))
  706. na_rows
  707. ## Inspect data structure
  708. table(ds1_eff_lang$ROI, ds1_eff_lang$hemi)
  709. ####Wide to Long DS2
  710. #Extract hemisphere info first, then clean ROI names
  711. ds2_eff_lang <- ds2_eff %>%
  712. dplyr::filter(condition == "EffectSize.mentalsocialphysical")
  713. ds2_eff_lang <- ds2_eff_lang %>%
  714. mutate(
  715. # Extract hemisphere from ROI names
  716. hemi_new = case_when(
  717. stringr::str_detect(as.character(ROI), "^LH_") ~ "lh",
  718. stringr::str_detect(as.character(ROI), "^RH_") ~ "rh",
  719. TRUE ~ as.character(hemi)
  720. ),
  721. # Clean ROI names by removing prefixes
  722. ROI_new = str_replace(as.character(ROI), "^(LH_|RH_)", "")
  723. ) %>%
  724. # Replace columns
  725. select(-hemi, -ROI) %>%
  726. rename(hemi = hemi_new, ROI = ROI_new) %>%
  727. mutate(
  728. hemi = as.factor(hemi),
  729. ROI = as.factor(ROI)
  730. )
  731. # Now apply the same standardization as DS1
  732. levels(ds2_eff_lang$ROI) <- gsub("R ", "", levels(ds2_eff_lang$ROI))
  733. levels(ds2_eff_lang$ROI) <- gsub("IFG orb", "IFGorb", levels(ds2_eff_lang$ROI))
  734. levels(ds2_eff_lang$ROI) <- gsub("Post temp", "PostTemp", levels(ds2_eff_lang$ROI))
  735. # Check the results
  736. print("DS2 ROI levels after cleaning:")
  737. print(levels(ds2_eff_lang$ROI))
  738. print("DS2 hemisphere levels:")
  739. print(levels(ds2_eff_lang$hemi))
  740. ## Inspect data structure
  741. table(ds2_eff_lang$ROI, ds2_eff_lang$hemi)
  742. cat("Data preparation completed for lateralization analysis.\n")
  743. cat("DS1 volume summary dimensions:", dim(ds1_vol_sum), "\n")
  744. cat("DS2 volume summary dimensions:", dim(ds2_vol_sum), "\n")
  745. ```
  746. ### LI Analysis Functions
  747. ```{r lateralization-functions}
  748. # Function to analyze Lateralization Index (LI) for a specific region
  749. analyze_LI_component <- function(data, dataset_label = "Dataset", region = "front") {
  750. # Filter for the specified region (front or post)
  751. data_region <- data %>%
  752. filter(front == region)
  753. # Pivot to wide format: separate 'lh' and 'rh' columns, then calculate LI
  754. data_wide <- data_region %>%
  755. pivot_wider(names_from = hemi, values_from = contrast) %>%
  756. mutate(LI = (lh - rh) / (lh + rh))
  757. # Fit linear model: LI predicted by age
  758. model <- lm(LI ~ age, data = data_wide)
  759. # Estimated marginal means and contrasts (each age vs. adult)
  760. emm <- emmeans(model, ~ age)
  761. contrast_res <- contrast(emm, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  762. # Return results
  763. list(
  764. dataset = dataset_label,
  765. region = region,
  766. model = model,
  767. data_wide = data_wide,
  768. contrasts = contrast_res
  769. )
  770. }
  771. # Function to analyze magnitude (contrast values) for a region
  772. analyze_magnitude_component <- function(data, dataset_label = "Dataset", region = "front") {
  773. # Filter for the specified region (front or post)
  774. data_region <- data %>%
  775. filter(front == region)
  776. # Fit linear mixed model: contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI)
  777. m <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI), data = data_region)
  778. # Estimated marginal means and contrasts for interaction
  779. emm <- emmeans(m, ~ hemi:age, lmer.df = "kenward-roger")
  780. contrast_res <- contrast(emm, interaction = c("pairwise", "trt.vs.ctrl"), adjust = "sidak")
  781. # Return results
  782. list(
  783. dataset = dataset_label,
  784. region = region,
  785. model = m,
  786. contrasts = contrast_res
  787. )
  788. }
  789. cat("Lateralization analysis functions defined.\n")
  790. ```
  791. ### Lateralization Index Analysis - Dataset 1
  792. ```{r li-analysis-ds1}
  793. # Dataset 1 - Frontal regions
  794. cat("Dataset 1 - Frontal LI Analysis:\n")
  795. cat("=================================================\n")
  796. res_li_ds1_front <- analyze_LI_component(ds1_vol_sum, "DS1", region = "front")
  797. print(summary(res_li_ds1_front$model))
  798. cat("\nContrasts (vs. Adult):\n")
  799. print(res_li_ds1_front$contrasts)
  800. cat("\n\nDataset 1 - Temporal LI Analysis:\n")
  801. cat("==================================================\n")
  802. res_li_ds1_post <- analyze_LI_component(ds1_vol_sum, "DS1", region = "post")
  803. print(summary(res_li_ds1_post$model))
  804. cat("\nContrasts (vs. Adult):\n")
  805. print(res_li_ds1_post$contrasts)
  806. ```
  807. ### LI Analysis - Dataset 2
  808. ```{r li-analysis-ds2}
  809. # Dataset 2 - Frontal regions
  810. cat("Dataset 2 - Frontal Lateralization Index Analysis:\n")
  811. cat("=================================================\n")
  812. res_li_ds2_front <- analyze_LI_component(ds2_vol_sum, "DS2", region = "front")
  813. print(summary(res_li_ds2_front$model))
  814. cat("\nContrasts (vs. Adult):\n")
  815. print(res_li_ds2_front$contrasts)
  816. cat("\n\nDataset 2 - Temporal Lateralization Index Analysis:\n")
  817. cat("==================================================\n")
  818. res_li_ds2_post <- analyze_LI_component(ds2_vol_sum, "DS2", region = "post")
  819. print(summary(res_li_ds2_post$model))
  820. cat("\nContrasts (vs. Adult):\n")
  821. print(res_li_ds2_post$contrasts)
  822. ```
  823. ### Magnitude Analysis - Dataset 1
  824. ```{r magnitude-analysis-ds1}
  825. # Dataset 1 - Frontal regions magnitude
  826. cat("Dataset 1 - Frontal Magnitude Analysis:\n")
  827. cat("=====================================\n")
  828. res_mag_ds1_front <- analyze_magnitude_component(ds1_eff_lang, "DS1", region = "front")
  829. print(summary(res_mag_ds1_front$model))
  830. cat("\nContrasts:\n")
  831. print(res_mag_ds1_front$contrasts)
  832. cat("\n\nDataset 1 - Temporal Magnitude Analysis:\n")
  833. cat("======================================\n")
  834. res_mag_ds1_post <- analyze_magnitude_component(ds1_eff_lang, "DS1", region = "post")
  835. print(summary(res_mag_ds1_post$model))
  836. cat("\nContrasts:\n")
  837. print(res_mag_ds1_post$contrasts)
  838. ```
  839. ### Magnitude Analysis - Dataset 2
  840. ```{r magnitude-analysis-ds2}
  841. # Dataset 2 - Frontal regions magnitude
  842. cat("Dataset 2 - Frontal Magnitude Analysis:\n")
  843. cat("=====================================\n")
  844. res_mag_ds2_front <- analyze_magnitude_component(ds2_eff_lang, "DS2", region = "front")
  845. print(summary(res_mag_ds2_front$model))
  846. cat("\nContrasts:\n")
  847. print(res_mag_ds2_front$contrasts)
  848. cat("\n\nDataset 2 - Temporal Magnitude Analysis:\n")
  849. cat("======================================\n")
  850. res_mag_ds2_post <- analyze_magnitude_component(ds2_eff_lang, "DS2", region = "post")
  851. print(summary(res_mag_ds2_post$model))
  852. cat("\nContrasts:\n")
  853. print(res_mag_ds2_post$contrasts)
  854. ```
  855. # SI-3B: Bayesian Analysis (commented because long runtime)
  856. ```{r Bayes-Factors}
  857. ds1_eff_lang
  858. ds2_eff_lang
  859. ## Prepare data for monotonic function
  860. ds1_eff_lang <- ds1_eff_lang %>%
  861. mutate(
  862. age_ordered_factor = factor(age, levels = c("early", "middle", "late", "adult"), ordered = TRUE)
  863. )
  864. ds2_eff_lang <- ds2_eff_lang %>%
  865. mutate(
  866. age_ordered_factor = factor(age, levels = c("early", "middle", "late", "adult"), ordered = TRUE)
  867. )
  868. save(
  869. ds1_eff_lang, ds2_eff_lang,
  870. file="data_to_openmind.RData"
  871. )
  872. # dir.create("brms_models", showWarnings = FALSE)
  873. #
  874. # base_args <- list(
  875. # chains = 4,
  876. # cores = 4,
  877. # iter = 20000,
  878. # warmup = 4000,
  879. # save_pars = save_pars(all = TRUE),
  880. # control = list(adapt_delta = 0.999),
  881. # seed = 123
  882. # )
  883. #
  884. # fit_or_load <- function(name, formula, data, priors, models_dir = "brms_models") {
  885. # f <- file.path(models_dir, paste0(name, ".RDS"))
  886. # if (file.exists(f)) return(readRDS(f))
  887. # fit <- do.call(brm, c(list(formula = formula, data = data, prior = priors), base_args))
  888. # saveRDS(fit, f)
  889. # fit
  890. # }
  891. #
  892. #
  893. # ## ---- Model Fitting ----
  894. # ### ---- DS1 ----
  895. # #### ---- Monotonic ----
  896. # ds1_monotonic_null <- fit_or_load(
  897. # formula = contrast ~ hemi + mo(age_ordered_factor) +
  898. # (hemi | Subject) + (hemi | ROI),
  899. # data=ds1_eff_lang,
  900. # priors=c(
  901. # prior(normal(0, 1), class="b"),
  902. # prior(normal(0, 2), class="Intercept"),
  903. # prior(normal(0, 2), class="sigma")
  904. # ),
  905. # name = "ds1_monotonic_null"
  906. # )
  907. #
  908. # ds1_monotonic_liberal <- fit_or_load(
  909. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  910. # (hemi | Subject) + (hemi | ROI),
  911. # data=ds1_eff_lang,
  912. # priors=c(
  913. # prior(normal(0, 1), class="b"),
  914. # prior(normal(0, .5), coef="moage_ordered_factor:hemirh"),
  915. # prior(normal(0, 2), class="Intercept"),
  916. # prior(normal(0, 2), class="sigma")
  917. # ),
  918. # name = "ds1_monotonic_liberal"
  919. # )
  920. #
  921. # ds1_monotonic_moderate <- fit_or_load(
  922. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  923. # (hemi | Subject) + (hemi | ROI),
  924. # data=ds1_eff_lang,
  925. # priors=c(
  926. # prior(normal(0, 1), class="b"),
  927. # prior(normal(0, .2), coef="moage_ordered_factor:hemirh"),
  928. # prior(normal(0, 2), class="Intercept"),
  929. # prior(normal(0, 2), class="sigma")
  930. # ),
  931. # name = "ds1_monotonic_moderate"
  932. # )
  933. #
  934. # ds1_monotonic_conservative <- fit_or_load(
  935. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  936. # (hemi | Subject) + (hemi | ROI),
  937. # data=ds1_eff_lang,
  938. # priors=c(
  939. # prior(normal(0, 1), class="b"),
  940. # prior(normal(0, .1), coef="moage_ordered_factor:hemirh"),
  941. # prior(normal(0, 2), class="Intercept"),
  942. # prior(normal(0, 2), class="sigma")
  943. # ),
  944. # name = "ds1_monotonic_conservative"
  945. # )
  946. #
  947. # ds1_monotonic_extraconservative <- fit_or_load(
  948. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  949. # (hemi | Subject) + (hemi | ROI),
  950. # data=ds1_eff_lang,
  951. # priors=c(
  952. # prior(normal(0, 1), class="b"),
  953. # prior(normal(0, .05), coef="moage_ordered_factor:hemirh"),
  954. # prior(normal(0, 2), class="Intercept"),
  955. # prior(normal(0, 2), class="sigma")
  956. # ),
  957. # name = "ds1_monotonic_extraconservative"
  958. # )
  959. #
  960. # ### ---- Categorical ----
  961. # ds1_categorical_null <- fit_or_load(
  962. # formula = contrast ~ hemi + age +
  963. # (hemi | Subject) + (hemi | ROI),
  964. # data=ds1_eff_lang,
  965. # priors=c(
  966. # prior(normal(0, 1), coef="ageearly"),
  967. # prior(normal(0, 1), coef="agemiddle"),
  968. # prior(normal(0, 1), coef="agelate"),
  969. # prior(normal(0, 1), coef="hemirh"),
  970. # prior(normal(0, 2), class="Intercept"),
  971. # prior(normal(0, 2), class="sigma")
  972. # ),
  973. # name = "ds1_categorical_null"
  974. # )
  975. #
  976. # ds1_categorical_liberal <- fit_or_load(
  977. # formula = contrast ~ hemi * age +
  978. # (hemi | Subject) + (hemi | ROI),
  979. # data=ds1_eff_lang,
  980. # priors=c(
  981. # prior(normal(0, 0.5), class = "b"),
  982. # prior(normal(0, 1), coef="ageearly"),
  983. # prior(normal(0, 1), coef="agemiddle"),
  984. # prior(normal(0, 1), coef="agelate"),
  985. # prior(normal(0, 1), coef="hemirh"),
  986. # prior(normal(0, 2), class="Intercept"),
  987. # prior(normal(0, 2), class="sigma")
  988. # ),
  989. # name = "ds1_categorical_liberal"
  990. # )
  991. #
  992. # ds1_categorical_moderate <- fit_or_load(
  993. # formula = contrast ~ hemi * age +
  994. # (hemi | Subject) + (hemi | ROI),
  995. # data=ds1_eff_lang,
  996. # priors=c(
  997. # prior(normal(0, 0.2), class = "b"),
  998. # prior(normal(0, 1), coef="ageearly"),
  999. # prior(normal(0, 1), coef="agemiddle"),
  1000. # prior(normal(0, 1), coef="agelate"),
  1001. # prior(normal(0, 1), coef="hemirh"),
  1002. # prior(normal(0, 2), class="Intercept"),
  1003. # prior(normal(0, 2), class="sigma")
  1004. # ),
  1005. # name = "ds1_categorical_moderate"
  1006. # )
  1007. #
  1008. # ds1_categorical_conservative <- fit_or_load(
  1009. # formula = contrast ~ hemi * age +
  1010. # (hemi | Subject) + (hemi | ROI),
  1011. # data=ds1_eff_lang,
  1012. # priors=c(
  1013. # prior(normal(0, 0.1), class = "b"),
  1014. # prior(normal(0, 1), coef="ageearly"),
  1015. # prior(normal(0, 1), coef="agemiddle"),
  1016. # prior(normal(0, 1), coef="agelate"),
  1017. # prior(normal(0, 1), coef="hemirh"),
  1018. # prior(normal(0, 2), class="Intercept"),
  1019. # prior(normal(0, 2), class="sigma")
  1020. # ),
  1021. # name = "ds1_categorical_conservative"
  1022. # )
  1023. #
  1024. # ds1_categorical_extraconservative <- fit_or_load(
  1025. # formula = contrast ~ hemi * age +
  1026. # (hemi | Subject) + (hemi | ROI),
  1027. # data=ds1_eff_lang,
  1028. # priors=c(
  1029. # prior(normal(0, 0.05), class = "b"),
  1030. # prior(normal(0, 1), coef="ageearly"),
  1031. # prior(normal(0, 1), coef="agemiddle"),
  1032. # prior(normal(0, 1), coef="agelate"),
  1033. # prior(normal(0, 1), coef="hemirh"),
  1034. # prior(normal(0, 2), class="Intercept"),
  1035. # prior(normal(0, 2), class="sigma")
  1036. # ),
  1037. # name = "ds1_categorical_extraconservative"
  1038. # )
  1039. #
  1040. # ### ---- DS2 ----
  1041. # #### ---- Monotonic ----
  1042. # ds2_monotonic_null <- fit_or_load(
  1043. # formula = contrast ~ hemi + mo(age_ordered_factor) +
  1044. # (hemi | Subject) + (hemi | ROI),
  1045. # data=ds2_eff_lang,
  1046. # priors=c(
  1047. # prior(normal(0, 1), class="b"),
  1048. # prior(normal(0, 2), class="Intercept"),
  1049. # prior(normal(0, 2), class="sigma")
  1050. # ),
  1051. # name = "ds2_monotonic_null"
  1052. # )
  1053. #
  1054. # ds2_monotonic_liberal <- fit_or_load(
  1055. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  1056. # (hemi | Subject) + (hemi | ROI),
  1057. # data=ds2_eff_lang,
  1058. # priors=c(
  1059. # prior(normal(0, 1), class="b"),
  1060. # prior(normal(0, .5), coef="moage_ordered_factor:hemirh"),
  1061. # prior(normal(0, 2), class="Intercept"),
  1062. # prior(normal(0, 2), class="sigma")
  1063. # ),
  1064. # name = "ds2_monotonic_liberal"
  1065. # )
  1066. #
  1067. # ds2_monotonic_moderate <- fit_or_load(
  1068. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  1069. # (hemi | Subject) + (hemi | ROI),
  1070. # data=ds2_eff_lang,
  1071. # priors=c(
  1072. # prior(normal(0, 1), class="b"),
  1073. # prior(normal(0, .2), coef="moage_ordered_factor:hemirh"),
  1074. # prior(normal(0, 2), class="Intercept"),
  1075. # prior(normal(0, 2), class="sigma")
  1076. # ),
  1077. # name = "ds2_monotonic_moderate"
  1078. # )
  1079. #
  1080. # ds2_monotonic_conservative <- fit_or_load(
  1081. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  1082. # (hemi | Subject) + (hemi | ROI),
  1083. # data=ds2_eff_lang,
  1084. # priors=c(
  1085. # prior(normal(0, 1), class="b"),
  1086. # prior(normal(0, .1), coef="moage_ordered_factor:hemirh"),
  1087. # prior(normal(0, 2), class="Intercept"),
  1088. # prior(normal(0, 2), class="sigma")
  1089. # ),
  1090. # name = "ds2_monotonic_conservative"
  1091. # )
  1092. #
  1093. # ds2_monotonic_extraconservative <- fit_or_load(
  1094. # formula = contrast ~ hemi * mo(age_ordered_factor) +
  1095. # (hemi | Subject) + (hemi | ROI),
  1096. # data=ds2_eff_lang,
  1097. # priors=c(
  1098. # prior(normal(0, 1), class="b"),
  1099. # prior(normal(0, .05), coef="moage_ordered_factor:hemirh"),
  1100. # prior(normal(0, 2), class="Intercept"),
  1101. # prior(normal(0, 2), class="sigma")
  1102. # ),
  1103. # name = "ds2_monotonic_extraconservative"
  1104. # )
  1105. #
  1106. # ### ---- Categorical ----
  1107. # ds2_categorical_null <- fit_or_load(
  1108. # formula = contrast ~ hemi + age +
  1109. # (hemi | Subject) + (hemi | ROI),
  1110. # data=ds2_eff_lang,
  1111. # priors=c(
  1112. # prior(normal(0, 1), coef="ageearly"),
  1113. # prior(normal(0, 1), coef="agemiddle"),
  1114. # prior(normal(0, 1), coef="agelate"),
  1115. # prior(normal(0, 1), coef="hemirh"),
  1116. # prior(normal(0, 2), class="Intercept"),
  1117. # prior(normal(0, 2), class="sigma")
  1118. # ),
  1119. # name = "ds2_categorical_null"
  1120. # )
  1121. #
  1122. # ds2_categorical_liberal <- fit_or_load(
  1123. # formula = contrast ~ hemi * age +
  1124. # (hemi | Subject) + (hemi | ROI),
  1125. # data=ds2_eff_lang,
  1126. # priors=c(
  1127. # prior(normal(0, 0.5), class = "b"),
  1128. # prior(normal(0, 1), coef="ageearly"),
  1129. # prior(normal(0, 1), coef="agemiddle"),
  1130. # prior(normal(0, 1), coef="agelate"),
  1131. # prior(normal(0, 1), coef="hemirh"),
  1132. # prior(normal(0, 2), class="Intercept"),
  1133. # prior(normal(0, 2), class="sigma")
  1134. # ),
  1135. # name = "ds2_categorical_liberal"
  1136. # )
  1137. #
  1138. # ds2_categorical_moderate <- fit_or_load(
  1139. # formula = contrast ~ hemi * age +
  1140. # (hemi | Subject) + (hemi | ROI),
  1141. # data=ds2_eff_lang,
  1142. # priors=c(
  1143. # prior(normal(0, 0.2), class = "b"),
  1144. # prior(normal(0, 1), coef="ageearly"),
  1145. # prior(normal(0, 1), coef="agemiddle"),
  1146. # prior(normal(0, 1), coef="agelate"),
  1147. # prior(normal(0, 1), coef="hemirh"),
  1148. # prior(normal(0, 2), class="Intercept"),
  1149. # prior(normal(0, 2), class="sigma")
  1150. # ),
  1151. # name = "ds2_categorical_moderate"
  1152. # )
  1153. #
  1154. # ds2_categorical_conservative <- fit_or_load(
  1155. # formula = contrast ~ hemi * age +
  1156. # (hemi | Subject) + (hemi | ROI),
  1157. # data=ds2_eff_lang,
  1158. # priors=c(
  1159. # prior(normal(0, 0.1), class = "b"),
  1160. # prior(normal(0, 1), coef="ageearly"),
  1161. # prior(normal(0, 1), coef="agemiddle"),
  1162. # prior(normal(0, 1), coef="agelate"),
  1163. # prior(normal(0, 1), coef="hemirh"),
  1164. # prior(normal(0, 2), class="Intercept"),
  1165. # prior(normal(0, 2), class="sigma")
  1166. # ),
  1167. # name = "ds2_categorical_conservative"
  1168. # )
  1169. #
  1170. # ds2_categorical_extraconservative <- fit_or_load(
  1171. # formula = contrast ~ hemi * age +
  1172. # (hemi | Subject) + (hemi | ROI),
  1173. # data=ds2_eff_lang,
  1174. # priors=c(
  1175. # prior(normal(0, 0.05), class = "b"),
  1176. # prior(normal(0, 1), coef="ageearly"),
  1177. # prior(normal(0, 1), coef="agemiddle"),
  1178. # prior(normal(0, 1), coef="agelate"),
  1179. # prior(normal(0, 1), coef="hemirh"),
  1180. # prior(normal(0, 2), class="Intercept"),
  1181. # prior(normal(0, 2), class="sigma")
  1182. # ),
  1183. # name = "ds2_categorical_extraconservative"
  1184. # )
  1185. #
  1186. # ## ---- Bayes Factors ----
  1187. # ### ---- DS1 ----
  1188. # if (file.exists("brms_models/ds1_bayes_factors.RData")) {
  1189. # load("brms_models/ds1_bayes_factors.RData")
  1190. # } else {
  1191. # BF01_monotonic_liberal_ds1 <- bayes_factor(ds1_monotonic_null, ds1_monotonic_liberal)
  1192. # BF01_monotonic_moderate_ds1 <- bayes_factor(ds1_monotonic_null, ds1_monotonic_moderate)
  1193. # BF01_monotonic_conservative_ds1 <- bayes_factor(ds1_monotonic_null, ds1_monotonic_conservative)
  1194. # BF01_monotonic_extraconservative_ds1 <- bayes_factor(ds1_monotonic_null, ds1_monotonic_extraconservative)
  1195. #
  1196. # BF01_categorical_liberal_ds1 <- bayes_factor(ds1_categorical_null, ds1_categorical_liberal)
  1197. # BF01_categorical_moderate_ds1 <- bayes_factor(ds1_categorical_null, ds1_categorical_moderate)
  1198. # BF01_categorical_conservative_ds1 <- bayes_factor(ds1_categorical_null, ds1_categorical_conservative)
  1199. # BF01_categorical_extraconservative_ds1 <- bayes_factor(ds1_categorical_null, ds1_categorical_extraconservative)
  1200. #
  1201. # save(
  1202. # BF01_monotonic_liberal_ds1, BF01_monotonic_moderate_ds1,
  1203. # BF01_monotonic_conservative_ds1, BF01_monotonic_extraconservative_ds1,
  1204. # BF01_categorical_liberal_ds1, BF01_categorical_moderate_ds1,
  1205. # BF01_categorical_conservative_ds1, BF01_categorical_extraconservative_ds1,
  1206. # file="brms_models/ds1_bayes_factors.RData"
  1207. # )
  1208. #
  1209. # }
  1210. #
  1211. # ### ---- DS2 ----
  1212. # if (file.exists("brms_models/ds2_bayes_factors.RData")) {
  1213. # load("brms_models/ds2_bayes_factors.RData")
  1214. # } else {
  1215. # BF01_monotonic_liberal_ds2 <- bayes_factor(ds2_monotonic_null, ds2_monotonic_liberal)
  1216. # BF01_monotonic_moderate_ds2 <- bayes_factor(ds2_monotonic_null, ds2_monotonic_moderate)
  1217. # BF01_monotonic_conservative_ds2 <- bayes_factor(ds2_monotonic_null, ds2_monotonic_conservative)
  1218. # BF01_monotonic_extraconservative_ds2 <- bayes_factor(ds2_monotonic_null, ds2_monotonic_extraconservative)
  1219. #
  1220. # BF01_categorical_liberal_ds2 <- bayes_factor(ds2_categorical_null, ds2_categorical_liberal)
  1221. # BF01_categorical_moderate_ds2 <- bayes_factor(ds2_categorical_null, ds2_categorical_moderate)
  1222. # BF01_categorical_conservative_ds2 <- bayes_factor(ds2_categorical_null, ds2_categorical_conservative)
  1223. # BF01_categorical_extraconservative_ds2 <- bayes_factor(ds2_categorical_null, ds2_categorical_extraconservative)
  1224. #
  1225. # save(
  1226. # BF01_monotonic_liberal_ds2, BF01_monotonic_moderate_ds2,
  1227. # BF01_monotonic_conservative_ds2, BF01_monotonic_extraconservative_ds2,
  1228. # BF01_categorical_liberal_ds2, BF01_categorical_moderate_ds2,
  1229. # BF01_categorical_conservative_ds2, BF01_categorical_extraconservative_ds2,
  1230. # file="brms_models/ds2_bayes_factors.RData"
  1231. # )
  1232. #
  1233. # }
  1234. #
  1235. # ## Bayes Factors with monotonic age function
  1236. # BF01_monotonic_liberal_ds1$bf
  1237. # BF01_monotonic_moderate_ds1$bf
  1238. # BF01_monotonic_conservative_ds1$bf
  1239. # BF01_monotonic_extraconservative_ds1$bf
  1240. #
  1241. # 1/BF01_monotonic_liberal_ds1$bf
  1242. # 1/BF01_monotonic_moderate_ds1$bf
  1243. # 1/BF01_monotonic_conservative_ds1$bf
  1244. # 1/BF01_monotonic_extraconservative_ds1$bf
  1245. #
  1246. #
  1247. # BF01_monotonic_liberal_ds2$bf
  1248. # BF01_monotonic_moderate_ds2$bf
  1249. # BF01_monotonic_conservative_ds2$bf
  1250. # BF01_monotonic_extraconservative_ds2$bf
  1251. #
  1252. # 1/BF01_monotonic_liberal_ds2$bf
  1253. # 1/BF01_monotonic_moderate_ds2$bf
  1254. # 1/BF01_monotonic_conservative_ds2$bf
  1255. # 1/BF01_monotonic_extraconservative_ds2$bf
  1256. #
  1257. #
  1258. # ## Bayes factors with categorical age
  1259. # BF01_categorical_liberal_ds1$bf
  1260. # BF01_categorical_moderate_ds1$bf
  1261. # BF01_categorical_conservative_ds1$bf
  1262. # BF01_categorical_extraconservative_ds1$bf
  1263. #
  1264. # 1/BF01_categorical_liberal_ds1$bf
  1265. # 1/BF01_categorical_moderate_ds1$bf
  1266. # 1/BF01_categorical_conservative_ds1$bf
  1267. # 1/BF01_categorical_extraconservative_ds1$bf
  1268. #
  1269. # BF01_categorical_liberal_ds2$bf
  1270. # BF01_categorical_moderate_ds2$bf
  1271. # BF01_categorical_conservative_ds2$bf
  1272. # BF01_categorical_extraconservative_ds2$bf
  1273. #
  1274. # 1/BF01_categorical_liberal_ds2$bf
  1275. # 1/BF01_categorical_moderate_ds2$bf
  1276. # 1/BF01_categorical_conservative_ds2$bf
  1277. # 1/BF01_categorical_extraconservative_ds2$bf
  1278. #
  1279. # ## Select model outputs
  1280. # ds1_monotonic_extraconservative
  1281. # ds2_monotonic_conservative
  1282. # ds1_categorical_moderate
  1283. # ds1_categorical_conservative
  1284. #
  1285. #
  1286. # ## REMOVE IF NOT USING :: PP_CHECK
  1287. # lmerfit <- lmer(contrast ~ hemi * age +
  1288. # (hemi | Subject) + (hemi | ROI), data=ds1_eff_lang, control=lmerControl(optimizer = "bobyqa"))
  1289. #
  1290. # ysim <- tibble(
  1291. # sample = numeric(),
  1292. # yhat = numeric()
  1293. # )
  1294. #
  1295. # for(i in 1:100) {
  1296. # ysim <- ysim %>%
  1297. # add_row(
  1298. # yhat=unlist(simulate(lmerfit, 1)),
  1299. # sample=i
  1300. # )
  1301. # }
  1302. #
  1303. # ggplot() +
  1304. # geom_density(data=ysim,
  1305. # aes(x=yhat, group=sample),
  1306. # color="cadetblue",
  1307. # linewidth=.1) +
  1308. # geom_density(aes(x=ds1_eff_lang$contrast))
  1309. #
  1310. # ggsave("posterior_predictive_check.png")
  1311. ```
  1312. # SI-3.C: Lateralization Index Analysis Using LI Toolbox
  1313. ### Load and Prepare LI Toolbox Data
  1314. ### Load and Prepare LI Toolbox Data
  1315. ```{r li-toolbox-data-prep}
  1316. # Load LI toolbox output data for both datasets
  1317. ds1_li_toolbox <- read.csv("../data/ds1_li_toolbox_output.csv")
  1318. ds2_li_toolbox <- read.csv("../data/ds2_li_toolbox_output.csv")
  1319. # Set age factor levels for both datasets
  1320. ds1_li_toolbox$age <- factor(ds1_li_toolbox$Group, levels = c("early", "middle", "late", "adult"))
  1321. ds2_li_toolbox$age <- factor(ds2_li_toolbox$Group, levels = c("early", "middle", "late", "adult"))
  1322. cat("LI Toolbox data loaded successfully.\n")
  1323. cat("DS1 LI data points:", nrow(ds1_li_toolbox), "\n")
  1324. cat("DS2 LI data points:", nrow(ds2_li_toolbox), "\n")
  1325. ```
  1326. ```{r li-toolbox-analysis}
  1327. # SI-3.C: Lateralization Index Analysis Using LI Toolbox
  1328. ## Load and Prepare LI Toolbox Data
  1329. # Standardize Subject ID columns function
  1330. standardize_subject_column <- function(data) {
  1331. if("Subject" %in% names(data)) {
  1332. return(data)
  1333. } else if("Participant" %in% names(data)) {
  1334. return(data %>% rename(Subject = Participant))
  1335. } else if("participant" %in% names(data)) {
  1336. return(data %>% rename(Subject = participant))
  1337. } else {
  1338. # Assume first column is Subject ID
  1339. names(data)[1] <- "Subject"
  1340. return(data)
  1341. }
  1342. }
  1343. # Apply standardization to LI datasets
  1344. ds1_li_toolbox <- standardize_subject_column(ds1_li_toolbox)
  1345. ds2_li_toolbox <- standardize_subject_column(ds2_li_toolbox)
  1346. # Ensure consistent factor levels for age groups
  1347. age_levels <- c("early", "middle", "late", "adult")
  1348. ds1_li_toolbox$age <- factor(ds1_li_toolbox$age, levels = age_levels)
  1349. ds2_li_toolbox$age <- factor(ds2_li_toolbox$age, levels = age_levels)
  1350. ## Visualization Function
  1351. create_li_barplot <- function(data, title) {
  1352. ggbarplot(data,
  1353. x = "age",
  1354. y = "LI_overall",
  1355. add = c("mean_se", "jitter"),
  1356. add.params = list(size = 1, alpha = 0.2),
  1357. width = 0.6,
  1358. size = 0.5,
  1359. fill = "age",
  1360. palette = c("gray", "gray", "gray", "black"),
  1361. alpha = 0.6,
  1362. title = title,
  1363. ylab = "Lateralization Index (LI)",
  1364. xlab = "Age Group") +
  1365. geom_hline(yintercept = 0, linetype = "solid", color = "black") +
  1366. theme_classic() +
  1367. theme(legend.position = "none",
  1368. text = element_text(size = 18),
  1369. plot.title = element_text(hjust = 0.5, face = "bold")) +
  1370. scale_y_continuous(limits = c(-1, NA))
  1371. }
  1372. ## Statistical Analysis Function
  1373. analyze_overall_li <- function(data, dataset_name) {
  1374. # Fit model with adult as reference
  1375. model_li <- lm(LI_overall ~ age, data = data)
  1376. # Get emmeans and contrasts vs adult
  1377. emm_result <- emmeans(model_li, ~ age)
  1378. contrasts_result <- contrast(emm_result, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  1379. # ANOVA for overall effect
  1380. anova_results <- anova(model_li)
  1381. eta_squared <- anova_results$"Sum Sq"[1] / sum(anova_results$"Sum Sq")
  1382. # Output results
  1383. cat("\n", dataset_name, " Statistical Analysis:\n")
  1384. cat("==============================\n")
  1385. cat("ANOVA Results:\n")
  1386. print(anova_results)
  1387. cat("\nPost-hoc Comparisons (vs. Adult):\n")
  1388. print(contrasts_result)
  1389. cat("\nEffect size (eta-squared):", round(eta_squared, 4), "\n\n")
  1390. return(list(
  1391. model = model_li,
  1392. contrasts = contrasts_result,
  1393. eta_squared = eta_squared,
  1394. anova = anova_results
  1395. ))
  1396. }
  1397. ## Table Generation Function
  1398. create_li_contrast_table <- function(contrast_result, title) {
  1399. contrast_df <- as.data.frame(contrast_result)
  1400. # Format results
  1401. results <- contrast_df %>%
  1402. mutate(
  1403. Comparison = gsub(" - adult", " vs. Adult", contrast),
  1404. B = round(estimate, 2),
  1405. SE = round(SE, 2),
  1406. t = round(t.ratio, 2),
  1407. p = case_when(
  1408. p.value < 0.001 ~ "**<0.001**",
  1409. p.value < 0.01 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1410. p.value < 0.05 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1411. TRUE ~ sprintf("%.3f", p.value)
  1412. )
  1413. ) %>%
  1414. select(Comparison, B, SE, t, p)
  1415. # Create kable table
  1416. kable(results,
  1417. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  1418. caption = paste0("**", title, "**"),
  1419. escape = FALSE) %>%
  1420. kable_styling(bootstrap_options = c("striped", "hover"))
  1421. }
  1422. ## Execute Dataset 1 Analysis
  1423. cat("## Dataset 1 - Lateralization Index Analysis\n")
  1424. # Create visualization
  1425. mean_lat_ds1 <- create_li_barplot(ds1_li_toolbox, "Dataset 1: Lateralization Index")
  1426. print(mean_lat_ds1)
  1427. # Run statistical analysis
  1428. ds1_results <- analyze_overall_li(ds1_li_toolbox, "Dataset 1")
  1429. # Create summary table
  1430. ds1_table <- create_li_contrast_table(ds1_results$contrasts,
  1431. "Dataset 1 - Lateralization Index Comparisons (vs. Adult)")
  1432. print(ds1_table)
  1433. ## Execute Dataset 2 Analysis
  1434. cat("## Dataset 2 - Lateralization Index Analysis\n")
  1435. # Create visualization
  1436. mean_lat_ds2 <- create_li_barplot(ds2_li_toolbox, "Dataset 2: Lateralization Index")
  1437. print(mean_lat_ds2)
  1438. # Run statistical analysis
  1439. ds2_results <- analyze_overall_li(ds2_li_toolbox, "Dataset 2")
  1440. # Create summary table
  1441. ds2_table <- create_li_contrast_table(ds2_results$contrasts,
  1442. "Dataset 2 - Lateralization Index Comparisons (vs. Adult)")
  1443. print(ds2_table)
  1444. ## Summary Statistics
  1445. cat("## Summary Statistics\n")
  1446. cat("Dataset 1 eta-squared:", round(ds1_results$eta_squared, 4), "\n")
  1447. cat("Dataset 2 eta-squared:", round(ds2_results$eta_squared, 4), "\n")
  1448. ```
  1449. ## SI-3.D: Lateralization Analysis Combining Datasets 1 and 2
  1450. ### Data Preparation for Combined Analysis
  1451. ```{r combined-lat-data-prep}
  1452. # SI-3.D: Lateralization Analysis Combining Datasets 1 and 2
  1453. ## Data Preparation for Combined Analysis
  1454. # Filter combined effect data for language conditions from both datasets
  1455. ds_eff_combined_lang <- ds_eff_combined %>%
  1456. filter(condition == "language" | condition == "EffectSize.mentalsocialphysical") %>%
  1457. mutate(age = factor(age, levels = c("early", "middle", "late", "adult")))
  1458. # Prepare combined volume data for LI calculation
  1459. ds_vol_sum_combined <- ds_vol_combined %>%
  1460. group_by(Subject, age, hemi, front, ds) %>%
  1461. dplyr::summarise(vol = sum(effect, na.rm = TRUE), .groups = "drop") %>%
  1462. mutate(age = factor(age, levels = c("early", "middle", "late", "adult")))
  1463. cat("Combined dataset prepared for lateralization analysis.\n")
  1464. cat("Effect data - Total observations:", nrow(ds_eff_combined_lang), "\n")
  1465. cat("Effect data - Number of subjects:", length(unique(ds_eff_combined_lang$Subject)), "\n")
  1466. cat("Volume data - Total observations:", nrow(ds_vol_sum_combined), "\n")
  1467. cat("Volume data - Number of subjects:", length(unique(ds_vol_sum_combined$Subject)), "\n")
  1468. cat("\nDataset distribution:\n")
  1469. print(table(ds_eff_combined_lang$ds))
  1470. cat("\nAge group distribution:\n")
  1471. print(table(ds_eff_combined_lang$age))
  1472. cat("\nHemisphere distribution:\n")
  1473. print(table(ds_eff_combined_lang$hemi))
  1474. ## 1. MIXED-EFFECTS MODEL ANALYSIS
  1475. # Fit mixed-effects model for activation magnitude
  1476. m_combined_effect <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI),
  1477. data = ds_eff_combined_lang, REML = FALSE)
  1478. # Model summary and R-squared
  1479. cat("\n## Combined Dataset Mixed-Effects Model\n")
  1480. cat("=====================================\n")
  1481. print(summary(m_combined_effect))
  1482. r2_values <- r.squaredGLMM(m_combined_effect)
  1483. cat("\nModel R-squared Values:\n")
  1484. cat("Marginal R² (fixed effects):", round(r2_values[1], 4), "\n")
  1485. cat("Conditional R² (fixed + random):", round(r2_values[2], 4), "\n")
  1486. # Post-hoc contrasts
  1487. cat("\nPost-hoc Contrasts:\n")
  1488. emm_combined <- emmeans(m_combined_effect, ~ hemi:age, lmer.df = "kenward-roger")
  1489. contrasts_combined <- contrast(emm_combined, interaction = c("pairwise", "trt.vs.ctrl"),
  1490. by = NULL, adjust = "sidak")
  1491. print(contrasts_combined)
  1492. ## 2. LATERALIZATION INDEX ANALYSIS
  1493. # Calculate LI from combined volume data
  1494. data_wide_li <- ds_vol_sum_combined %>%
  1495. pivot_wider(names_from = hemi, values_from = vol) %>%
  1496. mutate(
  1497. LI = ifelse(!is.na(lh) & !is.na(rh) & (lh + rh) != 0,
  1498. (lh - rh) / (lh + rh), NA_real_)
  1499. ) %>%
  1500. filter(!is.na(LI) & is.finite(LI))
  1501. cat("\n## Lateralization Index Analysis\n")
  1502. cat("Lateralization Index calculated for", nrow(data_wide_li), "subjects.\n")
  1503. # Statistical analysis
  1504. model_li <- lm(LI ~ age, data = data_wide_li)
  1505. anova_li <- anova(model_li)
  1506. eta_squared_li <- anova_li$"Sum Sq"[1] / sum(anova_li$"Sum Sq")
  1507. cat("\nLI Model Results:\n")
  1508. print(summary(model_li))
  1509. cat("\nANOVA Results:\n")
  1510. print(anova_li)
  1511. cat("\nEffect size (eta-squared):", round(eta_squared_li, 4), "\n")
  1512. # Contrasts vs. adult
  1513. emm_li <- emmeans(model_li, ~ age)
  1514. contrasts_li <- contrast(emm_li, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  1515. cat("\nContrasts vs. Adult:\n")
  1516. print(contrasts_li)
  1517. ## 3. DATA PREPARATION FOR VISUALIZATION
  1518. # LI plot data - one point per participant (average across regions/datasets if multiple)
  1519. combined_li_plot_data <- data_wide_li %>%
  1520. group_by(Subject, age) %>%
  1521. summarise(mean_LI = mean(LI, na.rm = TRUE), .groups = "drop") %>%
  1522. filter(!is.na(mean_LI) & is.finite(mean_LI))
  1523. # Magnitude difference data - one point per participant
  1524. combined_mag_data <- ds_eff_combined_lang %>%
  1525. group_by(Subject, age, hemi) %>%
  1526. summarise(contrast = mean(contrast, na.rm = TRUE), .groups = "drop") %>%
  1527. pivot_wider(names_from = hemi, values_from = contrast) %>%
  1528. mutate(difference = lh - rh) %>%
  1529. filter(!is.na(difference) & is.finite(difference)) %>%
  1530. group_by(Subject, age) %>%
  1531. summarise(difference = mean(difference, na.rm = TRUE), .groups = "drop")
  1532. cat("\nVisualization data prepared:\n")
  1533. cat("LI plot data:", nrow(combined_li_plot_data), "subjects\n")
  1534. cat("Magnitude plot data:", nrow(combined_mag_data), "subjects\n")
  1535. ## 4. STATISTICAL ANALYSIS FUNCTIONS
  1536. analyze_combined_metric <- function(data, metric_col, title) {
  1537. # Fit model
  1538. formula_str <- paste(metric_col, "~ age")
  1539. model <- lm(as.formula(formula_str), data = data)
  1540. # ANOVA and effect size
  1541. anova_result <- anova(model)
  1542. eta_squared <- anova_result$"Sum Sq"[1] / sum(anova_result$"Sum Sq")
  1543. # Emmeans and contrasts
  1544. emm_result <- emmeans(model, ~ age)
  1545. contrasts_result <- contrast(emm_result, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  1546. # Output results
  1547. cat("\n", title, " Analysis:\n")
  1548. cat("========================\n")
  1549. print(summary(model))
  1550. cat("\nANOVA Results:\n")
  1551. print(anova_result)
  1552. cat("\nEffect size (eta-squared):", round(eta_squared, 4), "\n")
  1553. cat("\nContrasts vs. Adult:\n")
  1554. print(contrasts_result)
  1555. return(list(
  1556. model = model,
  1557. anova = anova_result,
  1558. contrasts = contrasts_result,
  1559. eta_squared = eta_squared
  1560. ))
  1561. }
  1562. ## 5. VISUALIZATION FUNCTIONS
  1563. create_combined_plot <- function(data, y_var, y_label, y_limits) {
  1564. ggplot(data, aes_string(x = "age", y = y_var, fill = "age")) +
  1565. geom_hline(yintercept = 0, linetype = "solid", color = "black", linewidth = 1) +
  1566. geom_bar(stat = "summary", fun = "mean", width = 0.6, alpha = 0.6,
  1567. color = "black", linewidth = 0.5) +
  1568. geom_jitter(width = 0.2, alpha = 0.2, size = 1) +
  1569. stat_summary(fun.data = mean_se, geom = "errorbar", width = 0.2, linewidth = 1) +
  1570. scale_fill_manual(values = c("gray", "gray", "gray", "black")) +
  1571. scale_y_continuous(limits = y_limits, expand = expansion(mult = c(0.02, 0.02))) +
  1572. labs(y = y_label, x = "Age Group") +
  1573. theme_classic() +
  1574. theme(
  1575. legend.position = "none",
  1576. text = element_text(size = 18),
  1577. axis.text = element_text(size = 14),
  1578. axis.title = element_text(size = 16),
  1579. plot.margin = margin(10, 10, 20, 10)
  1580. )
  1581. }
  1582. ## 6. CREATE FIGURES
  1583. # Panel A: Lateralization Index
  1584. fig_li <- create_combined_plot(
  1585. data = combined_li_plot_data,
  1586. y_var = "mean_LI",
  1587. y_label = "Lateralization Index\n(LH - RH) / (LH + RH)",
  1588. y_limits = c(-1, 1)
  1589. )
  1590. # Panel B: Magnitude Difference
  1591. fig_magnitude <- create_combined_plot(
  1592. data = combined_mag_data,
  1593. y_var = "difference",
  1594. y_label = "Magnitude Difference\nLH - RH",
  1595. y_limits = c(-4, 4)
  1596. )
  1597. # Combine figures
  1598. combined_figure <- plot_grid(fig_li, fig_magnitude, ncol = 2, align = "hv",
  1599. labels = c("A", "B"), label_size = 18)
  1600. cat("\n## Combined Figure\n")
  1601. print(combined_figure)
  1602. ## 7. SUMMARY TABLES FUNCTIONS
  1603. create_summary_table <- function(data, metric_col, caption) {
  1604. summary_stats <- data %>%
  1605. group_by(age) %>%
  1606. summarise(
  1607. N = n(),
  1608. Mean = round(mean(.data[[metric_col]], na.rm = TRUE), 4),
  1609. SD = round(sd(.data[[metric_col]], na.rm = TRUE), 4),
  1610. SE = round(sd(.data[[metric_col]], na.rm = TRUE) / sqrt(n()), 4),
  1611. .groups = "drop"
  1612. )
  1613. kable(summary_stats,
  1614. caption = caption,
  1615. col.names = c("Age", "N", "Mean", "SD", "SE")) %>%
  1616. kable_styling(bootstrap_options = c("striped", "hover"))
  1617. }
  1618. create_contrast_table <- function(contrast_result, title) {
  1619. contrast_df <- as.data.frame(contrast_result) %>%
  1620. mutate(
  1621. Comparison = gsub(" - adult", " vs. Adult", contrast),
  1622. B = round(estimate, 2),
  1623. SE = round(SE, 2),
  1624. t = round(t.ratio, 2),
  1625. p = case_when(
  1626. p.value < 0.001 ~ "**<0.001**",
  1627. p.value < 0.01 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1628. p.value < 0.05 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1629. TRUE ~ sprintf("%.3f", p.value)
  1630. )
  1631. ) %>%
  1632. select(Comparison, B, SE, t, p)
  1633. kable(contrast_df,
  1634. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  1635. caption = paste0("**", title, "**"),
  1636. escape = FALSE) %>%
  1637. kable_styling(bootstrap_options = c("striped", "hover"))
  1638. }
  1639. ## 8. GENERATE SUMMARY TABLES
  1640. cat("\n## Summary Tables\n")
  1641. # LI summary statistics
  1642. li_summary_table <- create_summary_table(combined_li_plot_data, "mean_LI",
  1643. "Combined Dataset - Lateralization Index Summary by Age Group")
  1644. print(li_summary_table)
  1645. # LI contrasts table
  1646. li_contrast_table <- create_contrast_table(contrasts_li,
  1647. "Combined Dataset - Lateralization Index Contrasts (vs. Adult)")
  1648. print(li_contrast_table)
  1649. # Magnitude summary statistics
  1650. mag_summary_table <- create_summary_table(combined_mag_data, "difference",
  1651. "Combined Dataset - Magnitude Difference Summary by Age Group")
  1652. print(mag_summary_table)
  1653. # Analyze magnitude differences
  1654. mag_results <- analyze_combined_metric(combined_mag_data, "difference", "Magnitude Difference")
  1655. # Magnitude contrasts table
  1656. mag_contrast_table <- create_contrast_table(mag_results$contrasts,
  1657. "Combined Dataset - Magnitude Difference Contrasts (vs. Adult)")
  1658. print(mag_contrast_table)
  1659. ## 9. ANALYSIS SUMMARY
  1660. cat("\n## Analysis Summary\n")
  1661. cat("==================\n")
  1662. cat("Mixed-effects model R² (marginal):", round(r2_values[1], 4), "\n")
  1663. cat("Mixed-effects model R² (conditional):", round(r2_values[2], 4), "\n")
  1664. cat("LI analysis effect size (eta²):", round(eta_squared_li, 4), "\n")
  1665. cat("Magnitude analysis effect size (eta²):", round(mag_results$eta_squared, 4), "\n")
  1666. cat("LI sample size:", nrow(combined_li_plot_data), "subjects\n")
  1667. cat("Magnitude sample size:", nrow(combined_mag_data), "subjects\n")
  1668. # Check data distribution by dataset
  1669. cat("\nData distribution by dataset:\n")
  1670. li_by_ds <- data_wide_li %>%
  1671. group_by(ds, age) %>%
  1672. summarise(n = n(), .groups = "drop")
  1673. print(li_by_ds)
  1674. # Store results for further use
  1675. combined_lateralization_results <- list(
  1676. mixed_effects_model = m_combined_effect,
  1677. li_model = model_li,
  1678. magnitude_model = mag_results$model,
  1679. li_contrasts = contrasts_li,
  1680. magnitude_contrasts = mag_results$contrasts,
  1681. combined_figure = combined_figure,
  1682. li_data = combined_li_plot_data,
  1683. magnitude_data = combined_mag_data
  1684. )
  1685. ```
  1686. ```{r}
  1687. # Combined Dataset Analysis: Mixed-Effects Models and Lateralization
  1688. ## 1. MIXED-EFFECTS MODEL ANALYSIS
  1689. # Fit mixed-effects model for activation magnitude
  1690. m_combined_effect <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI),
  1691. data = ds_eff_combined_lang, REML = FALSE)
  1692. # Model summary and R-squared
  1693. cat("## Combined Dataset Mixed-Effects Model\n")
  1694. cat("=====================================\n")
  1695. print(summary(m_combined_effect))
  1696. r2_values <- r.squaredGLMM(m_combined_effect)
  1697. cat("\nModel R-squared Values:\n")
  1698. cat("Marginal R² (fixed effects):", round(r2_values[1], 4), "\n")
  1699. cat("Conditional R² (fixed + random):", round(r2_values[2], 4), "\n")
  1700. # Post-hoc contrasts
  1701. cat("\nPost-hoc Contrasts:\n")
  1702. emm_combined <- emmeans(m_combined_effect, ~ hemi:age, lmer.df = "kenward-roger")
  1703. contrasts_combined <- contrast(emm_combined, interaction = c("pairwise", "trt.vs.ctrl"),
  1704. by = NULL, adjust = "sidak")
  1705. print(contrasts_combined)
  1706. ## 2. LATERALIZATION INDEX ANALYSIS
  1707. # Calculate LI from volume data
  1708. data_wide_li <- ds_vol_sum_combined %>%
  1709. pivot_wider(names_from = hemi, values_from = vol) %>%
  1710. mutate(
  1711. LI = ifelse(!is.na(lh) & !is.na(rh) & (lh + rh) != 0,
  1712. (lh - rh) / (lh + rh), NA_real_)
  1713. ) %>%
  1714. filter(!is.na(LI))
  1715. # Ensure factor levels
  1716. data_wide_li$age <- factor(data_wide_li$age, levels = c("early", "middle", "late", "adult"))
  1717. cat("\n## Lateralization Index Analysis\n")
  1718. cat("Lateralization Index calculated for", nrow(data_wide_li), "subjects.\n")
  1719. # Statistical analysis
  1720. model_li <- lm(LI ~ age, data = data_wide_li)
  1721. anova_li <- anova(model_li)
  1722. eta_squared_li <- anova_li$"Sum Sq"[1] / sum(anova_li$"Sum Sq")
  1723. cat("\nLI Model Results:\n")
  1724. print(summary(model_li))
  1725. cat("\nANOVA Results:\n")
  1726. print(anova_li)
  1727. cat("\nEffect size (eta-squared):", round(eta_squared_li, 4), "\n")
  1728. # Contrasts vs. adult
  1729. emm_li <- emmeans(model_li, ~ age)
  1730. contrasts_li <- contrast(emm_li, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  1731. cat("\nContrasts vs. Adult:\n")
  1732. print(contrasts_li)
  1733. ## 3. DATA PREPARATION FOR VISUALIZATION
  1734. # LI plot data - one point per participant
  1735. combined_li_plot_data <- data_wide_li %>%
  1736. group_by(Subject, age) %>%
  1737. summarise(mean_LI = mean(LI, na.rm = TRUE), .groups = "drop") %>%
  1738. na.omit()
  1739. # Magnitude difference data - one point per participant
  1740. combined_mag_data <- ds_eff_combined_lang %>%
  1741. group_by(Subject, age, hemi) %>%
  1742. summarise(contrast = mean(contrast, na.rm = TRUE), .groups = "drop") %>%
  1743. pivot_wider(names_from = hemi, values_from = contrast) %>%
  1744. mutate(difference = lh - rh) %>%
  1745. na.omit() %>%
  1746. group_by(Subject, age) %>%
  1747. summarise(difference = mean(difference, na.rm = TRUE), .groups = "drop")
  1748. # Ensure consistent factor levels
  1749. combined_li_plot_data$age <- factor(combined_li_plot_data$age, levels = c("early", "middle", "late", "adult"))
  1750. combined_mag_data$age <- factor(combined_mag_data$age, levels = c("early", "middle", "late", "adult"))
  1751. ## 4. VISUALIZATION FUNCTION
  1752. create_aligned_plot <- function(data, y_var, y_label, y_limits) {
  1753. ggplot(data, aes_string(x = "age", y = y_var, fill = "age")) +
  1754. geom_hline(yintercept = 0, linetype = "solid", color = "black", linewidth = 1) +
  1755. geom_bar(stat = "summary", fun = "mean", width = 0.6, alpha = 0.6,
  1756. color = "black", linewidth = 0.5) +
  1757. geom_jitter(width = 0.2, alpha = 0.2, size = 1) +
  1758. stat_summary(fun.data = mean_se, geom = "errorbar", width = 0.2, linewidth = 1) +
  1759. scale_fill_manual(values = c("gray", "gray", "gray", "black")) +
  1760. scale_y_continuous(limits = y_limits, expand = expansion(mult = c(0.02, 0.02))) +
  1761. labs(y = y_label, x = "Age Group") +
  1762. theme_classic() +
  1763. theme(
  1764. legend.position = "none",
  1765. text = element_text(size = 18),
  1766. axis.text = element_text(size = 14),
  1767. axis.title = element_text(size = 16),
  1768. plot.margin = margin(10, 10, 20, 10)
  1769. )
  1770. }
  1771. ## 5. CREATE FIGURES
  1772. # Panel A: Lateralization Index
  1773. fig_li <- create_aligned_plot(
  1774. data = combined_li_plot_data,
  1775. y_var = "mean_LI",
  1776. y_label = "Lateralization Index\n(LH - RH) / (LH + RH)",
  1777. y_limits = c(-1, 1)
  1778. )
  1779. # Panel B: Magnitude Difference
  1780. fig_magnitude <- create_aligned_plot(
  1781. data = combined_mag_data,
  1782. y_var = "difference",
  1783. y_label = "Magnitude Difference\nLH - RH",
  1784. y_limits = c(-4, 4)
  1785. )
  1786. # Combine figures
  1787. combined_figure <- plot_grid(fig_li, fig_magnitude, ncol = 2, align = "hv",
  1788. labels = c("A", "B"), label_size = 18)
  1789. cat("\n## Combined Figure\n")
  1790. print(combined_figure)
  1791. ## 6. SUMMARY TABLES
  1792. # LI summary statistics
  1793. li_summary <- data_wide_li %>%
  1794. group_by(age) %>%
  1795. summarise(
  1796. N = n(),
  1797. Mean_LI = round(mean(LI, na.rm = TRUE), 4),
  1798. SD_LI = round(sd(LI, na.rm = TRUE), 4),
  1799. SE_LI = round(sd(LI, na.rm = TRUE) / sqrt(n()), 4),
  1800. .groups = "drop"
  1801. )
  1802. # LI contrasts table
  1803. li_contrast_df <- as.data.frame(contrasts_li) %>%
  1804. mutate(
  1805. Comparison = gsub(" - adult", " vs. Adult", contrast),
  1806. B = round(estimate, 2),
  1807. SE = round(SE, 2),
  1808. t = round(t.ratio, 2),
  1809. p = case_when(
  1810. p.value < 0.001 ~ "**<0.001**",
  1811. p.value < 0.01 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1812. p.value < 0.05 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1813. TRUE ~ sprintf("%.3f", p.value)
  1814. )
  1815. ) %>%
  1816. select(Comparison, B, SE, t, p)
  1817. # Display tables
  1818. cat("\n## Summary Tables\n")
  1819. print(kable(li_summary, caption = "Lateralization Index Summary by Age Group",
  1820. col.names = c("Age", "N", "Mean LI", "SD", "SE")) %>%
  1821. kable_styling(bootstrap_options = c("striped", "hover")))
  1822. print(kable(li_contrast_df, caption = "Lateralization Index Contrasts (vs. Adult)",
  1823. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  1824. escape = FALSE) %>%
  1825. kable_styling(bootstrap_options = c("striped", "hover")))
  1826. ```
  1827. #SI-3E. Cross-hemispheric functional correlations
  1828. ```{r}
  1829. # Cross-Hemispheric Functional Correlations Analysis
  1830. ## Load required libraries
  1831. library(ggsignif)
  1832. ## Data Preparation and Standardization
  1833. # Load cross-hemispheric correlation data
  1834. rs_w <- read.csv("../data/ds1_rs_w.csv")
  1835. # Standardize Subject column name
  1836. if("Subject" %in% names(rs_w)) {
  1837. # Already standardized
  1838. } else if("Participant" %in% names(rs_w)) {
  1839. rs_w <- rs_w %>% rename(Subject = Participant)
  1840. } else {
  1841. names(rs_w)[1] <- "Subject"
  1842. }
  1843. # Compute correlations between each LH-RH pair for each subject
  1844. roi_cor_df <- rs_w %>%
  1845. group_by(Subject) %>%
  1846. summarize(
  1847. IFGorb = cor(value.Lang_LH_IFGorb, value.Lang_RH_IFGorb, use = "pairwise.complete.obs"),
  1848. IFG = cor(value.Lang_LH_IFG, value.Lang_RH_IFG, use = "pairwise.complete.obs"),
  1849. MFG = cor(value.Lang_LH_MFG, value.Lang_RH_MFG, use = "pairwise.complete.obs"),
  1850. AntTemp = cor(value.Lang_LH_AntTemp, value.Lang_RH_AntTemp, use = "pairwise.complete.obs"),
  1851. PostTemp = cor(value.Lang_LH_PostTemp, value.Lang_RH_PostTemp, use = "pairwise.complete.obs"),
  1852. .groups = "drop"
  1853. )
  1854. # Compute mean correlation across ROIs per participant
  1855. roi_cor_summary <- roi_cor_df %>%
  1856. rowwise() %>%
  1857. mutate(mean_rs = mean(c(IFGorb, IFG, MFG, AntTemp, PostTemp), na.rm = TRUE)) %>%
  1858. select(Subject, mean_rs) %>%
  1859. ungroup()
  1860. # Add age group with consistent factor levels
  1861. roi_cor_summary$age <- as.factor(
  1862. ifelse(grepl("inside", roi_cor_summary$Subject), "late",
  1863. ifelse(grepl("reader", roi_cor_summary$Subject), "middle",
  1864. ifelse(grepl("FACT", roi_cor_summary$Subject), "early", "adult")))
  1865. )
  1866. roi_cor_summary$age <- factor(roi_cor_summary$age, levels = c("early", "middle", "late", "adult"))
  1867. cat("Cross-hemispheric correlations calculated for", nrow(roi_cor_summary), "subjects.\n")
  1868. ## Statistical Analysis Function
  1869. analyze_cross_hemi_corr <- function(data, title) {
  1870. # Fit model with adult as reference
  1871. model_rs <- lm(mean_rs ~ age, data = data)
  1872. # ANOVA and effect size
  1873. anova_rs <- anova(model_rs)
  1874. eta_squared <- anova_rs$"Sum Sq"[1] / sum(anova_rs$"Sum Sq")
  1875. # Emmeans and contrasts vs adult
  1876. emm_result <- emmeans(model_rs, ~ age)
  1877. contrasts_result <- contrast(emm_result, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  1878. # Output results
  1879. cat("\n", title, " Statistical Analysis:\n")
  1880. cat("=====================================\n")
  1881. cat("Model Summary:\n")
  1882. print(summary(model_rs))
  1883. cat("\nANOVA Results:\n")
  1884. print(anova_rs)
  1885. cat("\nEffect size (eta-squared):", round(eta_squared, 4), "\n")
  1886. cat("\nContrasts vs. Adult Group:\n")
  1887. print(contrasts_result)
  1888. return(list(
  1889. model = model_rs,
  1890. anova = anova_rs,
  1891. contrasts = contrasts_result,
  1892. eta_squared = eta_squared
  1893. ))
  1894. }
  1895. ## Summary Statistics Function
  1896. create_rs_summary_table <- function(data) {
  1897. rs_summary <- data %>%
  1898. group_by(age) %>%
  1899. summarise(
  1900. N = n(),
  1901. Mean_RS = round(mean(mean_rs, na.rm = TRUE), 4),
  1902. SD_RS = round(sd(mean_rs, na.rm = TRUE), 4),
  1903. SE_RS = round(sd(mean_rs, na.rm = TRUE) / sqrt(n()), 4),
  1904. .groups = "drop"
  1905. )
  1906. kable(rs_summary,
  1907. caption = "Cross-Hemispheric Correlation Summary by Age Group",
  1908. col.names = c("Age", "N", "Mean RS", "SD", "SE")) %>%
  1909. kable_styling(bootstrap_options = c("striped", "hover"))
  1910. }
  1911. ## Contrast Results Table Function
  1912. create_rs_contrast_table <- function(contrast_result, title) {
  1913. contrast_df <- as.data.frame(contrast_result)
  1914. results <- contrast_df %>%
  1915. mutate(
  1916. Comparison = gsub(" - adult", " vs. Adult", contrast),
  1917. B = round(estimate, 2),
  1918. SE = round(SE, 2),
  1919. t = round(t.ratio, 2),
  1920. p = case_when(
  1921. p.value < 0.001 ~ "**<0.001**",
  1922. p.value < 0.01 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1923. p.value < 0.05 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  1924. TRUE ~ sprintf("%.3f", p.value)
  1925. )
  1926. ) %>%
  1927. select(Comparison, B, SE, t, p)
  1928. kable(results,
  1929. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  1930. caption = paste0("**", title, "**"),
  1931. escape = FALSE) %>%
  1932. kable_styling(bootstrap_options = c("striped", "hover"))
  1933. }
  1934. ## Execute Analysis
  1935. cat("## Cross-Hemispheric Functional Correlations Analysis\n")
  1936. # Summary statistics table
  1937. rs_summary_table <- create_rs_summary_table(roi_cor_summary)
  1938. print(rs_summary_table)
  1939. # Statistical analysis
  1940. rs_results <- analyze_cross_hemi_corr(roi_cor_summary, "Cross-Hemispheric Correlations")
  1941. # Create figure (keeping original style as requested)
  1942. cross_hemi_corr_plot <- ggbarplot(roi_cor_summary,
  1943. x = "age",
  1944. y = "mean_rs",
  1945. add = c("mean_se", "jitter"),
  1946. add.params = list(size = 1, alpha = 0.2),
  1947. width = 0.6,
  1948. size = 0.5,
  1949. fill = "age",
  1950. palette = c("gray", "gray", "gray", "black"),
  1951. alpha = 0.6,
  1952. ylab = "Cross-hemispheric\nCorrelation",
  1953. xlab = "Age Group") +
  1954. geom_hline(yintercept = 0, linetype = "solid", color = "black") +
  1955. geom_signif(comparisons = list(c("middle", "adult")),
  1956. annotations = "***",
  1957. y_position = 0.9,
  1958. tip_length = 0.02,
  1959. textsize = 8,
  1960. family = "Times New Roman") +
  1961. theme_classic() +
  1962. theme(legend.position = "none",
  1963. text = element_text(size = 30, family = "Times New Roman"),
  1964. plot.title = element_text(hjust = 0.5, size = 24, family = "Times New Roman")) +
  1965. coord_cartesian(ylim = c(-0.5, 1))
  1966. print(cross_hemi_corr_plot)
  1967. # Create contrast results table
  1968. rs_contrast_table <- create_rs_contrast_table(rs_results$contrasts,
  1969. "Cross-Hemispheric Correlations Comparisons (vs. Adult)")
  1970. print(rs_contrast_table)
  1971. ## Analysis Summary
  1972. cat("\n## Analysis Summary\n")
  1973. cat("==================\n")
  1974. cat("Effect size (eta-squared):", round(rs_results$eta_squared, 4), "\n")
  1975. cat("Sample size:", nrow(roi_cor_summary), "subjects\n")
  1976. # Store results for further use
  1977. rs_analysis_results <- list(
  1978. model = rs_results$model,
  1979. contrasts = rs_results$contrasts,
  1980. eta_squared = rs_results$eta_squared,
  1981. plot = cross_hemi_corr_plot,
  1982. data = roi_cor_summary
  1983. )
  1984. ```
  1985. ##SI-3F. Ordered Age Group Contrasts Analysis
  1986. ## Compare LH>RH differences between adjacent age groups
  1987. ```{r}
  1988. # Adjacent Age Group Contrasts Analysis - Children Only
  1989. # Prepare data for DS1 (exclude adults)
  1990. ds1_eff_lang <- ds1_eff %>%
  1991. filter(condition == "language", age != "adult") %>%
  1992. mutate(
  1993. age = factor(age, levels = c("early", "middle", "late")),
  1994. hemi = factor(hemi, levels = c("lh", "rh"))
  1995. )
  1996. # Prepare data for DS2 (exclude adults)
  1997. ds2_eff_lang <- ds2_eff %>%
  1998. filter(condition == "EffectSize.mentalsocialphysical", age != "adult") %>%
  1999. mutate(
  2000. age = factor(age, levels = c("early", "middle", "late")),
  2001. hemi = factor(hemi, levels = c("lh", "rh"))
  2002. )
  2003. cat("DS1 observations:", nrow(ds1_eff_lang), "\n")
  2004. cat("DS2 observations:", nrow(ds2_eff_lang), "\n")
  2005. # Custom contrasts for LH>RH differences
  2006. # Order: early:lh, early:rh, middle:lh, middle:rh, late:lh, late:rh
  2007. age_contrasts <- list(
  2008. "early_vs_middle" = c( 1, -1, -1, 1, 0, 0),
  2009. "middle_vs_late" = c( 0, 0, 1, -1, -1, 1),
  2010. "early_vs_late" = c( 1, -1, 0, 0, -1, 1)
  2011. )
  2012. # DS1 Analysis
  2013. cat("\n=== DS1 ANALYSIS ===\n")
  2014. m1 <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI), data = ds1_eff_lang)
  2015. emm1 <- emmeans(m1, ~ hemi:age)
  2016. contrast(emm1, method = age_contrasts, adjust = "sidak")
  2017. # DS2 Analysis
  2018. cat("\n=== DS2 ANALYSIS ===\n")
  2019. m2 <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI), data = ds2_eff_lang)
  2020. emm2 <- emmeans(m2, ~ hemi:age)
  2021. contrast(emm2, method = age_contrasts, adjust = "sidak")
  2022. ```
  2023. # SI-3F Lateralization Analysis - Children Only (Consecutive Age Comparisons)
  2024. ## LI Analysis Consecutive
  2025. ```{r}
  2026. ## Data Preparation and Standardization Functions
  2027. standardize_li_data <- function(data, dataset_label) {
  2028. # Standardize Subject column
  2029. if("Subject" %in% names(data)) {
  2030. # Already standardized
  2031. } else if("Participant" %in% names(data)) {
  2032. data <- data %>% rename(Subject = Participant)
  2033. } else {
  2034. names(data)[1] <- "Subject"
  2035. }
  2036. # Ensure consistent factor levels (children only)
  2037. data <- data %>%
  2038. filter(age != "adult") %>%
  2039. mutate(age = factor(age, levels = c("early", "middle", "late")))
  2040. return(data)
  2041. }
  2042. ## Statistical Analysis Function
  2043. analyze_children_li <- function(data, dataset_label) {
  2044. # Standardize data
  2045. data <- standardize_li_data(data, dataset_label)
  2046. # Pivot to wide format and calculate LI
  2047. data_wide <- data %>%
  2048. pivot_wider(names_from = hemi, values_from = contrast) %>%
  2049. mutate(LI = (lh - rh) / (lh + rh)) %>%
  2050. filter(is.finite(LI))
  2051. # Store in global environment
  2052. assign(paste0("data_wide_li_", dataset_label), data_wide, envir = .GlobalEnv)
  2053. # Fit linear model
  2054. model_li <- lm(LI ~ age, data = data_wide)
  2055. # ANOVA and effect size
  2056. anova_li <- anova(model_li)
  2057. eta_squared <- anova_li$"Sum Sq"[1] / sum(anova_li$"Sum Sq")
  2058. # Emmeans and custom contrasts
  2059. emm_li <- emmeans(model_li, ~ age)
  2060. # Custom contrasts for consecutive age comparisons
  2061. age_contrasts <- list(
  2062. "early_vs_middle" = c( 1, -1, 0),
  2063. "middle_vs_late" = c( 0, 1, -1),
  2064. "early_vs_late" = c( 1, 0, -1)
  2065. )
  2066. contrasts_li <- contrast(emm_li, method = age_contrasts, adjust = "sidak")
  2067. # Output results
  2068. cat("\n=== ", toupper(dataset_label), " LATERALIZATION INDEX ANALYSIS ===\n")
  2069. cat("================================================\n")
  2070. print(summary(model_li))
  2071. cat("\nANOVA Results:\n")
  2072. print(anova_li)
  2073. cat("\nEffect size (eta-squared):", round(eta_squared, 4), "\n")
  2074. cat("\nConsecutive Age Group Contrasts:\n")
  2075. print(contrasts_li)
  2076. return(list(
  2077. dataset = dataset_label,
  2078. model = model_li,
  2079. anova = anova_li,
  2080. contrasts = contrasts_li,
  2081. eta_squared = eta_squared,
  2082. data = data_wide
  2083. ))
  2084. }
  2085. ## Summary Statistics Function
  2086. create_li_children_summary <- function(data, dataset_label) {
  2087. # Prepare data
  2088. data <- standardize_li_data(data, dataset_label)
  2089. data_wide <- data %>%
  2090. pivot_wider(names_from = hemi, values_from = contrast) %>%
  2091. mutate(LI = (lh - rh) / (lh + rh)) %>%
  2092. filter(is.finite(LI))
  2093. # Summary statistics
  2094. li_summary <- data_wide %>%
  2095. group_by(age) %>%
  2096. summarise(
  2097. N = n(),
  2098. Mean_LI = round(mean(LI, na.rm = TRUE), 4),
  2099. SD_LI = round(sd(LI, na.rm = TRUE), 4),
  2100. SE_LI = round(sd(LI, na.rm = TRUE) / sqrt(n()), 4),
  2101. .groups = "drop"
  2102. )
  2103. kable(li_summary,
  2104. caption = paste(dataset_label, "Lateralization Index Summary (Children Only)"),
  2105. col.names = c("Age", "N", "Mean LI", "SD", "SE")) %>%
  2106. kable_styling(bootstrap_options = c("striped", "hover"))
  2107. }
  2108. ## Contrast Results Table Function
  2109. create_li_children_contrast_table <- function(contrast_result, dataset_label) {
  2110. contrast_df <- as.data.frame(contrast_result)
  2111. results <- contrast_df %>%
  2112. mutate(
  2113. Comparison = case_when(
  2114. contrast == "early_vs_middle" ~ "Early vs. Middle",
  2115. contrast == "middle_vs_late" ~ "Middle vs. Late",
  2116. contrast == "early_vs_late" ~ "Early vs. Late",
  2117. TRUE ~ contrast
  2118. ),
  2119. B = round(estimate, 2),
  2120. SE = round(SE, 2),
  2121. t = round(t.ratio, 2),
  2122. p = case_when(
  2123. p.value < 0.001 ~ "**<0.001**",
  2124. p.value < 0.01 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  2125. p.value < 0.05 ~ paste0("**", sprintf("%.3f", p.value), "**"),
  2126. TRUE ~ sprintf("%.3f", p.value)
  2127. )
  2128. ) %>%
  2129. select(Comparison, B, SE, t, p)
  2130. kable(results,
  2131. col.names = c("", "***B***", "***SE***", "***t***", "***p***"),
  2132. caption = paste0("**", dataset_label, " - Consecutive Age Group Comparisons**"),
  2133. escape = FALSE) %>%
  2134. kable_styling(bootstrap_options = c("striped", "hover"))
  2135. }
  2136. ## Execute Analyses
  2137. # Dataset 1 Analysis
  2138. cat("## Dataset 1 - Volume-Based Lateralization Index Analysis\n")
  2139. # Summary table
  2140. ds1_li_summary <- create_li_children_summary(ds1_vol_sum, "Dataset 1")
  2141. print(ds1_li_summary)
  2142. # Statistical analysis
  2143. ds1_li_results <- analyze_children_li(ds1_vol_sum, "ds1")
  2144. # Contrast results table
  2145. ds1_li_contrast_table <- create_li_children_contrast_table(ds1_li_results$contrasts, "Dataset 1")
  2146. print(ds1_li_contrast_table)
  2147. # Dataset 2 Analysis
  2148. cat("\n## Dataset 2 - Volume-Based Lateralization Index Analysis\n")
  2149. # Summary table
  2150. ds2_li_summary <- create_li_children_summary(ds2_vol_sum, "Dataset 2")
  2151. print(ds2_li_summary)
  2152. # Statistical analysis
  2153. ds2_li_results <- analyze_children_li(ds2_vol_sum, "ds2")
  2154. # Contrast results table
  2155. ds2_li_contrast_table <- create_li_children_contrast_table(ds2_li_results$contrasts, "Dataset 2")
  2156. print(ds2_li_contrast_table)
  2157. ## Analysis Summary
  2158. cat("\n## Analysis Summary\n")
  2159. cat("==================\n")
  2160. cat("Dataset 1:\n")
  2161. cat(" Effect size (eta-squared):", round(ds1_li_results$eta_squared, 4), "\n")
  2162. cat(" Sample size:", nrow(ds1_li_results$data), "subjects\n")
  2163. cat("\nDataset 2:\n")
  2164. cat(" Effect size (eta-squared):", round(ds2_li_results$eta_squared, 4), "\n")
  2165. cat(" Sample size:", nrow(ds2_li_results$data), "subjects\n")
  2166. # Store results for further use
  2167. li_children_analysis_results <- list(
  2168. ds1 = ds1_li_results,
  2169. ds2 = ds2_li_results
  2170. )
  2171. ```
  2172. ## Effect Analysis Consecutive
  2173. ```{r}
  2174. # Custom contrasts for LH>RH differences between consecutive age groups
  2175. # Order: early:lh, early:rh, middle:lh, middle:rh, late:lh, late:rh
  2176. age_contrasts <- list(
  2177. "early_vs_middle" = c( 1, -1, -1, 1, 0, 0), # (early_lh - early_rh) - (middle_lh - middle_rh)
  2178. "middle_vs_late" = c( 0, 0, 1, -1, -1, 1), # (middle_lh - middle_rh) - (late_lh - late_rh)
  2179. "early_vs_late" = c( 1, -1, 0, 0, -1, 1) # (early_lh - early_rh) - (late_lh - late_rh)
  2180. )
  2181. # DS1 Analysis
  2182. cat("\n=== DS1 ANALYSIS ===\n")
  2183. m1 <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI), data = ds1_eff_lang)
  2184. emm1 <- emmeans(m1, ~ hemi:age)
  2185. contrast(emm1, method = age_contrasts, adjust = "sidak")
  2186. # DS2 Analysis
  2187. cat("\n=== DS2 ANALYSIS ===\n")
  2188. m2 <- lmer(contrast ~ hemi * age + (hemi | Subject) + (hemi | ROI), data = ds2_eff_lang)
  2189. emm2 <- emmeans(m2, ~ hemi:age)
  2190. contrast(emm2, method = age_contrasts, adjust = "sidak")
  2191. ```
  2192. # SI-4: Additional Analyses Related to Other Aspects of the Data
  2193. ##SI-4A
  2194. ```{r}
  2195. ds1_eff_rh <- ds1_eff %>% filter(hemi=='rh')
  2196. ds2_eff_rh <- ds2_eff %>% filter(hemi=='rh')
  2197. run_model <- function(df, dataset_label, age, region_label) {
  2198. if (nrow(df) == 0) {
  2199. return(data.frame(
  2200. Dataset = as.character(dataset_label),
  2201. Age = as.character(age),
  2202. Region = as.character(region_label),
  2203. # pairwise contrast stats
  2204. Effect_b = NA_real_, Effect_SE = NA_real_, Effect_t = NA_real_, Effect_p = NA_real_,
  2205. # omnibus/main effect stats
  2206. Main_F = NA_real_, Main_NumDF = NA_real_, Main_DenDF = NA_real_, Main_p = NA_real_,
  2207. stringsAsFactors = FALSE
  2208. ))
  2209. }
  2210. # Fit the linear mixed model
  2211. mod <- lmer(effect ~ condition + (1|Subject) + (1|ROI), data = df)
  2212. # 1) extract the pairwise condition contrast (same as before)
  2213. s <- summary(mod)$coefficients
  2214. condition_rows <- s[rownames(s) != "(Intercept)", , drop=FALSE]
  2215. # Check if condition row exists
  2216. if (nrow(condition_rows) == 0) {
  2217. return(data.frame(
  2218. Dataset = as.character(dataset_label),
  2219. Age = as.character(age),
  2220. Region = as.character(region_label),
  2221. # pairwise contrast stats
  2222. Effect_b = NA_real_, Effect_SE = NA_real_, Effect_t = NA_real_, Effect_p = NA_real_,
  2223. # omnibus/main effect stats
  2224. Main_F = NA_real_, Main_NumDF = NA_real_, Main_DenDF = NA_real_, Main_p = NA_real_,
  2225. stringsAsFactors = FALSE
  2226. ))
  2227. }
  2228. condition_row <- condition_rows[1, ]
  2229. # 2) extract omnibus/main effect from the ANOVA table
  2230. anov <- anova(mod)
  2231. # Check if "condition" row exists in ANOVA table
  2232. if (!"condition" %in% rownames(anov)) {
  2233. return(data.frame(
  2234. Dataset = as.character(dataset_label),
  2235. Age = as.character(age),
  2236. Region = as.character(region_label),
  2237. # pairwise contrast stats
  2238. Effect_b = NA_real_, Effect_SE = NA_real_, Effect_t = NA_real_, Effect_p = NA_real_,
  2239. # omnibus/main effect stats
  2240. Main_F = NA_real_, Main_NumDF = NA_real_, Main_DenDF = NA_real_, Main_p = NA_real_,
  2241. stringsAsFactors = FALSE
  2242. ))
  2243. }
  2244. me <- anov["condition", ]
  2245. # Extract values safely with explicit conversion
  2246. effect_b <- as.numeric(condition_row["Estimate"])
  2247. effect_se <- as.numeric(condition_row["Std. Error"])
  2248. effect_t <- as.numeric(condition_row["t value"])
  2249. effect_p <- as.numeric(condition_row["Pr(>|t|)"])
  2250. main_f <- if (!is.null(me$`F value`)) as.numeric(me$`F value`) else NA_real_
  2251. main_numdf <- if (!is.null(me$NumDF)) as.numeric(me$NumDF) else NA_real_
  2252. main_dendf <- if (!is.null(me$DenDF)) as.numeric(me$DenDF) else NA_real_
  2253. main_p <- if (!is.null(me$`Pr(>F)`)) as.numeric(me$`Pr(>F)`) else NA_real_
  2254. data.frame(
  2255. Dataset = as.character(dataset_label),
  2256. Age = as.character(age),
  2257. Region = as.character(region_label),
  2258. # pairwise contrast
  2259. Effect_b = effect_b,
  2260. Effect_SE = effect_se,
  2261. Effect_t = effect_t,
  2262. Effect_p = effect_p,
  2263. # omnibus/main effect
  2264. Main_F = main_f,
  2265. Main_NumDF = main_numdf,
  2266. Main_DenDF = main_dendf,
  2267. Main_p = main_p,
  2268. stringsAsFactors = FALSE
  2269. )
  2270. }
  2271. analyze_dataset <- function(data, dataset_label) {
  2272. age_levels <- sort(unique(data$age))
  2273. out <- NULL
  2274. for (ag in age_levels) {
  2275. sub_data <- filter(data, age==ag)
  2276. res_full <- run_model(sub_data, dataset_label, ag, "Full")
  2277. res_frontal<- run_model(filter(sub_data, front=="front"), dataset_label, ag, "Frontal")
  2278. res_temporal<-run_model(filter(sub_data, front=="post"), dataset_label, ag, "Temporal")
  2279. out <- bind_rows(out, res_full, res_frontal, res_temporal)
  2280. }
  2281. out
  2282. }
  2283. # run on each dataset
  2284. res1_rh <- analyze_dataset(ds1_eff_rh, "ds1_rh_effect")
  2285. res2_rh <- analyze_dataset(ds2_eff_rh, "ds2_rh_effect")
  2286. final_results_rh <- bind_rows(res1_rh, res2_rh)
  2287. kable(final_results_rh, digits=3,
  2288. caption="SI-2: Pairwise condition estimates *and* omnibus main‐effect stats") %>%
  2289. kable_styling(full_width=TRUE)
  2290. ```
  2291. ## SI-4B: Analyses that treat age as a continuous variable
  2292. ### DS1 Continous
  2293. ```{r}
  2294. ### Data Preparation for Continuous Age Analysis
  2295. # Use existing LI data created in previous sections
  2296. # From SI-3.A lateralization analysis - use the data_wide objects that were created
  2297. # Check if LI data exists from previous analyses
  2298. if(exists("data_wide_li_ds1")) {
  2299. ds1_li_wide <- data_wide_li_ds1
  2300. } else {
  2301. # Fallback: create from ds1_vol_sum if it exists
  2302. if(exists("ds1_vol_sum")) {
  2303. ds1_li_wide <- ds1_vol_sum %>%
  2304. pivot_wider(names_from = hemi, values_from = contrast) %>%
  2305. mutate(LI = (lh - rh) / (lh + rh)) %>%
  2306. filter(is.finite(LI))
  2307. } else {
  2308. stop("Cannot find existing DS1 LI data. Please run SI-3.A section first.")
  2309. }
  2310. }
  2311. if(exists("data_wide_li_ds2")) {
  2312. ds2_li_wide <- data_wide_li_ds2
  2313. } else {
  2314. # Fallback: create from ds2_vol_sum if it exists
  2315. if(exists("ds2_vol_sum")) {
  2316. ds2_li_wide <- ds2_vol_sum %>%
  2317. pivot_wider(names_from = hemi, values_from = contrast) %>%
  2318. mutate(LI = (lh - rh) / (lh + rh)) %>%
  2319. filter(is.finite(LI))
  2320. } else {
  2321. stop("Cannot find existing DS2 LI data. Please run SI-3.A section first.")
  2322. }
  2323. }
  2324. # Or use the combined LI data if available
  2325. if(exists("combined_li_plot_data")) {
  2326. # Use the combined data and split by dataset if needed
  2327. combined_li_cont_data <- combined_li_plot_data
  2328. cat("Using combined LI data from previous analysis:\n")
  2329. cat("Total subjects:", nrow(combined_li_cont_data), "\n")
  2330. } else if(exists("data_wide_li")) {
  2331. # Use the data_wide_li from the combined analysis
  2332. combined_li_cont_data <- data_wide_li %>%
  2333. group_by(Subject, age) %>%
  2334. summarise(mean_LI = mean(LI, na.rm = TRUE), .groups = "drop")
  2335. cat("Using data_wide_li from previous analysis:\n")
  2336. cat("Total subjects:", nrow(combined_li_cont_data), "\n")
  2337. }
  2338. # Compute subject-level summaries if using individual datasets
  2339. if(exists("ds1_li_wide") && !"mean_LI" %in% names(ds1_li_wide)) {
  2340. ds1_li_summary <- ds1_li_wide %>%
  2341. group_by(Subject, age) %>%
  2342. summarise(mean_LI = mean(LI, na.rm = TRUE), .groups = "drop")
  2343. } else if(exists("ds1_li_wide")) {
  2344. ds1_li_summary <- ds1_li_wide
  2345. }
  2346. if(exists("ds2_li_wide") && !"mean_LI" %in% names(ds2_li_wide)) {
  2347. ds2_li_summary <- ds2_li_wide %>%
  2348. group_by(Subject, age) %>%
  2349. summarise(mean_LI = mean(LI, na.rm = TRUE), .groups = "drop")
  2350. } else if(exists("ds2_li_wide")) {
  2351. ds2_li_summary <- ds2_li_wide
  2352. }
  2353. cat("LI data preparation completed using existing objects.\n")
  2354. if(exists("ds1_li_summary")) cat("DS1:", nrow(ds1_li_summary), "subjects\n")
  2355. if(exists("ds2_li_summary")) cat("DS2:", nrow(ds2_li_summary), "subjects\n")
  2356. if(exists("combined_li_cont_data")) cat("Combined:", nrow(combined_li_cont_data), "subjects\n")
  2357. ```
  2358. ### Dataset 1 Continuous Age Analysis
  2359. ```{r ds1-continuous}
  2360. #A. Volume-based lateralization index (LI)
  2361. # Merge DS1 LI data with demographic data (children only)
  2362. # Use ds1_li_summary created in previous chunk
  2363. if (exists("ds1_li_summary")) {
  2364. ds1_li_cont <- ds1_li_summary %>%
  2365. left_join(ds1_demo, by = "Subject") %>%
  2366. filter(age != "adult") %>%
  2367. mutate(age_centered = Age - mean(Age, na.rm = TRUE))
  2368. } else if (exists("ds1_li_wide")) {
  2369. ds1_li_cont <- ds1_li_wide %>%
  2370. group_by(Subject, age) %>%
  2371. summarise(mean_LI = mean(LI, na.rm = TRUE), .groups = "drop") %>%
  2372. left_join(ds1_demo, by = "Subject") %>%
  2373. filter(age != "adult") %>%
  2374. mutate(age_centered = Age - mean(Age, na.rm = TRUE))
  2375. } else {
  2376. stop("DS1 LI data not available. Please run previous sections first.")
  2377. }
  2378. cat("## Dataset 1 - Continuous Age Analysis\n")
  2379. cat("=====================================\n")
  2380. cat("Sample size:", nrow(ds1_li_cont), "children\n")
  2381. cat("Age range:", round(min(ds1_li_cont$Age, na.rm = TRUE), 1), "-",
  2382. round(max(ds1_li_cont$Age, na.rm = TRUE), 1), "years\n")
  2383. # Model 1: LI predicted by continuous age
  2384. m1_ds1_li <- lm(mean_LI ~ age_centered, data = ds1_li_cont)
  2385. cat("\n### DS1 Lateralization Index ~ Age (Continuous)\n")
  2386. print(summary(m1_ds1_li))
  2387. # Calculate effect size
  2388. anova_ds1_li <- anova(m1_ds1_li)
  2389. eta_squared_ds1_li <- anova_ds1_li$"Sum Sq"[1] / sum(anova_ds1_li$"Sum Sq")
  2390. cat("\nEffect size (eta-squared):", round(eta_squared_ds1_li, 4), "\n")
  2391. #B. Magnitude-based lateralization
  2392. # Merge DS1 effect data with demographic data (children only)
  2393. ds1_eff_cont <- ds1_eff_lang %>%
  2394. left_join(ds1_demo, by = "Subject") %>%
  2395. filter(age != "adult" & !is.na(Age)) %>%
  2396. mutate(age_centered = Age - mean(Age, na.rm = TRUE))
  2397. cat("\nEffect data sample size:", length(unique(ds1_eff_cont$Subject)), "children\n")
  2398. # Model 2: Effect magnitude predicted by hemisphere and continuous age
  2399. m2_ds1_eff <- lmerTest::lmer(contrast ~ hemi * age_centered + (hemi | Subject) + (hemi | ROI),
  2400. data = ds1_eff_cont)
  2401. cat("\n### DS1 Effect Magnitude ~ Hemisphere * Age (Continuous)\n")
  2402. print(summary(m2_ds1_eff))
  2403. # Extract R-squared for mixed model
  2404. r2_ds1_eff <- MuMIn::r.squaredGLMM(m2_ds1_eff)
  2405. cat("\nMixed model R-squared:\n")
  2406. cat("Marginal R²:", round(r2_ds1_eff[1], 4), "\n")
  2407. cat("Conditional R²:", round(r2_ds1_eff[2], 4), "\n")
  2408. #C. Magnitude of the Language > Control contrast in the LH language network
  2409. ```
  2410. ### Dataset 2 Continuous Age Analysis
  2411. ```{r ds2-continuous}
  2412. #A. Volume-based lateralization index (LI)
  2413. # Merge DS2 LI data with demographic data (children only)
  2414. ds2_cont_LI <- read.csv("../data/ds2_cont_age.csv")
  2415. # Compute subject-level mean neural effects and center age
  2416. ds2_cont_lat <- ds2_cont_LI %>%
  2417. group_by(Subject) %>%
  2418. summarise(
  2419. age_years = first(age_years),
  2420. age = first(age),
  2421. mean_LI = mean(lat, na.rm = TRUE),
  2422. .groups = "drop"
  2423. ) %>%
  2424. mutate(age_centered = age_years - mean(age_years, na.rm = TRUE))
  2425. cat("\n## Dataset 2 - Continuous Age Analysis\n")
  2426. cat("=====================================\n")
  2427. cat("Sample size:", nrow(ds2_cont_lat), "children\n")
  2428. cat("Age range:", round(min(ds2_cont_lat$age_years, na.rm = TRUE), 1), "-",
  2429. round(max(ds2_cont_lat$age_years, na.rm = TRUE), 1), "years\n")
  2430. # Model 3: LI predicted by continuous age (using subject-level data)
  2431. m3_ds2_li <- lm(mean_LI ~ age_centered, data = ds2_cont_lat)
  2432. cat("\n### DS2 Lateralization Index ~ Age (Continuous)\n")
  2433. print(summary(m3_ds2_li))
  2434. # Calculate effect size
  2435. anova_ds2_li <- anova(m3_ds2_li)
  2436. eta_squared_ds2_li <- anova_ds2_li$"Sum Sq"[1] / sum(anova_ds2_li$"Sum Sq")
  2437. cat("\nEffect size (eta-squared):", round(eta_squared_ds2_li, 4), "\n")
  2438. #B. Magnitude-based lateralization
  2439. # Merge DS2 effect data with demographic data (children only)
  2440. ds2_eff_cont <- ds2_eff_lang %>%
  2441. filter(age != "adult") %>%
  2442. mutate(age_centered = age_years - mean(age_years, na.rm = TRUE))
  2443. cat("\nEffect data sample size:", length(unique(ds2_eff_cont$Subject)), "children\n")
  2444. # Model 4: Effect magnitude predicted by hemisphere and continuous age
  2445. m4_ds2_eff <- lmerTest::lmer(contrast ~ hemi * age_centered + (hemi | Subject) + (hemi | ROI),
  2446. data = ds2_eff_cont)
  2447. cat("\n### DS2 Effect Magnitude ~ Hemisphere * Age (Continuous)\n")
  2448. print(summary(m4_ds2_eff))
  2449. # Extract R-squared for mixed model
  2450. r2_ds2_eff <- MuMIn::r.squaredGLMM(m4_ds2_eff)
  2451. cat("\nMixed model R-squared:\n")
  2452. cat("Marginal R²:", round(r2_ds2_eff[1], 4), "\n")
  2453. cat("Conditional R²:", round(r2_ds2_eff[2], 4), "\n")
  2454. ```
  2455. ### Combined Continuous Age Analysis
  2456. ```{r combined-continuous}
  2457. # Combine both datasets for meta-analysis approach
  2458. # Check if datasets exist and have data
  2459. if (exists("ds1_li_cont") && nrow(ds1_li_cont) > 0) {
  2460. ds1_li_cont$dataset <- "DS1"
  2461. } else {
  2462. warning("ds1_li_cont is empty or doesn't exist")
  2463. }
  2464. if (exists("ds2_cont_lat") && nrow(ds2_cont_lat) > 0) {
  2465. ds2_cont_lat$dataset <- "DS2"
  2466. } else {
  2467. warning("ds2_cont_lat is empty or doesn't exist")
  2468. }
  2469. # Combine datasets
  2470. combined_li_cont <- bind_rows(ds1_li_cont, ds2_cont_lat)
  2471. # Ensure dataset is a factor with at least 2 levels
  2472. if (length(unique(combined_li_cont$dataset)) < 2) {
  2473. warning("Only one dataset available for combined analysis. Skipping combined models.")
  2474. cat("\n## Combined Dataset - Continuous Age Analysis\n")
  2475. cat("=============================================\n")
  2476. cat("WARNING: Only one dataset available. Combined analysis skipped.\n")
  2477. } else {
  2478. combined_li_cont$dataset <- as.factor(combined_li_cont$dataset)
  2479. cat("\n## Combined Dataset - Continuous Age Analysis\n")
  2480. cat("=============================================\n")
  2481. cat("Combined sample size:", nrow(combined_li_cont), "children\n")
  2482. cat("DS1 contribution:", sum(combined_li_cont$dataset == "DS1"), "subjects\n")
  2483. cat("DS2 contribution:", sum(combined_li_cont$dataset == "DS2"), "subjects\n")
  2484. # Model 5: Combined LI analysis with dataset as covariate
  2485. m5_combined_li <- lm(mean_LI ~ age_centered + dataset, data = combined_li_cont)
  2486. cat("\n### Combined Lateralization Index ~ Age + Dataset\n")
  2487. print(summary(m5_combined_li))
  2488. # Calculate effect size for age effect
  2489. anova_combined_li <- anova(m5_combined_li)
  2490. # Use correct row name (age_centered, not Age)
  2491. if ("age_centered" %in% rownames(anova_combined_li)) {
  2492. eta_squared_age <- anova_combined_li["age_centered", "Sum Sq"] / sum(anova_combined_li$"Sum Sq")
  2493. cat("\nAge effect size (eta-squared):", round(eta_squared_age, 4), "\n")
  2494. }
  2495. # Test for dataset interaction with age (use age_centered, not Age)
  2496. m5b_combined_li <- lm(mean_LI ~ age_centered * dataset, data = combined_li_cont)
  2497. cat("\n### Testing Age x Dataset Interaction\n")
  2498. print(anova(m5_combined_li, m5b_combined_li))
  2499. }
  2500. # Combined effect analysis
  2501. ds1_eff_cont$dataset <- "DS1"
  2502. ds2_eff_cont$dataset <- "DS2"
  2503. combined_eff_cont <- bind_rows(ds1_eff_cont, ds2_eff_cont)
  2504. combined_eff_cont$dataset<-as.factor(combined_eff_cont$dataset)
  2505. cat("\nCombined effect data sample size:", length(unique(combined_eff_cont$Subject)), "children\n")
  2506. # Model 6: Combined effect analysis
  2507. m6_combined_eff <- lmerTest::lmer(contrast ~ hemi * age_centered + dataset +
  2508. (hemi | Subject) + (hemi | ROI),
  2509. data = combined_eff_cont)
  2510. cat("\n### Combined Effect Magnitude ~ Hemisphere * Age + Dataset\n")
  2511. print(summary(m6_combined_eff))
  2512. # Extract R-squared for combined mixed model
  2513. r2_combined_eff <- MuMIn::r.squaredGLMM(m6_combined_eff)
  2514. cat("\nCombined mixed model R-squared:\n")
  2515. cat("Marginal R²:", round(r2_combined_eff[1], 4), "\n")
  2516. cat("Conditional R²:", round(r2_combined_eff[2], 4), "\n")
  2517. ```
  2518. ### Summary Table for Continuous Age Effects
  2519. ```{r continuous-summary-table}
  2520. # Only create summary if combined models exist
  2521. if (exists("m5_combined_li") && exists("m6_combined_eff")) {
  2522. # Recompute combined LI eta² for the age term to avoid chunk-order issues
  2523. eta_squared_age <- {
  2524. a <- anova(m5_combined_li)
  2525. as.numeric(a["age_centered", "Sum Sq"] / sum(a[, "Sum Sq"]))
  2526. }
  2527. # Create summary table of all continuous age effects
  2528. continuous_results <- data.frame(
  2529. Analysis = c("DS1 - LI ~ Age", "DS1 - Effect ~ Hemi*Age",
  2530. "DS2 - LI ~ Age", "DS2 - Effect ~ Hemi*Age",
  2531. "Combined - LI ~ Age", "Combined - Effect ~ Hemi*Age"),
  2532. Dataset = c("DS1", "DS1", "DS2", "DS2", "Combined", "Combined"),
  2533. N_Subjects = c(length(unique(ds1_li_cont$Subject)),
  2534. length(unique(ds1_eff_cont$Subject)),
  2535. length(unique(ds2_cont_lat$Subject)),
  2536. length(unique(ds2_eff_cont$Subject)),
  2537. length(unique(combined_li_cont$Subject)),
  2538. length(unique(combined_eff_cont$Subject))),
  2539. age_centered_coefficient = c(
  2540. round(coef(m1_ds1_li)["age_centered"], 4),
  2541. round(fixef(m2_ds1_eff)["age_centered"], 4),
  2542. round(coef(m3_ds2_li)["age_centered"], 4),
  2543. round(fixef(m4_ds2_eff)["age_centered"], 4),
  2544. round(coef(m5_combined_li)["age_centered"], 4),
  2545. round(fixef(m6_combined_eff)["age_centered"], 4)
  2546. ),
  2547. age_centered_p_value = c(
  2548. round(summary(m1_ds1_li)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2549. round(summary(m2_ds1_eff)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2550. round(summary(m3_ds2_li)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2551. round(summary(m4_ds2_eff)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2552. round(summary(m5_combined_li)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2553. round(summary(m6_combined_eff)$coefficients["age_centered", "Pr(>|t|)"], 4)
  2554. ),
  2555. Effect_Size = c(
  2556. round(eta_squared_ds1_li, 4),
  2557. round(r2_ds1_eff[1], 4),
  2558. round(eta_squared_ds2_li, 4),
  2559. round(r2_ds2_eff[1], 4),
  2560. round(eta_squared_age, 4),
  2561. round(r2_combined_eff[1], 4)
  2562. )
  2563. )
  2564. # Format p-values for significance
  2565. continuous_results$age_centered_p_formatted <- case_when(
  2566. continuous_results$age_centered_p_value < 0.001 ~ "**<0.001**",
  2567. continuous_results$age_centered_p_value < 0.01 ~ paste0("**", sprintf("%.3f", continuous_results$age_centered_p_value), "**"),
  2568. continuous_results$age_centered_p_value < 0.05 ~ paste0("**", sprintf("%.3f", continuous_results$age_centered_p_value), "**"),
  2569. TRUE ~ sprintf("%.3f", continuous_results$age_centered_p_value)
  2570. )
  2571. } else {
  2572. # Create summary without combined models
  2573. continuous_results <- data.frame(
  2574. Analysis = c("DS1 - LI ~ Age", "DS1 - Effect ~ Hemi*Age",
  2575. "DS2 - LI ~ Age", "DS2 - Effect ~ Hemi*Age"),
  2576. Dataset = c("DS1", "DS1", "DS2", "DS2"),
  2577. N_Subjects = c(length(unique(ds1_li_cont$Subject)),
  2578. length(unique(ds1_eff_cont$Subject)),
  2579. length(unique(ds2_cont_lat$Subject)),
  2580. length(unique(ds2_eff_cont$Subject))),
  2581. age_centered_coefficient = c(
  2582. round(coef(m1_ds1_li)["age_centered"], 4),
  2583. round(fixef(m2_ds1_eff)["age_centered"], 4),
  2584. round(coef(m3_ds2_li)["age_centered"], 4),
  2585. round(fixef(m4_ds2_eff)["age_centered"], 4)
  2586. ),
  2587. age_centered_p_value = c(
  2588. round(summary(m1_ds1_li)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2589. round(summary(m2_ds1_eff)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2590. round(summary(m3_ds2_li)$coefficients["age_centered", "Pr(>|t|)"], 4),
  2591. round(summary(m4_ds2_eff)$coefficients["age_centered", "Pr(>|t|)"], 4)
  2592. ),
  2593. Effect_Size = c(
  2594. round(eta_squared_ds1_li, 4),
  2595. round(r2_ds1_eff[1], 4),
  2596. round(eta_squared_ds2_li, 4),
  2597. round(r2_ds2_eff[1], 4)
  2598. )
  2599. )
  2600. # Format p-values for significance
  2601. continuous_results$age_centered_p_formatted <- case_when(
  2602. continuous_results$age_centered_p_value < 0.001 ~ "**<0.001**",
  2603. continuous_results$age_centered_p_value < 0.01 ~ paste0("**", sprintf("%.3f", continuous_results$age_centered_p_value), "**"),
  2604. continuous_results$age_centered_p_value < 0.05 ~ paste0("**", sprintf("%.3f", continuous_results$age_centered_p_value), "**"),
  2605. TRUE ~ sprintf("%.3f", continuous_results$age_centered_p_value)
  2606. )
  2607. }
  2608. # Create formatted table
  2609. kable(continuous_results %>% select(-age_centered_p_value),
  2610. col.names = c("Analysis", "Dataset", "N", "Age β", "Age p", "Effect Size"),
  2611. caption = "**Summary of Continuous Age Effects Across All Analyses**",
  2612. escape = FALSE) %>%
  2613. kable_styling(bootstrap_options = c("striped", "hover")) %>%
  2614. add_header_above(c(" " = 3, "Age Effect" = 2, " " = 1))
  2615. cat("\n## Key Findings from Continuous Age Analysis\n")
  2616. cat("==========================================\n")
  2617. cat("- All models used children only (adults excluded)\n")
  2618. cat("- Age effects tested as continuous predictor variable\n")
  2619. cat("- LI = Lateralization Index, Effect = Activation magnitude\n")
  2620. cat("- Effect sizes: eta² for LI models, marginal R² for mixed models\n")
  2621. # Store results for further analysis
  2622. continuous_age_results <- list(
  2623. ds1_li_model = m1_ds1_li,
  2624. ds1_effect_model = m2_ds1_eff,
  2625. ds2_li_model = m3_ds2_li,
  2626. ds2_effect_model = m4_ds2_eff,
  2627. combined_li_model = m5_combined_li,
  2628. combined_effect_model = m6_combined_eff,
  2629. summary_table = continuous_results
  2630. )
  2631. ```
  2632. #SI-4C: Age-related changes in the magnitude of the Language > Control contrast in the LH language network controlling for motion. [parallels main Results section 3]
  2633. ## 2) Do different properties of the language network change between child ages (controlling for motion)?
  2634. ### Effect Size Analysis
  2635. #### Dataset 1
  2636. ```{r effect-size-ds1}
  2637. # Filter for language condition, left hemisphere, children only (exclude adults)
  2638. data_all_l_eff_lh_ds1 <- ds1_eff %>%
  2639. filter(condition == 'language',
  2640. hemi == 'lh') %>%
  2641. # age != 'adult') %>%
  2642. dplyr::select(Subject, ROI, age, front, hemi, contrast, num_outliers)
  2643. # Merge with demographic data and create mean_cont_df_ds1
  2644. # ds1_demo already has Subject column from CSV, no need to create from ID
  2645. cont_df_ds1 <- merge(data_all_l_eff_lh_ds1, ds1_demo, by = "Subject")
  2646. # Calculate subject-level means
  2647. mean_cont_df_ds1 <- cont_df_ds1 %>%
  2648. group_by(Subject) %>%
  2649. summarize(mean_num_outliers = mean(num_outliers),
  2650. mean_con = mean(contrast),
  2651. mean_age = mean(Age),
  2652. age_group = first(Set))
  2653. # Now run models that depend on mean_cont_df_ds1
  2654. m3_ds1 <- lm(mean_num_outliers ~ mean_age, data = mean_cont_df_ds1)
  2655. summary(m3_ds1)
  2656. motion_group_ds1 <- lm(mean_num_outliers ~ age_group, data = mean_cont_df_ds1)
  2657. summary(motion_group_ds1)
  2658. lsmeans(motion_group_ds1, list(pairwise ~ age_group), adjust = "tukey")
  2659. # Mixed effects model: effect size ~ motion + age
  2660. m1_ds1 <- lmer(contrast ~ num_outliers + age + (1|Subject) + (1|ROI),
  2661. data = data_all_l_eff_lh_ds1)
  2662. emm1 <- emmeans(m1_ds1, ~ age)
  2663. contrast(emm1, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  2664. ```
  2665. #### Dataset 2
  2666. ```{r effect-size-ds2}
  2667. ds2_outliers<-read.csv("../data/ds2_outliers.csv")
  2668. ds2_outliers$Subject<-ds2_outliers$SubID
  2669. # Run motion comparison by age
  2670. ds2_eff_v2<-merge(ds2_eff, ds2_outliers,by="Subject")%>% filter(condition=="EffectSize.mentalsocialphysical")
  2671. ds2_participant <- ds2_eff_v2 %>%
  2672. group_by(Subject) %>%
  2673. summarise(
  2674. # Average the outcome variable
  2675. num_outliers_avg = mean(num_outliers, na.rm = TRUE),
  2676. # Keep essential variables only
  2677. age = first(age),
  2678. # Count how many observations we averaged (useful for checking)
  2679. n_observations = n(),
  2680. .groups = 'drop'
  2681. )
  2682. # Check the result
  2683. head(ds2_participant)
  2684. cat("Original data rows:", nrow(ds2_eff_v2), "\n")
  2685. cat("Participant-level data rows:", nrow(ds2_participant), "\n")
  2686. cat("Observations per participant (should be consistent):", unique(ds2_participant$n_observations), "\n")
  2687. # Now run a simple linear model
  2688. m_ds2_simple <- lm(num_outliers_avg ~ age, data = ds2_participant)
  2689. anova(m_ds2_simple)
  2690. data_all_l_eff_lh_ds2 <- ds2_eff_v2 %>%
  2691. dplyr::filter(hemi == 'lh') %>%
  2692. dplyr::select(Subject, ROI, age, front, hemi, contrast, num_outliers)
  2693. # Mixed effects model: effect size ~ motion + age
  2694. m1_ds2 <- lmer(contrast ~ num_outliers + age + (1|Subject) + (1|ROI),
  2695. data = data_all_l_eff_lh_ds2)
  2696. summary(m1_ds2)
  2697. emm2 <- emmeans(m1_ds2, ~ age)
  2698. contrast(emm2, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  2699. ```
  2700. #### Dataset 1
  2701. ```{r continuous-age-ds1}
  2702. # SI-4B Part C: Continuous age analysis for magnitude (parallel to Table SI-4B part C)
  2703. # Linear regression: magnitude ~ age (children only, using mean_cont_df_ds1)
  2704. # Filter mean_cont_df_ds1 to children only (exclude adults)
  2705. # mean_cont_df_ds1 already created in effect-size-ds1 chunk with mean_con column
  2706. mean_cont_df_ds1_children <- mean_cont_df_ds1 %>%
  2707. filter(age_group != "adult")
  2708. # Fit linear regression model
  2709. m_cont_age_ds1 <- lm(mean_con ~ mean_age, data = mean_cont_df_ds1_children)
  2710. cat("\n## SI-4B Part C: Age as continuous predictor of LH magnitude (DS1)\n")
  2711. cat("Linear regression: magnitude ~ Age (children only)\n")
  2712. summary(m_cont_age_ds1)
  2713. ```
  2714. ### Resting State (Dataset 1 only)
  2715. ```{r resting-state-ds1}
  2716. # SI-4C Part B: IRFC analysis controlling for motion
  2717. # Table SI-4C: Inter-regional functional correlation strength in LH network, controlling for motion
  2718. # Load pre-summarized resting state data with age
  2719. # This file has: mean_rs, sub (Subject), age, hemi
  2720. ds1_rs_sum <- read.csv("../data/ds1_rs_sum_031025.csv")
  2721. # Filter to LH only and rename columns
  2722. ds1_rs_lh <- ds1_rs_sum %>%
  2723. filter(hemi == "lh") %>%
  2724. rename(Subject = sub, mean_r = mean_rs)
  2725. cat("\n## SI-4C Part B: IRFC controlling for motion (DS1)\n")
  2726. cat("Note: Analysis uses age groups; motion covariate integration pending\n")
  2727. # Model with age groups (parallel to Table SI-4C part B)
  2728. m_rs_age <- lm(mean_r ~ age, data = ds1_rs_lh)
  2729. summary(m_rs_age)
  2730. # Pairwise comparisons
  2731. emm_rs <- emmeans(m_rs_age, ~ age)
  2732. contrast(emm_rs, method = "trt.vs.ctrl", ref = "adult", adjust = "sidak")
  2733. # Store for use in SI-4D
  2734. ds1_rs_summary <- ds1_rs_lh
  2735. ```
  2736. #SI-4D. Ordered age comparisons for the magnitude of the Language > Control contrast and the strength of functional correlations in the LH language network.
  2737. ## A. Ordered age comparisons for magnitude
  2738. ```{r si-4d-magnitude}
  2739. # Table SI-4D Part A: Ordered age comparisons for LH magnitude
  2740. # Linear mixed-effects: magnitude ~ age, pairwise comparisons among child groups
  2741. # Dataset 1: Use data_all_l_eff_lh_ds1 (created in effect-size-ds1 chunk)
  2742. # Filter to children only
  2743. ds1_mag_children <- data_all_l_eff_lh_ds1 %>%
  2744. filter(age != "adult")
  2745. # Fit linear mixed-effects model
  2746. m_mag_ord_ds1 <- lmer(contrast ~ age + (1|Subject) + (1|ROI), data = ds1_mag_children)
  2747. summary(m_mag_ord_ds1)
  2748. # Pairwise comparisons among child age groups
  2749. emm_mag_ds1 <- emmeans(m_mag_ord_ds1, ~ age)
  2750. pairs_mag_ds1 <- pairs(emm_mag_ds1, adjust = "sidak")
  2751. cat("\n## SI-4D Part A: Ordered age comparisons for magnitude (DS1)\n")
  2752. print(pairs_mag_ds1)
  2753. # Dataset 2: Similar analysis
  2754. ds2_mag_children <- data_all_l_eff_lh_ds2 %>%
  2755. filter(age != "adult")
  2756. m_mag_ord_ds2 <- lmer(contrast ~ age + (1|Subject) + (1|ROI), data = ds2_mag_children)
  2757. summary(m_mag_ord_ds2)
  2758. emm_mag_ds2 <- emmeans(m_mag_ord_ds2, ~ age)
  2759. pairs_mag_ds2 <- pairs(emm_mag_ds2, adjust = "sidak")
  2760. cat("\n## SI-4D Part A: Ordered age comparisons for magnitude (DS2)\n")
  2761. print(pairs_mag_ds2)
  2762. ```
  2763. ## B. Ordered age comparisons for IRFC
  2764. ```{r si-4d-irfc}
  2765. # Table SI-4D Part B: Ordered age comparisons for IRFC strength
  2766. # Linear regression: IRFC ~ age, pairwise comparisons among child groups (DS1 only)
  2767. # Use ds1_rs_summary created in resting-state-ds1 chunk
  2768. # Filter to children only
  2769. ds1_rs_children <- ds1_rs_summary %>%
  2770. filter(age != "adult")
  2771. # Fit linear regression model
  2772. m_irfc_ord_ds1 <- lm(mean_r ~ age, data = ds1_rs_children)
  2773. summary(m_irfc_ord_ds1)
  2774. # Pairwise comparisons among child age groups
  2775. emm_irfc_ds1 <- emmeans(m_irfc_ord_ds1, ~ age)
  2776. pairs_irfc_ds1 <- pairs(emm_irfc_ds1, adjust = "sidak")
  2777. cat("\n## SI-4D Part B: Ordered age comparisons for IRFC (DS1 only)\n")
  2778. print(pairs_irfc_ds1)
  2779. ```

NCOMM_OzernovPalchik_OBrien_SI_code_091025.Rmd, no license · at the source

Overview

Authors: Ola Ozernov-Palchik1,2, Amanda M. O’Brien1,3, Elizabeth Jiachen Lee1,4, Hilary Richardson5, Rachel Romeo6, Moshe Poliak4, Benjamin Lipkin1,4, Hannah Small7, Jimmy Capella8, Alfonso Nieto-Castañón1, Rebecca Saxe1,4, John D. E. Gabrieli1,4, Evelina Fedorenko1,3,4
  1. McGovern Institute for Brain Research, Massachusetts Institute of Technology,Cambridge, MA USA
  2. Wheelock College of Education and Human Development, Boston University,Boston, MA USA
  3. Program in Speech and Hearing Bioscience and Technology, Harvard University,Cambridge, MA USA
  4. Department of Brain and Cognitive Sciences, Massachusetts Institute of Technology,Cambridge, MA USA
  5. School of Philosophy, Psychology, and Language Sciences, University of Edinburgh,Edinburgh, UK
  6. Department of Human Development and Quantitative Methodology, University of Maryland,College Park, MD USA
  7. Department of Cognitive Science, Johns Hopkins University,Baltimore, MD USA
  8. Department of Psychology and Neuroscience, University of North Carolina at Chapel Hill,Chapel Hill, NC USA
Institutions: Massachusetts Institute of Technology (United States); Wheelock College (United States); Harvard University (United States); University of Edinburgh (United Kingdom); University of Maryland, College Park (United States); Johns Hopkins University (United States); University of North Carolina at Chapel Hill (United States)
Journal: Nature communications, volume 17, issue 1, article 6505
Dates: received 16 July 2024; accepted 23 April 2026; published online 16 May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-72916-5 · PMID 42143030 · PMCID PMC13376885 · OpenAlex W4396973866
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), human (organism)
Methods: Spectral & time-frequency, Connectivity, Statistics, Machine learning, fMRI & imaging
Keywords: Language, Human behaviour
MeSH: Brain*, Functional Laterality*, Language*, Language Development*, Magnetic Resonance Imaging*, Adolescent, Brain Mapping, Child, Child, Preschool, Female, Humans, Male (* major topic)
Topic: Hemispheric Asymmetry in Neuroscience (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: cited by 11 papers (Europe PMC); 124 references in the paper

Abstract

In adults, left hemisphere (LH) damage often leads to aphasia, but many cases of early damage leave linguistic processing intact, with a functional language system developing in the right hemisphere. To explain this early apparent equipotentiality of the two hemispheres for language, some have proposed that the language system is more bilateral during early development and becomes increasingly left-lateralized with age. We examined language lateralization using fMRI in two large developmental cohorts (total n = 273 children aged 4-16 years; n = 107 adults). Strong, adult-like LH lateralization (in response magnitude and activation volume) was evident by age 4, although other features of the LH language network showed protracted development, including the magnitude of language response and the strength of functional connectivity. Thus, although the RH can take over language function in some cases of early brain damage, this plasticity occurs in spite of adult-level LH bias present by age 4 years.

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

Repositories

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

OSF 3mvpx

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 39 files
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: ggplot2 (5 files), ggpubr (5 files), lme4 (5 files), lmerTest (5 files), tidyverse (5 files), cowplot (3 files), brms (2 files), emmeans (2 files), SPM (2 files)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
8 files
At the source: osf.io/3mvpx/

web.conn-toolbox.org/resources/conn-extensions

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)

Code availability

Custom scripts used for fMRI preprocessing, functional localization, lateralization analyses, and Bayesian modeling are available at https://web.conn-toolbox.org/resources/conn-extensions/evlab and https://osf.io/3mvpx/. Additional analysis pipelines rely on publicly available software, including SPM12 and Wilke’s LI Toolbox.

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

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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 7 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

The source data underlying all main figures and tables are provided in a source data file accompanying this paper. Individual whole-brain activation maps and extracted data frames for all reported analyses have been deposited in the Open Science Framework (https://osf.io/3mvpx/). Consistent with historical data-sharing practices and participant consent constraints, raw fMRI data are not publicly released; for most legacy datasets, only activation maps are available, whereas raw data sharing for more recent participants depends on individual consent and must be evaluated case by case. To support reproducibility of the present findings, we provide group-level statistical maps for the language > control contrast. Data from the Olulade et al. (2020) replication analyses (Supplementary Information Sections 1–3) remain under the stewardship of the original author team and may be requested from Elissa Newport, with responses typically provided within 30 days. Source data are provided with this paper.

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 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 13 authors, 2 keywords, 12 MeSH terms, 1 funder, 112 references.

Cite

This paper

Ozernov-Palchik, O., O’Brien, A. M., Lee, E. J., Richardson, H., Romeo, R., Poliak, M., Lipkin, B., Small, H., Capella, J., Nieto-Castañón, A., Saxe, R., Gabrieli, J. D. E., & Fedorenko, E. (2026). Precision fMRI reveals that the language network exhibits adult-like left-hemispheric lateralization by 4 years of age. Nature communications, 17(1), 6505. https://doi.org/10.1038/s41467-026-72916-5

BibTeX

@article{ozernovpalchik2026precision,
author = {Ozernov-Palchik, Ola and O’Brien, Amanda M. and Lee, Elizabeth Jiachen and Richardson, Hilary and Romeo, Rachel and Poliak, Moshe and Lipkin, Benjamin and Small, Hannah and Capella, Jimmy and Nieto-Castañón, Alfonso and Saxe, Rebecca and Gabrieli, John D. E. and Fedorenko, Evelina},
title = {{Precision fMRI reveals that the language network exhibits adult-like left-hemispheric lateralization by 4 years of age}},
journal = {Nature communications},
year = {2026},
month = may,
volume = {17},
number = {1},
pages = {6505},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-72916-5},
url = {https://doi.org/10.1038/s41467-026-72916-5},
pmid = {42143030},
pmcid = {PMC13376885}
}

RIS

TY - JOUR
AU - Ozernov-Palchik, Ola
AU - O’Brien, Amanda M.
AU - Lee, Elizabeth Jiachen
AU - Richardson, Hilary
AU - Romeo, Rachel
AU - Poliak, Moshe
AU - Lipkin, Benjamin
AU - Small, Hannah
AU - Capella, Jimmy
AU - Nieto-Castañón, Alfonso
AU - Saxe, Rebecca
AU - Gabrieli, John D. E.
AU - Fedorenko, Evelina
TI - Precision fMRI reveals that the language network exhibits adult-like left-hemispheric lateralization by 4 years of age
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/05/16
VL - 17
IS - 1
SP - 6505
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-72916-5
UR - https://doi.org/10.1038/s41467-026-72916-5
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-72916-5",
"type": "article-journal",
"title": "Precision fMRI reveals that the language network exhibits adult-like left-hemispheric lateralization by 4 years of age",
"container-title": "Nature communications",
"author": [
{
"family": "Ozernov-Palchik",
"given": "Ola"
},
{
"family": "O’Brien",
"given": "Amanda M."
},
{
"family": "Lee",
"given": "Elizabeth Jiachen"
},
{
"family": "Richardson",
"given": "Hilary"
},
{
"family": "Romeo",
"given": "Rachel"
},
{
"family": "Poliak",
"given": "Moshe"
},
{
"family": "Lipkin",
"given": "Benjamin"
},
{
"family": "Small",
"given": "Hannah"
},
{
"family": "Capella",
"given": "Jimmy"
},
{
"family": "Nieto-Castañón",
"given": "Alfonso"
},
{
"family": "Saxe",
"given": "Rebecca"
},
{
"family": "Gabrieli",
"given": "John D. E."
},
{
"family": "Fedorenko",
"given": "Evelina"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "6505",
"DOI": "10.1038/s41467-026-72916-5",
"PMID": "42143030",
"PMCID": "PMC13376885",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-72916-5",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
16
]
]
}
}

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.1162/imag.a.1246 [code]
A 3.5-minute-long reading-based fMRI localizer for the language network.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: lmerTest, lme4, tidyverse, fMRI, 30 references, author Evelina Fedorenko
[2] doi:10.1038/s41467-026-76598-x
Preserved topography, lateralization, selectivity, and functional connectivity of the language network in older brains.
Journal: Nature communications
In common: fMRI, 26 references, author Evelina Fedorenko
[3] doi:10.1038/s41467-026-75745-8 [code]
A language network in the individualized functional connectomes of 1199 human brains doing arbitrary tasks.
Journal: Nature communications
In common: 24 references, author Evelina Fedorenko
[4] doi:10.1162/imag.a.1283 [code]
The language network responds robustly to sentences across tasks.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: emmeans, lmerTest, SPM, 1 other tool, fMRI, 21 references
[5] doi:10.1038/s42003-026-10040-2 [code]
Functional dissociation of language and theory of mind in the developing superior temporal lobe.
Journal: Communications biology
In common: emmeans, lmerTest, lme4, 3 other tools, 14 references
[6] doi:10.1523/jneurosci.0638-25.2026 [code]
The Extended Language Network: Language-Responsive Brain Areas Whose Contributions to Language Remain To Be Discovered.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: fMRI, 18 references
[7] doi:10.1162/imag.a.1347 [code]
Neural and behavioural correlates of theory of mind reasoning in five-year-old children born preterm.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: brms, lmerTest, lme4, 2 other tools, fMRI, 6 references, author Hilary Richardson
[8] doi:10.1016/j.isci.2026.116704
Domain-general and language-specific brain networks are differentially associated with verbal intelligence during development.
Journal: iScience
In common: 9 references
[9] doi:10.1038/s41597-026-07423-9 [code]
Unraveling the Complexity of Multilingual Comprehension: Neuroimaging and Linguistic Profiling in 700+ Adults.
Journal: Scientific data
In common: lmerTest, SPM, lme4, 4 other tools, 3 references
[10] doi:10.1016/j.dcn.2026.101765 [code]
Fusiform face area development correlates with development in higher-order social brain regions.
Journal: Developmental cognitive neuroscience
In common: lmerTest, lme4, ggpubr, 1 other tool, fMRI, 4 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.