OSCR

APOE ε4-associated hippocampal atrophy trajectories across the Alzheimer's disease continuum: a systematic review, meta-analysis, and longitudinal validation.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [1] § Methods › Meta-analysis strategy ↔ meta/meta.R, lines 120 to 261 · score 0.65 · bias corrected, meta regression, trim, Baujat, multiverse, fill · Report a mismatch
  2. [2] § Methods › Statistical models ↔ ADNI/adni_analysis.py, lines 67 to 95 · score 0.62 · field strength, APOE4 dosage, ADNI model, baseline diagnosis, ICV, Sex · Report a mismatch
  3. [3] § Results › Establishing the baseline effect and sources of heterogeneity ↔ meta/meta.R, lines 120 to 261 · score 0.59 · bias corrected, meta regressions, trim, female, fill, uncorrected · Report a mismatch
  4. [4] § Methods › Statistical models ↔ ADNI/adni_analysis.py, lines 133 to 170 · score 0.52 · field strength, baseline diagnosis interaction, CN, ICV, sex, age · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R · 505 lines · 30 KB · no license · 2 matches

  1. suppressPackageStartupMessages({
  2. library(tidyverse)
  3. library(meta)
  4. library(data.table)
  5. library(gridExtra)
  6. library(ggrepel)
  7. })
  8. pdf(NULL)
  9. settings.meta(
  10. col.square.lines = "transparent",
  11. col.diamond.lines = "transparent",
  12. text.tau2 = "τ²",
  13. text.I2 = "I²"
  14. )
  15. # --- Part 0: Configuration ---
  16. CORRELATION_R <- 0.85
  17. TIMESTAMP <- format(Sys.time(), "%Y-%m-%d_%H-%M")
  18. OUTPUT_BASE_DIR <- file.path(dirname(sys.frame(1)$ofile), "..", "output")
  19. OUTPUT_FOLDER <- OUTPUT_BASE_DIR
  20. PALETTE <- c(
  21. "Overall" = "#3C5488",
  22. "AD" = "#E64B35",
  23. "CN" = "#4DBBD5",
  24. "MCI" = "#E6A0C4",
  25. "Homo" = "#B09C85",
  26. "Hetero" = "#00A087",
  27. "Corrected" = "#8491B4",
  28. "Uncorrected" = "#91D1C2",
  29. "Bias" = "black"
  30. )
  31. THEME_MINIMAL <- theme_classic(base_size = 16) +
  32. theme(
  33. plot.title = element_text(hjust = 0.5, size = 19, face = "bold"),
  34. axis.title = element_text(size = 17, face = "bold"),
  35. axis.text = element_text(size = 14),
  36. axis.line = element_line(linewidth = 0.8),
  37. axis.ticks = element_line(linewidth = 0.8),
  38. legend.text = element_text(size = 14),
  39. legend.title = element_text(size = 14, face = "bold"),
  40. legend.background = element_blank(),
  41. legend.key = element_blank()
  42. )
  43. file_path <- file.path(dirname(sys.frame(1)$ofile), "..", "data", "meta.csv")
  44. if (!file.exists(file_path)) stop(paste("Data file not found:", file_path))
  45. print(paste("Using input file:", file_path))
  46. if (!dir.exists(OUTPUT_FOLDER)) dir.create(OUTPUT_FOLDER, recursive = TRUE)
  47. # --- Helper Functions ---
  48. fmt_p_exact <- function(p) {
  49. if (is.na(p)) return("NA")
  50. if (p < 0.0001) { return(formatC(p, format = "e", digits = 2)) }
  51. return(sprintf("%.4f", p))
  52. }
  53. extract_row <- function(model, name) {
  54. if (is.null(model)) return(NULL)
  55. data.frame(Analysis = name, k = model$k, SMD = round(model$TE.random, 4),
  56. CI_Lower = round(model$lower.random, 4), CI_Upper = round(model$upper.random, 4),
  57. P_Value_Display = fmt_p_exact(model$pval.random), P_Value_Raw = model$pval.random,
  58. I2 = paste0(round(model$I2 * 100, 1), "%"), Tau2 = round(model$tau2, 4))
  59. }
  60. extract_reg <- function(reg_model, name) {
  61. if (is.null(reg_model)) return(NULL)
  62. data.frame(Predictor = name, Coef = round(reg_model$b[2], 4),
  63. SE = round(reg_model$se[2], 4), P_Value_Display = fmt_p_exact(reg_model$pval[2]),
  64. P_Value_Raw = reg_model$pval[2])
  65. }
  66. # --- Part 1: Data Loading ---
  67. print("Step 1/5: Data preparation...")
  68. df_raw <- fread(file_path)
  69. core_cols <- c("apoe4_n", "apoe4_Mean", "apoe4_SD", "no_apoe4_n", "no_apoe4_Mean", "no_apoe4_SD", "age")
  70. df_cleaned <- df_raw %>% select(where(~!all(is.na(.)))) %>% filter(if_all(all_of(core_cols), ~ !is.na(.)))
  71. if("female_percentage" %in% colnames(df_cleaned)) df_cleaned$female_percentage <- as.numeric(df_cleaned$female_percentage)
  72. df_bilateral <- df_cleaned %>% filter(hemisphere == "bilateral") %>%
  73. rename(n.e = apoe4_n, mean.e = apoe4_Mean, sd.e = apoe4_SD, n.c = no_apoe4_n, mean.c = no_apoe4_Mean, sd.c = no_apoe4_SD)
  74. df_combined <- df_cleaned %>% filter(hemisphere %in% c("left", "right")) %>%
  75. pivot_wider(id_cols = any_of(c("title", "diagnosis", "apoe4_n", "no_apoe4_n", "age", "correction_method", "year", "e4_dosage", "female_percentage", "segmentation_method")),
  76. names_from = hemisphere, values_from = c(apoe4_Mean, apoe4_SD, no_apoe4_Mean, no_apoe4_SD)) %>%
  77. mutate(mean.e = apoe4_Mean_left + apoe4_Mean_right,
  78. sd.e = sqrt(apoe4_SD_left^2 + apoe4_SD_right^2 + 2 * CORRELATION_R * apoe4_SD_left * apoe4_SD_right),
  79. mean.c = no_apoe4_Mean_left + no_apoe4_Mean_right,
  80. sd.c = sqrt(no_apoe4_SD_left^2 + no_apoe4_SD_right^2 + 2 * CORRELATION_R * no_apoe4_SD_right * no_apoe4_SD_right)) %>%
  81. rename(n.e = apoe4_n, n.c = no_apoe4_n)
  82. df_final <- bind_rows(df_bilateral, df_combined) %>% rename(studlab = title) %>%
  83. mutate(studlab = str_replace_all(studlab, "[^A-Za-z0-9 ]", "")) %>%
  84. select(studlab, diagnosis, n.e, mean.e, sd.e, n.c, mean.c, sd.c, age, e4_dosage, correction_method, female_percentage, segmentation_method)
  85. write.csv(df_final, file.path(OUTPUT_FOLDER, "Data_Cleaned_Input.csv"), row.names = FALSE)
  86. df_corrected <- df_final %>% filter(correction_method != "None")
  87. df_uncorrected <- df_final %>% filter(correction_method == "None")
  88. write.csv(df_corrected, file.path(OUTPUT_FOLDER, "Data_Corrected_Input.csv"), row.names = FALSE)
  89. write.csv(df_uncorrected, file.path(OUTPUT_FOLDER, "Data_Uncorrected_Input.csv"), row.names = FALSE)
  90. # --- Helper Functions for Plotting ---
  91. draw_forest_clean <- function(meta_obj, filename, title, color_base) {
  92. if (is.null(meta_obj) || meta_obj$k < 2) return(NULL)
  93. png(filename=file.path(OUTPUT_FOLDER, filename), width=9, height=max(6, meta_obj$k*0.35+2.5), units="in", res=600, type="cairo")
  94. par(mar = c(1, 0, 1, 0), font.main = 2, cex.main = 1.3)
  95. forest(meta_obj, studlab=FALSE, layout="JAMA", comb.fixed=FALSE, header.line=TRUE, leftcols=c("n.e","mean.e","sd.e","n.c","mean.c","sd.c"), leftlabs=c("N","Mean","SD","N","Mean","SD"), rightcols=c("effect","ci","w.random"), rightlabs=c("SMD","95% CI","Weight"), label.e="APOE e4 (+)", label.c="APOE e4 (-)", col.square=color_base, col.diamond="maroon", col.inside="black", col.square.lines="transparent", col.diamond.lines="transparent", col.study=color_base, fontsize=12, digits=2, digits.pval=3, print.tau2=TRUE, print.I2=TRUE, print.pval.Q=TRUE, just.addcols="center", main=title)
  96. dev.off()
  97. }
  98. draw_bubble_plot <- function(reg_model, moderator_name, filename, color_point) {
  99. if (is.null(reg_model)) return(NULL)
  100. plot_data <- data.frame(y=reg_model$yi, x=reg_model$X[,2], w=1/reg_model$vi)
  101. intercept <- reg_model$b[1]; slope <- reg_model$b[2]; p_val <- reg_model$pval[2]
  102. p <- ggplot(plot_data, aes(x=x, y=y)) + geom_point(aes(size=w), color=color_point, alpha=0.6) + geom_abline(intercept=intercept, slope=slope, color="black", size=1, linetype="solid") + annotate("text", x=min(plot_data$x), y=max(plot_data$y), label=paste0("Slope P = ", fmt_p_exact(p_val)), hjust=0, vjust=1, size=6, fontface="bold") + scale_size_continuous(range=c(2,8), guide="none") + labs(title=paste("Meta-Regression:", moderator_name), x=moderator_name, y="Effect Size (SMD)") + THEME_MINIMAL
  103. ggsave(file.path(OUTPUT_FOLDER, filename), p, width=7, height=6, dpi=600)
  104. }
  105. # --- Main Analysis Function ---
  106. run_full_analysis <- function(data, prefix) {
  107. if(nrow(data) < 2) { print(paste("Skipping", prefix, "- insufficient data")); return(NULL) }
  108. print(paste("Analyzing", prefix, "group..."))
  109. # Meta-analysis models
  110. meta_main <- metacont(data=data, n.e=n.e, mean.e=mean.e, sd.e=sd.e, n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab, sm="SMD", method.tau="REML", hakn=TRUE, comb.fixed=FALSE)
  111. meta_ad <- if(sum(data$diagnosis=="AD")>1) metacont(data=filter(data, diagnosis=="AD"), n.e=n.e, mean.e=mean.e, sd.e=sd.e, n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab, sm="SMD", method.tau="REML", hakn=TRUE, comb.fixed=FALSE) else NULL
  112. meta_cn <- if(sum(data$diagnosis=="CN")>1) metacont(data=filter(data, diagnosis=="CN"), n.e=n.e, mean.e=mean.e, sd.e=sd.e, n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab, sm="SMD", method.tau="REML", hakn=TRUE, comb.fixed=FALSE) else NULL
  113. meta_mci <- if(sum(data$diagnosis=="MCI")>1) metacont(data=filter(data, diagnosis=="MCI"), n.e=n.e, mean.e=mean.e, sd.e=sd.e, n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab, sm="SMD", method.tau="REML", hakn=TRUE, comb.fixed=FALSE) else NULL
  114. meta_homo <- if(sum(data$e4_dosage==2, na.rm=TRUE)>1) metacont(data=filter(data, e4_dosage==2), n.e=n.e, mean.e=mean.e, sd.e=sd.e, n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab, sm="SMD", comb.fixed=FALSE) else NULL
  115. meta_het <- if(sum(data$e4_dosage==1, na.rm=TRUE)>1) metacont(data=filter(data, e4_dosage==1), n.e=n.e, mean.e=mean.e, sd.e=sd.e, n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab, sm="SMD", comb.fixed=FALSE) else NULL
  116. tf_model <- trimfill(meta_main)
  117. loo_model <- metainf(meta_main, pooled="random")
  118. meta_reg_age <- metareg(meta_main, ~age)
  119. meta_reg_sex <- if(sum(!is.na(data$female_percentage))>=3) metareg(meta_main, ~female_percentage) else NULL
  120. meta_reg_cn_age <- if(!is.null(meta_cn) && meta_cn$k>=3) metareg(meta_cn, ~age) else NULL
  121. # Tables
  122. results_list <- list(extract_row(meta_main, "Overall"), extract_row(meta_ad, "Subgroup: AD"), extract_row(meta_mci, "Subgroup: MCI"), extract_row(meta_cn, "Subgroup: CN"), extract_row(tf_model, "Bias Corrected"))
  123. write.csv(bind_rows(results_list), file.path(OUTPUT_FOLDER, paste0("Table_", prefix, "_Main_Results_Summary.csv")), row.names=FALSE)
  124. dosage_list <- list(extract_row(meta_homo, "Dosage: Homozygotes"), extract_row(meta_het, "Dosage: Heterozygotes"))
  125. write.csv(bind_rows(dosage_list), file.path(OUTPUT_FOLDER, paste0("Table_", prefix, "_Dosage_Analysis.csv")), row.names=FALSE)
  126. reg_list <- list(extract_reg(meta_reg_age, "Age (Overall)"), extract_reg(meta_reg_sex, "Female % (Overall)"), extract_reg(meta_reg_cn_age, "Age (CN Subgroup)"))
  127. write.csv(bind_rows(reg_list), file.path(OUTPUT_FOLDER, paste0("Table_", prefix, "_MetaRegression.csv")), row.names=FALSE)
  128. loo_df <- data.frame(Omitted_Study=loo_model$studlab, New_SMD=round(loo_model$TE, 4), New_I2=round(loo_model$I2*100, 1))
  129. write.csv(loo_df, file.path(OUTPUT_FOLDER, paste0("Table_", prefix, "_Leave_One_Out_Data.csv")), row.names=FALSE)
  130. # --- Leave-One-Out Forest Plot ---
  131. loo_filename <- if(prefix == "Corrected") "FigS3a_Forest_Corrected_LeaveOneOut.png" else "FigS3b_Forest_Uncorrected_LeaveOneOut.png"
  132. png(filename = file.path(OUTPUT_FOLDER, loo_filename),
  133. width = 9, height = max(6, meta_main$k * 0.35 + 2.5), units = "in", res = 600, type = "cairo")
  134. par(mar = c(1, 0, 1, 0), font.main = 2, cex.main = 1.3)
  135. forest(loo_model, studlab = FALSE, layout = "JAMA", comb.fixed = FALSE,
  136. col.square = PALETTE[["Overall"]], col.diamond = "maroon",
  137. col.inside = "black",
  138. col.square.lines = "transparent", col.diamond.lines = "transparent",
  139. fontsize = 12, digits = 2, digits.pval = 3,
  140. header.line = TRUE,
  141. just.addcols = "center",
  142. main = paste0(prefix, ": Leave-One-Out"),
  143. col.study = PALETTE[["Overall"]])
  144. dev.off()
  145. # Forest plots
  146. main_forest_filename <- if(prefix == "Corrected") "Fig3a_Forest_Corrected_Overall.png" else "Fig3b_Forest_Uncorrected_Overall.png"
  147. png(filename=file.path(OUTPUT_FOLDER, main_forest_filename), width=9, height=max(6, meta_main$k*0.35+2.5), units="in", res=600, type="cairo")
  148. par(mar = c(1, 0, 1, 0), font.main = 2, cex.main = 1.3)
  149. forest(meta_main, byvar=data$diagnosis, studlab=FALSE, layout="JAMA", comb.fixed=FALSE, header.line=TRUE, leftcols=c("n.e","mean.e","sd.e","n.c","mean.c","sd.c"), leftlabs=c("N","Mean","SD","N","Mean","SD"), rightcols=c("effect","ci","w.random"), rightlabs=c("SMD","95% CI","Weight"), col.square=PALETTE[["Overall"]], col.diamond="maroon", col.inside="black", col.square.lines="transparent", col.diamond.lines="transparent", col.study=PALETTE[["Overall"]], just.addcols="center", print.tau2=TRUE, print.I2=TRUE, print.pval.Q=TRUE, fontsize=12, digits=2, digits.pval=3, main=paste0(prefix, ": APOE4 Effect by Diagnosis"))
  150. dev.off()
  151. # Subgroup/dosage filenames based on prefix
  152. if (prefix == "Corrected") {
  153. fn_ad <- "FigS1a_Forest_Corrected_AD.png"
  154. fn_cn <- "FigS1b_Forest_Corrected_CN.png"
  155. fn_mci <- "Supp_Forest_Corrected_MCI.png"
  156. fn_homo <- "FigS1c_Forest_Corrected_Homo.png"
  157. fn_hetero <- "FigS1d_Forest_Corrected_Hetero.png"
  158. } else {
  159. fn_ad <- "Supp_Forest_Uncorrected_AD.png"
  160. fn_cn <- "FigS2a_Forest_Uncorrected_CN.png"
  161. fn_mci <- "Supp_Forest_Uncorrected_MCI.png"
  162. fn_homo <- "FigS2b_Forest_Uncorrected_Homo.png"
  163. fn_hetero <- "FigS2c_Forest_Uncorrected_Hetero.png"
  164. }
  165. draw_forest_clean(meta_ad, fn_ad, paste0(prefix, ": AD Subgroup"), PALETTE[["AD"]])
  166. draw_forest_clean(meta_cn, fn_cn, paste0(prefix, ": CN Subgroup"), PALETTE[["CN"]])
  167. draw_forest_clean(meta_mci, fn_mci, paste0(prefix, ": MCI Subgroup"), PALETTE[["MCI"]])
  168. draw_forest_clean(meta_homo, fn_homo, paste0(prefix, ": Homozygotes"), PALETTE[["Homo"]])
  169. draw_forest_clean(meta_het, fn_hetero, paste0(prefix, ": Heterozygotes"), PALETTE[["Hetero"]])
  170. # --- Funnel Plot with Trim-and-Fill ---
  171. funnel_filename <- if(prefix == "Corrected") "FigS4a_Funnel_Corrected_TrimFill.png" else "FigS4b_Funnel_Uncorrected_TrimFill.png"
  172. png(filename = file.path(OUTPUT_FOLDER, funnel_filename),
  173. width = 7, height = 6, units = "in", res = 600, type = "cairo")
  174. par(mar = c(4, 4, 2, 1), font.main = 2, cex.main = 1.3,
  175. cex.lab = 1.2, font.lab = 2, cex.axis = 1.1)
  176. funnel(tf_model,
  177. xlab = "Standardized Mean Difference (Hedges' g)",
  178. studlab = FALSE,
  179. main = paste0(prefix, ": Funnel Plot"))
  180. dev.off()
  181. # Prepare summary data for combined plot
  182. summary_data <- data.frame(
  183. Group=c("Overall","AD","CN","MCI","Homo","Hetero","Bias Corrected"),
  184. Category=c("Main","Diagnosis","Diagnosis","Diagnosis","Dose","Dose","Bias"),
  185. SMD=c(meta_main$TE.random, if(!is.null(meta_ad)) meta_ad$TE.random else NA, if(!is.null(meta_cn)) meta_cn$TE.random else NA, if(!is.null(meta_mci)) meta_mci$TE.random else NA, if(!is.null(meta_homo)) meta_homo$TE.random else NA, if(!is.null(meta_het)) meta_het$TE.random else NA, tf_model$TE.random),
  186. Lower=c(meta_main$lower.random, if(!is.null(meta_ad)) meta_ad$lower.random else NA, if(!is.null(meta_cn)) meta_cn$lower.random else NA, if(!is.null(meta_mci)) meta_mci$lower.random else NA, if(!is.null(meta_homo)) meta_homo$lower.random else NA, if(!is.null(meta_het)) meta_het$lower.random else NA, tf_model$lower.random),
  187. Upper=c(meta_main$upper.random, if(!is.null(meta_ad)) meta_ad$upper.random else NA, if(!is.null(meta_cn)) meta_cn$upper.random else NA, if(!is.null(meta_mci)) meta_mci$upper.random else NA, if(!is.null(meta_homo)) meta_homo$upper.random else NA, if(!is.null(meta_het)) meta_het$upper.random else NA, tf_model$upper.random),
  188. P_val=c(meta_main$pval.random, if(!is.null(meta_ad)) meta_ad$pval.random else NA, if(!is.null(meta_cn)) meta_cn$pval.random else NA, if(!is.null(meta_mci)) meta_mci$pval.random else NA, if(!is.null(meta_homo)) meta_homo$pval.random else NA, if(!is.null(meta_het)) meta_het$pval.random else NA, tf_model$pval.random)
  189. ) %>% filter(!is.na(SMD))
  190. # Baujat data
  191. b_res <- baujat(meta_main, plot=FALSE)
  192. baujat_data <- data.frame(
  193. x = b_res$x, y = b_res$y,
  194. studlab = meta_main$studlab,
  195. id = seq_len(meta_main$k)
  196. )
  197. # Meta-regression data
  198. reg_age_data <- if(!is.null(meta_reg_age)) data.frame(y=meta_reg_age$yi, x=meta_reg_age$X[,2], w=1/meta_reg_age$vi, intercept=meta_reg_age$b[1], slope=meta_reg_age$b[2], p_val=meta_reg_age$pval[2]) else NULL
  199. reg_sex_data <- if(!is.null(meta_reg_sex)) data.frame(y=meta_reg_sex$yi, x=meta_reg_sex$X[,2], w=1/meta_reg_sex$vi, intercept=meta_reg_sex$b[1], slope=meta_reg_sex$b[2], p_val=meta_reg_sex$pval[2]) else NULL
  200. reg_cn_age_data <- if(!is.null(meta_reg_cn_age)) data.frame(y=meta_reg_cn_age$yi, x=meta_reg_cn_age$X[,2], w=1/meta_reg_cn_age$vi, intercept=meta_reg_cn_age$b[1], slope=meta_reg_cn_age$b[2], p_val=meta_reg_cn_age$pval[2]) else NULL
  201. # Multiverse data
  202. # --- Sensitivity: remove most influential studies (Baujat-based) ---
  203. run_sens <- function(exclude_count) {
  204. influence_df <- data.frame(
  205. studlab = meta_main$studlab,
  206. het_contrib = b_res$x,
  207. influence = b_res$y,
  208. stringsAsFactors = FALSE
  209. )
  210. influence_df$dist <- sqrt(influence_df$het_contrib^2 + influence_df$influence^2)
  211. influence_df <- influence_df %>% arrange(desc(dist))
  212. exclude_studies <- influence_df$studlab[1:exclude_count]
  213. subset_data <- data %>% filter(!studlab %in% exclude_studies)
  214. m <- metacont(data = subset_data, n.e = n.e, mean.e = mean.e, sd.e = sd.e,
  215. n.c = n.c, mean.c = mean.c, sd.c = sd.c, studlab = studlab,
  216. sm = "SMD", method.tau = "REML", hakn = TRUE, comb.fixed = FALSE)
  217. return(data.frame(
  218. Scenario = paste0("Remove Top ", exclude_count, " Influential"),
  219. SMD = m$TE.random, Lower = m$lower.random, Upper = m$upper.random
  220. ))
  221. }
  222. max_exclude <- meta_main$k - 2
  223. multiverse_data <- bind_rows(
  224. data.frame(Scenario = "Original", SMD = meta_main$TE.random,
  225. Lower = meta_main$lower.random, Upper = meta_main$upper.random),
  226. if (max_exclude >= 1) run_sens(1) else NULL,
  227. if (max_exclude >= 3) run_sens(3) else NULL,
  228. if (max_exclude >= 5) run_sens(5) else NULL
  229. )
  230. return(list(meta_main=meta_main, meta_ad=meta_ad, meta_cn=meta_cn, meta_mci=meta_mci, meta_homo=meta_homo, meta_het=meta_het, tf_model=tf_model, summary_data=summary_data, baujat_data=baujat_data, reg_age_data=reg_age_data, reg_sex_data=reg_sex_data, reg_cn_age_data=reg_cn_age_data, multiverse_data=multiverse_data))
  231. }
  232. # --- Part 2: Execute analyses for both groups ---
  233. print("Step 2/5: Running Corrected group analysis...")
  234. results_corrected <- run_full_analysis(df_corrected, "Corrected")
  235. print("Step 3/5: Running Uncorrected group analysis...")
  236. results_uncorrected <- run_full_analysis(df_uncorrected, "Uncorrected")
  237. # --- Part 3: Combined visualization ---
  238. print("Step 4/5: Creating combined comparison plots...")
  239. if(!is.null(results_corrected) && !is.null(results_uncorrected)) {
  240. # 1. Combined Summary plot
  241. comp_summary <- bind_rows(
  242. results_corrected$summary_data %>% mutate(Method="Corrected"),
  243. results_uncorrected$summary_data %>% mutate(Method="Uncorrected")
  244. )
  245. comp_summary$Group <- factor(comp_summary$Group, levels=rev(unique(comp_summary$Group)))
  246. # Format: combined label below each point
  247. comp_summary$below_label <- paste0(
  248. sprintf("%.2f [%.2f, %.2f]", comp_summary$SMD, comp_summary$Lower, comp_summary$Upper),
  249. ", p = ", formatC(comp_summary$P_val, format = "e", digits = 2)
  250. )
  251. # Manual y offset: Corrected above, Uncorrected below; label further below each
  252. comp_summary$y_num <- as.numeric(comp_summary$Group)
  253. comp_summary$y_pos <- ifelse(comp_summary$Method == "Corrected",
  254. comp_summary$y_num + 0.35,
  255. comp_summary$y_num - 0.35)
  256. x_label_pos <- max(comp_summary$Upper, na.rm = TRUE) + 0.05
  257. p1 <- ggplot(comp_summary, aes(color=Method)) +
  258. geom_vline(xintercept=0, linetype="dashed", color="gray50") +
  259. geom_errorbarh(aes(xmin=Lower, xmax=Upper, y=y_pos), height=0.2) +
  260. geom_point(aes(x=SMD, y=y_pos), size=4) +
  261. geom_text(aes(x=x_label_pos, y=y_pos, label=below_label),
  262. size=4, color="black", hjust=0, show.legend=FALSE) +
  263. scale_y_continuous(breaks=seq_len(nlevels(comp_summary$Group)),
  264. labels=levels(comp_summary$Group),
  265. expand=expansion(add=c(0.8, 0.8))) +
  266. scale_color_manual(values=c("Corrected"=PALETTE[["Corrected"]], "Uncorrected"=PALETTE[["Uncorrected"]])) +
  267. scale_x_continuous(breaks = seq(-1.5, 0.5, 0.5)) +
  268. coord_cartesian(clip = "off") +
  269. labs(title="Effect Size Summary: ICV-Corrected vs ICV-Uncorrected", x="Effect Size (SMD)", y=NULL) +
  270. THEME_MINIMAL +
  271. theme(legend.position = c(1.15, 1.03), legend.justification = c(1, 1),
  272. axis.text.y=element_text(),
  273. plot.margin = margin(5.5, 140, 5.5, 5.5))
  274. ggsave(file.path(OUTPUT_FOLDER, "Fig3c_Combined_Summary.png"), p1, width=12, height=8, dpi=600, device=png, type="cairo")
  275. # 2. Combined Baujat plot (no outlier labels)
  276. comp_baujat <- bind_rows(
  277. results_corrected$baujat_data %>% mutate(Method = "Corrected"),
  278. results_uncorrected$baujat_data %>% mutate(Method = "Uncorrected")
  279. )
  280. p2 <- ggplot(comp_baujat, aes(x = x, y = y, color = Method)) +
  281. geom_point(size = 3, alpha = 0.7) +
  282. scale_color_manual(values = c("Corrected" = PALETTE[["Corrected"]],
  283. "Uncorrected" = PALETTE[["Uncorrected"]])) +
  284. labs(title = "Baujat Heterogeneity Diagnostic Plot",
  285. x = "Contribution to Overall Heterogeneity (Q)",
  286. y = "Influence on Pooled Result") +
  287. THEME_MINIMAL +
  288. theme(legend.position = "right")
  289. ggsave(file.path(OUTPUT_FOLDER, "Fig3d_Combined_Baujat.png"), p2, width = 8, height = 6, dpi = 600, device = png, type = "cairo")
  290. # 3. Combined Age Regression
  291. if(!is.null(results_corrected$reg_age_data) && !is.null(results_uncorrected$reg_age_data)) {
  292. comp_age <- bind_rows(results_corrected$reg_age_data %>% mutate(Method="Corrected"), results_uncorrected$reg_age_data %>% mutate(Method="Uncorrected"))
  293. p3 <- ggplot(comp_age, aes(x=x, y=y, color=Method, size=w)) + geom_point(alpha=0.6) + geom_abline(data=comp_age %>% distinct(Method, intercept, slope), aes(intercept=intercept, slope=slope, color=Method), size=1) + scale_color_manual(values=c("Corrected"=PALETTE[["Corrected"]], "Uncorrected"=PALETTE[["Uncorrected"]])) + scale_size_continuous(range=c(2,8), guide="none") + labs(title="Meta-Regression: Effect Size vs Mean Age", x="Mean Age", y="Effect Size (SMD)") + THEME_MINIMAL
  294. ggsave(file.path(OUTPUT_FOLDER, "FigS5a_MetaReg_Age.png"), p3, width=8, height=5.5, dpi=600, device=png, type="cairo")
  295. }
  296. # Combined Sex (Female %) Regression
  297. if (!is.null(results_corrected$reg_sex_data) && !is.null(results_uncorrected$reg_sex_data)) {
  298. comp_sex <- bind_rows(
  299. results_corrected$reg_sex_data %>% mutate(Method = "Corrected"),
  300. results_uncorrected$reg_sex_data %>% mutate(Method = "Uncorrected")
  301. )
  302. p_sex <- ggplot(comp_sex, aes(x = x, y = y, color = Method, size = w)) +
  303. geom_point(alpha = 0.6) +
  304. geom_abline(data = comp_sex %>% distinct(Method, intercept, slope),
  305. aes(intercept = intercept, slope = slope, color = Method), linewidth = 1) +
  306. scale_color_manual(values = c("Corrected" = PALETTE[["Corrected"]],
  307. "Uncorrected" = PALETTE[["Uncorrected"]])) +
  308. scale_size_continuous(range = c(2, 8), guide = "none") +
  309. labs(title = "Meta-Regression: Effect Size vs Female Percentage",
  310. x = "Female Percentage (%)", y = "Effect Size (SMD)") +
  311. THEME_MINIMAL
  312. ggsave(file.path(OUTPUT_FOLDER, "FigS5b_MetaReg_Sex.png"), p_sex, width = 8, height = 5.5, dpi = 600, device = png, type = "cairo")
  313. }
  314. # Combined CN-subgroup Age Regression
  315. if (!is.null(results_corrected$reg_cn_age_data) && !is.null(results_uncorrected$reg_cn_age_data)) {
  316. comp_cn_age <- bind_rows(
  317. results_corrected$reg_cn_age_data %>% mutate(Method = "Corrected"),
  318. results_uncorrected$reg_cn_age_data %>% mutate(Method = "Uncorrected")
  319. )
  320. p_cn <- ggplot(comp_cn_age, aes(x = x, y = y, color = Method, size = w)) +
  321. geom_point(alpha = 0.6) +
  322. geom_abline(data = comp_cn_age %>% distinct(Method, intercept, slope),
  323. aes(intercept = intercept, slope = slope, color = Method), linewidth = 1) +
  324. scale_color_manual(values = c("Corrected" = PALETTE[["Corrected"]],
  325. "Uncorrected" = PALETTE[["Uncorrected"]])) +
  326. scale_size_continuous(range = c(2, 8), guide = "none") +
  327. labs(title = "Meta-Regression: Effect Size vs Mean Age (CN Subgroup)",
  328. x = "Mean Age", y = "Effect Size (SMD)") +
  329. THEME_MINIMAL
  330. ggsave(file.path(OUTPUT_FOLDER, "FigS5c_MetaReg_CN_Age.png"), p_cn, width = 8, height = 5.5, dpi = 600, device = png, type = "cairo")
  331. }
  332. # 4. Combined Multiverse
  333. comp_multi <- bind_rows(results_corrected$multiverse_data %>% mutate(Method="Corrected"), results_uncorrected$multiverse_data %>% mutate(Method="Uncorrected"))
  334. p4 <- ggplot(comp_multi, aes(x=SMD, y=Scenario, color=Method)) + geom_vline(xintercept=0, linetype="dashed", color="gray50") + geom_errorbarh(aes(xmin=Lower, xmax=Upper), height=0.3, position=position_dodge(width=0.6)) + geom_point(size=4, position=position_dodge(width=0.6)) + scale_color_manual(values=c("Corrected"=PALETTE[["Corrected"]], "Uncorrected"=PALETTE[["Uncorrected"]])) + labs(title="Multiverse Sensitivity Analysis", x="Effect Size (SMD)", y=NULL) + THEME_MINIMAL
  335. ggsave(file.path(OUTPUT_FOLDER, "Fig3e_Combined_Multiverse.png"), p4, width=9, height=5, dpi=600, device=png, type="cairo")
  336. }
  337. print(paste("Analysis complete! Results in:", OUTPUT_FOLDER))
  338. # ===================================================================
  339. # Part 4: Sensitivity Analyses
  340. # ===================================================================
  341. # --- 4a. Excluding Wang 2019 (ADNI overlap) ---
  342. print("Step 4a: Sensitivity - Excluding Wang 2019...")
  343. wang_pattern <- "Relationship.*Hippocampal.*Delayed"
  344. wang_rows <- df_corrected %>% filter(grepl(wang_pattern, studlab, ignore.case = TRUE))
  345. cat("Wang 2019 cohorts found:\n")
  346. print(wang_rows %>% select(studlab, diagnosis, n.e, n.c, correction_method))
  347. df_corrected_no_wang <- df_corrected %>% filter(!grepl(wang_pattern, studlab, ignore.case = TRUE))
  348. cat(sprintf("\nCorrected stratum: k = %d cohorts\n", nrow(df_corrected)))
  349. cat(sprintf("Corrected stratum (excl. Wang 2019): k = %d cohorts\n\n", nrow(df_corrected_no_wang)))
  350. meta_full_wang <- metacont(data=df_corrected, n.e=n.e, mean.e=mean.e, sd.e=sd.e,
  351. n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab,
  352. sm="SMD", method.tau="REML", hakn=TRUE, comb.fixed=FALSE)
  353. meta_excl_wang <- metacont(data=df_corrected_no_wang, n.e=n.e, mean.e=mean.e, sd.e=sd.e,
  354. n.c=n.c, mean.c=mean.c, sd.c=sd.c, studlab=studlab,
  355. sm="SMD", method.tau="REML", hakn=TRUE, comb.fixed=FALSE)
  356. cat(sprintf("=== FULL Corrected Stratum (k=%d) ===\n", meta_full_wang$k))
  357. cat(sprintf(" SMD = %.4f, 95%% CI [%.4f, %.4f]\n", meta_full_wang$TE.random, meta_full_wang$lower.random, meta_full_wang$upper.random))
  358. cat(sprintf(" p = %.4f, I2 = %.1f%%\n\n", meta_full_wang$pval.random, meta_full_wang$I2*100))
  359. cat(sprintf("=== EXCLUDING Wang 2019 (k=%d) ===\n", meta_excl_wang$k))
  360. cat(sprintf(" SMD = %.4f, 95%% CI [%.4f, %.4f]\n", meta_excl_wang$TE.random, meta_excl_wang$lower.random, meta_excl_wang$upper.random))
  361. cat(sprintf(" p = %.4f, I2 = %.1f%%\n\n", meta_excl_wang$pval.random, meta_excl_wang$I2*100))
  362. cat("=== COMPARISON ===\n")
  363. cat(sprintf(" Full: SMD = %.3f [%.3f, %.3f], p = %.4f, I2 = %.1f%%\n",
  364. meta_full_wang$TE.random, meta_full_wang$lower.random, meta_full_wang$upper.random, meta_full_wang$pval.random, meta_full_wang$I2*100))
  365. cat(sprintf(" Excl Wang: SMD = %.3f [%.3f, %.3f], p = %.4f, I2 = %.1f%%\n",
  366. meta_excl_wang$TE.random, meta_excl_wang$lower.random, meta_excl_wang$upper.random, meta_excl_wang$pval.random, meta_excl_wang$I2*100))
  367. cat(sprintf(" Delta SMD = %.4f (%.1f%% change)\n",
  368. meta_excl_wang$TE.random - meta_full_wang$TE.random,
  369. abs(meta_excl_wang$TE.random - meta_full_wang$TE.random) / abs(meta_full_wang$TE.random) * 100))
  370. sig_full <- ifelse(meta_full_wang$pval.random < 0.05, "Significant", "Non-significant")
  371. sig_excl <- ifelse(meta_excl_wang$pval.random < 0.05, "Significant", "Non-significant")
  372. cat(sprintf(" Significance: Full = %s, Excl = %s\n", sig_full, sig_excl))
  373. cat(sprintf(" Conclusion change: %s\n\n", ifelse(sig_full == sig_excl, "NO - conclusion robust", "YES - conclusion changes!")))
  374. # --- 4b. Subgroup by Segmentation Method ---
  375. print("Step 4b: Sensitivity - Subgroup by Segmentation Method...")
  376. cat("\n=== Segmentation Method Subgroup Analysis (ICV-Corrected Stratum) ===\n\n")
  377. cat("Studies per method:\n")
  378. print(table(df_corrected$segmentation_method))
  379. meta_seg <- metacont(
  380. data = df_corrected,
  381. n.e = n.e, mean.e = mean.e, sd.e = sd.e,
  382. n.c = n.c, mean.c = mean.c, sd.c = sd.c,
  383. studlab = studlab, sm = "SMD", method.tau = "REML",
  384. hakn = TRUE, comb.fixed = FALSE,
  385. subgroup = segmentation_method
  386. )
  387. cat("\n--- Overall (ICV-corrected) ---\n")
  388. cat(sprintf(" k = %d, SMD = %.4f [%.4f, %.4f], I2 = %.1f%%\n",
  389. meta_seg$k, meta_seg$TE.random, meta_seg$lower.random,
  390. meta_seg$upper.random, meta_seg$I2 * 100))
  391. cat(sprintf(" p (random) = %s\n\n", formatC(meta_seg$pval.random, format = "e", digits = 2)))
  392. cat("--- Subgroup Results ---\n")
  393. for (sg in unique(df_corrected$segmentation_method)) {
  394. idx <- which(meta_seg$subgroup == sg)
  395. k_sg <- length(idx)
  396. cat(sprintf(" %s (k=%d): SMD = %.4f [%.4f, %.4f], I2 = %.1f%%\n",
  397. sg, k_sg,
  398. meta_seg$TE.random.w[meta_seg$subgroup.levels == sg],
  399. meta_seg$lower.random.w[meta_seg$subgroup.levels == sg],
  400. meta_seg$upper.random.w[meta_seg$subgroup.levels == sg],
  401. meta_seg$I2.w[meta_seg$subgroup.levels == sg] * 100))
  402. }
  403. cat("\n--- Between-subgroup heterogeneity test ---\n")
  404. cat(sprintf(" Q_between = %.4f, df = %d, p = %s\n",
  405. meta_seg$Q.b.random, meta_seg$df.Q.b.random,
  406. formatC(meta_seg$pval.Q.b.random, format = "e", digits = 4)))
  407. if (meta_seg$pval.Q.b.random >= 0.05) {
  408. cat("\n >> Segmentation method did NOT significantly moderate the effect (p >= 0.05)\n\n")
  409. } else {
  410. cat("\n >> Segmentation method SIGNIFICANTLY moderated the effect (p < 0.05)\n\n")
  411. }
  412. # --- Generate Master Summary Table ---
  413. print("Step 5/5: Creating master summary table...")
  414. master_table <- data.frame()
  415. # Add Corrected group results
  416. if(!is.null(results_corrected)) {
  417. corrected_summary <- results_corrected$summary_data %>%
  418. mutate(Method = "Corrected") %>%
  419. select(Method, Group, Category, SMD, Lower, Upper, P_val)
  420. master_table <- bind_rows(master_table, corrected_summary)
  421. }
  422. # Add Uncorrected group results
  423. if(!is.null(results_uncorrected)) {
  424. uncorrected_summary <- results_uncorrected$summary_data %>%
  425. mutate(Method = "Uncorrected") %>%
  426. select(Method, Group, Category, SMD, Lower, Upper, P_val)
  427. master_table <- bind_rows(master_table, uncorrected_summary)
  428. }
  429. # Format and save
  430. master_table <- master_table %>%
  431. mutate(
  432. CI_95 = paste0("[", round(Lower, 3), ", ", round(Upper, 3), "]"),
  433. SMD = round(SMD, 3),
  434. P_Value = sapply(P_val, fmt_p_exact),
  435. Significance = ifelse(P_val < 0.05, "Significant", "Non-significant")
  436. ) %>%
  437. select(Method, Group, Category, SMD, CI_95, P_Value, Significance) %>%
  438. arrange(Method, match(Category, c("Main", "Diagnosis", "Dose", "Bias")), Group)
  439. write.csv(master_table, file.path(OUTPUT_FOLDER, "Table_Master_Summary_All_Results.csv"), row.names = FALSE)
  440. print("Master summary table created!")

