OSCR

Decoding everyday levels of musical training from subcortical white-matter architecture.

Code ↔ Paper

7 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 7 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Results › Sample characteristics and analytical approach ↔ scripts/preprocessing/unpack_connectome_log_transformation.m, lines 51–91 · score 0.65 · log transformed, outcome variable, eITV, sex, structural connectivity, age
  2. [2] § Methods › Mediation and moderation models ↔ scripts/mediation/mediation_moderation.R, lines 154–227 · score 0.61 · full model, BCa, delta, bootstrapped, mediation, indirect
  3. [3] § Methods › Network-level inference ↔ scripts/analysis/network_statistics.py, lines 1903–1988 · score 0.56 · confidence intervals, prediction strength, network pairs, brain networks, FDR, permutation
  4. [4] § Methods › dMRI preprocessing and structural brain network construction ↔ mrtrix_pipeline_QC.sh, the whole file · a weak match · score 0.55 · mrtrix3, orientation, intensity, diffusion
  5. [5] § Methods › Mediation and moderation models ↔ scripts/mediation/mediation_moderation.R, lines 1–43 · score 0.55 · SubC, FIML, exogenous, SEM, mediation, moderation
  6. [6] § Methods › dMRI preprocessing and structural brain network construction ↔ mrtrix_pipeline_step_3.sh, the whole file · a weak match · score 0.54 · backtracking, cutoff, dynamic, FOD, seeding, algorithm
  7. [7] § Results › Sample characteristics and analytical approach ↔ scripts/analysis/network_statistics.py, lines 2147–2286 · score 0.51 · NBS Predict, brain networks, Destrieux, pipeline, structural connectivity, log

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 · 629 lines · 23 KB · no license · 2 matches

  1. # ============================================================
  2. # Subcortical (between) connectivity): Mediation vs Moderation
  3. # ------------------------------------------------------------
  4. # Model 1 (Mediation): F3 → SubC → MET (with IQ, Age, Gender covariates)
  5. # Model 1R (No-mediator): Remove MET ~ SubC for a true nested test (Δdf=1)
  6. # Model 2 (Moderation): MET ~ F3 + SubC + F3×SubC + IQ + Age + Gender
  7. # ------------------------------------------------------------
  8. # Key changes vs previous:
  9. # - F3 treated as EXOGENOUS (no regression on covariates)
  10. # - True nested mediation comparison (remove the b-path)
  11. # - Consistent preprocessing (z-score continuous; Gender as 0/1)
  12. # - Proper MI pooling with semTools::runMI (SEM) and mice::with/pool (lm)
  13. # - Report bootstrapped CIs for indirect when using FIML; pooled CIs otherwise
  14. # ============================================================
  15. suppressPackageStartupMessages({
  16. library(lavaan)
  17. library(semPlot)
  18. library(semTools) # runMI, lavTestLRT.mi, parameter pooling
  19. library(psych)
  20. library(ggplot2)
  21. library(dplyr)
  22. library(tidyr)
  23. library(corrplot)
  24. library(ggpubr)
  25. library(VIM) # missing data visualization
  26. library(mice) # multiple imputation
  27. library(interactions) # interact_plot (OLS moderation)
  28. library(lmtest) # coeftest
  29. library(sandwich) # vcovHC
  30. })
  31. # ------------------------
  32. # 0) Paths & I/O
  33. # ------------------------
  34. setwd(".")
  35. output_dir <- "results/mediation"
  36. if (!dir.exists(output_dir)) dir.create(output_dir, recursive = TRUE)
  37. # ------------------------
  38. # 1) Load data
  39. # ------------------------
  40. dat <- read.csv("results/connectivity_features/connectivity_mediation_features.csv")
  41. # ------------------------
  42. # 2) Select & rename
  43. # ------------------------
  44. dat0 <- dat %>%
  45. select(Age, Gender, TotalMETScore, F3, IQ, subcortical_between_mean_conn) %>%
  46. rename(
  47. MET = TotalMETScore,
  48. F3_S = F3, # lifetime training
  49. SubC = subcortical_between_mean_conn
  50. )
  51. # ------------------------
  52. # 3) Gender handling (binary 0/1)
  53. # ------------------------
  54. if (!is.numeric(dat0$Gender)) {
  55. if (is.factor(dat0$Gender) || is.character(dat0$Gender)) {
  56. lev <- unique(as.character(dat0$Gender))
  57. if (length(lev) == 2) {
  58. lev <- sort(lev)
  59. map <- setNames(c(0, 1), lev)
  60. dat0$Gender_num <- as.integer(map[as.character(dat0$Gender)])
  61. cat("Gender mapping (0/1):\n")
  62. print(map)
  63. } else {
  64. stop("Gender has more than 2 levels. Please recode to binary (0/1) before running.")
  65. }
  66. } else {
  67. stop("Gender is not numeric/factor/character. Please provide binary 0/1.")
  68. }
  69. } else {
  70. # Assume already 0/1 (prints summary for sanity)
  71. dat0$Gender_num <- dat0$Gender
  72. cat("Gender is numeric. Summary:\n")
  73. print(summary(dat0$Gender_num))
  74. }
  75. # ------------------------
  76. # 4) Missingness overview
  77. # ------------------------
  78. miss_tbl <- data.frame(
  79. Variable = c("Age", "Gender_num", "MET", "F3_S", "IQ", "SubC"),
  80. N_Missing = c(
  81. sum(is.na(dat0$Age)),
  82. sum(is.na(dat0$Gender_num)),
  83. sum(is.na(dat0$MET)),
  84. sum(is.na(dat0$F3_S)),
  85. sum(is.na(dat0$IQ)),
  86. sum(is.na(dat0$SubC))
  87. )
  88. )
  89. miss_tbl$Pct_Missing <- round(100 * miss_tbl$N_Missing / nrow(dat0), 1)
  90. write.csv(miss_tbl, file.path(output_dir, "missingness_summary.csv"), row.names = FALSE)
  91. print(miss_tbl)
  92. pdf(file.path(output_dir, "missing_data_pattern_SubC.pdf"), width = 8, height = 6)
  93. aggr(dat0[, c("Age","MET","F3_S","IQ","SubC")],
  94. col = c('navyblue','red'), numbers = TRUE, sortVars = TRUE,
  95. main = "Missing Data Pattern - SC-SM Analysis")
  96. dev.off()
  97. # Decision: impute if IQ missingness > 20%
  98. iq_missing_pct <- mean(is.na(dat0$IQ))*100
  99. use_imputation <- iq_missing_pct > 20
  100. cat("IQ missing %:", round(iq_missing_pct,1), "| Using multiple imputation:", use_imputation, "\n")
  101. # ------------------------
  102. # 5) Scale continuous variables (z-scores). Do NOT scale Gender.
  103. # ------------------------
  104. scale01 <- function(x) as.numeric(scale(x))
  105. datZ <- dat0 %>%
  106. mutate(
  107. Age_z = scale01(Age),
  108. MET_z = scale01(MET),
  109. F3_z = scale01(F3_S),
  110. IQ_z = scale01(IQ),
  111. SubC_z= scale01(SubC)
  112. )
  113. # ------------------------
  114. # 6) Descriptives & correlations
  115. # ------------------------
  116. desc <- psych::describe(datZ[, c("Age_z","MET_z","F3_z","IQ_z","SubC_z")])
  117. write.csv(round(desc, 3), file.path(output_dir, "descriptives.csv"))
  118. cor_mat <- cor(datZ[, c("MET","F3_S","IQ","Age","SubC")], use = "pairwise.complete.obs")
  119. write.csv(round(cor_mat, 3), file.path(output_dir, "correlation_matrix.csv"))
  120. pdf(file.path(output_dir, "correlation_matrix_SubC.pdf"), width = 7, height = 7)
  121. corrplot(cor_mat, method = "color", type = "upper", order = "hclust",
  122. tl.col = "black", tl.srt = 45, addCoef.col = "black", number.cex = .7)
  123. dev.off()
  124. # ------------------------
  125. # 7) Mediation models (F3 EXOGENOUS)
  126. # Full: SubC_z ~ a*F3_z + covs ; MET_z ~ b*SubC_z + cprime*F3_z + covs
  127. # No-M: SubC_z ~ a*F3_z + covs ; MET_z ~ ctot*F3_z + covs
  128. # Exogenous covariances among F3_z, IQ_z, Age_z, Gender_num
  129. # ------------------------
  130. # --- Model specifications ---
  131. # CRITICAL: Constrain MET_z ~~ 0*SubC_z in BOTH models to prevent
  132. # the reduced model from regaining the b-path via residual covariance
  133. model_med_full <- '
  134. # mediator & outcome regressions
  135. SubC_z ~ a*F3_z + IQ_z + Age_z + Gender_num
  136. MET_z ~ b*SubC_z + cprime*F3_z + IQ_z + Age_z + Gender_num
  137. # Constrain residual covariance to zero
  138. MET_z ~~ 0*SubC_z
  139. # effects
  140. indirect := a*b
  141. direct := cprime
  142. total := indirect + direct
  143. '
  144. model_med_nomedi <- '
  145. # mediator regression (same as full model)
  146. SubC_z ~ a*F3_z + IQ_z + Age_z + Gender_num
  147. # outcome regression WITHOUT b-path (SubC_z removed)
  148. MET_z ~ ctot*F3_z + IQ_z + Age_z + Gender_num
  149. # CRITICAL: Constrain residual covariance to zero (same as full model)
  150. MET_z ~~ 0*SubC_z
  151. '
  152. # ------------------------
  153. # 8) Fit mediation (FIML or MI pooled)
  154. # ------------------------
  155. set.seed(123)
  156. if (!use_imputation) {
  157. # ---- FIML + bootstrap CIs for indirect
  158. fit_full <- sem(
  159. model_med_full, data = datZ,
  160. missing = "fiml",
  161. meanstructure = TRUE,
  162. fixed.x = TRUE, # Treat exogenous vars as fixed
  163. auto.cov.y = FALSE, # Don't auto-add endogenous covariances
  164. se = "bootstrap", bootstrap = 5000
  165. )
  166. fit_nom <- sem(
  167. model_med_nomedi, data = datZ,
  168. missing = "fiml",
  169. meanstructure = TRUE,
  170. fixed.x = TRUE, # Treat exogenous vars as fixed
  171. auto.cov.y = FALSE, # Don't auto-add endogenous covariances
  172. se = "bootstrap", bootstrap = 5000
  173. )
  174. # LRT (true nested: removes b-path => Δdf=1)
  175. med_LRT <- anova(fit_full, fit_nom)
  176. write.csv(as.data.frame(med_LRT), file.path(output_dir, "mediation_LRT.csv"), row.names = FALSE)
  177. # Parameter estimates with bootstrapped CIs
  178. pe_full <- parameterEstimates(fit_full, standardized = TRUE,
  179. boot.ci.type = "bca.simple", ci = TRUE)
  180. write.csv(pe_full, file.path(output_dir, "mediation_param_estimates_full.csv"), row.names = FALSE)
  181. } else {
  182. # ---- Multiple Imputation
  183. # Build a mids object for variables used in SEM
  184. imp_vars <- datZ %>% select(MET_z, F3_z, SubC_z, IQ_z, Age_z, Gender_num)
  185. # Basic imputation spec (predictors default); m=20 for stability
  186. imp <- mice(imp_vars, m = 20, maxit = 20, printFlag = FALSE, seed = 123)
  187. fit_full <- runMI(model_med_full, fun = "sem", data = imp, se = "standard",
  188. estimator = "MLR", meanstructure = TRUE,
  189. fixed.x = TRUE, auto.cov.y = FALSE)
  190. fit_nom <- runMI(model_med_nomedi, fun = "sem", data = imp, se = "standard",
  191. estimator = "MLR", meanstructure = TRUE,
  192. fixed.x = TRUE, auto.cov.y = FALSE)
  193. # MI-pooled LRT (D3 method)
  194. med_LRT <- lavTestLRT.mi(fit_full, fit_nom)
  195. capture.output(med_LRT, file = file.path(output_dir, "mediation_LRT_MI.txt"))
  196. # MI-pooled parameter estimates (delta-method CIs)
  197. pe_full <- summary(fit_full, standardized = TRUE, ci = TRUE)
  198. capture.output(pe_full, file = file.path(output_dir, "mediation_param_estimates_full_MI.txt"))
  199. # NOTE: Bootstrapped CIs under MI require bootstrapLavaan.mi; omitted for brevity.
  200. }
  201. # ------------------------
  202. # 9) Extract mediation effects
  203. # ------------------------
  204. get_effect <- function(pe, label) {
  205. if (is.data.frame(pe)) {
  206. row <- pe[pe$label == label, , drop = FALSE]
  207. if (nrow(row) == 0) return(c(NA, NA, NA, NA))
  208. return(c(row$est, row$ci.lower, row$ci.upper, row$pvalue))
  209. } else {
  210. # pe is a "summary" print under MI; re-extract via parameterEstimates if possible
  211. # For MI, grab parameterEstimates from the fitted object:
  212. pe_tab <- tryCatch(parameterEstimates(fit_full, standardized = TRUE, ci = TRUE),
  213. error = function(e) NULL)
  214. if (is.null(pe_tab)) return(c(NA, NA, NA, NA))
  215. row <- pe_tab[pe_tab$label == label, , drop = FALSE]
  216. if (nrow(row) == 0) return(c(NA, NA, NA, NA))
  217. return(c(row$est, row$ci.lower, row$ci.upper, row$pvalue))
  218. }
  219. }
  220. indirect_vec <- get_effect(pe_full, "indirect")
  221. direct_vec <- get_effect(pe_full, "direct")
  222. total_vec <- get_effect(pe_full, "total")
  223. # ------------------------
  224. # 10) Moderation (OLS)
  225. # Compare base vs interaction to get ΔR² and test
  226. # If MI: pool coefficients and average ΔR² across imputations.
  227. # ------------------------
  228. form_base <- as.formula(MET_z ~ F3_z + IQ_z + SubC_z + Age_z + Gender_num)
  229. form_int <- as.formula(MET_z ~ F3_z + IQ_z + SubC_z + F3_z:SubC_z + Age_z + Gender_num)
  230. if (!use_imputation) {
  231. mod_data <- datZ[, all.vars(form_int)]
  232. cc <- complete.cases(mod_data)
  233. mod_data <- mod_data[cc, ]
  234. fit_base <- lm(form_base, data = mod_data)
  235. fit_int <- lm(form_int, data = mod_data)
  236. # ΔR² and ANOVA
  237. R2_base <- summary(fit_base)$r.squared
  238. R2_int <- summary(fit_int)$r.squared
  239. dR2 <- R2_int - R2_base
  240. anv <- anova(fit_base, fit_int)
  241. # Robust SEs (HC3) for interaction term
  242. robust_coefs <- coeftest(fit_int, vcov. = vcovHC(fit_int, type = "HC3"))
  243. int_row <- robust_coefs[rownames(robust_coefs) == "F3_z:SubC_z", , drop = FALSE]
  244. write.csv(broom::tidy(fit_int), file.path(output_dir, "moderation_lm_tidy.csv"), row.names = FALSE)
  245. write.csv(as.data.frame(anv), file.path(output_dir, "moderation_lm_anova_base_vs_int.csv"))
  246. robust_df <- data.frame(
  247. term = rownames(robust_coefs),
  248. estimate = robust_coefs[, "Estimate"],
  249. std.error = robust_coefs[, "Std. Error"],
  250. statistic = robust_coefs[, "t value"],
  251. p.value = robust_coefs[, "Pr(>|t|)"],
  252. row.names = NULL,
  253. check.names = FALSE
  254. )
  255. write.csv(robust_df, file.path(output_dir, "moderation_lm_robustHC3.csv"), row.names = FALSE)
  256. } else {
  257. # Moderation with MI pooling
  258. # Use the same mids (imp); fit base and interaction in each imputed dataset
  259. fit_base_mi <- with(data = imp, expr = lm(form_base))
  260. fit_int_mi <- with(data = imp, expr = lm(form_int))
  261. pool_int <- pool(fit_int_mi)
  262. # Extract pooled interaction coefficient
  263. summ_pool <- summary(pool_int, conf.int = TRUE)
  264. int_pool <- summ_pool[summ_pool$term == "F3_z:SubC_z", , drop = FALSE]
  265. # ΔR²: compute per-imputation and average
  266. R2_base_vec <- sapply(fit_base_mi$analyses, function(m) summary(m)$r.squared)
  267. R2_int_vec <- sapply(fit_int_mi$analyses, function(m) summary(m)$r.squared)
  268. dR2 <- mean(R2_int_vec - R2_base_vec, na.rm = TRUE)
  269. # Save
  270. write.csv(summ_pool, file.path(output_dir, "moderation_pooled_coefficients.csv"), row.names = FALSE)
  271. write.csv(data.frame(R2_base = R2_base_vec, R2_int = R2_int_vec,
  272. dR2 = R2_int_vec - R2_base_vec),
  273. file.path(output_dir, "moderation_R2_by_imputation.csv"), row.names = FALSE)
  274. }
  275. # ------------------------
  276. # 10.5) Conditional effects (simple slopes) of F3 at SC–SM = -1, 0, +1 SD
  277. # ------------------------
  278. simple_slopes_out <- file.path(output_dir, "moderation_simple_slopes.csv")
  279. if (!use_imputation) {
  280. # --- Single-model case (use robust HC3 variance) ---
  281. stopifnot(exists("fit_int"), inherits(fit_int, "lm"))
  282. b <- coef(fit_int)
  283. V <- sandwich::vcovHC(fit_int, type = "HC3") # robust variance-covariance
  284. # Helper: slope of F3 at SC level z, its SE using delta method, t, p
  285. slope_at <- function(z) {
  286. # beta_F3(z) = b_F3 + z * b_F3xSC
  287. beta <- unname(b["F3_z"] + z * b["F3_z:SubC_z"])
  288. var <- V["F3_z","F3_z"] +
  289. (z^2)*V["F3_z:SubC_z","F3_z:SubC_z"] +
  290. 2*z*V["F3_z","F3_z:SubC_z"]
  291. se <- sqrt(var)
  292. tval <- beta / se
  293. df <- fit_int$df.residual
  294. pval <- 2*pt(abs(tval) * -1, df = df) # two-sided
  295. c(beta = beta, SE = se, t = tval, p = pval)
  296. }
  297. levels <- c(-1, 0, 1)
  298. labs <- c("Low SC–SM (−1 SD)", "Mean SC–SM (0)", "High SC–SM (+1 SD)")
  299. M <- t(vapply(levels, slope_at, numeric(4L)))
  300. simple_df <- data.frame(
  301. Level = labs,
  302. `β (std.)` = round(M[, "beta"], 3),
  303. SE = round(M[, "SE"], 3),
  304. t = round(M[, "t"], 2),
  305. `p-value` = signif(M[, "p"], 3),
  306. check.names = FALSE
  307. )
  308. write.csv(simple_df, simple_slopes_out, row.names = FALSE)
  309. print(simple_df)
  310. } else {
  311. # --- MI case: compute per-imputation slopes then pool with Rubin's rules ---
  312. stopifnot(exists("fit_int_mi"))
  313. # Extract per-imputation coefficients and (model-based) vcov
  314. analyses <- fit_int_mi$analyses
  315. m <- length(analyses)
  316. if (m < 2) stop("Not enough imputations to pool simple slopes.")
  317. # Compute per-imputation Q (slope) and U (its variance) at each z
  318. levels <- c(-1, 0, 1)
  319. labs <- c("Low SC–SM (−1 SD)", "Mean SC–SM (0)", "High SC–SM (+1 SD)")
  320. pool_one_level <- function(z) {
  321. Q <- numeric(m) # estimates
  322. U <- numeric(m) # variances
  323. for (i in seq_len(m)) {
  324. bi <- coef(analyses[[i]])
  325. Vi <- vcov(analyses[[i]]) # model-based vcov; robust + MI is non-trivial
  326. beta <- unname(bi["F3_z"] + z * bi["F3_z:SubC_z"])
  327. var <- Vi["F3_z","F3_z"] +
  328. (z^2)*Vi["F3_z:SubC_z","F3_z:SubC_z"] +
  329. 2*z*Vi["F3_z","F3_z:SubC_z"]
  330. Q[i] <- beta
  331. U[i] <- var
  332. }
  333. # Rubin's rules
  334. Qbar <- mean(Q)
  335. Ubar <- mean(U)
  336. B <- var(Q) # between-imputation variance
  337. Tvar <- Ubar + (1 + 1/m) * B
  338. SE <- sqrt(Tvar)
  339. # df (Barnard & Rubin)
  340. lam <- (1 + 1/m) * B / Tvar
  341. df_old <- (m - 1) / (lam^2)
  342. # approximate df for Ubar (complete-data) – use average residual df
  343. df_complete <- mean(vapply(analyses, function(mod) mod$df.residual, numeric(1)))
  344. df <- 1 / ( (1/df_old) + (1/df_complete) )
  345. tval <- Qbar / SE
  346. pval <- 2 * pt(abs(tval) * -1, df = df)
  347. c(beta = Qbar, SE = SE, t = tval, p = pval)
  348. }
  349. M <- t(vapply(levels, pool_one_level, numeric(4L)))
  350. simple_df <- data.frame(
  351. Level = labs,
  352. `β (std.)` = round(M[, "beta"], 3),
  353. SE = round(M[, "SE"], 3),
  354. t = round(M[, "t"], 2),
  355. `p-value` = signif(M[, "p"], 3),
  356. check.names = FALSE
  357. )
  358. write.csv(simple_df, simple_slopes_out, row.names = FALSE)
  359. print(simple_df)
  360. }
  361. # Also write a short summary row with R²_base, R²_int, and ΔR² (if available)
  362. r2_summary_path <- file.path(output_dir, "moderation_model_comparison.csv")
  363. if (exists("R2_base") && exists("R2_int")) {
  364. write.csv(
  365. data.frame(
  366. R2_main_effects = round(R2_base, 3),
  367. R2_with_interaction = round(R2_int, 3),
  368. delta_R2 = round(R2_int - R2_base, 3)
  369. ),
  370. r2_summary_path,
  371. row.names = FALSE
  372. )
  373. } else if (exists("R2_base_vec") && exists("R2_int_vec")) {
  374. write.csv(
  375. data.frame(
  376. R2_main_effects_mean = round(mean(R2_base_vec), 3),
  377. R2_with_interaction_mean = round(mean(R2_int_vec), 3),
  378. delta_R2_mean = round(mean(R2_int_vec - R2_base_vec), 3)
  379. ),
  380. r2_summary_path,
  381. row.names = FALSE
  382. )
  383. }
  384. # ------------------------
  385. # 11) Visuals
  386. # ------------------------
  387. # Mediation path diagram
  388. pdf(file.path(output_dir, "sem_paths_mediation_full.pdf"), width = 10, height = 7)
  389. semPaths(fit_full, what = "std", layout = "tree2",
  390. edge.label.cex = .7, sizeMan = 7, sizeLat = 9,
  391. residuals = FALSE, nCharNodes = 0,
  392. title = TRUE, title.cex = 1.1)
  393. dev.off()
  394. # ---- Mediation effects plot: ALWAYS create a page ----
  395. # ---- Mediation effects plot: ALWAYS create a page ----
  396. pdf(file.path(output_dir, "mediation_effects_CI.pdf"), width = 8, height = 5)
  397. pe_ok <- (exists("pe_full") &&
  398. is.data.frame(pe_full) &&
  399. any(pe_full$label %in% c("indirect","direct","total")))
  400. if (pe_ok) {
  401. eff_tab <- subset(pe_full, label %in% c("indirect","direct","total"),
  402. select = c(label, est, ci.lower, ci.upper))
  403. # coerce to numeric
  404. eff_tab$est <- as.numeric(eff_tab$est)
  405. eff_tab$ci.lower <- as.numeric(eff_tab$ci.lower)
  406. eff_tab$ci.upper <- as.numeric(eff_tab$ci.upper)
  407. # ORDER: Direct, Indirect, Total
  408. eff_tab$label <- factor(eff_tab$label,
  409. levels = c("direct","indirect","total"),
  410. labels = c("Direct (c′)", "Indirect (a×b)", "Total"))
  411. g <- ggplot(eff_tab, aes(x = label, y = est, fill = label)) +
  412. geom_col() +
  413. geom_errorbar(aes(ymin = ci.lower, ymax = ci.upper), width = .2) +
  414. geom_hline(yintercept = 0, linetype = "dashed") +
  415. scale_fill_manual(values = c(
  416. "Direct (c′)" = "#d9d9d9", # light gray
  417. "Indirect (a×b)" = "#f2f2f2", # lightest gray
  418. "Total" = "#7a7a7a" # darker gray
  419. ), guide = "none") +
  420. theme_minimal() +
  421. labs(title = "Mediation effects with 95% CI",
  422. x = NULL, y = "Standardized effect (β)")
  423. print(g)
  424. } else {
  425. plot.new()
  426. text(.5, .6, "Mediation effects not available", cex = 1.2)
  427. text(.5, .45, "Expected labels in pe_full: indirect, direct, total.", cex = .9)
  428. }
  429. dev.off()
  430. # ---- Moderation interaction plot: ALWAYS create a page ----
  431. # ---- Moderation interaction plot: ALWAYS create a page ----
  432. pdf(file.path(output_dir, "moderation_interaction_plot.pdf"), width = 8, height = 6)
  433. if (!use_imputation && exists("fit_int") && inherits(fit_int, "lm")) {
  434. # ensure mod_data exists
  435. if (!exists("mod_data")) {
  436. mod_data <- tryCatch(model.frame(fit_int), error = function(e) NULL)
  437. }
  438. if (!is.null(mod_data)) {
  439. at_sc <- c(-1, 0, 1)
  440. x_min <- min(mod_data$F3_z, na.rm = TRUE)
  441. x_max <- max(mod_data$F3_z, na.rm = TRUE)
  442. # prediction grid for lines
  443. grid <- expand.grid(
  444. F3_z = seq(x_min, x_max, length.out = 120),
  445. SubC_z = at_sc,
  446. IQ_z = 0,
  447. Age_z = 0,
  448. Gender_num = 0
  449. )
  450. grid$MET_hat <- predict(fit_int, newdata = grid)
  451. grid$SC_lab <- factor(grid$SubC_z, levels = at_sc,
  452. labels = c("SC–SM: -1 SD", "SC–SM: 0", "SC–SM: +1 SD"))
  453. # group points to match the 3 SC–SM reference levels
  454. mod_data$SC_lab <- cut(mod_data$SubC_z,
  455. breaks = c(-Inf, -1, 1, Inf),
  456. labels = c("SC–SM: -1 SD", "SC–SM: 0", "SC–SM: +1 SD"),
  457. right = TRUE)
  458. g2 <- ggplot() +
  459. # observed points
  460. geom_point(data = mod_data, aes(x = F3_z, y = MET_z, shape = SC_lab),
  461. alpha = 0.35, size = 1, na.rm = TRUE) +
  462. # predicted lines
  463. geom_line(data = grid, aes(x = F3_z, y = MET_hat, linetype = SC_lab)) +
  464. theme_minimal() +
  465. labs(title = "Moderation: Simple slopes of F3 at SC–SM levels",
  466. x = "F3 (z)", y = "MET (z)",
  467. linetype = NULL, shape = NULL)
  468. print(g2)
  469. } else {
  470. plot.new()
  471. text(.5, .6, "Interaction model not available for plotting", cex = 1.2)
  472. text(.5, .45, "Could not reconstruct model.frame(fit_int).", cex = .9)
  473. }
  474. } else {
  475. plot.new()
  476. text(.5, .6, "Interaction model not available for plotting", cex = 1.2)
  477. text(.5, .45, "Check fit_int exists and use_imputation == FALSE.", cex = .9)
  478. }
  479. dev.off()
  480. # ------------------------
  481. # 12) Summaries & comparison table
  482. # ------------------------
  483. # LRT p-value
  484. if (!use_imputation) {
  485. LRT_p <- as.numeric(med_LRT$`Pr(>Chisq)`[2])
  486. } else {
  487. # lavTestLRT.mi prints an object; extract p-value if available, else NA
  488. LRT_p <- tryCatch({
  489. as.numeric(med_LRT["Pr(>Chisq)"][2, 1])
  490. }, error = function(e) NA)
  491. }
  492. # Moderation stats
  493. if (!use_imputation) {
  494. int_beta <- as.numeric(coef(fit_int)["F3_z:SubC_z"])
  495. int_p <- summary(fit_int)$coefficients["F3_z:SubC_z", "Pr(>|t|)"]
  496. # robust p (optional):
  497. int_p_HC3 <- tryCatch({
  498. as.numeric(coeftest(fit_int, vcov. = vcovHC(fit_int, type="HC3"))["F3_z:SubC_z","Pr(>|t|)"])
  499. }, error = function(e) NA)
  500. R2b <- R2_base; R2i <- R2_int
  501. } else {
  502. int_beta <- if (nrow(int_pool) == 0) NA else int_pool$estimate
  503. int_p <- if (nrow(int_pool) == 0) NA else int_pool$p.value
  504. int_p_HC3 <- NA
  505. R2b <- mean(R2_base_vec); R2i <- mean(R2_int_vec)
  506. }
  507. comp <- data.frame(
  508. Model = c("Mediation (full vs no-mediator)", "Moderation (interaction)"),
  509. Key_Test = c("LRT for b-path (Δdf=1)", "β(F3×SubC) & ΔR²"),
  510. Estimate_or_Δ = c(
  511. paste0("Indirect (a*b) = ", round(indirect_vec[1], 3),
  512. " [", round(indirect_vec[2], 3), ", ", round(indirect_vec[3], 3), "]"),
  513. paste0("β = ", round(int_beta, 3),
  514. ", ΔR² = ", round(R2i - R2b, 3))
  515. ),
  516. p_value = c(ifelse(is.na(LRT_p), "NA", formatC(LRT_p, format="f", digits=3)),
  517. ifelse(is.na(int_p), "NA", formatC(int_p, format="f", digits=3)))
  518. )
  519. write.csv(comp, file.path(output_dir, "comparison_table.csv"), row.names = FALSE)
  520. print(comp)
  521. # ------------------------
  522. # 13) Text report
  523. # ------------------------
  524. sink(file.path(output_dir, "report_SubC_reimpl.txt"))
  525. cat("SC–SM: Mediation vs Moderation (Re-implementation)\n")
  526. cat("===============================================\n\n")
  527. cat("F3 treated as EXOGENOUS. Covariates (IQ, Age, Gender) enter SubC and MET.\n")
  528. cat("Mediation comparison is TRUE NESTED (remove MET ~ SubC; Δdf=1).\n\n")
  529. cat("Missingness summary (%, N=", nrow(dat0), "):\n", sep = "")
  530. print(miss_tbl); cat("\n")
  531. cat("IQ missing %:", round(iq_missing_pct,1), " | Multiple Imputation:", use_imputation, "\n\n")
  532. cat("Mediation LRT p-value (full vs no-mediator): ", ifelse(is.na(LRT_p), "NA", LRT_p), "\n")
  533. cat("Indirect (a*b): est=", round(indirect_vec[1],3),
  534. " CI=[", round(indirect_vec[2],3), ", ", round(indirect_vec[3],3), "]",
  535. " p=", round(indirect_vec[4],3), "\n\n")
  536. cat("Moderation:\n")
  537. cat(" β(F3×SubC) =", round(int_beta,3),
  538. " | p =", ifelse(is.na(int_p), "NA", round(int_p,3)),
  539. " | ΔR² =", round(R2i - R2b, 3), "\n")
  540. if (!is.na(int_p_HC3)) cat(" (Robust HC3 p ≈", round(int_p_HC3,3), ")\n")
  541. cat("\nNotes:\n")
  542. cat("- Global SEM fit indices are not relied upon because the models are near-saturated.\n")
  543. cat("- Inference for mediation comes from indirect effect CI and nested LRT (Δdf=1).\n")
  544. cat("- Inference for moderation comes from the interaction term and ΔR².\n")
  545. cat("- MI pooling uses semTools::runMI for SEM and mice::with/pool for OLS.\n")
  546. sink()
  547. cat("\n=== DONE ===\nOutput in: ", normalizePath(output_dir), "\n", sep = "")

