OSCR

Corpus Callosum Dysgenesis impairs metacognition: evidence from multi-modality and multi-cohort replications

Code ↔ Paper

4 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 4 matches
  1. [1] § Methods › Random Dot Kinematogram (RDK) › Experiment 1 ↔ Scripts/Analysis_Script_Commented_Github.r, lines 678–726 · score 0.64 · posterior predictive checks, HMeta, ratio, binning, fitted, models
  2. [2] § Methods › Random Dot Kinematogram (RDK) › Experiment 1 ↔ Scripts/Analysis_Script_Commented_Github.r, lines 228–273 · score 0.57 · 50–100 %, position, coherently, confidence
  3. [3] § Results › Experiment 3 › Behavioral Analysis ↔ Scripts/Analysis_Script_Commented_Github.r, lines 1264–1351 · score 0.52 · monocular trials, lateralized trials, binocular trials, scores, VR, NT
  4. [4] § Methods › Random Dot Kinematogram (RDK) › Experiment 1 ↔ Scripts/Analysis_Script_Commented_Github.r, lines 2–43 · score 0.51 · perceptual accuracy, RDK task, calibration, online, efficiency, NT

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 · 1,351 lines · 66 KB · CC-BY-NC-SA-4.0 · 4 matches

  1. # Barnby et al. (2026): Corpus Callosum Dysgenesis impairs metacognition:
  2. # evidence from multi-modality and multi-cohort replications
  3. #
  4. # Analysis script covering three experiments:
  5. # Experiment 1: Online RDK (CCD vs NT, computer-based)
  6. # Experiment 2: In-lab RDK (CCD vs NT, MRI environment)
  7. # Experiment 3: VR RDK (CCD vs NT, binocular / monocular / lateralized)
  8. #
  9. # Key outcome measures: perceptual accuracy, confidence, metacognitive
  10. # efficiency (hierarchical meta-d' / d'; HMeta-D method, Maniscalco & Lau 2012)
  11. # ── 1. SETUP ------------------------------------------------------------
  12. library(ggpubr); library(tidyverse); library(patchwork)
  13. library(doParallel);library(rstanarm); library(brms)
  14. library(magrittr); library(reshape2); library(rjags)
  15. library(coda); library(lattice); library(lme4)
  16. library(broom); library(ggmcmc); library(foreach)
  17. library(sjPlot); library(tidyplots); library(tidybayes)
  18. source('Scripts/RDKUtilityFuncs.R')
  19. # Colour palettes used throughout all figures
  20. colpal <- c('#931F1D', '#FE938C', '#475B5A', '#81D6E3') # Exp 1 & 2 (4 groups)
  21. colpal_vr <- c('#B80C09', '#0E1C36') # Exp 3 (CCD / NT)
  22. # ── 2. LOAD DATA ------------------------------------------------------------
  23. # Experiments 1 (online) and 2 (MRI/lab): conventional RDK task
  24. Experiment_1_CCD <- read.csv('Data/Experiment1_CCD.csv')
  25. Experiment_1_NT <- read.csv('Data/Experiment1_NT.csv')
  26. Experiment_2_CCD <- read.csv('Data/Experiment2_CCD.csv')
  27. Experiment_2_NT <- read.csv('Data/Experiment2_NT.csv')
  28. # Experiment 3: VR task with binocular / monocular / lateralized presentations
  29. data_vr <- read.csv('Data/Experiment3.csv') %>% select(-X, -X.1)
  30. # ── 3. DATA QUALITY: CALIBRATION & EXCLUSIONS ------------------------------------------------------------
  31. # Participants were excluded if their coherence threshold during calibration
  32. # exceeded 0.5, if confidence ratings showed no variance, or if self-reported
  33. # error counts were implausibly high. Trials on which participants reported a
  34. # button-press mistake are also removed.
  35. ## --- 3a. Visual diagnostics ----
  36. # Inspect coherence trajectories across calibration trials to flag outliers
  37. (Experiment_1_NT %>% check_coherence('NT (Online)', legend = F) +
  38. Experiment_1_CCD %>% check_coherence('CCD (Online)', legend = F)) /
  39. (Experiment_2_NT %>% check_coherence('NT (MRI)', legend = F) +
  40. Experiment_2_CCD %>% check_coherence('CCD (MRI)', legend = F))
  41. # Spot-check specific outlier participants identified during calibration review
  42. Experiment_2_CCD %>%
  43. filter(ID %in% c('1001', '1027')) %>%
  44. check_coherence('CCD (MRI)', legend = F)
  45. # Inspect confidence-rating distributions per participant (ridge plots)
  46. # — applied to each of the four groups; code is identical so only one shown
  47. for (dat in list(Experiment_1_CCD, Experiment_2_CCD, Experiment_1_NT, Experiment_2_NT)) {
  48. P_dat <- dat %>%
  49. filter(conf != 0, type == 'main', mistake %in% c(0, NA, FALSE)) %>%
  50. ggplot(aes(conf, factor(ID), fill = ID, group = ID)) +
  51. ggridges::geom_density_ridges(scale = 1, jittered_points = TRUE,
  52. position = "raincloud", alpha = 0.7, scale = 0.2) +
  53. labs(x = 'Confidence', y = 'ID') +
  54. theme_bw() +
  55. theme(text = element_text(size = 18), legend.position = 'none')
  56. print(P_dat)
  57. }
  58. # Count self-reported button-press errors per participant (used to set
  59. # the ">20 mistakes" exclusion threshold applied below)
  60. for (dat in list(Experiment_1_CCD, Experiment_2_CCD, Experiment_1_NT, Experiment_2_NT)) {
  61. P_dat <- dat %>%
  62. filter(conf != 0, type == 'main') %>%
  63. group_by(ID) %>%
  64. mutate(mistake_sum = ifelse(!is.na(mistake) & mistake == TRUE, 1, 0),
  65. mistake_sum = sum(mistake_sum)) %>%
  66. dplyr::select(mistake_sum, ID) %>%
  67. unique() %>%
  68. ggplot(aes(factor(ID), mistake_sum, fill = ID)) +
  69. geom_col() +
  70. coord_cartesian(ylim = c(1, 25)) +
  71. labs(x = 'ID', y = 'Mistake') +
  72. theme_bw() +
  73. theme(text = element_text(size = 18), legend.position = 'none')
  74. print(P_dat)
  75. }
  76. ## --- 3b. Apply exclusions ----
  77. # Reasons documented per participant:
  78. # 2004 : coherence threshold > 0.5 (Exp 1 CCD)
  79. # 5f20… : coherence threshold > 0.5 (Exp 1 NT)
  80. # 1001 : coherence threshold > 0.5 (Exp 2 CCD)
  81. # 1006 : zero variance in confidence + >20 mistakes (Exp 2 CCD)
  82. # 1027 : >20 mistakes (Exp 2 CCD)
  83. # 3002 : zero variance in confidence (Exp 2 NT)
  84. # 1004 : self-reported mistakes not marked in data (Exp 2 CCD)
  85. # 2008 : uncertain phenotype (Exp 1 CCD); retained in sensitivity set
  86. Experiment_1_CCDcheck <- Experiment_1_CCD %>% filter(ID != '2004')
  87. Experiment_1_CCDcheck_w_exc <- Experiment_1_CCD %>% filter(ID != '2004', ID != '2008')
  88. Experiment_1_NTcheck <- Experiment_1_NT %>% filter(ID != '5f20a850b6d5ed3d186bc3c3')
  89. Experiment_2_CCDcheck <- Experiment_2_CCD %>% filter(!ID %in% c('1001', '1006', '1027'))
  90. Experiment_2_NTcheck <- Experiment_2_NT %>% filter(ID != '3002')
  91. # Remove mistake-flagged trials across all groups
  92. Experiment_1_CCDcheck <- Experiment_1_CCDcheck %>% filter(mistake == FALSE)
  93. Experiment_1_NTcheck <- Experiment_1_NTcheck %>% filter(mistake == FALSE)
  94. Experiment_2_CCDcheck <- Experiment_2_CCDcheck %>% filter(mistake == FALSE)
  95. Experiment_2_NTcheck <- Experiment_2_NTcheck %>% filter(mistake == FALSE)
  96. # Final sample sizes (Experiments 1 & 2)
  97. cat("Exp1 CCD (main):", length(unique(Experiment_1_CCDcheck$ID)), "\n")
  98. cat("Exp1 CCD (w/ 2008 excluded):", length(unique(Experiment_1_CCDcheck_w_exc$ID)), "\n")
  99. cat("Exp1 NT:", length(unique(Experiment_1_NTcheck$ID)), "\n")
  100. cat("Exp2 CCD:", length(unique(Experiment_2_CCDcheck$ID)), "\n")
  101. cat("Exp2 NT:", length(unique(Experiment_2_NTcheck$ID)), "\n")
  102. checkBothRDK <- rbind(
  103. Experiment_1_CCDcheck %>% mutate(correct = as.numeric(correct)),
  104. Experiment_1_NTcheck %>% mutate(correct = as.numeric(correct)),
  105. Experiment_2_CCDcheck %>% mutate(correct = as.numeric(correct)),
  106. Experiment_2_NTcheck %>% mutate(correct = as.numeric(correct))
  107. )
  108. # Sensitivity dataset (Exp 1 CCD with participant 2008 also excluded)
  109. checkBothRDK_w_exc <- rbind(
  110. Experiment_1_CCDcheck_w_exc %>% mutate(correct = as.numeric(correct)),
  111. Experiment_1_NTcheck %>% mutate(correct = as.numeric(correct)),
  112. Experiment_2_CCDcheck %>% mutate(correct = as.numeric(correct)),
  113. Experiment_2_NTcheck %>% mutate(correct = as.numeric(correct))
  114. )
  115. # Binary flag for the high-coherence (kmed × 2) condition
  116. checkBothRDK$highCoh <- ifelse(checkBothRDK$kmed == "kmed x 2", 1, 0)
  117. checkBothRDK_w_exc$highCoh <- ifelse(checkBothRDK_w_exc$kmed == "kmed x 2", 1, 0)
  118. # ── 4. ENVIRONMENT CHECK & DEMOGRAPHICS -------------------------
  119. # We test whether the testing platform (computer / MRI / VR) explains variance
  120. # in accuracy or confidence. If not, experiments can be analysed jointly.
  121. # Accuracy by environment
  122. accuracy_data_vr <- data_vr %>%
  123. rename(ID = PID, environment = Environment) %>%
  124. filter(TrialType == 'Main', Presentation == 'Binocular') %>%
  125. group_by(ID, environment, CCD) %>%
  126. summarise(Accuracy = mean(Correct), .groups = 'drop')
  127. accuracy_data_vr$ID <- as.character(accuracy_data_vr$ID)
  128. accuracy_data_non_vr <- checkBothRDK %>%
  129. filter(type == 'main') %>%
  130. group_by(ID, environment, CCD) %>%
  131. summarise(Accuracy = mean(correct), .groups = 'drop')
  132. accuracy_data <- rbind(accuracy_data_vr, accuracy_data_non_vr)
  133. # Linear model: environment and CCD group as predictors of accuracy
  134. model_acc <- lm(Accuracy ~ environment + CCD, data = accuracy_data)
  135. summary(aov(Accuracy ~ environment + CCD, data = accuracy_data))
  136. plot_model(model_acc, title = "Effect of environment on participant accuracy",
  137. show.values = TRUE, show.p = TRUE)
  138. tab_model(model_acc, show.re.var = TRUE,
  139. dv.labels = "Effect of environment on participant accuracy",
  140. file = "Stats/participant_accuracy_lmer.html")
  141. # Confidence by environment (scale confidence to z-scores before aggregating)
  142. data_vr$Confidence <- scale(data_vr$ReportedVeryConfident)
  143. confidence_data_vr <- data_vr %>%
  144. filter(TrialType == 'Main') %>%
  145. group_by(PID, Environment, CCD) %>%
  146. summarise(Confidence = mean(Confidence), .groups = 'drop') %>%
  147. mutate(PID = as.character(PID)) %>%
  148. rename(ID = PID, environment = Environment)
  149. checkBothRDKConfidenceTrialsOnly <- checkBothRDK %>%
  150. filter(conf != 0) %>%
  151. mutate(Confidence = scale(conf))
  152. confidence_data_non_vr <- checkBothRDKConfidenceTrialsOnly %>%
  153. filter(type == 'main') %>%
  154. group_by(ID, environment, CCD) %>%
  155. summarise(Confidence = mean(Confidence), .groups = 'drop')
  156. confidence_data <- rbind(confidence_data_vr, confidence_data_non_vr)
  157. summary(aov(Confidence ~ environment + CCD, data = confidence_data))
  158. # ── 5. BEHAVIOURAL RESULTS: EXPERIMENTS 1 & 2 -----------------------------------
  159. # Primary outcome: does accuracy / confidence scale with coherence level,
  160. # and does this scaling differ between CCD and NT?
  161. lmer_data <- checkBothRDK %>% filter(type == 'main', kmed != 'kmed', mistake %in% c(0,NA,FALSE), conf != 0)
  162. lmer_data_exc <- checkBothRDK_w_exc %>% filter(type == 'main', kmed != 'kmed', mistake %in% c(0,NA,FALSE), conf != 0)
  163. data_vr_tests <- data_vr %>%
  164. rename(ID=PID, type=TrialType, coherence=ActiveCoherence, group=Group) %>%
  165. filter(type=='Main') %>%
  166. group_by(ID, PresentationType) %>%
  167. mutate(
  168. conf = scale(ReportedVeryConfident),
  169. #conf = 1/(1+exp(-scale(conf))),
  170. kmed = ifelse(HighCoherenceTrial==1, 'x2','x0.5'),
  171. kmed = factor(kmed, levels = c('x0.5', 'x2'), ordered = T)
  172. ) %>%
  173. group_by(ID, group)
  174. # --- 5a. Reaction-time density plots (sanity check) ----
  175. CCDcheck1 <- ggplot(
  176. checkBothRDK %>%
  177. filter(type == 'main', kmed != 'kmed', RT < 5000, mistake %in% c(0,NA,FALSE)) %>%
  178. mutate(correct = ifelse(correct == 1, 'Correct: Yes', 'Correct: No')),
  179. aes(RT, fill = group)) +
  180. geom_density(show.legend = TRUE, alpha = 0.7) +
  181. scale_fill_manual(values = colpal) +
  182. facet_wrap(kmed ~ correct) +
  183. labs(x = 'Reaction Time (ms)') +
  184. theme_minimal() +
  185. theme(axis.title.y = element_blank())
  186. # Non-parametric tests on RT medians (group × environment)
  187. kruskal.test(RT ~ group, data = rbind(Experiment_1_CCDcheck, Experiment_1_NTcheck))
  188. kruskal.test(RT ~ group, data = rbind(Experiment_2_CCDcheck, Experiment_2_NTcheck))
  189. kruskal.test(RT ~ group, data = rbind(Experiment_2_CCDcheck, Experiment_1_CCDcheck))
  190. kruskal.test(RT ~ group, data = rbind(Experiment_1_NTcheck, Experiment_2_NTcheck))
  191. # --- 5b. Per-participant accuracy and confidence by coherence level --------
  192. # Compute individual means across the two coherence conditions
  193. ## EXPERIMENT 1 & 2 -------
  194. av_cor <- checkBothRDK %>%
  195. filter(type == 'main', kmed != 'kmed', mistake %in% c(0,NA,FALSE)) %>%
  196. group_by(group, ID, kmed) %>%
  197. mutate(av_cor = mean(corAdjusted),
  198. kmed = ifelse(kmed == 'kmed x 2', 'x2', 'x0.5')) %>%
  199. group_by(group, kmed) %>%
  200. dplyr::select(ID, av_cor, kmed) %>%
  201. distinct()
  202. av_co <- checkBothRDK %>%
  203. filter(type == 'main', kmed != 'kmed', conf != 0, mistake %in% c(0,NA,FALSE)) %>%
  204. group_by(group, ID, kmed) %>%
  205. mutate(av_co = mean(conf),
  206. kmed = ifelse(kmed == 'kmed x 2', 'x2', 'x0.5')) %>%
  207. group_by(group, kmed) %>%
  208. dplyr::select(ID, av_co, kmed) %>%
  209. distinct()
  210. # Shared aesthetics for accuracy and confidence plots
  211. dot_layer <- list(
  212. geom_jitter(shape = 21, width = 0.1, alpha = 0.1, colour = 'black'),
  213. stat_summary(),
  214. stat_summary(geom = 'line', show.legend = FALSE),
  215. scale_color_manual(values = colpal),
  216. scale_fill_manual(values = colpal),
  217. theme_minimal(),
  218. theme(panel.grid = element_blank(), text = element_text(size = 18))
  219. )
  220. CCDcheck3 <- ggplot(av_cor, aes(kmed, av_cor, color = group, group = group,
  221. fill = group)) +
  222. dot_layer +
  223. labs(x = 'Coherence', y = 'Correct') +
  224. coord_cartesian(ylim = c(0.5, 1)) +
  225. theme(legend.position = 'none')
  226. CCDcheck4 <- ggplot(av_co, aes(kmed, av_co, color = group, group = group,
  227. fill = group)) +
  228. dot_layer +
  229. labs(x = 'Coherence', y = 'Confidence') +
  230. coord_cartesian(ylim = c(50, 100)) +
  231. theme(legend.position = 'none')
  232. (CCDcheck3 | CCDcheck4)
  233. ## EXPERIMENT 3 --------
  234. # Per-participant accuracy and confidence by coherence × presentation type
  235. av_cor_vr <- data_vr_tests %>%
  236. group_by(group, ID, kmed, PresentationType) %>%
  237. summarise(av_cor = mean(Correct), .groups='drop')
  238. av_co_vr <- data_vr_tests %>%
  239. group_by(group, ID, kmed, PresentationType) %>%
  240. summarise(av_co = mean(conf), .groups='drop')
  241. # Accuracy and confidence plots (faceted by presentation type)
  242. vr_cor_plot <- ggplot(av_cor_vr, aes(kmed, av_cor, color = group, group = group, fill = group)) +
  243. geom_jitter(shape = 21, width = 0.1, alpha = 0.1, colour = 'black') +
  244. stat_summary() + stat_summary(geom = 'line', show.legend = FALSE) +
  245. labs(x = 'Coherence', y = 'Correct') +
  246. facet_wrap(~PresentationType) +
  247. scale_color_manual(values = colpal_vr) + scale_fill_manual(values = colpal_vr) +
  248. coord_cartesian(ylim = c(0.5, 1)) + theme_minimal() +
  249. theme(legend.position = 'none', axis.title.x = element_blank())
  250. vr_co_plot <- ggplot(av_co_vr, aes(kmed, av_co, color = group, group = group, fill = group)) +
  251. geom_jitter(shape = 21, width = 0.1, alpha = 0.1, colour = 'black', show.legend = FALSE) +
  252. stat_summary() + stat_summary(geom = 'line', show.legend = FALSE) +
  253. labs(x = 'Coherence', y = 'Confidence') +
  254. facet_wrap(~PresentationType) +
  255. scale_color_manual(values = colpal_vr, name = 'Group') +
  256. scale_fill_manual(values = colpal_vr) +
  257. theme_minimal() +
  258. theme(legend.position = 'none', strip.text.x = element_blank())
  259. (vr_cor_plot / vr_co_plot) & theme(text = element_text(size = 18), panel.grid = element_blank())
  260. # --- 5c. Paired t-tests: coherence effect within each group ----------
  261. for (grp in c('CCD (Online)', 'NT (Online)', 'CCD (MRI)', 'NT (MRI)')) {
  262. cat("\n--- Accuracy:", grp, "---\n")
  263. print(t.test(av_cor ~ kmed, data = av_cor %>% filter(group == grp), paired = TRUE))
  264. cat("\n--- Confidence:", grp, "---\n")
  265. print(t.test(av_co ~ kmed, data = av_co %>% filter(group == grp), paired = TRUE))
  266. }
  267. # Group × coherence ANOVAs (Exp 1 and Exp 2 separately)
  268. aov(av_cor ~ group*kmed, data = av_cor %>%
  269. filter(group %in% c('NT (Online)', 'CCD (Online)'))) %>% summary()
  270. aov(av_cor ~ group*kmed, data = av_cor %>%
  271. filter(group %in% c('NT (MRI)', 'CCD (MRI)'))) %>% summary()
  272. aov(av_co ~ group*kmed, data = av_co %>%
  273. filter(group %in% c('NT (Online)', 'CCD (Online)'))) %>% summary()
  274. aov(av_co ~ group*kmed, data = av_co %>%
  275. filter(group %in% c('NT (MRI)', 'CCD (MRI)'))) %>% summary()
  276. # --- 5d. Linear mixed-effects models (trial-level; primary inference) --------
  277. # kmed × group interaction on accuracy and confidence, with random intercept
  278. # per participant. Sensitivity models include/exclude participant 2008.
  279. ### Experiment 1 ------
  280. run_lmer(correct ~ kmed + (1|ID), lmer_data %>% filter(group == 'CCD (Online)'))
  281. run_lmer(correct ~ kmed + (1|ID), lmer_data %>% filter(group == 'NT (Online)'))
  282. run_lmer(correct ~ kmed * group + (1|ID), lmer_data %>% filter(group %in% c('NT (Online)', 'CCD (Online)')))
  283. run_lmer(conf ~ kmed + (1|ID), lmer_data %>% filter(group == 'CCD (Online)'))
  284. run_lmer(conf ~ kmed + (1|ID), lmer_data %>% filter(group == 'NT (Online)'))
  285. run_lmer(conf ~ kmed * group + (1|ID), lmer_data %>% filter(group %in% c('NT (Online)', 'CCD (Online)')))
  286. ### Experiment 2 --------
  287. run_lmer(correct ~ kmed + (1|ID), lmer_data %>% filter(group == 'CCD (MRI)'))
  288. run_lmer(correct ~ kmed + (1|ID), lmer_data %>% filter(group == 'NT (MRI)'))
  289. run_lmer(correct ~ kmed * group + (1|ID), lmer_data %>% filter(group %in% c('NT (MRI)', 'CCD (MRI)')))
  290. run_lmer(conf ~ kmed + (1|ID), lmer_data %>% filter(group == 'CCD (MRI)'))
  291. run_lmer(conf ~ kmed + (1|ID), lmer_data %>% filter(group == 'NT (MRI)'))
  292. run_lmer(conf ~ kmed * group + (1|ID), lmer_data %>% filter(group %in% c('NT (MRI)', 'CCD (MRI)')))
  293. ### Experiment 3 ---------
  294. run_lmer(Correct ~ kmed * group + (1|ID), data_vr_tests)
  295. run_lmer(conf ~ kmed * group + (1|ID), data_vr_tests)
  296. #### BINO ----
  297. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Binocular'))
  298. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Binocular'))
  299. run_lmer(Correct ~ kmed * group + (1|ID), data_vr_tests %>% filter(PresentationType=='Binocular'))
  300. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Binocular'))
  301. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Binocular'))
  302. run_lmer(conf ~ kmed * group + (1|ID), data_vr_tests %>% filter(PresentationType=='Binocular'))
  303. #### LAT ----
  304. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Lateralized'))
  305. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Lateralized'))
  306. run_lmer(Correct ~ kmed * group + (1|ID), data_vr_tests %>% filter(PresentationType=='Lateralized'))
  307. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Lateralized'))
  308. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Lateralized'))
  309. run_lmer(conf ~ kmed * group + (1|ID), data_vr_tests %>% filter(PresentationType=='Lateralized'))
  310. #### MONO ----
  311. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Monocular'))
  312. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Monocular'))
  313. run_lmer(Correct ~ kmed * group + (1|ID), data_vr_tests %>% filter(PresentationType=='Monocular'))
  314. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Monocular'))
  315. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Monocular'))
  316. run_lmer(conf ~ kmed * group + (1|ID), data_vr_tests %>% filter(PresentationType=='Monocular'))
  317. # --- 5e. Confidence Bin Spread --------
  318. LAB_CCD_metaDat <- HMetaGroupPrep(checkBothRDK, 'CCD (MRI)')
  319. LAB_CCD_metaDat_hc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 2'), 'CCD (MRI)')
  320. LAB_CCD_metaDat_lc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 0.5'), 'CCD (MRI)')
  321. # Create data set for ONL CCD participants
  322. ONL_CCD_metaDat <- HMetaGroupPrep(checkBothRDK, 'CCD (Online)')
  323. ONL_CCD_metaDat_hc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 2'), 'CCD (Online)')
  324. ONL_CCD_metaDat_lc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 0.5'), 'CCD (Online)')
  325. # Create data set for ONL NT participants
  326. ONL_NT_metaDat <- HMetaGroupPrep(checkBothRDK, 'NT (Online)')
  327. ONL_NT_metaDat_hc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 2'), 'NT (Online)')
  328. ONL_NT_metaDat_lc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 0.5'), 'NT (Online)')
  329. # Create data set for LAB CCD participants
  330. LAB_NT_metaDat <- HMetaGroupPrep(checkBothRDK, 'NT (MRI)')
  331. LAB_NT_metaDat_hc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 2'), 'NT (MRI)')
  332. LAB_NT_metaDat_lc <- HMetaGroupPrep(checkBothRDK %>% filter(kmed == 'kmed x 0.5'), 'NT (MRI)')
  333. # Confidence spread (normalised response count distributions by coherence)
  334. conf_spread <- data.frame(
  335. conf = c(6:1, -1:-6),
  336. cond = c(rep('lab', 72), rep('online', 72)),
  337. type = rep(c(rep('x2',24), rep('Both',24), rep('x0.5',24)), 2),
  338. s1 = c(rowSums(LAB_CCD_metaDat_hc$S1mat), rowSums(LAB_NT_metaDat_hc$S1mat),
  339. rowSums(LAB_CCD_metaDat$S1mat), rowSums(LAB_NT_metaDat$S1mat),
  340. rowSums(LAB_CCD_metaDat_lc$S1mat), rowSums(LAB_NT_metaDat_lc$S1mat),
  341. rowSums(ONL_CCD_metaDat_hc$S1mat), rowSums(ONL_NT_metaDat_hc$S1mat),
  342. rowSums(ONL_CCD_metaDat$S1mat), rowSums(ONL_NT_metaDat$S1mat),
  343. rowSums(ONL_CCD_metaDat_lc$S1mat), rowSums(ONL_NT_metaDat_lc$S1mat)),
  344. s2 = c(rev(rowSums(LAB_CCD_metaDat_hc$S2mat)), rev(rowSums(LAB_NT_metaDat_hc$S2mat)),
  345. rev(rowSums(LAB_CCD_metaDat$S2mat)), rev(rowSums(LAB_NT_metaDat$S2mat)),
  346. rev(rowSums(LAB_NT_metaDat_lc$S2mat)), rev(rowSums(LAB_NT_metaDat_lc$S2mat)),
  347. rev(rowSums(ONL_CCD_metaDat_hc$S2mat)), rev(rowSums(ONL_NT_metaDat_hc$S2mat)),
  348. rev(rowSums(ONL_CCD_metaDat$S2mat)), rev(rowSums(ONL_NT_metaDat$S2mat)),
  349. rev(rowSums(ONL_NT_metaDat_lc$S2mat)), rev(rowSums(ONL_NT_metaDat_lc$S2mat)))
  350. ) %>%
  351. pivot_longer(s1:s2, names_to = 'Dec', values_to = 'Count') %>%
  352. mutate(group = c(rep('CCD',144), rep('NT',144))) %>%
  353. group_by(type, Dec, cond, group) %>%
  354. mutate(type = factor(type, levels = c('x0.5','Both','x2')),
  355. Count = Count / sum(Count)) %>%
  356. ggplot(aes(conf, Count, fill = group)) +
  357. geom_col(position = 'dodge') +
  358. labs(x = 'Confidence Bin (1=low, 6=high)', y = 'Normalised Count') +
  359. scale_x_continuous(breaks = c(6,1,-1,-6)) +
  360. facet_wrap(~type) +
  361. theme_bw(base_size = 24) +
  362. theme(legend.position = c(0.10,0.8), legend.background = element_blank(),
  363. legend.title = element_blank(), panel.grid = element_blank())
  364. conf_spread
  365. # ── 6. HIERARCHICAL META-D' FITTING ---------------
  366. # Metacognitive efficiency (meta-d'/d') estimated using the hierarchical Bayesian
  367. # method (HMeta-D; Fleming 2017). Response matrices are built with HMetaGroupPrep()
  368. # then passed to metad_group(), which is slow and should only be run once.
  369. # --- 6a. Prepare response count matrices ----
  370. LAB_CCD_metaDat <- HMetaGroupPrep(checkBothRDK, 'CCD (MRI)')
  371. ONL_CCD_metaDat <- HMetaGroupPrep(checkBothRDK, 'CCD (Online)')
  372. ONL_CCD_metaDat_exc <- HMetaGroupPrep(checkBothRDK_w_exc, 'CCD (Online)')
  373. ONL_NT_metaDat <- HMetaGroupPrep(checkBothRDK, 'NT (Online)')
  374. LAB_NT_metaDat <- HMetaGroupPrep(checkBothRDK, 'NT (MRI)')
  375. # VR: three presentation types × with/without sensitivity exclusions
  376. exc_ids <- c('4003', '4004', '4007', '4008')
  377. VR_CCD_metaDat <- HMetaGroupPrep_vr(data_vr %>% filter(BinocularTrial == 1), 'CCD')
  378. VR_CCD_metaDat_L <- HMetaGroupPrep_vr(data_vr %>% filter(LateralizedTrial == 1), 'CCD')
  379. VR_CCD_metaDat_M <- HMetaGroupPrep_vr(data_vr %>% filter(MonocularTrial == 1), 'CCD')
  380. VR_CCD_metaDat_exc <- HMetaGroupPrep_vr(data_vr %>% filter(BinocularTrial == 1, !PID %in% exc_ids), 'CCD')
  381. VR_CCD_metaDat_L_exc <- HMetaGroupPrep_vr(data_vr %>% filter(LateralizedTrial == 1, !PID %in% exc_ids), 'CCD')
  382. VR_CCD_metaDat_M_exc <- HMetaGroupPrep_vr(data_vr %>% filter(MonocularTrial == 1, !PID %in% exc_ids), 'CCD')
  383. VR_NT_metaDat <- HMetaGroupPrep_vr(data_vr %>% filter(BinocularTrial == 1), 'NT')
  384. VR_NT_metaDat_L <- HMetaGroupPrep_vr(data_vr %>% filter(LateralizedTrial == 1), 'NT')
  385. VR_NT_metaDat_M <- HMetaGroupPrep_vr(data_vr %>% filter(MonocularTrial == 1), 'NT')
  386. # --- 6b. Fit HMeta-D (run once; results saved to disk) --------
  387. ## NOTE: outputs are stochastic. Expect minor numeric deviation from published work.
  388. do_mratio <- 0
  389. if(do_mratio == 1){
  390. source('HMeta-d-master/R/Function_metad_group.R')
  391. outputOnline_NT <- metad_group(nR_S1 = list(ONL_NT_metaDat[[1]]), nR_S2 = list(ONL_NT_metaDat[[2]]));
  392. outputLab_NT <- metad_group(nR_S1 = list(LAB_NT_metaDat[[1]]), nR_S2 = list(LAB_NT_metaDat[[2]]));
  393. outputOnline_CCD <- metad_group(nR_S1 = list(ONL_CCD_metaDat[[1]]), nR_S2 = list(ONL_CCD_metaDat[[2]]));
  394. outputLab_CCD <- metad_group(nR_S1 = list(LAB_CCD_metaDat[[1]]), nR_S2 = list(LAB_CCD_metaDat[[2]]));
  395. outputVR_NT <- metad_group(nR_S1 = list(VR_NT_metaDat[[1]]), nR_S2 = list(VR_NT_metaDat[[2]]));
  396. outputVR_NT_L <- metad_group(nR_S1 = list(VR_NT_metaDat_L[[1]]), nR_S2 = list(VR_NT_metaDat_L[[2]]));
  397. outputVR_NT_M <- metad_group(nR_S1 = list(VR_NT_metaDat_M[[1]]), nR_S2 = list(VR_NT_metaDat_M[[2]]));
  398. outputVR_CCD <- metad_group(nR_S1 = list(VR_CCD_metaDat[[1]]), nR_S2 = list(VR_CCD_metaDat[[2]]));
  399. outputVR_CCD_L <- metad_group(nR_S1 = list(VR_CCD_metaDat_L[[1]]),nR_S2 = list(VR_CCD_metaDat_L[[2]]));
  400. outputVR_CCD_M <- metad_group(nR_S1 = list(VR_CCD_metaDat_M[[1]]),nR_S2 = list(VR_CCD_metaDat_M[[2]]));
  401. outputVR_CCD_exc <- metad_group(nR_S1 = list(VR_CCD_metaDat_exc[[1]]), nR_S2 = list(VR_CCD_metaDat_exc[[2]]));
  402. outputVR_CCD_L_exc <- metad_group(nR_S1 = list(VR_CCD_metaDat_L_exc[[1]]), nR_S2 = list(VR_CCD_metaDat_L_exc[[2]]));
  403. outputVR_CCD_M_exc <- metad_group(nR_S1 = list(VR_CCD_metaDat_M_exc[[1]]), nR_S2 = list(VR_CCD_metaDat_M_exc[[2]]));
  404. # Save the output
  405. saveRDS(outputLab_CCD, 'HMetaDFit/Mratio/LAB_CCD.rdata')
  406. saveRDS(outputOnline_CCD, 'HMetaDFit/Mratio/ONL_CCD.rdata')
  407. saveRDS(outputOnline_CCD_exc, 'HMetaDFit/Mratio/ONL_CCD_exc.rdata')
  408. saveRDS(outputVR_CCD, 'HMetaDFit/Mratio/VR_CCD.rdata')
  409. saveRDS(outputVR_CCD_L, 'HMetaDFit/Mratio/VR_CCD_L.rdata')
  410. saveRDS(outputVR_CCD_M, 'HMetaDFit/Mratio/VR_CCD_M.rdata')
  411. saveRDS(outputVR_CCD_exc, 'HMetaDFit/Mratio/VR_CCD_exc.rdata')
  412. saveRDS(outputVR_CCD_L_exc, 'HMetaDFit/Mratio/VR_CCD_L_exc.rdata')
  413. saveRDS(outputVR_CCD_M_exc, 'HMetaDFit/Mratio/VR_CCD_M_exc.rdata')
  414. saveRDS(outputLab_NT, 'HMetaDFit/Mratio/LAB_NT.rdata')
  415. saveRDS(outputOnline_NT, 'HMetaDFit/Mratio/ONL_NT.rdata')
  416. saveRDS(outputVR_NT, 'HMetaDFit/Mratio/VR_NT.rdata')
  417. saveRDS(outputVR_NT_L, 'HMetaDFit/Mratio/VR_NT_L.rdata')
  418. saveRDS(outputVR_NT_M, 'HMetaDFit/Mratio/VR_NT_M.rdata')
  419. }
  420. # --- 6c. Load pre-fitted models ----
  421. outputLAB_CCD <- readRDS('HMetaDFit/Mratio/LAB_CCD.rdata')
  422. outputONL_CCD <- readRDS('HMetaDFit/Mratio/ONL_CCD.rdata')
  423. outputVR_CCD <- readRDS('HMetaDFit/Mratio/VR_CCD.rdata')
  424. outputVR_CCD_L <- readRDS('HMetaDFit/Mratio/VR_CCD_L.rdata')
  425. outputVR_CCD_M <- readRDS('HMetaDFit/Mratio/VR_CCD_M.rdata')
  426. outputLAB_NT <- readRDS('HMetaDFit/Mratio/LAB_NT.rdata')
  427. outputONL_NT <- readRDS('HMetaDFit/Mratio/ONL_NT.rdata')
  428. outputVR_NT <- readRDS('HMetaDFit/Mratio/VR_NT.rdata')
  429. outputVR_NT_L <- readRDS('HMetaDFit/Mratio/VR_NT_L.rdata')
  430. outputVR_NT_M <- readRDS('HMetaDFit/Mratio/VR_NT_M.rdata')
  431. # --- 6d. Extract cleaned MCMC samples and posterior summaries ----
  432. Results_LAB_CCD <- HMeta_post_clean(outputLAB_CCD, 'CCD (LAB)', F)
  433. Results_ONL_CCD <- HMeta_post_clean(outputONL_CCD, 'CCD (ONL)', F)
  434. Results_ONL_CCD_exc <- HMeta_post_clean(outputONL_CCD_exc, 'CCD (ONL)', F)
  435. Results_VR_CCD <- HMeta_post_clean(outputVR_CCD, 'CCD (VR)', F)
  436. Results_VR_CCD_L <- HMeta_post_clean(outputVR_CCD_L, 'CCD (VR L)',F)
  437. Results_VR_CCD_M <- HMeta_post_clean(outputVR_CCD_M, 'CCD (VR M)',F)
  438. Results_ONL_NT <- HMeta_post_clean(outputONL_NT, 'NT (ONL)', F)
  439. Results_LAB_NT <- HMeta_post_clean(outputLAB_NT, 'NT (LAB)', F)
  440. Results_VR_NT <- HMeta_post_clean(outputVR_NT, 'NT (VR)', F)
  441. Results_VR_NT_L <- HMeta_post_clean(outputVR_NT_L, 'NT (VR L)', F)
  442. Results_VR_NT_M <- HMeta_post_clean(outputVR_NT_M, 'NT (VR M)', F)
  443. # Stack MCMC draws for Experiments 1 & 2
  444. mcmcGroup <- rbind(Results_LAB_CCD[[1]], Results_ONL_CCD[[1]],
  445. Results_ONL_NT[[1]], Results_LAB_NT[[1]])
  446. # Stack MCMC draws for Experiment 3 (all three presentation types)
  447. mcmcGroup_vr <- rbind(Results_VR_CCD[[1]], Results_VR_NT[[1]],
  448. Results_VR_CCD_L[[1]], Results_VR_NT_L[[1]],
  449. Results_VR_CCD_M[[1]], Results_VR_NT_M[[1]])
  450. statGroup <- data.frame(
  451. CCD_EXP1 = c(unlist(extract_stat(Results_ONL_CCD, "mu_logMratio")),
  452. unlist(extract_stat(Results_ONL_CCD, "sigma_logMratio"))),
  453. NT_EXP1 = c(unlist(extract_stat(Results_ONL_NT, "mu_logMratio")),
  454. unlist(extract_stat(Results_ONL_NT, "sigma_logMratio"))),
  455. CCD_EXP2 = c(unlist(extract_stat(Results_LAB_CCD, "mu_logMratio")),
  456. unlist(extract_stat(Results_LAB_CCD, "sigma_logMratio"))),
  457. NT_EXP2 = c(unlist(extract_stat(Results_LAB_NT, "mu_logMratio")),
  458. unlist(extract_stat(Results_LAB_NT, "sigma_logMratio"))),
  459. Variable = c('mu', 'lower mu', 'upper mu', 'sigma', 'lower sigma', 'upper sigma')
  460. )
  461. statGroup
  462. # Print VR group means for quick reference
  463. cat("VR CCD mu_Mratio:", exp(Results_VR_CCD[[3]]$mean[Results_VR_CCD[[3]]$name == "mu_logMratio"]), "\n")
  464. cat("VR NT mu_Mratio:", exp(Results_VR_NT[[3]]$mean[Results_VR_NT[[3]]$name == "mu_logMratio"]), "\n")
  465. cat("VR CCD lat mu_Mratio:",exp(Results_VR_CCD_L[[3]]$mean[Results_VR_CCD_L[[3]]$name == "mu_logMratio"]),"\n")
  466. cat("VR NT lat mu_Mratio:",exp(Results_VR_NT_L[[3]]$mean[Results_VR_NT_L[[3]]$name == "mu_logMratio"]),"\n")
  467. cat("VR CCD mon mu_Mratio:",exp(Results_VR_CCD_M[[3]]$mean[Results_VR_CCD_M[[3]]$name == "mu_logMratio"]),"\n")
  468. cat("VR NT mon mu_Mratio:",exp(Results_VR_NT_M[[3]]$mean[Results_VR_NT_M[[3]]$name == "mu_logMratio"]),"\n")
  469. # --- 6e. Posterior distribution plots (mu_logMratio) -----------
  470. # Shared theme for HMeta-D histogram panels
  471. hmeta_theme <- list(
  472. coord_cartesian(xlim = c(0, 1.5)),
  473. scale_y_continuous(expand = c(0, 0)),
  474. ggdist::theme_tidybayes(),
  475. theme(panel.background = element_rect(fill = 'white'),
  476. plot.margin = margin(0.5, 0.5, 0.5, 0.5, 'cm'))
  477. )
  478. # Experiments 1 & 2
  479. p1 <- mcmcGroup %>%
  480. filter(Parameter == "mu_logMratio") %>%
  481. ggplot(aes(exp(value), fill = group)) +
  482. geom_histogram(binwidth = 0.03, alpha = 0.9, position = 'dodge') +
  483. scale_fill_manual(values = colpal) +
  484. labs(y = "Sample Count", x = expression(paste(mu["ratio"]))) +
  485. hmeta_theme +
  486. theme(legend.position = c(0.15, 0.70), legend.background = element_blank(),
  487. legend.title = element_blank())
  488. # Experiment 3: binocular, lateralized, monocular panels
  489. p1b <- make_vr_hist(c('CCD (VR)', 'NT (VR)'))
  490. p1c <- make_vr_hist(c('CCD (VR L)', 'NT (VR L)'))
  491. p1d <- make_vr_hist(c('CCD (VR M)', 'NT (VR M)'))
  492. p1; p1b; p1c; p1d
  493. # ── 7. INDIVIDUAL-LEVEL META-D' ESTIMATES -----------------------------
  494. # Compute individual d', meta-d', and M-ratio values using make_results()
  495. # and visualise spread across participants and presentation types.
  496. # NOTE: make_results() is slow; gate with do == 1 to re-run
  497. do_meta_d_d <- 0
  498. if (do_meta_d_d == 1) {
  499. source('HMeta-d-master/R/Function_metad_group.R')
  500. ccd_onl <- make_results(ONL_CCD_metaDat, 'CCD', 'ONL')
  501. ccd_lab <- make_results(LAB_CCD_metaDat, 'CCD', 'LAB')
  502. ccd_b <- make_results(VR_CCD_metaDat, 'CCD', 'BINO')
  503. ccd_l <- make_results(VR_CCD_metaDat_L, 'CCD', 'LAT')
  504. ccd_m <- make_results(VR_CCD_metaDat_M, 'CCD', 'MONO')
  505. nt_onl <- make_results(ONL_NT_metaDat, 'NT', 'ONL')
  506. nt_lab <- make_results(LAB_NT_metaDat, 'NT', 'LAB')
  507. nt_b <- make_results(VR_NT_metaDat, 'NT', 'BINO')
  508. nt_l <- make_results(VR_NT_metaDat_L, 'NT', 'LAT')
  509. nt_m <- make_results(VR_NT_metaDat_M, 'NT', 'MONO')
  510. meta_d_df <- rbind(ccd_onl, ccd_lab, ccd_b, ccd_l, ccd_m,
  511. nt_onl, nt_lab, nt_b, nt_l, nt_m)
  512. saveRDS(meta_d_df, 'HMetaDFit/Meta_d_d/meta_d_df.rdata')
  513. }
  514. meta_d_df <- readRDS('HMetaDFit/Meta_d_d/meta_d_df.rdata')
  515. # --- 7a. LM tests of group differences ---------
  516. # Online and Lab for M-ratio, meta-d', d'
  517. for (meas in c('Mratio', 'meta_d', 'd1')) {
  518. for (env in c('ONL', 'LAB', 'BINO')) {
  519. cat("\n", meas, env, "\n")
  520. print(summary(lm(mean ~ group,
  521. data = meta_d_df %>%
  522. filter(str_detect(name, meas),
  523. !name %in% c('mu_logMratio', 'sigma_logMratio', 'mu_meta_d', 'mu_d1'),
  524. type == env))))
  525. }
  526. }
  527. # --- 7b. Group-level summary plots -----
  528. d_prime_plot_1 <- ggplot(meta_d_df %>% filter(str_detect(name, "d1"), !name %in% c('mu_d1'), type %in% c('ONL','LAB')),
  529. aes(type, mean, fill = group))
  530. meta_d_plot_1 <- ggplot(meta_d_df %>% filter(str_detect(name, "meta_d"), !name %in% c('mu_meta_d'), type %in% c('ONL','LAB')),
  531. aes(type, mean, fill = group))
  532. m_ratio_plot_1 <- ggplot(meta_d_df %>% filter(str_detect(name, "Mratio"), !name %in% c('mu_logMratio', 'sigma_logMratio'), type %in% c('ONL','LAB')),
  533. aes(type, mean, fill = group))
  534. (d_prime_plot_1 | meta_d_plot_1 | m_ratio_plot_1) &
  535. stat_summary(shape = 21, size = 1.2) &
  536. coord_cartesian(ylim = c(0, 3)) &
  537. scale_y_continuous(expand = c(0,0), breaks = seq(0, 3, 0.5)) &
  538. scale_fill_manual(values = colpal) &
  539. theme_bw(base_size = 18) &
  540. theme(legend.position = 'none', panel.grid = element_blank(), axis.title = element_blank())
  541. d_prime_plot <- ggplot(meta_d_df %>% filter(str_detect(name, "d1"), !str_detect(name, "mu_d1"), type %in% c('BINO','MONO','LAT')),
  542. aes(type, abs(mean), fill = group))
  543. meta_d_plot <- ggplot(meta_d_df %>% filter(str_detect(name, "meta_d"), !str_detect(name, "mu_meta_d"), type %in% c('BINO','MONO','LAT')),
  544. aes(type, abs(mean), fill = group))
  545. m_ratio_plot <- ggplot(meta_d_df %>% filter(str_detect(name, "Mratio"), !str_detect(name, "mu_logMratio"), !str_detect(name, "sigma_logMratio"), type %in% c('BINO','MONO','LAT')),
  546. aes(type, abs(mean), fill = group))
  547. (d_prime_plot | meta_d_plot | m_ratio_plot) &
  548. stat_summary(geom = 'line', aes(group = group)) &
  549. stat_summary(shape = 21, size = 1.2) &
  550. coord_cartesian(ylim = c(0, 2)) &
  551. scale_y_continuous(expand = c(0,0), breaks = seq(0, 2, 0.5)) &
  552. scale_fill_manual(values = colpal_vr[1:2]) &
  553. theme_bw(base_size = 18) &
  554. theme(legend.position = 'none', panel.grid = element_blank(), axis.title = element_blank())
  555. # ── 8. MODEL RECOVERY & POSTERIOR PREDICTIVE CHECK ---------------------
  556. # Validate the HMeta-D model by recovering the fitted response counts from
  557. # the posterior and correlating them with the empirical counts.
  558. ## NOTE: outcomes are stochastic. Expect some deviation from published
  559. ## results if running again.
  560. # --- 8a. Run recovery -----
  561. do_recovery <- 0
  562. if(do_recovery==1){
  563. source('HMeta-d-master/R/Function_metad_group.R')
  564. fit_CCD_lab_rec <- run_recovery(LAB_CCD_metaDat, 'CCD', 'LAB')
  565. fit_NT_lab_rec <- run_recovery(LAB_NT_metaDat, 'NT', 'LAB')
  566. fit_CCD_onl_rec <- run_recovery(ONL_CCD_metaDat, 'CCD', 'ONL')
  567. fit_NT_onl_rec <- run_recovery(ONL_NT_metaDat, 'NT', 'ONL')
  568. fit_CCD_bin_rec <- run_recovery(VR_CCD_metaDat, 'CCD', 'BINO')
  569. fit_NT_bin_rec <- run_recovery(VR_NT_metaDat, 'NT', 'BINO')
  570. fit_CCD_lat_rec <- run_recovery(VR_CCD_metaDat_L,'CCD', 'LAT')
  571. fit_NT_lat_rec <- run_recovery(VR_NT_metaDat_L, 'NT', 'LAT')
  572. fit_CCD_mon_rec <- run_recovery(VR_CCD_metaDat_M,'CCD', 'MONO')
  573. fit_NT_mon_rec <- run_recovery(VR_NT_metaDat_M, 'NT', 'MONO')
  574. recovered_dfs <- rbind(fit_CCD_lab_rec[[1]], fit_NT_lab_rec[[1]],
  575. fit_CCD_onl_rec[[1]], fit_NT_onl_rec[[1]],
  576. fit_CCD_bin_rec[[1]], fit_NT_bin_rec[[1]]) %>%
  577. mutate(type = case_when(type == 'ONL' ~ 'EXP1',
  578. type == 'LAB' ~ 'EXP2',
  579. type == 'BINO' ~ 'EXP3',
  580. .default = type))
  581. saveRDS(recovered_dfs, 'HMetaDFit/Recovery/recovered_dfs.rdata')
  582. }
  583. recovered_dfs <- readRDS('HMetaDFit/Recovery/recovered_dfs.rdata')
  584. # Correlation of real vs recovered M-ratio per participant
  585. ggplot(recovered_dfs %>% filter(str_detect(name,'Mratio'), !str_detect(name,'log')),
  586. aes(mean_real, mean_rec)) +
  587. geom_point(alpha = 0.7) +
  588. geom_smooth(method = 'lm', colour = 'black', se = FALSE) +
  589. ggpubr::stat_cor(method = 'spearman', label.x.npc = 0.25, label.y.npc = 0,
  590. colour = 'firebrick', size = 5) +
  591. facet_wrap(group ~ type, scales = 'free', nrow = 2) +
  592. labs(x = 'Real', y = 'Recovered') +
  593. theme_minimal(base_size = 18) + theme(panel.grid = element_blank())
  594. # --- 8b. Posterior predictive check -----------
  595. # Compare recovered and real response count distributions across confidence bins
  596. post_pred_check <- data.frame(
  597. conf = rep(c(rep(c(6:1,-1:-6), 4), rep(c(2:1,-1:-2), 2)), 2),
  598. cond = rep(c(rep('EXP2',24), rep('EXP1',24), rep('EXP3',8)), 2),
  599. type = c(rep('Rec',56), rep('Real',56)),
  600. group = rep(c(rep(c(rep('CCD',12), rep('NT',12)), 2), c(rep('CCD',4), rep('NT',4))), 2),
  601. s1 = c(rowSums(fit_CCD_lab_rec[[2]][[1]]), rowSums(fit_NT_lab_rec[[2]][[1]]),
  602. rowSums(fit_CCD_onl_rec[[2]][[1]]), rowSums(fit_NT_onl_rec[[2]][[1]]),
  603. rowSums(fit_CCD_bin_rec[[2]][[1]]), rowSums(fit_NT_bin_rec[[2]][[1]]),
  604. rowSums(LAB_CCD_metaDat[[1]]), rowSums(LAB_NT_metaDat[[1]]),
  605. rowSums(ONL_CCD_metaDat[[1]]), rowSums(ONL_NT_metaDat[[1]]),
  606. rowSums(VR_CCD_metaDat[[1]]), rowSums(VR_NT_metaDat[[1]])),
  607. s2 = c(rowSums(fit_CCD_lab_rec[[2]][[2]]), rowSums(fit_NT_lab_rec[[2]][[2]]),
  608. rowSums(fit_CCD_onl_rec[[2]][[2]]), rowSums(fit_NT_onl_rec[[2]][[2]]),
  609. rowSums(fit_CCD_bin_rec[[2]][[2]]), rowSums(fit_NT_bin_rec[[2]][[2]]),
  610. rowSums(LAB_CCD_metaDat[[2]]), rowSums(LAB_NT_metaDat[[2]]),
  611. rowSums(ONL_CCD_metaDat[[2]]), rowSums(ONL_NT_metaDat[[2]]),
  612. rowSums(VR_CCD_metaDat[[2]]), rowSums(VR_NT_metaDat[[2]]))
  613. ) %>%
  614. pivot_longer(s1:s2, names_to = 'Dec', values_to = 'Count') %>%
  615. group_by(type, Dec, cond, group) %>%
  616. mutate(Count = Count / sum(Count))
  617. # Compute per-bin Pearson r between real and recovered counts for each experiment
  618. bin_correlations <- bind_rows(
  619. bind_rows(fit_to_count(fit_CCD_onl_rec[[2]][[1]],"Rec","EXP1","s1"),
  620. fit_to_count(ONL_CCD_metaDat[[1]], "Real","EXP1","s1"),
  621. fit_to_count(fit_NT_onl_rec[[2]][[2]], "Rec","EXP1","s2"),
  622. fit_to_count(ONL_NT_metaDat[[2]], "Real","EXP1","s2")) %>% mutate(exp='EXP1'),
  623. bind_rows(fit_to_count(fit_CCD_lab_rec[[2]][[1]],"Rec","EXP2","s1"),
  624. fit_to_count(LAB_CCD_metaDat[[1]], "Real","EXP2","s1"),
  625. fit_to_count(fit_NT_lab_rec[[2]][[2]], "Rec","EXP2","s2"),
  626. fit_to_count(LAB_NT_metaDat[[2]], "Real","EXP2","s2")) %>% mutate(exp='EXP2'),
  627. bind_rows(fit_to_count(fit_CCD_bin_rec[[2]][[1]],"Rec","EXP3","s1"),
  628. fit_to_count(VR_CCD_metaDat[[1]], "Real","EXP3","s1"),
  629. fit_to_count(fit_NT_bin_rec[[2]][[2]], "Rec","EXP3","s2"),
  630. fit_to_count(VR_NT_metaDat[[2]], "Real","EXP3","s2")) %>% mutate(exp='EXP3')
  631. ) %>%
  632. pivot_wider(names_from = Type, values_from = Count) %>%
  633. group_by(Bin, exp) %>%
  634. summarise(test_results = list(broom::tidy(cor.test(Real, Rec, method="pearson"))),
  635. .groups = 'drop') %>%
  636. unnest(test_results) %>%
  637. dplyr::select(Bin, exp, pearson_r = estimate, p_value = p.value,
  638. conf_low = conf.low, conf_high = conf.high) %>%
  639. mutate(conf_level = case_when(
  640. exp == 'EXP3' & Bin == 1 ~ 2, exp == 'EXP3' & Bin == 2 ~ 1,
  641. exp == 'EXP3' & Bin == 3 ~ -1, exp == 'EXP3' & Bin == 4 ~ -2,
  642. exp %in% c('EXP1','EXP2') & Bin %in% 1:6 ~ 7 - Bin,
  643. exp %in% c('EXP1','EXP2') & Bin %in% 7:12 ~ 6 - Bin,
  644. .default = as.numeric(Bin)),
  645. conf_level = as.numeric(conf_level))
  646. bin_correlations %>%
  647. filter(exp=='EXP3')
  648. post_pred_check %>%
  649. pivot_wider(names_from = type, values_from = Count) %>%
  650. ggplot(aes(x = Real, y = Rec, color = factor(conf))) +
  651. geom_point(alpha = 0.5) +
  652. geom_abline(intercept = 0, slope = 1, linetype = "dashed") + # Identity line
  653. facet_wrap(~cond) +
  654. labs(x = "Real",
  655. y = "Rec.")+
  656. theme_bw(base_size = 24)+
  657. theme(legend.position = 'top',
  658. legend.background = element_blank(),
  659. legend.title = element_blank(),
  660. panel.grid = element_blank(),
  661. strip.background.x = element_blank())
  662. # Plot raw distribution comparison + per-bin r (stacked)
  663. post_pred_check_plot <- ggplot(post_pred_check, aes(conf, Count, fill = type)) +
  664. geom_col(position = 'dodge') +
  665. scale_fill_manual(values = c('#2D3142','#F6AE2D')) +
  666. scale_x_continuous(breaks = c(6,1,-1,-6)) +
  667. scale_y_continuous(breaks = seq(0.1,0.5,0.1), expand = c(0,0)) +
  668. facet_wrap(group ~ cond, scales = 'free') +
  669. labs(y = 'Normalised Count') +
  670. theme_bw(base_size = 20) +
  671. theme(legend.position = 'none', panel.grid = element_blank(),
  672. strip.background.x = element_blank(), axis.title.x = element_blank())
  673. cor_plot_post_pred <- ggplot(bin_correlations, aes(conf_level, pearson_r)) +
  674. geom_col(fill = "black") +
  675. geom_hline(yintercept = 0.8, color = "red") +
  676. labs(x = "Confidence Bin (1=low, 6=high)", y = "Pearson r") +
  677. facet_wrap(~exp, scales = 'free') +
  678. scale_x_continuous(breaks = c(6,1,-1,-6)) +
  679. scale_y_continuous(breaks = c(0, 0.5, 1)) +
  680. theme_bw(base_size = 20) +
  681. theme(panel.grid = element_blank(), strip.background.x = element_blank(),
  682. strip.text.x = element_blank())
  683. (post_pred_check_plot / cor_plot_post_pred) & plot_layout(heights = c(5,1))
  684. # ── 9. PERMUTATION TESTS ON META-D' GROUP DIFFERENCES -------------------
  685. # We use a permutation procedure to test whether the observed NT − CCD difference
  686. # in mu_logMratio (and d', meta-d') exceeds the null distribution
  687. # generated by label-shuffling.
  688. # --- 9a. Extract observed group differences ----
  689. # Helper: pull exp(mu_logMratio) from a make_results() output dataframe
  690. pull_mu <- function(df, param = 'mu_logMratio') df %>% filter(name == param) %>% mutate(mean = exp(mean))
  691. pull_meta_d <- function(df, param = 'mu_meta_d') df %>% filter(name == param) %>% mutate(mean = abs(mean))
  692. pull_d1 <- function(df, param = 'mu_d1') df %>% filter(name == param) %>% mutate(mean = abs(mean))
  693. mu_log_exp1_NT <- pull_mu(Results_ONL_NT$Fit); mu_log_exp1_CCD <- pull_mu(Results_ONL_CCD$Fit)
  694. mu_log_exp2_NT <- pull_mu(Results_LAB_NT$Fit); mu_log_exp2_CCD <- pull_mu(Results_LAB_CCD$Fit)
  695. mu_log_exp3_NT <- pull_mu(Results_VR_NT$Fit); mu_log_exp3_CCD <- pull_mu(Results_VR_CCD$Fit)
  696. mu_log_exp3_NT_L <- pull_mu(Results_VR_NT_L$Fit); mu_log_exp3_CCD_L <- pull_mu(Results_VR_CCD_L$Fit)
  697. mu_log_exp3_NT_M <- pull_mu(Results_VR_NT_M$Fit); mu_log_exp3_CCD_M <- pull_mu(Results_VR_CCD_M$Fit)
  698. meta_d_log_exp1_NT <- pull_meta_d(nt_onl);meta_d_log_exp1_CCD <- pull_meta_d(ccd_onl);
  699. meta_d_log_exp2_NT <- pull_meta_d(nt_lab);meta_d_log_exp2_CCD <- pull_meta_d(ccd_lab);
  700. meta_d_log_exp3_NT <- pull_meta_d(nt_b); meta_d_log_exp3_CCD <- pull_meta_d(ccd_b);
  701. meta_d_log_exp3_NT_L <- pull_meta_d(nt_l); meta_d_log_exp3_CCD_L <- pull_meta_d(ccd_l);
  702. meta_d_log_exp3_NT_M <- pull_meta_d(nt_m); meta_d_log_exp3_CCD_M <- pull_meta_d(ccd_m);
  703. d1_log_exp1_NT <- pull_d1(nt_onl);d1_log_exp1_CCD <- pull_d1(ccd_onl);
  704. d1_log_exp2_NT <- pull_d1(nt_lab);d1_log_exp2_CCD <- pull_d1(ccd_lab);
  705. d1_log_exp3_NT <- pull_d1(nt_b); d1_log_exp3_CCD <- pull_d1(ccd_b);
  706. d1_log_exp3_NT_L <- pull_d1(nt_l); d1_log_exp3_CCD_L <- pull_d1(ccd_l);
  707. d1_log_exp3_NT_M <- pull_d1(nt_m); d1_log_exp3_CCD_M <- pull_d1(ccd_m);
  708. # Compute NT − CCD differences for M-ratio, d', and meta-d' in each context
  709. diffExp1 <- as.numeric(mu_log_exp1_NT[2] - mu_log_exp1_CCD[2])
  710. diffExp2 <- as.numeric(mu_log_exp2_NT[2] - mu_log_exp2_CCD[2])
  711. diffExp3 <- as.numeric(mu_log_exp3_NT[2] - mu_log_exp3_CCD[2])
  712. diffExp3_L <- as.numeric(mu_log_exp3_NT_L[2] - mu_log_exp3_CCD_L[2])
  713. diffExp3_M <- as.numeric(mu_log_exp3_NT_M[2] - mu_log_exp3_CCD_M[2])
  714. diffExp1_d1 <- as.numeric(d1_log_exp1_NT[2] - d1_log_exp1_CCD[2])
  715. diffExp2_d1 <- as.numeric(d1_log_exp2_NT[2] - d1_log_exp2_CCD[2])
  716. diffExp3_d1 <- as.numeric(d1_log_exp3_NT[2] - d1_log_exp3_CCD[2])
  717. diffExp3_L_d1 <- as.numeric(d1_log_exp3_NT_L[2] - d1_log_exp3_CCD_L[2])
  718. diffExp3_M_d1 <- as.numeric(d1_log_exp3_NT_M[2] - d1_log_exp3_CCD_M[2])
  719. diffExp1_metad <- as.numeric(meta_d_log_exp1_NT[2] - meta_d_log_exp1_CCD[2])
  720. diffExp2_metad <- as.numeric(meta_d_log_exp2_NT[2] - meta_d_log_exp2_CCD[2])
  721. diffExp3_metad <- as.numeric(meta_d_log_exp3_NT[2] - meta_d_log_exp3_CCD[2])
  722. diffExp3_L_metad <- as.numeric(meta_d_log_exp3_NT_L[2] - meta_d_log_exp3_CCD_L[2])
  723. diffExp3_M_metad <- as.numeric(meta_d_log_exp3_NT_M[2] - meta_d_log_exp3_CCD_M[2])
  724. cat("mu difference Lab:", diffExp1, "\n")
  725. cat("mu difference Onl:", diffExp2, "\n")
  726. cat("mu difference VR:", diffExp3, "\n")
  727. cat("mu difference VR-L:", diffExp3_L, "\n")
  728. cat("mu difference VR-M:", diffExp3_M, "\n")
  729. # --- 9b. Prepare data for permutation ----
  730. data_for_permute_exp1_CCD <- checkBothRDK %>% filter(group=='CCD (Online)') %>% dplyr::select(ID, Trial, conf, dotDirection, type, kmed, referenceSelection, group)
  731. data_for_permute_exp1_NT <- checkBothRDK %>% filter(group=='NT (Online)') %>% dplyr::select(ID, Trial, conf, dotDirection, type, kmed, referenceSelection, group)
  732. data_for_permute_exp2_CCD <- checkBothRDK %>% filter(group=='CCD (MRI)') %>% dplyr::select(ID, Trial, conf, dotDirection, type, kmed, referenceSelection, group)
  733. data_for_permute_exp2_NT <- checkBothRDK %>% filter(group=='NT (MRI)') %>% dplyr::select(ID, Trial, conf, dotDirection, type, kmed, referenceSelection, group)
  734. data_for_permute_exp3_CCD <- data_vr %>% filter(BinocularTrial == 1, Group=='CCD')
  735. data_for_permute_exp3_NT <- data_vr %>% filter(BinocularTrial == 1, Group=='NT')
  736. data_for_permute_exp3_CCD_L <- data_vr %>% filter(LateralizedTrial == 1, Group=='CCD')
  737. data_for_permute_exp3_NT_L <- data_vr %>% filter(LateralizedTrial == 1, Group=='NT')
  738. data_for_permute_exp3_CCD_M <- data_vr %>% filter(MonocularTrial == 1, Group=='CCD')
  739. data_for_permute_exp3_NT_M <- data_vr %>% filter(MonocularTrial == 1, Group=='NT')
  740. # --- 9c. Run permutation (slow; only re-run when needed) ----
  741. ## NOTE: outcomes are stochastic. Expect some deviation from published
  742. ## results if running again.
  743. do_perm <- 0
  744. if(do_perm==1){
  745. source('HMeta-d-master/R/Function_metad_group.R')
  746. nreps <- 500
  747. onl_CCDvsNTp <- permute_metad_group(data_for_permute_exp1_CCD, data_for_permute_exp1_NT, cores = 5, nreps = nreps);
  748. onl_CCDvsNTp_mrat <- permute_metad_group_mrat(data_for_permute_exp1_CCD, data_for_permute_exp1_NT, cores = 10, nreps = nreps);
  749. lab_CCDvsNTp <- permute_metad_group(data_for_permute_exp2_CCD, data_for_permute_exp2_NT, cores = 10, nreps = nreps);
  750. lab_CCDvsNTp_mrat <- permute_metad_group_mrat(data_for_permute_exp2_CCD, data_for_permute_exp2_NT, cores = 10, nreps = nreps);
  751. vr_CCDvsNTp <- permute_metad_group_vr(data_for_permute_exp3_CCD, data_for_permute_exp3_NT,cores = 10, nreps = nreps);
  752. vr_CCDvsNTp_L<- permute_metad_group_vr(data_for_permute_exp3_CCD_L, data_for_permute_exp3_NT_L,cores = 10, nreps = nreps);
  753. vr_CCDvsNTp_M<- permute_metad_group_vr(data_for_permute_exp3_CCD_M, data_for_permute_exp3_NT_M,cores = 10, nreps = nreps);
  754. saveRDS(onl_CCDvsNTp, 'HMetaDFit/Permutation/CCDvsNT_ONL.rdata')
  755. saveRDS(onl_CCDvsNTp_mrat, 'HMetaDFit/Permutation/CCDvsNT_ONL_mrat.rdata')
  756. saveRDS(lab_CCDvsNTp, 'HMetaDFit/Permutation/CCDvsNT_LAB.rdata')
  757. saveRDS(lab_CCDvsNTp_mrat, 'HMetaDFit/Permutation/CCDvsNT_LAB_mrat.rdata')
  758. saveRDS(vr_CCDvsNTp, 'HMetaDFit/Permutation/CCDvsNT_VR.rdata')
  759. saveRDS(vr_CCDvsNTp_L,'HMetaDFit/Permutation/CCDvsNT_VR_L.rdata')
  760. saveRDS(vr_CCDvsNTp_M,'HMetaDFit/Permutation/CCDvsNT_VR_M.rdata')
  761. }
  762. # --- 9d. Load permutation null distributions ----
  763. onl_CCDvsNTp <- readRDS('HMetaDFit/Permutation/CCDvsNT_ONL.rdata')
  764. onl_CCDvsNTp_mrat <- readRDS('HMetaDFit/Permutation/CCDvsNT_ONL_mrat.rdata')
  765. lab_CCDvsNTp <- readRDS('HMetaDFit/Permutation/CCDvsNT_LAB.rdata')
  766. lab_CCDvsNTp_mrat <- readRDS('HMetaDFit/Permutation/CCDvsNT_LAB_mrat.rdata')
  767. vr_CCDvsNTp <- readRDS('HMetaDFit/Permutation/CCDvsNT_VR.rdata')
  768. vr_CCDvsNTp_L <- readRDS('HMetaDFit/Permutation/CCDvsNT_VR_L.rdata')
  769. vr_CCDvsNTp_M <- readRDS('HMetaDFit/Permutation/CCDvsNT_VR_M.rdata')
  770. # --- 9e. Effect sizes and permutation p-values ------
  771. # perm_effect_stats() returns the p-value and U3 superiority index
  772. cat("\n--- Online (Exp 1) ---\n")
  773. perm_effect_stats(round(diffExp1, 2), onl_CCDvsNTp_mrat[,4])
  774. perm_effect_stats(round(diffExp1_d1, 2), onl_CCDvsNTp[,7])
  775. perm_effect_stats(round(diffExp1_metad, 2), onl_CCDvsNTp[,10])
  776. cat("\n--- Lab (Exp 2) ---\n")
  777. perm_effect_stats(round(diffExp2, 2), lab_CCDvsNTp_mrat[,4])
  778. perm_effect_stats(round(diffExp2_d1, 2), lab_CCDvsNTp[,7])
  779. perm_effect_stats(round(diffExp2_metad, 2), lab_CCDvsNTp[,10])
  780. cat("\n--- VR binocular (Exp 3) ---\n")
  781. perm_effect_stats(round(diffExp3, 2), vr_CCDvsNTp_mrat[,4])
  782. perm_effect_stats(round(diffExp3_d1, 2), vr_CCDvsNTp[,7])
  783. perm_effect_stats(round(diffExp3_meta_d, 2), vr_CCDvsNTp[,10])
  784. cat("\n--- VR lateralized (Exp 3) ---\n")
  785. perm_effect_stats(round(diffExp3_L, 2), vr_CCDvsNTp_mrat_L[,4])
  786. perm_effect_stats(round(diffExp3_L_d1, 2), vr_CCDvsNTp_L[,7])
  787. perm_effect_stats(round(diffExp3_L_metad, 2), vr_CCDvsNTp_L[,10])
  788. cat("\n--- VR monocular (Exp 3) ---\n")
  789. perm_effect_stats(round(diffExp3_M, 2), vr_CCDvsNTp_mrat_M[,4])
  790. perm_effect_stats(round(diffExp3_M_d1, 2), vr_CCDvsNTp_M[,7])
  791. perm_effect_stats(round(diffExp3_M_metad, 2), vr_CCDvsNTp_M[,10])
  792. # --- 9f. Permutation histogram plots -------
  793. # One panel per experiment, x-axis = null M-ratio difference, vertical line = observed
  794. p_p1 <- onl_CCDvsNTp %>% as.data.frame() %>% rename(Diff = 4) %>%
  795. ggplot(aes(Diff)) + geom_histogram(binwidth = 0.01) +
  796. labs(x = expression(paste("Randomised ",mu['ratio'])), y = 'Sample Count') +
  797. geom_label(x = diffExp1 + 0.07, y = 10, label = round(diffExp1,2), fill='#81D6E3', colour='white') +
  798. geom_vline(xintercept = diffExp1, color = '#81D6E3', size = 1.5) +
  799. theme_minimal() + theme(panel.grid = element_blank(), axis.text = element_text(size=24), axis.title = element_text(size=24))
  800. p_p2 <- lab_CCDvsNTp %>% as.data.frame() %>% rename(Diff = 4) %>%
  801. ggplot(aes(Diff)) + geom_histogram(binwidth = 0.01) +
  802. labs(x = expression(paste("Randomised ",mu['ratio'])), y = 'Sample Count') +
  803. geom_label(x = diffExp2 + 0.07, y = 10, label = round(diffExp2,2), fill='#475B5A', colour='white') +
  804. geom_vline(xintercept = diffExp2, color = '#475B5A', size = 1.5) +
  805. theme_minimal() + theme(panel.grid = element_blank(), axis.text = element_text(size=24), axis.title = element_text(size=24))
  806. p_p_b <- make_vr_perm_plot(vr_CCDvsNTp, diffExp3)
  807. p_p_l <- make_vr_perm_plot(vr_CCDvsNTp_L, diffExp3_L)
  808. p_p_m <- make_vr_perm_plot(vr_CCDvsNTp_M, diffExp3_M)
  809. (p_p1 | p_p2)
  810. (p_p_b | p_p_l | p_p_m)
  811. # ── 10. SUPPLEMENT: REACTION TIME ANALYSIS -----------------------------
  812. # Supplementary: RT distributions and LME tests by coherence and correctness.
  813. # Decision trials (conf == 0) and confidence trials (conf != 0) treated separately.
  814. library(ggridges)
  815. Experiment_1_both <- rbind(Experiment_1_CCD, Experiment_1_NT)
  816. Experiment_2_both <- rbind(Experiment_2_CCD, Experiment_2_NT)
  817. ## Non Confidence Trials -----
  818. ### Split by coherence ----
  819. Experiment_1_both %>%
  820. filter(type == 'main', kmed != 'kmed', ID != '5f20a850b6d5ed3d186bc3c3', conf == 0) %>%
  821. group_by(group, kmed, ID) %>%
  822. summarise(Subj_Acc = mean(correct)*100, Subj_RT = mean(RT, na.rm=TRUE), .groups='drop') %>%
  823. group_by(group, kmed) %>%
  824. summarise(Mean_Acc=mean(Subj_Acc), SD_Acc=sd(Subj_Acc),
  825. Mean_RT=mean(Subj_RT), SD_RT=sd(Subj_RT),
  826. SE_RT=SD_RT/sqrt(n()), n=n(), .groups='drop')
  827. Experiment_2_both %>%
  828. filter(type == 'main', kmed != 'kmed', conf == 0) %>%
  829. group_by(group, kmed, ID) %>%
  830. summarise(Subj_Acc = mean(correct)*100, Subj_RT = mean(RT, na.rm=TRUE), .groups='drop') %>%
  831. group_by(group, kmed) %>%
  832. summarise(Mean_Acc=mean(Subj_Acc), SD_Acc=sd(Subj_Acc),
  833. Mean_RT=mean(Subj_RT), SD_RT=sd(Subj_RT),
  834. SE_RT=SD_RT/sqrt(n()), n=n(), .groups='drop')
  835. run_rt_lmer(Experiment_1_both, 'Computer', conf_filter = 0) # Exp 1: decision RT × coherence
  836. run_rt_lmer(Experiment_2_both, 'MRI', conf_filter = 0) # Exp 2: decision RT × coherence
  837. ### Split by correctness ----
  838. Experiment_1_both %>%
  839. filter(type == 'main', kmed != 'kmed', ID != '5f20a850b6d5ed3d186bc3c3', conf == 0) %>%
  840. group_by(group, correct, ID) %>%
  841. summarise(Subj_Acc = mean(correct)*100, Subj_RT = mean(RT, na.rm=TRUE), .groups='drop') %>%
  842. group_by(group, correct) %>%
  843. summarise(Mean_Acc=mean(Subj_Acc), SD_Acc=sd(Subj_Acc),
  844. Mean_RT=mean(Subj_RT), SD_RT=sd(Subj_RT),
  845. SE_RT=SD_RT/sqrt(n()), n=n(), .groups='drop')
  846. Experiment_2_both %>%
  847. filter(type == 'main', kmed != 'kmed', conf == 0) %>%
  848. group_by(group, correct, ID) %>%
  849. summarise(Subj_Acc = mean(correct)*100, Subj_RT = mean(RT, na.rm=TRUE), .groups='drop') %>%
  850. group_by(group, correct) %>%
  851. summarise(Mean_Acc=mean(Subj_Acc), SD_Acc=sd(Subj_Acc),
  852. Mean_RT=mean(Subj_RT), SD_RT=sd(Subj_RT),
  853. SE_RT=SD_RT/sqrt(n()), n=n(), .groups='drop')
  854. lmerTest::lmer(log(RT) ~ CCD * correct + (1|ID),
  855. data = Experiment_1_both %>% filter(type=='main', conf==0, kmed!='kmed', environment=='Computer')) %>% summary()
  856. lmerTest::lmer(log(RT) ~ CCD * correct + (1|ID),
  857. data = Experiment_2_both %>% filter(type=='main', conf==0, kmed!='kmed', environment=='MRI')) %>% summary()
  858. # VR RT models by presentation type
  859. data_vr %>%
  860. filter(TrialType == 'Main') %>%
  861. group_by(Group, PresentationType, HighCoherenceTrialLabel, PID) %>%
  862. summarise(Subj_Acc = mean(Correct)*100, Subj_RT = mean(ReactionTime, na.rm=TRUE), .groups='drop') %>%
  863. group_by(Group, PresentationType, HighCoherenceTrialLabel) %>%
  864. summarise(Mean_Acc=mean(Subj_Acc), SD_Acc=sd(Subj_Acc),
  865. Mean_RT=mean(Subj_RT), SD_RT=sd(Subj_RT),
  866. SE_RT=SD_RT/sqrt(n()), n=n(), .groups='drop')
  867. for (ptype in c('Binocular','Lateralized','Monocular')) {
  868. cat("\n=== RT VR", ptype, "===\n")
  869. lmerTest::lmer(log(ReactionTime) ~ CCD * HighCoherenceTrialLabel + (1|PID),
  870. data = data_vr %>% filter(TrialType=='Main', PresentationType==ptype,
  871. HighCoherenceTrialLabel %in% c('x0.5','x2'))) %>% summary() %>% print()
  872. lmerTest::lmer(log(ReactionTime) ~ CCD * Correct + (1|PID),
  873. data = data_vr %>% filter(TrialType=='Main', PresentationType==ptype,
  874. HighCoherenceTrialLabel %in% c('x0.5','x2'))) %>% summary() %>% print()
  875. }
  876. # ── 11. SUPPLEMENT: CALIBRATION ASSESSMENT ---------------------------------
  877. # Show that k_med staircase converges similarly in both groups and environments.
  878. ## Non-VR Based ----
  879. ID_baseK <- checkBothRDK %>%
  880. filter(type == 'calibration', Trial > 100) %>%
  881. group_by(ID, CCD, environment) %>%
  882. summarise(baseK = median(coherence))
  883. allNonVRK <- ID_baseK %>%
  884. group_by(CCD, environment) %>%
  885. summarise(Coherence = mean(baseK),
  886. CoherenceSD = sd(baseK))
  887. allNonVRK
  888. # Test whether base k varied across computer or MRI versions of the same task
  889. t.test(x = (ID_baseK %>% filter(CCD == 1, environment == 'Computer'))$baseK,
  890. y = (ID_baseK %>% filter(CCD == 0, environment == 'Computer'))$baseK)
  891. t.test(x = (ID_baseK %>% filter(CCD == 1, environment == 'MRI'))$baseK,
  892. y = (ID_baseK %>% filter(CCD == 0, environment == 'MRI'))$baseK)
  893. # Base K between conditions ------
  894. coh_tog <- rbind(
  895. ID_baseK,
  896. data_vr %>%
  897. filter(TrialType == 'Main') %>%
  898. group_by(PID, Group) %>%
  899. summarise(Coherence = mean(BaseCoherence)) %>%
  900. mutate(environment = 'VR',
  901. Group = ifelse(Group=='CCD', 1, 0),
  902. PID = as.character(PID)) %>%
  903. dplyr::select(ID = PID, CCD = Group, environment, baseK = Coherence)
  904. )
  905. aov_tog_ccd <- aov(baseK ~ environment, coh_tog %>% filter(CCD==1))
  906. summary(aov_tog_ccd)
  907. TukeyHSD(aov_tog_ccd)
  908. aov_tog_nt <- aov(baseK ~ environment, coh_tog %>% filter(CCD==0))
  909. summary(aov_tog_nt)
  910. TukeyHSD(aov_tog_nt)
  911. ## VR Based -----
  912. allVRK <- data_vr %>%
  913. filter(TrialType == 'Main') %>%
  914. group_by(CCD, PresentationType) %>%
  915. summarise(Coherence = mean(BaseCoherence),
  916. CoherenceSD = sd(BaseCoherence))
  917. binoK <- data_vr %>%
  918. filter(BinocularTrial == 1, TrialType == 'Main') %>%
  919. group_by(PID, CCD) %>%
  920. summarise(Coherence = mean(BaseCoherence))
  921. monoK <- data_vr %>%
  922. filter(MonocularTrial == 1, TrialType == 'Main') %>%
  923. group_by(PID, CCD) %>%
  924. summarise(Coherence = mean(BaseCoherence))
  925. latK <- data_vr %>%
  926. filter(LateralizedTrial == 1, TrialType == 'Main') %>%
  927. group_by(PID, CCD) %>%
  928. summarise(Coherence = mean(BaseCoherence))
  929. # Test whether base k varied across presentations
  930. t.test((binoK %>% filter(CCD == 1))$Coherence, y = (binoK %>% filter(CCD == 0))$Coherence)
  931. t.test((monoK %>% filter(CCD == 1))$Coherence, y = (monoK %>% filter(CCD == 0))$Coherence)
  932. t.test((latK %>% filter(CCD == 1))$Coherence, y = (latK %>% filter(CCD == 0))$Coherence)
  933. #ANOVA
  934. coh_aov_ccd <- lmerTest::lmer(Coherence ~ PresentationType + (1|PID),
  935. data =
  936. data_vr %>%
  937. filter(TrialType == 'Main', Group == 'CCD') %>%
  938. group_by(PID, PresentationType) %>%
  939. mutate(PresentationType = factor(PresentationType, levels = c( 'Monocular', 'Binocular', 'Lateralized'))) %>%
  940. summarise(Coherence = mean(BaseCoherence)))
  941. anova(coh_aov_ccd)
  942. summary(coh_aov_ccd)
  943. coh_aov_nt <- lmerTest::lmer(Coherence ~ PresentationType + (1|PID),
  944. data =
  945. data_vr %>%
  946. filter(TrialType == 'Main', Group == 'NT') %>%
  947. group_by(PID, PresentationType) %>%
  948. summarise(Coherence = mean(BaseCoherence)))
  949. anova(coh_aov_nt)
  950. summary(coh_aov_nt)
  951. coh_aov_comb <- lmerTest::lmer(Coherence ~ PresentationType * Group + (1|PID),
  952. data =
  953. data_vr %>%
  954. filter(TrialType == 'Main') %>%
  955. group_by(PID, PresentationType, Group) %>%
  956. summarise(Coherence = mean(BaseCoherence)))
  957. anova(coh_aov_comb)
  958. summary(coh_aov_comb)
  959. # Combine all experiments into one long format
  960. check_coh_RDK <- rbind(
  961. Experiment_1_CCD, Experiment_2_CCD, Experiment_1_NT, Experiment_2_NT
  962. ) %>%
  963. mutate(prestype = NA, vr = 0) %>%
  964. dplyr::select(ID, Trial, coherence, group, prestype, type, vr) %>%
  965. rbind(
  966. data_vr %>%
  967. rename(ID = PID, group = Group, coherence = ActiveCoherence,
  968. prestype = PresentationType, type = TrialType) %>%
  969. mutate(vr = 1) %>%
  970. dplyr::select(ID, Trial, coherence, group, prestype, type, vr)
  971. )
  972. # Coherence trajectory plot: Experiments 1 & 2
  973. CCDcheck2 <-
  974. ggplot(check_coh_RDK %>% filter(type == 'calibration', vr == 0)) +
  975. stat_summary(aes(Trial, coherence, fill = group), geom='ribbon', alpha=0.1, colour=NA) +
  976. stat_summary(aes(Trial, coherence, colour = group), geom='line') +
  977. scale_color_manual(values = colpal) + scale_fill_manual(values = colpal) +
  978. labs(x = 'Trial', y = 'Coherence') +
  979. coord_cartesian(ylim = c(0, 0.5), xlim = c(0, 118)) +
  980. scale_x_continuous(expand = c(0,0)) +
  981. theme_minimal(base_size = 20) +
  982. theme(legend.position = 'top', panel.grid.minor = element_blank(), legend.title = element_blank())
  983. bino_check <- make_vr_cal_plot('Binocular', 'Bino.')
  984. lat_check <- make_vr_cal_plot('Lateralized','Lat.')
  985. mono_check <- make_vr_cal_plot('Monocular', 'Mono.')
  986. coh_vr_check_full <- ggarrange(bino_check, lat_check, mono_check, nrow = 1)
  987. CCDcheck2; coh_vr_check_full
  988. # Correlation: does mean confidence predict k_med? (supplementary validity check)
  989. check_coh_lm <- checkBothRDK %>%
  990. group_by(ID) %>%
  991. filter(type == 'main', conf != 0) %>%
  992. mutate(kmed_av = ifelse(kmed == 'kmed x 0.5', coherence*2, coherence/2),
  993. conf = mean(conf)) %>%
  994. dplyr::select(kmed_av, conf, ID, group) %>%
  995. distinct()
  996. ggplot(check_coh_lm, aes(conf, kmed_av, colour = group)) +
  997. geom_point() + geom_smooth(method = 'lm') +
  998. coord_cartesian(ylim = c(0, 0.5)) +
  999. scale_color_manual(values = colpal) +
  1000. stat_cor(show.legend = FALSE, method = 'spearman') +
  1001. stat_cor(show.legend = FALSE, aes(colour = NULL), data = check_coh_lm,
  1002. label.y.npc = 0.50, method = 'spearman') +
  1003. theme_bw(base_size = 20) +
  1004. theme(legend.position = 'left', legend.title = element_blank())
  1005. # ── 12. SUPPLEMENT: GROUP BEHAVIOUR FIGURES --------------------------
  1006. # Experiments 1 & 2: beeswarm accuracy and confidence plots with significance bars
  1007. tidy_acc <- tidyplot(
  1008. av_cor %>% mutate(
  1009. kmed = ifelse(kmed == 'x0.5', 'x0.5', 'x2.0'),
  1010. group = dplyr::recode(group, 'CCD (MRI)'='CCD\n(MRI)', 'NT (Online)'='NT\n(Online)',
  1011. 'CCD (Online)'='CCD\n(Online)', 'NT (MRI)'='NT\n(MRI)')),
  1012. x = group, y = av_cor, colour = kmed) %>%
  1013. add_data_points_beeswarm(alpha=0.1, cex=1) %>% add_sd_errorbar() %>% add_mean_dot(size=2) %>%
  1014. adjust_size(width=75, height=50) %>% adjust_x_axis_title(title='') %>%
  1015. adjust_y_axis_title(title='p(Correct)') %>% adjust_font(fontsize=15) %>%
  1016. adjust_legend_title(title='Coh.') %>% adjust_legend_position(position='top') +
  1017. stat_compare_means(paired=TRUE, hide.ns=TRUE, size=5, show.legend=FALSE,
  1018. label='p.signif', label.y=1.04) +
  1019. coord_cartesian(clip="off")
  1020. tidy_conf <- tidyplot(
  1021. av_co %>% mutate(
  1022. kmed = ifelse(kmed == 'x0.5', 'x0.5', 'x2.0'),
  1023. group = dplyr::recode(group, 'CCD (MRI)'='CCD\n(MRI)', 'NT (Online)'='NT\n(Online)',
  1024. 'CCD (Online)'='CCD\n(Online)', 'NT (MRI)'='NT\n(MRI)'),
  1025. ID = factor(ID)),
  1026. x = group, y = av_co, colour = kmed) %>%
  1027. add_data_points_beeswarm(alpha=0.1, cex=1) %>% add_sd_errorbar() %>% add_mean_dot(size=2) %>%
  1028. adjust_size(width=75, height=50) %>% adjust_x_axis_title(title='') %>%
  1029. adjust_y_axis_title(title='Confidence') %>% adjust_font(fontsize=15) %>%
  1030. adjust_legend_title(title='Coh.') %>% adjust_legend_position(position='top') +
  1031. stat_compare_means(paired=TRUE, hide.ns=TRUE, size=5, show.legend=FALSE,
  1032. label='p.signif', label.y=102) +
  1033. coord_cartesian(clip="off")
  1034. tidy_acc | tidy_conf
  1035. tidyplot_exp3_cor <- av_cor_vr %>%
  1036. mutate(kmed = as.character(kmed),
  1037. Pres = dplyr::recode(PresentationType, 'Monocular'='Mono.', 'Lateralized'='Lat.', 'Binocular'='Bino.'),
  1038. ID = factor(ID))
  1039. tidyplot_exp3_co <- av_co_vr %>%
  1040. mutate(kmed = as.character(kmed),
  1041. Pres = dplyr::recode(PresentationType, 'Monocular'='Mono.', 'Lateralized'='Lat.', 'Binocular'='Bino.'),
  1042. ID = factor(ID))
  1043. Cor <- make_exp3_plot(tidyplot_exp3_cor, 'av_cor', 'p(Correct)', 1.04)
  1044. Conf <- make_exp3_plot(tidyplot_exp3_co, 'av_co', 'Confidence', 0.75)
  1045. Cor / Conf
  1046. # ── 13. SUPPLEMENT: INTELLIGENCE CORRELATION -------------------------------
  1047. # ICAR scores from online platforms linked to individual meta-d' estimates to
  1048. # check whether metacognitive efficiency correlates with fluid intelligence.
  1049. ## NT ------
  1050. nt_join_ICAR_mrat <- read_csv('Data/ICAR_NT_mrat.csv')
  1051. nt_join_ICAR_meta_d <- read_csv('Data/ICAR_NT_meta_d.csv')
  1052. nt_join_ICAR_d1 <- read_csv('Data/ICAR_NT_d1.csv')
  1053. cor.test(nt_join_ICAR_mrat$mean, nt_join_ICAR_mrat$ICAR)
  1054. cor.test(nt_join_ICAR_meta_d$mean, nt_join_ICAR_meta_d$ICAR)
  1055. cor.test(nt_join_ICAR_d1$mean, nt_join_ICAR_d1$ICAR)
  1056. ## CCD ------
  1057. ccd_join_ICAR_mrat <- read_csv('Data/ICAR_CCD_mrat.csv')
  1058. ccd_join_ICAR_meta_d <- read_csv('Data/ICAR_CCD_meta_d.csv')
  1059. ccd_join_ICAR_d1 <- read_csv('Data/ICAR_CCD_d1.csv')
  1060. cor.test(ccd_join_ICAR_mrat$mean, ccd_join_ICAR_mrat$ICAR)
  1061. cor.test(ccd_join_ICAR_meta_d$mean, ccd_join_ICAR_meta_d$ICAR)
  1062. cor.test(ccd_join_ICAR_d1$mean, ccd_join_ICAR_d1$ICAR)
  1063. # ── 14. SUPPLEMENT: EXCLUSION ANALYSIS -------------------------------
  1064. ### Experiment 1 ------
  1065. run_lmer(correct ~ kmed + (1|ID), lmer_data_exc %>% filter(group == 'CCD (Online)'))
  1066. run_lmer(correct ~ kmed + (1|ID), lmer_data %>% filter(group == 'NT (Online)'))
  1067. run_lmer(correct ~ kmed * group + (1|ID), lmer_data_exc %>% filter(group %in% c('NT (Online)', 'CCD (Online)')))
  1068. run_lmer(conf ~ kmed + (1|ID), lmer_data_exc %>% filter(group == 'CCD (Online)'))
  1069. run_lmer(conf ~ kmed + (1|ID), lmer_data %>% filter(group == 'NT (Online)'))
  1070. run_lmer(conf ~ kmed * group + (1|ID), lmer_data_exc %>% filter(group %in% c('NT (Online)', 'CCD (Online)')))
  1071. ### Experiment 3 ---------
  1072. run_lmer(Correct ~ kmed * group + (1|ID), data_vr_tests %>% filter(!ID %in% c('4003', '4004', '4007', '4008')))
  1073. run_lmer(conf ~ kmed * group + (1|ID), data_vr_tests %>% filter(!ID %in% c('4003', '4004', '4007', '4008')))
  1074. #### BINO ----
  1075. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Binocular', !ID %in% c('4003', '4004', '4007', '4008')))
  1076. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Binocular'))
  1077. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Binocular', !ID %in% c('4003', '4004', '4007', '4008')))
  1078. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Binocular'))
  1079. #### LAT ----
  1080. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Lateralized', !ID %in% c('4003', '4004', '4007', '4008')))
  1081. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Lateralized'))
  1082. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Lateralized', !ID %in% c('4003', '4004', '4007', '4008')))
  1083. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Lateralized'))
  1084. #### MONO ----
  1085. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Monocular', !ID %in% c('4003', '4004', '4007', '4008')))
  1086. run_lmer(Correct ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Monocular'))
  1087. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='CCD', PresentationType=='Monocular', !ID %in% c('4003', '4004', '4007', '4008')))
  1088. run_lmer(conf ~ kmed + (1|ID), data_vr_tests %>% filter(group=='NT', PresentationType=='Monocular'))
  1089. #### Computational -----------------------------------------------------------
  1090. # Experiment 1
  1091. outputONL_CCD_exc <- readRDS('HMetaDFit/Mratio/ONL_CCD_exc.rdata')
  1092. Results_ONL_CCD_exc <- HMeta_post_clean(outputONL_CCD_exc, 'CCD (ONL)', F)
  1093. data_for_permute_exp1_CCD_exc <- checkBothRDK_w_exc %>% filter(group=='CCD (Online)') %>% dplyr::select(ID, Trial, conf, dotDirection, type, kmed, referenceSelection, group)
  1094. onl_CCDvsNTp_exc <- permute_metad_group_mrat(data_for_permute_exp1_CCD_exc, data_for_permute_exp1_NT, cores = 10, nreps = nreps);
  1095. #Experiment 3
  1096. data_for_permute_exp3_CCD_exc <- data_vr %>% filter(BinocularTrial == 1, Group=='CCD', !PID %in% exc_ids)
  1097. data_for_permute_exp3_CCD_L_exc<- data_vr %>% filter(LateralizedTrial == 1, Group=='CCD', !PID %in% exc_ids)
  1098. data_for_permute_exp3_CCD_M_exc<- data_vr %>% filter(MonocularTrial == 1, Group=='CCD', !PID %in% exc_ids)
  1099. VR_CCD_metaDat_exc <- HMetaGroupPrep_vr(data_vr %>% filter(BinocularTrial == 1, !PID %in% exc_ids), 'CCD')
  1100. outputVR_CCD_exc <- metad_group(nR_S1 = list(VR_CCD_metaDat_exc[[1]]), nR_S2 = list(VR_CCD_metaDat_exc[[2]]));
  1101. exc_output_clean <- HMeta_post_clean(outputVR_CCD_exc, 'CCD (VR)', F)
  1102. mu_log_vr_CCD_exc <- exc_output_clean$Fit %>% filter(name == 'mu_logMratio') %>% mutate(mean = exp(mean))
  1103. diffVR_exc = as.numeric(mu_log_exp3_NT[2] - mu_log_vr_CCD_exc[2])
  1104. vr_CCDvsNTp_exc <- permute_metad_group_vr(data_for_permute_exp3_CCD_exc, data_for_permute_exp3_NT,cores = 10, nreps = nreps);
  1105. perm_effect_stats(round(diffVR_exc, 2), vr_CCDvsNTp_exc[,4])

