OSCR

Peripheral blood microarray-based transcriptomic and epigenetic analyses identify immune, inflammation, and metabolic dysregulation in Alzheimer's disease.

Code ↔ Paper

16 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 16 matches
  1. [1] § Methods › DNA methylation profiling ↔ R/DNAm-preprocessing.R, lines 107–150 · score 0.85 · bisulfite conversion rates, rmSNPandCH, cross hybridizing, sex chromosomes, genomic, technical
  2. [2] § Methods › Functional enrichment and visualization ↔ R/differential_analyses.R, lines 723–852 · score 0.81 · normalized enrichment score, MSigDB, fold change, NES, BH FDR, fgsea
  3. [3] § Methods › Functional enrichment and visualization ↔ R/MOFA.R, lines 361–502 · score 0.81 · normalized enrichment score, MSigDB, NES, ranked, fgsea, ORA
  4. [4] § Methods › Amyloid PET and MRI-based atrophy measures ↔ R/phenotype.R, lines 538–597 · score 0.74 · cubic age, meta ROI cortical, cortical thickness, Hippocampal volumes, ICV, ADNI
  5. [5] § Methods › Gene expression profiling ↔ R/phenotype.R, lines 599–637 · score 0.69 · Affymetrix Human Genome, U219, chip, Gene expression, ADNI, probes
  6. [6] § Methods › Network and multi-omics analyses ↔ R/WGCNA.R, lines 845–940 · score 0.69 · principal component, module eigengene, limma, linear, fit, matrices
  7. [7] § Methods › Survival analysis for MCI to AD progression ↔ R/WGCNA.R, lines 1418–1563 · score 0.60 · module assignments, original modules, MEs, AD progression, predictors, covariates
  8. [8] § Methods › DNA methylation profiling ↔ R/phenotype.R, lines 1131–1173 · score 0.58 · ADNI Genetics Core, DNA Methylation, QC
  9. [9] § Results › Co-methylation network analysis reveals immune-related dysregulation in AD ↔ R/wgcna2cytoscape.R, lines 151–274 · score 0.56 · co methylation network, CpGs, Hub gene, WGCNA, modules
  10. [10] § Results › Gene co-expression network analysis (WGCNA) identifies robust immune networks ↔ R/WGCNA.R, lines 608–633 · score 0.55 · S13a, S13c, WGCNA, women, gene
  11. [11] § Methods › Network and multi-omics analyses ↔ R/MOFA.R, lines 35–89 · score 0.55 · MOFA model, limma, trained, variance, linear, matched
  12. [12] § Methods › DNA methylation profiling ↔ R/phenotype.R, lines 1393–1471 · score 0.55 · immune cell, DNA methylation, EpiDISH, blood, profiling
  13. [13] § Results › Participant characteristics ↔ R/tables.R, lines 18–85 · score 0.55 · amyloid PET positive, hippocampal volume, S1a, carriers, variables, plasma
  14. [14] § Results › Gene co-expression network analysis (WGCNA) identifies robust immune networks ↔ R/WGCNA.R, lines 446–485 · score 0.54 · module membership, gene co expression, gene modules, MM, WGCNA, network
  15. [15] § Methods › Sensitivity analyses ↔ R/tables.R, lines 18–85 · score 0.54 · amyloid positive, carrier status, amyloid negative, amyloid PET, MCI, CU
  16. [16] § Results › AD-related gene modules are linked to AD endophenotypes ↔ R/phenotype.R, lines 236–281 · score 0.53 · mini mental state, examination, cognitive, MMSE

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R · 1,471 lines · 65 KB · no license · 5 matches

  1. library(SummarizedExperiment)
  2. library(tidyverse)
  3. library(lme4)
  4. library(EpiDISH)
  5. # Define Paths & Functions ####
  6. base_dir <- ".../data"
  7. proc_dir <- ".../processed"
  8. idat_dir <- ".../IDAT"
  9. addZeros <- function(x){
  10. sprintf("%04d", as.integer(x))
  11. }
  12. # Define function to match tables at same time or within 1-year of gene expression profiling
  13. gene_within1yr <- function(df1, df2_Edate, df2_VISCODE = FALSE) {
  14. df1 <- df1
  15. # If table contains both VISCODE and EXAMDATE, use both to determine closest match
  16. if (df2_VISCODE){
  17. df1 <- df1 %>%
  18. mutate(date_diff = abs(lubridate::time_length(difftime(GeneX_Edate, {{ df2_Edate }}), "years"))) %>%
  19. mutate(Edate_match = ifelse(date_diff <= 1, 1, 0),
  20. VISCODE_match = ifelse(VISCODE == VISCODE2, 1, 0)) %>%
  21. group_by(RID) %>%
  22. mutate(min_match_date = min(date_diff)) %>%
  23. # Same time = VISCODE_match; within 1-year = Edate_match
  24. mutate(match_priority = case_when(
  25. VISCODE_match == 1 ~ 1,
  26. Edate_match == 1 ~ 2,
  27. TRUE ~ 3
  28. )) %>%
  29. # Only keep matches
  30. filter(match_priority <= 2) %>%
  31. # If multiple matches, keep min date diff
  32. filter(min_match_date == date_diff) %>%
  33. ungroup() %>%
  34. distinct(RID, VISCODE, .keep_all = T) %>%
  35. dplyr::select(-c(Edate_match, VISCODE_match, match_priority, date_diff,
  36. min_match_date, VISCODE2))
  37. } else{
  38. # If table is missing VISCODE, use EXAMDATE only
  39. df1 <- df1 %>%
  40. mutate(date_diff = abs(lubridate::time_length(difftime(GeneX_Edate, {{ df2_Edate }}), "years"))) %>%
  41. mutate(Edate_match = ifelse(date_diff <= 1, 1, 0)) %>%
  42. group_by(RID) %>%
  43. mutate(min_match_date = min(date_diff)) %>%
  44. filter(Edate_match == 1) %>%
  45. filter(min_match_date == date_diff) %>%
  46. ungroup() %>%
  47. distinct(RID, VISCODE, .keep_all = T) %>%
  48. dplyr::select(-c(Edate_match, date_diff, min_match_date))
  49. }
  50. }
  51. # Define function to match tables at same time or within 1-year of DNA methylation profiling
  52. dnam_within1yr <- function(df1, df2_Edate, df2_VISCODE = FALSE) {
  53. df1 <- df1
  54. # If table contains both VISCODE and EXAMDATE, use both to determine closest match
  55. if (df2_VISCODE){
  56. df1 <- df1 %>%
  57. mutate(date_diff = abs(lubridate::time_length(difftime(DNAm_Edate, {{ df2_Edate }}), "years"))) %>%
  58. mutate(Edate_match = ifelse(date_diff <= 1, 1, 0),
  59. VISCODE_match = ifelse(VISCODE == VISCODE2, 1, 0)) %>%
  60. group_by(RID, DNAm_Edate) %>%
  61. mutate(min_match_date = min(date_diff)) %>%
  62. # Same time = VISCODE_match; within 1-year = Edate_match
  63. mutate(match_priority = case_when(
  64. VISCODE_match == 1 ~ 1,
  65. Edate_match == 1 ~ 2,
  66. TRUE ~ 3
  67. )) %>%
  68. # Only keep matches
  69. filter(match_priority <= 2) %>%
  70. # If multiple matches, keep min date diff
  71. filter(min_match_date == date_diff) %>%
  72. ungroup() %>%
  73. distinct(RID, barcodes, .keep_all = T) %>%
  74. dplyr::select(-c(Edate_match, VISCODE_match, match_priority, date_diff,
  75. min_match_date, VISCODE2))
  76. } else{
  77. # If table is missing VISCODE, use EXAMDATE only
  78. df1 <- df1 %>%
  79. mutate(date_diff = abs(lubridate::time_length(difftime(DNAm_Edate, {{ df2_Edate }}), "years"))) %>%
  80. mutate(Edate_match = ifelse(date_diff <= 1, 1, 0)) %>%
  81. group_by(RID, DNAm_Edate) %>%
  82. mutate(min_match_date = min(date_diff)) %>%
  83. filter(Edate_match == 1) %>%
  84. filter(min_match_date == date_diff) %>%
  85. ungroup() %>%
  86. distinct(RID, barcodes, .keep_all = T) %>%
  87. dplyr::select(-c(Edate_match, date_diff, min_match_date))
  88. }
  89. }
  90. #______________________________________Prepare ADNI Tables Prior to Merging With -Omics Tables______________________________________#####
  91. message("Now reading in and preparing ADNI tables prior to merging with -omics metadata...")
  92. ## Demographics Table ####
  93. # Read in and prepare demographics table
  94. demog <- read.csv(file.path(base_dir, "All_Subjects_PTDEMOG_28Oct2024.csv"), stringsAsFactors = FALSE)
  95. adni_merge <- read.csv(file.path(base_dir, "ADNIMERGE.csv"), stringsAsFactors = FALSE)
  96. adni_merge_unique <- adni_merge %>% distinct_at(vars(RID), .keep_all = TRUE)
  97. demog_unique <- demog %>% distinct_at(vars(RID), .keep_all = TRUE)
  98. demog_merged <- left_join(adni_merge_unique, demog_unique %>%
  99. dplyr::select(RID, PTDOB, PTDOBYY), by = "RID")
  100. demog_merged <- demog_merged %>%
  101. dplyr::select(RID, ORIGPROT, AGE, PTDOB, PTGENDER,
  102. PTEDUCAT, PTRACCAT, PTETHCAT) %>%
  103. dplyr::rename(AGE_bl = AGE,
  104. PTDOBMMYY = PTDOB)
  105. demog_merged <- demog_merged %>%
  106. mutate(day = 1,
  107. PTDOB = as.character(paste(day, PTDOBMMYY, sep = "/")))
  108. demog_merged$PTDOB <- as.Date(demog_merged$PTDOB, format = "%d/%m/%Y")
  109. demog_merged <- demog_merged %>%
  110. dplyr::select(-c(PTDOBMMYY, day))
  111. demog_merged <- relocate(demog_merged, "PTDOB", .before = "AGE_bl")
  112. demog_merged$RID <- addZeros(demog_merged$RID)
  113. ## Diagnosis Table ####
  114. # Read in and prepare diagnosis table
  115. dx <- read.csv(file.path(base_dir, "All_Subjects_DXSUM_22Nov2024.csv"), stringsAsFactors = FALSE)
  116. dx <- dx %>%
  117. dplyr::select(RID, DIAGNOSIS, EXAMDATE, VISCODE2) %>%
  118. dplyr::rename(DX_Edate = EXAMDATE,
  119. VISCODE = VISCODE2) %>%
  120. mutate(DXSUM = case_when(
  121. (DIAGNOSIS == 1) ~ "CU",
  122. (DIAGNOSIS == 2) ~ "MCI",
  123. (DIAGNOSIS == 3) ~ "Dementia"),
  124. VISCODE = case_when(
  125. VISCODE == "sc" ~ "bl",
  126. TRUE ~ VISCODE)
  127. ) %>% filter(!is.na(DXSUM))
  128. dx$RID <- addZeros(dx$RID)
  129. dx <- dx %>%
  130. arrange(RID, DX_Edate) %>%
  131. group_by(RID) %>%
  132. mutate(DX_last = DXSUM[[length(DXSUM)]]) %>%
  133. ungroup() %>%
  134. mutate(DX_Edate = as.Date(DX_Edate, "%Y-%m-%d")) %>%
  135. dplyr::select(RID, VISCODE, DX_Edate, DXSUM, DX_last)
  136. ## Registry Table ####
  137. # Read in and prepare registry table
  138. registry <- read.csv(file.path(base_dir, "REGISTRY_28Jul2023.csv"), stringsAsFactor = FALSE)
  139. registry$RID <- addZeros(registry$RID)
  140. registry <- registry %>% dplyr::select(RID, VISCODE, VISCODE2)
  141. registry$VISCODE <- ifelse(registry$VISCODE == "v03", "bl", registry$VISCODE)
  142. registry$VISCODE <- ifelse(registry$VISCODE == "v04", "m03", registry$VISCODE)
  143. registry$VISCODE <- ifelse(registry$VISCODE == "v05", "m06", registry$VISCODE)
  144. registry <- registry %>% distinct_at(vars(RID, VISCODE), .keep_all = TRUE)
  145. ## Neurofilament Light Chain (NfL) Table ####
  146. # Read in and prepare NfL table
  147. nfl_data <- read.csv(file.path(base_dir, "ADNI_BLENNOWPLASMANFLLONG_10_03_18_22Apr2025.csv"), stringsAsFactor = FALSE, check.names = FALSE)
  148. # Recode with correct VISCODE2
  149. nfl_data$VISCODE2[which(nfl_data$RID=="1097"&nfl_data$EXAMDATE=="2013-01-31")]<-"m72"
  150. nfl_data <- nfl_data %>%
  151. group_by(RID,EXAMDATE) %>%
  152. mutate(PLASMA_NFL = mean(PLASMA_NFL)) %>%
  153. dplyr::distinct_at(vars(RID,EXAMDATE), .keep_all = TRUE) %>%
  154. ungroup()
  155. nfl_data <- nfl_data %>%
  156. mutate(VISCODE2 = case_when(
  157. VISCODE2 == "sc" ~ "bl",
  158. TRUE ~ VISCODE2
  159. )) %>%
  160. dplyr::select(RID, VISCODE2, EXAMDATE, PLASMA_NFL) %>%
  161. dplyr::rename(NfL_Edate = EXAMDATE, NfL_B = PLASMA_NFL)
  162. nfl_data$NfL_Edate <- as.Date(nfl_data$NfL_Edate, "%Y-%m-%d")
  163. nfl_data$RID <- addZeros(nfl_data$RID)
  164. ## Estimated CSF AB/TAU-Positivity Onset Table ####
  165. # Read in and prepare estimated CSF AB/TAU positivity onset table
  166. csf_age <- read.csv(file.path(base_dir, "estimated_csf_ages.csv"), stringsAsFactor = FALSE, check.names = FALSE)
  167. colnames(csf_age)[2] <- "AB_AGE"
  168. colnames(csf_age)[3] <- "TAU_AGE"
  169. csf_age$AB_AGE <- ifelse(csf_age$AB_AGE == "NaN", NA, csf_age$AB_AGE)
  170. csf_age$TAU_AGE <- ifelse(csf_age$TAU_AGE == "NaN", NA, csf_age$TAU_AGE)
  171. csf_age$RID <- addZeros(csf_age$RID)
  172. csf_age <- csf_age[!duplicated(csf_age$RID),]
  173. ## APOE Genotypes Table ####
  174. # Read in and prepare APOE genotypes table
  175. apoeres <- read.csv(file.path(base_dir, "APOERES_24Aug2023.csv"), stringsAsFactor = FALSE, check.names = FALSE)
  176. apoeres$APOE4 <- as.numeric(apply(apoeres[, c('APGEN1', 'APGEN2')]==4, 1, sum)>=1)
  177. apoeres$APOE2 <- as.numeric(apply(apoeres[, c('APGEN1', 'APGEN2')]==2, 1, sum)>=1)
  178. apoeres$APOE3 <- as.numeric(apply(apoeres[, c('APGEN1', 'APGEN2')]==3, 1, sum)>=1)
  179. apoeres$APOE4_n <- apply(apoeres[, c('APGEN1', 'APGEN2')], 1, function(row) {
  180. if (any(row == 4)) {
  181. if (all(row == 4)) {
  182. return(2)
  183. } else {
  184. return(1)
  185. }
  186. } else {
  187. return(0)
  188. }
  189. })
  190. apoeres$APOE2_n <- apply(apoeres[, c('APGEN1', 'APGEN2')], 1, function(row) {
  191. if (any(row == 2)) {
  192. if (all(row == 2)) {
  193. return(2)
  194. } else {
  195. return(1)
  196. }
  197. } else {
  198. return(0)
  199. }
  200. })
  201. apoeres$APOE3_n <- apply(apoeres[, c('APGEN1', 'APGEN2')], 1, function(row) {
  202. if (any(row == 3)) {
  203. if (all(row == 3)) {
  204. return(2)
  205. } else {
  206. return(1)
  207. }
  208. } else {
  209. return(0)
  210. }
  211. })
  212. apoeres_df <- apoeres[c("RID", "APOE4", "APOE2", "APOE3", "APOE4_n", "APOE2_n", "APOE3_n")]
  213. apoeres_df$RID <- addZeros(apoeres_df$RID)
  214. ## Cognitive Performance Tables ####
  215. # Read in and prepare cognitive performance tables
  216. # Clinical Dementia Rating (CDR)
  217. cdr <- read.csv(file.path(base_dir, "All_Subjects_CDR_22Nov2024.csv"), stringsAsFactors = FALSE)
  218. cdr <- cdr[cdr$CDGLOBAL>=0,]
  219. cdr$CDRSB <- rowSums(cdr[,c('CDMEMORY', 'CDORIENT', 'CDJUDGE', 'CDCOMMUN', 'CDHOME', 'CDCARE')])
  220. cdr <- cdr %>%
  221. mutate(VISCODE2 = case_when(
  222. VISCODE2 == "sc" ~ "bl",
  223. TRUE ~ VISCODE2
  224. )) %>%
  225. dplyr::select(RID, VISCODE2, VISDATE, CDRSB) %>%
  226. dplyr::rename(VISCODE = VISCODE2)
  227. cdr$RID <- addZeros(cdr$RID)
  228. cdr$CDR_Edate <- as.Date(cdr$VISDATE, "%Y-%m-%d")
  229. # 13-Item Alzheimer's Disease Assessment Scale–Cognitive Subscale (ADAS13)
  230. adas <- read.csv(file.path(base_dir, "All_Subjects_ADAS_ADNIGO23_22Nov2024.csv"), stringsAsFactors = FALSE)
  231. adas <- adas %>%
  232. dplyr::select(RID, VISCODE2, TOTAL13) %>%
  233. dplyr::rename(VISCODE = VISCODE2) %>%
  234. dplyr::rename(ADAS13 = TOTAL13) %>%
  235. group_by(RID, VISCODE) %>%
  236. mutate(across(.cols = dplyr::where(is.numeric), .fns=~mean(.,na.rm = TRUE))) %>%
  237. distinct_at(vars(RID), .keep_all = TRUE)
  238. adas$RID <- addZeros(adas$RID)
  239. adas$ADAS13 <- ifelse(adas$ADAS13 == "NaN", NA, adas$ADAS13)
  240. # Mini-Mental State Examination (MMSE)
  241. mmse <- read.csv(file.path(base_dir, "All_Subjects_MMSE_22Nov2024.csv"), stringsAsFactors = FALSE)
  242. mmse <- mmse %>%
  243. dplyr::select(RID, VISCODE2, VISDATE, MMSCORE) %>%
  244. dplyr::rename(MMSE = MMSCORE) %>%
  245. mutate(VISCODE2 = case_when(
  246. VISCODE2 == "sc" ~ "bl",
  247. TRUE ~ VISCODE2
  248. )) %>%
  249. group_by(RID, VISCODE2) %>%
  250. mutate(across(.cols = dplyr::where(is.numeric), .fns=~mean(., na.rm = TRUE))) %>%
  251. distinct_at(vars(RID), .keep_all = TRUE) %>%
  252. ungroup()
  253. mmse$MMSE_Edate <- as.Date(mmse$VISDATE, "%Y-%m-%d")
  254. mmse <- mmse %>% dplyr::select(-VISDATE)
  255. mmse$MMSE <- ifelse(mmse$MMSE == "NaN", NA, mmse$MMSE)
  256. mmse$RID <- addZeros(mmse$RID)
  257. ## CSF Biomarkers Table ####
  258. # Read in and prepare CSF biomarkers table
  259. upenn_csf <- read.csv(file.path(base_dir, "All_Subjects_UPENNBIOMK_ROCHE_ELECSYS_23Nov2024.csv"), stringsAsFactors = FALSE)
  260. upenn_csf <- upenn_csf %>%
  261. arrange(desc(RUNDATE)) %>%
  262. distinct_at(vars(RID, VISCODE2), .keep_all = TRUE) #keep most recent replicate
  263. upenn_csf <- upenn_csf %>%
  264. dplyr::rename(ABETA40_csf = ABETA40,
  265. ABETA42_csf = ABETA42,
  266. tTAU_csf = TAU,
  267. pTAU181_csf = PTAU)
  268. upenn_csf <- upenn_csf %>%
  269. mutate(ABETA42_csf = round(ABETA42_csf),
  270. ptau_pos_csf = pTAU181_csf > 24,
  271. amyloid_pos_csf = ABETA42_csf < 980,
  272. ptau_ab_ratio_csf = (as.numeric(pTAU181_csf) / as.numeric(ABETA42_csf)),
  273. ad_pathology_pos_csf = ptau_ab_ratio_csf > 0.025)
  274. upenn_csf <- upenn_csf %>%
  275. mutate(across(.cols=c(ABETA42_csf, ABETA40_csf, tTAU_csf,pTAU181_csf, ptau_ab_ratio_csf), .fns=as.numeric))
  276. upenn_csf <- upenn_csf %>%
  277. mutate(across(.cols=c(ptau_pos_csf, amyloid_pos_csf, ad_pathology_pos_csf), .fns=as.numeric))
  278. # reassign records with VISCODE2 = "UNK" to correct VISCODE2
  279. upenn_csf$VISCODE2[upenn_csf$RID==89 & upenn_csf$VISCODE2=="UNK"]<-"m162"
  280. upenn_csf$VISCODE2[upenn_csf$RID==232 & upenn_csf$VISCODE2=="UNK"]<-"m36"
  281. upenn_csf$VISCODE2[upenn_csf$RID==726 & upenn_csf$VISCODE2=="UNK"]<-"m24"
  282. upenn_csf$VISCODE2[upenn_csf$RID==790 & upenn_csf$VISCODE2=="UNK"]<-"m18"
  283. upenn_csf$VISCODE2[upenn_csf$RID==1200 & upenn_csf$VISCODE2=="UNK"]<-"m36"
  284. upenn_csf$VISCODE2[upenn_csf$RID==2373 & upenn_csf$VISCODE2=="UNK"]<-"m120"
  285. upenn_csf$VISCODE2[upenn_csf$RID==4216 & upenn_csf$VISCODE2=="UNK"]<-"m102"
  286. upenn_csf$VISCODE2[upenn_csf$RID==4393 & upenn_csf$VISCODE2=="UNK"]<-"m96"
  287. upenn_csf$VISCODE2[upenn_csf$RID==4643 & upenn_csf$VISCODE2=="UNK"]<-"m72"
  288. upenn_csf$VISCODE2[upenn_csf$RID==5140 & upenn_csf$VISCODE2=="UNK"]<-"m84"
  289. upenn_csf$VISCODE2[upenn_csf$RID==6161 & upenn_csf$VISCODE2=="UNK"]<-"bl"
  290. upenn_csf$VISCODE2[upenn_csf$RID==6880 & upenn_csf$VISCODE2=="UNK"]<-"bl"
  291. upenn_csf$VISCODE2[upenn_csf$RID==6906 & upenn_csf$VISCODE2=="UNK"]<-"bl"
  292. upenn_csf <- upenn_csf %>%
  293. dplyr::select(RID, VISCODE2, EXAMDATE, ABETA40_csf, ABETA42_csf, tTAU_csf, pTAU181_csf,
  294. ptau_pos_csf, amyloid_pos_csf, ptau_ab_ratio_csf, ad_pathology_pos_csf) %>%
  295. dplyr::rename(VISCODE = VISCODE2,
  296. CSF_Edate = EXAMDATE)
  297. upenn_csf$CSF_Edate <- as.Date(upenn_csf$CSF_Edate, "%Y-%m-%d")
  298. upenn_csf$RID <- addZeros(upenn_csf$RID)
  299. # Identify the first/last negative/positive dates of CSF amyloid
  300. last_negative_date_csf <- upenn_csf %>%
  301. group_by(RID) %>%
  302. filter(amyloid_pos_csf == 0) %>%
  303. mutate(last_AB_neg_date = max(CSF_Edate)) %>%
  304. dplyr::select(RID, last_AB_neg_date) %>%
  305. ungroup() %>%
  306. distinct()
  307. first_positive_date_csf <- upenn_csf %>%
  308. group_by(RID) %>%
  309. filter(amyloid_pos_csf == 1) %>%
  310. mutate(first_AB_pos_date = min(CSF_Edate)) %>%
  311. dplyr::select(RID, first_AB_pos_date) %>%
  312. ungroup() %>%
  313. distinct()
  314. last_negative_date2_csf <- upenn_csf %>%
  315. group_by(RID) %>%
  316. filter(ptau_pos_csf == 0) %>%
  317. mutate(last_TAU_neg_date2 = max(CSF_Edate)) %>%
  318. dplyr::select(RID, last_TAU_neg_date2) %>%
  319. ungroup() %>%
  320. distinct()
  321. first_positive_date2_csf <- upenn_csf %>%
  322. group_by(RID) %>%
  323. filter(ptau_pos_csf == 1) %>%
  324. mutate(first_TAU_pos_date2 = min(CSF_Edate)) %>%
  325. dplyr::select(RID, first_TAU_pos_date2) %>%
  326. ungroup() %>%
  327. distinct()
  328. last_negative_date3_csf <- upenn_csf %>%
  329. group_by(RID) %>%
  330. filter(ad_pathology_pos_csf == 0) %>%
  331. mutate(last_AD_neg_date3 = max(CSF_Edate)) %>%
  332. dplyr::select(RID, last_AD_neg_date3) %>%
  333. ungroup() %>%
  334. distinct()
  335. first_positive_date3_csf <- upenn_csf %>%
  336. group_by(RID) %>%
  337. filter(ad_pathology_pos_csf == 1) %>%
  338. mutate(first_AD_pos_date3 = min(CSF_Edate)) %>%
  339. dplyr::select(RID, first_AD_pos_date3) %>%
  340. ungroup() %>%
  341. distinct()
  342. ## Amyloid PET Table ####
  343. # Read in and prepare amyloid PET table
  344. ab_pet <- read.csv(file.path(base_dir, "All_Subjects_UCBERKELEY_AMY_6MM_23Nov2024.csv"), stringsAsFactors = FALSE)
  345. ab_pet$RID <- addZeros(ab_pet$RID)
  346. ab_pet <- ab_pet %>%
  347. dplyr::rename(PET_Edate = SCANDATE,
  348. amyloid_pos_pet_cross = AMYLOID_STATUS,
  349. amyloid_pos_pet_long = AMYLOID_STATUS_COMPOSITE_REF) %>%
  350. mutate(amyloid_pos_pet_centiloid = case_when(CENTILOIDS > 22 ~ 1,
  351. TRUE ~ 0)) %>%
  352. dplyr::select(RID, PET_Edate, IMAGE_RESOLUTION, TRACER, SUMMARY_SUVR, CENTILOIDS, amyloid_pos_pet_cross,
  353. amyloid_pos_pet_long, amyloid_pos_pet_centiloid)
  354. ab_pet$PET_Edate <- as.Date(ab_pet$PET_Edate, "%Y-%m-%d")
  355. # Identify the first/last negative/positive dates of amyloid PET
  356. last_negative_date_pet <- ab_pet %>%
  357. group_by(RID) %>%
  358. filter(amyloid_pos_pet_cross == 0) %>%
  359. mutate(last_AB_neg_date = max(PET_Edate)) %>%
  360. dplyr::select(RID, last_AB_neg_date) %>%
  361. ungroup() %>%
  362. distinct()
  363. first_positive_date_pet <- ab_pet %>%
  364. group_by(RID) %>%
  365. filter(amyloid_pos_pet_cross == 1) %>%
  366. mutate(first_AB_pos_date = min(PET_Edate)) %>%
  367. dplyr::select(RID, first_AB_pos_date) %>%
  368. ungroup() %>%
  369. distinct()
  370. last_negative_date2_pet <- ab_pet %>%
  371. group_by(RID) %>%
  372. filter(amyloid_pos_pet_long == 0) %>%
  373. mutate(last_AB_neg_date2 = max(PET_Edate)) %>%
  374. dplyr::select(RID, last_AB_neg_date2) %>%
  375. ungroup() %>%
  376. distinct()
  377. first_positive_date2_pet <- ab_pet %>%
  378. group_by(RID) %>%
  379. filter(amyloid_pos_pet_long == 1) %>%
  380. mutate(first_AB_pos_date2 = min(PET_Edate)) %>%
  381. dplyr::select(RID, first_AB_pos_date2) %>%
  382. ungroup() %>%
  383. distinct()
  384. last_negative_date3_pet <- ab_pet %>%
  385. group_by(RID) %>%
  386. filter(amyloid_pos_pet_centiloid == 0) %>%
  387. mutate(last_AB_neg_date3 = max(PET_Edate)) %>%
  388. dplyr::select(RID, last_AB_neg_date3) %>%
  389. ungroup() %>%
  390. distinct()
  391. first_positive_date3_pet <- ab_pet %>%
  392. group_by(RID) %>%
  393. filter(amyloid_pos_pet_centiloid == 1) %>%
  394. mutate(first_AB_pos_date3 = min(PET_Edate)) %>%
  395. dplyr::select(RID, first_AB_pos_date3) %>%
  396. ungroup() %>%
  397. distinct()
  398. ## FreeSurfer MRI Table ####
  399. # Get amyloid negative RIDs that were consistently negative for every scan they had
  400. ab_negs <- ab_pet %>%
  401. group_by(RID) %>%
  402. filter(amyloid_pos_pet_cross == 0) %>%
  403. mutate(first_a_neg_date_pet = min(PET_Edate)) %>%
  404. dplyr::select(RID, first_a_neg_date_pet) %>%
  405. ungroup() %>%
  406. distinct()
  407. # # Read in and prepare cross-sectional FreeSurfer MRI table
  408. fs_cs <- read.csv(file.path(base_dir, "UCSF_FS_CS_5.1_2019.csv"), stringsAsFactors = FALSE)
  409. fs_cs <- fs_cs %>%
  410. filter(OVERALLQC == "Pass") %>%
  411. dplyr::select(-VISCODE) %>%
  412. dplyr::rename(ICV = ST10CV,
  413. VISCODE = VISCODE2) %>%
  414. group_by(RID, VISCODE) %>%
  415. mutate(n = n()) %>%
  416. filter(
  417. # Keep all single-scan visits
  418. n == 1 |
  419. # For multiple scans: keep Non-Accelerated if available
  420. (n > 1 & IMAGETYPE == "Non-Accelerated T1") |
  421. # If no Non-Accelerated, then keep Accelerated
  422. (n > 1 & !any(IMAGETYPE == "Non-Accelerated T1") & IMAGETYPE == "Accelerated T1")
  423. ) %>%
  424. ungroup() %>%
  425. dplyr::select(-n)
  426. fs_cs$MRIcs_Edate <- as.Date(fs_cs$EXAMDATE, "%Y-%m-%d")
  427. fs_cs$RID <- addZeros(fs_cs$RID)
  428. fs_cs <- fs_cs %>% dplyr::select(-EXAMDATE)
  429. fs_cs <- relocate(fs_cs, "MRIcs_Edate", .after = "VISCODE")
  430. # Get demographics and closest diagnosis information
  431. fs_cs <- merge(fs_cs, demog_merged, by = "RID", all.x = TRUE) %>%
  432. mutate(age = round(lubridate::time_length(difftime(MRIcs_Edate, PTDOB), "years"), digits = 1))
  433. fs_cs <- merge(fs_cs, dx %>%
  434. dplyr::select(-VISCODE) %>%
  435. filter(!is.na(DX_Edate)), by = "RID") %>%
  436. mutate(date_diff = abs(lubridate::time_length(difftime(MRIcs_Edate, DX_Edate), "years"))) %>%
  437. group_by(RID, VISCODE) %>%
  438. mutate(min_match_date = min(date_diff)) %>%
  439. ungroup() %>%
  440. filter(min_match_date == date_diff) %>%
  441. distinct()
  442. # For CU only, get earliest amyloid negative scan per RID
  443. fs_cs_temp <- fs_cs %>%
  444. filter(DXSUM == "CU")
  445. fs_cs_temp <- fs_cs_temp %>%
  446. left_join(ab_negs, by = "RID") %>%
  447. mutate(diff_time = abs(lubridate::time_length(difftime(MRIcs_Edate, first_a_neg_date_pet), "years"))) %>%
  448. group_by(RID) %>%
  449. filter(diff_time == min(diff_time)) %>%
  450. ungroup()
  451. fs_cs_temp <- fs_cs_temp %>%
  452. group_by(RID) %>%
  453. filter(row_number() == 1) %>%
  454. ungroup()
  455. # ICV adjustment for ROI volumes
  456. # Create list of volumes that need to be ICV-adjusted
  457. volumes_cs <- fs_cs_temp %>%
  458. dplyr::select(ICV | contains("CV") | contains("SV"))
  459. volumes_cs <- volumes_cs %>%
  460. dplyr::select(-ICV)
  461. # Perform ICV adjustment across all ROI volumes
  462. results_cs <- c()
  463. for (roi_num in 1:length(names(volumes_cs))){
  464. current_roi <- names(volumes_cs)[roi_num]
  465. results_cs <- append(results_cs, current_roi)
  466. }
  467. for (i in results_cs){
  468. fit <- lm(fs_cs_temp[[i]] ~ ICV, data = fs_cs_temp, na.action = na.exclude)
  469. fs_cs[paste0(i,'_ICV')] <- fs_cs[paste0(i)] - (fs_cs$ICV - mean(fs_cs_temp$ICV, na.rm = T)) * fit$coefficients[2]
  470. }
  471. fs_cs <- fs_cs %>%
  472. dplyr::select(-names(volumes_cs))
  473. # Cubic age adjustment for ROI volumes (captures normative aging)
  474. # Obtain list of variables that need to be age-adjusted
  475. # Add "_ICV" suffix to CV and SV in temp to match
  476. colnames(fs_cs_temp) <- ifelse(grepl("CV|SV", colnames(fs_cs_temp)),
  477. paste0(colnames(fs_cs_temp), "_ICV"),
  478. colnames(fs_cs_temp))
  479. fs_cs_temp <- fs_cs_temp %>% dplyr::rename(ICV = ICV_ICV)
  480. features_cs <- fs_cs_temp %>%
  481. dplyr::select(ICV | contains("CV") | contains("SV"))
  482. features_cs <- features_cs %>%
  483. dplyr::select(-ICV)
  484. # Perform age adjustment across all ROI volumes
  485. results_cs <- c()
  486. for (roi_num in 1:length(names(features_cs))){
  487. current_roi <- names(features_cs)[roi_num]
  488. results_cs <- append(results_cs, current_roi)
  489. }
  490. # Adjust for cubic age
  491. for (i in results_cs){
  492. fit <- lm(fs_cs_temp[[i]] ~ age + poly(age, 3, raw = TRUE)[,"3"], data = fs_cs_temp, na.action = na.exclude)
  493. fs_cs[paste0(i,'_age')] <- fs_cs[paste0(i)] - (fs_cs$age - mean(fs_cs_temp$age, na.rm = T)) * fit$coefficients[2]
  494. }
  495. # Compute global measure of hippocampal volume after ICV and age adjustments
  496. fs_cs <- fs_cs %>%
  497. dplyr::select(-names(features_cs)) %>%
  498. dplyr::rename(HVa_L = ST29SV_ICV_age,
  499. HVa_R = ST88SV_ICV_age) %>%
  500. mutate(HVa = HVa_L + HVa_R)
  501. # Compute meta-ROI cortical thickness
  502. fs_cs <- fs_cs %>%
  503. mutate(SA_total_L = ST24SA + ST32SA + ST40SA + ST26SA,
  504. SA_total_R = ST83SA + ST91SA + ST99SA + ST85SA,
  505. meta_ROI_L = (ST24TA*ST24SA + ST32TA*ST32SA + ST40TA*ST40SA + ST26TA*ST26SA) / SA_total_L,
  506. meta_ROI_R = (ST83TA*ST83SA + ST91TA*ST91SA + ST99TA*ST99SA + ST85TA*ST85SA) / SA_total_R,
  507. meta_ROI = (meta_ROI_L * SA_total_L + meta_ROI_R * SA_total_R) / (SA_total_L + SA_total_R))
  508. fs_cs <- fs_cs %>%
  509. dplyr::select(RID, VISCODE, STATUS, IMAGETYPE, MRIcs_Edate, HVa_L, HVa_R, HVa, meta_ROI_L, meta_ROI_R, meta_ROI)
  510. #______________________________________Merge Prepared ADNI Tables With Gene Expression Metadata______________________________________#####
  511. ## Read in and Preprocess Raw Gene Expression Data ####
  512. message("Now reading in and preparing raw gene expression data...")
  513. raw_exp <- read.csv(file.path(base_dir, "ADNI_Gene_Expression_Profile.csv"), stringsAsFactors = FALSE)
  514. # Isolate and minimally format gene expression-level data
  515. # Extract RIDs from PTID
  516. id <- as.data.frame(t(raw_exp[raw_exp$Phase == "SubjectID", ]))
  517. id$RID <- gsub("^.*_(\\d{4})$", "\\1", id[,1])
  518. # Extract expression-level data
  519. exp <- raw_exp
  520. colnames(exp) <- exp[8,]
  521. exp <- exp[9:nrow(exp),]
  522. # Update column names to RIDs
  523. colnames(exp)[4:ncol(exp)] <- id$RID[4:nrow(id)]
  524. # Combine gene symbol and probe set ID
  525. exp$GeneSet <- paste(exp$Symbol, exp$ProbeSet, sep = " / ")
  526. # Restrict to the expression data
  527. row.names(exp) <- NULL
  528. exp <- exp[, names(exp) != ""]
  529. exp <- exp %>%
  530. tibble::column_to_rownames("GeneSet") %>%
  531. dplyr::select(-c(ProbeSet, LocusLink, Symbol, ))
  532. # Ensure expression data is numeric
  533. exp <- exp %>%
  534. mutate(dplyr::across(everything(), ~ as.numeric(as.character(.x))))
  535. # Sort by RID
  536. exp <- exp[, order(names(exp))]
  537. ### Update Gene Symbols Using Affymetrix & MSigDB Annotation Files ####
  538. # Ensures that we maximize overlap with MSigDB's gene sets for functional enrichment analyses
  539. # There are gene symbols that were unintentionally converted to dates (e.g., 1-Sep = SEPTIN 1)
  540. message("Now updating the gene symbols in the gene expression matrix using Affymetrix and MSigDB annotation files...")
  541. # Read in and prepare Affymetrix's annotation file
  542. anno <- data.table::fread(file.path(base_dir, "AFFY-U219_GPL13667-15572.txt"), header = T, sep = "\t")
  543. anno <- anno %>%
  544. mutate(`Gene Symbol` = ifelse(`Gene Symbol` == "---" | `Gene Symbol` == "", NA, `Gene Symbol`),
  545. `Gene Symbol` = ifelse(`Gene Symbol` == "Schr2q13", "SEPTIN10", `Gene Symbol`),
  546. `Gene Symbol` = gsub("///", "||", `Gene Symbol`))
  547. # Read in and prepare a modified version of Affymetrix's annotation file tailored to MSigDB
  548. chip <- read.table(file.path(base_dir, "Affymetrix_Human_Genome_U219_MSigDB.v.7.4_custom.chip"), sep = "\t",
  549. comment.char = "", quote = "", stringsAsFactors = FALSE,
  550. fill = TRUE, header = T)
  551. chip_collapsed <- chip %>%
  552. group_by(Probe.Set.ID) %>%
  553. summarise(Gene.Symbol.New = paste(unique(Gene.Symbol), collapse = " || "), .groups = "drop")
  554. # Further refine the reformatted gene expression matrix
  555. exp$GeneSet <- row.names(exp)
  556. exp <- exp %>%
  557. mutate(Symbol = sub(" /.*", "", GeneSet),
  558. ProbeSet = sub(".*/ ", "", GeneSet)) %>%
  559. relocate(Symbol, .after = GeneSet) %>%
  560. relocate(ProbeSet, .after = Symbol)
  561. # Update gene symbols with the following priority as needed:
  562. # (1): MSigDB version, (2) Affymetrix version, and (3) ADNI version
  563. exp_final <- left_join(exp, anno %>%
  564. dplyr::select(c(ID, `Gene Symbol`)) %>%
  565. rename(ProbeSet = ID,
  566. Affy_Symbol = `Gene Symbol`), by = "ProbeSet")
  567. exp_final <- left_join(exp_final, chip_collapsed %>%
  568. dplyr::select(c(Probe.Set.ID, Gene.Symbol.New)) %>%
  569. rename(ProbeSet = Probe.Set.ID,
  570. MSigDB_Symbol = Gene.Symbol.New), by = "ProbeSet") %>%
  571. relocate(ProbeSet, .before = Symbol)
  572. # Apply the naming priority when updating the gene symbols
  573. exp_final <- exp_final %>%
  574. mutate(
  575. MSigDB_Symbol = case_when(
  576. is.na(MSigDB_Symbol) & !is.na(Affy_Symbol) ~ Affy_Symbol,
  577. is.na(MSigDB_Symbol) & is.na(Affy_Symbol) ~ Symbol,
  578. TRUE ~ MSigDB_Symbol
  579. ),
  580. # Remove all asterisks
  581. MSigDB_Symbol = gsub("\\*", "", MSigDB_Symbol),
  582. # Manually correct symbol for SEPTIN which was still in date format (e.g., 1-SEP)
  583. # Symbols for other months (Dec & Mar) were fixed using annotation files
  584. MSigDB_Symbol = ifelse(grepl("^[0-9]+-Sep$", MSigDB_Symbol, ignore.case=T),
  585. paste0("SEPTIN", sub("-Sep$", "", MSigDB_Symbol, ignore.case=T)),
  586. MSigDB_Symbol),
  587. Combined = paste(MSigDB_Symbol, ProbeSet, sep = " / ")
  588. ) %>%
  589. column_to_rownames('Combined') %>%
  590. dplyr::select(-c(GeneSet, ProbeSet, Symbol, Affy_Symbol, MSigDB_Symbol))
  591. message("Now saving the final gene expression matrix for analysis...")
  592. # Save the updated gene expression matrix for analysis
  593. write.csv(exp_final, file.path(base_dir, "gene_expression_final.csv"), row.names = T)
  594. # Clear up workspace
  595. rm(exp, exp_final, id, chip, chip_collapsed, anno)
  596. ## Create and Prepare Gene Expression Metadata Table ####
  597. # Extract gene expression metadata
  598. exp_pheno <- raw_exp[1:7,]
  599. exp_pheno <- exp_pheno %>%
  600. dplyr::select(where(~ !all(. == "")))
  601. exp_pheno <- rbind(colnames(exp_pheno), exp_pheno)
  602. exp_pheno <- as.data.frame(t(exp_pheno))
  603. colnames(exp_pheno) <- exp_pheno[1,]
  604. exp_pheno <- exp_pheno[-1, ]
  605. exp_pheno <- exp_pheno %>%
  606. dplyr::rename(VISCODE = Visit,
  607. PTID = SubjectID,
  608. GeneX_YearDrawn = YearofCollection,
  609. AffyPlate = `Affy Plate`)
  610. exp_pheno$RID <- gsub("^.*_(\\d{4})$", "\\1", exp_pheno$PTID)
  611. exp_pheno <- relocate(exp_pheno, "RID", .before = "Phase")
  612. # Fix one case of RIN written as "[5.1]" and ensure RIN is numeric
  613. exp_pheno$RIN[exp_pheno$RID == "0307"] <- "5.1"
  614. exp_pheno$RIN <- as.numeric(exp_pheno$RIN)
  615. # Sort by RID
  616. exp_pheno <- exp_pheno[order(exp_pheno$RID), ]
  617. rownames(exp_pheno) <- NULL
  618. exp_pheno$index <- seq.int(nrow(exp_pheno))
  619. ### Combine ADNI Tables with Gene Expression Metadata ####
  620. # Main demographics
  621. exp_pheno_merged <- left_join(demog_merged, exp_pheno, by = "RID")
  622. exp_pheno_merged <- exp_pheno_merged[!is.na(exp_pheno_merged$GeneX_YearDrawn),]
  623. # Update VISCODES using registry
  624. exp_pheno_merged$VISCODE <- ifelse(exp_pheno_merged$VISCODE == "v03", "bl", exp_pheno_merged$VISCODE)
  625. exp_pheno_merged$VISCODE <- ifelse(exp_pheno_merged$VISCODE == "v04", "m03", exp_pheno_merged$VISCODE)
  626. exp_pheno_merged$VISCODE <- ifelse(exp_pheno_merged$VISCODE == "v05", "m06", exp_pheno_merged$VISCODE)
  627. exp_pheno_merged <- left_join(exp_pheno_merged, registry, by = c("RID","VISCODE"))
  628. exp_pheno_merged$VISCODE <- ifelse(exp_pheno_merged$VISCODE == "v06", exp_pheno_merged$VISCODE2, exp_pheno_merged$VISCODE)
  629. exp_pheno_merged$VISCODE <- ifelse(exp_pheno_merged$VISCODE == "v11", exp_pheno_merged$VISCODE2, exp_pheno_merged$VISCODE)
  630. # One case of v02, so use bl instead since it is only 11 days from v02
  631. exp_pheno_merged$VISCODE <- ifelse(exp_pheno_merged$VISCODE == "v02", "bl", exp_pheno_merged$VISCODE)
  632. exp_pheno_merged <- exp_pheno_merged %>%
  633. dplyr::select(-c(VISCODE2))
  634. # Approximate EXAMDATE for gene expression profiling from ADNIMERGE to calculate age
  635. # This is necessary because the gene expression metadata only came with the year of collection
  636. adni_merge2 <- adni_merge %>% dplyr::select(RID, VISCODE, EXAMDATE)
  637. adni_merge2$RID <- as.character(adni_merge2$RID)
  638. adni_merge2$RID <- addZeros(adni_merge2$RID)
  639. exp_pheno_merged <- left_join(exp_pheno_merged, adni_merge2, by = c("RID","VISCODE"))
  640. exp_pheno_merged <- exp_pheno_merged %>%
  641. dplyr::rename(GeneX_Edate = EXAMDATE)
  642. exp_pheno_merged$GeneX_Edate <- as.Date(exp_pheno_merged$GeneX_Edate,"%Y-%m-%d")
  643. exp_pheno_merged$AGE <- round(time_length(difftime(exp_pheno_merged$GeneX_Edate,
  644. exp_pheno_merged$PTDOB),
  645. "years"), digits = 1)
  646. exp_pheno_merged <- relocate(exp_pheno_merged, "AGE", .before = "AGE_bl")
  647. exp_pheno_merged <- relocate(exp_pheno_merged, "index", .before = "RID")
  648. # APOE genotypes
  649. exp_pheno_merged <- left_join(exp_pheno_merged, apoeres_df, by = "RID")
  650. # CSF AB/TAU estimated age of positivity onset
  651. exp_pheno_merged <- left_join(exp_pheno_merged, csf_age, by = "RID")
  652. exp_pheno_merged <- exp_pheno_merged %>%
  653. mutate(AB_time = AGE - AB_AGE,
  654. TAU_time = AGE - TAU_AGE)
  655. # Diagnosis
  656. exp_pheno_merged1 <- merge(exp_pheno_merged, dx %>%
  657. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  658. exp_pheno_merged2 <- gene_within1yr(exp_pheno_merged1, DX_Edate, df2_VISCODE = TRUE)
  659. # Get back rest of the data
  660. unmatched <- anti_join(exp_pheno_merged, exp_pheno_merged2, by = c("RID", "GeneX_Edate"))
  661. exp_pheno_merged3 <- bind_rows(exp_pheno_merged2, unmatched)
  662. exp_pheno_merged3 <- exp_pheno_merged3 %>%
  663. distinct(index, .keep_all = TRUE)
  664. # Compute time2event and MCI progressor status
  665. mci <- left_join(dx, exp_pheno_merged3 %>%
  666. dplyr::select(RID, GeneX_Edate), by = "RID")
  667. mci <- mci %>%
  668. group_by(RID) %>%
  669. mutate(date_diff = abs(lubridate::time_length(difftime(GeneX_Edate, DX_Edate), "years"))) %>%
  670. group_by(RID, GeneX_Edate) %>%
  671. mutate(min_match_date = min(date_diff)) %>%
  672. ungroup()
  673. first <- mci %>%
  674. group_by(RID) %>%
  675. mutate(DX_bl = ifelse(min_match_date == date_diff, DXSUM, NA),
  676. bl_DX_date = min(DX_Edate[min_match_date == date_diff], na.rm = T),
  677. bl_DX_date = as.Date(bl_DX_date, "%Y-%m-%d")) %>%
  678. filter(!is.na(DX_bl)) %>%
  679. ungroup() %>%
  680. dplyr::select(RID, DX_bl, bl_DX_date)
  681. mci <- left_join(mci, first, by = "RID")
  682. mci <- mci %>%
  683. arrange(RID, DX_Edate) %>%
  684. group_by(RID) %>%
  685. mutate(last_DX_date = max(DX_Edate, na.rm = T),
  686. last_DX_date = as.Date(last_DX_date, "%Y-%m-%d"),
  687. window = DX_Edate >= GeneX_Edate & DX_Edate <= last_DX_date,
  688. window = ifelse(window == FALSE & bl_DX_date <= GeneX_Edate & min_match_date == date_diff, TRUE, window)) %>%
  689. filter(window == TRUE) %>%
  690. mutate(first_AD_date = min(DX_Edate[DXSUM == "Dementia" & window], na.rm = T),
  691. first_AD_date = as.Date(first_AD_date, "%Y-%m-%d"),
  692. last_MCI_date = max(DX_Edate[DXSUM == "MCI" & window], na.rm = T),
  693. last_MCI_date = as.Date(last_MCI_date, "%Y-%m-%d"),
  694. swap = DX_bl == "MCI" & window & first_AD_date <= last_MCI_date,
  695. AD_flag = any(DXSUM == "Dementia" & window),
  696. Progressor = case_when(
  697. DX_bl == "MCI" & AD_flag & swap ~ NA,
  698. DX_bl == "MCI" & AD_flag ~ 1 ,
  699. DX_bl == "MCI" & !AD_flag ~ 0,
  700. TRUE ~ NA
  701. )) %>%
  702. ungroup()
  703. mci[sapply(mci, is.infinite)] <- NA
  704. mci <- mci %>%
  705. group_by(RID) %>%
  706. mutate(time2event = ifelse(Progressor == 1,
  707. lubridate::time_length(difftime(first_AD_date, GeneX_Edate), "years"),
  708. lubridate::time_length(difftime(last_DX_date, GeneX_Edate), "years"))) %>%
  709. filter(time2event >= 0)
  710. exp_pheno_merged3 <- left_join(exp_pheno_merged3, mci %>%
  711. dplyr::select(RID, bl_DX_date, last_DX_date, first_AD_date, last_MCI_date, Progressor, time2event), by = "RID")
  712. exp_pheno_merged3 <- exp_pheno_merged3 %>%
  713. distinct(index, .keep_all = TRUE)
  714. exp_pheno_merged3 <- exp_pheno_merged3 %>%
  715. mutate(DXSUM2 = case_when(
  716. (DXSUM == "CU") ~ "CU",
  717. (DXSUM == "Dementia") ~ "Dementia",
  718. (Progressor == 0) ~ "Non-Progressor",
  719. (Progressor == 1) ~ "Progressor MCI",
  720. TRUE ~ DXSUM
  721. ))
  722. # Cognitive performance
  723. cdr <- left_join(cdr, exp_pheno_merged3 %>%
  724. dplyr::select(RID, GeneX_Edate), by = "RID")
  725. cdr <-cdr %>%
  726. arrange(RID, CDR_Edate) %>%
  727. group_by(RID) %>%
  728. mutate(CDR_time=lubridate::time_length(difftime(CDR_Edate, GeneX_Edate), "years")) %>%
  729. mutate(n = length(unique(CDR_Edate))) %>%
  730. ungroup()
  731. # Compute individualized slopes of CDRSB
  732. model <- lmer(CDRSB ~ CDR_time + (CDR_time | RID), subset = n > 1, data = cdr, na.action = na.omit)
  733. slopes <- ranef(model)$RID[2]
  734. colnames(slopes)[1] <- "CDRSB_slope"
  735. slopes$RID <- row.names(slopes)
  736. cdr <- left_join(cdr, slopes, by = "RID")
  737. cdr <- cdr %>%
  738. dplyr::select(-c(n, GeneX_Edate, CDR_time, VISDATE))
  739. cdr_slopes <- cdr %>%
  740. dplyr::select(RID, CDRSB_slope) %>%
  741. distinct()
  742. exp_pheno_merged4 <- merge(exp_pheno_merged3, cdr %>%
  743. dplyr::select(-CDRSB_slope) %>%
  744. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  745. exp_pheno_merged5 <- gene_within1yr(exp_pheno_merged4, CDR_Edate, df2_VISCODE = TRUE)
  746. # Get back rest of the data
  747. unmatched <- anti_join(exp_pheno_merged3, exp_pheno_merged5, by = c("RID", "GeneX_Edate"))
  748. exp_pheno_merged6 <- bind_rows(exp_pheno_merged5, unmatched)
  749. exp_pheno_merged6 <- exp_pheno_merged6[order(exp_pheno_merged6$RID), ]
  750. rownames(exp_pheno_merged6) <- NULL
  751. exp_pheno_merged6 <- left_join(exp_pheno_merged6, cdr_slopes, by = "RID")
  752. exp_pheno_merged6 <- left_join(exp_pheno_merged6, adas, by = c("RID", "VISCODE"))
  753. mmse <- left_join(mmse, exp_pheno_merged6 %>%
  754. dplyr::select(RID, GeneX_Edate), by = "RID")
  755. mmse <- mmse %>%
  756. arrange(RID, MMSE_Edate) %>%
  757. group_by(RID) %>%
  758. mutate(MMSE_time=lubridate::time_length(difftime(MMSE_Edate, GeneX_Edate), "years")) %>%
  759. mutate(n = length(unique(MMSE_Edate))) %>%
  760. ungroup()
  761. # Compute individualized slopes of MMSE
  762. model <- lmer(MMSE ~ MMSE_time + (MMSE_time | RID), subset = n > 1, data = mmse, na.action = na.omit)
  763. slopes <- ranef(model)$RID[2]
  764. colnames(slopes)[1] <- "MMSE_slope"
  765. slopes$RID <- row.names(slopes)
  766. mmse <- left_join(mmse, slopes, by = "RID")
  767. mmse <- mmse %>%
  768. dplyr::select(-c(n, GeneX_Edate, MMSE_time))
  769. mmse_slopes <- mmse %>%
  770. dplyr::select(RID, MMSE_slope) %>%
  771. distinct()
  772. exp_pheno_merged7 <- merge(exp_pheno_merged6, mmse %>%
  773. dplyr::select(-MMSE_slope), by = "RID", all.x = TRUE)
  774. exp_pheno_merged8 <- gene_within1yr(exp_pheno_merged7, MMSE_Edate, df2_VISCODE = TRUE)
  775. # Get back rest of the data
  776. unmatched <- anti_join(exp_pheno_merged6, exp_pheno_merged8, by = c("RID", "GeneX_Edate"))
  777. exp_pheno_merged9 <- bind_rows(exp_pheno_merged8, unmatched)
  778. exp_pheno_merged9 <- exp_pheno_merged9[order(exp_pheno_merged9$RID), ]
  779. rownames(exp_pheno_merged9) <- NULL
  780. exp_pheno_merged9 <- left_join(exp_pheno_merged9, mmse_slopes, by = "RID")
  781. # Plasma NfL
  782. nfl_data <- left_join(nfl_data, exp_pheno_merged9 %>%
  783. dplyr::select(RID, GeneX_Edate), by = "RID")
  784. nfl_data <- nfl_data %>%
  785. arrange(RID, NfL_Edate) %>%
  786. group_by(RID) %>%
  787. mutate(NfL_time=lubridate::time_length(difftime(NfL_Edate, GeneX_Edate), "years")) %>%
  788. mutate(n = length(unique(NfL_Edate))) %>%
  789. ungroup()
  790. # Compute individualized slopes of NfL
  791. model <- lmer(NfL_B ~ NfL_time + (NfL_time | RID), subset = n > 1, data = nfl_data, na.action = na.omit)
  792. slopes <- ranef(model)$RID[2]
  793. colnames(slopes)[1] <- "NfL_B_slope"
  794. slopes$RID <- row.names(slopes)
  795. nfl_data <- left_join(nfl_data, slopes, by = "RID")
  796. nfl_data <- nfl_data %>%
  797. dplyr::select(-c(n, GeneX_Edate, NfL_time))
  798. nfl_slopes <- nfl_data %>%
  799. dplyr::select(RID, NfL_B_slope) %>%
  800. distinct()
  801. exp_pheno_merged10 <- merge(exp_pheno_merged9, nfl_data %>%
  802. dplyr::select(-NfL_B_slope), by = "RID", all.x = TRUE)
  803. exp_pheno_merged11 <- gene_within1yr(exp_pheno_merged10, NfL_Edate, df2_VISCODE = TRUE)
  804. # Get back rest of the data
  805. unmatched <- anti_join(exp_pheno_merged9, exp_pheno_merged11, by = c("RID", "GeneX_Edate"))
  806. exp_pheno_merged12 <- bind_rows(exp_pheno_merged11, unmatched)
  807. exp_pheno_merged12 <- exp_pheno_merged12[order(exp_pheno_merged12$RID), ]
  808. rownames(exp_pheno_merged12) <- NULL
  809. exp_pheno_merged12 <- left_join(exp_pheno_merged12, nfl_slopes, by = "RID")
  810. # Amyloid PET
  811. ab_pet <- left_join(ab_pet, exp_pheno_merged12 %>%
  812. dplyr::select(RID, GeneX_Edate), by = "RID")
  813. ab_pet <- ab_pet %>%
  814. arrange(RID, PET_Edate) %>%
  815. group_by(RID) %>%
  816. mutate(PET_time=lubridate::time_length(difftime(PET_Edate, GeneX_Edate), "years")) %>%
  817. mutate(n = length(unique(PET_Edate))) %>%
  818. ungroup()
  819. # Compute individualized slopes of amyloid centiloids
  820. model <- lmer(CENTILOIDS ~ PET_time + (PET_time | RID), subset = n > 1, data = ab_pet, na.action = na.omit)
  821. slopes <- ranef(model)$RID[2]
  822. colnames(slopes)[1] <- "CENTILOIDS_slope"
  823. slopes$RID <- row.names(slopes)
  824. ab_pet <- left_join(ab_pet, slopes, by = "RID")
  825. ab_pet <- ab_pet %>%
  826. dplyr::select(-c(n, GeneX_Edate, PET_time))
  827. ab_slopes <- ab_pet %>%
  828. dplyr::select(RID, CENTILOIDS_slope) %>%
  829. distinct()
  830. exp_pheno_merged13 <- merge(exp_pheno_merged12, ab_pet %>%
  831. dplyr::select(-CENTILOIDS_slope), by = "RID", all.x = TRUE)
  832. exp_pheno_merged14 <- gene_within1yr(exp_pheno_merged13, PET_Edate, df2_VISCODE = FALSE)
  833. # Get back rest of the data
  834. unmatched <- anti_join(exp_pheno_merged12, exp_pheno_merged14, by = c("RID", "GeneX_Edate"))
  835. exp_pheno_merged15 <- bind_rows(exp_pheno_merged14, unmatched)
  836. exp_pheno_merged15 <- exp_pheno_merged15[order(exp_pheno_merged15$RID), ]
  837. rownames(exp_pheno_merged15) <- NULL
  838. exp_pheno_merged15 <- left_join(exp_pheno_merged15, ab_slopes, by = "RID")
  839. exp_pheno_merged15 <- merge(exp_pheno_merged15, last_negative_date_pet, by = "RID", all.x = TRUE)
  840. exp_pheno_merged15 <- merge(exp_pheno_merged15, first_positive_date_pet, by = "RID", all.x = TRUE)
  841. exp_pheno_merged15 <- merge(exp_pheno_merged15, last_negative_date2_pet, by = "RID", all.x = TRUE)
  842. exp_pheno_merged15 <- merge(exp_pheno_merged15, first_positive_date2_pet, by = "RID", all.x = TRUE)
  843. exp_pheno_merged15 <- merge(exp_pheno_merged15, last_negative_date3_pet, by = "RID", all.x = TRUE)
  844. exp_pheno_merged15 <- merge(exp_pheno_merged15, first_positive_date3_pet, by = "RID", all.x = TRUE)
  845. exp_pheno_merged15 <- exp_pheno_merged15[order(exp_pheno_merged15$RID), ]
  846. rownames(exp_pheno_merged15) <- NULL
  847. # Ensure amyloid PET positivity follows expected logic
  848. # Prioritize using available data, then apply expected logic where appropriate
  849. # (e.g., can progress from negative to positive; once positive stays positive)
  850. exp_pheno_merged16 <- exp_pheno_merged15 %>%
  851. group_by(RID) %>%
  852. mutate(amyloid_pos_pet_cross = case_when(
  853. !is.na(last_AB_neg_date) & !is.na(first_AB_pos_date) & is.na(amyloid_pos_pet_cross) & first_AB_pos_date < last_AB_neg_date ~ amyloid_pos_pet_cross,
  854. !is.na(last_AB_neg_date) & is.na(amyloid_pos_pet_cross) & GeneX_Edate <= last_AB_neg_date ~ 0,
  855. !is.na(first_AB_pos_date) & is.na(amyloid_pos_pet_cross) & GeneX_Edate >= first_AB_pos_date ~ 1,
  856. TRUE ~ amyloid_pos_pet_cross)) %>%
  857. ungroup()
  858. exp_pheno_merged16 <- exp_pheno_merged16 %>%
  859. group_by(RID) %>%
  860. mutate(amyloid_pos_pet_long = case_when(
  861. !is.na(last_AB_neg_date2) & !is.na(first_AB_pos_date2) & is.na(amyloid_pos_pet_long) & first_AB_pos_date2 < last_AB_neg_date2 ~ amyloid_pos_pet_long,
  862. !is.na(last_AB_neg_date2) & is.na(amyloid_pos_pet_long) & GeneX_Edate <= last_AB_neg_date2 ~ 0,
  863. !is.na(first_AB_pos_date2) & is.na(amyloid_pos_pet_long) & GeneX_Edate >= first_AB_pos_date2 ~ 1,
  864. TRUE ~ amyloid_pos_pet_long)) %>%
  865. ungroup()
  866. exp_pheno_merged16 <- exp_pheno_merged16 %>%
  867. group_by(RID) %>%
  868. mutate(amyloid_pos_pet_centiloid = case_when(
  869. !is.na(last_AB_neg_date3) & !is.na(first_AB_pos_date3) & is.na(amyloid_pos_pet_centiloid) & first_AB_pos_date3 < last_AB_neg_date3 ~ amyloid_pos_pet_centiloid,
  870. !is.na(last_AB_neg_date3) & is.na(amyloid_pos_pet_centiloid) & GeneX_Edate <= last_AB_neg_date3 ~ 0,
  871. !is.na(first_AB_pos_date3) & is.na(amyloid_pos_pet_centiloid) & GeneX_Edate >= first_AB_pos_date3 ~ 1,
  872. TRUE ~ amyloid_pos_pet_centiloid)) %>%
  873. ungroup() %>%
  874. dplyr::select(-c("last_AB_neg_date", "first_AB_pos_date", "last_AB_neg_date2", "first_AB_pos_date2", "last_AB_neg_date3", "first_AB_pos_date3"))
  875. # CSF biomarkers
  876. upenn_csf <- left_join(upenn_csf, exp_pheno_merged16 %>%
  877. dplyr::select(RID, GeneX_Edate), by = "RID")
  878. upenn_csf <- upenn_csf %>%
  879. arrange(RID, CSF_Edate) %>%
  880. group_by(RID) %>%
  881. mutate(CSF_time=lubridate::time_length(difftime(CSF_Edate, GeneX_Edate), "years")) %>%
  882. mutate(n = length(unique(CSF_Edate))) %>%
  883. ungroup()
  884. # Compute individualized slopes of CSF p-tau181
  885. model <- lmer(pTAU181_csf ~ CSF_time + (CSF_time | RID), subset = n > 1, data = upenn_csf, na.action = na.omit)
  886. slopes <- ranef(model)$RID[2]
  887. colnames(slopes)[1] <- "pTAU181_csf_slope"
  888. slopes$RID <- row.names(slopes)
  889. upenn_csf <- left_join(upenn_csf, slopes, by = "RID")
  890. upenn_csf <- upenn_csf %>%
  891. dplyr::select(-c(n, GeneX_Edate, CSF_time))
  892. csf_slopes <- upenn_csf %>%
  893. dplyr::select(RID, pTAU181_csf_slope) %>%
  894. distinct()
  895. exp_pheno_merged17 <- merge(exp_pheno_merged16, upenn_csf %>%
  896. dplyr::select(-pTAU181_csf_slope) %>%
  897. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  898. exp_pheno_merged18 <- gene_within1yr(exp_pheno_merged17, CSF_Edate, df2_VISCODE = TRUE)
  899. # Get back rest of the data
  900. unmatched <- anti_join(exp_pheno_merged16, exp_pheno_merged18, by = c("RID", "GeneX_Edate"))
  901. exp_pheno_merged19 <- bind_rows(exp_pheno_merged18, unmatched)
  902. exp_pheno_merged19 <- exp_pheno_merged19[order(exp_pheno_merged19$RID), ]
  903. rownames(exp_pheno_merged19) <- NULL
  904. exp_pheno_merged19 <- left_join(exp_pheno_merged19, csf_slopes, by = "RID")
  905. exp_pheno_merged19 <- merge(exp_pheno_merged19, last_negative_date_csf, by = "RID", all.x = TRUE)
  906. exp_pheno_merged19 <- merge(exp_pheno_merged19, first_positive_date_csf, by = "RID", all.x = TRUE)
  907. exp_pheno_merged19 <- merge(exp_pheno_merged19, last_negative_date2_csf, by = "RID", all.x = TRUE)
  908. exp_pheno_merged19 <- merge(exp_pheno_merged19, first_positive_date2_csf, by = "RID", all.x = TRUE)
  909. exp_pheno_merged19 <- merge(exp_pheno_merged19, last_negative_date3_csf, by = "RID", all.x = TRUE)
  910. exp_pheno_merged19 <- merge(exp_pheno_merged19, first_positive_date3_csf, by = "RID", all.x = TRUE)
  911. exp_pheno_merged19 <- exp_pheno_merged19[order(exp_pheno_merged19$RID), ]
  912. rownames(exp_pheno_merged19) <- NULL
  913. # Ensure CSF biomarker positivity follows expected logic
  914. exp_pheno_merged20 <- exp_pheno_merged19 %>%
  915. group_by(RID) %>%
  916. mutate(amyloid_pos_csf = case_when(
  917. !is.na(last_AB_neg_date) & !is.na(first_AB_pos_date) & is.na(amyloid_pos_csf) & first_AB_pos_date < last_AB_neg_date ~ amyloid_pos_csf,
  918. !is.na(last_AB_neg_date) & is.na(amyloid_pos_csf) & GeneX_Edate <= last_AB_neg_date ~ 0,
  919. !is.na(first_AB_pos_date) & is.na(amyloid_pos_csf) & GeneX_Edate >= first_AB_pos_date ~ 1,
  920. TRUE ~ amyloid_pos_csf)) %>%
  921. ungroup()
  922. exp_pheno_merged20 <- exp_pheno_merged20 %>%
  923. group_by(RID) %>%
  924. mutate(ptau_pos_csf = case_when(
  925. !is.na(last_TAU_neg_date2) & !is.na(first_TAU_pos_date2) & is.na(ptau_pos_csf) & first_TAU_pos_date2 < last_TAU_neg_date2 ~ ptau_pos_csf,
  926. !is.na(last_TAU_neg_date2) & is.na(ptau_pos_csf) & GeneX_Edate <= last_TAU_neg_date2 ~ 0,
  927. !is.na(first_TAU_pos_date2) & is.na(ptau_pos_csf) & GeneX_Edate >= first_TAU_pos_date2 ~ 1,
  928. TRUE ~ ptau_pos_csf)) %>%
  929. ungroup()
  930. exp_pheno_merged20 <- exp_pheno_merged20 %>%
  931. group_by(RID) %>%
  932. mutate(ad_pathology_pos_csf = case_when(
  933. !is.na(last_AD_neg_date3) & !is.na(first_AD_pos_date3) & is.na(ad_pathology_pos_csf) & first_AD_pos_date3 < last_AD_neg_date3 ~ ad_pathology_pos_csf,
  934. !is.na(last_AD_neg_date3) & is.na(ad_pathology_pos_csf) & GeneX_Edate <= last_AD_neg_date3 ~ 0,
  935. !is.na(first_AD_pos_date3) & is.na(ad_pathology_pos_csf) & GeneX_Edate >= first_AD_pos_date3 ~ 1,
  936. TRUE ~ ad_pathology_pos_csf)) %>%
  937. ungroup() %>%
  938. dplyr::select(-c("last_AB_neg_date", "first_AB_pos_date", "last_TAU_neg_date2", "first_TAU_pos_date2", "last_AD_neg_date3", "first_AD_pos_date3", "index"))
  939. # Hippocampal volume and meta-ROI cortical thickness
  940. fs_cs <- left_join(fs_cs, exp_pheno_merged20 %>%
  941. dplyr::select(RID, GeneX_Edate), by = "RID")
  942. fs_cs <- fs_cs %>%
  943. arrange(RID, MRIcs_Edate) %>%
  944. group_by(RID) %>%
  945. mutate(MRI_time=lubridate::time_length(difftime(MRIcs_Edate, GeneX_Edate), "years")) %>%
  946. mutate(n = length(unique(MRIcs_Edate))) %>%
  947. ungroup()
  948. # Compute individualized slopes of hippocampal volume
  949. model <- lmer(HVa ~ MRI_time + (MRI_time | RID), subset = n > 1, data = fs_cs, na.action = na.omit)
  950. slopes <- ranef(model)$RID[2]
  951. colnames(slopes)[1] <- "HVa_slope"
  952. slopes$RID <- row.names(slopes)
  953. fs_cs <- left_join(fs_cs, slopes, by = "RID")
  954. # Compute individualized slopes of meta-ROI cortical thickness
  955. model <- lmer(meta_ROI ~ MRI_time + (MRI_time | RID), subset = n > 1, data = fs_cs, na.action = na.omit)
  956. slopes <- ranef(model)$RID[2]
  957. colnames(slopes)[1] <- "meta_ROI_slope"
  958. slopes$RID <- row.names(slopes)
  959. fs_cs <- left_join(fs_cs, slopes, by = "RID")
  960. fs_slopes <- fs_cs %>%
  961. dplyr::select(RID, HVa_slope, meta_ROI_slope) %>%
  962. distinct()
  963. fs_cs <- fs_cs %>%
  964. dplyr::select(-c(n, GeneX_Edate, MRI_time, HVa_slope, meta_ROI_slope))
  965. exp_pheno_merged21 <- merge(exp_pheno_merged20, fs_cs %>%
  966. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  967. exp_pheno_merged22 <- gene_within1yr(exp_pheno_merged21, MRIcs_Edate, df2_VISCODE = TRUE)
  968. # Get back rest of the data
  969. unmatched <- anti_join(exp_pheno_merged20, exp_pheno_merged22, by = c("RID", "GeneX_Edate"))
  970. exp_pheno_merged23 <- bind_rows(exp_pheno_merged22, unmatched)
  971. exp_pheno_merged23 <- exp_pheno_merged23[order(exp_pheno_merged23$RID), ]
  972. rownames(exp_pheno_merged23) <- NULL
  973. exp_pheno_merged23 <- left_join(exp_pheno_merged23, fs_slopes, by = "RID")
  974. message("Now saving final gene expression phenotype data for analysis...")
  975. # Save final gene expression phenotype data for analysis
  976. write.csv(exp_pheno_merged23, file.path(base_dir, "GeneX_pheno_final.csv"), row.names = F)
  977. #______________________________________Merge Prepared ADNI Tables With DNA Methylation Metadata______________________________________#####
  978. ## Create and Prepare DNA Methylation Metadata Table ####
  979. message("Now reading in and preparing DNA methylation metadata table...")
  980. dnam_pheno <- read.csv(file.path(base_dir, "Sample_Sheet_rv1.csv"), stringsAsFactor = FALSE)
  981. # Exclude the 14 samples not in metadata (failed QC by ADNI Genetics Core)
  982. dnam_pheno <- dnam_pheno[!is.na(dnam_pheno$RID), ]
  983. dnam_pheno <- dnam_pheno %>%
  984. dplyr::rename(DNAm_Edate = Edate,
  985. DNAm_DateDrawn = DateDrawn) %>%
  986. dplyr::select(-Basename)
  987. dnam_pheno$RID <- addZeros(dnam_pheno$RID)
  988. dnam_pheno$DNAm_Edate <- as.Date(dnam_pheno$DNAm_Edate, "%m/%d/%Y")
  989. dnam_pheno$DNAm_DateDrawn <- as.Date(dnam_pheno$DNAm_DateDrawn, "%m/%d/%Y")
  990. ### Combine ADNI Tables with DNA Methylation Metadata ####
  991. # Main demographics
  992. dnam_pheno_merged <- left_join(demog_merged, dnam_pheno, by = "RID")
  993. dnam_pheno_merged <- dnam_pheno_merged[!is.na(dnam_pheno_merged$barcodes),]
  994. dnam_pheno_merged$AGE <- round(time_length(difftime(dnam_pheno_merged$DNAm_DateDrawn,
  995. dnam_pheno_merged$PTDOB),
  996. "years"), digits = 1)
  997. dnam_pheno_merged <- relocate(dnam_pheno_merged, "AGE", .before = "AGE_bl")
  998. dnam_pheno_merged <- relocate(dnam_pheno_merged, "barcodes", .after = "RID")
  999. # APOE genotypes
  1000. dnam_pheno_merged <- left_join(dnam_pheno_merged, apoeres_df, by = "RID")
  1001. # CSF AB/TAU estimated age of positivity onset
  1002. dnam_pheno_merged <- left_join(dnam_pheno_merged, csf_age, by = "RID")
  1003. dnam_pheno_merged <- dnam_pheno_merged %>%
  1004. mutate(AB_time = AGE - AB_AGE,
  1005. TAU_time = AGE - TAU_AGE)
  1006. # Diagnosis
  1007. dnam_pheno_merged1 <- merge(dnam_pheno_merged, dx, by = "RID", all.x = TRUE)
  1008. dnam_pheno_merged2 <- dnam_within1yr(dnam_pheno_merged1, DX_Edate, df2_VISCODE = FALSE)
  1009. # Get back rest of the data
  1010. unmatched <- anti_join(dnam_pheno_merged, dnam_pheno_merged2, by = c("RID", "DNAm_Edate"))
  1011. dnam_pheno_merged3 <- bind_rows(dnam_pheno_merged2, unmatched)
  1012. dnam_pheno_merged3 <- dnam_pheno_merged3 %>%
  1013. distinct(barcodes, .keep_all = TRUE)
  1014. # Compute time2event and MCI progressor status
  1015. mci <- left_join(dx, dnam_pheno_merged3 %>%
  1016. dplyr::select(RID, DNAm_Edate), by = "RID")
  1017. mci <- mci %>%
  1018. group_by(RID, DNAm_Edate) %>%
  1019. mutate(date_diff = abs(lubridate::time_length(difftime(DNAm_Edate, DX_Edate), "years")),
  1020. min_match_date = min(date_diff)) %>%
  1021. ungroup()
  1022. first <- mci %>%
  1023. arrange(RID, DNAm_Edate) %>%
  1024. group_by(RID) %>%
  1025. mutate(DX_bl = DXSUM[min_match_date == date_diff][1],
  1026. bl_DX_date = DX_Edate[min_match_date == date_diff][1],
  1027. bl_DX_date = as.Date(bl_DX_date, "%Y-%m-%d")) %>%
  1028. filter(!is.na(DX_bl)) %>%
  1029. ungroup() %>%
  1030. distinct(RID, .keep_all = T) %>%
  1031. dplyr::select(RID, DX_bl, bl_DX_date)
  1032. mci <- left_join(mci, first, by = "RID")
  1033. mci <- mci %>%
  1034. arrange(RID, DNAm_Edate, DX_Edate) %>%
  1035. group_by(RID) %>%
  1036. mutate(last_DX_date = max(DX_Edate, na.rm = T),
  1037. last_DX_date = as.Date(last_DX_date, "%Y-%m-%d"),
  1038. window = DX_Edate >= DNAm_Edate[[1]] & DX_Edate <= last_DX_date) %>%
  1039. filter(window == TRUE) %>%
  1040. mutate(first_AD_date = min(DX_Edate[DXSUM == "Dementia" & window], na.rm = T),
  1041. first_AD_date = as.Date(first_AD_date, "%Y-%m-%d"),
  1042. last_MCI_date = max(DX_Edate[DXSUM == "MCI" & window], na.rm = T),
  1043. last_MCI_date = as.Date(last_MCI_date, "%Y-%m-%d"),
  1044. swap = DX_bl == "MCI" & window & first_AD_date <= last_MCI_date,
  1045. AD_flag = any(DXSUM == "Dementia" & window),
  1046. Progressor = case_when(
  1047. DX_bl == "MCI" & AD_flag & swap ~ NA,
  1048. DX_bl == "MCI" & AD_flag ~ 1 ,
  1049. DX_bl == "MCI" & !AD_flag ~ 0,
  1050. TRUE ~ NA
  1051. )) %>%
  1052. ungroup()
  1053. mci[sapply(mci, is.infinite)] <- NA
  1054. mci <- mci %>%
  1055. group_by(RID) %>%
  1056. mutate(time2event = ifelse(Progressor == 1,
  1057. lubridate::time_length(difftime(first_AD_date, DNAm_Edate), "years"),
  1058. lubridate::time_length(difftime(last_DX_date, DNAm_Edate), "years"))) %>%
  1059. filter(time2event >= 0)
  1060. dnam_pheno_merged3 <- left_join(dnam_pheno_merged3, mci %>%
  1061. dplyr::select(RID, DX_bl, bl_DX_date, last_DX_date, first_AD_date, last_MCI_date, Progressor, time2event), by = "RID")
  1062. dnam_pheno_merged3 <- dnam_pheno_merged3 %>%
  1063. distinct(barcodes, .keep_all = TRUE)
  1064. dnam_pheno_merged3 <- dnam_pheno_merged3 %>%
  1065. mutate(DXSUM2 = case_when(
  1066. (Progressor == 0) ~ "Non-Progressor",
  1067. (Progressor == 1) ~ "Progressor MCI",
  1068. (DXSUM == "CU") ~ "CU",
  1069. (DXSUM == "Dementia") ~ "Dementia",
  1070. TRUE ~ DXSUM
  1071. ))
  1072. # Cognitive performance & individualized slopes of CDRSB and MMSE
  1073. dnam_pheno_merged4 <- merge(dnam_pheno_merged3, cdr %>%
  1074. dplyr::select(-CDRSB_slope) %>%
  1075. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  1076. dnam_pheno_merged5 <- dnam_within1yr(dnam_pheno_merged4, CDR_Edate, df2_VISCODE = TRUE)
  1077. # Get back rest of the data
  1078. unmatched <- anti_join(dnam_pheno_merged3, dnam_pheno_merged5, by = c("RID", "DNAm_Edate"))
  1079. dnam_pheno_merged6 <- bind_rows(dnam_pheno_merged5, unmatched)
  1080. dnam_pheno_merged6 <- dnam_pheno_merged6[order(dnam_pheno_merged6$RID, dnam_pheno_merged6$DNAm_Edate), ]
  1081. rownames(dnam_pheno_merged6) <- NULL
  1082. dnam_pheno_merged6 <- left_join(dnam_pheno_merged6, cdr_slopes, by = "RID")
  1083. dnam_pheno_merged6 <- left_join(dnam_pheno_merged6, adas, by = c("RID", "VISCODE"))
  1084. dnam_pheno_merged7 <- merge(dnam_pheno_merged6, mmse %>%
  1085. dplyr::select(-MMSE_slope), by = "RID", all.x = TRUE)
  1086. dnam_pheno_merged8 <- dnam_within1yr(dnam_pheno_merged7, MMSE_Edate, df2_VISCODE = TRUE)
  1087. # Get back rest of the data
  1088. unmatched <- anti_join(dnam_pheno_merged6, dnam_pheno_merged8, by = c("RID", "DNAm_Edate"))
  1089. dnam_pheno_merged9 <- bind_rows(dnam_pheno_merged8, unmatched)
  1090. dnam_pheno_merged9 <- dnam_pheno_merged9[order(dnam_pheno_merged9$RID, dnam_pheno_merged9$DNAm_Edate), ]
  1091. rownames(dnam_pheno_merged9) <- NULL
  1092. dnam_pheno_merged9 <- left_join(dnam_pheno_merged9, mmse_slopes, by = "RID")
  1093. # Plasma NfL & individualized slopes
  1094. dnam_pheno_merged10 <- merge(dnam_pheno_merged9, nfl_data %>%
  1095. dplyr::select(-NfL_B_slope), by = "RID", all.x = TRUE)
  1096. dnam_pheno_merged11 <- dnam_within1yr(dnam_pheno_merged10, NfL_Edate, df2_VISCODE = TRUE)
  1097. # Get back rest of the data
  1098. unmatched <- anti_join(dnam_pheno_merged9, dnam_pheno_merged11, by = c("RID", "DNAm_Edate"))
  1099. dnam_pheno_merged12 <- bind_rows(dnam_pheno_merged11, unmatched)
  1100. dnam_pheno_merged12 <- dnam_pheno_merged12[order(dnam_pheno_merged12$RID, dnam_pheno_merged12$DNAm_Edate), ]
  1101. rownames(dnam_pheno_merged12) <- NULL
  1102. dnam_pheno_merged12 <- left_join(dnam_pheno_merged12, nfl_slopes, by = "RID")
  1103. # Amyloid PET & individualized slopes of centiloids
  1104. dnam_pheno_merged13 <- merge(dnam_pheno_merged12, ab_pet %>%
  1105. dplyr::select(-CENTILOIDS_slope), by = "RID", all.x = TRUE)
  1106. dnam_pheno_merged14 <- dnam_within1yr(dnam_pheno_merged13, PET_Edate, df2_VISCODE = FALSE)
  1107. # Get back rest of the data
  1108. unmatched <- anti_join(dnam_pheno_merged12, dnam_pheno_merged14, by = c("RID", "DNAm_Edate"))
  1109. dnam_pheno_merged15 <- bind_rows(dnam_pheno_merged14, unmatched)
  1110. dnam_pheno_merged15 <- dnam_pheno_merged15[order(dnam_pheno_merged15$RID, dnam_pheno_merged15$DNAm_Edate), ]
  1111. rownames(dnam_pheno_merged15) <- NULL
  1112. dnam_pheno_merged15 <- left_join(dnam_pheno_merged15, ab_slopes, by = "RID")
  1113. dnam_pheno_merged15 <- merge(dnam_pheno_merged15, last_negative_date_pet, by = "RID", all.x = TRUE)
  1114. dnam_pheno_merged15 <- merge(dnam_pheno_merged15, first_positive_date_pet, by = "RID", all.x = TRUE)
  1115. dnam_pheno_merged15 <- merge(dnam_pheno_merged15, last_negative_date2_pet, by = "RID", all.x = TRUE)
  1116. dnam_pheno_merged15 <- merge(dnam_pheno_merged15, first_positive_date2_pet, by = "RID", all.x = TRUE)
  1117. dnam_pheno_merged15 <- merge(dnam_pheno_merged15, last_negative_date3_pet, by = "RID", all.x = TRUE)
  1118. dnam_pheno_merged15 <- merge(dnam_pheno_merged15, first_positive_date3_pet, by = "RID", all.x = TRUE)
  1119. dnam_pheno_merged15 <- dnam_pheno_merged15[order(dnam_pheno_merged15$RID, dnam_pheno_merged15$DNAm_Edate), ]
  1120. rownames(dnam_pheno_merged15) <- NULL
  1121. # Ensure amyloid PET positivity follows expected logic
  1122. dnam_pheno_merged16 <- dnam_pheno_merged15 %>%
  1123. group_by(RID) %>%
  1124. mutate(amyloid_pos_pet_cross = case_when(
  1125. !is.na(last_AB_neg_date) & !is.na(first_AB_pos_date) & is.na(amyloid_pos_pet_cross) & first_AB_pos_date < last_AB_neg_date ~ amyloid_pos_pet_cross,
  1126. !is.na(last_AB_neg_date) & is.na(amyloid_pos_pet_cross) & DNAm_Edate <= last_AB_neg_date ~ 0,
  1127. !is.na(first_AB_pos_date) & is.na(amyloid_pos_pet_cross) & DNAm_Edate >= first_AB_pos_date ~ 1,
  1128. TRUE ~ amyloid_pos_pet_cross)) %>%
  1129. ungroup()
  1130. dnam_pheno_merged16 <- dnam_pheno_merged16 %>%
  1131. group_by(RID) %>%
  1132. mutate(amyloid_pos_pet_long = case_when(
  1133. !is.na(last_AB_neg_date2) & !is.na(first_AB_pos_date2) & is.na(amyloid_pos_pet_long) & first_AB_pos_date2 < last_AB_neg_date2 ~ amyloid_pos_pet_long,
  1134. !is.na(last_AB_neg_date2) & is.na(amyloid_pos_pet_long) & DNAm_Edate <= last_AB_neg_date2 ~ 0,
  1135. !is.na(first_AB_pos_date2) & is.na(amyloid_pos_pet_long) & DNAm_Edate >= first_AB_pos_date2 ~ 1,
  1136. TRUE ~ amyloid_pos_pet_long)) %>%
  1137. ungroup()
  1138. dnam_pheno_merged16 <- dnam_pheno_merged16 %>%
  1139. group_by(RID) %>%
  1140. mutate(amyloid_pos_pet_centiloid = case_when(
  1141. !is.na(last_AB_neg_date3) & !is.na(first_AB_pos_date3) & is.na(amyloid_pos_pet_centiloid) & first_AB_pos_date3 < last_AB_neg_date3 ~ amyloid_pos_pet_centiloid,
  1142. !is.na(last_AB_neg_date3) & is.na(amyloid_pos_pet_centiloid) & DNAm_Edate <= last_AB_neg_date3 ~ 0,
  1143. !is.na(first_AB_pos_date3) & is.na(amyloid_pos_pet_centiloid) & DNAm_Edate >= first_AB_pos_date3 ~ 1,
  1144. TRUE ~ amyloid_pos_pet_centiloid)) %>%
  1145. ungroup() %>%
  1146. dplyr::select(-c("last_AB_neg_date", "first_AB_pos_date", "last_AB_neg_date2", "first_AB_pos_date2", "last_AB_neg_date3", "first_AB_pos_date3"))
  1147. # CSF biomarkers & individualized slopes of p-tau181
  1148. dnam_pheno_merged17 <- merge(dnam_pheno_merged16, upenn_csf %>%
  1149. dplyr::select(-pTAU181_csf_slope) %>%
  1150. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  1151. dnam_pheno_merged18 <- dnam_within1yr(dnam_pheno_merged17, CSF_Edate, df2_VISCODE = TRUE)
  1152. # Get back rest of the data
  1153. unmatched <- anti_join(dnam_pheno_merged16, dnam_pheno_merged18, by = c("RID", "DNAm_Edate"))
  1154. dnam_pheno_merged19 <- bind_rows(dnam_pheno_merged18, unmatched)
  1155. dnam_pheno_merged19 <- dnam_pheno_merged19[order(dnam_pheno_merged19$RID, dnam_pheno_merged19$DNAm_Edate), ]
  1156. rownames(dnam_pheno_merged19) <- NULL
  1157. dnam_pheno_merged19 <- left_join(dnam_pheno_merged19, csf_slopes, by = "RID")
  1158. dnam_pheno_merged19 <- merge(dnam_pheno_merged19, last_negative_date_csf, by = "RID", all.x = TRUE)
  1159. dnam_pheno_merged19 <- merge(dnam_pheno_merged19, first_positive_date_csf, by = "RID", all.x = TRUE)
  1160. dnam_pheno_merged19 <- merge(dnam_pheno_merged19, last_negative_date2_csf, by = "RID", all.x = TRUE)
  1161. dnam_pheno_merged19 <- merge(dnam_pheno_merged19, first_positive_date2_csf, by = "RID", all.x = TRUE)
  1162. dnam_pheno_merged19 <- merge(dnam_pheno_merged19, last_negative_date3_csf, by = "RID", all.x = TRUE)
  1163. dnam_pheno_merged19 <- merge(dnam_pheno_merged19, first_positive_date3_csf, by = "RID", all.x = TRUE)
  1164. dnam_pheno_merged19 <- dnam_pheno_merged19[order(dnam_pheno_merged19$RID, dnam_pheno_merged19$DNAm_Edate), ]
  1165. rownames(dnam_pheno_merged19) <- NULL
  1166. # Ensure CSF biomarker positivity follows expected logic
  1167. dnam_pheno_merged20 <- dnam_pheno_merged19 %>%
  1168. group_by(RID) %>%
  1169. mutate(amyloid_pos_csf = case_when(
  1170. !is.na(last_AB_neg_date) & !is.na(first_AB_pos_date) & is.na(amyloid_pos_csf) & first_AB_pos_date < last_AB_neg_date ~ amyloid_pos_csf,
  1171. !is.na(last_AB_neg_date) & is.na(amyloid_pos_csf) & DNAm_Edate <= last_AB_neg_date ~ 0,
  1172. !is.na(first_AB_pos_date) & is.na(amyloid_pos_csf) & DNAm_Edate >= first_AB_pos_date ~ 1,
  1173. TRUE ~ amyloid_pos_csf)) %>%
  1174. ungroup()
  1175. dnam_pheno_merged20 <- dnam_pheno_merged20 %>%
  1176. group_by(RID) %>%
  1177. mutate(ptau_pos_csf = case_when(
  1178. !is.na(last_TAU_neg_date2) & !is.na(first_TAU_pos_date2) & is.na(ptau_pos_csf) & first_TAU_pos_date2 < last_TAU_neg_date2 ~ ptau_pos_csf,
  1179. !is.na(last_TAU_neg_date2) & is.na(ptau_pos_csf) & DNAm_Edate <= last_TAU_neg_date2 ~ 0,
  1180. !is.na(first_TAU_pos_date2) & is.na(ptau_pos_csf) & DNAm_Edate >= first_TAU_pos_date2 ~ 1,
  1181. TRUE ~ ptau_pos_csf)) %>%
  1182. ungroup()
  1183. dnam_pheno_merged20 <- dnam_pheno_merged20 %>%
  1184. group_by(RID) %>%
  1185. mutate(ad_pathology_pos_csf = case_when(
  1186. !is.na(last_AD_neg_date3) & !is.na(first_AD_pos_date3) & is.na(ad_pathology_pos_csf) & first_AD_pos_date3 < last_AD_neg_date3 ~ ad_pathology_pos_csf,
  1187. !is.na(last_AD_neg_date3) & is.na(ad_pathology_pos_csf) & DNAm_Edate <= last_AD_neg_date3 ~ 0,
  1188. !is.na(first_AD_pos_date3) & is.na(ad_pathology_pos_csf) & DNAm_Edate >= first_AD_pos_date3 ~ 1,
  1189. TRUE ~ ad_pathology_pos_csf)) %>%
  1190. ungroup() %>%
  1191. dplyr::select(-c("last_AB_neg_date", "first_AB_pos_date", "last_TAU_neg_date2", "first_TAU_pos_date2", "last_AD_neg_date3", "first_AD_pos_date3"))
  1192. # Hippocampal volume and meta-ROI cortical thickness & individualized slopes
  1193. dnam_pheno_merged21 <- merge(dnam_pheno_merged20, fs_cs %>%
  1194. dplyr::rename(VISCODE2 = VISCODE), by = "RID", all.x = TRUE)
  1195. dnam_pheno_merged22 <- dnam_within1yr(dnam_pheno_merged21, MRIcs_Edate, df2_VISCODE = TRUE)
  1196. # Get back rest of the data
  1197. unmatched <- anti_join(dnam_pheno_merged20, dnam_pheno_merged22, by = c("RID", "DNAm_Edate"))
  1198. dnam_pheno_merged23 <- bind_rows(dnam_pheno_merged22, unmatched)
  1199. dnam_pheno_merged23 <- dnam_pheno_merged23[order(dnam_pheno_merged23$RID, dnam_pheno_merged23$DNAm_Edate), ]
  1200. rownames(dnam_pheno_merged23) <- NULL
  1201. dnam_pheno_merged23 <- left_join(dnam_pheno_merged23, fs_slopes, by = "RID")
  1202. # Estimate immune cell-type proportions
  1203. adni_se <- readRDS(file.path(proc_dir, "dasen_se_all_rv1.RDS"))
  1204. beta <- as.matrix(assays(adni_se)$DNAm)
  1205. rm(adni_se)
  1206. data(centDHSbloodDMC.m)
  1207. BloodFrac.m <- epidish(beta.m = beta, ref.m = centDHSbloodDMC.m, method = "RPC")$estF
  1208. cell_types <- as.data.frame(BloodFrac.m)
  1209. cell_types <- tibble::rownames_to_column(cell_types, "barcodes")
  1210. cell_types$Gran <- cell_types$Neutro + cell_types$Eosino
  1211. dnam_pheno_merged24 <- left_join(dnam_pheno_merged23, cell_types, by = "barcodes")
  1212. message("Now saving final (longitudinal) DNA methylation phenotype data...")
  1213. # Save final DNA methylation phenotype data for analysis
  1214. write.csv(dnam_pheno_merged24, file.path(base_dir, "DNAm_pheno_final.csv"), row.names = F)
  1215. ## Create Subset of DNA Methylation Phenotype Data Aligned to Time of Gene Expression Profiling ####
  1216. # Matched at same time-point or within 1-year of gene expression profiling
  1217. message("Now creating a cross-sectional subset of DNA methylation phenotype data based on time of gene expression profiling...")
  1218. # Read in phenotype data from both -omics that we just created
  1219. exp_pheno <- read.csv(file.path(base_dir, "GeneX_pheno_final.csv"), stringsAsFactor = FALSE, check.names = FALSE)
  1220. exp_pheno$RID <- addZeros(exp_pheno$RID)
  1221. dnam_pheno <- read.csv(file.path(base_dir, "DNAm_pheno_final.csv"), stringsAsFactor = FALSE)
  1222. dnam_pheno$RID <- addZeros(dnam_pheno$RID)
  1223. # Identify the cross-sectional matched time-point for DNA methylation based on time of gene expression profiling
  1224. merged <- merge(dnam_pheno %>%
  1225. dplyr::select(-c(DXSUM2, Progressor, time2event)) %>%
  1226. dplyr::rename(DNAm_DX_bl = DX_bl),
  1227. exp_pheno %>%
  1228. dplyr::select(RID, VISCODE, GeneX_Edate, DX_Edate, DXSUM, DXSUM2, Progressor, time2event) %>%
  1229. dplyr::rename(GeneX_VISCODE = VISCODE,
  1230. GeneX_DXSUM = DXSUM,
  1231. GeneX_DX_Edate = DX_Edate), by = "RID", all.x = TRUE) %>%
  1232. # Use time of gene expression profiling as the anchor
  1233. mutate(date_diff = abs(lubridate::time_length(difftime(DNAm_Edate, GeneX_Edate), "years"))) %>%
  1234. mutate(Edate_match = ifelse(date_diff <= 1, 1, 0),
  1235. VISCODE_match = ifelse(VISCODE == GeneX_VISCODE, 1, 0)) %>%
  1236. group_by(RID) %>%
  1237. mutate(min_match_date = min(date_diff)) %>%
  1238. # Same time = VISCODE_match; within 1-year = Edate_match
  1239. mutate(match_priority = case_when(
  1240. VISCODE_match == 1 ~ 1,
  1241. Edate_match == 1 ~ 2,
  1242. TRUE ~ 3
  1243. )) %>%
  1244. # Only keep matches
  1245. filter(match_priority <= 2) %>%
  1246. # If multiple matches, keep min date diff
  1247. filter(min_match_date == date_diff) %>%
  1248. # Replace any missing diagnosis, EXAMDATE, and VISCODE using the ones from gene expression pheno data
  1249. mutate(DXSUM = ifelse(is.na(DXSUM) & date_diff == 0 & !is.na(GeneX_DXSUM), GeneX_DXSUM, DXSUM),
  1250. VISCODE = ifelse(is.na(VISCODE) & date_diff == 0 & !is.na(GeneX_VISCODE), GeneX_VISCODE, VISCODE),
  1251. DX_Edate = ifelse(is.na(DX_Edate) & date_diff == 0 & !is.na(GeneX_DX_Edate), GeneX_DX_Edate, DX_Edate)
  1252. ) %>%
  1253. # Filter missing B, a convenient proxy for the replicates that were excluded during original DNA methylation preprocessing
  1254. filter(!is.na(B)) %>%
  1255. ungroup() %>%
  1256. dplyr::select(-c(Edate_match, VISCODE_match, match_priority, min_match_date, date_diff,
  1257. GeneX_DXSUM, GeneX_DX_Edate))
  1258. message("Now saving the final DNA methylation phenotype data for analysis...")
  1259. # Save the final DNA methylation phenotype data (same time or within 1-year of gene expression) for cross-sectional analysis
  1260. write.csv(merged, file.path(base_dir, "DNAm_pheno_final_shared.csv"), row.names = F)

phenotype.R at commit 5874e91, no license · at the source

Overview

Authors: Brendan A. Mitchell1,2, Isabella Hausle1, Sara Smith3,4, Pamela Thropp5, Marina Sirota6,7, Duygu Tosun1
  1. Department of Radiology and Biomedical Imaging, University of California,San Francisco, CA USA
  2. UC Berkeley - UCSF Graduate Program in Bioengineering,Berkeley, CA USA
  3. Department of Medicine, Division of Rheumatology, University of California,San Francisco, CA USA
  4. CoLabs, University of California,San Francisco, CA USA
  5. Northern California Institute for Research and Education (NCIRE),San Francisco, CA USA
  6. Bakar Computational Health Sciences Institute, University of California,San Francisco, CA USA
  7. Department of Pediatrics, University of California,San Francisco, CA USA
Journal: NPJ dementia, volume 2, issue 1, article 74
Dates: received 28 January 2026; accepted 9 May 2026; published online 31 August 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s44400-026-00100-z · PMID 42687894 · PMCID PMC13529590 · OpenAlex W7204824944
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), human (organism), Alzheimer's / dementia (population)
Methods: Statistics, Smoothing, state filtering, decompositions, fMRI & imaging, Preprocessing
Keywords: Biomarkers, Computational biology and bioinformatics, Diseases, Neurology, Neuroscience
Topic: Alzheimer's disease research and treatments (Physiology, Medicine), according to OpenAlex
Citations: not cited yet (Europe PMC); 83 references in the paper

Abstract

Leveraging multi-omics to better understand the molecular signatures and pathways underlying Alzheimer’s disease (AD) pathogenesis is critical for early diagnosis and disease modifying interventions. We performed peripheral blood transcriptome (N = 669) and epigenome microarray analyses (N = 553) on non-Hispanic white participants from the Alzheimer’s Disease Neuroimaging Initiative (ADNI) to identify molecular signatures of AD. We identified specific transcripts (e.g., MAPK14, GM2A, CD177) and co-expression networks that were dysregulated in AD, marked by a strong influence of APOE ε4 genotype, and characterized by a consistent pattern of immune activation, inflammation, and metabolic suppression. Further, these peripheral signatures were linked to central AD pathology (amyloid PET, CSF p-tau181) and neurodegeneration (plasma NfL, regional atrophy), with two genes, MXD3 and NR4A1, identified as protective against progression from MCI to AD. Our work emphasizes the importance of APOE genotypes in AD pathophysiology and highlights potential targets for biomarker discovery and personalized therapeutic strategies.

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

cind/adni-mrna-dnam-analysis

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 5874e916559bb984d4614f4299af1611056a7fa8, 19 June 2026
Languages: R (16)
Size: 17 files, 16 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (15 files), WGCNA (9 files), limma (7 files), broom (4 files), reshape2 (4 files), ComplexHeatmap (3 files), survival (3 files), clusterProfiler (2 files), data.table (2 files), ggpubr (1 file), lme4 (1 file), pheatmap (1 file), UMAP (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
17 files

Code availability

The code used in this manuscript can be found at https://github.com/cind/adni-mrna-dnam-analysis/.

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

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;
  • 16 scripts, each with its path and the digest of its content;
  • 16 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

Data used in the preparation of this article were obtained from the ADNI database (adni.loni.usc.edu). As such, the investigators within the ADNI contributed to the design and implementation of ADNI and/or provided data but did not participate in the analysis or writing of this report. A complete listing of ADNI investigators can be found at: http://adni.loni.usc.edu/wp-content/uploads/how_to_apply/ADNI_Acknowledgement_List.pdf.

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 2, 28 September 2026

  • Publisher: n/a → Springer Science+Business Media

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 5 keywords, 2 funders, 79 references.

Cite

This paper

Mitchell, B. A., Hausle, I., Smith, S., Thropp, P., Sirota, M., & Tosun, D. (2026). Peripheral blood microarray-based transcriptomic and epigenetic analyses identify immune, inflammation, and metabolic dysregulation in Alzheimer's disease. NPJ dementia, 2(1), 74. https://doi.org/10.1038/s44400-026-00100-z

BibTeX

@article{mitchell2026peripheral,
author = {Mitchell, Brendan A. and Hausle, Isabella and Smith, Sara and Thropp, Pamela and Sirota, Marina and Tosun, Duygu},
title = {{Peripheral blood microarray-based transcriptomic and epigenetic analyses identify immune, inflammation, and metabolic dysregulation in Alzheimer's disease}},
journal = {NPJ dementia},
year = {2026},
month = aug,
volume = {2},
number = {1},
pages = {74},
publisher = {Springer Science+Business Media},
issn = {3005-1940},
doi = {10.1038/s44400-026-00100-z},
url = {https://doi.org/10.1038/s44400-026-00100-z},
pmid = {42687894},
pmcid = {PMC13529590}
}

RIS

TY - JOUR
AU - Mitchell, Brendan A.
AU - Hausle, Isabella
AU - Smith, Sara
AU - Thropp, Pamela
AU - Sirota, Marina
AU - Tosun, Duygu
TI - Peripheral blood microarray-based transcriptomic and epigenetic analyses identify immune, inflammation, and metabolic dysregulation in Alzheimer's disease
T2 - NPJ dementia
J2 - NPJ Dement
PY - 2026
DA - 2026/08/31
VL - 2
IS - 1
SP - 74
SN - 3005-1940
PB - Springer Science+Business Media
DO - 10.1038/s44400-026-00100-z
UR - https://doi.org/10.1038/s44400-026-00100-z
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s44400-026-00100-z",
"type": "article-journal",
"title": "Peripheral blood microarray-based transcriptomic and epigenetic analyses identify immune, inflammation, and metabolic dysregulation in Alzheimer's disease",
"container-title": "NPJ dementia",
"author": [
{
"family": "Mitchell",
"given": "Brendan A."
},
{
"family": "Hausle",
"given": "Isabella"
},
{
"family": "Smith",
"given": "Sara"
},
{
"family": "Thropp",
"given": "Pamela"
},
{
"family": "Sirota",
"given": "Marina"
},
{
"family": "Tosun",
"given": "Duygu"
}
],
"container-title-short": "NPJ Dement",
"volume": "2",
"issue": "1",
"page": "74",
"DOI": "10.1038/s44400-026-00100-z",
"PMID": "42687894",
"PMCID": "PMC13529590",
"ISSN": "3005-1940",
"publisher": "Springer Science+Business Media",
"URL": "https://doi.org/10.1038/s44400-026-00100-z",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
31
]
]
}
}

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.1016/j.xcrm.2026.102682 [code]
TET CpG sequence-context-specific DNA demethylation shapes progression of IDH-mutant gliomas.
Journal: Cell reports. Medicine
In common: survival, limma, UMAP, 9 other tools, genetics / omics, 2 references
[2] doi:10.1093/bioinformatics/btag592 [code]
Network-based stratification of allele-specific expression reveals patient subgroups in Huntington's disease.
Journal: Bioinformatics (Oxford, England)
In common: survival, WGCNA, limma, 10 other tools, genetics / omics
[3] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: survival, WGCNA, limma, 10 other tools
[4] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: WGCNA, limma, UMAP, 8 other tools, genetics / omics, 2 references
[5] doi:10.1016/j.isci.2026.115657 [code]
Integration of machine learning to develop a disulfidptosis model for predicting glioma prognosis, immunotherapy response, and drug.
Journal: iScience
In common: survival, WGCNA, limma, 8 other tools
[6] doi:10.1038/s41593-026-02367-0 [code]
A reproducible three-dimensional model of human brain tissue to investigate physiological and disease-associated microglia phenotypes.
Journal: Nature neuroscience
In common: WGCNA, limma, clusterProfiler, 6 other tools, Alzheimer's / dementia, 2 references
[7] doi:10.3390/ijms27104466 [code]
Uncovering the Key Circuit FOSL2/FOS/EGR3/EGR1, Contributing to the Hyperexcitability of Excitatory Neurons in the Epileptic Temporal Cortex and Hippocampus.
Journal: International journal of molecular sciences
In common: limma, UMAP, clusterProfiler, 7 other tools, genetics / omics, 1 reference
[8] doi:10.1038/s41380-026-03629-w [code]
Maternal fasting during early gestation induces epigenetic alterations and schizophrenia-related phenotypes.
Journal: Molecular psychiatry
In common: WGCNA, limma, clusterProfiler, 4 other tools, genetics / omics, 3 references
[9] doi:10.1038/s41467-026-77170-3 [code]
DNA methylation profiling identifies long-range epigenetic silencing of clustered protocadherins as a key determinant of meningioma progression.
Journal: Nature communications
In common: survival, limma, broom, 6 other tools, genetics / omics
[10] doi:10.1038/s41467-026-73305-8 [code]
Comparative analysis of the cellular landscape in mammalian striatum.
Journal: Nature communications
In common: WGCNA, clusterProfiler, ComplexHeatmap, 5 other tools, genetics / omics, 2 references

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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