OSCR

Genetic drivers of progression in Alzheimer's disease are distinct from disease risk.

Code ↔ Paper

3 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 3 matches
  1. [1] § Methods › Genomic quality control and imputation ↔ Progression_Genetic_Modelling/Genetic QC/genetic_qc_consolidated.R, lines 1–59 · score 0.85 · Hardy Weinberg Equilibrium, minor allele frequency, missing genotyping, deviations, QC, quality
  2. [2] § Methods › Patient filtering ↔ Progression_Genetic_Modelling/Clinical Data Modelling and Filtering/comprehensive_analysis.qmd, lines 84–105 · score 0.70 · Pittsburgh compound, positive patients, CSF, AV45, PiB, filtered
  3. [3] § Methods › Mixed-effects modelling ↔ Progression_Genetic_Modelling/Clinical Data Modelling and Filtering/comprehensive_analysis.qmd, lines 150–186 · score 0.56 · random intercept, baseline MMSE, PC1, PC2, education, age

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

Quarto · 1,071 lines · 36 KB · no license · 2 matches

  1. ---
  2. title: "Exploratory Data Analysis, Model Optimisation and Clinical Filtering"
  3. author:
  4. - "CCOHEN"
  5. - "MSHOAI"
  6. date: today
  7. format:
  8. html:
  9. theme: cosmo
  10. toc: true
  11. toc-depth: 3
  12. toc-location: left
  13. number-sections: true
  14. code-fold: true
  15. code-tools: true
  16. df-print: kable
  17. fig-width: 10
  18. fig-height: 6
  19. embed-resources: true
  20. execute:
  21. warning: false
  22. message: false
  23. cache: false
  24. ---
  25. # Introduction
  26. This document presents a comprehensive analysis of longitudinal cognitive decline in Alzheimer's Disease (AD) patients from the Alzheimer's Disease Neuroimaging Initiative (ADNI). The analysis includes:
  27. - Quality control and patient filtering based on amyloid-beta biomarkers
  28. - Exploratory data analysis and descriptive statistics
  29. - Mixed-effects model optimization and comparison
  30. - Integration with polygenic risk scores
  31. # Data Loading and Preparation
  32. ```{r setup, warning=FALSE, message=FALSE}
  33. # Load required libraries
  34. library(tidyverse)
  35. library(tseries)
  36. library(visdat)
  37. library(ggplot2)
  38. library(dplyr)
  39. library(gridExtra)
  40. library(Amelia)
  41. library(corrplot)
  42. library(knitr)
  43. library(lme4)
  44. library(lmerTest)
  45. library(performance)
  46. library(fitdistrplus)
  47. library(glmmTMB)
  48. # Set options for output formatting
  49. options(knitr.kable.NA = '', digits = 2)
  50. ```
  51. ```{r load_data}
  52. # Read ADNIMERGE clinical data
  53. adnimerge <- readRDS("./QCjune/clinicaladni/adnimerge.rds")
  54. # Select relevant columns
  55. dataset <- adnimerge[, c(4, 9, 10, 11, 114, 27, 69, 61, 8, 18, 110, 17, 109, 20, 105, 14, 15, 1)]
  56. colnames(dataset) <- c("Patient_ID", "Age", "Gender", "Education", "Months_since_bl",
  57. "MMSE", "MMSE.bl", "DX", "DX.bl", "AV45", "AV45.bl", "PIB", "PIB.bl",
  58. "CSF_AB", "CSF_AB.bl", "Marital", "APOE4", "RID")
  59. # Clean CSF Amyloid-beta values
  60. dataset$CSF_AB.bl <- gsub(">", "", dataset$CSF_AB.bl)
  61. dataset$CSF_AB.bl <- gsub("<", "", dataset$CSF_AB.bl)
  62. dataset$CSF_AB.bl <- as.numeric(dataset$CSF_AB.bl)
  63. dataset$CSF_AB <- gsub(">", "", dataset$CSF_AB)
  64. dataset$CSF_AB <- gsub("<", "", dataset$CSF_AB)
  65. dataset$CSF_AB <- as.numeric(dataset$CSF_AB)
  66. ```
  67. ## Initial Dataset Summary
  68. ```{r initial_summary}
  69. cat("Total unique patients:", length(unique(dataset$Patient_ID)), "\n")
  70. cat("Total datapoints:", nrow(dataset), "\n")
  71. ```
  72. # Patient Filtering Pipeline
  73. ## Step 1: Amyloid-Beta Positive Selection {#step1}
  74. Patients are classified as amyloid-beta positive if they meet at least one of the following criteria at baseline:
  75. - **AV45 (Florbetapir PET)**: ≥ 1.1
  76. - **PIB (Pittsburgh Compound B PET)**: ≥ 1.5
  77. - **CSF Amyloid-beta**: ≤ 550 pg/mL
  78. ```{r filter_amyloid}
  79. # Define thresholds
  80. th_amyl_AV45 <- 1.1
  81. th_amyl_PIB <- 1.5
  82. th_amyl_CSF <- 550
  83. # Filter for amyloid-beta positive patients
  84. df_amyloid <- subset(dataset, AV45.bl >= th_amyl_AV45 | PIB.bl >= th_amyl_PIB | CSF_AB.bl <= th_amyl_CSF)
  85. cat("Amyloid-beta positive patients:", length(unique(df_amyloid$Patient_ID)), "\n")
  86. cat("Number of datapoints:", nrow(df_amyloid), "\n")
  87. ```
  88. ### Amyloid-Beta Distribution
  89. ```{r amyloid_dist, fig.height=4}
  90. # Create binary amyloid status variable
  91. dataset$Amyloid_Status <- ifelse(
  92. dataset$AV45.bl >= th_amyl_AV45 | dataset$PIB.bl >= th_amyl_PIB | dataset$CSF_AB.bl <= th_amyl_CSF,
  93. "Positive", "Negative"
  94. )
  95. # Plot distribution
  96. ggplot(dataset %>% group_by(Patient_ID) %>% slice(1), aes(x = Amyloid_Status, fill = Amyloid_Status)) +
  97. geom_bar() +
  98. geom_text(stat='count', aes(label=..count..), vjust=-0.5) +
  99. labs(title = "Distribution of Amyloid-Beta Status",
  100. x = "Amyloid-Beta Status", y = "Number of Patients") +
  101. scale_fill_manual(values = c("Positive" = "#E74C3C", "Negative" = "#3498DB")) +
  102. theme_minimal() +
  103. theme(legend.position = "none", text = element_text(size = 12))
  104. ```
  105. # Model Optimization on Amyloid-Positive Cohort
  106. Before applying additional filtering, we test multiple mixed-effects models on the amyloid-positive cohort to identify the optimal model structure.
  107. ```{r prepare_model_data}
  108. # Prepare data for modeling (amyloid+ only, at this stage)
  109. df_model1 <- df_amyloid
  110. df_model1$Gender <- as.factor(df_model1$Gender)
  111. df_model1$Months_since_bl <- as.numeric(as.character(df_model1$Months_since_bl))
  112. # For this initial modeling, we need PC1 and PC2
  113. # Read PCA data
  114. pcs <- read.table("./QCjune/adnimerge_genomes/FINALADNI_nohapmap_pca.eigenvec")[, 2:4]
  115. colnames(pcs) <- c("PTID", "PC1", "PC2")
  116. # Merge with clinical data
  117. df_model1$PTID <- as.character(df_model1$Patient_ID)
  118. df_model1_pcs <- inner_join(df_model1, pcs, by = "PTID", multiple = "all")
  119. cat("Amyloid+ patients with genetic data for modeling:", length(unique(df_model1_pcs$Patient_ID)), "\n")
  120. cat("Datapoints for modeling:", nrow(df_model1_pcs), "\n")
  121. ```
  122. ## Model 1-9 Comparison
  123. We test nine hierarchical models of increasing complexity:
  124. ```{r model_comparison, results='hide'}
  125. # Model 1: Time only
  126. model1 <- lm(MMSE ~ Months_since_bl, data = df_model1_pcs)
  127. # Model 2: Time + Baseline age
  128. model2 <- lm(MMSE ~ Months_since_bl + Age, data = df_model1_pcs)
  129. # Model 3: Time + Current age
  130. model3 <- lm(MMSE ~ Months_since_bl + Age, data = df_model1_pcs)
  131. # Model 4: Time + Baseline MMSE + Baseline age
  132. model4 <- lm(MMSE ~ MMSE.bl + Months_since_bl + Age, data = df_model1_pcs)
  133. # Model 5: Random intercept
  134. model5 <- lmer(MMSE ~ MMSE.bl + Age + Months_since_bl + (1|Patient_ID),
  135. data = df_model1_pcs, REML = TRUE)
  136. # Model 6: Random intercept + Education
  137. model6 <- lmer(MMSE ~ MMSE.bl + Age + Months_since_bl + (1|Patient_ID) + Education,
  138. data = df_model1_pcs, REML = TRUE)
  139. # Model 7: Random intercept + Education + Gender
  140. model7 <- lmer(MMSE ~ MMSE.bl + Age + Months_since_bl + (1|Patient_ID) + Education + Gender,
  141. data = df_model1_pcs, REML = TRUE)
  142. # Model 8: Random intercept + Education + Gender + PCs
  143. model8 <- lmer(MMSE ~ MMSE.bl + Age + Months_since_bl + (1|Patient_ID) + Education + Gender + PC1 + PC2,
  144. data = df_model1_pcs, REML = TRUE)
  145. # Model 9: Random slope and intercept + Education + Gender + PCs
  146. model9 <- lmer(MMSE ~ MMSE.bl + Age + Months_since_bl + (Months_since_bl|Patient_ID) +
  147. Education + Gender + PC1 + PC2, data = df_model1_pcs, REML = TRUE)
  148. ```
  149. ```{r model_performance}
  150. # Compile model performance metrics
  151. models <- list(model1, model2, model3, model4, model5, model6, model7, model8, model9)
  152. model_names <- paste0("Model ", 1:9)
  153. # Get performance metrics manually
  154. aics <- sapply(models, AIC)
  155. bics <- sapply(models, BIC)
  156. r2_vals <- sapply(models, function(m) {
  157. r2_result <- tryCatch(r2(m), error = function(e) list(R2 = NA))
  158. if ("R2" %in% names(r2_result)) return(r2_result$R2)
  159. if ("R2_conditional" %in% names(r2_result)) return(r2_result$R2_conditional)
  160. return(NA)
  161. })
  162. performance_table <- data.frame(
  163. Model = model_names,
  164. AIC = aics,
  165. BIC = bics,
  166. R2 = r2_vals
  167. )
  168. kable(performance_table,
  169. caption = "Performance Comparison of Models 1-9 on Amyloid-Positive Cohort",
  170. digits = 2)
  171. ```
  172. ::: {.callout-note}
  173. ## Best Model Selection
  174. Based on AIC, BIC, and R² metrics, **Model 9** (random slopes and intercepts with full covariates) provides the best fit. This model will be used for subsequent analyses after full filtering.
  175. :::
  176. # Continued Patient Filtering
  177. ## Step 2: Remove Missing MMSE and Require ≥3 Datapoints
  178. ```{r filter_mmse}
  179. df <- df_amyloid
  180. cat("Patients before filtering:", length(unique(df$Patient_ID)), "\n")
  181. cat("Datapoints before filtering:", nrow(df), "\n\n")
  182. # Remove rows with missing MMSE
  183. df <- df[-which(is.na(df$MMSE)), ]
  184. # Require at least 3 datapoints per patient
  185. Keep <- df %>%
  186. group_by(Patient_ID) %>%
  187. summarize(datapoints = n()) %>%
  188. filter(datapoints >= 3)
  189. df <- df[df$Patient_ID %in% Keep$Patient_ID, ]
  190. cat("Patients after filtering:", length(unique(df$Patient_ID)), "\n")
  191. cat("Datapoints after filtering:", nrow(df), "\n")
  192. ```
  193. ## Step 3: Remove "Always Cognitively Normal" Patients
  194. ```{r filter_always_cn}
  195. # Identify patients who are CN at baseline
  196. PTID_CNbl <- unique(df[df$Months_since_bl == 0 & df$DX == "CN", ]$Patient_ID)
  197. PTID_CNbl_df <- df[df$Patient_ID %in% PTID_CNbl, ]
  198. # Find those who progress to MCI or Dementia
  199. PTID_CNthenMCI <- unique(PTID_CNbl_df[which(PTID_CNbl_df$DX == "MCI" | PTID_CNbl_df$DX == "Dementia"), ]$Patient_ID)
  200. # Remove always-CN patients
  201. PTID_alwaysCN <- PTID_CNbl[-which(PTID_CNbl %in% PTID_CNthenMCI)]
  202. df_clean <- df[-which(df$Patient_ID %in% PTID_alwaysCN), ]
  203. cat("Patients not always cognitively normal:", length(unique(df_clean$Patient_ID)), "\n")
  204. cat("Datapoints:", nrow(df_clean), "\n")
  205. ```
  206. ## Step 4: Remove Patients with Minimum MMSE ≥ 28
  207. ```{r filter_min_mmse}
  208. # Calculate minimum MMSE per patient
  209. minMMSEperpatient <- df_clean %>%
  210. group_by(Patient_ID) %>%
  211. summarise(minMMSE = min(MMSE))
  212. min28 <- minMMSEperpatient$Patient_ID[which(minMMSEperpatient$minMMSE >= 28)]
  213. df_clean <- df_clean[!df_clean$Patient_ID %in% min28, ]
  214. cat("Patients with minimum MMSE < 28:", length(unique(df_clean$Patient_ID)), "\n")
  215. cat("Datapoints:", nrow(df_clean), "\n")
  216. ```
  217. ## Step 5: Remove Patients with Last MMSE 28-30 OR Last DX = CN
  218. ```{r filter_last_status}
  219. # Calculate last MMSE and DX per patient
  220. lastMMSE <- c()
  221. lastDX <- c()
  222. for (i in unique(df_clean$Patient_ID)) {
  223. d <- subset(df_clean, Patient_ID == i)
  224. d_sort <- d[order(d$Months_since_bl, decreasing = FALSE), ]
  225. lastMMSE <- c(lastMMSE, d_sort$MMSE[length(d_sort$MMSE)])
  226. lastDX <- c(lastDX, d_sort$DX[length(d_sort$DX)])
  227. }
  228. # Remove patients with last MMSE 28-30
  229. df_lastMMSE30 <- subset(df_clean, Patient_ID %in% unique(df_clean$Patient_ID)[which(lastMMSE == 30 | lastMMSE == 29 | lastMMSE == 28)])
  230. df_clean <- df_clean[!df_clean$Patient_ID %in% df_lastMMSE30$Patient_ID, ]
  231. # Remove patients with last DX = CN
  232. df_lastDXCN <- subset(df_clean, Patient_ID %in% unique(df_clean$Patient_ID)[which(lastDX == "CN")])
  233. df_clean <- df_clean[!df_clean$Patient_ID %in% df_lastDXCN$Patient_ID, ]
  234. cat("Patients without ceiling effects:", length(unique(df_clean$Patient_ID)), "\n")
  235. cat("Datapoints:", nrow(df_clean), "\n")
  236. ```
  237. ## Step 6: Remove Datapoints Before MMSE = 30
  238. For patients who achieved MMSE of 30 at any point, we remove all observations before the last occurrence of MMSE = 30. This focuses the analysis on the decline phase.
  239. ```{r filter_before_30}
  240. before_count <- nrow(df_clean)
  241. df_clean$dpid <- seq(1, nrow(df_clean))
  242. rm_rows <- c()
  243. for (i in unique(df_clean$Patient_ID)) {
  244. d <- subset(df_clean, Patient_ID == i)
  245. d_sort <- d[order(d$Months_since_bl, decreasing = FALSE), ]
  246. instances <- c()
  247. for (n in 1:(length(d_sort$MMSE) - 1)) {
  248. if (d_sort$MMSE[n] == 30) {
  249. instances <- c(instances, n)
  250. }
  251. }
  252. if (length(instances) > 0) {
  253. rm <- d_sort$dpid[1:(max(instances) - 1)]
  254. rm_rows <- c(rm_rows, rm)
  255. }
  256. }
  257. df_clean <- df_clean[!df_clean$dpid %in% rm_rows, ]
  258. df_clean$dpid <- NULL
  259. cat("Datapoints removed:", before_count - nrow(df_clean), "\n")
  260. cat("Patients remaining:", length(unique(df_clean$Patient_ID)), "\n")
  261. cat("Datapoints remaining:", nrow(df_clean), "\n")
  262. ```
  263. ## Step 7: Keep Only Last 5 Observations Per Patient
  264. This critical step focuses the analysis on recent disease trajectory.
  265. ```{r filter_last_5}
  266. before_count <- nrow(df_clean)
  267. df_clean$dpid <- seq(1, nrow(df_clean))
  268. rm_rows <- c()
  269. for (i in unique(df_clean$Patient_ID)) {
  270. d <- subset(df_clean, Patient_ID == i)
  271. d_sort <- d[order(d$Months_since_bl, decreasing = FALSE), ]
  272. if (length(d$dpid) > 5) {
  273. rm <- d_sort$dpid[1:(length(d_sort$dpid) - 5)]
  274. rm_rows <- c(rm_rows, rm)
  275. }
  276. }
  277. df_clean <- df_clean[!df_clean$dpid %in% rm_rows, ]
  278. df_clean$dpid <- NULL
  279. cat("Datapoints removed:", before_count - nrow(df_clean), "\n")
  280. cat("Datapoints remaining:", nrow(df_clean), "\n")
  281. ```
  282. ## Step 8: Require ≥3 Datapoints (After Pruning)
  283. ```{r filter_3pts_final}
  284. Keep <- df_clean %>%
  285. group_by(Patient_ID) %>%
  286. summarize(datapoints = n()) %>%
  287. filter(datapoints >= 3)
  288. df_clean <- df_clean[df_clean$Patient_ID %in% Keep$Patient_ID, ]
  289. cat("Final clinically filtered patients:", length(unique(df_clean$Patient_ID)), "\n")
  290. cat("Final datapoints:", nrow(df_clean), "\n")
  291. ```
  292. ::: {.callout-important}
  293. ## Filtering Summary
  294. Starting with **`r length(unique(dataset$Patient_ID))`** patients, the filtering pipeline identified **`r length(unique(df_clean$Patient_ID))`** patients with:
  295. - Confirmed amyloid-beta pathology
  296. - Sufficient longitudinal data (3-5 recent observations)
  297. - Evidence of cognitive decline
  298. :::
  299. # Descriptive Statistics
  300. ## Missing Data Visualization
  301. ```{r missing_data, fig.height=8}
  302. # Visualize missing data patterns
  303. missmap(df_clean[, c("MMSE", "Age", "Education", "Gender", "DX", "AV45", "PIB", "CSF_AB", "APOE4")],
  304. main = "Missing Data Map - Filtered Cohort",
  305. col = c("lightcoral", "lightblue"),
  306. legend = TRUE,
  307. x.cex = 0.8,
  308. y.cex = 0.6)
  309. ```
  310. ## Baseline Characteristics
  311. ```{r baseline_char}
  312. # Get baseline characteristics (one row per patient)
  313. df_baseline <- df_clean %>%
  314. group_by(Patient_ID) %>%
  315. arrange(Months_since_bl) %>%
  316. slice(1) %>%
  317. ungroup()
  318. # Summary statistics
  319. baseline_stats <- data.frame(
  320. Variable = c("Age", "Education", "MMSE"),
  321. Mean = c(mean(df_baseline$Age, na.rm = TRUE),
  322. mean(df_baseline$Education, na.rm = TRUE),
  323. mean(df_baseline$MMSE, na.rm = TRUE)),
  324. SD = c(sd(df_baseline$Age, na.rm = TRUE),
  325. sd(df_baseline$Education, na.rm = TRUE),
  326. sd(df_baseline$MMSE, na.rm = TRUE)),
  327. Median = c(median(df_baseline$Age, na.rm = TRUE),
  328. median(df_baseline$Education, na.rm = TRUE),
  329. median(df_baseline$MMSE, na.rm = TRUE)),
  330. Min = c(min(df_baseline$Age, na.rm = TRUE),
  331. min(df_baseline$Education, na.rm = TRUE),
  332. min(df_baseline$MMSE, na.rm = TRUE)),
  333. Max = c(max(df_baseline$Age, na.rm = TRUE),
  334. max(df_baseline$Education, na.rm = TRUE),
  335. max(df_baseline$MMSE, na.rm = TRUE))
  336. )
  337. kable(baseline_stats, caption = "Baseline Characteristics (Continuous Variables)", digits = 2)
  338. # Categorical variables
  339. cat_summary <- data.frame(
  340. Variable = c("Gender (Male)", "Gender (Female)",
  341. "DX.bl (AD)", "DX.bl (MCI)", "DX.bl (CN)", "DX.bl (Dementia)"),
  342. N = c(sum(df_baseline$Gender == "Male", na.rm = TRUE),
  343. sum(df_baseline$Gender == "Female", na.rm = TRUE),
  344. sum(df_baseline$DX.bl == "AD", na.rm = TRUE),
  345. sum(df_baseline$DX.bl == "MCI" | df_baseline$DX.bl == "EMCI" | df_baseline$DX.bl == "LMCI", na.rm = TRUE),
  346. sum(df_baseline$DX.bl == "CN", na.rm = TRUE),
  347. sum(df_baseline$DX.bl == "Dementia", na.rm = TRUE)),
  348. Percentage = c(100 * sum(df_baseline$Gender == "Male", na.rm = TRUE) / nrow(df_baseline),
  349. 100 * sum(df_baseline$Gender == "Female", na.rm = TRUE) / nrow(df_baseline),
  350. 100 * sum(df_baseline$DX.bl == "AD", na.rm = TRUE) / nrow(df_baseline),
  351. 100 * sum(df_baseline$DX.bl == "MCI" | df_baseline$DX.bl == "EMCI" | df_baseline$DX.bl == "LMCI", na.rm = TRUE) / nrow(df_baseline),
  352. 100 * sum(df_baseline$DX.bl == "CN", na.rm = TRUE) / nrow(df_baseline),
  353. 100 * sum(df_baseline$DX.bl == "Dementia", na.rm = TRUE) / nrow(df_baseline))
  354. )
  355. kable(cat_summary, caption = "Baseline Characteristics (Categorical Variables)", digits = 2)
  356. ```
  357. ## Age Distribution by Diagnosis and Gender
  358. ```{r age_distributions, fig.height=8}
  359. # Plot age distribution by baseline diagnosis
  360. p1 <- ggplot(df_baseline, aes(x = Age, fill = DX.bl)) +
  361. geom_histogram(binwidth = 5, alpha = 0.7) +
  362. labs(title = "Age Distribution by Baseline Diagnosis",
  363. x = "Age (years)", y = "Count", fill = "Baseline DX") +
  364. theme_minimal() +
  365. theme(text = element_text(size = 11))
  366. # Plot age distribution by gender
  367. p2 <- ggplot(df_baseline, aes(x = Age, fill = Gender)) +
  368. geom_histogram(binwidth = 5, alpha = 0.7, position = "identity") +
  369. labs(title = "Age Distribution by Gender",
  370. x = "Age (years)", y = "Count", fill = "Gender") +
  371. scale_fill_manual(values = c("Male" = "#3498DB", "Female" = "#E74C3C")) +
  372. theme_minimal() +
  373. theme(text = element_text(size = 11))
  374. # Boxplot of age by gender with t-test
  375. t_test_result <- t.test(Age ~ Gender, data = df_baseline)
  376. p3 <- ggplot(df_baseline, aes(x = Gender, y = Age, fill = Gender)) +
  377. geom_boxplot(alpha = 0.7) +
  378. labs(title = paste0("Age by Gender (p = ", format(t_test_result$p.value, digits = 3, scientific = TRUE), ")"),
  379. x = "Gender", y = "Age (years)") +
  380. scale_fill_manual(values = c("Male" = "#3498DB", "Female" = "#E74C3C")) +
  381. theme_minimal() +
  382. theme(legend.position = "none", text = element_text(size = 11))
  383. # MMSE trajectory over time by gender
  384. p4 <- ggplot(df_clean, aes(x = Months_since_bl, y = MMSE, color = Gender)) +
  385. geom_point(alpha = 0.3, size = 0.8) +
  386. geom_smooth(method = "lm", se = TRUE, size = 1.2) +
  387. labs(title = "MMSE Trajectory Over Time by Gender",
  388. x = "Months Since Baseline", y = "MMSE Score", color = "Gender") +
  389. scale_color_manual(values = c("Male" = "#3498DB", "Female" = "#E74C3C")) +
  390. theme_minimal() +
  391. theme(text = element_text(size = 11))
  392. # Arrange plots
  393. grid.arrange(p1, p2, p3, p4, ncol = 2)
  394. ```
  395. ## Age and Progression Analysis
  396. ### Progression Rate by Age Group
  397. ```{r age_progression, fig.height=6}
  398. # Calculate progression slopes by age groups
  399. agerange <- c(50, 60, 65, 70, 75, 80, 85, 95)
  400. slopes <- c()
  401. age_group <- c()
  402. for (i in 1:(length(agerange) - 1)) {
  403. low <- agerange[i]
  404. high <- agerange[i + 1]
  405. dfa <- df_clean[which(df_clean$Age >= low & df_clean$Age < high), ]
  406. for (id in unique(dfa$Patient_ID)) {
  407. d <- dfa[which(dfa$Patient_ID == id), ]
  408. if (nrow(d) >= 2) {
  409. slopes <- c(slopes, coef(lm(MMSE ~ Months_since_bl, data = d))[2])
  410. age_group <- c(age_group, paste(low, "to", high))
  411. }
  412. }
  413. }
  414. age_prog_df <- data.frame(Age_Group = age_group, Progression_Rate = slopes)
  415. # Box plot
  416. ggplot(age_prog_df, aes(x = Age_Group, y = Progression_Rate)) +
  417. geom_boxplot(fill = "#3498DB", alpha = 0.7) +
  418. geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  419. labs(title = "Cognitive Decline Rate by Age Group",
  420. x = "Age Group (years)", y = "MMSE Change per Month",
  421. caption = paste("ANOVA p-value:", format(anova(lm(Progression_Rate ~ Age_Group, data = age_prog_df))$Pr[1], digits = 3))) +
  422. theme_minimal() +
  423. theme(axis.text.x = element_text(angle = 45, hjust = 1), text = element_text(size = 11))
  424. ```
  425. ### Progression by Age of Onset
  426. ```{r age_onset, fig.height=4}
  427. # Analyze progression by age of onset (for dementia patients)
  428. df_dementia <- df_clean[which(df_clean$DX == "Dementia"), ]
  429. slopes_dem <- c()
  430. onset_age <- c()
  431. for (i in unique(df_dementia$Patient_ID)) {
  432. d <- subset(df_dementia, Patient_ID == i)
  433. if (nrow(d) >= 2) {
  434. slopes_dem <- c(slopes_dem, coef(lm(MMSE ~ Months_since_bl, data = d))[2])
  435. onset_age <- c(onset_age, min(d$Age))
  436. }
  437. }
  438. onset_df <- data.frame(Onset_Age = onset_age, Progression_Rate = slopes_dem)
  439. onset_df$Onset_Category <- cut(onset_df$Onset_Age,
  440. breaks = c(-Inf, 65, 75, Inf),
  441. labels = c("EOAD (<65)", "MOAD (65-75)", "LOAD (>75)"))
  442. # Plot
  443. p1 <- ggplot(onset_df, aes(x = Onset_Age, y = Progression_Rate)) +
  444. geom_point(alpha = 0.6, color = "#E74C3C") +
  445. geom_smooth(method = "lm", se = TRUE, color = "#3498DB") +
  446. labs(title = "Progression Rate vs Age of Onset",
  447. x = "Age of Onset (years)", y = "MMSE Change per Month") +
  448. theme_minimal()
  449. p2 <- ggplot(onset_df, aes(x = Onset_Category, y = Progression_Rate, fill = Onset_Category)) +
  450. geom_boxplot(alpha = 0.7) +
  451. labs(title = "Progression by Onset Category",
  452. x = "Age of Onset Category", y = "MMSE Change per Month") +
  453. scale_fill_brewer(palette = "Set2") +
  454. theme_minimal() +
  455. theme(legend.position = "none")
  456. grid.arrange(p1, p2, ncol = 2)
  457. # Statistical test
  458. lm_onset <- lm(Progression_Rate ~ Onset_Age, data = onset_df)
  459. cat("\nLinear regression p-value:", format(summary(lm_onset)$coefficients[2, 4], digits = 3), "\n")
  460. ```
  461. # Final Model on Filtered Cohort
  462. ## Data Preparation
  463. ```{r prepare_final_model}
  464. # Calculate baseline values
  465. df_clean <- df_clean[order(df_clean$Patient_ID, df_clean$Months_since_bl), ]
  466. Age_bl <- c()
  467. MMSE_bl <- c()
  468. DX_bl <- c()
  469. AV45_bl <- c()
  470. PIB_bl <- c()
  471. CSF_AB_bl <- c()
  472. prograte <- c()
  473. visits <- c()
  474. for (i in unique(df_clean$Patient_ID)) {
  475. d <- subset(df_clean, Patient_ID == i)
  476. datapoints <- nrow(d)
  477. visits <- c(visits, rep(datapoints, datapoints))
  478. # Calculate progression rate
  479. if (datapoints >= 2) {
  480. prograte <- c(prograte, rep(coef(lm(MMSE ~ Months_since_bl, data = d))[2], datapoints))
  481. } else {
  482. prograte <- c(prograte, rep(NA, datapoints))
  483. }
  484. Age_bl <- c(Age_bl, rep(min(d$Age), datapoints))
  485. MMSE_bl <- c(MMSE_bl, rep(d$MMSE[1], datapoints))
  486. DX_bl <- c(DX_bl, rep(d$DX[1], datapoints))
  487. AV45_bl <- c(AV45_bl, rep(d$AV45[1], datapoints))
  488. PIB_bl <- c(PIB_bl, rep(d$PIB[1], datapoints))
  489. CSF_AB_bl <- c(CSF_AB_bl, rep(d$CSF_AB[1], datapoints))
  490. }
  491. df_clean$Age_bl <- Age_bl
  492. df_clean$MMSE_bl <- MMSE_bl
  493. df_clean$DX_bl <- DX_bl
  494. df_clean$AV45_bl_calc <- AV45_bl
  495. df_clean$PIB_bl_calc <- PIB_bl
  496. df_clean$CSF_AB_bl_calc <- CSF_AB_bl
  497. df_clean$prograte <- prograte
  498. df_clean$visits <- visits
  499. # Remove old baseline columns
  500. df_clean$MMSE.bl <- NULL
  501. df_clean$CSF_AB.bl <- NULL
  502. df_clean$PIB.bl <- NULL
  503. df_clean$AV45.bl <- NULL
  504. df_clean$DX.bl <- NULL
  505. # Convert variables
  506. df_clean$Months_since_bl <- as.numeric(as.character(df_clean$Months_since_bl))
  507. df_clean$Gender <- as.factor(df_clean$Gender)
  508. df_clean$PTID <- as.character(df_clean$Patient_ID)
  509. # Merge with PCA data
  510. pcs <- read.table("./QCjune/adnimerge_genomes/FINALADNI_nohapmap_pca.eigenvec")[, 2:4]
  511. colnames(pcs) <- c("PTID", "PC1", "PC2")
  512. df_final <- inner_join(df_clean, pcs, by = "PTID", multiple = "all")
  513. cat("Patients with clinical and genetic data:", length(unique(df_final$Patient_ID)), "\n")
  514. cat("Final datapoints for modeling:", nrow(df_final), "\n")
  515. ```
  516. ## Optimal Model: Random Slopes and Intercepts
  517. Based on the model comparison in @step1, we fit Model 9 to the filtered cohort:
  518. ```{r final_model}
  519. final_model <- lmer(MMSE ~ MMSE_bl + Age_bl + Months_since_bl + (Months_since_bl|Patient_ID) +
  520. Education + Gender + PC1 + PC2,
  521. data = df_final, REML = TRUE)
  522. summary(final_model)
  523. ```
  524. ## Model Diagnostics
  525. ```{r model_diagnostics, fig.height=8}
  526. # Shapiro-Wilk test for normality of residuals
  527. shapiro_result <- shapiro.test(resid(final_model))
  528. cat("Shapiro-Wilk test for normality:\n")
  529. cat(" W =", format(shapiro_result$statistic, digits = 4), "\n")
  530. cat(" p-value =", format(shapiro_result$p.value, digits = 4), "\n\n")
  531. # Homogeneity of variances test
  532. hom_var_test <- function(m) {
  533. p <- coef(summary(lm(abs(resid(m)) ~ fitted(m))))[2, 4]
  534. return(p)
  535. }
  536. hom_p <- hom_var_test(final_model)
  537. cat("Homogeneity of variance test:\n")
  538. cat(" p-value =", format(hom_p, digits = 4), "\n")
  539. cat(" ", ifelse(hom_p < 0.05, "❌ Assumption violated", "✓ Assumption met"), "\n\n")
  540. # Model performance
  541. model_perf <- performance::r2(final_model)
  542. cat("Model Performance:\n")
  543. cat(" R² Conditional:", format(model_perf$R2_conditional, digits = 3), "\n")
  544. cat(" R² Marginal:", format(model_perf$R2_marginal, digits = 3), "\n")
  545. cat(" AIC:", format(AIC(final_model), digits = 1), "\n")
  546. cat(" BIC:", format(BIC(final_model), digits = 1), "\n")
  547. ```
  548. ### Random Effects Visualization
  549. ```{r random_effects, fig.height=6}
  550. # Extract random effects
  551. ranef_data <- as.data.frame(ranef(final_model)$Patient_ID)
  552. ranef_data$Patient_ID <- rownames(ranef_data)
  553. colnames(ranef_data)[1:2] <- c("Intercept", "Slope")
  554. # Merge with baseline diagnosis
  555. group_info <- df_final %>%
  556. dplyr::select(Patient_ID, DX_bl) %>%
  557. dplyr::distinct()
  558. ranef_with_group <- merge(ranef_data, group_info, by = "Patient_ID")
  559. # Plot random slopes by baseline diagnosis
  560. ggplot(ranef_with_group, aes(x = Slope, fill = DX_bl)) +
  561. geom_density(alpha = 0.6) +
  562. geom_vline(xintercept = 0, linetype = "dashed", color = "black", size = 1) +
  563. labs(title = "Distribution of Individual Progression Rates by Baseline Diagnosis",
  564. x = "Random Slope (MMSE change per month)",
  565. y = "Density",
  566. fill = "Baseline DX") +
  567. scale_fill_brewer(palette = "Set2") +
  568. theme_minimal() +
  569. theme(text = element_text(size = 12))
  570. ```
  571. # Alternative Model Specifications
  572. ## Testing MMSE_bl vs DX_bl as Baseline Predictor
  573. ```{r baseline_predictor_comparison, results='hide'}
  574. # Model with MMSE_bl (may have convergence issues)
  575. model_mmse <- glmmTMB(MMSE ~ MMSE_bl + Age_bl + Months_since_bl * APOE4 +
  576. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  577. data = df_final, REML = TRUE)
  578. # Model with MMSE_bl and BFGS optimizer
  579. model_mmse_bfgs <- glmmTMB(MMSE ~ MMSE_bl + Age_bl + Months_since_bl * APOE4 +
  580. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  581. data = df_final, REML = TRUE,
  582. control = glmmTMBControl(optimizer = optim, optArgs = list(method = "BFGS")))
  583. # Model with DX_bl
  584. model_dx <- glmmTMB(MMSE ~ DX_bl + Age_bl + Months_since_bl * APOE4 +
  585. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  586. data = df_final, REML = TRUE)
  587. # Model with DX_bl and BFGS optimizer
  588. model_dx_bfgs <- glmmTMB(MMSE ~ DX_bl + Age_bl + Months_since_bl * APOE4 +
  589. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  590. data = df_final, REML = TRUE,
  591. control = glmmTMBControl(optimizer = optim, optArgs = list(method = "BFGS")))
  592. ```
  593. ```{r baseline_comparison}
  594. models_baseline <- list(model_mmse, model_mmse_bfgs, model_dx, model_dx_bfgs)
  595. model_names_baseline <- c("MMSE_bl (default)", "MMSE_bl (BFGS)", "DX_bl (default)", "DX_bl (BFGS)")
  596. # Performance metrics
  597. aics <- sapply(models_baseline, AIC)
  598. r2cond <- sapply(models_baseline, function(m) r2(m)$R2_conditional)
  599. r2marg <- sapply(models_baseline, function(m) r2(m)$R2_marginal)
  600. shap_w <- sapply(models_baseline, function(m) shapiro.test(resid(m))$statistic)
  601. shap_p <- sapply(models_baseline, function(m) shapiro.test(resid(m))$p.value)
  602. homosced <- sapply(models_baseline, function(m) hom_var_test(m))
  603. perform_baseline <- data.frame(
  604. Model = model_names_baseline,
  605. AIC = aics,
  606. R2_Conditional = r2cond,
  607. R2_Marginal = r2marg,
  608. Shapiro_W = shap_w,
  609. Shapiro_p = shap_p,
  610. Homoscedasticity_p = homosced
  611. )
  612. kable(perform_baseline, caption = "Model Comparison: MMSE_bl vs DX_bl as Baseline Predictor", digits = 2)
  613. ```
  614. # Polygenic Score Analysis
  615. ## Polygenic Hazard Score (PHS)
  616. ```{r phs_analysis}
  617. # Read PHS data
  618. PHS <- read.csv("./QCjune/clinicaladni/DESIKANLAB_18Jun2024.csv")
  619. df_final$RID <- as.integer(df_final$RID)
  620. df_PHS <- inner_join(df_final, PHS, by = "RID", multiple = "all")
  621. cat("Patients with PHS data:", length(unique(df_PHS$Patient_ID)), "\n")
  622. cat("Datapoints:", nrow(df_PHS), "\n")
  623. # Model with PHS
  624. model_PHS <- glmmTMB(MMSE ~ DX_bl + Age_bl + PHS * Months_since_bl +
  625. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  626. data = df_PHS, REML = TRUE)
  627. # Extract coefficients
  628. phs_coef <- summary(model_PHS)$coef$cond
  629. kable(phs_coef, caption = "PHS Model Coefficients", digits = 4)
  630. ```
  631. ## Polygenic Risk Scores (PRS)
  632. ```{r prs_analysis}
  633. # Read PRS data
  634. PRS <- read.csv("./QCjune/clinicaladni/PRS_andrealtmann.csv")[, 1:5]
  635. df_PRS <- inner_join(df_final, PRS, by = "RID", multiple = "all")
  636. cat("Patients with PRS data:", length(unique(df_PRS$Patient_ID)), "\n")
  637. cat("Datapoints:", nrow(df_PRS), "\n")
  638. # Models with different PRS
  639. model_PRS1 <- glmmTMB(MMSE ~ DX_bl + Age_bl + PRS1 * Months_since_bl +
  640. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  641. data = df_PRS, REML = TRUE)
  642. model_PRS2 <- glmmTMB(MMSE ~ DX_bl + Age_bl + PRS2 * Months_since_bl +
  643. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  644. data = df_PRS, REML = TRUE)
  645. model_PRScs <- glmmTMB(MMSE ~ DX_bl + Age_bl + PRScs_auto * Months_since_bl +
  646. (1 + Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  647. data = df_PRS, REML = TRUE)
  648. # Summary table
  649. prs_results <- data.frame(
  650. Model = c("PRS1", "PRS2", "PRScs_auto"),
  651. AIC = c(AIC(model_PRS1), AIC(model_PRS2), AIC(model_PRScs)),
  652. PRS_Interaction_Beta = c(
  653. summary(model_PRS1)$coef$cond["PRS1:Months_since_bl", "Estimate"],
  654. summary(model_PRS2)$coef$cond["PRS2:Months_since_bl", "Estimate"],
  655. summary(model_PRScs)$coef$cond["PRScs_auto:Months_since_bl", "Estimate"]
  656. ),
  657. PRS_Interaction_p = c(
  658. summary(model_PRS1)$coef$cond["PRS1:Months_since_bl", "Pr(>|z|)"],
  659. summary(model_PRS2)$coef$cond["PRS2:Months_since_bl", "Pr(>|z|)"],
  660. summary(model_PRScs)$coef$cond["PRScs_auto:Months_since_bl", "Pr(>|z|)"]
  661. )
  662. )
  663. kable(prs_results, caption = "Polygenic Risk Score Models - Interaction Effects", digits = 4)
  664. ```
  665. # Advanced Model Testing
  666. ## Data Normalization
  667. Testing whether data transformation improves model fit:
  668. ```{r normalization}
  669. # Reflect and transform MMSE_bl (beta-distributed)
  670. MMSE_bl_refl <- max(df_final$MMSE_bl + 1) - df_final$MMSE_bl
  671. MMSE_bl_norm <- sqrt(MMSE_bl_refl)
  672. df_final$MMSE_bl_norm <- MMSE_bl_norm
  673. # Reflect and transform MMSE
  674. MMSE_refl <- max(df_final$MMSE + 1) - df_final$MMSE
  675. MMSE_norm <- sqrt(MMSE_refl)
  676. df_final$MMSE_norm <- MMSE_norm
  677. # Normalized model
  678. model_norm <- lmer(MMSE_norm ~ MMSE_bl_norm + Age_bl + Months_since_bl +
  679. (Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  680. data = df_final, REML = TRUE)
  681. ```
  682. ## Data Scaling
  683. ```{r scaling}
  684. # Scale numeric variables
  685. df_scaled <- df_final
  686. numeric_cols <- sapply(df_scaled, is.numeric)
  687. for (col in names(df_scaled)[numeric_cols]) {
  688. if (col != "Patient_ID" && col != "RID") {
  689. df_scaled[[col]] <- round((df_scaled[[col]] - mean(df_scaled[[col]], na.rm = TRUE)) /
  690. sd(df_scaled[[col]], na.rm = TRUE), 3)
  691. }
  692. }
  693. # Scaled model
  694. model_scaled <- lmer(MMSE ~ MMSE_bl + Age_bl + Months_since_bl +
  695. (Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  696. data = df_scaled, REML = TRUE)
  697. # Normalized and scaled model
  698. model_norm_scaled <- lmer(MMSE_norm ~ MMSE_bl_norm + Age_bl + Months_since_bl +
  699. (Months_since_bl|Patient_ID) + Education + Gender + PC1 + PC2,
  700. data = df_scaled, REML = TRUE)
  701. ```
  702. ## Comprehensive Model Comparison
  703. ```{r model_comparison_all}
  704. models_all <- list(final_model, model_scaled, model_norm, model_norm_scaled)
  705. model_names_all <- c("Original", "Scaled", "Normalized", "Normalized + Scaled")
  706. aics <- sapply(models_all, AIC)
  707. bics <- sapply(models_all, BIC)
  708. r2cond <- sapply(models_all, function(m) r2(m)$R2_conditional)
  709. r2marg <- sapply(models_all, function(m) r2(m)$R2_marginal)
  710. shap_w <- sapply(models_all, function(m) shapiro.test(resid(m))$statistic)
  711. shap_p <- sapply(models_all, function(m) shapiro.test(resid(m))$p.value)
  712. homosced <- sapply(models_all, function(m) hom_var_test(m))
  713. comparison_table <- data.frame(
  714. Model = model_names_all,
  715. AIC = aics,
  716. BIC = bics,
  717. R2_Conditional = r2cond,
  718. R2_Marginal = r2marg,
  719. Shapiro_W = shap_w,
  720. Shapiro_p = shap_p,
  721. Homoscedasticity_p = homosced
  722. )
  723. kable(comparison_table,
  724. caption = "Comprehensive Model Comparison: Transformation and Scaling Effects",
  725. digits = 4)
  726. ```
  727. ::: {.callout-tip}
  728. ## Model Selection Summary
  729. Lower AIC/BIC values indicate better model fit. Higher R² values indicate more variance explained. The Shapiro-Wilk test assesses normality of residuals (p > 0.05 desired). Homoscedasticity test assesses equal variance (p > 0.05 desired).
  730. :::
  731. # Patient Summary Table
  732. ## Generate Comprehensive Summary
  733. ```{r patient_summary_table}
  734. # Read FAM file for genotype status
  735. fam_data <- read.table("./QCjune/adnimerge_genomes/adnimerge.fam", header = FALSE)
  736. fam_ptids <- as.character(fam_data[, 2])
  737. # Get list of filtered patients
  738. filtered_patients <- unique(df_clean$Patient_ID)
  739. # Create summary for ALL patients in original dataset
  740. unique_patients <- unique(dataset$Patient_ID)
  741. summary_list <- list()
  742. for (patient in unique_patients) {
  743. patient_data <- dataset[dataset$Patient_ID == patient, ]
  744. patient_data <- patient_data[order(patient_data$Months_since_bl), ]
  745. # Basic info
  746. ptid <- as.character(patient)
  747. n_obs <- nrow(patient_data)
  748. n_mmse_obs <- sum(!is.na(patient_data$MMSE))
  749. # Baseline diagnosis
  750. dx_baseline <- as.character(patient_data$DX.bl[1])
  751. if (is.na(dx_baseline) || dx_baseline == "") dx_baseline <- "NA"
  752. # Last diagnosis
  753. max_idx <- which.max(patient_data$Months_since_bl)
  754. dx_last <- as.character(patient_data$DX[max_idx])
  755. if (is.na(dx_last) || dx_last == "") dx_last <- "NA"
  756. # Last MMSE
  757. last_mmse <- patient_data$MMSE[max_idx]
  758. if (is.na(last_mmse)) last_mmse <- "NA" else last_mmse <- as.character(last_mmse)
  759. # In FAM file
  760. in_fam <- ifelse(any(grepl(ptid, fam_ptids, fixed = TRUE)), "Yes", "No")
  761. # Amyloid-beta status
  762. av45_bl <- patient_data$AV45.bl[1]
  763. pib_bl <- patient_data$PIB.bl[1]
  764. csf_ab_bl <- patient_data$CSF_AB.bl[1]
  765. abeta_pos <- FALSE
  766. if (!is.na(av45_bl) && av45_bl >= th_amyl_AV45) abeta_pos <- TRUE
  767. if (!is.na(pib_bl) && pib_bl >= th_amyl_PIB) abeta_pos <- TRUE
  768. if (!is.na(csf_ab_bl) && csf_ab_bl <= th_amyl_CSF) abeta_pos <- TRUE
  769. abeta_status <- ifelse(abeta_pos, "AbetaPos", "AbetaNeg")
  770. # DX trajectory
  771. dx_trajectory <- sapply(patient_data$DX, function(x) {
  772. if (is.na(x) || x == "") return("NA")
  773. return(as.character(x))
  774. })
  775. dx_trajectory_str <- paste(dx_trajectory, collapse = "_")
  776. # Always CN
  777. dx_values_no_na <- patient_data$DX[!is.na(patient_data$DX) & patient_data$DX != ""]
  778. always_cn <- ""
  779. if (length(dx_values_no_na) > 0 && all(dx_values_no_na == "CN")) {
  780. always_cn <- "alwaysCN"
  781. }
  782. # Survives filtering
  783. survives <- ifelse(ptid %in% filtered_patients, "Yes", "No")
  784. summary_list[[patient]] <- data.frame(
  785. PTID = ptid,
  786. N_Observations = n_obs,
  787. N_MMSE_Observations = n_mmse_obs,
  788. DX_Baseline = dx_baseline,
  789. DX_Last = dx_last,
  790. Last_MMSE = last_mmse,
  791. In_FAM_File = in_fam,
  792. AbetaPos = abeta_status,
  793. DX_Trajectory = dx_trajectory_str,
  794. AlwaysCN = always_cn,
  795. Survives_Filtering = survives,
  796. stringsAsFactors = FALSE
  797. )
  798. }
  799. # Combine into data frame
  800. summary_table <- bind_rows(summary_list)
  801. summary_table <- summary_table[order(summary_table$PTID), ]
  802. # Save table
  803. write.csv(summary_table, "./patient_summary_table_complete.csv", row.names = FALSE, quote = TRUE)
  804. cat("Total patients in summary table:", nrow(summary_table), "\n")
  805. cat("Patients surviving filtering:", sum(summary_table$Survives_Filtering == "Yes"), "\n")
  806. cat("Patients not surviving filtering:", sum(summary_table$Survives_Filtering == "No"), "\n")
  807. ```
  808. ## Summary Table Preview
  809. ```{r summary_preview}
  810. kable(head(summary_table, 20),
  811. caption = "Patient Summary Table (First 20 Patients)",
  812. digits = 2)
  813. ```
  814. ## Filtering Statistics
  815. ```{r filtering_stats}
  816. filter_stats <- data.frame(
  817. Category = c("Total Patients", "Amyloid-Beta Positive", "After MMSE Filtering",
  818. "After Clinical Filtering", "With Genotype Data", "Final Cohort"),
  819. Count = c(
  820. length(unique(dataset$Patient_ID)),
  821. length(unique(df_amyloid$Patient_ID)),
  822. length(unique(df$Patient_ID)),
  823. length(unique(df_clean$Patient_ID)),
  824. length(unique(df_final$Patient_ID)),
  825. length(unique(df_final$Patient_ID))
  826. ),
  827. Percentage = c(
  828. 100,
  829. 100 * length(unique(df_amyloid$Patient_ID)) / length(unique(dataset$Patient_ID)),
  830. 100 * length(unique(df$Patient_ID)) / length(unique(dataset$Patient_ID)),
  831. 100 * length(unique(df_clean$Patient_ID)) / length(unique(dataset$Patient_ID)),
  832. 100 * length(unique(df_final$Patient_ID)) / length(unique(dataset$Patient_ID)),
  833. 100 * length(unique(df_final$Patient_ID)) / length(unique(dataset$Patient_ID))
  834. )
  835. )
  836. kable(filter_stats, caption = "Filtering Pipeline Statistics", digits = 2)
  837. ```
  838. # Conclusions
  839. This comprehensive analysis identified **`r length(unique(df_final$Patient_ID))` patients** with:
  840. - Confirmed amyloid-beta pathology
  841. - Sufficient longitudinal cognitive data (3-5 observations)
  842. - Evidence of cognitive decline
  843. - High-quality genotype data
  844. The optimal model (random slopes and intercepts with full covariates) achieved:
  845. - **R² Conditional**: `r format(r2(final_model)$R2_conditional, digits = 3)`
  846. - **R² Marginal**: `r format(r2(final_model)$R2_marginal, digits = 3)`
  847. - **AIC**: `r format(AIC(final_model), digits = 1)`
  848. Polygenic scores (PHS and PRS) were successfully integrated and show no effect on progression.
  849. # Session Information
  850. ```{r session_info}
  851. sessionInfo()
  852. ```

