Emergent blink rate in early childhood is associated with neural origins of executive function.
The 6 matches
- [1] § Methods › Analysis plan ↔ code/MainAnalysis.R, lines 396–441 · score 0.88 · Residual diagnostics, residual variance, design cells, random intercept, SD ratio, fit
- [2] § Methods › Analysis plan ↔ code/MainAnalysis.R, lines 275–312 · score 0.76 · confidence intervals, Tukey adjustment, multiplicity adjustment, post hoc contrasts, pairwise, oxy Hb
- [3] § Results › Spontaneous eye-blink rate is associated with switching performance ↔ code/MainAnalysis.R, lines 146–211 · score 0.76 · post hoc Wilcoxon, rank biserial, signed rank, Friedman, Kendall, Bonferroni
- [4] § Methods › fNIRS recordings and analysis ↔ code/Fig4_plot.R, lines 1–58 · score 0.75 · lDLPFC, rRLPFC, lRLPFC, rDLPFC, oxy Hb, fNIRS
- [5] § Methods › Analysis plan ↔ code/Fig4_plot.R, lines 1–58 · score 0.74 · lDLPFC, rRLPFC, lRLPFC, sEBR, rDLPFC, switching accuracy
- [6] § Methods › fNIRS recordings and analysis ↔ code/MainAnalysis.R, lines 508–549 · score 0.65 · lDLPFC, rRLPFC, lRLPFC, rDLPFC, neural, correlated
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 · 767 lines · 41 KB · no license · 4 matches
- # ==============================================================================
- # MainAnalysis.R
- # Emergent blink rate in early childhood is associated with neural origins
- # of executive function
- # Kuwamizu, Yamamoto, Otani & Moriguchi
- #
- # Copyright (c) 2026 the authors. Released under the MIT License; see code/LICENSE.
- # The accompanying data are released under CC BY 4.0; see LICENSE.md.
- #
- # Integrated analysis pipeline. Reproduces every statistic reported in the
- # manuscript and Supplementary Information, and exports figure source data.
- #
- # Section 1 Behavioural correlations Fig 2a-c; Supp Fig 1a-d
- # Section 2 fNIRS linear mixed models Fig 3a-f; Supp Table 3
- # Section 3 Neural correlations Fig 4a-b; Supp Table 4; Supp Fig 5
- # Section 4 Robustness and sensitivity Supp Notes 1 and 4; Methods
- # Section 5 Summary CSVs and session information
- #
- # Input : data/SourceData.csv
- # Output: outputs_summary/results_summary_generated.csv
- # source_data_figures/*.csv
- # R >= 4.3
- # ==============================================================================
- ## ====== 0. Setup, data, helpers ==============================================
- if (!require("pacman")) install.packages("pacman")
- pacman::p_load(tidyverse, lme4, lmerTest, emmeans, ppcor, rstatix,
- effectsize, performance, mediation)
- options(scipen = 999)
- set.seed(365)
- OUTDIR <- "outputs_summary"
- FIGDIR <- "source_data_figures"
- dir.create(OUTDIR, showWarnings = FALSE, recursive = TRUE)
- dir.create(FIGDIR, showWarnings = FALSE, recursive = TRUE)
- # ---- single data path (override here if needed) ----
- candidate_paths <- c(
- file.path("data", "SourceData.csv"),
- "SourceData.csv",
- file.path("..", "data", "SourceData.csv")
- )
- data_path <- candidate_paths[file.exists(candidate_paths)][1]
- if (is.na(data_path)) stop("SourceData.csv not found; set data_path manually.")
- cat("Loading data from:", data_path, "\n")
- raw_data <- read.csv(data_path, na.strings = c("NA", "NaN", ""),
- fileEncoding = "UTF-8", stringsAsFactors = FALSE) %>%
- mutate(ID = as.factor(ID))
- fnirs_cols <- grep("^(r|l)_(DL|RL)_(pre|post|mix|sw)$", names(raw_data), value = TRUE)
- count_cols <- c("sw_corr","sw_rep","Pre_corr","Pre_rep",
- "Post_corr","Post_rep","Mix_corr","Mix_rep")
- target_cols <- c("age","sEBR","sw_acc","pre_acc","post_acc","mix_acc",
- count_cols, fnirs_cols)
- raw_data <- raw_data %>%
- mutate(across(any_of(target_cols), ~ as.numeric(as.character(.))),
- sex = if ("sex" %in% names(.)) factor(sex) else NA,
- site = if ("site" %in% names(.)) factor(site) else NA)
- cat("N participants:", nrow(raw_data), "\n")
- # ---- helper functions ----
- # Spearman with N and 95% CI.
- # CI from Fisher's z transform with the Bonett & Wright (2000) standard error
- # for Spearman's rho: SE_z = sqrt((1 + rho^2/2)/(n - 3)).
- sp <- function(x, y, conf = 0.95) {
- d <- na.omit(data.frame(x, y))
- n <- nrow(d)
- ct <- cor.test(d$x, d$y, method = "spearman", exact = FALSE)
- rho <- unname(ct$estimate)
- if (n > 3 && abs(rho) < 1) {
- z <- atanh(rho)
- se <- sqrt((1 + rho^2 / 2) / (n - 3))
- zc <- qnorm(1 - (1 - conf) / 2)
- lo <- tanh(z - zc * se); hi <- tanh(z + zc * se)
- } else { lo <- NA_real_; hi <- NA_real_ }
- c(rho = rho, p = ct$p.value, n = n, ci_low = lo, ci_high = hi)
- }
- # Partial Spearman via ppcor (df = n - 2 - k); covariates are ranked.
- # CI from Fisher's z transform with SE_z = 1/sqrt(n - k - 3) (k = # covariates).
- pspear <- function(x, y, Z, conf = 0.95) {
- Z <- as.data.frame(Z)
- d <- na.omit(data.frame(x = x, y = y, Z))
- zc <- setdiff(names(d), c("x", "y"))
- k <- length(zc)
- n <- nrow(d)
- zr <- as.data.frame(lapply(d[zc], function(v) rank(xtfrm(v))))
- pc <- ppcor::pcor.test(rank(d$x), rank(d$y), zr, method = "pearson")
- rho <- unname(pc$estimate)
- if ((n - k - 3) > 0 && abs(rho) < 1) {
- z <- atanh(rho)
- se <- 1 / sqrt(n - k - 3)
- zcrit <- qnorm(1 - (1 - conf) / 2)
- lo <- tanh(z - zcrit * se); hi <- tanh(z + zcrit * se)
- } else { lo <- NA_real_; hi <- NA_real_ }
- c(rho = rho, p = pc$p.value, n = n, ci_low = lo, ci_high = hi)
- }
- winsorize <- function(v, p = 0.05) {
- lo <- quantile(v, p, na.rm = TRUE); hi <- quantile(v, 1-p, na.rm = TRUE)
- pmin(pmax(v, lo), hi)
- }
- # ---- running summary collector (-> results_summary_generated.csv) ----
- # Every value written to the CSV is also echoed to the console (prefixed "[CSV]"),
- # so the console log and the CSV contain the same numbers.
- SUMMARY <- list()
- # `stat` : NAME of the test statistic or estimate type (e.g. "t", "V", "F")
- # `stat_value` : VALUE of that test statistic, when it differs from `estimate`
- # (e.g. estimate = Cohen's d while stat_value = the t statistic).
- # Left empty when `estimate` already IS the statistic.
- # `stat_value` is the LAST argument so that every pre-existing positional call
- # in this script keeps its original meaning.
- add_row <- function(loc, analysis, estimate, stat = "", df = "",
- p = "", n = "", model = "", note = "",
- ci_low = "", ci_high = "", stat_value = "") {
- row <- data.frame(
- ms_location = loc, analysis = analysis, estimate = as.character(estimate),
- stat = stat, stat_value = as.character(stat_value),
- df = as.character(df), p = as.character(p),
- n = as.character(n),
- ci_low = as.character(ci_low), ci_high = as.character(ci_high),
- model = model, note = note, stringsAsFactors = FALSE)
- SUMMARY[[length(SUMMARY) + 1]] <<- row
- # ---- console echo of the same numbers ----
- e <- as.character(estimate); st <- as.character(stat); d <- as.character(df)
- sv <- as.character(stat_value)
- pp <- as.character(p); nn <- as.character(n)
- cl <- as.character(ci_low); ch <- as.character(ci_high)
- md <- as.character(model); nt <- as.character(note)
- part <- function(lab, v) if (length(v) && !is.na(v) && nzchar(v)) paste0(" ", lab, v) else ""
- ci_str <- if (length(cl) && length(ch) && !is.na(cl) && !is.na(ch) && nzchar(cl) && nzchar(ch))
- sprintf(" 95%%CI=[%s, %s]", cl, ch) else ""
- # when a statistic value is present, echo it as e.g. " t=3.214"
- st_str <- if (length(sv) && !is.na(sv) && nzchar(sv))
- paste0(" ", st, "=", sv) else part("", st)
- cat(sprintf("[CSV] %-18s | %-46s | est=%s%s%s%s%s%s%s%s\n",
- loc, analysis, e,
- st_str, part("df=", d), part("p=", pp), part("n=", nn), ci_str,
- part("model=", md), part("note=", nt)))
- }
- fmtp <- function(p) ifelse(p < 0.001, "<0.001", signif(p, 3))
- # 95% CI as a printable string, e.g. "[0.12, 0.34]"
- fmtci <- function(lo, hi, d = 3) ifelse(is.na(lo) | is.na(hi), "",
- sprintf(paste0("[%.", d, "f, %.", d, "f]"), lo, hi))
- ## =============================================================================
- ## SECTION 1 -- Behavioural correlations [Fig 2a-c; Supp Fig 1a-d]
- ## =============================================================================
- cat("\n\n========== SECTION 1: Behaviour (Fig 2; Supp Fig 1) ==========\n")
- ## 1-1 Friedman + post-hoc [Supp Fig 1a]
- beh_long <- raw_data %>%
- dplyr::select(ID, pre_acc, post_acc, mix_acc) %>%
- pivot_longer(c(pre_acc, post_acc, mix_acc), names_to = "Condition", values_to = "Accuracy") %>%
- mutate(Condition = factor(Condition, levels = c("pre_acc","post_acc","mix_acc"))) %>%
- drop_na()
- fr <- friedman.test(Accuracy ~ Condition | ID, data = beh_long); print(fr)
- # The omnibus Friedman test is reported in full as chi2(df) and an exact p.
- # (No separate effect size is added here: the editor's effect-size + CI
- # requirement targets t-tests and ANOVAs; the rank-based post-hoc comparisons
- # below carry the effect sizes and CIs.)
- n_blk <- nrow(raw_data %>% dplyr::select(pre_acc, post_acc, mix_acc) %>% tidyr::drop_na())
- add_row("SuppFig1a","Pre/Post/Mix accuracy (Friedman)", round(unname(fr$statistic),2),
- "chi2", fr$parameter, fmtp(fr$p.value), n_blk)
- # Effect size for the Friedman test: Kendall's W with 95% CI.
- kw <- as.data.frame(suppressWarnings(
- effectsize::kendalls_w(Accuracy ~ Condition | ID, data = beh_long,
- ci = 0.95, alternative = "two.sided")))
- cat(sprintf("Friedman effect size: Kendall's W = %.3f, 95%% CI [%.3f, %.3f]\n",
- kw$Kendalls_W, kw$CI_low, kw$CI_high))
- add_row("SuppFig1a","Friedman effect size (Kendall's W)", round(kw$Kendalls_W,3),
- "Kendall W", "", "", n_blk, "", "",
- round(kw$CI_low,3), round(kw$CI_high,3))
- # Post-hoc paired Wilcoxon signed-rank tests, reported in full and separately.
- # Statistic V; p from the normal approximation with continuity correction
- # (exact = FALSE, appropriate here because accuracy values are tied), then
- # Bonferroni-adjusted across the 2 comparisons; matched-pairs rank-biserial
- # correlation r (+95% CI) as the effect size.
- # NOTE: the signed-rank statistic V has no degrees of freedom, so V is
- # written to `stat_value` and the `df` column is left empty.
- ph_pairs <- list(c("pre_acc","post_acc","Pre vs Post"),
- c("pre_acc","mix_acc","Pre vs Mix"))
- ph_raw_p <- sapply(ph_pairs, function(pr)
- wilcox.test(raw_data[[pr[1]]], raw_data[[pr[2]]], paired = TRUE, exact = FALSE)$p.value)
- ph_adj_p <- p.adjust(ph_raw_p, method = "bonferroni")
- for (i in seq_along(ph_pairs)) {
- pr <- ph_pairs[[i]]
- d2 <- na.omit(data.frame(a = raw_data[[pr[1]]], b = raw_data[[pr[2]]]))
- wt <- wilcox.test(d2$a, d2$b, paired = TRUE, exact = FALSE)
- rb <- tryCatch(as.data.frame(effectsize::rank_biserial(d2$a, d2$b, paired = TRUE, ci = 0.95)),
- error = function(e) data.frame(r_rank_biserial = NA, CI_low = NA, CI_high = NA))
- cat(sprintf("Post-hoc %-11s V = %.0f, p_adj = %s, r_rb = %.3f, 95%% CI %s, n = %d\n",
- pr[3], unname(wt$statistic), fmtp(ph_adj_p[i]),
- rb$r_rank_biserial, fmtci(rb$CI_low, rb$CI_high), nrow(d2)))
- add_row("SuppFig1a", paste0("Post-hoc Wilcoxon ", pr[3]),
- estimate = round(rb$r_rank_biserial, 3),
- stat = "V",
- df = "", # V has no df
- p = fmtp(ph_adj_p[i]),
- n = nrow(d2),
- note = paste0("two-sided, normal approximation; Bonferroni-adjusted p; ",
- "estimate = matched-pairs rank-biserial r (95% CI)"),
- ci_low = round(rb$CI_low, 3),
- ci_high = round(rb$CI_high, 3),
- stat_value = round(unname(wt$statistic), 1))
- }
- ## 1-2 Primary correlations [Fig 2a-c]
- for (nm in list(c("age","sw_acc","Fig2a","age ~ switching accuracy"),
- c("age","sEBR","Fig2b","age ~ sEBR"),
- c("sEBR","sw_acc","Fig2c","sEBR ~ switching accuracy"))) {
- r <- sp(raw_data[[nm[1]]], raw_data[[nm[2]]])
- cat(sprintf("%-28s rho=%.2f p=%s 95%% CI %s n=%d\n",
- nm[4], r["rho"], fmtp(r["p"]), fmtci(r["ci_low"], r["ci_high"]), r["n"]))
- add_row(nm[3], nm[4], round(r["rho"],3), "Spearman rho", "", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- }
- r <- pspear(raw_data$sEBR, raw_data$sw_acc, raw_data["age"])
- cat(sprintf("sEBR ~ switching acc | age rho=%.2f p=%s 95%% CI %s n=%d\n",
- r["rho"], fmtp(r["p"]), fmtci(r["ci_low"], r["ci_high"]), r["n"]))
- add_row("Fig2c","sEBR ~ switching accuracy | age", round(r["rho"],3),
- "partial Spearman", "", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- ## 1-3 Phase-wise sEBR x accuracy + age x phase-accuracy [Supp Fig 1b-d]
- for (v in c("pre_acc","post_acc","mix_acc")) {
- rs <- sp(raw_data$sEBR, raw_data[[v]]); rp <- pspear(raw_data$sEBR, raw_data[[v]], raw_data["age"])
- add_row("SuppFig1", paste0("sEBR ~ ", v), round(rs["rho"],3), "Spearman rho","", fmtp(rs["p"]), rs["n"],
- "", "", round(rs["ci_low"],3), round(rs["ci_high"],3))
- add_row("SuppFig1", paste0("sEBR ~ ", v, " | age"), round(rp["rho"],3), "partial Spearman","", fmtp(rp["p"]), rp["n"],
- "", "", round(rp["ci_low"],3), round(rp["ci_high"],3))
- }
- for (v in c("pre_acc","post_acc","mix_acc")) {
- ra <- sp(raw_data$age, raw_data[[v]])
- add_row("SuppFig1", paste0("age ~ ", v), round(ra["rho"],3), "Spearman rho","", fmtp(ra["p"]), ra["n"],
- "", "", round(ra["ci_low"],3), round(ra["ci_high"],3))
- }
- ## =============================================================================
- ## SECTION 2 -- fNIRS LMM [Fig 3a-f; Supp Table 3]
- ## =============================================================================
- cat("\n\n========== SECTION 2: fNIRS LMM (Fig 3; Supp Table 3) ==========\n")
- df_long <- raw_data %>%
- pivot_longer(matches("^(r|l)_(DL|RL)_(pre|post|mix)$"),
- names_to = c("Hemisphere","Region","Phase"),
- names_pattern = "^([rl])_([A-Z]{2})_([a-z]+)", values_to = "OxyHb") %>%
- mutate(Subject = ID,
- Hemisphere = factor(Hemisphere, levels = c("l","r"), labels = c("Left","Right")),
- Region = factor(Region, levels = c("RL","DL"), labels = c("RLPFC","DLPFC")),
- Phase = factor(Phase, levels = c("pre","mix","post")),
- PhaseRep = dplyr::case_when(Phase=="pre"~Pre_rep, Phase=="mix"~Mix_rep, Phase=="post"~Post_rep)) %>%
- drop_na(OxyHb)
- cat("df_long rows:", nrow(df_long), "\n")
- ## 2-1 Base model [Fig 3b-d]
- model_base <- lmer(OxyHb ~ Phase*Hemisphere*Region + (1|Subject), data = df_long)
- print(anova(model_base))
- ab <- anova(model_base)
- for (eff in c("Phase","Hemisphere","Region")) {
- add_row("Fig3", paste0(eff," main effect"), round(ab[eff,"F value"],2), "F",
- paste0(ab[eff,"NumDF"], ",", sprintf("%.1f", ab[eff,"DenDF"])), fmtp(ab[eff,"Pr(>F)"]), "", "base")
- }
- # Interaction terms reported in full (F, df, exact p) even though non-significant.
- for (eff in c("Phase:Hemisphere","Phase:Region","Hemisphere:Region","Phase:Hemisphere:Region")) {
- add_row("Fig3", paste0(eff," interaction"), round(ab[eff,"F value"],2), "F",
- paste0(ab[eff,"NumDF"], ",", sprintf("%.1f", ab[eff,"DenDF"])), fmtp(ab[eff,"Pr(>F)"]), "", "base")
- }
- ## Base-model post-hoc EMM contrasts [Fig 3c,d]
- ## Reported in full: EMM difference with 95% CI, t(df), p.
- ## >>> SIGN: contrasts are (first level - second level); all five come out
- ## >>> NEGATIVE here. The manuscript quotes magnitudes. See the header block.
- ## `infer = c(TRUE, TRUE)` is added so that the confidence interval is returned
- ## alongside the test; emmeans applies the SAME multiplicity adjustment to the
- ## interval as to the p value, so both carry the adjustment named below.
- ## Phase has three levels -> Tukey adjustment is active.
- ## Hemisphere and Region have two levels -> a single contrast, no adjustment.
- ## The corresponding standardised effect sizes (Cohen's d) are written
- ## separately by report_effsizes() above.
- emm_contrast_rows <- function(model, fac, adjust, loc, tag) {
- ct <- as.data.frame(summary(
- emmeans(model, as.formula(paste0("pairwise ~ ", fac)), adjust = adjust)$contrasts,
- infer = c(TRUE, TRUE)))
- cat(sprintf("\n-- %s post-hoc contrasts (adjust = %s) --\n", fac, adjust))
- print(ct)
- for (j in seq_len(nrow(ct))) {
- add_row(loc, paste0(fac, " post-hoc (", ct$contrast[j], ")"),
- estimate = round(ct$estimate[j], 4),
- stat = "t",
- df = sprintf("%.1f", ct$df[j]),
- p = fmtp(ct$p.value[j]),
- n = "",
- model = tag,
- note = paste0("estimate = UNSTANDARDISED EMM difference in oxy-Hb units ",
- "(95% CI on that difference, NOT on Cohen's d); ",
- "sign follows the contrast label (first - second level); ",
- "p and CI adjustment: ", adjust,
- " (", nrow(ct), " contrast(s)); ",
- "the standardised effect size for this contrast is reported ",
- "separately as a 'Cohen d' row"),
- ci_low = round(ct$lower.CL[j], 4),
- ci_high = round(ct$upper.CL[j], 4),
- stat_value = round(ct$t.ratio[j], 3))
- }
- ct
- }
- cph <- emm_contrast_rows(model_base, "Phase", "tukey", "Fig3c", "base")
- chh <- emm_contrast_rows(model_base, "Hemisphere", "none", "Fig3d", "base")
- crr <- emm_contrast_rows(model_base, "Region", "none", "Fig3d", "base")
- ## 2-2 Effect sizes for BASE and AGE models
- report_effsizes <- function(model, label, tag) {
- cat(sprintf("\n--- Effect sizes [%s] ---\n", label))
- cat("partial eta^2:\n"); es <- eta_squared(anova(model, type = 3), partial = TRUE, ci = 0.95, alternative = "two.sided"); print(es)
- cat("R^2 (Nakagawa):\n"); r2 <- r2_nakagawa(model); print(r2)
- sig <- sigma(model); edf <- df.residual(model)
- for (fac in c("Phase","Hemisphere","Region")) {
- cat(sprintf(" Cohen d [%s]:\n", fac))
- esz <- eff_size(emmeans(model, as.formula(paste0("~ ", fac))), sigma = sig, edf = edf)
- print(esz)
- ed <- as.data.frame(esz)
- for (j in seq_len(nrow(ed))) {
- add_row(paste0("Fig3 effsize [",tag,"]"),
- paste0("Cohen d ", fac, ": ", ed$contrast[j]),
- round(ed$effect.size[j],3), "Cohen d", round(ed$df[j],1), "", "",
- tag, paste0("post-hoc EMM contrast (sign = first - second level); ",
- "eff_size() calls contrast(adjust = \"none\"), so this 95% CI is ",
- "UNADJUSTED for multiplicity even where the corresponding p value ",
- "is Tukey-adjusted; sigma = residual SD of the LMM, edf = df.residual"),
- round(ed$lower.CL[j],3), round(ed$upper.CL[j],3))
- }
- }
- es <- as.data.frame(es)
- for (i in seq_len(nrow(es))) {
- add_row(paste0("Fig3 effsize [",tag,"]"), paste0("partial eta2 ", es$Parameter[i]),
- signif(es$Eta2_partial[i],3), "eta2_p","","","", tag, "",
- round(es$CI_low[i],3), round(es$CI_high[i],3))
- }
- add_row(paste0("Fig3 effsize [",tag,"]"),"Marginal R2", round(r2$R2_marginal,3), "R2","","","", tag)
- add_row(paste0("Fig3 effsize [",tag,"]"),"Conditional R2",round(r2$R2_conditional,3), "R2","","","", tag)
- }
- ## 2-3 Age model [Fig 3e,f]
- model_age <- lmer(OxyHb ~ (Phase*Hemisphere*Region)*age + (1|Subject), data = df_long)
- cat("\nBase vs Age LRT:\n"); print(anova(model_base, model_age))
- print(anova(model_age))
- aa <- anova(model_age)
- ## Every term of the age-model ANOVA is written to the summary CSV, not only the
- ## three significant age interactions: the manuscript reports F, df and P for the
- ## non-significant higher-order interactions as well.
- for (eff in rownames(aa)) {
- add_row("Fig3e/f", eff, round(aa[eff,"F value"],2), "F",
- paste0(aa[eff,"NumDF"], ",", sprintf("%.1f", aa[eff,"DenDF"])),
- fmtp(aa[eff,"Pr(>F)"]), "", "age",
- "Type III ANOVA, Satterthwaite df")
- }
- ## Post-hoc slope contrasts decomposing the age interactions (Fig 3e,f).
- ## Reported in full: estimate (difference in age slopes) with 95% CI, t(df), p.
- ## >>> SIGN: (first level - second level), as for the base-model contrasts above.
- ## >>> See the sign-convention block in the file header before transcribing.
- for (fac in c("Hemisphere","Region","Phase")) {
- ct <- as.data.frame(summary(
- emtrends(model_age, as.formula(paste0("pairwise ~ ", fac)), var = "age")$contrasts,
- infer = c(TRUE, TRUE)))
- cat(sprintf("\n-- age-slope contrasts by %s --\n", fac)); print(ct)
- for (j in seq_len(nrow(ct))) {
- # emmeans applies a Tukey adjustment to BOTH the p values and the CIs of
- # pairwise contrasts by default. Hemisphere and Region have two levels
- # (a single contrast, so the adjustment is inert); Phase has three levels,
- # where the adjustment is active. This is recorded in the note.
- add_row("Fig3e/f", paste0(fac, ":age contrast (", ct$contrast[j], ")"),
- estimate = round(ct$estimate[j], 4),
- stat = "t",
- df = sprintf("%.1f", ct$df[j]),
- p = fmtp(ct$p.value[j]),
- n = "",
- model = "age",
- note = paste0("estimate = difference in age slopes (95% CI); ",
- "Tukey-adjusted p and CI across the ", nrow(ct),
- " contrast(s) of ", fac),
- ci_low = round(ct$lower.CL[j], 4),
- ci_high = round(ct$upper.CL[j], 4),
- stat_value = round(ct$t.ratio[j], 3))
- }
- }
- report_effsizes(model_base, "BASE model: Phase x Hemisphere x Region", "BASE")
- report_effsizes(model_age, "AGE model: (Phase x Hemisphere x Region) x age", "AGE")
- ## 2-3b Residual diagnostics for the mixed-effects models [Methods]
- ## Normality of residuals and of the random intercepts, dependence of the
- ## residual variance on the fitted values, and equality of residual variance
- ## across the design cells.
- lmm_diagnostics <- function(model, tag) {
- mf <- model@frame
- r <- residuals(model)
- f <- fitted(model)
- sw <- shapiro.test(r)
- sk <- mean((r - mean(r))^3) / sd(r)^3
- ku <- mean((r - mean(r))^4) / sd(r)^4 - 3
- u <- r^2 / mean(r^2)
- LM <- 0.5 * sum((fitted(lm(u ~ f)) - 1)^2)
- bp <- pchisq(LM, 1, lower.tail = FALSE)
- cell <- interaction(mf$Phase, mf$Hemisphere, mf$Region, drop = TRUE)
- z <- abs(r - ave(r, cell, FUN = median))
- bf <- anova(lm(z ~ cell))
- sds <- tapply(r, cell, sd)
- re <- ranef(model)$Subject[, 1]
- swr <- shapiro.test(re)
- cat(sprintf("\n--- Residual diagnostics [%s] ---\n", tag))
- cat(sprintf("Residual normality: W = %.4f, p = %s (skewness %.3f, excess kurtosis %.3f)\n",
- sw$statistic, fmtp(sw$p.value), sk, ku))
- cat(sprintf("Residual variance vs fitted values: LM(1) = %.3f, p = %s\n", LM, fmtp(bp)))
- cat(sprintf("Residual variance across design cells: F(%d, %d) = %.3f, p = %s\n",
- bf$Df[1], bf$Df[2], bf$`F value`[1], fmtp(bf$`Pr(>F)`[1])))
- cat(sprintf("Residual SD by cell: %.4f to %.4f (max/min = %.2f)\n",
- min(sds), max(sds), max(sds) / min(sds)))
- cat(sprintf("Random-intercept normality: W = %.4f, p = %s\n",
- swr$statistic, fmtp(swr$p.value)))
- add_row("Methods", paste0("Residual normality [", tag, "]"),
- round(unname(sw$statistic), 4), "Shapiro-Wilk W", "", fmtp(sw$p.value), length(r), tag)
- add_row("Methods", paste0("Residual skewness / excess kurtosis [", tag, "]"),
- sprintf("%.3f / %.3f", sk, ku), "desc", "", "", length(r), tag)
- add_row("Methods", paste0("Residual variance vs fitted [", tag, "]"),
- round(LM, 3), "Breusch-Pagan LM", 1, fmtp(bp), length(r), tag)
- add_row("Methods", paste0("Residual variance across cells [", tag, "]"),
- round(bf$`F value`[1], 3), "Brown-Forsythe F",
- paste0(bf$Df[1], ",", bf$Df[2]), fmtp(bf$`Pr(>F)`[1]), length(r), tag)
- add_row("Methods", paste0("Residual SD ratio across cells [", tag, "]"),
- round(max(sds) / min(sds), 2), "max/min", "", "", length(sds), tag)
- add_row("Methods", paste0("Random-intercept normality [", tag, "]"),
- round(unname(swr$statistic), 4), "Shapiro-Wilk W", "", fmtp(swr$p.value), length(re), tag)
- }
- lmm_diagnostics(model_base, "BASE")
- lmm_diagnostics(model_age, "AGE")
- ## 2-3c Effect size for the base-vs-age model comparison [Fig 3e,f]
- r2_base <- r2_nakagawa(model_base)
- r2_age <- r2_nakagawa(model_age)
- d_marg <- r2_age$R2_marginal - r2_base$R2_marginal
- d_cond <- r2_age$R2_conditional - r2_base$R2_conditional
- lrt_ba <- anova(model_base, model_age)
- aic_diff <- lrt_ba$AIC[2] - lrt_ba$AIC[1]
- cat(sprintf("\nBase vs Age model comparison: change in marginal R2 = %.3f, ", d_marg))
- cat(sprintf("change in conditional R2 = %.3f, AIC difference = %.1f\n", d_cond, aic_diff))
- add_row("Fig3e/f","Base vs Age: likelihood ratio test", round(lrt_ba$Chisq[2],2), "chi2",
- lrt_ba$Df[2], fmtp(lrt_ba$`Pr(>Chisq)`[2]), "", "age",
- "LRT of the base model against the model adding age and all its interactions")
- add_row("Fig3e/f","Base vs Age: change in marginal R2", round(d_marg,3), "dR2","","","","age")
- add_row("Fig3e/f","Base vs Age: change in conditional R2", round(d_cond,3), "dR2","","","","age")
- add_row("Fig3e/f","Base vs Age: AIC difference", round(aic_diff,1), "dAIC","","","","age",
- "AIC(age) - AIC(base); equals 2*Df - Chisq of the LRT above")
- ## 2-4 One-sample t-tests vs baseline [Supp Table 3]
- ## Full reporting per cell: t(df), exact p (+Bonferroni), Cohen's d with 95% CI.
- st3 <- df_long %>% group_by(Phase, Hemisphere, Region) %>%
- group_modify(~{
- x <- .x$OxyHb
- tt <- t.test(x, mu = 0)
- dd <- tryCatch(as.data.frame(effectsize::cohens_d(x, mu = 0, ci = 0.95)),
- error = function(e) data.frame(Cohens_d = mean(x)/sd(x),
- CI_low = NA, CI_high = NA))
- tibble(N = length(x), Mean_Oxy = mean(x, na.rm = TRUE),
- Mean_CI_low = unname(tt$conf.int[1]), Mean_CI_high = unname(tt$conf.int[2]),
- t_value = unname(tt$statistic), df = unname(tt$parameter),
- p_raw = tt$p.value,
- Cohen_d = dd$Cohens_d, d_CI_low = dd$CI_low, d_CI_high = dd$CI_high)
- }) %>%
- ungroup() %>%
- mutate(p_bonf = p.adjust(p_raw, method = "bonferroni")) %>%
- arrange(Phase, Hemisphere, Region)
- print(as.data.frame(st3))
- write.csv(st3, file.path(FIGDIR, "source_SuppTable3_onesample.csv"), row.names = FALSE)
- for (i in seq_len(nrow(st3))) {
- add_row("SuppTable3",
- paste0("one-sample t vs 0: ", st3$Phase[i], " ", st3$Hemisphere[i], " ", st3$Region[i]),
- estimate = round(st3$Cohen_d[i], 3),
- stat = "t",
- df = st3$df[i],
- p = fmtp(st3$p_raw[i]),
- n = st3$N[i],
- note = paste0("two-sided; p shown is raw, p_bonf=", fmtp(st3$p_bonf[i]),
- "; estimate = Cohen d (95% CI)"),
- ci_low = round(st3$d_CI_low[i], 3),
- ci_high = round(st3$d_CI_high[i], 3),
- stat_value = round(st3$t_value[i], 3))
- ## The Oxy-Hb column of Supplementary Table 3 is the cell mean, which is a
- ## different quantity from Cohen's d above and is reported in mM*mm. Written
- ## as its own row so that the table can be reproduced from this file alone.
- add_row("SuppTable3",
- paste0("mean oxy-Hb: ", st3$Phase[i], " ", st3$Hemisphere[i], " ", st3$Region[i]),
- estimate = round(st3$Mean_Oxy[i], 3),
- stat = "mean",
- n = st3$N[i],
- note = "cell mean oxy-Hb in mM*mm with 95% CI; Oxy-Hb column of Supp Table 3",
- ci_low = round(st3$Mean_CI_low[i], 3),
- ci_high = round(st3$Mean_CI_high[i], 3))
- }
- ## 2-5 Fig 3c-f source-data export (EMMs for Prism) [Fig 3c,d,e,f]
- write.csv(as.data.frame(summary(emmeans(model_base, ~ Phase))),
- file.path(FIGDIR, "Fig3c_phase_emm.csv"), row.names = FALSE)
- write.csv(as.data.frame(summary(emmeans(model_base, ~ Hemisphere*Region))),
- file.path(FIGDIR, "Fig3d_hemi_region_emm.csv"), row.names = FALSE)
- age_seq <- seq(floor(min(df_long$age)), ceiling(max(df_long$age)), by = 1)
- write.csv(as.data.frame(summary(emmeans(model_age, ~ Hemisphere | age, at = list(age = age_seq)), infer = TRUE)),
- file.path(FIGDIR, "Fig3e_hemisphere_age.csv"), row.names = FALSE)
- write.csv(as.data.frame(summary(emmeans(model_age, ~ Region | age, at = list(age = age_seq)), infer = TRUE)),
- file.path(FIGDIR, "Fig3f_region_age.csv"), row.names = FALSE)
- ## =============================================================================
- ## SECTION 3 -- Neural correlations [Fig 4a-b; Supp Table 4; Supp Fig 5]
- ## =============================================================================
- cat("\n\n========== SECTION 3: Neural correlations (Fig 4; Supp Table 4) ==========\n")
- rois <- c(r_DL_sw="rDLPFC", l_DL_sw="lDLPFC", r_RL_sw="rRLPFC", l_RL_sw="lRLPFC")
- outcomes <- c(sEBR="sEBR", sw_acc="Switching accuracy")
- forest_df <- purrr::map_dfr(names(rois), function(r) purrr::map_dfr(names(outcomes), function(ov) {
- raw <- sp(raw_data[[r]], raw_data[[ov]])
- adj <- pspear(raw_data[[r]], raw_data[[ov]], raw_data["age"])
- tibble(Panel = rois[[r]], Series = outcomes[[ov]],
- rho_raw = raw["rho"], p_raw = raw["p"], n_raw = raw["n"],
- ci_low_raw = raw["ci_low"], ci_high_raw = raw["ci_high"],
- rho_age = adj["rho"], p_age = adj["p"], n_age = adj["n"],
- ci_low_age = adj["ci_low"], ci_high_age = adj["ci_high"])
- }))
- print(as.data.frame(forest_df %>% mutate(across(starts_with("rho"), ~round(.,3)),
- across(starts_with("p_"), ~signif(.,3)),
- across(starts_with("ci_"), ~round(.,3)))))
- write.csv(forest_df, file.path(FIGDIR, "Fig4_SuppTable4_correlations.csv"), row.names = FALSE)
- for (i in seq_len(nrow(forest_df))) {
- fr <- forest_df[i,]
- add_row("Fig4/SuppTable4", paste0(fr$Panel," ~ ", fr$Series), round(fr$rho_raw,3),
- "Spearman rho","", fmtp(fr$p_raw), fr$n_raw,
- "", "", round(fr$ci_low_raw,3), round(fr$ci_high_raw,3))
- if (fr$Panel == "rDLPFC")
- add_row("Fig4", paste0(fr$Panel," ~ ", fr$Series, " | age"), round(fr$rho_age,3),
- "partial Spearman","", fmtp(fr$p_age), fr$n_age,
- "", "", round(fr$ci_low_age,3), round(fr$ci_high_age,3))
- }
- ## 3-2 Mediation: sEBR -> rDLPFC -> switching accuracy [Supp Fig 5]
- med_df <- raw_data %>% dplyr::select(sEBR, sw_acc, r_DL_sw) %>% drop_na()
- cat("\n-- Mediation (Supp Fig 5): N =", nrow(med_df), "--\n")
- q75 <- quantile(med_df$sEBR, 0.75); q25 <- quantile(med_df$sEBR, 0.25)
- ## The sEBR "effect" in this mediation is the interquartile-range increase, so
- ## the two quartiles define the contrast being estimated and are reported in the
- ## Supplementary Information. Written to the summary CSV as their own rows.
- cat(sprintf("sEBR interquartile range used as the treatment contrast: %.2f -> %.2f blinks/min\n",
- q25, q75))
- add_row("SuppFig5","sEBR 25th percentile (control value)", round(unname(q25),2), "percentile",
- "","", nrow(med_df), "", "blinks/min; lower end of the interquartile-range contrast")
- add_row("SuppFig5","sEBR 75th percentile (treatment value)", round(unname(q75),2), "percentile",
- "","", nrow(med_df), "", "blinks/min; upper end of the interquartile-range contrast")
- model.m <- lm(r_DL_sw ~ sEBR, data = med_df)
- model.y <- lm(sw_acc ~ sEBR + r_DL_sw, data = med_df)
- set.seed(365)
- med.out <- mediate(model.m, model.y, treat = "sEBR", mediator = "r_DL_sw",
- treat.value = q75, control.value = q25,
- boot = FALSE, sims = 5000, robustSE = TRUE)
- print(summary(med.out))
- ms <- summary(med.out)
- add_row("SuppFig5","ACME (indirect via rDLPFC)", round(ms$d0,4), "quasi-Bayes","", fmtp(ms$d0.p), nrow(med_df), "", "exploratory",
- round(ms$d0.ci[1],4), round(ms$d0.ci[2],4))
- add_row("SuppFig5","ADE (direct)", round(ms$z0,4), "quasi-Bayes","", fmtp(ms$z0.p), nrow(med_df), "", "exploratory",
- round(ms$z0.ci[1],4), round(ms$z0.ci[2],4))
- add_row("SuppFig5","Proportion mediated", round(ms$n0,4), "quasi-Bayes","", "", nrow(med_df), "",
- "exploratory; kept to 4 dp because the manuscript quotes this as a percentage to 2 dp",
- round(ms$n0.ci[1],4), round(ms$n0.ci[2],4))
- sens.out <- medsens(med.out, rho.by = 0.1, sims = 1000)
- print(summary(sens.out))
- ## Sensitivity to violations of sequential ignorability. `err.cr.d` is the value
- ## of the residual correlation rho at which the ACME is driven to zero -- the
- ## quantity quoted in the Supplementary Information. It has length 2 only when
- ## the ACME differs between treatment and control groups, which is not the case
- ## here; the guard keeps the row well defined either way. Note that the grid is
- ## stepped by rho.by = 0.1, so this threshold is resolved to that granularity.
- rho0 <- unname(sens.out$err.cr.d)
- add_row("SuppFig5","Sensitivity: rho at which ACME = 0",
- paste(round(rho0, 2), collapse = "; "), "rho",
- "","", nrow(med_df), "",
- paste0("residual correlation between the mediator and outcome errors at which ",
- "the ACME reaches zero; evaluated on a grid with rho.by = 0.1"))
- ## =============================================================================
- ## SECTION 4 -- Supp Note 1 (sex/site/trials), Supp Note 4 (outliers),
- ## Methods (low pre-switch accuracy exclusion)
- ## =============================================================================
- cat("\n\n========== SECTION 4: Robustness / sensitivity ==========\n")
- ## 4-1 sex / site adjusted associations [Supp Note 1]
- pairs <- list(c("sEBR","r_DL_sw","sEBR-rDLPFC"),
- c("sEBR","sw_acc","sEBR-sw_acc"),
- c("r_DL_sw","sw_acc","rDLPFC-sw_acc"))
- for (cv in c("sex","site")) {
- if (all(is.na(raw_data[[cv]]))) next
- cat(sprintf("\n-- adjust for %s --\n", cv))
- for (pr in pairs) {
- r <- pspear(raw_data[[pr[1]]], raw_data[[pr[2]]], raw_data[cv])
- cat(sprintf("%-14s rho=%.2f p=%s n=%d\n", pr[3], r["rho"], fmtp(r["p"]), r["n"]))
- add_row("SuppNote1", paste0(pr[3]," | ", cv), round(r["rho"],3), "partial Spearman","", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- }
- }
- ## 4-2 LME robustness: add sex / site / phase-matched trial count [Supp Note 1]
- dlx <- df_long %>% mutate(sex = raw_data$sex[match(Subject, raw_data$ID)],
- site = raw_data$site[match(Subject, raw_data$ID)])
- fit_lrt <- function(extra, sub) {
- d <- dlx %>% drop_na(OxyHb, age, all_of(sub))
- m0 <- lmer(OxyHb ~ (Phase*Hemisphere*Region)*age + (1|Subject), d, REML = FALSE)
- m1 <- lmer(as.formula(paste0("OxyHb ~ (Phase*Hemisphere*Region)*age + ", extra, " + (1|Subject)")),
- d, REML = FALSE)
- an <- anova(m0, m1); list(chi = an$Chisq[2], df = an$Df[2], p = an$`Pr(>Chisq)`[2])
- }
- for (s in list(c("sex","sex"), c("site","site"))) {
- r <- fit_lrt(s[1], s[2]); cat(sprintf("LME +%s: chi2(%d)=%.3f p=%s\n", s[1], r$df, r$chi, fmtp(r$p)))
- add_row("SuppNote1", paste0("LME age vs age+", s[1]), round(r$chi,3), "chi2", r$df, fmtp(r$p), "", "age")
- }
- dl2 <- df_long %>% drop_na(OxyHb, age, PhaseRep)
- m_a <- lmer(OxyHb ~ (Phase*Hemisphere*Region)*age + (1|Subject), dl2, REML = FALSE)
- m_r <- lmer(OxyHb ~ (Phase*Hemisphere*Region)*age + PhaseRep + (1|Subject), dl2, REML = FALSE)
- m_rm <- lmer(OxyHb ~ (Phase*Hemisphere*Region)*age + PhaseRep*age + (1|Subject), dl2, REML = FALSE)
- # Step 1: does the PhaseRep main effect improve fit over the age model? (1 df)
- # Step 2: does the PhaseRep x age interaction improve fit over the model that
- # ALREADY contains the PhaseRep main effect? (1 df)
- # Step 2 must compare m_r with m_rm, not m_a with m_rm: the latter would test the
- # main effect and the interaction jointly on 2 df. The comparison below isolates
- # the interaction on 1 df, as the label states.
- lr1 <- anova(m_a, m_r) # m_a -> m_a + PhaseRep
- lr2 <- anova(m_r, m_rm) # m_r -> m_r + PhaseRep:age
- stopifnot(lr1$Df[2] == 1, lr2$Df[2] == 1) # both steps must be 1 df
- cat(sprintf("LME +trial: chi2(%d)=%.3f p=%s | +trial x age: chi2(%d)=%.3f p=%s\n",
- lr1$Df[2], lr1$Chisq[2], fmtp(lr1$`Pr(>Chisq)`[2]),
- lr2$Df[2], lr2$Chisq[2], fmtp(lr2$`Pr(>Chisq)`[2])))
- add_row("SuppNote1","LME age vs age+PhaseRep", round(lr1$Chisq[2],3),"chi2",lr1$Df[2],
- fmtp(lr1$`Pr(>Chisq)`[2]),"","age+rep",
- "LRT of the PhaseRep main effect (m_a vs m_r)")
- add_row("SuppNote1","LME PhaseRep x age", round(lr2$Chisq[2],3),"chi2",lr2$Df[2],
- fmtp(lr2$`Pr(>Chisq)`[2]),"","age+rep",
- "LRT of the PhaseRep x age interaction over the main-effect model (m_r vs m_rm)")
- # Reference only: the joint 2-df test of both terms together, retained for
- # traceability. The 1-df test above is the one reported.
- lr2_joint <- anova(m_a, m_rm)
- cat(sprintf(" [reference, joint 2-df test] +trial and trial x age: chi2(%d)=%.3f p=%s\n",
- lr2_joint$Df[2], lr2_joint$Chisq[2], fmtp(lr2_joint$`Pr(>Chisq)`[2])))
- add_row("SuppNote1","LME PhaseRep + PhaseRep x age (joint)", round(lr2_joint$Chisq[2],3),
- "chi2", lr2_joint$Df[2], fmtp(lr2_joint$`Pr(>Chisq)`[2]),"","age+rep",
- "joint 2-df test of both terms; reference only, superseded by the 1-df test above")
- arp <- anova(m_r)
- for (eff in c("Phase:age","Hemisphere:age","Region:age"))
- add_row("SuppNote1", paste0(eff," (with trial)"), round(arp[eff,"F value"],2),"F",
- paste0(arp[eff,"NumDF"],",",round(arp[eff,"DenDF"],0)), fmtp(arp[eff,"Pr(>F)"]),"","age+rep")
- ## Effect sizes for the trial-adjusted model and for the model comparisons.
- es_rep <- as.data.frame(eta_squared(anova(m_r, type = 3), partial = TRUE,
- ci = 0.95, alternative = "two.sided"))
- for (eff in c("Phase:age","Hemisphere:age","Region:age")) {
- i <- match(eff, es_rep$Parameter)
- add_row("SuppNote1", paste0("partial eta2 ", eff, " (with trial)"),
- signif(es_rep$Eta2_partial[i],3), "eta2_p","","","", "age+rep","",
- round(es_rep$CI_low[i],3), round(es_rep$CI_high[i],3))
- }
- for (s in c("sex","site")) {
- if (all(is.na(raw_data[[s]]))) next
- d <- dlx %>% drop_na(OxyHb, age, all_of(s))
- q0 <- lmer(OxyHb ~ (Phase*Hemisphere*Region)*age + (1|Subject), d, REML = FALSE)
- q1 <- lmer(as.formula(paste0("OxyHb ~ (Phase*Hemisphere*Region)*age + ", s, " + (1|Subject)")),
- d, REML = FALSE)
- dm <- r2_nakagawa(q1)$R2_marginal - r2_nakagawa(q0)$R2_marginal
- cat(sprintf("Change in marginal R2 when adding %s: %.4f\n", s, dm))
- add_row("SuppNote1", paste0("Change in marginal R2 with ", s), round(dm,4), "dR2","","","","age")
- }
- dm_rep <- r2_nakagawa(m_r)$R2_marginal - r2_nakagawa(m_a)$R2_marginal
- cat(sprintf("Change in marginal R2 when adding trial count: %.4f\n", dm_rep))
- add_row("SuppNote1","Change in marginal R2 with trial count", round(dm_rep,4),
- "dR2","","","","age+rep")
- ## 4-3 rep-corrected partial correlations [Supp Note 1]
- for (pr in list(c("sEBR","r_DL_sw","sEBR-rDLPFC"), c("sEBR","sw_acc","sEBR-sw_acc"))) {
- for (z in list(list("sw_rep","| sw_rep"), list(c("age","sw_rep"),"| age+sw_rep"))) {
- r <- pspear(raw_data[[pr[1]]], raw_data[[pr[2]]], raw_data[z[[1]]])
- add_row("SuppNote1", paste0(pr[3]," ", z[[2]]), round(r["rho"],3), "partial Spearman","", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- }
- }
- ## 4-4 completed-trial descriptives + age x trials [Methods / Supp Note 1]
- desc <- raw_data %>% summarise(across(c(Pre_rep, Post_rep, Mix_rep),
- list(m = ~mean(.,na.rm=TRUE), s = ~sd(.,na.rm=TRUE))))
- print(desc)
- for (ph in c("Pre_rep","Post_rep","Mix_rep")) {
- add_row("Methods/SuppNote1", paste0("completed trials ", ph),
- sprintf("%.1f+/-%.1f", mean(raw_data[[ph]],na.rm=TRUE), sd(raw_data[[ph]],na.rm=TRUE)), "desc","","",nrow(raw_data))
- ra <- sp(raw_data$age, raw_data[[ph]])
- add_row("SuppNote1", paste0("age ~ ", ph), round(ra["rho"],3), "Spearman rho","", fmtp(ra["p"]), ra["n"],
- "", "", round(ra["ci_low"],3), round(ra["ci_high"],3))
- }
- ## 4-5 Outlier robustness [Supp Note 4]
- raw_data$sEBR_w <- winsorize(raw_data$sEBR, 0.05)
- thr <- mean(raw_data$sEBR, na.rm = TRUE) + 3*sd(raw_data$sEBR, na.rm = TRUE)
- trim <- raw_data %>% filter(is.na(sEBR) | sEBR <= thr)
- for (z in list(list("sEBR_w","sw_acc","winsorized","sEBR-sw_acc"),
- list("sEBR_w","r_DL_sw","winsorized","sEBR-rDLPFC"))) {
- r <- sp(raw_data[[z[[1]]]], raw_data[[z[[2]]]])
- add_row("SuppNote4", paste0(z[[4]]," ", z[[3]]), round(r["rho"],3),"Spearman rho","", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- }
- for (z in list(list("sw_acc","trimmed(n-3)","sEBR-sw_acc"), list("r_DL_sw","trimmed(n-3)","sEBR-rDLPFC"))) {
- r <- sp(trim$sEBR, trim[[z[[1]]]])
- add_row("SuppNote4", paste0(z[[3]]," ", z[[2]]), round(r["rho"],3),"Spearman rho","", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- }
- ## 4-6 Low pre-switch accuracy exclusion [Methods]
- excl <- raw_data %>% filter(pre_acc <= 0.5)
- cat("\nExcluded (pre_acc<=0.5): n =", nrow(excl), "| IDs:", paste(excl$ID, collapse=","), "\n")
- de <- raw_data %>% filter(pre_acc > 0.5)
- for (z in list(list("sEBR","sw_acc","sEBR-sw_acc"), list("sEBR","r_DL_sw","sEBR-rDLPFC"),
- list("age","sw_acc","age-sw_acc"), list("age","sEBR","age-sEBR"))) {
- r <- sp(de[[z[[1]]]], de[[z[[2]]]])
- add_row("Methods", paste0("after excl: ", z[[3]]), round(r["rho"],3),"Spearman rho","", fmtp(r["p"]), r["n"],
- "", "", round(r["ci_low"],3), round(r["ci_high"],3))
- }
- ## =============================================================================
- ## SECTION 5 -- Write summary CSVs
- ## =============================================================================
- cat("\n\n========== SECTION 5: Writing summaries ==========\n")
- summary_df <- dplyr::bind_rows(SUMMARY)
- write.csv(summary_df, file.path(OUTDIR, "results_summary_generated.csv"), row.names = FALSE)
- cat("Wrote:", file.path(OUTDIR, "results_summary_generated.csv"),
- "(", nrow(summary_df), "rows )\n")
- cat("Source-data CSVs in:", FIGDIR, "\n")
- ## ---- session information -----------------------------------------------
- ## Written to outputs_summary/sessionInfo.txt as well as echoed to the console.
- ## The script name below is the single source of truth for that file's header;
- ## keep it in sync with the filename if this script is revised again.
- SCRIPT_NAME <- "MainAnalysis.R"
- si_path <- file.path(OUTDIR, "sessionInfo.txt")
- si_con <- file(si_path, open = "wt")
- writeLines(c(
- paste0(SCRIPT_NAME, " -- session information"),
- paste0("Generated: ", format(Sys.time(), "%Y-%m-%d %H:%M:%S %Z")),
- paste0("Data file: ", data_path),
- ""), si_con)
- capture.output(sessionInfo(), file = si_con)
- close(si_con)
- cat("Wrote:", si_path, "\n")
- cat("\n=== Session info ===\n"); print(sessionInfo())
- # ==============================================================================
- # END
- # ==============================================================================
MainAnalysis.R, no license · at the source
Overview
- Institute of Health and Sport Sciences, University of Tsukuba, Ibaraki, Japan
- Graduate School of Letters, Kyoto University, Kyoto, Japan
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repository
Its files are read in the Code ↔ Paper reader above, with 6 matches between paragraphs and lines of code.
OSF mrz89
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
- 26 September 2026: the link answers (HTTP 200)
3 files
- code/
Fig4_plot.R , R, 326 lines, 2 matches - code/
MainAnalysis.R , R, 767 lines, 4 matches - README.md, Text, 180 lines
Code availability statement
The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to the authors' code: OSF mrz89
Read it in the paper: doi.org/10.1038/s44271-026-00524-6.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 2 scripts, each with its path and the digest of its content;
- 6 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability statement
The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- no repository, dataset or request procedure was recognized in it
Read it in the paper: doi.org/10.1038/s44271-026-00524-6.
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, issue, pages, dates, 4 authors, 2 keywords, 1 funder, 80 references.
Cite
This paper
Kuwamizu, R., Yamamoto, N., Otani, K., & Moriguchi, Y. (2026). Emergent blink rate in early childhood is associated with neural origins of executive function. Communications psychology, 4(1), 126. https://
BibTeX
@article{kuwamizu2026eme
author = {Kuwamizu, Ryuta and Yamamoto, Nozomi and Otani, Kota and Moriguchi, Yusuke},
title = {{Emergent blink rate in early childhood is associated with neural origins of executive function}},
journal = {Communications psychology},
year = {2026},
month = sep,
volume = {4},
number = {1},
pages = {126},
publisher = {Nature Publishing Group},
issn = {2731-9121},
doi = {10.1038/
url = {https://
pmid = {42744968},
pmcid = {PMC13578739}
}
RIS
TY - JOUR
AU - Kuwamizu, Ryuta
AU - Yamamoto, Nozomi
AU - Otani, Kota
AU - Moriguchi, Yusuke
TI - Emergent blink rate in early childhood is associated with neural origins of executive function
T2 - Communications psychology
J2 - Commun Psychol
PY - 2026
DA - 2026/
VL - 4
IS - 1
SP - 126
SN - 2731-9121
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Emergent blink rate in early childhood is associated with neural origins of executive function",
"container-title": "Communications psychology",
"author": [
{
"family": "Kuwamizu",
"given": "Ryuta"
},
{
"family": "Yamamoto",
"given": "Nozomi"
},
{
"family": "Otani",
"given": "Kota"
},
{
"family": "Moriguchi",
"given": "Yusuke"
}
],
"container-title-short":
"volume": "4",
"issue": "1",
"page": "126",
"DOI": "10.1038/
"PMID": "42744968",
"PMCID": "PMC13578739",
"ISSN": "2731-9121",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
15
]
]
}
}
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-69950-8 [code]
- Dopaminergic processes predict temporal distortions in event memory.Journal: Nature communicationsIn common: rstatix, easystats, emmeans, 4 other tools, cognitive, 8 references
- [2] doi:10.1073/pnas.2603114123 [code]
- The human hippocampus can pattern separate memories by meaning.Journal: Proceedings of the National Academy of Sciences of the United States of AmericaIn common: rstatix, easystats, emmeans, 5 other tools, cognitive
- [3] doi:10.1016/j.neuroimage.2026.122115 [code]
- Midfrontal theta power relates to response speeding following frustrative nonreward.Journal: NeuroImageIn common: rstatix, easystats, emmeans, 5 other tools, cognitive
- [4] doi:10.1038/s41467-026-74753-y [code]
- A human-specific microRNA controls the timing of excitatory synaptogenesis.Journal: Nature communicationsIn common: rstatix, easystats, emmeans, 5 other tools
- [5] doi:10.1038/s41398-026-04010-9 [code]
- Bullying victimization and brain development: a longitudinal structural magnetic resonance imaging study from adolescence to early adulthood.Journal: Translational psychiatryIn common: rstatix, easystats, emmeans, 5 other tools
- [6] doi:10.64898/2026.05.08.26348885 [code]
- Insights from nine nights of self-applied, low-density sleep EEG during sleep restriction therapy: a proof-of-concept evaluationJournal: medRxiv (preprint)In common: rstatix, easystats, emmeans, 5 other tools
- [7] doi:10.1126/sciadv.aeb8106 [code]
- A thyroid hormone-mediated opsin switch initiates metamorphosis in a proto-vertebrate.Journal: Science advancesIn common: rstatix, easystats, emmeans, 5 other tools
- [8] doi:10.1016/j.isci.2026.116047 [code]
- Deciding to simulate: Cognitive mechanisms of predicting the decisions of others.Journal: iScienceIn common: rstatix, easystats, emmeans, 4 other tools, cognitive
- [9] doi:10.1038/s41467-026-73865-9 [code]
- Histamine shapes the neurocomputational dynamics of human learning.Journal: Nature communicationsIn common: rstatix, easystats, emmeans, 4 other tools, cognitive
- [10] doi:10.1038/s42003-026-10040-2 [code]
- Functional dissociation of language and theory of mind in the developing superior temporal lobe.Journal: Communications biologyIn common: rstatix, emmeans, lmerTest, 3 other tools, cognitive, 1 reference
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: 1 repository of the authors' code, each at its verified commit and with its license, 2 scripts, and 6 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:a8ecbde99cae79ae…
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
[.
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.
