Decoding everyday levels of musical training from subcortical white-matter architecture.
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] § 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] § Methods › Mediation and moderation models ↔ scripts/mediation/mediation_moderation.R, lines 154–227 · score 0.61 · full model, BCa, delta, bootstrapped, mediation, indirect
- [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] § 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] § Methods › Mediation and moderation models ↔ scripts/mediation/mediation_moderation.R, lines 1–43 · score 0.55 · SubC, FIML, exogenous, SEM, mediation, moderation
- [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] § 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
- # ============================================================
- # Subcortical (between) connectivity): Mediation vs Moderation
- # ------------------------------------------------------------
- # Model 1 (Mediation): F3 → SubC → MET (with IQ, Age, Gender covariates)
- # Model 1R (No-mediator): Remove MET ~ SubC for a true nested test (Δdf=1)
- # Model 2 (Moderation): MET ~ F3 + SubC + F3×SubC + IQ + Age + Gender
- # ------------------------------------------------------------
- # Key changes vs previous:
- # - F3 treated as EXOGENOUS (no regression on covariates)
- # - True nested mediation comparison (remove the b-path)
- # - Consistent preprocessing (z-score continuous; Gender as 0/1)
- # - Proper MI pooling with semTools::runMI (SEM) and mice::with/pool (lm)
- # - Report bootstrapped CIs for indirect when using FIML; pooled CIs otherwise
- # ============================================================
- suppressPackageStartupMessages({
- library(lavaan)
- library(semPlot)
- library(semTools) # runMI, lavTestLRT.mi, parameter pooling
- library(psych)
- library(ggplot2)
- library(dplyr)
- library(tidyr)
- library(corrplot)
- library(ggpubr)
- library(VIM) # missing data visualization
- library(mice) # multiple imputation
- library(interactions) # interact_plot (OLS moderation)
- library(lmtest) # coeftest
- library(sandwich) # vcovHC
- })
- # ------------------------
- # 0) Paths & I/O
- # ------------------------
- setwd(".")
- output_dir <- "results/mediation"
- if (!dir.exists(output_dir)) dir.create(output_dir, recursive = TRUE)
- # ------------------------
- # 1) Load data
- # ------------------------
- dat <- read.csv("results/connectivity_features/connectivity_mediation_features.csv")
- # ------------------------
- # 2) Select & rename
- # ------------------------
- dat0 <- dat %>%
- select(Age, Gender, TotalMETScore, F3, IQ, subcortical_between_mean_conn) %>%
- rename(
- MET = TotalMETScore,
- F3_S = F3, # lifetime training
- SubC = subcortical_between_mean_conn
- )
- # ------------------------
- # 3) Gender handling (binary 0/1)
- # ------------------------
- if (!is.numeric(dat0$Gender)) {
- if (is.factor(dat0$Gender) || is.character(dat0$Gender)) {
- lev <- unique(as.character(dat0$Gender))
- if (length(lev) == 2) {
- lev <- sort(lev)
- map <- setNames(c(0, 1), lev)
- dat0$Gender_num <- as.integer(map[as.character(dat0$Gender)])
- cat("Gender mapping (0/1):\n")
- print(map)
- } else {
- stop("Gender has more than 2 levels. Please recode to binary (0/1) before running.")
- }
- } else {
- stop("Gender is not numeric/factor/character. Please provide binary 0/1.")
- }
- } else {
- # Assume already 0/1 (prints summary for sanity)
- dat0$Gender_num <- dat0$Gender
- cat("Gender is numeric. Summary:\n")
- print(summary(dat0$Gender_num))
- }
- # ------------------------
- # 4) Missingness overview
- # ------------------------
- miss_tbl <- data.frame(
- Variable = c("Age", "Gender_num", "MET", "F3_S", "IQ", "SubC"),
- N_Missing = c(
- sum(is.na(dat0$Age)),
- sum(is.na(dat0$Gender_num)),
- sum(is.na(dat0$MET)),
- sum(is.na(dat0$F3_S)),
- sum(is.na(dat0$IQ)),
- sum(is.na(dat0$SubC))
- )
- )
- miss_tbl$Pct_Missing <- round(100 * miss_tbl$N_Missing / nrow(dat0), 1)
- write.csv(miss_tbl, file.path(output_dir, "missingness_summary.csv"), row.names = FALSE)
- print(miss_tbl)
- pdf(file.path(output_dir, "missing_data_pattern_SubC.pdf"), width = 8, height = 6)
- aggr(dat0[, c("Age","MET","F3_S","IQ","SubC")],
- col = c('navyblue','red'), numbers = TRUE, sortVars = TRUE,
- main = "Missing Data Pattern - SC-SM Analysis")
- dev.off()
- # Decision: impute if IQ missingness > 20%
- iq_missing_pct <- mean(is.na(dat0$IQ))*100
- use_imputation <- iq_missing_pct > 20
- cat("IQ missing %:", round(iq_missing_pct,1), "| Using multiple imputation:", use_imputation, "\n")
- # ------------------------
- # 5) Scale continuous variables (z-scores). Do NOT scale Gender.
- # ------------------------
- scale01 <- function(x) as.numeric(scale(x))
- datZ <- dat0 %>%
- mutate(
- Age_z = scale01(Age),
- MET_z = scale01(MET),
- F3_z = scale01(F3_S),
- IQ_z = scale01(IQ),
- SubC_z= scale01(SubC)
- )
- # ------------------------
- # 6) Descriptives & correlations
- # ------------------------
- desc <- psych::describe(datZ[, c("Age_z","MET_z","F3_z","IQ_z","SubC_z")])
- write.csv(round(desc, 3), file.path(output_dir, "descriptives.csv"))
- cor_mat <- cor(datZ[, c("MET","F3_S","IQ","Age","SubC")], use = "pairwise.complete.obs")
- write.csv(round(cor_mat, 3), file.path(output_dir, "correlation_matrix.csv"))
- pdf(file.path(output_dir, "correlation_matrix_SubC.pdf"), width = 7, height = 7)
- corrplot(cor_mat, method = "color", type = "upper", order = "hclust",
- tl.col = "black", tl.srt = 45, addCoef.col = "black", number.cex = .7)
- dev.off()
- # ------------------------
- # 7) Mediation models (F3 EXOGENOUS)
- # Full: SubC_z ~ a*F3_z + covs ; MET_z ~ b*SubC_z + cprime*F3_z + covs
- # No-M: SubC_z ~ a*F3_z + covs ; MET_z ~ ctot*F3_z + covs
- # Exogenous covariances among F3_z, IQ_z, Age_z, Gender_num
- # ------------------------
- # --- Model specifications ---
- # CRITICAL: Constrain MET_z ~~ 0*SubC_z in BOTH models to prevent
- # the reduced model from regaining the b-path via residual covariance
- model_med_full <- '
- # mediator & outcome regressions
- SubC_z ~ a*F3_z + IQ_z + Age_z + Gender_num
- MET_z ~ b*SubC_z + cprime*F3_z + IQ_z + Age_z + Gender_num
- # Constrain residual covariance to zero
- MET_z ~~ 0*SubC_z
- # effects
- indirect := a*b
- direct := cprime
- total := indirect + direct
- '
- model_med_nomedi <- '
- # mediator regression (same as full model)
- SubC_z ~ a*F3_z + IQ_z + Age_z + Gender_num
- # outcome regression WITHOUT b-path (SubC_z removed)
- MET_z ~ ctot*F3_z + IQ_z + Age_z + Gender_num
- # CRITICAL: Constrain residual covariance to zero (same as full model)
- MET_z ~~ 0*SubC_z
- '
- # ------------------------
- # 8) Fit mediation (FIML or MI pooled)
- # ------------------------
- set.seed(123)
- if (!use_imputation) {
- # ---- FIML + bootstrap CIs for indirect
- fit_full <- sem(
- model_med_full, data = datZ,
- missing = "fiml",
- meanstructure = TRUE,
- fixed.x = TRUE, # Treat exogenous vars as fixed
- auto.cov.y = FALSE, # Don't auto-add endogenous covariances
- se = "bootstrap", bootstrap = 5000
- )
- fit_nom <- sem(
- model_med_nomedi, data = datZ,
- missing = "fiml",
- meanstructure = TRUE,
- fixed.x = TRUE, # Treat exogenous vars as fixed
- auto.cov.y = FALSE, # Don't auto-add endogenous covariances
- se = "bootstrap", bootstrap = 5000
- )
- # LRT (true nested: removes b-path => Δdf=1)
- med_LRT <- anova(fit_full, fit_nom)
- write.csv(as.data.frame(med_LRT), file.path(output_dir, "mediation_LRT.csv"), row.names = FALSE)
- # Parameter estimates with bootstrapped CIs
- pe_full <- parameterEstimates(fit_full, standardized = TRUE,
- boot.ci.type = "bca.simple", ci = TRUE)
- write.csv(pe_full, file.path(output_dir, "mediation_param_estimates_full.csv"), row.names = FALSE)
- } else {
- # ---- Multiple Imputation
- # Build a mids object for variables used in SEM
- imp_vars <- datZ %>% select(MET_z, F3_z, SubC_z, IQ_z, Age_z, Gender_num)
- # Basic imputation spec (predictors default); m=20 for stability
- imp <- mice(imp_vars, m = 20, maxit = 20, printFlag = FALSE, seed = 123)
- fit_full <- runMI(model_med_full, fun = "sem", data = imp, se = "standard",
- estimator = "MLR", meanstructure = TRUE,
- fixed.x = TRUE, auto.cov.y = FALSE)
- fit_nom <- runMI(model_med_nomedi, fun = "sem", data = imp, se = "standard",
- estimator = "MLR", meanstructure = TRUE,
- fixed.x = TRUE, auto.cov.y = FALSE)
- # MI-pooled LRT (D3 method)
- med_LRT <- lavTestLRT.mi(fit_full, fit_nom)
- capture.output(med_LRT, file = file.path(output_dir, "mediation_LRT_MI.txt"))
- # MI-pooled parameter estimates (delta-method CIs)
- pe_full <- summary(fit_full, standardized = TRUE, ci = TRUE)
- capture.output(pe_full, file = file.path(output_dir, "mediation_param_estimates_full_MI.txt"))
- # NOTE: Bootstrapped CIs under MI require bootstrapLavaan.mi; omitted for brevity.
- }
- # ------------------------
- # 9) Extract mediation effects
- # ------------------------
- get_effect <- function(pe, label) {
- if (is.data.frame(pe)) {
- row <- pe[pe$label == label, , drop = FALSE]
- if (nrow(row) == 0) return(c(NA, NA, NA, NA))
- return(c(row$est, row$ci.lower, row$ci.upper, row$pvalue))
- } else {
- # pe is a "summary" print under MI; re-extract via parameterEstimates if possible
- # For MI, grab parameterEstimates from the fitted object:
- pe_tab <- tryCatch(parameterEstimates(fit_full, standardized = TRUE, ci = TRUE),
- error = function(e) NULL)
- if (is.null(pe_tab)) return(c(NA, NA, NA, NA))
- row <- pe_tab[pe_tab$label == label, , drop = FALSE]
- if (nrow(row) == 0) return(c(NA, NA, NA, NA))
- return(c(row$est, row$ci.lower, row$ci.upper, row$pvalue))
- }
- }
- indirect_vec <- get_effect(pe_full, "indirect")
- direct_vec <- get_effect(pe_full, "direct")
- total_vec <- get_effect(pe_full, "total")
- # ------------------------
- # 10) Moderation (OLS)
- # Compare base vs interaction to get ΔR² and test
- # If MI: pool coefficients and average ΔR² across imputations.
- # ------------------------
- form_base <- as.formula(MET_z ~ F3_z + IQ_z + SubC_z + Age_z + Gender_num)
- form_int <- as.formula(MET_z ~ F3_z + IQ_z + SubC_z + F3_z:SubC_z + Age_z + Gender_num)
- if (!use_imputation) {
- mod_data <- datZ[, all.vars(form_int)]
- cc <- complete.cases(mod_data)
- mod_data <- mod_data[cc, ]
- fit_base <- lm(form_base, data = mod_data)
- fit_int <- lm(form_int, data = mod_data)
- # ΔR² and ANOVA
- R2_base <- summary(fit_base)$r.squared
- R2_int <- summary(fit_int)$r.squared
- dR2 <- R2_int - R2_base
- anv <- anova(fit_base, fit_int)
- # Robust SEs (HC3) for interaction term
- robust_coefs <- coeftest(fit_int, vcov. = vcovHC(fit_int, type = "HC3"))
- int_row <- robust_coefs[rownames(robust_coefs) == "F3_z:SubC_z", , drop = FALSE]
- write.csv(broom::tidy(fit_int), file.path(output_dir, "moderation_lm_tidy.csv"), row.names = FALSE)
- write.csv(as.data.frame(anv), file.path(output_dir, "moderation_lm_anova_base_vs_int.csv"))
- robust_df <- data.frame(
- term = rownames(robust_coefs),
- estimate = robust_coefs[, "Estimate"],
- std.error = robust_coefs[, "Std. Error"],
- statistic = robust_coefs[, "t value"],
- p.value = robust_coefs[, "Pr(>|t|)"],
- row.names = NULL,
- check.names = FALSE
- )
- write.csv(robust_df, file.path(output_dir, "moderation_lm_robustHC3.csv"), row.names = FALSE)
- } else {
- # Moderation with MI pooling
- # Use the same mids (imp); fit base and interaction in each imputed dataset
- fit_base_mi <- with(data = imp, expr = lm(form_base))
- fit_int_mi <- with(data = imp, expr = lm(form_int))
- pool_int <- pool(fit_int_mi)
- # Extract pooled interaction coefficient
- summ_pool <- summary(pool_int, conf.int = TRUE)
- int_pool <- summ_pool[summ_pool$term == "F3_z:SubC_z", , drop = FALSE]
- # ΔR²: compute per-imputation and average
- R2_base_vec <- sapply(fit_base_mi$analyses, function(m) summary(m)$r.squared)
- R2_int_vec <- sapply(fit_int_mi$analyses, function(m) summary(m)$r.squared)
- dR2 <- mean(R2_int_vec - R2_base_vec, na.rm = TRUE)
- # Save
- write.csv(summ_pool, file.path(output_dir, "moderation_pooled_coefficients.csv"), row.names = FALSE)
- write.csv(data.frame(R2_base = R2_base_vec, R2_int = R2_int_vec,
- dR2 = R2_int_vec - R2_base_vec),
- file.path(output_dir, "moderation_R2_by_imputation.csv"), row.names = FALSE)
- }
- # ------------------------
- # 10.5) Conditional effects (simple slopes) of F3 at SC–SM = -1, 0, +1 SD
- # ------------------------
- simple_slopes_out <- file.path(output_dir, "moderation_simple_slopes.csv")
- if (!use_imputation) {
- # --- Single-model case (use robust HC3 variance) ---
- stopifnot(exists("fit_int"), inherits(fit_int, "lm"))
- b <- coef(fit_int)
- V <- sandwich::vcovHC(fit_int, type = "HC3") # robust variance-covariance
- # Helper: slope of F3 at SC level z, its SE using delta method, t, p
- slope_at <- function(z) {
- # beta_F3(z) = b_F3 + z * b_F3xSC
- beta <- unname(b["F3_z"] + z * b["F3_z:SubC_z"])
- var <- V["F3_z","F3_z"] +
- (z^2)*V["F3_z:SubC_z","F3_z:SubC_z"] +
- 2*z*V["F3_z","F3_z:SubC_z"]
- se <- sqrt(var)
- tval <- beta / se
- df <- fit_int$df.residual
- pval <- 2*pt(abs(tval) * -1, df = df) # two-sided
- c(beta = beta, SE = se, t = tval, p = pval)
- }
- levels <- c(-1, 0, 1)
- labs <- c("Low SC–SM (−1 SD)", "Mean SC–SM (0)", "High SC–SM (+1 SD)")
- M <- t(vapply(levels, slope_at, numeric(4L)))
- simple_df <- data.frame(
- Level = labs,
- `β (std.)` = round(M[, "beta"], 3),
- SE = round(M[, "SE"], 3),
- t = round(M[, "t"], 2),
- `p-value` = signif(M[, "p"], 3),
- check.names = FALSE
- )
- write.csv(simple_df, simple_slopes_out, row.names = FALSE)
- print(simple_df)
- } else {
- # --- MI case: compute per-imputation slopes then pool with Rubin's rules ---
- stopifnot(exists("fit_int_mi"))
- # Extract per-imputation coefficients and (model-based) vcov
- analyses <- fit_int_mi$analyses
- m <- length(analyses)
- if (m < 2) stop("Not enough imputations to pool simple slopes.")
- # Compute per-imputation Q (slope) and U (its variance) at each z
- levels <- c(-1, 0, 1)
- labs <- c("Low SC–SM (−1 SD)", "Mean SC–SM (0)", "High SC–SM (+1 SD)")
- pool_one_level <- function(z) {
- Q <- numeric(m) # estimates
- U <- numeric(m) # variances
- for (i in seq_len(m)) {
- bi <- coef(analyses[[i]])
- Vi <- vcov(analyses[[i]]) # model-based vcov; robust + MI is non-trivial
- beta <- unname(bi["F3_z"] + z * bi["F3_z:SubC_z"])
- var <- Vi["F3_z","F3_z"] +
- (z^2)*Vi["F3_z:SubC_z","F3_z:SubC_z"] +
- 2*z*Vi["F3_z","F3_z:SubC_z"]
- Q[i] <- beta
- U[i] <- var
- }
- # Rubin's rules
- Qbar <- mean(Q)
- Ubar <- mean(U)
- B <- var(Q) # between-imputation variance
- Tvar <- Ubar + (1 + 1/m) * B
- SE <- sqrt(Tvar)
- # df (Barnard & Rubin)
- lam <- (1 + 1/m) * B / Tvar
- df_old <- (m - 1) / (lam^2)
- # approximate df for Ubar (complete-data) – use average residual df
- df_complete <- mean(vapply(analyses, function(mod) mod$df.residual, numeric(1)))
- df <- 1 / ( (1/df_old) + (1/df_complete) )
- tval <- Qbar / SE
- pval <- 2 * pt(abs(tval) * -1, df = df)
- c(beta = Qbar, SE = SE, t = tval, p = pval)
- }
- M <- t(vapply(levels, pool_one_level, numeric(4L)))
- simple_df <- data.frame(
- Level = labs,
- `β (std.)` = round(M[, "beta"], 3),
- SE = round(M[, "SE"], 3),
- t = round(M[, "t"], 2),
- `p-value` = signif(M[, "p"], 3),
- check.names = FALSE
- )
- write.csv(simple_df, simple_slopes_out, row.names = FALSE)
- print(simple_df)
- }
- # Also write a short summary row with R²_base, R²_int, and ΔR² (if available)
- r2_summary_path <- file.path(output_dir, "moderation_model_comparison.csv")
- if (exists("R2_base") && exists("R2_int")) {
- write.csv(
- data.frame(
- R2_main_effects = round(R2_base, 3),
- R2_with_interaction = round(R2_int, 3),
- delta_R2 = round(R2_int - R2_base, 3)
- ),
- r2_summary_path,
- row.names = FALSE
- )
- } else if (exists("R2_base_vec") && exists("R2_int_vec")) {
- write.csv(
- data.frame(
- R2_main_effects_mean = round(mean(R2_base_vec), 3),
- R2_with_interaction_mean = round(mean(R2_int_vec), 3),
- delta_R2_mean = round(mean(R2_int_vec - R2_base_vec), 3)
- ),
- r2_summary_path,
- row.names = FALSE
- )
- }
- # ------------------------
- # 11) Visuals
- # ------------------------
- # Mediation path diagram
- pdf(file.path(output_dir, "sem_paths_mediation_full.pdf"), width = 10, height = 7)
- semPaths(fit_full, what = "std", layout = "tree2",
- edge.label.cex = .7, sizeMan = 7, sizeLat = 9,
- residuals = FALSE, nCharNodes = 0,
- title = TRUE, title.cex = 1.1)
- dev.off()
- # ---- Mediation effects plot: ALWAYS create a page ----
- # ---- Mediation effects plot: ALWAYS create a page ----
- pdf(file.path(output_dir, "mediation_effects_CI.pdf"), width = 8, height = 5)
- pe_ok <- (exists("pe_full") &&
- is.data.frame(pe_full) &&
- any(pe_full$label %in% c("indirect","direct","total")))
- if (pe_ok) {
- eff_tab <- subset(pe_full, label %in% c("indirect","direct","total"),
- select = c(label, est, ci.lower, ci.upper))
- # coerce to numeric
- eff_tab$est <- as.numeric(eff_tab$est)
- eff_tab$ci.lower <- as.numeric(eff_tab$ci.lower)
- eff_tab$ci.upper <- as.numeric(eff_tab$ci.upper)
- # ORDER: Direct, Indirect, Total
- eff_tab$label <- factor(eff_tab$label,
- levels = c("direct","indirect","total"),
- labels = c("Direct (c′)", "Indirect (a×b)", "Total"))
- g <- ggplot(eff_tab, aes(x = label, y = est, fill = label)) +
- geom_col() +
- geom_errorbar(aes(ymin = ci.lower, ymax = ci.upper), width = .2) +
- geom_hline(yintercept = 0, linetype = "dashed") +
- scale_fill_manual(values = c(
- "Direct (c′)" = "#d9d9d9", # light gray
- "Indirect (a×b)" = "#f2f2f2", # lightest gray
- "Total" = "#7a7a7a" # darker gray
- ), guide = "none") +
- theme_minimal() +
- labs(title = "Mediation effects with 95% CI",
- x = NULL, y = "Standardized effect (β)")
- print(g)
- } else {
- plot.new()
- text(.5, .6, "Mediation effects not available", cex = 1.2)
- text(.5, .45, "Expected labels in pe_full: indirect, direct, total.", cex = .9)
- }
- dev.off()
- # ---- Moderation interaction plot: ALWAYS create a page ----
- # ---- Moderation interaction plot: ALWAYS create a page ----
- pdf(file.path(output_dir, "moderation_interaction_plot.pdf"), width = 8, height = 6)
- if (!use_imputation && exists("fit_int") && inherits(fit_int, "lm")) {
- # ensure mod_data exists
- if (!exists("mod_data")) {
- mod_data <- tryCatch(model.frame(fit_int), error = function(e) NULL)
- }
- if (!is.null(mod_data)) {
- at_sc <- c(-1, 0, 1)
- x_min <- min(mod_data$F3_z, na.rm = TRUE)
- x_max <- max(mod_data$F3_z, na.rm = TRUE)
- # prediction grid for lines
- grid <- expand.grid(
- F3_z = seq(x_min, x_max, length.out = 120),
- SubC_z = at_sc,
- IQ_z = 0,
- Age_z = 0,
- Gender_num = 0
- )
- grid$MET_hat <- predict(fit_int, newdata = grid)
- grid$SC_lab <- factor(grid$SubC_z, levels = at_sc,
- labels = c("SC–SM: -1 SD", "SC–SM: 0", "SC–SM: +1 SD"))
- # group points to match the 3 SC–SM reference levels
- mod_data$SC_lab <- cut(mod_data$SubC_z,
- breaks = c(-Inf, -1, 1, Inf),
- labels = c("SC–SM: -1 SD", "SC–SM: 0", "SC–SM: +1 SD"),
- right = TRUE)
- g2 <- ggplot() +
- # observed points
- geom_point(data = mod_data, aes(x = F3_z, y = MET_z, shape = SC_lab),
- alpha = 0.35, size = 1, na.rm = TRUE) +
- # predicted lines
- geom_line(data = grid, aes(x = F3_z, y = MET_hat, linetype = SC_lab)) +
- theme_minimal() +
- labs(title = "Moderation: Simple slopes of F3 at SC–SM levels",
- x = "F3 (z)", y = "MET (z)",
- linetype = NULL, shape = NULL)
- print(g2)
- } else {
- plot.new()
- text(.5, .6, "Interaction model not available for plotting", cex = 1.2)
- text(.5, .45, "Could not reconstruct model.frame(fit_int).", cex = .9)
- }
- } else {
- plot.new()
- text(.5, .6, "Interaction model not available for plotting", cex = 1.2)
- text(.5, .45, "Check fit_int exists and use_imputation == FALSE.", cex = .9)
- }
- dev.off()
- # ------------------------
- # 12) Summaries & comparison table
- # ------------------------
- # LRT p-value
- if (!use_imputation) {
- LRT_p <- as.numeric(med_LRT$`Pr(>Chisq)`[2])
- } else {
- # lavTestLRT.mi prints an object; extract p-value if available, else NA
- LRT_p <- tryCatch({
- as.numeric(med_LRT["Pr(>Chisq)"][2, 1])
- }, error = function(e) NA)
- }
- # Moderation stats
- if (!use_imputation) {
- int_beta <- as.numeric(coef(fit_int)["F3_z:SubC_z"])
- int_p <- summary(fit_int)$coefficients["F3_z:SubC_z", "Pr(>|t|)"]
- # robust p (optional):
- int_p_HC3 <- tryCatch({
- as.numeric(coeftest(fit_int, vcov. = vcovHC(fit_int, type="HC3"))["F3_z:SubC_z","Pr(>|t|)"])
- }, error = function(e) NA)
- R2b <- R2_base; R2i <- R2_int
- } else {
- int_beta <- if (nrow(int_pool) == 0) NA else int_pool$estimate
- int_p <- if (nrow(int_pool) == 0) NA else int_pool$p.value
- int_p_HC3 <- NA
- R2b <- mean(R2_base_vec); R2i <- mean(R2_int_vec)
- }
- comp <- data.frame(
- Model = c("Mediation (full vs no-mediator)", "Moderation (interaction)"),
- Key_Test = c("LRT for b-path (Δdf=1)", "β(F3×SubC) & ΔR²"),
- Estimate_or_Δ = c(
- paste0("Indirect (a*b) = ", round(indirect_vec[1], 3),
- " [", round(indirect_vec[2], 3), ", ", round(indirect_vec[3], 3), "]"),
- paste0("β = ", round(int_beta, 3),
- ", ΔR² = ", round(R2i - R2b, 3))
- ),
- p_value = c(ifelse(is.na(LRT_p), "NA", formatC(LRT_p, format="f", digits=3)),
- ifelse(is.na(int_p), "NA", formatC(int_p, format="f", digits=3)))
- )
- write.csv(comp, file.path(output_dir, "comparison_table.csv"), row.names = FALSE)
- print(comp)
- # ------------------------
- # 13) Text report
- # ------------------------
- sink(file.path(output_dir, "report_SubC_reimpl.txt"))
- cat("SC–SM: Mediation vs Moderation (Re-implementation)\n")
- cat("===============================================\n\n")
- cat("F3 treated as EXOGENOUS. Covariates (IQ, Age, Gender) enter SubC and MET.\n")
- cat("Mediation comparison is TRUE NESTED (remove MET ~ SubC; Δdf=1).\n\n")
- cat("Missingness summary (%, N=", nrow(dat0), "):\n", sep = "")
- print(miss_tbl); cat("\n")
- cat("IQ missing %:", round(iq_missing_pct,1), " | Multiple Imputation:", use_imputation, "\n\n")
- cat("Mediation LRT p-value (full vs no-mediator): ", ifelse(is.na(LRT_p), "NA", LRT_p), "\n")
- cat("Indirect (a*b): est=", round(indirect_vec[1],3),
- " CI=[", round(indirect_vec[2],3), ", ", round(indirect_vec[3],3), "]",
- " p=", round(indirect_vec[4],3), "\n\n")
- cat("Moderation:\n")
- cat(" β(F3×SubC) =", round(int_beta,3),
- " | p =", ifelse(is.na(int_p), "NA", round(int_p,3)),
- " | ΔR² =", round(R2i - R2b, 3), "\n")
- if (!is.na(int_p_HC3)) cat(" (Robust HC3 p ≈", round(int_p_HC3,3), ")\n")
- cat("\nNotes:\n")
- cat("- Global SEM fit indices are not relied upon because the models are near-saturated.\n")
- cat("- Inference for mediation comes from indirect effect CI and nested LRT (Δdf=1).\n")
- cat("- Inference for moderation comes from the interaction term and ΔR².\n")
- cat("- MI pooling uses semTools::runMI for SEM and mice::with/pool for OLS.\n")
- sink()
- cat("\n=== DONE ===\nOutput in: ", normalizePath(output_dir), "\n", sep = "")
mediation_moderation.R at commit f21d2ab, no license · at the source
Overview
- Center for Music in the Brain, Department of Clinical Medicine, Aarhus University & The Royal Academy of Music Aarhus/Aalborg, Aarhus, Denmark
- School of Electronic Engineering and Computer Science, Queen Mary University of London, London, United Kingdom
- The MARCS Institute for Brain, Behaviour and Development, Western Sydney University, Penrith, Australia
- Department of Education, Psychology, Communication, University of Bari, Bari, Italy
- Language Acquisition and Language Processing Lab, Norwegian University of Science and Technology, Trondheim, Norway
- Consciousness Lab, Institute of Psychology, Jagiellonian University, Kraków, Poland
- Center of Functionally Integrative Neuroscience, Department of Clinical Medicine, Aarhus University, Aarhus, Denmark
- Neurobiology Research Unit, Copenhagen University Hospital Rigshospitalet, Copenhagen, Denmark
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
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
cc7dd46694fc0d44559bd9f2eacc16550512d4a3, 28 February 2024Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
9 files
- gen_connectome.sh, Shell, 42 lines
- gen_connectome_all.sh, Shell, 11 lines
- mrtrix_pipeline_QC.sh, Shell, 47 lines, 1 match
- mrtrix_pipeline_step_1.s
h , Shell, 101 lines - mrtrix_pipeline_step_2.s
h , Shell, 20 lines - mrtrix_pipeline_step_3.s
h , Shell, 59 lines, 1 match - mrtrix_pipeline_step_4_m
u_coeff.sh , Shell, 21 lines - mrtrix_pipeline_step_5_c
onnectome_gen.sh , Shell, 41 lines - README.md, Text, 52 lines
MassimoLumaca/neuroTraining
f21d2abe75d365b4b22f3b18df0ef6e1ade7aab6, 27 January 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
6 files
- scripts/
analysis/ , Python, 535 linesextract_connectivity_fea tures.py - scripts/
analysis/ , Python, 832 linesnetwork_pairs_behaviour_ correlations.py - scripts/
analysis/ , Python, 2,414 lines, 2 matchesnetwork_statistics.py - scripts/
mediation/ , R, 629 lines, 2 matchesmediation_moderation.R - scripts/
preprocessing/ , MATLAB, 560 lines, 1 matchunpack_connectome_log_tr ansformation.m - README.md, Text, 401 lines
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/
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://
BibTeX
@article{lumaca2026decod
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/
url = {https://
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/
VL - 4
SP - IMAG.a.1325
SN - 2837-6056
PB - MIT Press
DO - 10.1162/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1162/
"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":
"volume": "4",
"page": "IMAG.a.1325",
"DOI": "10.1162/
"PMID": "42559177",
"PMCID": "PMC13440155",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://
"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 communicationsIn 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 advancesIn 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 communicationsIn 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 communicationsIn 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. ClinicalIn 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: eLifeIn 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 communicationsIn 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: eLifeIn 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 mappingIn 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 13 scripts, and 7 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:49a97610d25e91ed…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