mediation_moderation.R at commit f21d2ab, no license · at the source

Overview

  1. Center for Music in the Brain, Department of Clinical Medicine, Aarhus University & The Royal Academy of Music Aarhus/Aalborg, Aarhus, Denmark
  2. School of Electronic Engineering and Computer Science, Queen Mary University of London, London, United Kingdom
  3. The MARCS Institute for Brain, Behaviour and Development, Western Sydney University, Penrith, Australia
  4. Department of Education, Psychology, Communication, University of Bari, Bari, Italy
  5. Language Acquisition and Language Processing Lab, Norwegian University of Science and Technology, Trondheim, Norway
  6. Consciousness Lab, Institute of Psychology, Jagiellonian University, Kraków, Poland
  7. Center of Functionally Integrative Neuroscience, Department of Clinical Medicine, Aarhus University, Aarhus, Denmark
  8. Neurobiology Research Unit, Copenhagen University Hospital Rigshospitalet, Copenhagen, Denmark
Journal: Imaging neuroscience (Cambridge, Mass.), volume 4, article IMAG.a.1325
Dates: received 25 February 2026; accepted 8 July 2026; published online 4 August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1162/imag.a.1325 · PMID 42559177 · PMCID PMC13440155 · OpenAlex W7168242511
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), systems (subfield)
Methods: Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, fMRI & imaging, Physiology & signal measures
Keywords: network neuroscience, machine learning, musical training, neuroplasticity, subcortical circuits, non-musicians
Topic: Neuroscience and Music Perception (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: European Cooperation in Science and Technology (COST) (CA18106); Center for Music in the Brain (MIB) is funded by The Lundbeck Foundation (R469-2024-1573); Center for Music in the Brain (MIB) is funded by Købmand Herman Sallings Fond
Citations: not cited yet (Europe PMC); 176 references in the paper

Abstract

Most people engage with music informally or through the standard school curriculum rather than through extensive professional practice, resulting in graded levels of accumulated musical training. In this study, we used cross-validated machine learning on whole-brain structural connectomes to investigate whether individual differences in brain wiring predict variation in musical training across 225 adults with none-to-moderate training levels. Connectomes were weighted by fiber bundle capacity (FBC), a quantitative diffusion MRI metric of structural connectivity. Musical training was quantified using the Gold-MSI Musical Training subscale (F3), a self-reported measure encompassing both formal and informal musical practice. Subcortical white-matter pathways linking thalamus, putamen, and pallidum with sensorimotor cortex carried the strongest predictive signal (r = 0.261; R2 = 0.068; p_perm = 0.003). Furthermore, this connectivity predicted the association between musical training and auditory perception skill, which was present in individuals with stronger subcortical–sensorimotor connectivity (+1 SD: β = 0.192, p = 0.004), but absent in those with weaker connectivity (−1 SD: β = −0.066, p = 0.488). Subcortical–sensorimotor connectivity is, therefore, the strongest structural correlate of graded musical training in non-specialists within the general population, identifying the structural neural substrate on which the training–skill association depends. This work warrants renewed targeted focus on subcortical circuits in models of musical expertise.

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 7 matches between paragraphs and lines of code.

MassimoLumaca/neuroARC

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: cc7dd46694fc0d44559bd9f2eacc16550512d4a3, 28 February 2024
Languages: Shell (8)
Size: 12 files, 8 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: MRtrix3 (7 files), FreeSurfer (2 files), FSL (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
9 files

MassimoLumaca/neuroTraining

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: f21d2abe75d365b4b22f3b18df0ef6e1ade7aab6, 27 January 2026
Languages: Python (3), R (1), MATLAB (1)
Size: 22 files, 5 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README, environment (requirements.txt)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: Matplotlib (3 files), NumPy (3 files), pandas (3 files), SciPy (3 files), seaborn (3 files), statsmodels (3 files), broom (1 file), ggplot2 (1 file), ggpubr (1 file), lavaan (1 file), Statistics and Machine Learning Toolbox (1 file), psych (1 file), tidyverse (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
6 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:

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

Data cannot be shared publicly as it is part of an ongoing study, and thus considered unanonymized under Danish law, even if pseudonymized. Researchers who wish to access the data may contact Dr Kristian Sandberg () at The Center of Functionally Integrative Neuroscience and/or The Technology Transfer Office () at Aarhus University, Denmark, to establish a data-sharing agreement. After permission has been given by the relevant ethics committee, data will be made available to the researchers for replication purposes. As the project is ongoing, sharing requests for other purposes will be evaluated on a case-by-case basis. Codes for reproducing diffusion-MRI analyses and results are available on GitHub: https://github.com/MassimoLumaca/neuroARC and https://github.com/MassimoLumaca/neuroTraining.

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

Recorded: type, language, journal, volume, pages, dates, 9 authors, 6 keywords, 3 funders, 173 references, 5 RRIDs.

Cite

This paper

Lumaca, M., Pearce, M. T., Keller, P. E., Vuust, P., Brattico, E., Baggio, G., Hat, K., Heggli, O. A., & Sandberg, K. (2026). Decoding everyday levels of musical training from subcortical white-matter architecture. Imaging neuroscience (Cambridge, Mass.), 4, IMAG.a.1325. https://doi.org/10.1162/imag.a.1325

BibTeX

@article{lumaca2026decoding,
author = {Lumaca, Massimo and Pearce, Marcus T. and Keller, Peter E. and Vuust, Peter and Brattico, Elvira and Baggio, Giosuè and Hat, Katarzyna and Heggli, Ole A. and Sandberg, Kristian},
title = {{Decoding everyday levels of musical training from subcortical white-matter architecture}},
journal = {Imaging neuroscience (Cambridge, Mass.)},
year = {2026},
month = aug,
volume = {4},
pages = {IMAG.a.1325},
publisher = {MIT Press},
issn = {2837-6056},
doi = {10.1162/imag.a.1325},
url = {https://doi.org/10.1162/imag.a.1325},
pmid = {42559177},
pmcid = {PMC13440155}
}

RIS

TY - JOUR
AU - Lumaca, Massimo
AU - Pearce, Marcus T.
AU - Keller, Peter E.
AU - Vuust, Peter
AU - Brattico, Elvira
AU - Baggio, Giosuè
AU - Hat, Katarzyna
AU - Heggli, Ole A.
AU - Sandberg, Kristian
TI - Decoding everyday levels of musical training from subcortical white-matter architecture
T2 - Imaging neuroscience (Cambridge, Mass.)
J2 - Imaging Neurosci (Camb)
PY - 2026
DA - 2026/08/04
VL - 4
SP - IMAG.a.1325
SN - 2837-6056
PB - MIT Press
DO - 10.1162/imag.a.1325
UR - https://doi.org/10.1162/imag.a.1325
LA - en
ER -

CSL-JSON

{
"id": "10.1162/imag.a.1325",
"type": "article-journal",
"title": "Decoding everyday levels of musical training from subcortical white-matter architecture",
"container-title": "Imaging neuroscience (Cambridge, Mass.)",
"author": [
{
"family": "Lumaca",
"given": "Massimo"
},
{
"family": "Pearce",
"given": "Marcus T."
},
{
"family": "Keller",
"given": "Peter E."
},
{
"family": "Vuust",
"given": "Peter"
},
{
"family": "Brattico",
"given": "Elvira"
},
{
"family": "Baggio",
"given": "Giosuè"
},
{
"family": "Hat",
"given": "Katarzyna"
},
{
"family": "Heggli",
"given": "Ole A."
},
{
"family": "Sandberg",
"given": "Kristian"
}
],
"container-title-short": "Imaging Neurosci (Camb)",
"volume": "4",
"page": "IMAG.a.1325",
"DOI": "10.1162/imag.a.1325",
"PMID": "42559177",
"PMCID": "PMC13440155",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://doi.org/10.1162/imag.a.1325",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
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.1038/s41467-026-73366-9 [code]
Cortical and white matter myelination proceed in concert during early infancy.
Journal: Nature communications
In common: MRtrix3, FreeSurfer, statsmodels, 5 other tools, 11 references
[2] doi:10.1126/sciadv.aec2348 [code]
Congenital blindness reduces myelination in human visual cortex.
Journal: Science advances
In common: lavaan, FreeSurfer, ggplot2, 3 other tools, structural MRI / diffusion, 13 references
[3] doi:10.1038/s41467-026-73072-6 [code]
Mapping the spatiotemporal continuum of structural connectivity development across the human connectome in youth.
Journal: Nature communications
In common: MRtrix3, psych, FreeSurfer, 5 other tools, structural MRI / diffusion, 10 references
[4] doi:10.1162/imag.a.1153 [code]
TRAMFIX: TRavelling Across Melbourne for FIXel-based analysis (a reproducibility and reliability study).
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: MRtrix3, FSL, statsmodels, 5 other tools, structural MRI / diffusion, 10 references
[5] doi:10.1038/s41467-026-71151-2 [code]
Common and distinct neural correlates of social interaction processing and theory of mind in narratives.
Journal: Nature communications
In common: MRtrix3, FreeSurfer, FSL, 7 other tools, 8 references
[6] doi:10.1016/j.nicl.2026.104012 [code]
Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.
Journal: NeuroImage. Clinical
In common: psych, FreeSurfer, ggpubr, 9 other tools, structural MRI / diffusion, 5 references
[7] doi:10.7554/elife.103097 [code]
Canonical neurodevelopmental trajectories of structural and functional manifolds.
Journal: eLife
In common: ggpubr, statsmodels, Statistics and Machine Learning Toolbox, 5 other tools, structural MRI / diffusion, 10 references
[8] doi:10.1038/s41467-026-71918-7 [code]
Developmental disinhibition gates language lateralization in childhood.
Journal: Nature communications
In common: MRtrix3, FreeSurfer, FSL, 5 other tools, 8 references
[9] doi:10.7554/elife.108109 [code]
Multimodal MRI marker of cognition explains the association between cognition and mental health in the UK Biobank.
Journal: eLife
In common: lavaan, psych, ggplot2, 5 other tools, structural MRI / diffusion, 8 references
[10] doi:10.1002/hbm.70562 [code]
Distinct Physiological Mechanisms Drive Grey Matter Plasticity in Complex Versus Simple Sequence Learning.
Journal: Human brain mapping
In common: MRtrix3, statsmodels, seaborn, 4 other tools, structural MRI / diffusion, 6 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.