Analysis_Script_Commented_Github.r at commit 8cd58ed, under CC-BY-NC-SA-4.0 · at the source

Overview

Authors: J.M. Barnby1,2, R. Dean3,4, H. Burgess3,4, P. Dayan5,6, L.J. Richards3,4
  1. Institute of Psychiatry, Psychology and Neuroscience, King’s College London, UK
  2. Centre for AI and Machine Learning, Edith Cowan University, WA, Australia
  3. Department of Neuroscience, Washington University in St. Louis Medical School, St Louis, MO, USA
  4. The University of Queensland, Queensland Brain Institute, Brisbane, Australia
  5. Max Planck Institute for Biological Cybernetics, Tübingen, Germany
  6. University of Tübingen, Germany
Dates: published online 4 March 2026
Type: Preprint
License: CC BY
Identifiers: DOI 10.64898/2026.03.02.709173 · OpenAlex W7133771409
Open access: green, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), cognitive (subfield)
Methods: Statistics, Connectivity, Physiology & signal measures
Keywords: Agenesis of the corpus callosum, corpus callosum dysgenesis, perception, metacognition, social inference, abstract decision making
Topic: Fetal and Pediatric Neurological Disorders (Pediatrics, Perinatology and Child Health, Medicine), according to OpenAlex
Funding: Wellcome Trust (WT228268/Z/23/Z)
Citations: not cited yet (Europe PMC); 43 references in the paper