comprehensive_analysis.qmd at commit 0bd2f20, no license · at the source

Overview

Authors: Celeste E. Cohen1,2, Shane Fernandez3,4, Umran Yaman1,5, Ahmad R. Ehyaei6, Eleftheria Kodosaki1,5, Aydan Askarova7,8, Tenielle Porter3,4, Eleanor O’Brien3,4, Australian Imaging Biomarkers and Lifestyle Study, Alzheimer’s Disease Neuroimaging Initiative, Paul Maruff9,10, Alexi Nott7,8, John A. Hardy1,5,11, Simon M. Laws3,4, Dervis A. Salih1,5, Maryam Shoai1,5
  1. Department of Neurodegenerative Disease, UCL Queen Square Institute of Neurology,London, UK
  2. Wellcome Sanger Institute,Cambridge, UK
  3. Centre for Precision Health, Edith Cowan University,Joondalup, WA Australia
  4. Collaborative Genomics and Translation Group, School of Medical and Health Sciences, Edith Cowan University,Joondalup, WA Australia
  5. Dementia Research Institute, University College London,London, UK
  6. Max-Planck-Institute for Intelligent Systems,Tubingen, Germany
  7. UK Dementia Research Institute, Imperial College London,London, UK
  8. Department of Brain Sciences, Imperial College London,London, UK
  9. Florey Department of Neuroscience and Mental Health, University of Melbourne,Melbourne, Australia
  10. CogState Ltd,Melbourne, Australia
  11. Reta Lila Weston Institute of Neurological Studies,London, UK