meta.R at commit 09b915a, no license · at the source

Overview

Authors: Minnuo Cai1,2, Hang Lei1, Yuetong Zhang1, Jiaxiang Zou1, Wanjing Cao1, Yiquan Wang1,2,3, Kai Wei1
ORCID iDs: Yiquan Wang, Kai Wei
  1. Xinjiang Key Laboratory of Biological Resources and Genetic Engineering, College of Life Science and Technology, Xinjiang University, Urumqi, Xinjiang, China
  2. Shenzhen X-Institute, Shenzhen, China
  3. College of Mathematics and System Science, Xinjiang University, Urumqi, Xinjiang, China
Institutions: Xinjiang University (China)
Journal: Frontiers in aging neuroscience, volume 18, article 1847611
Dates: received 4 April 2026; accepted 11 June 2026; published online 1 July 2026
Type: Systematic review · Language: English
License: CC BY
Identifiers: DOI 10.3389/fnagi.2026.1847611 · PMID 42459526 · PMCID PMC13369312 · OpenAlex W4417258294
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: structural MRI / diffusion (modality), human (organism), Alzheimer's / dementia (population)
Methods: Statistics, Preprocessing
Keywords: AD spectrum, amyloid interaction, APOE-ε4, hippocampal volume, systematic review
Topic: Alzheimer's disease research and treatments (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Funding: NIA NIH HHS (U19 AG024904, U24 AG072122)
Citations: cited by 1 paper (Europe PMC); 73 references in the paper