Abstract

The corpus callosum is the largest commissure in the mammalian brain and plays a major role in supporting cognitive processes required for adapting to complex environments. Individuals born with Corpus Callosum Dysgenesis (CCD), characterized by malformations of the corpus callosum, commonly exhibit deficits in social navigation, abstract problem-solving, decision-making, and self-awareness. Metacognition is a key cognitive process that supports these functions; however, it has yet to be tested comprehensively in individuals with CCD. Over three experiments, and three CCD cohorts, we tested the impact of this neurodevelopmental disorder on perceptual accuracy, confidence judgements, and metacognitive efficiency using two variants of a Random Dot Kinematogram task within lab, online, and VR conditions. We found that individuals with CCD typically displayed normal perceptual accuracy but failed to adjust their confidence judgements in line with task difficulty. Computational modelling revealed that this difference was explained by lower metacognitive efficiency driven by consistently lower metacognitive sensitivity. Together, these results provide evidence that the corpus callosum plays a crucial role in supporting metacognition.

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

Repository

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

Brain-Development-and-Disorders-Lab/Barnby_etal_2026_ccd_impairs_metacognition

License: CC-BY-NC-SA-4.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 8cd58ed054b724da8008732a7ec8978a3bea35d3, 30 July 2026
Languages: MATLAB (34), R (15)
Size: 115 files, 49 scripts
Software Heritage: not archived
Found in: “Data & Code”
Holds: README, license file, 1 notebook
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Statistics and Machine Learning Toolbox (14 files), tidyverse (10 files), broom (9 files), ggpubr (9 files), JAGS (9 files), reshape2 (9 files), lmerTest (2 files), brms (1 file), easystats (1 file), lme4 (1 file), Optimization Toolbox (1 file), Parallel Computing Toolbox (1 file), patchwork (1 file), Stan (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
51 files

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

Tracing map

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

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 49 scripts, each with its path and the digest of its content;
  • 4 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 & Code

Data and analysis code is freely available here: https://github.com/Brain-Development-and-Disorders-Lab/Barnby_etal_2026_ccd_impairs_metacognition

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

Recorded: type, journal, dates, 5 authors, 6 keywords, 1 funder, 39 references.

Cite

This paper

Barnby, J., Dean, R., Burgess, H., Dayan, P., & Richards, L. (2026). Corpus Callosum Dysgenesis impairs metacognition: evidence from multi-modality and multi-cohort replications. bioRxiv (preprint). https://doi.org/10.64898/2026.03.02.709173

BibTeX

@article{barnby2026corpus,
author = {Barnby, J.M. and Dean, R. and Burgess, H. and Dayan, P. and Richards, L.J.},
title = {{Corpus Callosum Dysgenesis impairs metacognition: evidence from multi-modality and multi-cohort replications}},
journal = {bioRxiv (preprint)},
year = {2026},
month = mar,
publisher = {bioRxiv},
issn = {2692-8205},
doi = {10.64898/2026.03.02.709173},
url = {https://doi.org/10.64898/2026.03.02.709173}
}

RIS

TY - JOUR
AU - Barnby, J.M.
AU - Dean, R.
AU - Burgess, H.
AU - Dayan, P.
AU - Richards, L.J.
TI - Corpus Callosum Dysgenesis impairs metacognition: evidence from multi-modality and multi-cohort replications
T2 - bioRxiv (preprint)
J2 - bioRxiv
PY - 2026
DA - 2026/03/04
SN - 2692-8205
PB - bioRxiv
DO - 10.64898/2026.03.02.709173
UR - https://doi.org/10.64898/2026.03.02.709173
ER -

CSL-JSON

{
"id": "10.64898/2026.03.02.709173",
"type": "article",
"title": "Corpus Callosum Dysgenesis impairs metacognition: evidence from multi-modality and multi-cohort replications",
"container-title": "bioRxiv (preprint)",
"author": [
{
"family": "Barnby",
"given": "J.M."
},
{
"family": "Dean",
"given": "R."
},
{
"family": "Burgess",
"given": "H."
},
{
"family": "Dayan",
"given": "P."
},
{
"family": "Richards",
"given": "L.J."
}
],
"container-title-short": "bioRxiv",
"DOI": "10.64898/2026.03.02.709173",
"ISSN": "2692-8205",
"publisher": "bioRxiv",
"URL": "https://doi.org/10.64898/2026.03.02.709173",
"issued": {
"date-parts": [
[
2026,
3,
4
]
]
}
}

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.1332 [code]
Decision processes underlying effort avoidance and their relationship with metacognition.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: JAGS, Optimization Toolbox, broom, 5 other tools, cognitive, 3 references
[2] doi:10.1016/j.celrep.2026.117505 [code]
Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice.
Journal: Cell reports
In common: JAGS, Stan, brms, 7 other tools
[3] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: Stan, brms, Optimization Toolbox, 7 other tools
[4] doi:10.1162/imag.a.1258 [code]
Non-specific increase in alpha power during a neurofeedback session targeting its downregulation.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Stan, brms, easystats, 7 other tools
[5] doi:10.1038/s44271-026-00431-w [code]
Alpha power increases spontaneously during a neurofeedback session.
Journal: Communications psychology
In common: Stan, brms, easystats, 6 other tools, cognitive
[6] doi:10.1002/hbm.70605 [code]
BrainEnrich: Revealing Biological Insights for Imaging-Derived Phenotypes Through Transcriptomic Enrichment.
Journal: Human brain mapping
In common: JAGS, easystats, broom, 6 other tools
[7] doi:10.1111/psyp.70285 [code]
Spontaneous Modulation of Alpha Power During a Neurofeedback Session Without Instructions.
Journal: Psychophysiology
In common: Stan, brms, easystats, 6 other tools
[8] doi:10.1016/j.isci.2026.116747 [code]
Age and loneliness relate to reduced trust learning and alterations in amygdala function.
Journal: iScience
In common: Stan, brms, easystats, 5 other tools, cognitive
[9] doi:10.1093/braincomms/fcag217 [code]
Investigation of stress hormones across multiday seizure cycles.
Journal: Brain communications
In common: brms, easystats, broom, 6 other tools
[10] doi:10.1186/s13229-026-00730-3 [code]
Predicting emotional valence in autism: a preregistered study in the Bayesian Brain framework.
Journal: Molecular autism
In common: Stan, brms, easystats, 5 other tools, cognitive

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.