Journal: Alzheimer's research & therapy, volume 18, issue 1, article 142
Dates: received 3 November 2025; accepted 24 March 2026; published online 25 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1186/s13195-026-02036-1 · PMID 42035106 · PMCID PMC13248444 · OpenAlex W4416378515
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), human (organism), Alzheimer's / dementia (population), clinical / translational (subfield)
Methods: Preprocessing, Statistics, Smoothing, state filtering, decompositions
Keywords: Progression, Alzheimer’s disease, Genome-wide association studies, APOE‑ε4, Polygenic risk score
MeSH: Alzheimer Disease*, Disease Progression*, Genetic Predisposition to Disease*, Aged, Aged, 80 and over, Apolipoprotein E4, Female, Genetic Risk Score, Genome-Wide Association Study, Humans, Longitudinal Studies, Male, Polymorphism, Single Nucleotide, Risk Factors (* major topic)
Topic: Alzheimer's disease research and treatments (Physiology, Medicine), according to OpenAlex
Funding: Imperial College London (President’s PhD Scholarships award); UK Dementia Research Institute through UK DRI Ltd (UKDRI-5208); Vivensa Foundation (AISRPG2305\26); Alzheimer’s Association (ADSF-24-1345198-C); Fidelity Foundation; Dolby Family Ventures; National Health and Medical Research Council (GNT1161706, GNT1191535); Fidelity Foundation, United States
Citations: not cited yet (Europe PMC); 103 references in the paper