Abstract

Background: Predicting patient-specific neurodegenerative trajectories is essential for targeted interventions in Alzheimer's disease. Although the APOE-ε4 allele is the predominant genetic risk factor for hippocampal atrophy, whether its structural impact reflects a static developmental phenotype or an accelerated neurodegenerative process conditional on amyloid pathology remains unresolved.

Objectives: To quantify the effect of APOE-ε4 on hippocampal volume via cross-sectional meta-analysis, and to validate the gene-dose effect through independent longitudinal modeling in two cohorts.

Eligibility criteria: Original observational neuroimaging studies reporting hippocampal volume stratified by APOE genotype in human participants across the Alzheimer's disease continuum.

Information sources: PubMed, Embase, Web of Science, and Cochrane Library were searched from inception to August 2025.

Risk of bias: Methodological quality was assessed using the Newcastle–Ottawa Scale (NOS) adapted for cross-sectional studies.

Included studies and synthesis of results: The meta-analysis included 18 studies (N = 4, 311) and indicated reduced hippocampal volume in carriers (ICV-corrected stratum: SMD = −0.41, 95% CI [−0.67, −0.16], p = 0.004; I2 = 74.0%), with the effect absent in uncorrected analyses. Longitudinal gene-dose models, run independently in the post-QC NACC LMM sample (N = 3, 239; imaging extraction N = 3, 248) and ADNI (N = 1, 150) cohorts and combined via fixed-effect meta-analysis, demonstrated a dose-dependent acceleration of hippocampal atrophy: homozygotes exhibited a pooled additional volume loss of −58.25 mm3/year (95% CI [−89.26, −27.23], p = 2.33 × 10−4; I2 = 0%) and heterozygotes −34.32 mm3/year (95% CI [−52.31, −16.32], p = 1.85 × 10−4) relative to non-carriers.

Limitations: Biomarker-stratified analyses used baseline-only CSF measurements subject to time-varying confounding and should be interpreted as exploratory. Moderate between-cohort heterogeneity was observed for the heterozygote effect (I2 = 63.0%).

Conclusions and implications: These findings provide two-cohort evidence for a dose-dependent APOE-ε4 effect on hippocampal atrophy rates. Exploratory biomarker-stratified analyses in ADNI suggest this acceleration may be conditional on amyloid-β positivity, a hypothesis requiring validation with time-varying causal models.

Systematic review registration: https://www.crd.york.ac.uk/PROSPERO/view/CRD420251243460, PROSPERO: CRD420251243460.

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

Repository

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

wyqmath/admeta

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 09b915a36aec7144152e2946681423fc39d8d70e, 22 May 2026
Languages: Python (2), R (1)
Size: 4 files, 3 scripts
Software Heritage: not archived
Found in: “Data availability statement”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (2 files), NumPy (2 files), pandas (2 files), SciPy (2 files), seaborn (2 files), statsmodels (2 files), data.table (1 file), tidyverse (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
4 files

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

Tracing map

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

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 3 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability statement

The code used for the meta-analysis and longitudinal modeling is publicly available in the GitHub repository: https://github.com/wyqmath/admeta.

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, 7 authors, 5 keywords, 1 funder, 72 references.

Cite

This paper

Cai, M., Lei, H., Zhang, Y., Zou, J., Cao, W., Wang, Y., & Wei, K. (2026). APOE ε4-associated hippocampal atrophy trajectories across the Alzheimer's disease continuum: a systematic review, meta-analysis, and longitudinal validation. Frontiers in aging neuroscience, 18, 1847611. https://doi.org/10.3389/fnagi.2026.1847611

BibTeX

@article{cai2026apoe,
author = {Cai, Minnuo and Lei, Hang and Zhang, Yuetong and Zou, Jiaxiang and Cao, Wanjing and Wang, Yiquan and Wei, Kai},
title = {{APOE ε4-associated hippocampal atrophy trajectories across the Alzheimer's disease continuum: a systematic review, meta-analysis, and longitudinal validation}},
journal = {Frontiers in aging neuroscience},
year = {2026},
month = jul,
volume = {18},
pages = {1847611},
publisher = {Frontiers Media SA},
issn = {1663-4365},
doi = {10.3389/fnagi.2026.1847611},
url = {https://doi.org/10.3389/fnagi.2026.1847611},
pmid = {42459526},
pmcid = {PMC13369312}
}

RIS

TY - JOUR
AU - Cai, Minnuo
AU - Lei, Hang
AU - Zhang, Yuetong
AU - Zou, Jiaxiang
AU - Cao, Wanjing
AU - Wang, Yiquan
AU - Wei, Kai
TI - APOE ε4-associated hippocampal atrophy trajectories across the Alzheimer's disease continuum: a systematic review, meta-analysis, and longitudinal validation
T2 - Frontiers in aging neuroscience
J2 - Front Aging Neurosci
PY - 2026
DA - 2026/07/01
VL - 18
SP - 1847611
SN - 1663-4365
PB - Frontiers Media SA
DO - 10.3389/fnagi.2026.1847611
UR - https://doi.org/10.3389/fnagi.2026.1847611
LA - en
ER -

CSL-JSON

{
"id": "10.3389/fnagi.2026.1847611",
"type": "article-journal",
"title": "APOE ε4-associated hippocampal atrophy trajectories across the Alzheimer's disease continuum: a systematic review, meta-analysis, and longitudinal validation",
"container-title": "Frontiers in aging neuroscience",
"author": [
{
"family": "Cai",
"given": "Minnuo"
},
{
"family": "Lei",
"given": "Hang"
},
{
"family": "Zhang",
"given": "Yuetong"
},
{
"family": "Zou",
"given": "Jiaxiang"
},
{
"family": "Cao",
"given": "Wanjing"
},
{
"family": "Wang",
"given": "Yiquan"
},
{
"family": "Wei",
"given": "Kai"
}
],
"container-title-short": "Front Aging Neurosci",
"volume": "18",
"page": "1847611",
"DOI": "10.3389/fnagi.2026.1847611",
"PMID": "42459526",
"PMCID": "PMC13369312",
"ISSN": "1663-4365",
"publisher": "Frontiers Media SA",
"URL": "https://doi.org/10.3389/fnagi.2026.1847611",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
1
]
]
}
}

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.1002/dad2.70024 [code]
Hippocampal volumes in UK Biobank are associated with &lt;i&gt;APOE&lt;/i&gt; only in older adults.
Journal: Alzheimer's & dementia (Amsterdam, Netherlands)
In common: tidyverse, Alzheimer's / dementia, structural MRI / diffusion, 8 references
[2] doi:10.1093/braincomms/fcad229
Pathway-specific polygenic scores for Alzheimer's disease are associated with changes in brain structure in younger and older adults.
Journal: Brain communications
In common: Alzheimer's / dementia, structural MRI / diffusion, 8 references
[3] doi:10.1016/j.ebiom.2025.105659 [code]
Exploring the neuromagnetic signatures of cognitive decline from mild cognitive impairment to Alzheimer's disease dementia.
Journal: EBioMedicine
In common: data.table, statsmodels, seaborn, 5 other tools, Alzheimer's / dementia, structural MRI / diffusion, 5 references
[4] doi:10.1038/s41467-025-63564-2 [code]
Hemispheric asymmetry of tau pathology is related to asymmetric amyloid deposition in Alzheimer's Disease.
Journal: Nature communications
In common: statsmodels, seaborn, pandas, 3 other tools, Alzheimer's / dementia, structural MRI / diffusion, 6 references
[5] doi:10.1038/s41398-024-03097-2 [code]
Magnitude and kinetics of a set of neuroanatomic volume and thickness together with white matter hyperintensity is definitive of cognitive status and brain age.
Journal: Translational psychiatry
In common: statsmodels, seaborn, tidyverse, 4 other tools, Alzheimer's / dementia, structural MRI / diffusion, 4 references
[6] doi:10.1038/s41467-022-31037-5 [code]
Multimodal deep learning for Alzheimer's disease dementia assessment.
Journal: Nature communications
In common: seaborn, pandas, SciPy, 2 other tools, Alzheimer's / dementia, 5 references
[7] doi:10.1038/s41591-025-03574-1 [code]
Microglial mechanisms drive amyloid-β clearance in immunized patients with Alzheimer's disease.
Journal: Nature medicine
In common: data.table, tidyverse, pandas, 3 other tools, Alzheimer's / dementia, 4 references
[8] doi:10.1002/hbm.70280 [code]
Cross-Dataset Evaluation of Dementia Longitudinal Progression Prediction Models.
Journal: Human brain mapping
In common: statsmodels, pandas, SciPy, 2 other tools, Alzheimer's / dementia, 5 references
[9] doi:10.1038/s41591-024-03118-z [code]
AI-based differential diagnosis of dementia etiologies on multimodal data.
Journal: Nature medicine
In common: statsmodels, seaborn, pandas, 3 other tools, Alzheimer's / dementia, 3 references
[10] doi:10.1371/journal.pbio.3001863 [code]
APOE alleles are associated with sex-specific structural differences in brain regions affected in Alzheimer's disease and related dementia.
Journal: PLoS biology
In common: data.table, Alzheimer's / dementia, 5 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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.3389/fnagi.2026.1847611.svg)](https://openscicode.org/badge/doi/10.3389/fnagi.2026.1847611)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.3389/fnagi.2026.1847611.svg
   :target: https://openscicode.org/badge/doi/10.3389/fnagi.2026.1847611
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.3389/fnagi.2026.1847611"><img src="https://openscicode.org/badge/doi/10.3389/fnagi.2026.1847611.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge 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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.