Abstract

Background: Recent trials in Alzheimer’s disease (AD) demonstrate encouraging outcomes. These trials target risk mechanisms identified through genetic analysis whilst directly aiming to reduce progression rates. Evidence from other neurodegenerative diseases suggests the genetics of progression is distinct from risk of disease. To expand these initial successes and improve clinical outcomes further we need to understand genetics of progression of disease. These can be deduced through rigorous analysis of meticulously phenotyped longitudinal cohorts. In this study we first looked at known genetic drivers of risk, namely polygenic risk scores for AD and APOE‑ε4, to assess their role in progression. This was then extended to a genome wide association analysis to identify the role of other genetic variants in progression of AD.

Methods: A total of 387 individuals with genetic data, amyloid positivity, and in active decline (ADNI (n = 222) and AIBL(n = 165)) were used to perform generalised mixed effects linear model genome wide association studies of longitudinal cognitive decline as measured by mini mental state examination (MMSE). The resulting summary statistics were subjected to functional annotation, and colocalisation analyses.

Results: Established AD risk factors, including APOE‑ε4 dosage and polygenic risk scores, were not associated with disease progression in amyloid positive individuals who are actively declining. A mixed effects GWAS meta-analysis revealed one genome-wide significant locus on chromosome 22 (rs78369883) and several nominally significant loci linked with AD progression. Functional annotation, finemapping, and colocalisation analyses implicated genes primarily involved in immune response, neurodegeneration (including tau pathology), brain resilience, and neurogenesis. These progression-related genes were significantly enriched in neuronal-interferon-microglial signalling pathways and normal homeostatic processes of neuronal networks, with specific enrichment in dopaminergic and inhibitory neuronal populations.

Conclusion: These findings enhance our understanding of the biological underpinnings of AD progression, opening new avenues for therapeutic intervention.

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

MaryamShoai/Codes

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 0bd2f20200b3fcd0d8403aada2da72be3393bb58, 16 October 2025
Languages: R (5), Quarto (1)
Size: 19 files, 6 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README, 1 notebook
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (4 files), data.table (3 files), ggplot2 (3 files), glmmTMB (2 files), cowplot (1 file), easystats (1 file), emmeans (1 file), lme4 (1 file), lmerTest (1 file), metafor (1 file), nlme (1 file), reticulate (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
7 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;
  • 6 scripts, each with its path and the digest of its content;
  • 3 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

The codes used for running the mixed effect models and the full resulting summary statistics are available from: https://github.com/MaryamShoai/Codes The Alzheimer's Disease Neuroimaging Initiative (ADNI) and the Australian Imaging, Biomarkers and Lifestyle (AIBL) study of aging are publicly available and can be accessed upon request. ADNI data are available via the LONI Image and Data Archive (IDA LONI). AIBL data are available via expression of interest to AIBL data access committee.

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

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 16 authors, 5 keywords, 14 MeSH terms, 8 funders, 103 references.

Cite

This paper

Cohen, C. E., Fernandez, S., Yaman, U., Ehyaei, A. R., Kodosaki, E., Askarova, A., Porter, T., O’Brien, E., Australian Imaging Biomarkers and Lifestyle Study, Alzheimer’s Disease Neuroimaging Initiative, Maruff, P., Nott, A., Hardy, J. A., Laws, S. M., A. Salih, D., & Shoai, M. (2026). Genetic drivers of progression in Alzheimer's disease are distinct from disease risk. Alzheimer's research & therapy, 18(1), 142. https://doi.org/10.1186/s13195-026-02036-1

BibTeX

@article{cohen2026genetic,
author = {Cohen, Celeste E. and Fernandez, Shane and Yaman, Umran and Ehyaei, Ahmad R. and Kodosaki, Eleftheria and Askarova, Aydan and Porter, Tenielle and O’Brien, Eleanor and {Australian Imaging Biomarkers and Lifestyle Study} and {Alzheimer’s Disease Neuroimaging Initiative} and Maruff, Paul and Nott, Alexi and Hardy, John A. and Laws, Simon M. and A. Salih, Dervis and Shoai, Maryam},
title = {{Genetic drivers of progression in Alzheimer's disease are distinct from disease risk}},
journal = {Alzheimer's research \& therapy},
year = {2026},
month = apr,
volume = {18},
number = {1},
pages = {142},
publisher = {BMC},
issn = {1758-9193},
doi = {10.1186/s13195-026-02036-1},
url = {https://doi.org/10.1186/s13195-026-02036-1},
pmid = {42035106},
pmcid = {PMC13248444}
}

RIS

TY - JOUR
AU - Cohen, Celeste E.
AU - Fernandez, Shane
AU - Yaman, Umran
AU - Ehyaei, Ahmad R.
AU - Kodosaki, Eleftheria
AU - Askarova, Aydan
AU - Porter, Tenielle
AU - O’Brien, Eleanor
AU - Australian Imaging Biomarkers and Lifestyle Study
AU - Alzheimer’s Disease Neuroimaging Initiative
AU - Maruff, Paul
AU - Nott, Alexi
AU - Hardy, John A.
AU - Laws, Simon M.
AU - A. Salih, Dervis
AU - Shoai, Maryam
TI - Genetic drivers of progression in Alzheimer's disease are distinct from disease risk
T2 - Alzheimer's research & therapy
J2 - Alzheimers Res Ther
PY - 2026
DA - 2026/04/25
VL - 18
IS - 1
SP - 142
SN - 1758-9193
PB - BMC
DO - 10.1186/s13195-026-02036-1
UR - https://doi.org/10.1186/s13195-026-02036-1
LA - en
ER -

CSL-JSON

{
"id": "10.1186/s13195-026-02036-1",
"type": "article-journal",
"title": "Genetic drivers of progression in Alzheimer's disease are distinct from disease risk",
"container-title": "Alzheimer's research & therapy",
"author": [
{
"family": "Cohen",
"given": "Celeste E."
},
{
"family": "Fernandez",
"given": "Shane"
},
{
"family": "Yaman",
"given": "Umran"
},
{
"family": "Ehyaei",
"given": "Ahmad R."
},
{
"family": "Kodosaki",
"given": "Eleftheria"
},
{
"family": "Askarova",
"given": "Aydan"
},
{
"family": "Porter",
"given": "Tenielle"
},
{
"family": "O’Brien",
"given": "Eleanor"
},
{
"literal": "Australian Imaging Biomarkers and Lifestyle Study"
},
{
"literal": "Alzheimer’s Disease Neuroimaging Initiative"
},
{
"family": "Maruff",
"given": "Paul"
},
{
"family": "Nott",
"given": "Alexi"
},
{
"family": "Hardy",
"given": "John A."
},
{
"family": "Laws",
"given": "Simon M."
},
{
"family": "A. Salih",
"given": "Dervis"
},
{
"family": "Shoai",
"given": "Maryam"
}
],
"container-title-short": "Alzheimers Res Ther",
"volume": "18",
"issue": "1",
"page": "142",
"DOI": "10.1186/s13195-026-02036-1",
"PMID": "42035106",
"PMCID": "PMC13248444",
"ISSN": "1758-9193",
"publisher": "BMC",
"URL": "https://doi.org/10.1186/s13195-026-02036-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
25
]
]
}
}

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/s41380-026-03686-1 [code]
Early oligodendrocyte dysfunction signature in Alzheimer's disease: Insights from DNA methylomics and transcriptomics.
Journal: Molecular psychiatry
In common: data.table, ggplot2, tidyverse, Alzheimer's / dementia, genetics / omics, 5 references, 2 authors
[2] doi:10.1038/s41562-026-02486-5 [code]
Genome-wide association studies of infant and toddler temperament in European and multi-ancestry populations.
Journal: Nature human behaviour
In common: metafor, nlme, data.table, 2 other tools, genetics / omics, 7 references
[3] doi:10.1016/j.celrep.2026.117505 [code]
Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice.
Journal: Cell reports
In common: glmmTMB, metafor, nlme, 6 other tools, Alzheimer's / dementia
[4] doi:10.1371/journal.pgen.1012170 [code]
Genome wide association study meta-analysis of neuropathologic lesions of Alzheimer's disease and related dementias in a multi-site autopsy cohort.
Journal: PLoS genetics
In common: data.table, ggplot2, tidyverse, Alzheimer's / dementia, clinical / translational, genetics / omics, 8 references
[5] doi:10.1002/hbm.70605 [code]
BrainEnrich: Revealing Biological Insights for Imaging-Derived Phenotypes Through Transcriptomic Enrichment.
Journal: Human brain mapping
In common: nlme, reticulate, easystats, 5 other tools, genetics / omics, 1 reference
[6] doi:10.1038/s41467-026-71682-8 [code]
GWAS meta-analysis of cerebrospinal fluid Alzheimer's biomarkers reveals loci regulating lipids, brain volume and autophagy.
Journal: Nature communications
In common: data.table, ggplot2, tidyverse, Alzheimer's / dementia, genetics / omics, 7 references
[7] doi:10.1038/s41467-026-73865-9 [code]
Histamine shapes the neurocomputational dynamics of human learning.
Journal: Nature communications
In common: metafor, easystats, emmeans, 6 other tools
[8] doi:10.1073/pnas.2606871123 [code]
Oxytocin modulates the neurocomputational mechanisms engaged in learning rank relationships in social networks.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: nlme, easystats, emmeans, 6 other tools
[9] doi:10.1186/s40168-026-02342-8 [code]
Impacts of host genetics on gut microbiome composition in Alzheimer's disease.
Journal: Microbiome
In common: metafor, cowplot, data.table, 2 other tools, Alzheimer's / dementia, genetics / omics, 4 references
[10] doi:10.1038/s41467-026-74753-y [code]
A human-specific microRNA controls the timing of excitatory synaptogenesis.
Journal: Nature communications
In common: reticulate, easystats, emmeans, 6 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

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