OSCR

A human lysosomal storage disorder toolkit for decoding proteome landscapes in cortical-like and dopaminergic-like induced neurons.

Code ↔ Paper

7 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 7 matches
  1. [1] § Results › A Stem Cell Toolkit for Systematic Analysis of LSD Genes. ↔ proteome/diff132_d50_nDIA.Rmd, lines 243–306 · score 0.81 · ATP13A2, CLN1, CLN2, CLN7, CTSF, DNAJC5
  2. [2] § Results › LSD Mutant iNeuron Organelle Landscapes. ↔ proteome/diff132_d50_nDIA.Rmd, lines 510–602 · score 0.76 · technical noise, biological signals, genotype variance, organelle annotations, log2fc, ratio
  3. [3] § Results › Proteomic Fingerprint Resource for LSD Mutant iN and iDA Neurons. ↔ proteome/diff132_d50_nDIA.Rmd, lines 2310–2349 · score 0.66 · Integral membrane protein, SynapseSV, recycling endosome, Disease class, Ternary, log2FC
  4. [4] § Results › LSD Mutant iNeuron Organelle Landscapes. ↔ proteome/diff132_d50_nDIA.Rmd, lines 310–349 · score 0.62 · neuronal ceroid lipofuscinoses, integral membrane protein, disease class, IDs, disorders, GRN
  5. [5] § Results › LSD Mutant iNeuron Organelle Landscapes. ↔ proteome/diff132_d50_nDIA.Rmd, lines 12–114 · score 0.61 · subcellular compartments, synaptic compartments, impact score, disease classes, knockout, variance
  6. [6] § Results › Visualization of ASAH1−/− Endolysosomal Compartments by Cryo-ET. ↔ lipidome/Lipidomics_HeLa_iN-diff133_ASAH1-WC-OrganellIeIP.Rmd, lines 85–198 · score 0.53 · LysoIP, HeLa, GM1, lipidomics, GM2, GM3
  7. [7] § Results › Proteomic Fingerprint Resource for LSD Mutant iN and iDA Neurons. ↔ proteome/diff132_d50_nDIA.Rmd, lines 243–306 · score 0.51 · hrMS2, nDIA, Astral, GRN, triplicate, SMPD1

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 Markdown · 4,896 lines · 195 KB · MIT · 6 matches

  1. ---
  2. title: "diff132_d50_nDIA"
  3. output: html_document
  4. date: "`r Sys.Date()`"
  5. chunk_output_type: console
  6. ---
  7. ```{r setup, include=FALSE}
  8. knitr::opts_chunk$set(echo = TRUE)
  9. ```
  10. # =================================================
  11. # Module 0: Setup & Configuration & Experimental Background
  12. # =================================================
  13. # Module overview & aims
  14. Module 1: Imports replicate-level proteomics data, performs quality control through replicate correlation analysis, F-ratio variance testing (biological vs. technical noise), and PCA visualization across genotypes, neuron types, and differentiation timepoints.
  15. Module 2: Processes summarized fold-change data with subcellular annotations, generates global heatmaps, validates LSD knockout efficiency, and visualizes organelle-level proteome changes using ternary plots, half-circos plots, and annotation-specific PCA for synaptic compartments.
  16. Module 3: Validates neuronal identity through pre/postsynaptic and dopaminergic marker expression, then profiles synaptic machinery (v-ATPase, SNARE, SV docking complexes) across genotypes with ranked log2FC plots and ChimeraX-compatible color tables for structure mapping.
  17. Module 4: Compares day 30 versus day 50 proteomes through neuronal maturation marker trajectories, pre/postsynaptic protein dynamics, and annotation-level log2FC violin plots with Wilcoxon statistics for ASAH1 and GBA1 knockouts across differentiation timepoints.
  18. Module 5: Integrates day 70 ASAH1 knockout data with day 30/50 to build a three-timepoint longitudinal analysis, including PCA trajectories, annotation-level violin plots across differentiation, pairwise annotation correlations, and ChimeraX color-mapping commands for mitochondrial Complex I structure visualization.
  19. Module 6: Provides reusable functions for generating annotation-stratified violin plots of log2FC across genotypes and volcano plots with customizable annotation highlights, plus iN-vs-iDA correlation scatter plots preserving top hits from both neuron types.
  20. Module 7: Computes genotype-by-genotype and annotation-by-annotation Spearman correlations of log2FC values, generates impact scoring heatmaps ranking genotypes by their deviation from the group, and produces bubble plots comparing iN vs iDA correlation patterns across disease classes and subcellular compartments.
  21. Module 8: Performs disease-class-focused analyses including sphingolipidosis subset heatmaps and correlations, cross-experiment ASAH1 timecourse comparisons (diff118 vs diff132), Euler diagrams for genotype overlap within annotations, limma-based differential analysis between genotypes, and circos plots visualizing annotation-level correlations stratified by disease class.
  22. Module 9:Linear Regression
  23. Module 10: Performs a comprehensive cross-omics correlation analysis between neuron (iN/iDA) nDIA proteomes and HeLa nMOST proteomes across LSD genotypes, integrating GO and organelle annotations to visualize genotype-, cluster-, and annotation-specific correlation structure using boxplots, heatmaps, and multi-layer circos plots.
  24. Module 11: Builds an integrated PPI vulnerability and rewiring pipeline for neuronal proteomes (iN and iDA) across lysosomal gene knockouts by merging experimental proteomics with predicted and validated interaction networks. It quantifies network destabilization, identifies organelle- and neuron-type–specific vulnerabilities, and visualizes PPI loss, rewiring, and functional impacts using network, enrichment, and comparative analyses.
  25. Module 12: Extends the PPI vulnerability framework to HeLa whole-cell proteome data from nMOST, computes control-normalized baseline stability, identifies lost/retained edges and broken trimers/complexes per KO, then performs cross-cell-type comparisons (iN, iDA, HeLa) using streamgraphs and matching pattern analysis to identify cell-type-specific versus shared PPI vulnerabilities.
  26. Module 13: Validates predicted PPI networks against XL-MS cross-linking data (DSSO-CLASP from Zhu et al.) by mapping gene names to UniProt IDs, computing edge-key overlaps between CLASP-detected interactions and neuronal/HeLa baseline networks, and quantifying both forward (CLASP in baseline) and reverse (mito baseline in CLASP) validation rates with mitochondrial annotation breakdowns.
  27. # Load packackes & set project directory
  28. ```{r setup, include=FALSE}
  29. # install.packages(c(
  30. # "plyr", "ggrepel", "ggpubr", "ggpmisc", "patchwork", "gghighlight",
  31. # "ggVennDiagram", "UpSetR", "ComplexUpset", "pheatmap", "circlize",
  32. # "magick", "viridis", "NatParksPalettes", "limma", "bigstatsr",
  33. # "irlba", "ggbiplot", "plotly", "Cairo", "devtools", "lintr"
  34. # ))
  35. # Core Data Manipulation & Tidyverse
  36. library(tidyverse) # Includes ggplot2, dplyr, tidyr, readr, tibble, purrr, stringr, forcats
  37. library(plyr)
  38. library(dplyr)
  39. library(data.table)
  40. library(broom)
  41. library(rlang)
  42. library(tidyr)
  43. # Visualization: ggplot2 Extensions & Plots
  44. library(ggplot2)
  45. library(ggrepel)
  46. library(ggpubr)
  47. library(ggpmisc)
  48. library(cowplot)
  49. library(patchwork)
  50. library(scales)
  51. library(gghighlight)
  52. library(superheat)
  53. library(ggVennDiagram)
  54. library(UpSetR)
  55. library(ComplexUpset)
  56. install.packages("BiocManager")
  57. BiocManager::install("ComplexHeatmap")
  58. library(ComplexHeatmap)
  59. #install.packages("eulerr")
  60. library(eulerr)
  61. #install.packages("Ternary")
  62. library(Ternary)
  63. #devtools::install_github("davidsjoberg/ggsankey")
  64. library(ggsankey)
  65. # Heatmaps & Clustering
  66. library(pheatmap)
  67. library(RColorBrewer)
  68. library(circlize)
  69. library(magick)
  70. # Color Palettes
  71. library(viridis)
  72. library(NatParksPalettes)
  73. # Dimension Reduction / Statistics
  74. library(limma)
  75. library(bigstatsr)
  76. library(irlba)
  77. library(ggbiplot)
  78. # Interactive & Advanced Plotting
  79. library(plotly)
  80. library(circlize)
  81. # Utilities / Dev / Misc
  82. library(grid)
  83. library(gridExtra)
  84. #install.packages("Cairo")
  85. library(Cairo)
  86. #library(png)
  87. library(devtools)
  88. library(lintr)
  89. ```
  90. # Output dirs overview
  91. ```{r}
  92. out_dir_d50_QC <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/QC"
  93. dir.create(out_dir_d50_QC, recursive = TRUE, showWarnings = FALSE)
  94. out_dir_d50_timecourse <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/timecourse"
  95. dir.create(out_dir_d50_timecourse, recursive = TRUE, showWarnings = FALSE)
  96. out_dir_d50_euler <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/QC/euler_annotations"
  97. dir.create(out_dir_d50_euler, recursive = TRUE, showWarnings = FALSE)
  98. out_dir_d50_ascd_avg <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/ascendingAvg"
  99. dir.create(out_dir_d50_ascd_avg, recursive = TRUE, showWarnings = FALSE)
  100. out_dir_d50_ascd_avg_SV <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/ascendingAvg/synapsevar"
  101. dir.create(out_dir_d50_ascd_avg_SV, recursive = TRUE, showWarnings = FALSE)
  102. out_dir_d50_heatmaps <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/heatmaps"
  103. dir.create(out_dir_d50_heatmaps, recursive = TRUE, showWarnings = FALSE)
  104. out_dir_d50_dataframes <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/dataframes"
  105. dir.create(out_dir_d50_dataframes, recursive = TRUE, showWarnings = FALSE)
  106. out_dir_d50_violins <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_Violin"
  107. dir.create(out_dir_d50_violins, recursive = TRUE, showWarnings = FALSE)
  108. out_dir_d50_volcano <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Volcano"
  109. dir.create(out_dir_d50_volcano, recursive = TRUE, showWarnings = FALSE)
  110. out_dir_d50_SphMut <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/SphMut"
  111. dir.create(out_dir_d50_SphMut, recursive = TRUE, showWarnings = FALSE)
  112. out_dir_d50_SphMuteuler <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/SphMut/euler"
  113. dir.create(out_dir_d50_SphMuteuler, recursive = TRUE, showWarnings = FALSE)
  114. out_dir_d50_DisClass <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/DisClass"
  115. dir.create(out_dir_d50_DisClass, recursive = TRUE, showWarnings = FALSE)
  116. out_dir_d50_DisClass_circos <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/DisClass/Cirosdiagram"
  117. dir.create(out_dir_d50_DisClass_circos, recursive = TRUE, showWarnings = FALSE)
  118. out_dir_d50_DisClass_splitcorr_dir <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/DisClass/Cirosdiagram/splitcorr"
  119. dir.create(out_dir_d50_DisClass_splitcorr_dir, recursive = TRUE, showWarnings = FALSE)
  120. out_dir_d50_GRN <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/GRN"
  121. dir.create(out_dir_d50_GRN, recursive = TRUE, showWarnings = FALSE)
  122. out_dir_d50_CrossCorr <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_CrossCorr"
  123. dir.create(out_dir_d50_CrossCorr, recursive = TRUE, showWarnings = FALSE)
  124. out_dir_d50_CrossCorr_annotavg <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_CrossCorr/annot_avg"
  125. dir.create(out_dir_d50_CrossCorr_annotavg, recursive = TRUE, showWarnings = FALSE)
  126. out_dir_d50_CrossCorr_nMOSTGOavgannot <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_CrossCorr/nMOSTGOavgannnot"
  127. dir.create(out_dir_d50_CrossCorr_nMOSTGOavgannot, recursive = TRUE, showWarnings = FALSE)
  128. out_dir_d50_CrossCorr_genoavg <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_CrossCorr/geno_avg"
  129. dir.create(out_dir_d50_CrossCorr_genoavg, recursive = TRUE, showWarnings = FALSE)
  130. out_dir_d50_CrossCorr_bubble <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_CrossCorr/bubble"
  131. dir.create(out_dir_d50_CrossCorr_bubble, recursive = TRUE, showWarnings = FALSE)
  132. out_dir_d50_CrossCorr_bubble_ASAH1 <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/Organelle_CrossCorr/ASAH1_c23d30d50"
  133. dir.create(out_dir_d50_CrossCorr_bubble_ASAH1, recursive = TRUE, showWarnings = FALSE)
  134. out_dir_d50_corrProtein <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/proteincorr"
  135. dir.create(out_dir_d50_corrProtein, recursive = TRUE, showWarnings = FALSE)
  136. out_dir_d50_SynPRM <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/SynPRM"
  137. dir.create(out_dir_d50_SynPRM, recursive = TRUE, showWarnings = FALSE)
  138. out_dir_d50_LinReg <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/LinReg"
  139. dir.create(out_dir_d50_LinReg, recursive = TRUE, showWarnings = FALSE)
  140. out_dir_d50_LinReg_limma <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/LinReg/limma_all_coef"
  141. dir.create(out_dir_d50_LinReg_limma, recursive = TRUE, showWarnings = FALSE)
  142. out_dir_d50_LinReg_limma_select <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/LinReg/limma_select"
  143. dir.create(out_dir_d50_LinReg_limma_select, recursive = TRUE, showWarnings = FALSE)
  144. out_dir_d50_nMOST_nDIAcorrelation <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/nMOST-nDIAcorrelation"
  145. dir.create(out_dir_d50_nMOST_nDIAcorrelation, recursive = TRUE, showWarnings = FALSE)
  146. out_dir_d50_nMOST_nDIAcorrelationS_single <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/nMOST-nDIAcorrelation/SingleGeno"
  147. dir.create(out_dir_d50_nMOST_nDIAcorrelationS_single, recursive = TRUE, showWarnings = FALSE)
  148. out_dir_d50_nMOST_nDIAcorrelationS_OrganelleComp <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/nMOST-nDIAcorrelation/HeLa_Neuron_SelectAnnotation_comparisons"
  149. dir.create(out_dir_d50_nMOST_nDIAcorrelationS_OrganelleComp, recursive = TRUE, showWarnings = FALSE)
  150. out_dir_d50_nMOST_nDIAcorrelationS_Circos <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/nMOST-nDIAcorrelation/HeLa_Neuron_SelectAnnotation_comparisons"
  151. dir.create(out_dir_d50_nMOST_nDIAcorrelationS_Circos, recursive = TRUE, showWarnings = FALSE)
  152. out_dir_d70_dataframes <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day70/dataframes"
  153. dir.create(out_dir_d70_dataframes, recursive = TRUE, showWarnings = FALSE)
  154. out_dir_d70_heatmap <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day70/heatmap"
  155. dir.create(out_dir_d70_heatmap, recursive = TRUE, showWarnings = FALSE)
  156. out_dir_d70_timecourse <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day70/timecourse"
  157. dir.create(out_dir_d70_timecourse, recursive = TRUE, showWarnings = FALSE)
  158. out_dir_d70_pca <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day70/pca"
  159. dir.create(out_dir_d70_pca, recursive = TRUE, showWarnings = FALSE)
  160. out_dir_d70_violins <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day70/violins"
  161. dir.create(out_dir_d70_violins, recursive = TRUE, showWarnings = FALSE)
  162. out_dir_d70_corr <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day70/corr"
  163. dir.create(out_dir_d70_corr, recursive = TRUE, showWarnings = FALSE)
  164. out_dir_PPI <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/PPI"
  165. dir.create(out_dir_PPI, recursive = TRUE, showWarnings = FALSE)
  166. ```
  167. # Experimental Background: nDIA proteomics iN & iDA d30+d50
  168. - Samples are in triplicates whole-cell samples from 12-well dishes.
  169. - day 30 of in vitro differentiation of iN and iDA:
  170. Genotypes: Ctrl, ASAH1, CLN11.GRN, GBA1, SMPD1
  171. - day 50 of in vitro differentiation of iN and iDA:
  172. Genotypes: Ctrl, ASAH1, CLN1.PPT1, CLN3, CLN4.DNAJC5, CLN5, CLN6, CLN7.MFSD8, CLN8, CLN10.CTSD, CLN11.GRN, CLN12.ATP13A2, CLN13.CTSF, CTNS, GAA, GBA1, GLB1, HEXA, HEXB, LIPA, MCOLN1.TRPML1, NPC1, NPC2, PSAP, SMPD1
  173. run on Thermo Orbitrap Astral, nDIA, hrMS2
  174. # Circos plot for neuroLSD
  175. ```{r}
  176. library(circlize)
  177. #read the csv file
  178. #read in first column (==gene) as row title
  179. LSD <- read.csv('/Users/felix/Documents/PostDoc/Harvard/03_LSD/Proteomics_Lipidomics/Data_forplots/Circos/LSD_circos.csv',
  180. header=T, row.names=1)
  181. #convert the table to a martix
  182. data <- as.matrix(LSD)
  183. #create a Ciros diagram
  184. chordDiagram(data)
  185. ############## CIRCOS Plot with confirmed KOs #####################
  186. ## plot only confirmed KOs in color, rest of genes in grey/ per disease group
  187. ## save plot as pdf
  188. # set colors for disease groups
  189. col = c(Sphingolipidoses="#D53E4F", Mucopolysaccharidoses="#F46D43", Glycoproteinoses="#FDAE61", Neuronal.ceroid.lipofuscinoses="#FEE08B",
  190. Integral.membrane.protein.disorders="#E6F598", PTM.defects="#ABDDA4", Lipid.storage.diseases= "#66C2A5",
  191. AGA="grey76", ARSA="grey76", ARSB="grey76", ASAH1="#D53E4F", CLN1.PPT1="#FEE08B", CLN10.CTSD="#FEE08B", CLN11.GRN="#FEE08B", CLN12.ATP13A2="#FEE08B",CLN12.ATP13A2="#E6F598", CLN13.CTSF="#FEE08B", CLN14.KCTD7="grey76", CLN2.TPP1="#FEE08B", CLN3="#FEE08B", CLN4.DNAJC5="#FEE08B", CLN5="#FEE08B", CLN6="#FEE08B", CLN7.MFSD8="#FEE08B", CLN8="#FEE08B", CTNS="grey76", CTSA="grey76", FUCA="grey76", GAA="#66C2A5", GALC="grey76", GALNS="grey76", GBA1="#D53E4F", GLA="grey76", GLB1="grey76", GM2A="grey76", GNPTAB="grey76", GNPTG="grey76", GNS="grey76", GUSB="grey76", HEXA="#D53E4F", HEXB="#D53E4F", HGSNAT="grey76", HYAL1="grey76", IDS="grey76", IDUA="grey76", LAMP2="grey76", LIPA="#66C2A5", MAN2B1="grey76", MANBA="grey76", MCOLN1="#E6F598", NAGA="grey76", NAGLU="grey76", NEU1="grey76", NPC1="#E6F598", NPC2="#E6F598", PSAP="#D53E4F", SCARB2="grey76", SGSH="grey76", SLC17A5="grey76", SMPD1="#D53E4F", SUMF1="grey76")
  192. # Set output path and size
  193. pdf("/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/LSD_circos_plot_confirmed_KOs.pdf", width = 10, height = 10) # adjust size as needed
  194. # Plot: chord diagram
  195. chordDiagram(data,
  196. grid.col = col,
  197. annotationTrack = "grid",
  198. preAllocateTracks = 1,
  199. transparency = 0.1,
  200. link.lwd = 1,
  201. link.lty = 1,
  202. link.border = 1)
  203. # Add labels
  204. circos.trackPlotRegion(track.index = 2, panel.fun = function(x, y) {
  205. xlim = get.cell.meta.data("xlim")
  206. ylim = get.cell.meta.data("ylim")
  207. sector.name = get.cell.meta.data("sector.index")
  208. circos.text(mean(xlim), ylim[1] + 2.5, sector.name,
  209. facing = "clockwise", niceFacing = TRUE,
  210. adj = c(0, 0.5), cex = 0.6)
  211. }, bg.border = "black")
  212. # Finish writing the PDF
  213. dev.off()
  214. ```
  215. # =================================================
  216. # Module 1: Data Import, QC (Replicate-level)
  217. # =================================================
  218. # top level disese-class annotation
  219. ```{r}
  220. disease_classes <- list(
  221. Sphingolipidoses = c("ARSA", "ASAH1", "GALC", "GBA1", "GLA", "GLB1", "GM2A", "HEXA", "HEXB", "PSAP", "SMPD1"),
  222. Mucopolysaccharidoses = c("GLB1", "ARSB", "GALNS", "GNS", "GUSB", "HGSNAT", "HYAL1", "IDS", "IDUA", "NAGLU", "SGSH"),
  223. Glycoproteinoses = c("AGA", "CTSA", "FUCA", "MAN2B1", "MANBA", "NAGA", "NEU1"),
  224. Neuronal.ceroid.lipofuscinoses = c("PPT1", "CTSD", "GRN", "ATP13A2", "CTSF", "KCTD7", "TPP1", "CLN3", "DNAJC5", "CLN5", "CLN6", "MFSD8", "CLN8"),
  225. Integral.membrane.protein.disorders = c("ATP13A2", "CLN3", "CTNS", "LAMP2", "MCOLN1", "NPC1", "NPC2", "SCARB2", "SLC17A5"),
  226. PTM.defects = c("GNPTAB", "GNPTG", "SUMF1"),
  227. Lipid.storage.diseases = c("GAA", "LIPA")
  228. )
  229. # convert into df
  230. disease_classes_dataframe <- stack(disease_classes)
  231. colnames(disease_classes_dataframe) <- c("sample", "DiseaseClass")
  232. # give it a color
  233. disease_class_palette <- c(
  234. Sphingolipidoses = "#D53E4F",
  235. Mucopolysaccharidoses = "#F46D43",
  236. Glycoproteinoses = "#FDAE61",
  237. Neuronal.ceroid.lipofuscinoses = "#FEE08B",
  238. Integral.membrane.protein.disorders = "#E6F598",
  239. PTM.defects = "#ABDDA4",
  240. Lipid.storage.diseases = "#66C2A5"
  241. )
  242. # create disease_class_df
  243. disease_class_df <- data.frame(
  244. sample = c("ARSA", "ASAH1", "GALC", "GBA1", "GLA", "GLB1", "GM2A", "HEXA", "HEXB", "PSAP", "SMPD1", "GLB1", "ARSB", "GALNS", "GNS", "GUSB", "HGSNAT", "HYAL1", "IDS", "IDUA", "NAGLU", "SGSH", "AGA", "CTSA", "FUCA", "MAN2B1", "MANBA", "NAGA", "NEU1", "PPT1", "CTSD", "GRN", "ATP13A2", "CTSF", "KCTD7", "TPP1", "CLN3", "DNAJC5", "CLN5", "CLN6", "MFSD8", "CLN8", "ATP13A2", "CLN3", "CTNS", "LAMP2", "MCOLN1", "NPC1", "NPC2", "SCARB2", "SLC17A5", "GNPTAB", "GNPTG", "SUMF1", "GAA", "LIPA"),
  245. DiseaseClass = c(rep("Sphingolipidoses", 11), rep("Mucopolysaccharidoses", 11), rep("Glycoproteinoses", 7), rep("Neuronal.ceroid.lipofuscinoses", 13), rep("Integral.membrane.protein.disorders", 9), rep("PTM.defects", 3), rep("Lipid.storage.diseases", 2))
  246. )
  247. ```
  248. # Data cleaning of replicate data
  249. plot PCA plots to check data landscape on top level
  250. Have following genotypes and cells in there
  251. iN, DA, pool (== QC mmaker)
  252. 3 replicates for genotypes
  253. ```{r}
  254. # --------------------------------------------------- #
  255. # Data cleaning and preparation
  256. # --------------------------------------------------- #
  257. # --- Step 1: Read in the data from the CSV file
  258. # The CSV file has been pre-processed in Excel to replace all NA values with 0.
  259. diff132_d50_PCA_df_full <- read.csv('/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/datasets/lsd_pro_long.csv', header = TRUE, sep = ',', stringsAsFactors = FALSE)
  260. # --- Step 1.5: Fill NA or empty values in 'neuron' and 'replicate' with QC info
  261. diff132_d50_PCA_df_full <- diff132_d50_PCA_df_full %>%
  262. mutate(
  263. is_qc = is.na(neuron) | neuron == "",
  264. neuron = ifelse(is_qc, "QC", neuron),
  265. replicate = ifelse(is_qc, as.character(seq_len(sum(is_qc))), replicate)
  266. )
  267. # --- Step 2: Inspect the structure of the data
  268. # This helps to understand the data types and identify which columns to retain or remove.
  269. str(diff132_d50_PCA_df_full)
  270. # --- Step 3: Remove unnecessary columns
  271. # Assuming columns 1, 2, 4, 5, and 10 are not needed for PCA, they are removed.
  272. diff132_d50_PCA_df <- diff132_d50_PCA_df_full[, -c(1,2,4,6,7,13,14)]
  273. # --- Step 4: Identify and remove rows with missing or empty 'Genes' entries
  274. # It's crucial to ensure that each gene has a valid identifier for accurate analysis.
  275. # Identify problematic rows where 'Genes' is NA or an empty string
  276. problematic_rows <- diff132_d50_PCA_df %>%
  277. filter(is.na(Genes) | Genes == "")
  278. # Remove these problematic rows from the dataset
  279. cleaned_df <- diff132_d50_PCA_df %>%
  280. filter(!is.na(Genes) & Genes != "") %>% # remove empty gene names
  281. filter(!is.na(quan)) # remove missing values in quan
  282. # --- Step 4a: Load subcellular annotations
  283. cols_to_read <- c(1,2,3,4,5,6,7,9,10,12,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34)
  284. subcell_df <- read.csv("/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/tmpneurogit/proteome/SubCellAnnotation.csv", stringsAsFactors = FALSE)[, cols_to_read]
  285. subcell_df[] <- lapply(subcell_df, as.character)
  286. long_subcell_df <- subcell_df %>%
  287. pivot_longer(cols = everything(), names_to = "Localization", values_to = "Genes") %>%
  288. filter(!is.na(Genes) & Genes != "") %>%
  289. mutate(Genes = trimws(Genes)) %>%
  290. distinct()
  291. ```
  292. # Replicate correlation analysis
  293. ```{r}
  294. # --- Step 4b: Replicate correlation summary
  295. rep_corr <- cleaned_df %>%
  296. dplyr::filter(neuron != "QC", !is.na(genotype)) %>%
  297. dplyr::group_by(Genes, day, neuron, genotype, replicate) %>%
  298. dplyr::summarise(quan = sum(quan), .groups = "drop") %>%
  299. tidyr::pivot_wider(names_from = replicate, values_from = quan, names_prefix = "rep") %>%
  300. dplyr::group_by(day, neuron, genotype) %>%
  301. dplyr::summarise(
  302. r12 = cor(rep1, rep2, use = "complete.obs"),
  303. r13 = cor(rep1, rep3, use = "complete.obs"),
  304. r23 = cor(rep2, rep3, use = "complete.obs"),
  305. .groups = "drop"
  306. ) %>%
  307. tidyr::pivot_longer(cols = c(r12, r13, r23), names_to = "pair", values_to = "correlation")
  308. summary(rep_corr$correlation)
  309. # Distribution plot
  310. ReplicateDist <- ggplot(rep_corr, aes(x = correlation)) +
  311. geom_histogram(bins = 30, fill = "dodgerblue4", color = "white") +
  312. geom_vline(xintercept = 0.95, linetype = "dashed", color = "red") +
  313. labs(x = "Pairwise replicate correlation (Pearson)", y = "Count") +
  314. theme_bw(base_size = 6) +
  315. theme(panel.grid = element_blank())
  316. ReplicateDist2 <- ggplot(rep_corr, aes(x = correlation, fill = neuron, color = neuron)) +
  317. geom_density(alpha = 0.5) +
  318. scale_fill_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  319. scale_color_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  320. geom_vline(xintercept = 0.95, linetype = "dashed", color = "firebrick") +
  321. labs(x = "Pairwise replicate correlation (Pearson)", y = "Density", fill = NULL, color = NULL) +
  322. theme_bw(base_size = 6) +
  323. theme(panel.grid = element_blank(), legend.position = "bottom")
  324. ggsave(file.path(out_dir_d50_QC, "diff132_replicateDataDistribution.pdf"), ReplicateDist, width = 4, height = 4, device = cairo_pdf)
  325. ggsave(file.path(out_dir_d50_QC, "diff132_replicateDataDistributionDensity.pdf"), ReplicateDist2, width = 4, height = 4, device = cairo_pdf)
  326. # --- correlation matrix of samples
  327. rep_wide_df <- cleaned_df %>%
  328. dplyr::filter(neuron != "QC", !is.na(genotype)) %>%
  329. dplyr::group_by(Genes, day, neuron, genotype, replicate) %>%
  330. dplyr::summarise(quan = sum(quan), .groups = "drop") %>%
  331. dplyr::mutate(sample_id = paste(genotype, neuron, day, replicate, sep = "_")) %>%
  332. dplyr::arrange(day, neuron, genotype, replicate) %>%
  333. dplyr::select(Genes, sample_id, quan) %>%
  334. tidyr::pivot_wider(names_from = sample_id, values_from = quan)
  335. sample_order <- cleaned_df %>%
  336. dplyr::filter(neuron != "QC", !is.na(genotype)) %>%
  337. dplyr::distinct(day, neuron, genotype, replicate) %>%
  338. dplyr::arrange(day, neuron, genotype, replicate) %>%
  339. dplyr::mutate(sample_id = paste(genotype, neuron, day, replicate, sep = "_")) %>%
  340. dplyr::pull(sample_id)
  341. rep_corr_matrix <- cor(rep_wide_df[, sample_order], use = "pairwise.complete.obs")
  342. pdf(file.path(out_dir_d50_QC, "diff132_replicate_correlation_heatmap.pdf"), width = 12, height = 12)
  343. pheatmap::pheatmap(
  344. rep_corr_matrix,
  345. color = colorRampPalette(rev(RColorBrewer::brewer.pal(11, "RdYlBu")))(100),
  346. breaks = seq(0.5, 1, length.out = 101),
  347. cluster_rows = FALSE,
  348. cluster_cols = FALSE,
  349. cellheight = 4,
  350. cellwidth = 4,
  351. border_color = NA,
  352. fontsize = 4,
  353. main = "Sample-wise Pearson Correlation"
  354. )
  355. dev.off()
  356. # --- Within vs between replicate variance plots
  357. rep_variance_per_gene <- cleaned_df %>%
  358. dplyr::filter(neuron != "QC", genotype != "ctrl", !is.na(genotype)) %>%
  359. dplyr::group_by(Genes, day, neuron, genotype) %>%
  360. dplyr::filter(dplyr::n() == 3) %>%
  361. dplyr::summarise(var_within = var(quan, na.rm = TRUE), .groups = "drop") %>%
  362. dplyr::mutate(sample_id = paste(genotype, neuron, day, sep = "_"))
  363. sample_ids <- unique(rep_variance_per_gene$sample_id)
  364. sample_colors <- setNames(colorRampPalette(rev(RColorBrewer::brewer.pal(11, "RdYlBu")))(length(sample_ids)), sample_ids)
  365. rep_variance_scatter <- ggplot(rep_variance_per_gene, aes(x = Genes, y = log2(var_within), color = sample_id)) +
  366. geom_point(size = 0.1, alpha = 0.3) +
  367. scale_color_manual(values = sample_colors) +
  368. labs(x = "Proteins", y = "Within-replicate variance [log2]", color = NULL) +
  369. theme_bw(base_size = 6) +
  370. theme(
  371. panel.grid = element_blank(),
  372. axis.text.x = element_blank(),
  373. axis.ticks.x = element_blank(),
  374. legend.position = "right"
  375. )
  376. ggsave(file.path(out_dir_d50_QC, "diff132_within_replicate_variance_scatter.pdf"), rep_variance_scatter, width = 8, height = 4, device = cairo_pdf)
  377. ```
  378. # F-ratio variance analysis
  379. ```{r}
  380. # --- Step 4c: F-ratio approach per annotation
  381. # F-ratio = between-genotype variance / within-genotype variance
  382. # MSbetween: variance between genotypes (based on all replicates)
  383. # MSwithin: variance within genotypes (across replicates)
  384. # If F > 1: biological signal is larger than technical noise = real differences
  385. # If F < 1: technical noise is larger than biological signal = can't trust the differences
  386. # also have log2 implementation and then it is F > 0 as cuttoff since log2(1) = 0
  387. # --- Create replicate-level fold changes per organelle annotation
  388. rep_fc_organelle <- cleaned_df %>%
  389. dplyr::filter(neuron != "QC", !is.na(genotype)) %>%
  390. dplyr::group_by(Genes, day, neuron, genotype, replicate) %>%
  391. dplyr::summarise(quan = sum(quan), .groups = "drop") %>%
  392. # pivot to get ctrl and KO quantities side by side
  393. tidyr::pivot_wider(names_from = genotype, values_from = quan) %>%
  394. # reshape back to long, keeping ctrl as reference
  395. tidyr::pivot_longer(cols = -c(Genes, day, neuron, replicate, ctrl), names_to = "genotype", values_to = "ko_quan") %>%
  396. dplyr::rename(ctrl_quan = ctrl) %>%
  397. dplyr::filter(!is.na(ko_quan), !is.na(ctrl_quan)) %>%
  398. # calculate log2 fold change vs ctrl
  399. dplyr::mutate(log2fc = log2(ko_quan / ctrl_quan)) %>%
  400. # add subcellular localization annotation
  401. dplyr::left_join(long_subcell_df, by = "Genes") %>%
  402. dplyr::filter(!is.na(Localization))
  403. # --- Calculate F-ratio per gene per annotation
  404. f_ratio_per_gene <- rep_fc_organelle %>%
  405. dplyr::group_by(Localization, day, neuron, genotype, Genes) %>%
  406. dplyr::filter(dplyr::n() == 3) %>%
  407. dplyr::ungroup() %>%
  408. dplyr::group_by(Localization, day, neuron, Genes) %>%
  409. dplyr::summarise(
  410. ms_within = mean(tapply(log2fc, genotype, var, na.rm = TRUE), na.rm = TRUE),
  411. ms_between = var(log2fc, na.rm = TRUE),
  412. f_ratio = ms_between / ms_within,
  413. log2_f_ratio = log2(ms_between / ms_within),
  414. .groups = "drop"
  415. ) %>%
  416. dplyr::filter(is.finite(f_ratio), is.finite(log2_f_ratio))
  417. # --- Summarize F-ratio per annotation x day x neuron
  418. f_ratio_df <- f_ratio_per_gene %>%
  419. dplyr::group_by(Localization, day, neuron) %>%
  420. dplyr::summarise(
  421. mean_f = mean(f_ratio, na.rm = TRUE),
  422. sd_f = sd(f_ratio, na.rm = TRUE),
  423. se_f = sd_f / sqrt(dplyr::n()),
  424. mean_log2f = mean(log2_f_ratio, na.rm = TRUE),
  425. sd_log2f = sd(log2_f_ratio, na.rm = TRUE),
  426. se_log2f = sd_log2f / sqrt(dplyr::n()),
  427. .groups = "drop"
  428. )
  429. write.csv(f_ratio_per_gene, file.path(out_dir_d50_QC, "diff132_f_ratio_per_gene_sourcedata.csv"), row.names = FALSE)
  430. write.csv(f_ratio_df, file.path(out_dir_d50_QC, "diff132_f_ratio_summary_sourcedata.csv"), row.names = FALSE)
  431. # --- Plot F-ratio (linear)
  432. f_ratio_plot <- ggplot(f_ratio_df, aes(x = Localization, y = mean_f, color = neuron, fill = neuron, group = neuron)) +
  433. geom_ribbon(aes(ymin = mean_f - se_f, ymax = mean_f + se_f), alpha = 0.2, color = NA) +
  434. geom_line() +
  435. geom_point(size = 0) +
  436. geom_hline(yintercept = 1, linetype = "dashed", color = "firebrick") +
  437. scale_color_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  438. scale_fill_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  439. facet_wrap(~ day, ncol = 2) +
  440. labs(x = NULL, y = "F-ratio (between / within genotype variance)", color = NULL, fill = NULL,
  441. caption = "F > 1: biological signal > technical noise\nF < 1: technical noise > biological signal") +
  442. theme_bw(base_size = 6) +
  443. theme(panel.grid = element_blank(), axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5),
  444. legend.position = "bottom", plot.caption = element_text(hjust = 0))
  445. # --- Plot F-ratio (log2)
  446. f_ratio_log2_plot <- ggplot(f_ratio_df, aes(x = Localization, y = mean_log2f, color = neuron, fill = neuron, group = neuron)) +
  447. geom_ribbon(aes(ymin = mean_log2f - se_log2f, ymax = mean_log2f + se_log2f), alpha = 0.2, color = NA) +
  448. geom_line() +
  449. geom_point(size = 0) +
  450. geom_hline(yintercept = 0, linetype = "dashed", color = "firebrick") +
  451. scale_color_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  452. scale_fill_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  453. facet_wrap(~ day, ncol = 2) +
  454. labs(x = NULL, y = "log2 F-ratio (between / within genotype variance)", color = NULL, fill = NULL,
  455. caption = "F > 0: biological signal > technical noise\nF < 0: technical noise > biological signal") +
  456. theme_bw(base_size = 6) +
  457. theme(panel.grid = element_blank(), axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5),
  458. legend.position = "bottom", plot.caption = element_text(hjust = 0))
  459. ggsave(file.path(out_dir_d50_QC, "diff132_f_ratio_variance.pdf"), f_ratio_plot, width = 4, height = 2, device = cairo_pdf)
  460. ggsave(file.path(out_dir_d50_QC, "diff132_f_ratio_variance_log2.pdf"), f_ratio_log2_plot, width = 4, height = 2, device = cairo_pdf)
  461. ```
  462. # PCA new
  463. ```{r}
  464. # =============================================================================
  465. # PCA plots
  466. # =============================================================================
  467. # --- Load data
  468. lsd_pro_phen <- readr::read_csv("/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/tmpneurogit/proteome/lsd_pro_phen.csv", show_col_types = FALSE)
  469. .quan_raw <- readr::read_csv("/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/tmpneurogit/proteome/lsd_pro_quan_na.csv", show_col_types = FALSE)
  470. lsd_pro_quan_na <- as.matrix(.quan_raw[, -1]); rownames(lsd_pro_quan_na) <- .quan_raw[[1]]; rm(.quan_raw)
  471. # --- Align phenotype to matrix column order
  472. phen_ord <- lsd_pro_phen[match(colnames(lsd_pro_quan_na), lsd_pro_phen$sample), ]
  473. is_qc <- is.na(phen_ord$genotype)
  474. # --- PCA: all samples
  475. mat_all <- lsd_pro_quan_na[complete.cases(lsd_pro_quan_na), ]
  476. pca_all <- prcomp(t(mat_all), scale. = TRUE)
  477. var_all <- round(100 * pca_all$sdev^2 / sum(pca_all$sdev^2), 1)
  478. pc_df <- data.frame(PC1 = pca_all$x[, 1], PC2 = pca_all$x[, 2],
  479. genotype = ifelse(is_qc, "", phen_ord$genotype),
  480. neuron = ifelse(is_qc, "QC", phen_ord$neuron),
  481. day = factor(phen_ord$day))
  482. # --- PCA: noQC
  483. mat_noqc <- lsd_pro_quan_na[, !is_qc]; mat_noqc <- mat_noqc[complete.cases(mat_noqc), ]
  484. pca_noqc <- prcomp(t(mat_noqc), scale. = TRUE)
  485. var_noqc <- round(100 * pca_noqc$sdev^2 / sum(pca_noqc$sdev^2), 1)
  486. phen_noqc <- phen_ord[!is_qc, ]
  487. pc_df_noqc <- data.frame(PC1 = pca_noqc$x[, 1], PC2 = pca_noqc$x[, 2],
  488. genotype = phen_noqc$genotype, neuron = phen_noqc$neuron, day = factor(phen_noqc$day))
  489. # --- Palettes
  490. n_geno_nqc <- length(unique(pc_df_noqc$genotype))
  491. n_days <- length(unique(pc_df$day))
  492. n_days_nqc <- length(unique(pc_df_noqc$day))
  493. geno_cols <- colorRampPalette(RColorBrewer::brewer.pal(11, "RdYlBu"))(length(unique(pc_df$genotype)))
  494. geno_cols_nqc <- colorRampPalette(RColorBrewer::brewer.pal(11, "RdYlBu"))(n_geno_nqc)
  495. day_cols <- colorRampPalette(RColorBrewer::brewer.pal(11, "RdYlBu"))(n_days)
  496. day_cols_nqc <- colorRampPalette(RColorBrewer::brewer.pal(11, "RdYlBu"))(n_days_nqc)
  497. neuron_cols <- c("iDA" = "#A50026", "iN" = "#313695", "QC" = "grey50")
  498. lab_x <- paste0("PC1 (", var_all[1], "% variance)"); lab_y <- paste0("PC2 (", var_all[2], "% variance)")
  499. lab_xn <- paste0("PC1 (", var_noqc[1], "% variance)"); lab_yn <- paste0("PC2 (", var_noqc[2], "% variance)")
  500. bt <- theme_minimal() + theme(panel.border = element_rect(color = "black", fill = NA, linewidth = 1))
  501. # --- All-sample plots
  502. d50plot1 <- ggplot(pc_df, aes(x = PC1, y = PC2, color = genotype)) +
  503. geom_point(size = 3, alpha = 0.8) +
  504. geom_text_repel(data = dplyr::distinct(pc_df, genotype, .keep_all = TRUE), aes(label = genotype), size = 2, box.padding = 0.5, point.padding = 0.5, max.overlaps = 200) +
  505. scale_color_manual(values = geno_cols) +
  506. labs(title = "PCA of Gene Intensities Colored by Genotype", x = lab_x, y = lab_y) +
  507. coord_fixed(ratio = 1.75) + bt
  508. d50plot2 <- ggplot(pc_df, aes(x = PC1, y = PC2, color = neuron)) +
  509. geom_point(size = 3, alpha = 0.8) +
  510. stat_ellipse(aes(fill = neuron), geom = "polygon", alpha = 0.2, level = 0.95) +
  511. scale_color_manual(values = neuron_cols) + scale_fill_manual(values = neuron_cols) +
  512. labs(title = "PCA of Gene Intensities Colored by Neuron Type", x = lab_x, y = lab_y) +
  513. coord_fixed(ratio = 1) + bt
  514. d50plot3 <- ggplot(pc_df, aes(x = PC1, y = PC2, color = day)) +
  515. geom_point(size = 3, alpha = 0.8) +
  516. stat_ellipse(aes(fill = day), geom = "polygon", alpha = 0.2, level = 0.95) +
  517. scale_color_manual(values = day_cols) + scale_fill_manual(values = day_cols) +
  518. labs(title = "PCA of Gene Intensities Colored by Day of Differentiation", x = lab_x, y = lab_y) +
  519. coord_fixed(ratio = 1) + bt
  520. # --- noQC plots
  521. d50plot1_noQC <- ggplot(pc_df_noqc, aes(x = PC1, y = PC2, color = genotype, alpha = day)) +
  522. geom_point(size = 3) +
  523. scale_alpha_manual(values = setNames(seq(0.4, 1, length.out = n_days_nqc), sort(unique(pc_df_noqc$day)))) +
  524. geom_text_repel(data = dplyr::distinct(pc_df_noqc, genotype, .keep_all = TRUE), aes(label = genotype), color = "black", size = 6, box.padding = 0.5, point.padding = 0.5, max.overlaps = 1000) +
  525. scale_color_manual(values = geno_cols_nqc) +
  526. labs(title = "PCA of Gene Intensities Colored by Genotype", x = lab_xn, y = lab_yn) +
  527. coord_fixed(ratio = 0.5) + bt + theme(legend.position = "bottom")
  528. d50plot2_noQC <- ggplot(pc_df_noqc, aes(x = PC1, y = PC2, color = neuron)) +
  529. geom_point(size = 3, alpha = 0.8) +
  530. stat_ellipse(aes(fill = neuron), geom = "polygon", alpha = 0.2, level = 0.998) +
  531. scale_color_manual(values = neuron_cols) + scale_fill_manual(values = neuron_cols) +
  532. labs(title = "PCA of Gene Intensities Colored by Neuron Type", x = lab_xn, y = lab_yn) +
  533. coord_fixed(ratio = 0.5) + bt
  534. d50plot3_noQC <- ggplot(pc_df_noqc, aes(x = PC1, y = PC2, color = day)) +
  535. geom_point(size = 3, alpha = 0.8) +
  536. stat_ellipse(aes(fill = day), geom = "polygon", alpha = 0.2, level = 0.95) +
  537. scale_color_manual(values = day_cols_nqc) + scale_fill_manual(values = day_cols_nqc) +
  538. labs(title = "PCA of Gene Intensities Colored by Day of Differentiation", x = lab_xn, y = lab_yn) +
  539. coord_fixed(ratio = 1) + bt
  540. # --- Display
  541. d50plot1; d50plot2; d50plot3
  542. d50plot1_noQC; d50plot2_noQC; d50plot3_noQC
  543. # --- Save
  544. ggsave(file.path(out_dir_d50_QC, "diff132_d50_PCA_byGenotype.pdf"), d50plot1, width = 15, height = 15, units = "cm", dpi = 600)
  545. ggsave(file.path(out_dir_d50_QC, "diff132_d50_PCA_byCelltype.pdf"), d50plot2, width = 15, height = 15, units = "cm", dpi = 600)
  546. ggsave(file.path(out_dir_d50_QC, "diff132_d50_PCA_byDay.pdf"), d50plot3, width = 15, height = 15, units = "cm", dpi = 600)
  547. ggsave(file.path(out_dir_d50_QC, "diff132_d50_noQC_PCA_byGenotype.pdf"), d50plot1_noQC, width = 15, height = 15, units = "cm", dpi = 600)
  548. ggsave(file.path(out_dir_d50_QC, "diff132_d50_noQC_PCA_byCelltype.pdf"), d50plot2_noQC, width = 15, height = 15, units = "cm", dpi = 600)
  549. ggsave(file.path(out_dir_d50_QC, "diff132_d50_noQC_PCA_byDay.pdf"), d50plot3_noQC, width = 15, height = 15, units = "cm", dpi = 600)
  550. # --- export source data csvs
  551. write.csv(pc_df, file.path(out_dir_d50_QC, "diff132_d50_PCA_allsamples_sourcedata.csv"), row.names = FALSE)
  552. write.csv(pc_df_noqc, file.path(out_dir_d50_QC, "diff132_d50_PCA_noQC_sourcedata.csv"), row.names = FALSE)
  553. ```
  554. # QC plot: Protein ID per sample x neuron
  555. ``` {r}
  556. # --------------------------------------------------- #
  557. # QC plot - Protein ID per sample x neuron
  558. # --------------------------------------------------- #
  559. n_genotypes_noQC <- n_geno_nqc
  560. n_neurons_noQC <- length(unique(pc_df_noqc$neuron))
  561. n_days_noQC <- n_days_nqc
  562. # --- Step 1. Average number of ProteinIDs per type on neuron
  563. gene_counts_neuron <- cleaned_df %>%
  564. dplyr::group_by(neuron) %>%
  565. dplyr::summarise(quantified_genes = n_distinct(Genes), .groups = "drop")
  566. neuronquant <- ggplot(gene_counts_neuron, aes(x = neuron, y = quantified_genes, fill = neuron)) +
  567. geom_bar(stat = "identity", width = 0.6) +
  568. geom_text(aes(label = quantified_genes), vjust = -0.5, size = 3) +
  569. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80", "QC" = "grey30")) +
  570. labs(
  571. title = "Total Number of Quantified ProteinIDs per Neuron Type",
  572. y = "ProteinIDs", x = "Neuron"
  573. ) +
  574. theme_bw() +
  575. theme(
  576. legend.position = "none",
  577. panel.grid = element_blank()
  578. )
  579. neuronquant
  580. # --- Step 2. plot protein IDs vs per genotype / neuron type ----
  581. genotype_colors_noQC_2 <- colorRampPalette(brewer.pal(11, "RdYlBu"))(2*n_genotypes_noQC)
  582. gene_seq_data <- diff132_d50_PCA_df %>%
  583. dplyr::mutate(
  584. genotype = ifelse(is.na(genotype) & neuron == "QC", "QC", genotype),
  585. replicate = ifelse(genotype == "QC", NA, replicate)
  586. ) %>%
  587. dplyr::filter(genotype != "QC") %>% # <-- this line removes QC
  588. dplyr::group_by(Genes, genotype, neuron) %>%
  589. dplyr::summarise(
  590. total_sequences = sum(N.Sequences, na.rm = TRUE),
  591. .groups = "drop"
  592. ) %>%
  593. dplyr::mutate(group = interaction(genotype, neuron))
  594. peptide_ID_QC <- ggplot(gene_seq_data, aes(x = Genes, y = log2(total_sequences), color = group)) +
  595. geom_point(alpha = 0.7) +
  596. scale_color_manual(values = genotype_colors_noQC_2) +
  597. labs(
  598. title = "Number N.Sequences vs IDs by Genotype × Neuron",
  599. x = "Genes",
  600. y = "Log2 Total N.Sequences"
  601. ) +
  602. theme_bw() +
  603. theme(
  604. axis.text.x = element_blank(),
  605. axis.ticks.x = element_blank(),
  606. axis.text.y = element_text(angle = 0, hjust = 1),
  607. panel.grid = element_blank(),
  608. legend.position = "bottom"
  609. )
  610. peptide_ID_QC
  611. # --- Step 3. plot average protein IDs per genotype / neuron type ----
  612. # First, compute quantified genes per replicate (if replicate column exists)
  613. # calculate the average for all QC IDs
  614. gene_counts_per_rep <- cleaned_df %>%
  615. dplyr::mutate(genotype = ifelse(is.na(genotype) & neuron == "QC", "QC", genotype),
  616. replicate = ifelse(genotype == "QC", NA, replicate)) %>%
  617. dplyr::group_by(genotype, neuron, replicate) %>%
  618. dplyr::summarise(quantified_genes = n_distinct(Genes), .groups = "drop")
  619. # Separate QC and non-QC
  620. qc_summary <- gene_counts_per_rep %>%
  621. dplyr::filter(genotype == "QC") %>%
  622. dplyr::group_by(genotype, neuron) %>%
  623. dplyr::summarise(quantified_genes = sum(quantified_genes), .groups = "drop")
  624. non_qc_summary <- gene_counts_per_rep %>%
  625. dplyr::filter(genotype != "QC") %>%
  626. dplyr::group_by(genotype, neuron) %>%
  627. dplyr::summarise(quantified_genes = mean(quantified_genes), .groups = "drop")
  628. # Combine
  629. gene_counts_summary <- dplyr::bind_rows(qc_summary, non_qc_summary)
  630. # Then, calculate mean and SD across replicates per genotype × neuron group
  631. gene_counts_genotype <- gene_counts_summary %>%
  632. dplyr::group_by(genotype, neuron) %>%
  633. dplyr::summarise(
  634. total_genes = sum(quantified_genes),
  635. sd_genes = ifelse(n() == 1, 0, sd(quantified_genes)),
  636. .groups = "drop"
  637. ) %>%
  638. dplyr::mutate(group = interaction(genotype, neuron))
  639. # add "QC" in neuron column for color matching
  640. gene_counts_genotype <- gene_counts_genotype %>%
  641. dplyr::mutate(genotype = replace_na(genotype, "QC"))
  642. # Plot with SD error bars
  643. ProteinID_genotype_neurontype <- ggplot(gene_counts_genotype, aes(x = group, y = total_genes, fill = neuron)) +
  644. geom_bar(stat = "identity", width = 0.6) +
  645. geom_errorbar(aes(ymin = total_genes - sd_genes, ymax = total_genes + sd_genes), width = 0.2) +
  646. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80", "QC" = "grey30")) +
  647. labs(
  648. title = "Number of Quantified Genes per Genotype and Neuron Type",
  649. y = "Number of ProteinIDs", x = "Genotype × Neuron"
  650. ) +
  651. theme_bw() +
  652. theme(
  653. axis.text.x = element_text(angle = 90, hjust = 1),
  654. panel.grid = element_blank()
  655. )
  656. ProteinID_genotype_neurontype
  657. # Compute average IDs per neuron type
  658. avg_IDs_by_neuron <- gene_counts_summary %>%
  659. dplyr::group_by(neuron) %>%
  660. dplyr::summarise(mean_IDs = mean(quantified_genes, na.rm = TRUE)) %>%
  661. arrange(desc(mean_IDs))
  662. # Plot the average per neuron type
  663. ProteinID_avg_neuron <- ggplot(avg_IDs_by_neuron, aes(x = neuron, y = mean_IDs, fill = neuron)) +
  664. geom_col(width = 0.6) +
  665. theme_bw() +
  666. theme(
  667. panel.grid = element_blank(),
  668. legend.position = "none",) +
  669. labs(title = "Avg. Protein IDs per Neuron Type", y = "Mean # Protein IDs", x = "") +
  670. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80", "QC" = "grey30")) +
  671. geom_text(aes(label = round(mean_IDs, 0)), vjust = -0.5, size = 3)
  672. # --- Step 4. Save QC plots as PDFs in homedir of the project
  673. ggsave(file.path(out_dir_d50_QC,"diff132_d50_QC_neuronquant.pdf"), neuronquant, width = 8, height = 10, units = "cm", dpi=600)
  674. ggsave(file.path(out_dir_d50_QC,"diff132_d50_QC_ProteinID_genotype_neurontypee.pdf"), ProteinID_genotype_neurontype, width =15, height = 15, units = "cm", dpi=600)
  675. ggsave(file.path(out_dir_d50_QC, "diff132_d50_QC_ProteinID_avg_by_neuron.pdf"),ProteinID_avg_neuron, width =12 , height = 6, units = "cm", dpi = 600)
  676. # --- Step 5. Combine Plots
  677. # save three main plots for QC under each other
  678. # Force consistent theme and remove legends
  679. p1 <- d50plot2 + theme(legend.position = "bottom")
  680. p2 <- d50plot3 + theme(legend.position = "bottom")
  681. p3 <- ProteinID_genotype_neurontype + theme(legend.position = "none")
  682. p4 <- peptide_ID_QC + theme(legend.position = "none")
  683. # Combine layout: (p1 | p2) / p3
  684. combined_plot_QC <- ((p1 | p2) / p3 / p4) +
  685. plot_layout(heights = c(1, 1,0.5)) &
  686. theme(plot.margin = margin(0.01, 10, 0.01, 10)) # Top, Right, Bottom, Left padding
  687. # save three main plots for QC under each other
  688. # Force consistent theme and remove legends
  689. p5 <- d50plot2_noQC + theme(legend.position = "bottom")
  690. p6 <- d50plot3_noQC + theme(legend.position = "bottom")
  691. p7 <- d50plot1_noQC + theme(legend.position = "bottom")
  692. # Combine layout: (p5 | p6) / p7
  693. combined_plot <- ((p5 | p6) / p7) +
  694. plot_layout(heights = c(1, 1)) &
  695. theme(plot.margin = margin(0.01, 10, 0.01, 10)) # Top, Right, Bottom, Left padding
  696. # View or save
  697. combined_plot
  698. combined_plot_QC
  699. # Save as PDF
  700. ggsave(file.path(out_dir_d50_QC,"diff132_d50_QC_combined_plot.pdf"), combined_plot_QC, width = 12, height = 12, device = cairo_pdf)
  701. ggsave(file.path(out_dir_d50_QC,"diff132_d50_combined_plot.pdf"), combined_plot, width = 12, height = 12, device = cairo_pdf)
  702. ```
  703. # QC plot: RSD plot
  704. ```{r}
  705. # --------------------------------------------------- #
  706. # QC plot - RSD plot
  707. # --------------------------------------------------- #
  708. # Load the RData file containing rsd_q1_to_q3
  709. load("/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/datasets/DataFormated.RData")
  710. # Preprocess the RSD data:
  711. # - Filter only Day 30 and Day 50
  712. # - Calculate the IQR (Q3 - Q1)
  713. # - Define lower and upper bounds as median ± half IQR
  714. rsd_filtered <- rsd_q1_to_q3 %>%
  715. dplyr::mutate(
  716. Day = sub("^(\\d+)\\..*", "\\1", day_neuron_geno),
  717. Neuron = sub("^\\d+\\.(\\w+)\\..*", "\\1", day_neuron_geno),
  718. Genotype = sub("^\\d+\\.\\w+\\.(.*)", "\\1", day_neuron_geno)
  719. ) %>%
  720. dplyr::filter(Day %in% c("30", "50")) %>%
  721. dplyr::mutate(IQR = Q3 - Q1, lower = medianRSD - IQR / 2, upper = medianRSD + IQR / 2)
  722. # --- 1. Function to generate a single plot for one neuron type and one day
  723. make_day_plot <- function(df, neuron_type, day_label) {
  724. # Subset the data for the chosen neuron type and day
  725. df_day <- df %>% dplyr::filter(Neuron == neuron_type, Day == day_label)
  726. ggplot(df_day, aes(x = Genotype)) +
  727. # Semi-transparent ribbon showing the IQR band
  728. geom_ribbon(aes(ymin = lower, ymax = upper, group = 1),
  729. fill = "grey70", alpha = 0.3) +
  730. # Dashed lines for lower and upper bounds
  731. geom_line(aes(y = lower, group = 1),
  732. color = "dodgerblue2", size = 0.8, linetype = "dashed") +
  733. geom_line(aes(y = upper, group = 1),
  734. color = "dodgerblue2", size = 0.8, linetype = "dashed") +
  735. # Solid line for the median RSD
  736. geom_line(aes(y = medianRSD, group = 1),
  737. color = "dodgerblue3", size = 1.2, linetype = "solid") +
  738. # Points marking the median values
  739. geom_point(aes(y = medianRSD),
  740. color = "dodgerblue3", size = 2) +
  741. # Titles and axis labels
  742. labs(title = paste("Day", day_label),
  743. x = "Genotype", y = "Median RSD ± IQR") +
  744. # Theme adjustments for cleaner look
  745. theme_bw(base_size = 6) +
  746. theme(
  747. panel.grid = element_blank(),
  748. axis.text.x = element_text(angle = 90, hjust = 0.5)
  749. )
  750. }
  751. # --- 2. Function to combine Day 30 and Day 50 plots for one neuron type
  752. # - Day 30 panel narrower (rel_widths = c(1,4)) since fewer genotypes
  753. plot_qc_rsd <- function(neuron_type, data) {
  754. p30 <- make_day_plot(data, neuron_type, "30")
  755. p50 <- make_day_plot(data, neuron_type, "50")
  756. plot_grid(p30, p50, nrow = 1, rel_widths = c(1, 4))
  757. }
  758. # --- 3. Generate row plots for iN and iDA
  759. plot_iN <- plot_qc_rsd("iN", rsd_filtered)
  760. plot_iDA <- plot_qc_rsd("iDA", rsd_filtered)
  761. # --- 4. Combine iN (top) and iDA (bottom) into a single figure
  762. combined_plot_QC <- plot_grid(plot_iN, plot_iDA, ncol = 1, labels = c("iN", "iDA"))
  763. # --- 5. Save the combined QC plot as a PDF
  764. ggsave(file.path(out_dir_d50_QC, "diff132_d50_QC_combined_plot.pdf"), combined_plot_QC, width = 12, height = 4, device = cairo_pdf)
  765. # --- 6. Export soucredata
  766. write.csv(rsd_filtered, file.path(out_dir_d50_QC, "diff132_d50_QC_combined_plot_sourcedata.csv"), row.names = FALSE)
  767. ```
  768. # =================================================
  769. # Module 2: Summarized Data & Subcellular Analysis
  770. # =================================================
  771. # Read fold-change data (day 30 & day 50)
  772. use the full dataset for plotting volcano and heatmaps etc
  773. each measured protein has mean ctrl, mean ko and fold-change and p/q value associated with it
  774. probably makes sense to have it in tibble for easy data manipulation
  775. ```{r}
  776. # --------------------------------------------------- #
  777. # iN/iDA: Day 50 Proteomics Heatmap Pipeline
  778. # --------------------------------------------------- #
  779. library(ComplexHeatmap)
  780. detach("package:ComplexHeatmap", unload = TRUE, character.only = TRUE)
  781. library(pheatmap)
  782. library(RColorBrewer) # For color palettes
  783. # --- Step 1. Load and Inspect Data
  784. # Define the path to the input CSV file
  785. input_LSD_file <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/datasets/lsd_fc_pv.csv"
  786. # Read the CSV file into a data frame
  787. diff132_d50_df <- read.csv(input_LSD_file, header = TRUE, sep = ",", stringsAsFactors = FALSE)
  788. # Inspect the structure of the data
  789. str(diff132_d50_df)
  790. # --- Step 2. Data Cleaning
  791. # Remove unnecessary columns: Protein.Group (1), Protein.Name (2), First.Protein.Description (4)
  792. diff132_d50_df <- diff132_d50_df[, -c(1, 2, 4,14,15,16,17,18,19,21)]
  793. # Filter out rows with missing or empty 'Genes' entries
  794. clean_d50_df <- diff132_d50_df %>%
  795. dplyr::filter(!is.na(Genes) & Genes != "") %>%
  796. dplyr::mutate(Genes = sub(";.*", "", Genes)) # Keep first gene symbol only
  797. # --- Step 3. Prepare Data for Heatmap
  798. # Create a unique identifier for each sample by combining genotype and neuron
  799. clean_d50_df<- clean_d50_df %>%
  800. dplyr::mutate(sample_id = paste(genotype, neuron, sep = "_"))
  801. # Pivot the data to have genes as rows and samples as columns
  802. heatmap_d50_data <- clean_d50_df %>%
  803. select(Genes, sample_id, fold_change) %>%
  804. pivot_wider(
  805. names_from = sample_id,
  806. values_from = fold_change,
  807. values_fill = list(fold_change = 0),
  808. values_fn = list(fold_change = sum)
  809. )
  810. # Convert the data frame to a matrix and set row names to gene names
  811. rownames_df <- heatmap_d50_data$Genes # Extract rownames before dropping the column
  812. heatmap_matrix_d50 <- as.matrix(heatmap_d50_data[, -1])
  813. rownames(heatmap_matrix_d50) <- rownames_df
  814. heatmap_matrix_d50[is.na(heatmap_matrix_d50)] <- 0
  815. # Remove rows/columns with zero variance
  816. heatmap_matrix_d50 <- heatmap_matrix_d50[apply(heatmap_matrix_d50, 1, var) != 0, ]
  817. heatmap_matrix_d50 <- heatmap_matrix_d50[, apply(heatmap_matrix_d50, 2, var) != 0]
  818. # --- Step 4. Create Annotations for Heatmap & save as csv
  819. # Extract sample information for annotations
  820. sample_d50_info <- clean_d50_df %>%
  821. dplyr::select(sample_id, genotype, neuron) %>%
  822. distinct() %>%
  823. column_to_rownames("sample_id")
  824. df_day30 <- clean_d50_df %>% dplyr::filter(day == 30)
  825. df_day50 <- clean_d50_df %>% dplyr::filter(day == 50)
  826. write.csv(df_day30, file = file.path(out_dir_d50_dataframes, "df_day30_heatmap.csv"),row.names = FALSE)
  827. write.csv(df_day50, file = file.path(out_dir_d50_dataframes, "df_day50_heatmap.csv"),row.names = FALSE)
  828. # --- Step 5. Define a function to generate heatmaps
  829. generate_heatmap <- function(df, day_label) {
  830. heatmap_data <- df %>%
  831. dplyr::mutate(sample_id = paste(genotype, neuron, sep = "_")) %>%
  832. dplyr::select(Genes, sample_id, fold_change) %>%
  833. pivot_wider(
  834. names_from = sample_id,
  835. values_from = fold_change,
  836. values_fill = list(fold_change = 0),
  837. values_fn = list(fold_change = sum)
  838. )
  839. # Extract matrix and rownames
  840. rownames_mat <- heatmap_data$Genes
  841. mat <- as.matrix(heatmap_data[, -1])
  842. rownames(mat) <- rownames_mat
  843. storage.mode(mat) <- "numeric"
  844. mat[!is.finite(mat)] <- 0
  845. mat <- mat[apply(mat, 1, var) != 0, , drop = FALSE]
  846. mat <- mat[, apply(mat, 2, var) != 0, drop = FALSE]
  847. # Annotations
  848. sample_info <- df %>%
  849. dplyr::mutate(sample_id = paste(genotype, neuron, sep = "_")) %>%
  850. dplyr::select(sample_id, genotype, neuron) %>%
  851. distinct() %>%
  852. column_to_rownames("sample_id")
  853. annotation_col <- data.frame(
  854. Genotype = sample_info$genotype,
  855. Neuron = sample_info$neuron
  856. )
  857. rownames(annotation_col) <- rownames(sample_info)
  858. genotypes <- unique(sample_info$genotype)
  859. neurons <- unique(sample_info$neuron)
  860. ann_colors <- list(
  861. Genotype = setNames(colorRampPalette(brewer.pal(9, "Set1"))(length(genotypes)), genotypes),
  862. Neuron = setNames(colorRampPalette(c("grey30", "grey60", "grey80"))(length(neurons)), neurons)
  863. )
  864. pheatmap::pheatmap(
  865. mat = mat,
  866. scale = "row",
  867. color = colorRampPalette(rev(brewer.pal(n = 11, name = "RdYlBu")))(100),
  868. annotation_col = annotation_col,
  869. annotation_colors = ann_colors,
  870. breaks = seq(-4, 4, length.out = 101),
  871. cluster_rows = TRUE,
  872. cluster_cols = TRUE,
  873. show_rownames = FALSE,
  874. show_colnames = TRUE,
  875. use_raster = FALSE,
  876. angle_col = "90",
  877. main = paste("Day", day_label, "iN / iDA Data"),
  878. fontsize = 10,
  879. border_color = NA
  880. )
  881. }
  882. heatmap_d30 <- generate_heatmap(df_day30, 30)
  883. heatmap_d50 <- generate_heatmap(df_day50, 50)
  884. # --- Step 6. save single heatmaps
  885. # Save Day 30 heatmap
  886. pdf(file.path(out_dir_d50_heatmaps, "heatmap_day30.pdf"), width = 3, height = 6)
  887. grid::grid.draw(heatmap_d30$gtable)
  888. dev.off()
  889. # Save Day 50 heatmap
  890. pdf(file.path(out_dir_d50_heatmaps, "heatmap_day50.pdf"), width = 6, height = 6)
  891. grid::grid.draw(heatmap_d50$gtable)
  892. dev.off()
  893. # --- Step 7. Combine Plots & save as PDF
  894. # have to use gridExtra, since do not use ggplot objects, but pheatmap() returns a grid object, not a ggplot. To arrange multiple pheatmap() plots, use gridExtra::grid.arrange() instead:
  895. library(gridExtra)
  896. # Define the output file path
  897. output_file_50 <- file.path(out_dir_d50_heatmaps, "diff132_d30_d50_combined_heatmap.pdf")
  898. # Save the heatmap to a PDF file
  899. pdf(output_file_50, width = 40 / 2.54, height = 30 / 2.54)
  900. gridExtra::grid.arrange(
  901. heatmap_d30$gtable,
  902. heatmap_d50$gtable,
  903. ncol = 2,
  904. widths = c(1, 3) # Adjust for 25% / 75% layout
  905. )
  906. dev.off()
  907. ```
  908. # Add subcell annotation & plot basic heatmaps
  909. add subcell annotation to df and save
  910. sub sub-matrix for d30 and d50
  911. export heatmap as csv file
  912. add all colums of interest: FC, mean intensity, q-value
  913. ```{r}
  914. # --------------------------------------------------- #
  915. # Subcell annotation to df for QC and Organelle Violin Plots
  916. # add annotations do df and split by day and save as .csv files
  917. # --------------------------------------------------- #
  918. # --- Step 0: Define contaminant list
  919. contaminant_genes <- c(
  920. "KRT8", "KRT19", "KRT9", "KRT10", "KRT2", "KRT18", "KRT1", "KRT5",
  921. "COL10A1", "COL13A1", "COL14A1", "COL18A1", "COL1A1", "COL1A2",
  922. "COL26A1", "COL2A1", "COL4A1", "COL4A2", "COL5A1", "COL6A1", "COL6A2"
  923. )
  924. # --- Step 1. Transform columns
  925. clean_d50_transformed_df <- clean_d50_df %>%
  926. dplyr::mutate(
  927. log2_fold_change = ifelse(fold_change > 0, log2(fold_change), NA),
  928. neg_log10_q_value = ifelse(q_value > 0, -log10(q_value), NA),
  929. neg_log10_p_value = ifelse(p_value > 0, -log10(p_value), NA)
  930. ) %>%
  931. dplyr::filter(!Genes %in% contaminant_genes)
  932. # --- Step 2: Load and prepare annotation matrix (only once)
  933. cols_to_read <- c(1,2,3,4,5,6,7,9,10,12,15,16,17,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33,34)
  934. subcell_df <- read.csv("/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/tmpneurogit/proteome/SubCellAnnotation.csv", stringsAsFactors = FALSE)[, cols_to_read]
  935. subcell_df[] <- lapply(subcell_df, as.character)
  936. long_subcell_df <- subcell_df %>%
  937. tidyr::pivot_longer(
  938. cols = everything(),
  939. names_to = "Localization",
  940. values_to = "Genes"
  941. ) %>%
  942. dplyr::filter(!is.na(Genes) & Genes != "") %>%
  943. dplyr::mutate(Genes = trimws(Genes)) %>%
  944. distinct()
  945. binary_matrix <- long_subcell_df %>%
  946. dplyr::mutate(value = TRUE) %>%
  947. pivot_wider(
  948. names_from = Localization,
  949. values_from = value,
  950. values_fill = list(value = FALSE)
  951. )
  952. # --- Step 3: Function to process each day independently
  953. process_day_data <- function(df, day_value, save_path) {
  954. df_day <- df %>% dplyr::filter(day == day_value)
  955. complete_data <- df_day %>%
  956. dplyr::select(Genes, sample_id, log2_fold_change, mean_ctrl, mean_ko, neg_log10_q_value, neg_log10_p_value) %>%
  957. pivot_wider(
  958. names_from = sample_id,
  959. values_from = c(log2_fold_change, mean_ctrl, mean_ko, neg_log10_q_value, neg_log10_p_value),
  960. names_glue = "{sample_id}_{.value}",
  961. values_fill = list(
  962. log2_fold_change = 0, mean_ctrl = 0, mean_ko = 0,
  963. neg_log10_q_value = 0, neg_log10_p_value = 0
  964. ),
  965. values_fn = list(
  966. log2_fold_change = sum, mean_ctrl = mean, mean_ko = mean,
  967. neg_log10_q_value = mean, neg_log10_p_value = mean
  968. )
  969. )
  970. complete_data_annotated <- complete_data %>%
  971. left_join(binary_matrix, by = "Genes")
  972. complete_data_annotated[is.na(complete_data_annotated)] <- 0
  973. # Save to CSV
  974. write.csv(complete_data_annotated, save_path, row.names = FALSE)
  975. return(complete_data_annotated)
  976. }
  977. # --- Step 4: Run for Day 30 and Day 50
  978. complete_data_annotated_day30 <- process_day_data(
  979. clean_d50_transformed_df, 30,
  980. file.path(out_dir_d50_dataframes, "diff132_complete_annotated_day30.csv")
  981. )
  982. complete_data_annotated_day50 <- process_day_data(
  983. clean_d50_transformed_df, 50,
  984. file.path(out_dir_d50_dataframes, "diff132_complete_annotated_day50.csv")
  985. )
  986. # --- Step 5: Create log2FC_d50_annotated for downstream analysis (ternary, correlation, etc.)
  987. # Reshape to have simple KO_neuron column names with log2fc values
  988. log2fc_cols <- grep("_log2_fold_change$", colnames(complete_data_annotated_day50), value = TRUE)
  989. annotation_cols <- colnames(binary_matrix)[-1] # exclude "Genes"
  990. log2FC_d50_annotated <- complete_data_annotated_day50 %>%
  991. dplyr::select(Genes, all_of(log2fc_cols), all_of(annotation_cols))
  992. # Rename columns: remove "_log2_fold_change" suffix
  993. colnames(log2FC_d50_annotated) <- gsub("_log2_fold_change$", "", colnames(log2FC_d50_annotated))
  994. # --- Step 6: count sig values
  995. diff132_log2fc_df <- clean_d50_df %>%
  996. dplyr::mutate(log2fc = ifelse(fold_change > 0, log2(fold_change), NA))
  997. diff132_log2fc_df %>%
  998. dplyr::group_by(sample_id, day, neuron, genotype) %>%
  999. dplyr::summarise(
  1000. total = n(),
  1001. n_na = sum(is.na(p_value)),
  1002. p001 = sum(p_value < 0.001, na.rm = TRUE),
  1003. p01 = sum(p_value < 0.01 & p_value >= 0.001, na.rm = TRUE),
  1004. p05 = sum(p_value < 0.05 & p_value >= 0.01, na.rm = TRUE),
  1005. ns = sum(p_value >= 0.05, na.rm = TRUE),
  1006. q001 = sum(q_value < 0.001, na.rm = TRUE),
  1007. q01 = sum(q_value < 0.01 & q_value >= 0.001, na.rm = TRUE),
  1008. q05 = sum(q_value < 0.05 & q_value >= 0.01, na.rm = TRUE),
  1009. q_ns = sum(q_value >= 0.05, na.rm = TRUE),
  1010. .groups = "drop"
  1011. ) %>%
  1012. arrange(day, neuron, genotype) %>%
  1013. print(n = Inf)
  1014. # save as csv
  1015. write.csv(diff132_log2fc_df, file.path(out_dir_d50_dataframes, "diff132_log2fc_stats.csv"), row.names = FALSE)
  1016. # Join diff132_log2fc_df with annotations
  1017. log2fc_annotated <- diff132_log2fc_df %>%
  1018. inner_join(long_subcell_df, by = "Genes")
  1019. # Filter significant (p < 0.05), summarize mean log2fc per annotation x sample_id
  1020. sig_annotation_heatmap <- log2fc_annotated %>%
  1021. dplyr::filter(p_value < 0.05) %>%
  1022. dplyr::group_by(Localization, sample_id, day, neuron) %>%
  1023. dplyr::summarise(mean_log2fc = mean(log2fc, na.rm = TRUE), .groups = "drop")
  1024. # Pivot to matrix: rows = annotations, columns = sample_id
  1025. sig_heatmap_matrix <- sig_annotation_heatmap %>%
  1026. dplyr::select(Localization, sample_id, mean_log2fc) %>%
  1027. pivot_wider(names_from = sample_id, values_from = mean_log2fc) %>%
  1028. column_to_rownames("Localization") %>%
  1029. as.matrix()
  1030. # Order columns by day and neuron
  1031. sample_order <- sig_annotation_heatmap %>%
  1032. distinct(sample_id, day, neuron) %>%
  1033. arrange(day, neuron, sample_id) %>%
  1034. pull(sample_id)
  1035. sig_heatmap_matrix <- sig_annotation_heatmap %>%
  1036. dplyr::select(Localization, sample_id, mean_log2fc) %>%
  1037. pivot_wider(names_from = sample_id, values_from = mean_log2fc, values_fn = mean) %>%
  1038. column_to_rownames("Localization") %>%
  1039. as.matrix()
  1040. # Plot
  1041. neuron_colors <- c("iN" = "grey50", "iDA" = "grey80")
  1042. # reorder columns
  1043. sig_heatmap_matrix <- sig_heatmap_matrix[, sample_order]
  1044. # filter for only day 50
  1045. sig_annotation_heatmap <- sig_annotation_heatmap %>%
  1046. dplyr::filter(day == 50)
  1047. # --- Column annotation
  1048. col_ann <- sig_annotation_heatmap %>%
  1049. dplyr::distinct(sample_id, day, neuron) %>%
  1050. dplyr::arrange(day, neuron, sample_id) %>%
  1051. tibble::column_to_rownames("sample_id") %>%
  1052. dplyr::mutate(day = factor(day))
  1053. # --- Color scale
  1054. lim <- max(abs(sig_heatmap_matrix), na.rm = TRUE)
  1055. breaks <- seq(-lim, lim, length.out = 101)
  1056. colors <- colorRampPalette(rev(brewer.pal(11,"RdYlBu")))(100)
  1057. # --- Plot
  1058. ann_colors <- list(neuron = c("iN" = "grey50", "iDA" = "grey80"))
  1059. sig_heatmap_matrix[is.na(sig_heatmap_matrix)] <- 0
  1060. ph_sig <- pheatmap::pheatmap(sig_heatmap_matrix,
  1061. color = colors,
  1062. breaks = breaks,
  1063. annotation_col = col_ann,
  1064. annotation_colors = ann_colors,
  1065. cluster_rows = TRUE,
  1066. cluster_cols = TRUE,
  1067. clustering_method = "ward.D2",
  1068. border_color = NA,
  1069. cellheight = 8,
  1070. cellwidth = 8,
  1071. fontsize = 6,
  1072. angle_col = 90,
  1073. na_col = "grey90",
  1074. main = "Mean log2FC by Localization (p < 0.05)")
  1075. ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "diff132_d50_subcell_heatmap_pval.pdf"), ph_sig, width = 14, height = 10, device = grDevices::cairo_pdf)
  1076. dev.off()
  1077. ```
  1078. # Euler plots for overlap annotation
  1079. ```{r}
  1080. # --------------------------------------------------- #
  1081. # Euler plots of Subcellular Annotation Overlap
  1082. # --------------------------------------------------- #
  1083. # Load eulerr if not already loaded
  1084. library(eulerr)
  1085. # -- 1.1: Euler Diagram for Top 10 Vesicular Categories --
  1086. # Define terms to exclude from the top-N plot (housekeeping or broad compartments)
  1087. excluded_terms <- c("Nucleus", "Mito", "MitoIMS", "MitoMatrix", "MitoMIM", "MitoMOM", "OXPHOS", "mtComplexI",
  1088. "Golgi", "Lysosome", "EndoLyso", "ER", "Cytoplasm", "Autophagy", "NeuroDev",
  1089. "EndoDetailLoac", "RecyclingEndosome", "EarlyEndosome", "Autophagy_det")
  1090. # Select all included terms
  1091. included_terms <- setdiff(names(subcell_df), excluded_terms)
  1092. # Create named list of gene sets from annotations
  1093. gene_sets <- subcell_df %>%
  1094. dplyr::select(all_of(included_terms)) %>%
  1095. tidyr::pivot_longer(cols = everything(), names_to = "Category", values_to = "Gene") %>%
  1096. dplyr::filter(!is.na(Gene) & Gene != "") %>%
  1097. dplyr::mutate(Gene = trimws(Gene)) %>%
  1098. dplyr::group_by(Category) %>%
  1099. dplyr::summarise(GeneSet = list(unique(Gene)), .groups = "drop") %>%
  1100. deframe()
  1101. # Limit to top 10 categories by set size
  1102. top_terms <- names(sort(sapply(gene_sets, length), decreasing = TRUE))[1:10]
  1103. gene_sets_top <- gene_sets[top_terms]
  1104. gene_sets_top <- gene_sets_top[!is.na(names(gene_sets_top)) & names(gene_sets_top) != ""]
  1105. # Fit and plot Euler diagram for top vesicular categories
  1106. fit <- euler(gene_sets_top)
  1107. pdf(file.path(out_dir_d50_euler, "subcell_eulerdiagram_top10.pdf"), width = 8, height = 8)
  1108. plot(fit,
  1109. fills = list(fill = "dodgerblue3", alpha = 0.2),
  1110. labels = list(font = 1),
  1111. main = "Euler Diagram: Top 10 Subcellular Annotations")
  1112. dev.off()
  1113. # -- 3.2: Euler Diagram for Endosomal/Lysosomal Categories --
  1114. endo_terms <- c("Endo_iN_curated_Hundley", "Lysosome", "EarlyEndosome", "RecyclingEndosome")
  1115. # Create named list of gene sets
  1116. gene_sets_endo <- subcell_df %>%
  1117. dplyr::select(all_of(endo_terms)) %>%
  1118. tidyr::pivot_longer(cols = everything(), names_to = "Category", values_to = "Gene") %>%
  1119. dplyr::filter(!is.na(Gene) & Gene != "") %>%
  1120. dplyr::mutate(Gene = trimws(Gene)) %>%
  1121. dplyr::group_by(Category) %>%
  1122. dplyr::summarise(GeneSet = list(unique(Gene)), .groups = "drop") %>%
  1123. deframe()
  1124. # Fit and plot Euler diagram for endosomal/lysosomal terms
  1125. fit_endo <- euler(gene_sets_endo)
  1126. pdf(file.path(out_dir_d50_euler, "subcell_eulerdiagram_endo-lysosome.pdf"), width = 8, height = 8)
  1127. plot(fit_endo,
  1128. fills = list(fill = "firebrick", alpha = 0.5),
  1129. labels = list(font = 1),
  1130. main = "Endosomal/Lysosomal Annotations")
  1131. dev.off()
  1132. # -- 3.3: Euler Diagram for Synaptic Categories --
  1133. synaptic_terms <- c("SynGO", "SynapseSVs", "Presynaptic", "Postsynaptic",
  1134. "SVfusion", "Svexocytosis", "Svendocytosis")
  1135. # Create named list of gene sets
  1136. gene_sets_synaptic <- subcell_df %>%
  1137. dplyr::select(all_of(synaptic_terms)) %>%
  1138. tidyr::pivot_longer(cols = everything(), names_to = "Category", values_to = "Gene") %>%
  1139. dplyr::filter(!is.na(Gene) & Gene != "") %>%
  1140. dplyr::mutate(Gene = trimws(Gene)) %>%
  1141. dplyr::group_by(Category) %>%
  1142. dplyr::summarise(GeneSet = list(unique(Gene)), .groups = "drop") %>%
  1143. deframe()
  1144. # Fit and plot Euler diagram for synaptic annotations
  1145. fit_syn <- euler(gene_sets_synaptic)
  1146. pdf(file.path(out_dir_d50_euler, "subcell_eulerdiagram_synaptic.pdf"), width = 8, height = 8)
  1147. plot(fit_syn,
  1148. fills = list(fill = "dodgerblue3", alpha = 0.5),
  1149. labels = list(font = 1),
  1150. main = "Synaptic Annotations")
  1151. dev.off()
  1152. ```
  1153. # LSD protein abundance across genotypes
  1154. plot bar graph of abundance of all LSDs found in the dataset
  1155. plot a bar graph of all LSD-KO matched pairs in the dataset (iN and iDA)
  1156. plot a heatmap of all LSD proteins of interest across the dataset -> check that these are KOs
  1157. ```{r}
  1158. # --------------------------------------------------- #
  1159. # Plot LSD protein abundance in their respective KOs
  1160. # barplots
  1161. # --------------------------------------------------- #
  1162. # ---- Define LSD Gene List ----
  1163. LSDgenes <- c(
  1164. "AGA", "ARSA", "ARSB", "ASAH1", "ATP13A2", "CLN3", "CLN5", "CLN6", "CLN8", "CTNS", "CTSA", "CTSD", "CTSF", "DNAJC5", "FUCA", "GAA", "GALC", "GALNS", "GBA1", "GLA", "GLB1", "GM2A", "GNPTAB", "GNPTG", "GNS", "GRN", "GUSB", "HEXA", "HEXB", "HGSNAT", "HYAL1", "IDS", "IDUA", "KCTD7", "LAMP2", "LIPA", "MAN2B1", "MANBA", "MCOLN1", "MFSD8", "NAGA", "NAGLU", "NEU1", "NPC1", "NPC2", "PPT1", "PSAP", "SCARB2", "SGSH", "SLC17A5", "SMPD1", "SUMF1", "TPP1"
  1165. )
  1166. # ---- Prepare Long Format Data ----
  1167. # Filter for LSD genes and pivot to long format
  1168. df_clean_heatmap <- clean_d50_transformed_df %>%
  1169. dplyr::filter(Genes %in% LSDgenes) %>%
  1170. dplyr::select(Genes, genotype, neuron, day, mean_ctrl, mean_ko) %>%
  1171. tidyr::pivot_longer(cols = c(mean_ctrl, mean_ko), names_to = "condition", values_to = "value") %>%
  1172. dplyr::mutate(day = as.integer(day))
  1173. # ---- Filter for Matching Gene-Genotype Combinations at Day 50 ----
  1174. # Only keep rows where the gene matches the genotype (e.g., GBA1 expression in GBA1 KO)
  1175. plot_df <- df_clean_heatmap %>%
  1176. dplyr::filter(Genes == genotype, day == 50)
  1177. # ---- Replace NAs with 0 ----
  1178. plot_df <- plot_df %>%
  1179. dplyr::mutate(value = ifelse(is.na(value), 0, value))
  1180. # ---- Create DataFrame for Annotating Undetected Proteins ----
  1181. label_df <- plot_df %>%
  1182. dplyr::group_by(Genes) %>%
  1183. dplyr::mutate(max_y = max(value, na.rm = TRUE)) %>%
  1184. dplyr::filter(condition == "mean_ko" & value == 0) %>%
  1185. dplyr::mutate(
  1186. label = "not detected",
  1187. y_label = max_y * 0.5, # Position label at 50% of max height
  1188. x_shift = as.numeric(factor(neuron)) + 0.2 # Shift x position slightly for clarity
  1189. )
  1190. # ---- Plot Bar Plot with Facets per Gene ----
  1191. LSDgene_Abundance <- ggplot(plot_df, aes(x = neuron, y = value, fill = condition)) +
  1192. geom_bar(stat = "summary", fun = mean, position = position_dodge(width = 0.6), width = 0.6) +
  1193. geom_text( # Add labels for undetected mean_ko
  1194. data = label_df,
  1195. aes(x = x_shift, y = y_label, label = label),
  1196. inherit.aes = FALSE,
  1197. angle = 90,
  1198. size = 3,
  1199. color = "red"
  1200. ) +
  1201. facet_wrap(~Genes, scales = "free_y") +
  1202. scale_fill_manual(values = c("mean_ctrl" = "grey70", "mean_ko" = "dodgerblue3")) +
  1203. scale_color_manual(values = c("mean_ctrl" = "grey40", "mean_ko" = "dodgerblue4")) +
  1204. labs(
  1205. title = "LSD Protein Abundance (ctrl vs KO)",
  1206. x = "Neuron Type",
  1207. y = "Abundance (raw)",
  1208. fill = "Condition"
  1209. ) +
  1210. theme_bw() +
  1211. theme(
  1212. legend.position = "bottom",
  1213. panel.grid = element_blank()
  1214. )
  1215. # ---- Plot LSD gene abundance in control background ----
  1216. plot_df_ctrl <- df_clean_heatmap %>%
  1217. dplyr::filter(condition == "mean_ctrl", day == 50) %>%
  1218. distinct(Genes, neuron, value) %>%
  1219. dplyr::mutate(value = ifelse(is.na(value), 0, value))
  1220. LSDgene_Abundance_ctrl <- ggplot(plot_df_ctrl, aes(x = reorder(Genes, -value), y = value, fill = neuron)) +
  1221. geom_bar(stat = "summary", fun = mean, position = position_dodge(width = 0.7), width = 0.7) +
  1222. scale_fill_manual(values = c("iN" = "grey70", "iDA" = "dodgerblue3")) +
  1223. labs(title = "LSD Protein Abundance in Control Neurons", x = "LSD Gene", y = "Abundance (raw)", fill = "Neuron Type") +
  1224. theme_bw() +
  1225. theme(legend.position = "bottom",
  1226. panel.grid = element_blank(),
  1227. axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5, size = 6))
  1228. # ---- Display Plot ----
  1229. LSDgene_Abundance
  1230. LSDgene_Abundance_ctrl
  1231. # ---- Save Plot and Data ----
  1232. ggsave(file.path(out_dir_d50_QC,"diff132_LSDgene_Abundance.pdf"), LSDgene_Abundance, width = 12, height = 12, device = cairo_pdf)
  1233. ggsave(file.path(out_dir_d50_QC,"diff132_LSDgeneAll_Abundance_inCtrl.pdf"), LSDgene_Abundance_ctrl, width = 4, height = 4, device = cairo_pdf)
  1234. write.csv(df_clean_heatmap, file = file.path(out_dir_d50_QC, "LSD_sorted.csv"), row.names = FALSE)
  1235. # --------------------------------------------------- #
  1236. # Plot LSD protein abundance in their respective KOs
  1237. # heatmaps
  1238. # --------------------------------------------------- #
  1239. # --- 0. Define expected genotype list (from your LSD gene list)
  1240. expected_genes <- c( "ASAH1", "ATP13A2", "CLN3", "CLN5", "CLN6", "CLN8", "CTSD", "CTSF",
  1241. "DNAJC5", "GAA", "GBA1", "GRN", "HEXA", "HEXB", "LIPA", "MCOLN1",
  1242. "MFSD8", "NPC1", "NPC2", "PPT1", "PSAP", "SMPD1", "TPP1")
  1243. # --- 1. Construct all combinations of neuron × condition × gene
  1244. all_rows <- expand.grid(
  1245. neuron = c("iN", "iDA"),
  1246. condition = c("mean_ctrl", "mean_ko"),
  1247. Genes = expected_genes,
  1248. stringsAsFactors = FALSE
  1249. ) %>%
  1250. dplyr::mutate(RowID = paste(condition, neuron, sep = "_"))
  1251. # --- 2. Get actual values from df_clean_heatmap
  1252. values_df <- df_clean_heatmap %>%
  1253. dplyr::filter(day == 50, Genes == genotype) %>%
  1254. dplyr::group_by(Genes, neuron, condition) %>%
  1255. dplyr::summarise(value = mean(value, na.rm = TRUE), .groups = "drop") %>%
  1256. dplyr::mutate(RowID = paste(condition, neuron, sep = "_"))
  1257. # --- 4. Build matrix
  1258. heatmap_matrix_abundance <- all_rows %>%
  1259. left_join(values_df, by = c("Genes","neuron","condition","RowID")) %>%
  1260. dplyr::select(Genes, RowID, value) %>%
  1261. pivot_wider(names_from = Genes, values_from = value) %>%
  1262. column_to_rownames("RowID") %>%
  1263. as.matrix()
  1264. # --- 5. Clip high values for better contrast at low end
  1265. cap_value <- 10000000
  1266. heatmap_matrix_capped <- pmin(heatmap_matrix_abundance, cap_value)
  1267. # --- 6. Save CSV copies (raw and capped)
  1268. readr::write_csv(
  1269. heatmap_matrix_abundance %>% as.data.frame() %>% rownames_to_column("RowID"),
  1270. file.path(out_dir_d50_QC, "LSDgene_Abundance_Heatmap_matrix_raw.csv")
  1271. )
  1272. readr::write_csv(
  1273. heatmap_matrix_capped %>% as.data.frame() %>% rownames_to_column("RowID"),
  1274. file.path(out_dir_d50_QC, "LSDgene_Abundance_Heatmap_matrix_cilpped.csv")
  1275. )
  1276. # --- 7. Plot heatmap with clipped range
  1277. min_val <- suppressWarnings(min(heatmap_matrix_capped, na.rm = TRUE))
  1278. bk <- seq(min_val, cap_value, length.out = 100)
  1279. pdf(file.path(out_dir_d50_QC, "LSDgene_Abundance_Heatmap_RdYlBu_capped.pdf"),
  1280. width = 8, height = 6)
  1281. pheatmap::pheatmap(
  1282. heatmap_matrix_capped,
  1283. color = colorRampPalette(rev(brewer.pal(11, "RdYlBu")))(length(bk) - 1),
  1284. breaks = bk,
  1285. cluster_rows = FALSE,
  1286. cluster_cols = FALSE,
  1287. na_col = "grey70",
  1288. cellheight = 15,
  1289. cellwidth = 15,
  1290. border_color = "black",
  1291. legend_labels = NA,
  1292. main = "LSD Protein Abundance (Control vs KO) at Day 50 - capped"
  1293. )
  1294. dev.off()
  1295. # --- 8. Implement log2 converted heatmap here
  1296. heatmap_matrix_log2 <- all_rows %>%
  1297. left_join(values_df, by = c("Genes","neuron","condition","RowID")) %>%
  1298. dplyr::select(Genes, RowID, value) %>%
  1299. pivot_wider(names_from = Genes, values_from = value) %>%
  1300. column_to_rownames("RowID") %>%
  1301. # log2 transform (add pseudocount to avoid log2(0))
  1302. apply(2, function(x) log2(x + 1)) %>%
  1303. as.matrix()
  1304. # --- 9. Save CSV
  1305. readr::write_csv(heatmap_matrix_log2 %>% as.data.frame() %>% rownames_to_column("RowID"), file.path(out_dir_d50_QC,"LSDgene_Abundance_log2_matrix.csv"))
  1306. # --- 10. Determine breaks for color scale
  1307. min_val_log2 <- suppressWarnings(min(heatmap_matrix_log2, na.rm = TRUE))
  1308. max_val_log2 <- suppressWarnings(max(heatmap_matrix_log2, na.rm = TRUE))
  1309. bk_log2 <- seq(min_val_log2, max_val_log2, length.out = 100)
  1310. # --- 11. Plot heatmap log2 scaled
  1311. pdf(file.path(out_dir_d50_QC, "LSDgene_Abundance_Heatmap_RdYlBu_log2.pdf"),
  1312. width = 8, height = 6)
  1313. pheatmap::pheatmap(
  1314. heatmap_matrix_log2,
  1315. color = colorRampPalette(rev(brewer.pal(11, "RdYlBu")))(length(bk_log2) - 1),
  1316. breaks = bk_log2,
  1317. cluster_rows = FALSE,
  1318. cluster_cols = FALSE,
  1319. na_col = "grey70",
  1320. cellheight = 15,
  1321. cellwidth = 15,
  1322. border_color = "black",
  1323. main = "LSD Protein Abundance (Control vs KO) at Day 50 - log2 transformed"
  1324. )
  1325. dev.off()
  1326. dev.off()
  1327. ```
  1328. # Sorted heatmap of neuro-related annotations
  1329. ```{r}
  1330. # --------------------------------------------------- #
  1331. # Heatmap of neuro-related annotations
  1332. # plot by genotype and split for iN and iDA
  1333. # --------------------------------------------------- #
  1334. loc_ann <- c("Lysosome","RecyclingEndosome", "Endo_iN_curated_Hundley","EarlyEndosome",
  1335. "SynapseSVs","Presynaptic","Postsynaptic",
  1336. "SVfusion","Svendocytosis","Svexocytosis","vATPase")
  1337. # --- 1. helper function for one neuron type
  1338. make_ann_heatmap <- function(df, neuron_type, loc_ann, out_file) {
  1339. long_df <- df %>%
  1340. tidyr::pivot_longer(
  1341. cols = matches(paste0("_", neuron_type, "_log2_fold_change$")),
  1342. names_to = "Condition",
  1343. values_to = "log2FC"
  1344. ) %>%
  1345. dplyr::mutate(genotype = sub("_.*", "", Condition)) %>%
  1346. tidyr::pivot_longer(cols = all_of(loc_ann),
  1347. names_to = "Annotation", values_to = "is_annotated") %>%
  1348. dplyr::filter(is_annotated == 1)
  1349. ann_matrix <- long_df %>%
  1350. dplyr::group_by(genotype, Annotation) %>%
  1351. dplyr::summarise(mean_log2FC = mean(log2FC, na.rm = TRUE), .groups = "drop") %>%
  1352. pivot_wider(names_from = Annotation, values_from = mean_log2FC) %>%
  1353. column_to_rownames("genotype")
  1354. ann_mat <- as.matrix(ann_matrix)
  1355. ann_mat <- ann_mat[, loc_ann, drop = FALSE]
  1356. ann_mat[!is.finite(ann_mat)] <- NA
  1357. ann_mat <- ann_mat[rowSums(!is.na(ann_mat)) > 0, ]
  1358. # call heatmap function and save row order from pheatmap object
  1359. sorted_heatmap_iNiDA <- pheatmap::pheatmap(
  1360. mat = ann_mat,
  1361. scale = "row",
  1362. color = colorRampPalette(rev(brewer.pal(11, "RdYlBu")))(100),
  1363. cluster_rows = TRUE,
  1364. cluster_cols = FALSE,
  1365. show_rownames = TRUE,
  1366. show_colnames = TRUE,
  1367. border_color = NA,
  1368. fontsize = 6,
  1369. cellheight = 8,
  1370. cellwidth = 8,
  1371. main = paste("Mean log2FC per Genotype - ", neuron_type),
  1372. filename = out_file
  1373. )
  1374. # return row order (genotype names)
  1375. return(rownames(ann_mat)[sorted_heatmap_iNiDA$tree_row$order])
  1376. }
  1377. # --- 2. Call the function and plot heatmaps for both iN and iDA
  1378. # make_ann_heatmap(
  1379. # complete_data_annotated_day50,
  1380. # "iN", loc_ann,
  1381. # file.path(out_dir_d50_ascd_avg, "heatmap_d50_iN_rowZ_byGenotype.pdf")
  1382. # )
  1383. #
  1384. # make_ann_heatmap(
  1385. # complete_data_annotated_day50,
  1386. # "iDA", loc_ann,
  1387. # file.path(out_dir_d50_ascd_avg, "heatmap_d50_iDA_rowZ_byGenotype.pdf")
  1388. # )
  1389. row_order_iN <- make_ann_heatmap(
  1390. complete_data_annotated_day50,
  1391. "iN", loc_ann,
  1392. file.path(out_dir_d50_ascd_avg, "heatmap_d50_iN_rowZ_byGenotype.pdf")
  1393. )
  1394. row_order_iDA <- make_ann_heatmap(
  1395. complete_data_annotated_day50,
  1396. "iDA", loc_ann,
  1397. file.path(out_dir_d50_ascd_avg, "heatmap_d50_iDA_rowZ_byGenotype.pdf")
  1398. )
  1399. # Sig-filtered heatmaps (p and q versions)
  1400. # --------------------------------------------------- #
  1401. # Same as make_ann_heatmap but masks non-significant
  1402. # log2FC values to NA before computing means
  1403. # sig_type: "p" or "q"
  1404. # --------------------------------------------------- #
  1405. make_ann_heatmap_sig <- function(df, neuron_type, loc_ann, out_file,
  1406. sig_type = "q", sig_threshold = 0.05) {
  1407. val_suffix <- if (sig_type == "q") "neg_log10_q_value" else "neg_log10_p_value"
  1408. neg_log10_thresh <- -log10(sig_threshold)
  1409. long_fc <- df %>%
  1410. tidyr::pivot_longer(cols = matches(paste0("_", neuron_type, "_log2_fold_change$")),
  1411. names_to = "Condition", values_to = "log2FC") %>%
  1412. dplyr::mutate(genotype = sub("_.*", "", Condition))
  1413. long_sig <- df %>%
  1414. tidyr::pivot_longer(cols = matches(paste0("_", neuron_type, "_", val_suffix, "$")),
  1415. names_to = "Condition_sig", values_to = "sig_val") %>%
  1416. dplyr::mutate(genotype = sub("_.*", "", Condition_sig)) %>%
  1417. dplyr::select(Genes, genotype, sig_val)
  1418. long_df <- long_fc %>%
  1419. left_join(long_sig, by = c("Genes", "genotype")) %>%
  1420. dplyr::mutate(log2FC = ifelse(!is.na(sig_val) & sig_val >= neg_log10_thresh, log2FC, NA)) %>%
  1421. tidyr::pivot_longer(cols = all_of(loc_ann), names_to = "Annotation", values_to = "is_annotated") %>%
  1422. dplyr::filter(is_annotated == 1)
  1423. ann_matrix <- long_df %>%
  1424. dplyr::group_by(genotype, Annotation) %>%
  1425. dplyr::summarise(mean_log2FC = mean(log2FC, na.rm = TRUE), .groups = "drop") %>%
  1426. pivot_wider(names_from = Annotation, values_from = mean_log2FC) %>%
  1427. column_to_rownames("genotype")
  1428. ann_mat <- as.matrix(ann_matrix)
  1429. ann_mat <- ann_mat[, loc_ann, drop = FALSE]
  1430. ann_mat[!is.finite(ann_mat)] <- NA
  1431. ann_mat <- ann_mat[rowSums(!is.na(ann_mat)) > 0, , drop = FALSE]
  1432. ph <- pheatmap::pheatmap(
  1433. mat = ann_mat,
  1434. scale = "row",
  1435. color = colorRampPalette(rev(brewer.pal(11, "RdYlBu")))(100),
  1436. cluster_rows = TRUE,
  1437. cluster_cols = FALSE,
  1438. show_rownames = TRUE,
  1439. show_colnames = TRUE,
  1440. border_color = NA,
  1441. fontsize = 6,
  1442. cellheight = 8,
  1443. cellwidth = 8,
  1444. na_col = "grey90",
  1445. main = paste0("Mean log2FC (", sig_type, " < ", sig_threshold, ") - ", neuron_type),
  1446. filename = out_file
  1447. )
  1448. readr::write_csv(tibble::rownames_to_column(as.data.frame(ann_mat), "genotype"),
  1449. sub("\\.pdf$", "_sourcedata.csv", out_file))
  1450. return(rownames(ann_mat)[ph$tree_row$order])
  1451. }
  1452. # --- p-value filtered
  1453. make_ann_heatmap_sig(complete_data_annotated_day50, "iN", loc_ann,
  1454. file.path(out_dir_d50_ascd_avg, "heatmap_d50_iN_rowZ_byGenotype_psig.pdf"), sig_type = "p")
  1455. make_ann_heatmap_sig(complete_data_annotated_day50, "iDA", loc_ann,
  1456. file.path(out_dir_d50_ascd_avg, "heatmap_d50_iDA_rowZ_byGenotype_psig.pdf"), sig_type = "p")
  1457. # --- q-value filtered
  1458. make_ann_heatmap_sig(complete_data_annotated_day50, "iN", loc_ann,
  1459. file.path(out_dir_d50_ascd_avg, "heatmap_d50_iN_rowZ_byGenotype_qsig.pdf"), sig_type = "q")
  1460. make_ann_heatmap_sig(complete_data_annotated_day50, "iDA", loc_ann,
  1461. file.path(out_dir_d50_ascd_avg, "heatmap_d50_iDA_rowZ_byGenotype_qsig.pdf"), sig_type = "q")
  1462. ```
  1463. # Pvalue heatmaps for annotation KO vs Control
  1464. Keep same clustering / order from mean Log2FC heatmap
  1465. ```{r}
  1466. # --------------------------------------------------- #
  1467. # Heatmap of neuro-related annotations (p-values)
  1468. # Same layout as VId, but cells encode signed −log10(q)
  1469. # --------------------------------------------------- #
  1470. # --- 0. helper function for stats
  1471. combine_p_fisher <- function(p_vec) {
  1472. p_use <- pmin(pmax(p_vec, .Machine$double.xmin), 1)
  1473. stat <- -2 * sum(log(p_use))
  1474. pchisq(stat, df = 2 * length(p_use), lower.tail = FALSE)
  1475. }
  1476. # --- 1. P-value heatmap with fixed row order and CSV export
  1477. make_ann_heatmap_pvals <- function(df_day50, annot_wide, neuron_type, loc_ann, out_file,
  1478. out_csv_base = NULL, row_order = NULL) {
  1479. # 1.1 defaults
  1480. if (is.null(out_csv_base)) out_csv_base <- sub("\\.pdf$", "", out_file)
  1481. # 1.2 annotation flags in long form, keep only TRUE/1, deduplicate
  1482. ann_flags_long <- annot_wide %>%
  1483. dplyr::select(Genes, dplyr::all_of(loc_ann)) %>%
  1484. tidyr::pivot_longer(cols = dplyr::all_of(loc_ann),
  1485. names_to = "Annotation", values_to = "is_annotated") %>%
  1486. dplyr::mutate(is_annotated = is_annotated %in% c(TRUE, 1)) %>%
  1487. dplyr::filter(is_annotated) %>%
  1488. dplyr::distinct(Genes, Annotation)
  1489. # 1.3 restrict to neuron, join flags (many-to-many is expected)
  1490. df_neu <- df_day50 %>%
  1491. dplyr::filter(neuron == neuron_type) %>%
  1492. dplyr::inner_join(ann_flags_long, by = "Genes", relationship = "many-to-many")
  1493. # 1.4 combine p-values per genotype x annotation; sign by mean fold_change
  1494. comb_tbl <- df_neu %>%
  1495. dplyr::filter(is.finite(p_value)) %>%
  1496. dplyr::group_by(genotype, Annotation) %>%
  1497. dplyr::summarise(
  1498. n_genes = dplyr::n(),
  1499. mean_fc = mean(fold_change, na.rm = TRUE),
  1500. p_comb = if (dplyr::n() >= 3) combine_p_fisher(p_value) else NA_real_,
  1501. .groups = "drop"
  1502. ) %>%
  1503. dplyr::mutate(q_comb = p.adjust(p_comb, method = "BH")) %>%
  1504. dplyr::mutate(score = sign(replace_na(mean_fc, 0)) * (-log10(q_comb)))
  1505. # 1.5 wide matrices in requested column order
  1506. ann_matrix <- comb_tbl %>%
  1507. dplyr::select(genotype, Annotation, score, q_comb) %>%
  1508. tidyr::pivot_wider(names_from = Annotation, values_from = c(score, q_comb)) %>%
  1509. dplyr::arrange(genotype)
  1510. score_cols <- paste0("score_", loc_ann)
  1511. q_cols <- paste0("q_comb_", loc_ann)
  1512. missing_sc <- setdiff(score_cols, names(ann_matrix))
  1513. missing_q <- setdiff(q_cols, names(ann_matrix))
  1514. if (length(missing_sc)) ann_matrix[, missing_sc] <- NA_real_
  1515. if (length(missing_q)) ann_matrix[, missing_q] <- NA_real_
  1516. score_mat <- ann_matrix %>%
  1517. dplyr::select(dplyr::all_of(score_cols)) %>%
  1518. as.matrix()
  1519. rownames(score_mat) <- ann_matrix$genotype
  1520. colnames(score_mat) <- loc_ann
  1521. q_mat <- ann_matrix %>%
  1522. dplyr::select(dplyr::all_of(q_cols)) %>%
  1523. as.matrix()
  1524. rownames(q_mat) <- ann_matrix$genotype
  1525. colnames(q_mat) <- loc_ann
  1526. # 1.6 cap Inf from q=0
  1527. if (any(is.infinite(score_mat), na.rm = TRUE)) {
  1528. finite_max <- max(score_mat[is.finite(score_mat)], na.rm = TRUE)
  1529. score_mat[is.infinite(score_mat)] <- finite_max + 1
  1530. }
  1531. # 1.7 mask non-significant (q >= 0.05) or missing to NA
  1532. mask <- (q_mat >= 0.05) | is.na(q_mat)
  1533. score_mat[mask] <- NA
  1534. # 1.8 enforce external row order exactly; do not drop rows after masking
  1535. if (!is.null(row_order)) {
  1536. idx <- match(row_order, rownames(score_mat)) # preserves supplied order
  1537. idx <- idx[!is.na(idx)]
  1538. score_mat <- score_mat[idx, , drop = FALSE]
  1539. q_mat <- q_mat[idx, colnames(score_mat), drop = FALSE]
  1540. }
  1541. # 1.9 CSV exports
  1542. comb_tbl %>%
  1543. dplyr::arrange(genotype, Annotation) %>%
  1544. write.csv(file = paste0(out_csv_base, "_", neuron_type, "_summary_long.csv"),
  1545. row.names = FALSE, quote = FALSE)
  1546. tmp_scores <- cbind(genotype = rownames(score_mat), as.data.frame(score_mat))
  1547. write.csv(tmp_scores,
  1548. file = paste0(out_csv_base, "_", neuron_type, "_heatmap_scores.csv"),
  1549. row.names = FALSE, quote = FALSE)
  1550. tmp_q <- cbind(genotype = rownames(q_mat), as.data.frame(q_mat))
  1551. write.csv(tmp_q,
  1552. file = paste0(out_csv_base, "_", neuron_type, "_heatmap_qvals.csv"),
  1553. row.names = FALSE, quote = FALSE)
  1554. # 1.10 plot (no clustering to preserve order)
  1555. piyg_cols <- colorRampPalette(brewer.pal(11, "PiYG"))(201)
  1556. pheatmap::pheatmap(
  1557. mat = score_mat,
  1558. color = piyg_cols,
  1559. na_col = "grey70",
  1560. cluster_rows = FALSE,
  1561. cluster_cols = FALSE,
  1562. show_rownames = TRUE,
  1563. show_colnames = TRUE,
  1564. border_color = NA,
  1565. fontsize = 6,
  1566. cellheight = 8,
  1567. cellwidth = 8,
  1568. main = paste("Annotation p-value (signed -log10 q) -", neuron_type),
  1569. filename = out_file
  1570. )
  1571. invisible(list(summary_long = comb_tbl, score_mat = score_mat, q_mat = q_mat))
  1572. }
  1573. # --- 2. Use the row orders returned by make_ann_heatmap() above
  1574. make_ann_heatmap_pvals(
  1575. df_day50 = df_day50,
  1576. annot_wide = complete_data_annotated_day50,
  1577. neuron_type = "iN",
  1578. loc_ann = loc_ann,
  1579. out_file = file.path(out_dir_d50_heatmaps, "heatmap_d50_iN_pvals_byGenotype.pdf"),
  1580. out_csv_base = file.path(out_dir_d50_heatmaps, "heatmap_d50"),
  1581. row_order = row_order_iN
  1582. )
  1583. make_ann_heatmap_pvals(
  1584. df_day50 = df_day50,
  1585. annot_wide = complete_data_annotated_day50,
  1586. neuron_type = "iDA",
  1587. loc_ann = loc_ann,
  1588. out_file = file.path(out_dir_d50_heatmaps, "heatmap_d50_iDA_pvals_byGenotype.pdf"),
  1589. out_csv_base = file.path(out_dir_d50_heatmaps, "heatmap_d50"),
  1590. row_order = row_order_iDA
  1591. )
  1592. ```
  1593. # Stouffer's method correction
  1594. ```{r}
  1595. # Weighted Stouffer’s method (combine z scores with weights like √n or 1/SE). This balances smaller and larger sets.
  1596. ### Plot heatmaps of pvalues for annotation KO vs Control comparison. Keep same clustering / order from mean Log2FC heatmap in VId.
  1597. # --------------------------------------------------- #
  1598. # Heatmap of neuro-related annotations (p-values)
  1599. # Same layout as VId, but cells encode signed −log10(q)
  1600. # --------------------------------------------------- #
  1601. # --- 0. helper function for stats
  1602. combine_p_fisher <- function(p_vec) {
  1603. p_use <- pmin(pmax(p_vec, .Machine$double.xmin), 1)
  1604. stat <- -2 * sum(log(p_use))
  1605. pchisq(stat, df = 2 * length(p_use), lower.tail = FALSE)
  1606. }
  1607. # --- helper: weighted Stouffer’s method
  1608. combine_p_stouffer <- function(p_vec, w_vec = NULL) {
  1609. # clip to avoid Inf
  1610. p_use <- pmin(pmax(p_vec, .Machine$double.xmin), 1)
  1611. z_vec <- qnorm(p_use, lower.tail = FALSE)
  1612. if (is.null(w_vec)) {
  1613. w_vec <- rep(1, length(z_vec)) # equal weights if none supplied
  1614. }
  1615. z_comb <- sum(w_vec * z_vec, na.rm = TRUE) / sqrt(sum(w_vec^2, na.rm = TRUE))
  1616. pnorm(z_comb, lower.tail = FALSE)
  1617. }
  1618. # --- 1. P-value heatmap with fixed row order and CSV export
  1619. make_ann_heatmap_pvals <- function(df_day50, annot_wide, neuron_type, loc_ann, out_file,
  1620. out_csv_base = NULL, row_order = NULL) {
  1621. # 1.1 defaults
  1622. if (is.null(out_csv_base)) out_csv_base <- sub("\\.pdf$", "", out_file)
  1623. # 1.2 annotation flags in long form, keep only TRUE/1, deduplicate
  1624. ann_flags_long <- annot_wide %>%
  1625. dplyr::select(Genes, dplyr::all_of(loc_ann)) %>%
  1626. tidyr::pivot_longer(cols = dplyr::all_of(loc_ann),
  1627. names_to = "Annotation", values_to = "is_annotated") %>%
  1628. dplyr::mutate(is_annotated = is_annotated %in% c(TRUE, 1)) %>%
  1629. dplyr::filter(is_annotated) %>%
  1630. dplyr::distinct(Genes, Annotation)
  1631. # 1.3 restrict to neuron, join flags (many-to-many is expected)
  1632. df_neu <- df_day50 %>%
  1633. dplyr::filter(neuron == neuron_type) %>%
  1634. dplyr::inner_join(ann_flags_long, by = "Genes", relationship = "many-to-many")
  1635. # 1.4 combine p-values per genotype x annotation; sign by mean fold_change
  1636. comb_tbl <- df_neu %>%
  1637. dplyr::filter(is.finite(p_value)) %>%
  1638. dplyr::group_by(genotype, Annotation) %>%
  1639. dplyr::summarise(
  1640. n_genes = dplyr::n(),
  1641. mean_fc = mean(fold_change, na.rm = TRUE),
  1642. p_comb = if (n_genes >= 3) combine_p_stouffer(p_value, w_vec = sqrt(n_genes)) else NA_real_,
  1643. .groups = "drop"
  1644. ) %>%
  1645. dplyr::mutate(q_comb = p.adjust(p_comb, method = "BH")) %>%
  1646. dplyr::mutate(score = sign(replace_na(mean_fc, 0)) * (-log10(q_comb)))
  1647. # 1.5 wide matrices in requested column order
  1648. ann_matrix <- comb_tbl %>%
  1649. dplyr::select(genotype, Annotation, score, q_comb) %>%
  1650. tidyr::pivot_wider(names_from = Annotation, values_from = c(score, q_comb)) %>%
  1651. dplyr::arrange(genotype)
  1652. score_cols <- paste0("score_", loc_ann)
  1653. q_cols <- paste0("q_comb_", loc_ann)
  1654. missing_sc <- setdiff(score_cols, names(ann_matrix))
  1655. missing_q <- setdiff(q_cols, names(ann_matrix))
  1656. if (length(missing_sc)) ann_matrix[, missing_sc] <- NA_real_
  1657. if (length(missing_q)) ann_matrix[, missing_q] <- NA_real_
  1658. score_mat <- ann_matrix %>%
  1659. dplyr::select(dplyr::all_of(score_cols)) %>%
  1660. as.matrix()
  1661. rownames(score_mat) <- ann_matrix$genotype
  1662. colnames(score_mat) <- loc_ann
  1663. q_mat <- ann_matrix %>%
  1664. dplyr::select(dplyr::all_of(q_cols)) %>%
  1665. as.matrix()
  1666. rownames(q_mat) <- ann_matrix$genotype
  1667. colnames(q_mat) <- loc_ann
  1668. # 1.6 cap Inf from q=0
  1669. if (any(is.infinite(score_mat), na.rm = TRUE)) {
  1670. finite_max <- max(score_mat[is.finite(score_mat)], na.rm = TRUE)
  1671. score_mat[is.infinite(score_mat)] <- finite_max + 1
  1672. }
  1673. # 1.7 mask non-significant (q >= 0.05) or missing to NA
  1674. mask <- (q_mat >= 0.05) | is.na(q_mat)
  1675. score_mat[mask] <- NA
  1676. # 1.8 enforce external row order exactly; do not drop rows after masking
  1677. if (!is.null(row_order)) {
  1678. idx <- match(row_order, rownames(score_mat)) # preserves supplied order
  1679. idx <- idx[!is.na(idx)]
  1680. score_mat <- score_mat[idx, , drop = FALSE]
  1681. q_mat <- q_mat[idx, colnames(score_mat), drop = FALSE]
  1682. }
  1683. # 1.9 CSV exports
  1684. comb_tbl %>%
  1685. dplyr::arrange(genotype, Annotation) %>%
  1686. write.csv(file = paste0(out_csv_base, "_", neuron_type, "_summary_long_Stouffer.csv"),
  1687. row.names = FALSE, quote = FALSE)
  1688. tmp_scores <- cbind(genotype = rownames(score_mat), as.data.frame(score_mat))
  1689. write.csv(tmp_scores,
  1690. file = paste0(out_csv_base, "_", neuron_type, "_heatmap_scores_Stouffer.csv"),
  1691. row.names = FALSE, quote = FALSE)
  1692. tmp_q <- cbind(genotype = rownames(q_mat), as.data.frame(q_mat))
  1693. write.csv(tmp_q,
  1694. file = paste0(out_csv_base, "_", neuron_type, "_heatmap_qvals_Stouffer.csv"),
  1695. row.names = FALSE, quote = FALSE)
  1696. # 1.10 plot (no clustering to preserve order)
  1697. piyg_cols <- colorRampPalette(brewer.pal(11, "PiYG"))(201)
  1698. pheatmap::pheatmap(
  1699. mat = score_mat,
  1700. color = piyg_cols,
  1701. na_col = "grey70",
  1702. cluster_rows = FALSE,
  1703. cluster_cols = FALSE,
  1704. show_rownames = TRUE,
  1705. show_colnames = TRUE,
  1706. border_color = NA,
  1707. fontsize = 6,
  1708. cellheight = 8,
  1709. cellwidth = 8,
  1710. main = paste("Annotation p-value (signed -log10 q) -", neuron_type),
  1711. filename = out_file
  1712. )
  1713. invisible(list(summary_long = comb_tbl, score_mat = score_mat, q_mat = q_mat))
  1714. }
  1715. # --- 2. Use the row orders returned by make_ann_heatmap() above
  1716. make_ann_heatmap_pvals(
  1717. df_day50 = df_day50,
  1718. annot_wide = complete_data_annotated_day50,
  1719. neuron_type = "iN",
  1720. loc_ann = loc_ann,
  1721. out_file = file.path(out_dir_d50_ascd_avg, "heatmap_d50_iN_pvals_byGenotype_Stouffer.pdf"),
  1722. out_csv_base = file.path(out_dir_d50_ascd_avg, "heatmap_d50"),
  1723. row_order = row_order_iN
  1724. )
  1725. make_ann_heatmap_pvals(
  1726. df_day50 = df_day50,
  1727. annot_wide = complete_data_annotated_day50,
  1728. neuron_type = "iDA",
  1729. loc_ann = loc_ann,
  1730. out_file = file.path(out_dir_d50_ascd_avg, "heatmap_d50_iDA_pvals_byGenotypeStouffer.pdf"),
  1731. out_csv_base = file.path(out_dir_d50_ascd_avg, "heatmap_d50"),
  1732. row_order = row_order_iDA
  1733. )
  1734. ```
  1735. # Ternary plots from log2FC per annotation
  1736. ```{r}
  1737. library(Ternary)
  1738. # --- Step 1: create out dir for function call
  1739. out_dir_d50_ternaryplots <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/TernaryPlots_log2fc"
  1740. dir.create(out_dir_d50_ternaryplots, recursive = TRUE, showWarnings = FALSE)
  1741. # --- Step 2: Define function to loop over log2Fc and calculate ternary plots
  1742. plot_ternary_log2fc <- function(log2fc_mat, disease_class_df, out_dir, disease_class_palette,
  1743. tern_classes = c("SynGO", "Mito", "EndoLyso")) {
  1744. # Create output directory
  1745. tern_out_dir <- file.path(out_dir, "plots")
  1746. dir.create(tern_out_dir, recursive = TRUE, showWarnings = FALSE)
  1747. tern_suffix <- paste(tern_classes, collapse = "_")
  1748. # Identify KO columns (format: GENE_neuron, exclude annotation cols and _mean_ctrl)
  1749. ko_cols <- grep("^[A-Z0-9]+_(iN|iDA)$", colnames(log2fc_mat), value = TRUE)
  1750. ko_cols <- ko_cols[!grepl("_mean_ctrl", ko_cols)]
  1751. # Check that all tern_classes exist as columns
  1752. missing <- setdiff(tern_classes, colnames(log2fc_mat))
  1753. if (length(missing) > 0) stop("Missing annotation columns: ", paste(missing, collapse = ", "))
  1754. # Prepare disease class mapping
  1755. disease_map <- disease_class_df |>
  1756. dplyr::mutate(sample = toupper(sample)) |>
  1757. dplyr::select(sample, DiseaseClass) |>
  1758. dplyr::distinct()
  1759. # For each KO column, compute mean log2FC for genes in each annotation
  1760. results <- lapply(ko_cols, function(ko_col) {
  1761. parts <- strsplit(ko_col, "_")[[1]]
  1762. ko_gene <- parts[1]
  1763. neuron <- parts[2]
  1764. # Get log2FC values and annotation membership
  1765. df_sub <- log2fc_mat[, c("Genes", ko_col, tern_classes), drop = FALSE]
  1766. colnames(df_sub)[2] <- "log2fc"
  1767. # Compute mean log2FC for genes in each annotation, separately for pos and neg
  1768. means_pos <- sapply(tern_classes, function(tc) {
  1769. in_annot <- (df_sub[[tc]] == TRUE | df_sub[[tc]] == 1) & df_sub$log2fc > 0
  1770. if (sum(in_annot, na.rm = TRUE) == 0) return(NA_real_)
  1771. mean(df_sub$log2fc[in_annot], na.rm = TRUE)
  1772. })
  1773. means_neg <- sapply(tern_classes, function(tc) {
  1774. in_annot <- (df_sub[[tc]] == TRUE | df_sub[[tc]] == 1) & df_sub$log2fc < 0
  1775. if (sum(in_annot, na.rm = TRUE) == 0) return(NA_real_)
  1776. mean(abs(df_sub$log2fc[in_annot]), na.rm = TRUE)
  1777. })
  1778. data.frame(genotype = ko_col, KO = ko_gene, neuron = neuron,
  1779. v1_pos = means_pos[1], v2_pos = means_pos[2], v3_pos = means_pos[3],
  1780. v1_neg = means_neg[1], v2_neg = means_neg[2], v3_neg = means_neg[3],
  1781. stringsAsFactors = FALSE)
  1782. }) |> dplyr::bind_rows()
  1783. # Add disease class
  1784. results <- results |>
  1785. dplyr::left_join(disease_map, by = c("KO" = "sample"), relationship = "many-to-many") |>
  1786. dplyr::filter(!is.na(DiseaseClass)) |>
  1787. dplyr::distinct(genotype, KO, neuron, v1_pos, v2_pos, v3_pos, v1_neg, v2_neg, v3_neg, DiseaseClass)
  1788. # Compute proportions for positive log2FC (accumulation)
  1789. results_pos <- results |>
  1790. dplyr::filter(!is.na(v1_pos), !is.na(v2_pos), !is.na(v3_pos)) |>
  1791. dplyr::mutate(total = v1_pos + v2_pos + v3_pos, p_1 = v1_pos / total, p_2 = v2_pos / total, p_3 = v3_pos / total) |>
  1792. dplyr::filter(total > 0)
  1793. # Compute proportions for negative log2FC (vulnerability)
  1794. results_neg <- results |>
  1795. dplyr::filter(!is.na(v1_neg), !is.na(v2_neg), !is.na(v3_neg)) |>
  1796. dplyr::mutate(total = v1_neg + v2_neg + v3_neg, p_1 = v1_neg / total, p_2 = v2_neg / total, p_3 = v3_neg / total) |>
  1797. dplyr::filter(total > 0)
  1798. message("log2FC ternary data: ", nrow(results_pos), " pos, ", nrow(results_neg), " neg KO x neuron combinations")
  1799. # Color palettes for contours
  1800. blue_contour <- function(n) colorRampPalette(c("grey90", "dodgerblue", "dodgerblue3"))(n)
  1801. red_contour <- function(n) colorRampPalette(c("grey90", "salmon", "firebrick"))(n)
  1802. # --- Helper function to plot one ternary ---
  1803. plot_ternary_single <- function(dat, suffix, title_extra, contour_col) {
  1804. if (nrow(dat) < 3) return(invisible(NULL))
  1805. # Plot 1: All genotypes, colored by disease class, dots + labels
  1806. pdf(file.path(tern_out_dir, paste0("ternary_log2fc_combined_", suffix, "_", tern_suffix, ".pdf")), width = 6, height = 6)
  1807. TernaryPlot(alab = tern_classes[1], blab = tern_classes[2], clab = tern_classes[3],
  1808. lab.cex = 0.8, grid.lines = 5, grid.lty = "dashed", grid.minor.lines = 0,
  1809. axis.cex = 0.6, main = paste0("log2FC composition - ", title_extra))
  1810. cols <- disease_class_palette[as.character(dat$DiseaseClass)]
  1811. pch_vec <- ifelse(dat$neuron == "iN", 19, 17)
  1812. TernaryPoints(dat[, c("p_1", "p_2", "p_3")], col = cols, pch = pch_vec, cex = 1.2)
  1813. TernaryText(dat[, c("p_1", "p_2", "p_3")], labels = dat$genotype, col = "black", cex = 0.4, pos = 3, offset = 0.3)
  1814. legend("topright", legend = names(disease_class_palette), fill = disease_class_palette, cex = 0.5, bty = "n", title = "Disease Class")
  1815. legend("bottomright", legend = c("iN", "iDA"), pch = c(19, 17), cex = 0.6, bty = "n", title = "Neuron")
  1816. dev.off()
  1817. # Plot 2: Faceted by neuron and disease class
  1818. disease_class_counts <- dat |>
  1819. dplyr::group_by(DiseaseClass, neuron) |>
  1820. dplyr::summarise(n_geno = dplyr::n(), .groups = "drop") |>
  1821. dplyr::filter(n_geno >= 3) |>
  1822. dplyr::distinct(DiseaseClass) |>
  1823. dplyr::pull(DiseaseClass)
  1824. disease_classes <- intersect(unique(dat$DiseaseClass), disease_class_counts)
  1825. neuron_types <- c("iN", "iDA")
  1826. n_dc <- length(disease_classes)
  1827. if (n_dc > 0) {
  1828. pdf(file.path(tern_out_dir, paste0("ternary_log2fc_faceted_", suffix, "_", tern_suffix, ".pdf")), width = 3 * n_dc, height = 6)
  1829. par(mfrow = c(2, n_dc), mar = c(1, 1, 2, 1))
  1830. for (nt in neuron_types) {
  1831. for (dc in disease_classes) {
  1832. dd <- dat |> dplyr::filter(neuron == nt, DiseaseClass == dc)
  1833. TernaryPlot(alab = tern_classes[1], blab = tern_classes[2], clab = tern_classes[3],
  1834. lab.cex = 0.5, grid.lines = 4, grid.lty = "dashed", grid.minor.lines = 0,
  1835. axis.cex = 0.5, main = paste0(dc, " (", nt, ") - ", title_extra))
  1836. if (nrow(dd) >= 3) {
  1837. coords <- as.matrix(dd[, c("p_1", "p_2", "p_3")])
  1838. tryCatch({
  1839. TernaryDensityContour(coords, resolution = 50, col = contour_col, filled = TRUE, nlevels = 10, lwd = 0.25, labcex = 0.3)
  1840. }, error = function(e) NULL)
  1841. pt_col <- disease_class_palette[dc]
  1842. TernaryPoints(coords, pch = 19, cex = 0.8, col = pt_col)
  1843. TernaryText(coords, labels = dd$KO, cex = 0.5, pos = 3, offset = 0.3, col = "black")
  1844. }
  1845. }
  1846. }
  1847. dev.off()
  1848. }
  1849. }
  1850. # Generate plots for positive (accumulation) and negative (vulnerability)
  1851. plot_ternary_single(results_pos, "pos", "Accumulation (pos log2FC)", red_contour)
  1852. plot_ternary_single(results_neg, "neg", "Vulnerability (neg log2FC)", blue_contour)
  1853. # Save summary tables
  1854. write.csv(results_pos, file.path(tern_out_dir, paste0("ternary_log2fc_pos_summary_", tern_suffix, ".csv")), row.names = FALSE)
  1855. write.csv(results_neg, file.path(tern_out_dir, paste0("ternary_log2fc_neg_summary_", tern_suffix, ".csv")), row.names = FALSE)
  1856. invisible(list(pos = results_pos, neg = results_neg))
  1857. }
  1858. # --- Step 3: Call for main annotation triplet
  1859. plot_ternary_log2fc(
  1860. log2fc_mat = log2FC_d50_annotated,
  1861. disease_class_df = disease_class_df,
  1862. out_dir = out_dir_d50_ternaryplots,
  1863. disease_class_palette = disease_class_palette,
  1864. tern_classes = c("SynapseSVs", "OXPHOS", "Postsynaptic")
  1865. )
  1866. # c("SynapseSV", "OXPHOS", "Postsynaptic")
  1867. # c("Endo_iN_curated_Hundley", "OXPHOS","Lysosome")
  1868. #Sphingolipidoses iN:
  1869. # c("RecyclingEndosome", "Mito", "SynapseSVs")
  1870. #Sphingolipidoses iDA:
  1871. # c("RecyclingEndosome", "Svexocytosis", "SVfusion")
  1872. #Integral Membrane Protein Disorders iN:
  1873. # c("ER", "Autophagy", "EarlyEndosome")
  1874. #Integral Membrane Protein Disorders iDA:
  1875. # c("ER", "Presynaptic", "Postsynaptic")
  1876. #NCL iN:
  1877. # c("SVfusion", "Svendocytosis", "Lysosome")
  1878. #NCL iDA:
  1879. # c("ER", "EarlyEndosome", "Mito")
  1880. ###### Ternary / Triangle Plots across all annotations (log2FC)
  1881. # Generates ternary plots showing log2FC composition across ALL ANNOTATIONS / SELECT PLURALITY
  1882. # 3-way combinations of specified annotation tags.
  1883. # Split by positive (accumulation) and negative (vulnerability) log2FC
  1884. # --- Step 4: Define function that plots for any given annotations the ternary plot
  1885. plot_all_ternary_log2fc_combinations <- function(log2fc_mat, disease_class_df, out_dir, disease_class_palette, tags = NULL) {
  1886. # --- Setup ---
  1887. if (is.null(tags)) {
  1888. ko_cols <- grep("^[A-Z0-9]+_(iN|iDA)$", colnames(log2fc_mat), value = TRUE)
  1889. tags <- setdiff(colnames(log2fc_mat), c("Genes", ko_cols))
  1890. tags <- tags[!grepl("_mean_ctrl", tags)]
  1891. }
  1892. # Create subfolder for all combinations
  1893. combo_out_dir <- file.path(out_dir, "plots/all_combinations")
  1894. dir.create(combo_out_dir, recursive = TRUE, showWarnings = FALSE)
  1895. # Generate all 3-way combinations
  1896. combos <- combn(tags, 3, simplify = FALSE)
  1897. n_combos <- length(combos)
  1898. message("Generating ", n_combos, " ternary plots from ", length(tags), " tags...")
  1899. # Identify KO columns
  1900. ko_cols <- grep("^[A-Z0-9]+_(iN|iDA)$", colnames(log2fc_mat), value = TRUE)
  1901. ko_cols <- ko_cols[!grepl("_mean_ctrl", ko_cols)]
  1902. # Prepare disease class mapping
  1903. disease_map <- disease_class_df |>
  1904. dplyr::mutate(sample = toupper(sample)) |>
  1905. dplyr::select(sample, DiseaseClass) |>
  1906. dplyr::distinct()
  1907. # Color palettes
  1908. blue_contour <- function(n) colorRampPalette(c("grey90", "dodgerblue", "dodgerblue3"))(n)
  1909. red_contour <- function(n) colorRampPalette(c("grey90", "salmon", "firebrick"))(n)
  1910. # --- Loop through each 3-way combination ---
  1911. for (i in seq_along(combos)) {
  1912. tern_classes <- combos[[i]]
  1913. tern_suffix <- paste(tern_classes, collapse = "_")
  1914. message("[", i, "/", n_combos, "] ", tern_suffix)
  1915. tryCatch({
  1916. # Check that all tern_classes exist as columns
  1917. missing <- setdiff(tern_classes, colnames(log2fc_mat))
  1918. if (length(missing) > 0) {
  1919. message(" Skipped: missing columns ", paste(missing, collapse = ", "))
  1920. next
  1921. }
  1922. # For each KO column, compute mean log2FC for genes in each annotation
  1923. results <- lapply(ko_cols, function(ko_col) {
  1924. parts <- strsplit(ko_col, "_")[[1]]
  1925. ko_gene <- parts[1]
  1926. neuron <- parts[2]
  1927. df_sub <- log2fc_mat[, c("Genes", ko_col, tern_classes), drop = FALSE]
  1928. colnames(df_sub)[2] <- "log2fc"
  1929. means_pos <- sapply(tern_classes, function(tc) {
  1930. in_annot <- (df_sub[[tc]] == TRUE | df_sub[[tc]] == 1) & df_sub$log2fc > 0
  1931. if (sum(in_annot, na.rm = TRUE) == 0) return(NA_real_)
  1932. mean(df_sub$log2fc[in_annot], na.rm = TRUE)
  1933. })
  1934. means_neg <- sapply(tern_classes, function(tc) {
  1935. in_annot <- (df_sub[[tc]] == TRUE | df_sub[[tc]] == 1) & df_sub$log2fc < 0
  1936. if (sum(in_annot, na.rm = TRUE) == 0) return(NA_real_)
  1937. mean(abs(df_sub$log2fc[in_annot]), na.rm = TRUE)
  1938. })
  1939. data.frame(genotype = ko_col, KO = ko_gene, neuron = neuron,
  1940. v1_pos = means_pos[1], v2_pos = means_pos[2], v3_pos = means_pos[3],
  1941. v1_neg = means_neg[1], v2_neg = means_neg[2], v3_neg = means_neg[3],
  1942. stringsAsFactors = FALSE)
  1943. }) |> dplyr::bind_rows()
  1944. # Add disease class
  1945. results <- results |>
  1946. dplyr::left_join(disease_map, by = c("KO" = "sample"), relationship = "many-to-many") |>
  1947. dplyr::filter(!is.na(DiseaseClass)) |>
  1948. dplyr::distinct(genotype, KO, neuron, v1_pos, v2_pos, v3_pos, v1_neg, v2_neg, v3_neg, DiseaseClass)
  1949. # Compute proportions for pos and neg
  1950. results_pos <- results |>
  1951. dplyr::filter(!is.na(v1_pos), !is.na(v2_pos), !is.na(v3_pos)) |>
  1952. dplyr::mutate(total = v1_pos + v2_pos + v3_pos, p_1 = v1_pos / total, p_2 = v2_pos / total, p_3 = v3_pos / total) |>
  1953. dplyr::filter(total > 0)
  1954. results_neg <- results |>
  1955. dplyr::filter(!is.na(v1_neg), !is.na(v2_neg), !is.na(v3_neg)) |>
  1956. dplyr::mutate(total = v1_neg + v2_neg + v3_neg, p_1 = v1_neg / total, p_2 = v2_neg / total, p_3 = v3_neg / total) |>
  1957. dplyr::filter(total > 0)
  1958. # Skip if insufficient data
  1959. if (nrow(results_pos) < 3 && nrow(results_neg) < 3) {
  1960. message(" Skipped: insufficient data")
  1961. next
  1962. }
  1963. # --- Helper to generate one faceted plot ---
  1964. plot_faceted <- function(dat, suffix, title_extra, contour_col) {
  1965. if (nrow(dat) < 3) return(invisible(NULL))
  1966. disease_class_counts <- dat |>
  1967. dplyr::group_by(DiseaseClass, neuron) |>
  1968. dplyr::summarise(n_geno = dplyr::n(), .groups = "drop") |>
  1969. dplyr::filter(n_geno >= 3) |>
  1970. dplyr::distinct(DiseaseClass) |>
  1971. dplyr::pull(DiseaseClass)
  1972. disease_classes <- intersect(unique(dat$DiseaseClass), disease_class_counts)
  1973. neuron_types <- c("iN", "iDA")
  1974. n_dc <- length(disease_classes)
  1975. if (n_dc == 0) return(invisible(NULL))
  1976. pdf(file.path(combo_out_dir, paste0("ternary_log2fc_", suffix, "_", tern_suffix, ".pdf")), width = 3 * n_dc, height = 7)
  1977. par(mfrow = c(2, n_dc), mar = c(1, 1, 2, 1), oma = c(0, 0, 3, 0))
  1978. for (nt in neuron_types) {
  1979. for (dc in disease_classes) {
  1980. dd <- dat |> dplyr::filter(neuron == nt, DiseaseClass == dc)
  1981. TernaryPlot(alab = tern_classes[1], blab = tern_classes[2], clab = tern_classes[3],
  1982. lab.cex = 0.5, grid.lines = 4, grid.lty = "dashed", grid.col = "skyblue",
  1983. grid.lwd = 0.25, grid.minor.lines = 0, axis.cex = 0.5,
  1984. main = paste0(dc, " (", nt, ")"))
  1985. if (nrow(dd) >= 3) {
  1986. coords <- as.matrix(dd[, c("p_1", "p_2", "p_3")])
  1987. tryCatch({
  1988. TernaryDensityContour(coords, resolution = 50, col = contour_col, filled = TRUE, nlevels = 10, lwd = 0.25, labcex = 0.3)
  1989. }, error = function(e) NULL)
  1990. pt_col <- disease_class_palette[dc]
  1991. TernaryPoints(coords, pch = 19, cex = 0.8, col = pt_col)
  1992. TernaryText(coords, labels = dd$genotype, cex = 0.5, pos = 3, offset = 0.3, col = "black")
  1993. }
  1994. }
  1995. }
  1996. mtext(paste0("Each KO positioned by % |log2FC| - ", title_extra), outer = TRUE, cex = 0.6, line = 0.5)
  1997. dev.off()
  1998. }
  1999. # Generate both pos and neg plots
  2000. plot_faceted(results_pos, "pos", "Accumulation (pos log2FC)", red_contour)
  2001. plot_faceted(results_neg, "neg", "Vulnerability (neg log2FC)", blue_contour)
  2002. }, error = function(e) {
  2003. message(" Error: ", e$message)
  2004. })
  2005. }
  2006. message("Done. ", n_combos, " combinations processed. Output: ", combo_out_dir)
  2007. }
  2008. # --- Step 5: Call function to iterate over the plotting call
  2009. plot_all_ternary_log2fc_combinations(
  2010. log2fc_mat = log2FC_d50_annotated,
  2011. disease_class_df = disease_class_df,
  2012. out_dir = out_dir_d50_ternaryplots,
  2013. disease_class_palette = disease_class_palette,
  2014. tags = c("Golgi", "EarlyEndosome", "RecyclingEndosome", "Endo_iN_curated_Hundley", "OXPHOS", "MitoIMS", "MitoMatrix", "MitoMIM", "mtComplexI", "Lysosome", "Presynaptic", "Postsynaptic", "SynapseSVs", "Svendocytosis", "Svexocytosis", "SVfusion")
  2015. )
  2016. ```
  2017. # Half-circos plot of mean log2FC per annotation per KO
  2018. ```{r}
  2019. # Split by neuron type (iN left, iDA right), sorted by overall mean log2FC
  2020. # Approach: duplicate data to fill full circle, then only render real sectors in top half
  2021. library(circlize)
  2022. # --- Step 1: create out dir
  2023. out_dir_d50_semicircos <- "/Users/felix/Documents/PostDoc/Harvard/03_LSD-PD/Proteomics/d50_diff132/day50/halfcircos_log2fc"
  2024. dir.create(out_dir_d50_semicircos, recursive = TRUE, showWarnings = FALSE)
  2025. # --- Step 2: Define function
  2026. plot_halfcircos_log2fc <- function(log2fc_mat, disease_class_df, out_dir,
  2027. filename = "halfcircos_log2fc",
  2028. disease_class_palette,
  2029. celltype_colors = c(iDA = "grey80", iN = "grey40"),
  2030. annotations = c("Lysosome", "Mito", "OXPHOS", "Golgi",
  2031. "Endo_iN_curated_Hundley", "EarlyEndosome",
  2032. "RecyclingEndosome", "SynapseSVs", "ER", "Autophagy")) {
  2033. # Identify KO columns
  2034. ko_cols <- grep("^[A-Z0-9]+_(iN|iDA)$", colnames(log2fc_mat), value = TRUE)
  2035. ko_cols <- ko_cols[!grepl("_mean_ctrl", ko_cols)]
  2036. # Check annotations exist
  2037. missing <- setdiff(annotations, colnames(log2fc_mat))
  2038. if (length(missing) > 0) stop("Missing annotation columns: ", paste(missing, collapse = ", "))
  2039. # Prepare disease class mapping
  2040. disease_map <- disease_class_df |>
  2041. dplyr::mutate(sample = toupper(sample)) |>
  2042. dplyr::select(sample, DiseaseClass) |>
  2043. dplyr::distinct()
  2044. # Compute mean log2FC per KO per annotation
  2045. summary_list <- lapply(ko_cols, function(ko_col) {
  2046. parts <- strsplit(ko_col, "_")[[1]]
  2047. ko_gene <- parts[1]
  2048. neuron <- parts[2]
  2049. means <- sapply(annotations, function(ann) {
  2050. in_annot <- log2fc_mat[[ann]] == TRUE
  2051. if (sum(in_annot, na.rm = TRUE) == 0) return(NA_real_)
  2052. mean(log2fc_mat[[ko_col]][in_annot], na.rm = TRUE)
  2053. })
  2054. data.frame(KO = ko_gene, neuron = neuron, sample_id = ko_col, t(means), stringsAsFactors = FALSE)
  2055. })
  2056. summary_df <- dplyr::bind_rows(summary_list)
  2057. # Add disease class
  2058. summary_df <- summary_df |>
  2059. dplyr::left_join(disease_map, by = c("KO" = "sample"), relationship = "many-to-many") |>
  2060. dplyr::filter(!is.na(DiseaseClass)) |>
  2061. dplyr::distinct(sample_id, .keep_all = TRUE)
  2062. # Compute overall mean for sorting
  2063. summary_df$overall_mean <- rowMeans(summary_df[, annotations], na.rm = TRUE)
  2064. # Save summary
  2065. write.csv(summary_df, file.path(out_dir, paste0(filename, "_summary.csv")), row.names = FALSE)
  2066. message("Summary saved: ", nrow(summary_df), " KO x neuron combinations")
  2067. # Sort: within each neuron type, sort by overall_mean
  2068. df_iN <- summary_df |> dplyr::filter(neuron == "iN") |> dplyr::arrange(desc(overall_mean))
  2069. df_iDA <- summary_df |> dplyr::filter(neuron == "iDA") |> dplyr::arrange(overall_mean)
  2070. # Combine real data - iN first (left side), then iDA (right side)
  2071. plot_df_real <- rbind(df_iN, df_iDA)
  2072. plot_df_real$sector_id <- paste0(plot_df_real$sample_id, "_", seq_len(nrow(plot_df_real)))
  2073. plot_df_real$is_dummy <- FALSE
  2074. # Create dummy data (same number of sectors, will be in bottom half - not visible)
  2075. plot_df_dummy <- plot_df_real
  2076. plot_df_dummy$sector_id <- paste0("dummy_", seq_len(nrow(plot_df_dummy)))
  2077. plot_df_dummy$is_dummy <- TRUE
  2078. # Combine: real data first (top half), then dummy (bottom half)
  2079. plot_df <- rbind(plot_df_real, plot_df_dummy)
  2080. n_iN <- nrow(df_iN)
  2081. n_iDA <- nrow(df_iDA)
  2082. n_real <- nrow(plot_df_real)
  2083. n_total <- nrow(plot_df)
  2084. # Color scale for log2FC (spectral)
  2085. log2fc_range <- range(unlist(plot_df[, annotations]), na.rm = TRUE)
  2086. log2fc_max <- max(abs(log2fc_range))
  2087. #spectral_cols <- colorRampPalette(rev(RColorBrewer::brewer.pal(11, "Spectral")))(100)
  2088. spectral_cols <- colorRampPalette(rev(brewer.pal(11, "RdYlBu")))(100)
  2089. get_color <- function(val) {
  2090. if (is.na(val)) return("grey70")
  2091. idx <- round((val + log2fc_max) / (2 * log2fc_max) * 99) + 1
  2092. idx <- max(1, min(100, idx))
  2093. spectral_cols[idx]
  2094. }
  2095. # --- Plot ---
  2096. pdf(file.path(out_dir, paste0(filename, ".pdf")), width = 14, height = 20)
  2097. circos.clear()
  2098. # gap.after: one value per sector
  2099. # Structure: [iN real] gap [iDA real] gap [iN dummy] [iDA dummy]
  2100. gap_after <- c(
  2101. rep(0, n_iN - 1), 5,
  2102. rep(0, n_iDA - 1), 5,
  2103. rep(0, n_iN - 1), 0,
  2104. rep(0, n_iDA - 1), 0
  2105. )
  2106. circos.par(
  2107. canvas.xlim = c(-1, 1),
  2108. canvas.ylim = c(-1, 0), # show bottom half (which contains our real data)
  2109. start.degree = 180, # flip so real data appears at top visually
  2110. gap.after = gap_after,
  2111. cell.padding = c(0, 0, 0, 0)
  2112. )
  2113. message("Total sectors: ", n_total, ", gap.after length: ", length(gap_after))
  2114. # Initialize sectors
  2115. circos.initialize(
  2116. factors = factor(plot_df$sector_id, levels = plot_df$sector_id),
  2117. xlim = c(0, 1)
  2118. )
  2119. # Helper to check if sector is real (not dummy)
  2120. is_real_sector <- function(sector.index) {
  2121. !grepl("^dummy_", sector.index)
  2122. }
  2123. # Add "iN" and "iDA" labels at the sides
  2124. text(-1.05, -0.5, "iN", cex = 1.2, font = 2)
  2125. text(1.05, -0.5, "iDA", cex = 1.2, font = 2)
  2126. # Track 1: KO labels (outermost) - only for real sectors
  2127. circos.track(ylim = c(0, 1), track.height = 0.05, bg.border = NA, panel.fun = function(x, y) {
  2128. sector.index <- CELL_META$sector.index
  2129. if (!is_real_sector(sector.index)) return()
  2130. ko_name <- plot_df$KO[plot_df$sector_id == sector.index]
  2131. circos.text(0.5, 0.5, ko_name, facing = "clockwise", niceFacing = TRUE, cex = 0.4)
  2132. })
  2133. # Track 2: Neuron type - only for real sectors
  2134. circos.track(ylim = c(0, 1), track.height = 0.04, bg.border = NA, panel.fun = function(x, y) {
  2135. sector.index <- CELL_META$sector.index
  2136. if (!is_real_sector(sector.index)) return()
  2137. neuron <- plot_df$neuron[plot_df$sector_id == sector.index]
  2138. circos.rect(0, 0, 1, 1, col = celltype_colors[neuron], border = NA)
  2139. })
  2140. # Track 3: Disease class - only for real sectors
  2141. circos.track(ylim = c(0, 1), track.height = 0.04, bg.border = NA, panel.fun = function(x, y) {
  2142. sector.index <- CELL_META$sector.index
  2143. if (!is_real_sector(sector.index)) return()
  2144. dc <- plot_df$DiseaseClass[plot_df$sector_id == sector.index]
  2145. circos.rect(0, 0, 1, 1, col = disease_class_palette[dc], border = NA)
  2146. })
  2147. # Tracks 4+: Annotation heatmaps - only for real sectors
  2148. for (ann in annotations) {
  2149. circos.track(ylim = c(0, 1), track.height = 0.05, bg.border = NA, panel.fun = function(x, y) {
  2150. sector.index <- CELL_META$sector.index
  2151. if (!is_real_sector(sector.index)) return()
  2152. val <- plot_df[[ann]][plot_df$sector_id == sector.index]
  2153. circos.rect(0, 0, 1, 1, col = get_color(val), border = NA)
  2154. })
  2155. }
  2156. # Add legends
  2157. circos.clear()
  2158. # Add track labels on the right side
  2159. track_labels <- c("Neuron", "Disease", annotations)
  2160. track_y <- seq(0.95, 0.95 - 0.07 * length(track_labels), length.out = length(track_labels))
  2161. for (i in seq_along(track_labels)) {
  2162. text(1.15, track_y[i], track_labels[i], adj = 0, cex = 0.5)
  2163. }
  2164. # Neuron legend
  2165. legend("topleft", legend = names(celltype_colors), fill = celltype_colors, title = "Neuron", cex = 0.6, bty = "n")
  2166. # Disease class legend
  2167. legend("topright", legend = names(disease_class_palette), fill = disease_class_palette, title = "Disease Class", cex = 0.5, bty = "n")
  2168. # Color scale legend
  2169. legend("bottomright", legend = c(sprintf("%.1f", log2fc_max), "0", sprintf("%.1f", -log2fc_max)),
  2170. fill = spectral_cols[c(100, 50, 1)], title = "log2FC", cex = 0.5, bty = "n")
  2171. dev.off()
  2172. message("Plot saved: ", file.path(out_dir, "halfcircos_log2fc.pdf"))
  2173. invisible(summary_df)
  2174. }
  2175. # --- Step 3: Call function
  2176. plot_halfcircos_log2fc(
  2177. log2fc_mat = log2FC_d50_annotated,
  2178. disease_class_df = disease_class_df,
  2179. out_dir = out_dir_d50_semicircos,
  2180. filename = "halfcircos_log2fc",
  2181. disease_class_palette = disease_class_palette,
  2182. celltype_colors = c(iDA = "grey80", iN = "grey40"),
  2183. annotations = c("Lysosome", "Mito", "OXPHOS", "Golgi", "Endo_iN_curated_Hundley",
  2184. "EarlyEndosome", "RecyclingEndosome", "SynapseSVs", "ER", "Autophagy")
  2185. )
  2186. ## --- q-value filtered half-circos
  2187. # mask non-significant values to NA
  2188. fc_cols_circos <- grep("^[A-Z0-9]+_(iN|iDA)$", colnames(log2FC_d50_annotated), value = TRUE)
  2189. q_sig_pairs <- diff132_log2fc_df %>%
  2190. dplyr::filter(day == 50, q_value < 0.05) %>%
  2191. dplyr::select(Genes, sample_id)
  2192. log2FC_d50_qsig <- log2FC_d50_annotated
  2193. for (col in fc_cols_circos) {
  2194. sig_genes <- q_sig_pairs$Genes[q_sig_pairs$sample_id == col]
  2195. log2FC_d50_qsig[[col]][!log2FC_d50_qsig$Genes %in% sig_genes] <- NA
  2196. }
  2197. plot_halfcircos_log2fc(
  2198. log2fc_mat = log2FC_d50_qsig,
  2199. filename = "halfcircos_log2fc_qsig",
  2200. disease_class_df = disease_class_df,
  2201. out_dir = out_dir_d50_semicircos,
  2202. disease_class_palette = disease_class_palette,
  2203. celltype_colors = c(iDA = "grey80", iN = "grey40"),
  2204. annotations = c("Lysosome", "Mito", "OXPHOS", "Golgi", "Endo_iN_curated_Hundley",
  2205. "EarlyEndosome", "RecyclingEndosome", "SynapseSVs", "ER", "Autophagy")
  2206. )
  2207. ```
  2208. # Barplots of # of IDs sig up / down or -0.5 log2FC per KO/neuron
  2209. ```{r}
  2210. # Count genes with log2FC <= -0.5 per sample
  2211. sample_cols_lostIDs <- grep("_i(N|DA)$", colnames(log2FC_d50_annotated), value = TRUE)
  2212. lost_counts <- sapply(sample_cols_lostIDs, function(col) {
  2213. sum(log2FC_d50_annotated[[col]] <= -1, na.rm = TRUE)
  2214. })
  2215. # Create df for plotting
  2216. lostIDs_df <- data.frame(
  2217. Sample = names(lost_counts),
  2218. Count = as.numeric(lost_counts)
  2219. ) %>%
  2220. dplyr::mutate(
  2221. Genotype = gsub("_i(N|DA)$", "", Sample),
  2222. Neuron = gsub(".*_(i[NDA]+)$", "\\1", Sample)
  2223. )
  2224. # Order genotypes by total lost
  2225. genotype_order <- lostIDs_df %>%
  2226. dplyr::group_by(Genotype) %>%
  2227. dplyr::summarise(Total = sum(Count), .groups = "drop") %>%
  2228. dplyr::arrange(dplyr::desc(Total)) %>%
  2229. dplyr::pull(Genotype)
  2230. lostIDs_df$Genotype <- factor(lostIDs_df$Genotype, levels = genotype_order)
  2231. celltype_colors <- c(iDA = "grey80", iN = "grey40")
  2232. p_IDlost_stack <- ggplot2::ggplot(lostIDs_df, ggplot2::aes(x = Genotype, y = Count, fill = Neuron)) +
  2233. ggplot2::geom_col() +
  2234. ggplot2::scale_fill_manual(values = celltype_colors) +
  2235. ggplot2::labs(
  2236. x = NULL,
  2237. y = "Proteins lost (log2FC =< -1)",
  2238. fill = NULL
  2239. ) +
  2240. ggplot2::theme_bw(base_size = 6) +
  2241. ggplot2::theme(
  2242. panel.grid = ggplot2::element_blank(),
  2243. axis.text.x = ggplot2::element_text(angle = 90, hjust = 1, vjust = 0.5)
  2244. )
  2245. ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "lost_proteins_stacked_barplot.pdf"), p_IDlost_stack, width = 3.5, height = 3)
  2246. ## based on sig levels p and q levels
  2247. # Significance thresholds
  2248. q_threshold <- -log10(0.05) # 1.3
  2249. p_threshold <- -log10(0.05) # 1.3
  2250. # Function to count significant proteins by direction
  2251. count_sig_proteins <- function(data, sig_type = "q") {
  2252. if (sig_type == "q") {
  2253. fc_cols <- grep("_log2_fold_change$", colnames(data), value = TRUE)
  2254. sig_cols <- grep("_neg_log10_q_value$", colnames(data), value = TRUE)
  2255. threshold <- q_threshold
  2256. } else {
  2257. fc_cols <- grep("_log2_fold_change$", colnames(data), value = TRUE)
  2258. sig_cols <- grep("_neg_log10_p_value$", colnames(data), value = TRUE)
  2259. threshold <- p_threshold
  2260. }
  2261. results <- list()
  2262. for (i in seq_along(fc_cols)) {
  2263. fc_col <- fc_cols[i]
  2264. genotype_neuron <- gsub("_log2_fold_change$", "", fc_col)
  2265. sig_col <- paste0(genotype_neuron, "_neg_log10_", sig_type, "_value")
  2266. if (sig_col %in% sig_cols) {
  2267. is_sig <- data[[sig_col]] >= threshold & !is.na(data[[sig_col]])
  2268. fc_vals <- data[[fc_col]]
  2269. n_up <- sum(is_sig & fc_vals > 0, na.rm = TRUE)
  2270. n_down <- sum(is_sig & fc_vals < 0, na.rm = TRUE)
  2271. neuron <- gsub(".*_(iDA|iN)$", "\\1", genotype_neuron)
  2272. genotype <- gsub("_(iDA|iN)$", "", genotype_neuron)
  2273. results[[length(results) + 1]] <- data.frame(Genotype = genotype, Neuron = neuron, Direction = "Up", Count = n_up, stringsAsFactors = FALSE)
  2274. results[[length(results) + 1]] <- data.frame(Genotype = genotype, Neuron = neuron, Direction = "Down", Count = n_down, stringsAsFactors = FALSE)
  2275. }
  2276. }
  2277. do.call(rbind, results)
  2278. }
  2279. # Count for q-values
  2280. sig_df_q <- count_sig_proteins(complete_data_annotated_day50, sig_type = "q")
  2281. sig_df_q_iDA <- sig_df_q %>% dplyr::filter(Neuron == "iDA")
  2282. sig_df_q_iN <- sig_df_q %>% dplyr::filter(Neuron == "iN")
  2283. # Count for p-values
  2284. sig_df_p <- count_sig_proteins(complete_data_annotated_day50, sig_type = "p")
  2285. sig_df_p_iDA <- sig_df_p %>% dplyr::filter(Neuron == "iDA")
  2286. sig_df_p_iN <- sig_df_p %>% dplyr::filter(Neuron == "iN")
  2287. # Order genotypes by total
  2288. genotype_order_q_iDA <- sig_df_q_iDA %>% dplyr::group_by(Genotype) %>% dplyr::summarise(Total = sum(Count), .groups = "drop") %>% dplyr::arrange(dplyr::desc(Total)) %>% dplyr::pull(Genotype)
  2289. sig_df_q_iDA$Genotype <- factor(sig_df_q_iDA$Genotype, levels = genotype_order_q_iDA)
  2290. genotype_order_q_iN <- sig_df_q_iN %>% dplyr::group_by(Genotype) %>% dplyr::summarise(Total = sum(Count), .groups = "drop") %>% dplyr::arrange(dplyr::desc(Total)) %>% dplyr::pull(Genotype)
  2291. sig_df_q_iN$Genotype <- factor(sig_df_q_iN$Genotype, levels = genotype_order_q_iN)
  2292. genotype_order_p_iDA <- sig_df_p_iDA %>% dplyr::group_by(Genotype) %>% dplyr::summarise(Total = sum(Count), .groups = "drop") %>% dplyr::arrange(dplyr::desc(Total)) %>% dplyr::pull(Genotype)
  2293. sig_df_p_iDA$Genotype <- factor(sig_df_p_iDA$Genotype, levels = genotype_order_p_iDA)
  2294. genotype_order_p_iN <- sig_df_p_iN %>% dplyr::group_by(Genotype) %>% dplyr::summarise(Total = sum(Count), .groups = "drop") %>% dplyr::arrange(dplyr::desc(Total)) %>% dplyr::pull(Genotype)
  2295. sig_df_p_iN$Genotype <- factor(sig_df_p_iN$Genotype, levels = genotype_order_p_iN)
  2296. # Colors
  2297. direction_colors_iDA <- c(Down = "#9ECAE1", Up = "#F28B72")
  2298. direction_colors_iN <- c(Down = "#2A77AD", Up = "#CC4221")
  2299. ## Plots
  2300. # Plot q-value iDA
  2301. p_sig_q_iDA <- ggplot2::ggplot(sig_df_q_iDA, ggplot2::aes(x = Genotype, y = Count, fill = Direction)) +
  2302. ggplot2::geom_col() + ggplot2::scale_fill_manual(values = direction_colors_iDA) +
  2303. ggplot2::labs(x = NULL, y = "Significant proteins (q < 0.05)", fill = NULL, title = "iDA") +
  2304. ggplot2::theme_bw(base_size = 6) +
  2305. ggplot2::theme(
  2306. panel.grid = ggplot2::element_blank(),
  2307. axis.text.x = ggplot2::element_text(angle = 90, hjust = 1, vjust = 0.5))
  2308. ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "sig_proteins_qvalue_iDA_barplot.pdf"), p_sig_q_iDA, width = 3.5, height = 3)
  2309. #ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "sig_proteins_qvalue_iDA_barplot.pdf"), p_sig_q_iDA, width = 3.5, height = 1.5)
  2310. # Plot q-value iN
  2311. p_sig_q_iN <- ggplot2::ggplot(sig_df_q_iN, ggplot2::aes(x = Genotype, y = Count, fill = Direction)) +
  2312. ggplot2::geom_col() + ggplot2::scale_fill_manual(values = direction_colors_iN) +
  2313. ggplot2::labs(x = NULL, y = "Significant proteins (q < 0.05)", fill = NULL, title = "iN") +
  2314. ggplot2::theme_bw(base_size = 6) +
  2315. ggplot2::theme(
  2316. panel.grid = ggplot2::element_blank(),
  2317. axis.text.x = ggplot2::element_text(angle = 90, hjust = 1, vjust = 0.5))
  2318. ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "sig_proteins_qvalue_iN_barplot.pdf"), p_sig_q_iN, width = 3.5, height = 3)
  2319. #ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "sig_proteins_qvalue_iN_barplot.pdf"), p_sig_q_iN, width = 3.5, height = 1.5)
  2320. # Plot p-value iDA
  2321. p_sig_p_iDA <- ggplot2::ggplot(sig_df_p_iDA, ggplot2::aes(x = Genotype, y = Count, fill = Direction)) +
  2322. ggplot2::geom_col() + ggplot2::scale_fill_manual(values = direction_colors_iDA) +
  2323. ggplot2::labs(x = NULL, y = "Significant proteins (p < 0.05)", fill = NULL, title = "iDA") +
  2324. ggplot2::theme_bw(base_size = 6) +
  2325. ggplot2::theme(
  2326. panel.grid = ggplot2::element_blank(),
  2327. axis.text.x = ggplot2::element_text(angle = 90, hjust = 1, vjust = 0.5))
  2328. ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "sig_proteins_pvalue_iDA_barplot.pdf"), p_sig_p_iDA, width = 3.5, height = 3)
  2329. # Plot p-value iN
  2330. p_sig_p_iN <- ggplot2::ggplot(sig_df_p_iN, ggplot2::aes(x = Genotype, y = Count, fill = Direction)) +
  2331. ggplot2::geom_col() + ggplot2::scale_fill_manual(values = direction_colors_iN) +
  2332. ggplot2::labs(x = NULL, y = "Significant proteins (p < 0.05)", fill = NULL, title = "iN") +
  2333. ggplot2::theme_bw(base_size = 6) + ggplot2::theme(
  2334. panel.grid = ggplot2::element_blank(),
  2335. axis.text.x = ggplot2::element_text(angle = 90, hjust = 1, vjust = 0.5))
  2336. ggplot2::ggsave(file.path(out_dir_d50_heatmaps, "sig_proteins_pvalue_iN_barplot.pdf"), p_sig_p_iN, width = 3.5, height = 3)
  2337. ```
  2338. # Sorted lineplots of neuro-related annotations
  2339. ```{r}
  2340. # --------------------------------------------------- #
  2341. # Line plots of Sph mutants of interest
  2342. # plot by genotype and split for iN and iDA
  2343. # --------------------------------------------------- #
  2344. # --- 0. Define input groups for selective highlights on plot
  2345. highlight_blue <- c("SMPD1","ASAH1","GBA1","PSAP","HEXA","HEXB")
  2346. highlight_thick <- c("SMPD1","ASAH1","GBA1","CTSD","CLN6","ATP13A2","PPT1")
  2347. # --- 1. define plotting function for user-select annotations
  2348. make_lineplot_all <- function(df, neuron_type, loc_ann, out_file) {
  2349. # 1.1 reshape to long
  2350. long_df <- df %>%
  2351. tidyr::pivot_longer(
  2352. cols = matches(paste0("_", neuron_type, "_log2_fold_change$")),
  2353. names_to = "Condition",
  2354. values_to = "log2FC"
  2355. ) %>%
  2356. dplyr::mutate(genotype = sub("_.*", "", Condition))
  2357. # 1.2 attach annotations
  2358. long_df <- long_df %>%
  2359. tidyr::pivot_longer(cols = all_of(loc_ann),
  2360. names_to = "Annotation", values_to = "is_annotated") %>%
  2361. filter(is_annotated == 1)
  2362. # 1.3 summarise mean log2FC per genotype × annotation
  2363. plot_df <- long_df %>%
  2364. dplyr::group_by(genotype, Annotation) %>%
  2365. dplyr::summarise(mean_log2FC = mean(log2FC, na.rm = TRUE), .groups = "drop") %>%
  2366. dplyr::mutate(Annotation = factor(Annotation, levels = loc_ann))
  2367. # 1.4 flag highlight sets
  2368. plot_df <- plot_df %>%
  2369. dplyr::mutate(
  2370. highlight_color = case_when(
  2371. genotype %in% highlight_blue ~ "dodgerblue",
  2372. TRUE ~ "grey80"
  2373. ),
  2374. highlight_size = case_when(
  2375. genotype %in% highlight_thick ~ 1.2,
  2376. TRUE ~ 0.4
  2377. ),
  2378. highlight_label = ifelse(genotype %in% union(highlight_blue, highlight_thick),
  2379. genotype, NA)
  2380. )
  2381. # 1.5 plot
  2382. p <- ggplot(plot_df, aes(x = Annotation, y = mean_log2FC, group = genotype)) +
  2383. geom_line(aes(color = highlight_color, size = highlight_size)) +
  2384. scale_color_identity() +
  2385. scale_size_identity() +
  2386. geom_text_repel(
  2387. data = plot_df %>% filter(!is.na(highlight_label)) %>% group_by(genotype) %>% slice_tail(n = 1),
  2388. aes(label = highlight_label, color = highlight_color),
  2389. size = 2, nudge_x = 0.5, direction = "y", hjust = 0
  2390. ) +
  2391. theme_bw(base_size = 6) +
  2392. theme(
  2393. panel.grid = element_blank(),
  2394. axis.text.x = element_text(angle = 45, hjust = 1)
  2395. ) +
  2396. labs(
  2397. title = paste("Mean log2FC across annotations –", neuron_type),
  2398. x = "Annotation",
  2399. y = expression("Mean log"[2]*"(KO / Ctrl)")
  2400. )
  2401. ggsave(out_file, p, width = 4, height = 4, device = cairo_pdf)
  2402. return(p)
  2403. }
  2404. # --- 2. Call function and run for both iN and iDA
  2405. p_iN <- make_lineplot_all(
  2406. complete_data_annotated_day50, "iN", loc_ann,
  2407. file.path(out_dir_d50_ascd_avg, "lineplot_allGenotypes_iN.pdf")
  2408. )
  2409. p_iDA <- make_lineplot_all(
  2410. complete_data_annotated_day50, "iDA", loc_ann,
  2411. file.path(out_dir_d50_ascd_avg, "lineplot_allGenotypes_iDA.pdf")
  2412. )
  2413. ```
  2414. # Key driver of SVfusion & exocytosis in iDA
  2415. ```{r}
  2416. # --------------------------------------------------- #
  2417. # Heatmap of keydrivers of synaptic anotations in iDA neurons
  2418. # --------------------------------------------------- #
  2419. # --- 1. Parameters
  2420. n_top_drivers <- 40
  2421. out_dir_key <- out_dir_d50_ascd_avg_SV
  2422. # --- 2. Genotype groups
  2423. svexo_group1 <- c("CLN6","CTSF","MFSD8","PPT1","MCOLN1","CLN3","NPC2","CLN5","GRN")
  2424. svexo_group2 <- c("ASAH1","GBA1","CLN8","NPC1","DNAJC5","TPP1","ATP13A2","SMPD1","PSAP","HEXA","HEXB","GAA","CTSD","LIPA")
  2425. svfusion_group1 <- c("CLN6","CTSF","MFSD8","PPT1","MCOLN1","CLN3","NPC2","CLN5","GRN","NPC1","DNAJC5","TPP1","GAA","CTSD","LIPA")
  2426. svfusion_group2 <- c("ASAH1","CLN8","GBA1","ATP13A2","PSAP","SMPD1","HEXA","HEXB")
  2427. # --- 3. Helper: make ranked table + heatmap for one annotation ---
  2428. make_topvar_heatmap_iDA <- function(annotation_col, group1_vec, group2_vec, n_top = 40, out_prefix = "SV") {
  2429. # 3.1 subset to annotation == 1 and iDA log2FC columns
  2430. long_df <- complete_data_annotated_day50 %>%
  2431. dplyr::filter(.data[[annotation_col]] == 1) %>%
  2432. tidyr::pivot_longer(
  2433. cols = matches("_iDA_log2_fold_change$"),
  2434. names_to = "Condition",
  2435. values_to = "log2FC"
  2436. ) %>%
  2437. dplyr::mutate(genotype = sub("_.*$", "", Condition)) %>%
  2438. dplyr::select(Genes, genotype, log2FC)
  2439. # 3.2 restrict to requested genotypes that actually exist
  2440. requested_genos <- unique(c(group1_vec, group2_vec))
  2441. present_genos <- intersect(requested_genos, unique(long_df$genotype))
  2442. if (length(present_genos) == 0) {
  2443. message(sprintf("No requested genotypes found for %s. Skipping.", annotation_col))
  2444. return(invisible(NULL))
  2445. }
  2446. long_df <- long_df %>% dplyr::filter(genotype %in% present_genos)
  2447. # 3.3 variability per protein across present genotypes
  2448. var_tbl <- long_df %>%
  2449. dplyr::group_by(Genes) %>%
  2450. dplyr::summarise(
  2451. n_genotypes = n_distinct(genotype),
  2452. mean_log2FC = mean(log2FC, na.rm = TRUE),
  2453. sd_log2FC = sd(log2FC, na.rm = TRUE),
  2454. var_log2FC = var(log2FC, na.rm = TRUE),
  2455. mad_log2FC = mad(log2FC, na.rm = TRUE),
  2456. .groups = "drop"
  2457. ) %>%
  2458. dplyr::filter(n_genotypes >= 2) %>%
  2459. dplyr::arrange(desc(sd_log2FC))
  2460. # write full ranking
  2461. write_csv(var_tbl, file.path(out_dir_key, paste0(out_prefix, "_top_variable_genes_iDA_ranked.csv")))
  2462. # 3.4 select top N and build matrix
  2463. top_genes <- var_tbl %>% slice_head(n = n_top) %>% pull(Genes)
  2464. wide_mat <- long_df %>%
  2465. dplyr::filter(Genes %in% top_genes) %>%
  2466. dplyr::mutate(Genes = factor(Genes, levels = top_genes)) %>%
  2467. tidyr::pivot_wider(names_from = genotype, values_from = log2FC) %>%
  2468. dplyr::arrange(Genes)
  2469. # order columns: group1 then group2
  2470. g1_order <- intersect(group1_vec, colnames(wide_mat))
  2471. g2_order <- intersect(group2_vec, colnames(wide_mat))
  2472. col_order <- c(g1_order, g2_order)
  2473. wide_mat <- wide_mat[, c("Genes", col_order), drop = FALSE]
  2474. # to matrix
  2475. mat_vals <- wide_mat %>%
  2476. column_to_rownames("Genes") %>%
  2477. as.matrix()
  2478. # save the actual heatmap matrix (gene names are row.names)
  2479. write.csv(mat_vals,
  2480. file = file.path(out_dir_key, paste0(out_prefix, "_heatmap_matrix.csv")),
  2481. row.names = TRUE)
  2482. # 3.5) heat range + palette
  2483. rng_min <- suppressWarnings(min(mat_vals, na.rm = TRUE))
  2484. rng_max <- suppressWarnings(max(mat_vals, na.rm = TRUE))
  2485. if (!is.finite(rng_min) || !is.finite(rng_max)) {
  2486. message(sprintf("All values are NA for %s. Skipping heatmap.", annotation_col))
  2487. return(invisible(NULL))
  2488. }
  2489. if (isTRUE(all.equal(rng_min, rng_max))) {
  2490. rng_min <- rng_min - 0.05
  2491. rng_max <- rng_max + 0.05
  2492. }
  2493. bk <- seq(rng_min, rng_max, length.out = 100)
  2494. pal <- colorRampPalette(rev(brewer.pal(11, "RdYlBu")))(length(bk) - 1)
  2495. # 3.6 plot heatmap
  2496. pdf(file.path(out_dir_key, paste0(out_prefix, "_top_variable_genes_iDA_heatmap.pdf")), width = 6, height = 8)
  2497. pheatmap::pheatmap(
  2498. mat_vals,
  2499. color = pal,
  2500. breaks = bk,
  2501. cluster_rows = FALSE,
  2502. cluster_cols = TRUE,
  2503. na_col = "grey90",
  2504. border_color = NA,
  2505. show_rownames = TRUE,
  2506. show_colnames = TRUE,
  2507. fontsize = 6,
  2508. cellheight = 8,
  2509. cellwidth = 8,
  2510. main = paste0("Top variable ", out_prefix, " proteins in iDA (log2FC)")
  2511. )
  2512. dev.off()
  2513. }
  2514. # --- 4. Run for both annotations & save outputs in specified outdir
  2515. make_topvar_heatmap_iDA(
  2516. annotation_col = "Svexocytosis",
  2517. group1_vec = svexo_group1,
  2518. group2_vec = svexo_group2,
  2519. n_top = n_top_drivers,
  2520. out_prefix = "SVexocytosis"
  2521. )
  2522. make_topvar_heatmap_iDA(
  2523. annotation_col = "Svendocytosis",
  2524. group1_vec = svfusion_group1,
  2525. group2_vec = svfusion_group2,
  2526. n_top = n_top_drivers,
  2527. out_prefix = "Svendocytosis"
  2528. )
  2529. make_topvar_heatmap_iDA(
  2530. annotation_col = "vATPase",
  2531. group1_vec = svfusion_group1,
  2532. group2_vec = svfusion_group2,
  2533. n_top = n_top_drivers,
  2534. out_prefix = "vATPase"
  2535. )
  2536. make_topvar_heatmap_iDA(
  2537. annotation_col = "SVfusion",
  2538. group1_vec = svfusion_group1,
  2539. group2_vec = svfusion_group2,
  2540. n_top = n_top_drivers,
  2541. out_prefix = "SVfusion"
  2542. )
  2543. dev.off()
  2544. dev.off()
  2545. ```
  2546. # PCA on iDA annotation subsets
  2547. #### PCA on iDA Svendocytosis","vATPase","SVfusion","Svexocytosis" in separate iterations
  2548. ```{r}
  2549. # --------------------------------------------------- #
  2550. # --- PCA drivers for synaptic-angle annotations in iDA
  2551. # --------------------------------------------------- #
  2552. library(tidyverse)
  2553. library(ggplot2)
  2554. # --- 1. define genotypes to highlight in PCA plots => mostly Sph-Mutants
  2555. highlight_genos <- c("ASAH1","CLN8","GBA1","ATP13A2","PSAP","SMPD1","HEXA","HEXB")
  2556. # --- 2. Define output dir and also user-select annotations which IDs will be PCA run on
  2557. # plot PC1 - PC5 in all pairwise permutations --> facet-wrap them
  2558. pca_out_dir <- out_dir_d50_ascd_avg_SV
  2559. candidate_annotations <- unique(c("Svendocytosis","vATPase","SVfusion","Svexocytosis"))
  2560. annotations_to_run <- intersect(candidate_annotations, colnames(complete_data_annotated_day50))
  2561. n_pcs_plot <- 5
  2562. # --- 3. define PCA function for iDA protein abundance
  2563. run_synaptic_pca_iDA <- function(annotation_col, n_pcs = 5) {
  2564. ann_long <- complete_data_annotated_day50 %>%
  2565. dplyr::filter(.data[[annotation_col]] == 1) %>%
  2566. dplyr::select(Genes, matches("_iDA_log2_fold_change$")) %>%
  2567. tidyr::pivot_longer(cols = -Genes, names_to = "Condition", values_to = "log2FC") %>%
  2568. dplyr::mutate(genotype = sub("_.*$", "", Condition)) %>%
  2569. dplyr::select(genotype, Genes, log2FC)
  2570. if (nrow(ann_long) == 0) {
  2571. message("No rows after filtering for ", annotation_col, ". Skipping.")
  2572. return(invisible(NULL))
  2573. }
  2574. wide_geno_by_gene <- ann_long %>%
  2575. tidyr::pivot_wider(names_from = Genes, values_from = log2FC) %>%
  2576. dplyr::arrange(genotype)
  2577. geno_ids <- wide_geno_by_gene$genotype
  2578. mat <- wide_geno_by_gene %>% select(-genotype) %>% as.data.frame()
  2579. # 3.1 drop genes with any NA across genotypes
  2580. mat <- mat[, colSums(is.na(mat)) == 0, drop = FALSE]
  2581. # 3.2 drop zero-variance genes (constant across genotypes)
  2582. sds <- apply(mat, 2, sd)
  2583. const_genes <- names(sds)[!is.finite(sds) | sds == 0]
  2584. if (length(const_genes) > 0) {
  2585. # save the dropped list
  2586. readr::write_csv(tibble(Genes = const_genes),
  2587. file.path(pca_out_dir, paste0(annotation_col, "_iDA_PCA_constant_genes_removed.csv")))
  2588. mat <- mat[, setdiff(colnames(mat), const_genes), drop = FALSE]
  2589. }
  2590. # 3.3 keep only genotypes with at least one non-NA left (should already hold)
  2591. keep_rows <- rowSums(is.na(mat)) < ncol(mat)
  2592. mat <- mat[keep_rows, , drop = FALSE]
  2593. geno_ids <- geno_ids[keep_rows]
  2594. if (ncol(mat) < 2 || nrow(mat) < 2) {
  2595. message("Not enough variable genes or genotypes for PCA in ", annotation_col, ". Skipping.")
  2596. return(invisible(NULL))
  2597. }
  2598. # 3.4 center PCA matrix & scale
  2599. pca_fit <- prcomp(mat, center = TRUE, scale. = TRUE)
  2600. pcs_to_use <- min(n_pcs, ncol(pca_fit$x))
  2601. scores_df <- as.data.frame(pca_fit$x[, 1:pcs_to_use, drop = FALSE]) %>%
  2602. dplyr::mutate(genotype = geno_ids) %>%
  2603. relocate(genotype)
  2604. loadings_df <- as.data.frame(pca_fit$rotation[, 1:pcs_to_use, drop = FALSE]) %>%
  2605. rownames_to_column("Genes")
  2606. # 3.5 facet all PC pairings among first n PCs
  2607. pc_pairs <- combn(paste0("PC", 1:pcs_to_use), 2, simplify = FALSE)
  2608. plot_df <- purrr::map_dfr(pc_pairs, function(p) {
  2609. tibble(
  2610. genotype = scores_df$genotype,
  2611. x = scores_df[[p[1]]],
  2612. y = scores_df[[p[2]]],
  2613. panel = paste(p[1], "vs", p[2]),
  2614. highlight = genotype %in% highlight_genos
  2615. )
  2616. })
  2617. # 3.6 define colors for highlighed genotypes and background genotypes
  2618. geno_colors <- setNames(
  2619. ifelse(unique(plot_df$genotype) %in% highlight_genos, "dodgerblue", "grey60"),
  2620. unique(plot_df$genotype)
  2621. )
  2622. # 3.7 plot PCA facetplot
  2623. p_facets <- ggplot(plot_df, aes(x = x, y = y)) +
  2624. geom_point(aes(color = genotype), size = 1.6, alpha = 0.85) +
  2625. geom_text_repel(
  2626. data = subset(plot_df, highlight),
  2627. aes(x = x, y = y, label = genotype, color = genotype),
  2628. size = 2.5,
  2629. max.overlaps = Inf,
  2630. min.segment.length = 0,
  2631. box.padding = 0.3,
  2632. segment.color = "grey50"
  2633. ) +
  2634. scale_color_manual(values = geno_colors) +
  2635. facet_wrap(~ panel, scales = "free") +
  2636. labs(x = NULL, y = NULL,
  2637. title = paste0(annotation_col, " iDA PCA: pairwise PCs (top ", pcs_to_use, ")"),
  2638. color = "Genotype") +
  2639. theme_bw() +
  2640. theme(panel.grid = element_blank())
  2641. # 3.7 save PCA scores, loadings and plot
  2642. readr::write_csv(scores_df, file.path(pca_out_dir, paste0(annotation_col, "_iDA_PCA_scores.csv")))
  2643. readr::write_csv(loadings_df, file.path(pca_out_dir, paste0(annotation_col, "_iDA_PCA_loadings.csv")))
  2644. ggsave(file.path(pca_out_dir, paste0(annotation_col, "_iDA_PCA_PCpairs_facet.pdf")), p_facets, width = 7.5, height = 6.0, units = "in")
  2645. invisible(list(scores = scores_df, loadings = loadings_df))
  2646. }
  2647. # --- 4. run for all available annotations (use walk() from purr)
  2648. walk(annotations_to_run, ~ run_synaptic_pca_iDA(.x, n_pcs = n_pcs_plot))
  2649. ```
  2650. #### PCA on iDA union annotation
  2651. ```{r}
  2652. highlight_genos <- c("ASAH1","CLN8","GBA1","ATP13A2","PSAP","SMPD1","HEXA","HEXB")
  2653. annotations_to_combine <- c("Svexocytosis","Svendocytosis","vATPase","SVfusion")
  2654. # --- 1. get combined gene set
  2655. combined_genes <- complete_data_annotated_day50 %>%
  2656. dplyr::filter(rowSums(select(., all_of(annotations_to_combine))) > 0) %>%
  2657. pull(Genes) %>%
  2658. unique()
  2659. # --- 2. subset iDA log2FC data
  2660. ida_df <- complete_data_annotated_day50 %>%
  2661. dplyr::filter(Genes %in% combined_genes) %>%
  2662. dplyr::select(Genes, matches("_iDA_log2_fold_change$")) %>%
  2663. tidyr::pivot_longer(cols = -Genes, names_to = "Condition", values_to = "log2FC") %>%
  2664. dplyr::mutate(genotype = sub("_.*$", "", Condition)) %>%
  2665. tidyr::pivot_wider(names_from = Genes, values_from = log2FC) %>%
  2666. dplyr::arrange(genotype)
  2667. geno_ids <- ida_df$genotype
  2668. mat <- ida_df %>% dplyr::select(-genotype) %>% as.data.frame()
  2669. # 2.1 Ensure all features are numeric
  2670. mat <- mat %>% mutate(across(everything(), as.numeric))
  2671. # 2.2 Remove genes with any NA across genotypes
  2672. mat <- mat[, colSums(is.na(mat)) == 0, drop = FALSE]
  2673. # 2.3 Calculate SDs
  2674. sds <- apply(mat, 2, sd, na.rm = TRUE)
  2675. # 2.4 Drop zero-variance (constant) genes
  2676. mat <- mat[, sds > 0, drop = FALSE]
  2677. # --- 3. run PCA
  2678. pca_combined <- prcomp(mat, center = TRUE, scale. = TRUE)
  2679. # --- 4. PC permutations plot
  2680. # 4.1 scores for genotypes
  2681. scores_df <- as.data.frame(pca_combined$x) %>%
  2682. dplyr::mutate(genotype = geno_ids) %>%
  2683. relocate(genotype)
  2684. pcs_to_use <- min(5, ncol(pca_combined$x))
  2685. pc_names <- paste0("PC", 1:pcs_to_use)
  2686. # 4.2 build all PC pair panels with x = first, y = second
  2687. pc_pairs <- combn(pc_names, 2, simplify = FALSE)
  2688. plot_df <- purrr::map_dfr(pc_pairs, function(p) {
  2689. tibble(
  2690. genotype = scores_df$genotype,
  2691. x = scores_df[[p[1]]],
  2692. y = scores_df[[p[2]]],
  2693. panel = paste0(p[1], " vs ", p[2]),
  2694. highlight = genotype %in% highlight_genos
  2695. )
  2696. })
  2697. # 4.3 color map: highlights in dodgerblue, others grey60
  2698. geno_levels <- unique(scores_df$genotype)
  2699. geno_colors <- setNames(
  2700. ifelse(geno_levels %in% highlight_genos, "dodgerblue", "grey60"),
  2701. geno_levels
  2702. )
  2703. # 4.4 define plotting function for facets
  2704. p_facets <- ggplot(plot_df, aes(x = x, y = y)) +
  2705. geom_point(aes(color = genotype), size = 1.6, alpha = 0.85) +
  2706. geom_text_repel(
  2707. data = dplyr::filter(plot_df, highlight),
  2708. aes(label = genotype, color = genotype),
  2709. size = 2.5,
  2710. max.overlaps = Inf,
  2711. min.segment.length = 0,
  2712. box.padding = 0.3,
  2713. segment.color = "grey50"
  2714. ) +
  2715. scale_color_manual(values = geno_colors) +
  2716. facet_wrap(~ panel, scales = "free") +
  2717. labs(
  2718. x = NULL, y = NULL,
  2719. title = "iDA PCA on union of SV annotations (PC1–PC5 pairings)",
  2720. color = "Genotype"
  2721. ) +
  2722. theme_bw() +
  2723. theme(panel.grid = element_blank())
  2724. # 4.5 save PDF
  2725. ggsave(file.path(pca_out_dir, "iDA_union_annotations_PCA_PCpairs_facet.pdf"), p_facets, width = 7.5, height = 6.0, units = "in")
  2726. # --- 5. Save main drivers for each PC behind GBA1 and ASAH1
  2727. scores_df <- as.data.frame(pca_combined$x) %>% mutate(genotype = geno_ids)
  2728. # --- 6. Call function for ASAH1, PC of interest = PC where ASAH1 has sig. score (check on PCA plots)
  2729. pc_for_asah1 <- which.max(abs(scores_df %>% dplyr::filter(genotype == "ASAH1") %>% dplyr::select(PC3)))
  2730. top_asah1_drivers <- as.data.frame(pca_combined$rotation)[, pc_for_asah1, drop = FALSE] %>%
  2731. tibble::rownames_to_column("Genes") %>%
  2732. dplyr::rename(loading = 2) %>%
  2733. dplyr::arrange(desc(abs(loading))) %>%
  2734. dplyr::slice_head(n = 20)
  2735. write_csv(top_asah1_drivers, file.path(pca_out_dir, paste0("combined_PCA_top20_drivers_", "ASAH1", "_PC", pc_for_asah1, ".csv")))
  2736. # --- 7. Repeat for GBA1
  2737. pc_for_gba1 <- which.max(abs(scores_df %>% dplyr::filter(genotype == "GBA1") %>% dplyr::select(PC3)))
  2738. top_gba1_drivers <- as.data.frame(pca_combined$rotation)[, pc_for_gba1, drop = FALSE] %>%
  2739. tibble::rownames_to_column("Genes") %>%
  2740. dplyr::rename(loading = 2) %>%
  2741. dplyr::arrange(desc(abs(loading))) %>%
  2742. dplyr::slice_head(n = 20)
  2743. write_csv(top_gba1_drivers, file.path(pca_out_dir, paste0("combined_PCA_top20_drivers_", "GBA1", "_PC", pc_for_gba1, ".csv")))
  2744. ```
  2745. # Cell type comparison: iN vs iDA (ctrl, day 50)
  2746. ```{r}
  2747. # -------------------------------------------------- #
  2748. # Build celltypecompare_df
  2749. # -------------------------------------------------- #
  2750. # --- Step 1: Extract ctrl neurons at day 50 from cleaned_df (raw quan)
  2751. ctrl_d50 <- cleaned_df %>%
  2752. dplyr::filter(genotype == "ctrl", day == 50, neuron %in% c("iN", "iDA")) %>%
  2753. dplyr::select(Genes, neuron, replicate, quan)
  2754. # --- Step 2: Mean quan per gene per cell type
  2755. mean_by_celltype <- ctrl_d50 %>%
  2756. dplyr::group_by(Genes, neuron) %>%
  2757. dplyr::summarise(mean_quan = mean(quan, na.rm = TRUE), .groups = "drop") %>%
  2758. tidyr::pivot_wider(names_from = neuron, values_from = mean_quan, names_prefix = "mean_")
  2759. # --- Step 3: Two-sample t-test per gene (iN replicates vs iDA replicates, raw quan)
  2760. # Only genes detected in both cell types are tested.
  2761. gene_list_ct <- intersect(
  2762. ctrl_d50 %>% dplyr::filter(neuron == "iN") %>% pull(Genes) %>% unique(),
  2763. ctrl_d50 %>% dplyr::filter(neuron == "iDA") %>% pull(Genes) %>% unique()
  2764. )
  2765. ttest_results_ct <- lapply(gene_list_ct, function(g) {
  2766. iN_vals <- ctrl_d50 %>% dplyr::filter(Genes == g, neuron == "iN") %>% pull(quan)
  2767. iDA_vals <- ctrl_d50 %>% dplyr::filter(Genes == g, neuron == "iDA") %>% pull(quan)
  2768. if (length(iN_vals) < 2 | length(iDA_vals) < 2) {
  2769. return(data.frame(Genes = g, ttest_p = NA_real_))
  2770. }
  2771. tt <- suppressWarnings(t.test(iN_vals, iDA_vals, var.equal = FALSE))
  2772. data.frame(Genes = g, ttest_p = tt$p.value)
  2773. })
  2774. ttest_ct_df <- dplyr::bind_rows(ttest_results_ct)
  2775. ttest_ct_df$ttest_padj <- p.adjust(ttest_ct_df$ttest_p, method = "BH")
  2776. # --- Step 3b: Linear model per gene – quan ~ CellType (iDA reference)
  2777. # coefficient CellTypeiN = iN - iDA effect; p-value from model summary
  2778. lm_results_ct <- lapply(gene_list_ct, function(g) {
  2779. df_lm <- ctrl_d50 %>%
  2780. dplyr::filter(Genes == g) %>%
  2781. dplyr::mutate(CellType = factor(neuron, levels = c("iDA", "iN")))
  2782. if (nrow(df_lm) < 4) return(NULL)
  2783. fit <- tryCatch(lm(quan ~ CellType, data = df_lm), error = function(e) NULL)
  2784. if (is.null(fit)) return(NULL)
  2785. s <- summary(fit)
  2786. ct <- coef(fit)["CellTypeiN"]
  2787. p <- coef(s)["CellTypeiN", "Pr(>|t|)"]
  2788. data.frame(Genes = g, lm_coef_iN = as.numeric(ct), lm_p = p)
  2789. })
  2790. lm_ct_df <- dplyr::bind_rows(lm_results_ct)
  2791. lm_ct_df$lm_padj <- p.adjust(lm_ct_df$lm_p, method = "BH")
  2792. # --- Step 4: Ratio iN / iDA (keep only proteins quantified in both; avoid Inf/NaN)
  2793. ratio_ct_df <- mean_by_celltype %>%
  2794. dplyr::filter(!is.na(mean_iN) & !is.na(mean_iDA) & mean_iN > 0 & mean_iDA > 0) %>%
  2795. dplyr::mutate(
  2796. ratio_iN_iDA = mean_iN / mean_iDA,
  2797. log2_ratio_iN_iDA = log2(mean_iN / mean_iDA)
  2798. )
  2799. # --- Step 5: Join stats, annotate significance and direction (both methods)
  2800. # method 1: ttest + log2FC >= 1
  2801. # method 2: linear model p < 0.05 + log2FC >= 1
  2802. celltypecompare_base <- ratio_ct_df %>%
  2803. dplyr::left_join(ttest_ct_df, by = "Genes") %>%
  2804. dplyr::left_join(lm_ct_df, by = "Genes") %>%
  2805. dplyr::mutate(
  2806. sig_ttest = !is.na(ttest_p) & ttest_p < 0.05,
  2807. sig_fc = abs(log2_ratio_iN_iDA) >= 1,
  2808. direction = dplyr::case_when(
  2809. sig_ttest & sig_fc & ratio_iN_iDA > 1 ~ "sig_enriched_iN",
  2810. sig_ttest & sig_fc & ratio_iN_iDA < 1 ~ "sig_enriched_iDA",
  2811. TRUE ~ "unchanged"
  2812. ),
  2813. direction_lm = dplyr::case_when(
  2814. !is.na(lm_p) & lm_p < 0.05 & sig_fc & lm_coef_iN > 0 ~ "sig_enriched_iN",
  2815. !is.na(lm_p) & lm_p < 0.05 & sig_fc & lm_coef_iN < 0 ~ "sig_enriched_iDA",
  2816. TRUE ~ "unchanged"
  2817. )
  2818. )
  2819. # --- Step 6: Add binary subcellular annotation columns
  2820. celltypecompare_df <- celltypecompare_base %>%
  2821. dplyr::left_join(binary_matrix, by = "Genes")
  2822. # --- Step 7: Save dataframe
  2823. write.csv(celltypecompare_df, file = file.path(out_dir_d50_dataframes, "celltypecompare_iN_iDA.csv"), row.names = FALSE)
  2824. # -------------------------------------------------- #
  2825. # Barplot: relative enrichment per annotation group
  2826. # mean log2(iN/iDA) per subcellular annotation
  2827. # -------------------------------------------------- #
  2828. annot_cols_ct <- colnames(binary_matrix)[-1]
  2829. barplot_ct_data <- lapply(annot_cols_ct, function(ann) {
  2830. genes_in_ann <- binary_matrix %>%
  2831. dplyr::filter(.data[[ann]] == TRUE) %>%
  2832. pull(Genes)
  2833. sub_df <- celltypecompare_df %>%
  2834. dplyr::filter(Genes %in% genes_in_ann, is.finite(log2_ratio_iN_iDA))
  2835. if (nrow(sub_df) == 0) return(NULL)
  2836. data.frame(
  2837. Annotation = ann,
  2838. mean_log2ratio = mean(sub_df$log2_ratio_iN_iDA, na.rm = TRUE),
  2839. sem = sd(sub_df$log2_ratio_iN_iDA, na.rm = TRUE) / sqrt(nrow(sub_df)),
  2840. n_proteins = nrow(sub_df)
  2841. )
  2842. })
  2843. barplot_ct_data <- dplyr::bind_rows(barplot_ct_data)
  2844. barplot_ct_data <- barplot_ct_data %>%
  2845. dplyr::arrange(mean_log2ratio) %>%
  2846. dplyr::mutate(Annotation = factor(Annotation, levels = Annotation))
  2847. p_ct_barplot <- ggplot(barplot_ct_data, aes(x = Annotation, y = mean_log2ratio)) +
  2848. geom_col(fill = "grey75", width = 0.7) +
  2849. geom_errorbar(aes(ymin = mean_log2ratio - sem, ymax = mean_log2ratio + sem),
  2850. width = 0.3, linewidth = 0.3) +
  2851. geom_hline(yintercept = 0, linetype = "dashed", color = "black", linewidth = 0.3) +
  2852. labs(x = "Annotation", y = "Mean log2(iN / iDA)",
  2853. title = "Relative Enrichment of Subcellular Annotations: iN vs iDA (ctrl, day 50)") +
  2854. theme_bw(base_size = 6) +
  2855. theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5), panel.grid = element_blank())
  2856. ggsave(file.path(out_dir_d50_QC, "celltype_barplot_annotation_iN_iDA.pdf"), p_ct_barplot, width = 6, height = 4, device = cairo_pdf)
  2857. readr::write_csv(barplot_ct_data, file.path(out_dir_d50_QC, "celltype_barplot_annotation_iN_iDA_sourcedata.csv"))
  2858. # -------------------------------------------------- #
  2859. # Upset plots
  2860. # 1. ttest + log2FC method
  2861. # 2. linear model method
  2862. # 3. combined – overlap between both methods (4 sets)
  2863. # -------------------------------------------------- #
  2864. library(UpSetR)
  2865. # gene sets: ttest + log2FC method
  2866. upset_ttest_list <- list(
  2867. unchanged = celltypecompare_df %>% dplyr::filter(direction == "unchanged") %>% pull(Genes),
  2868. sig_enriched_iN = celltypecompare_df %>% dplyr::filter(direction == "sig_enriched_iN") %>% pull(Genes),
  2869. sig_enriched_iDA = celltypecompare_df %>% dplyr::filter(direction == "sig_enriched_iDA") %>% pull(Genes)
  2870. )
  2871. # gene sets: linear model method
  2872. upset_lm_list <- list(
  2873. unchanged = celltypecompare_df %>% dplyr::filter(direction_lm == "unchanged") %>% pull(Genes),
  2874. sig_enriched_iN = celltypecompare_df %>% dplyr::filter(direction_lm == "sig_enriched_iN") %>% pull(Genes),
  2875. sig_enriched_iDA = celltypecompare_df %>% dplyr::filter(direction_lm == "sig_enriched_iDA") %>% pull(Genes)
  2876. )
  2877. # gene sets: combined (4 sets for cross-method overlap)
  2878. upset_combined_list <- list(
  2879. ttest_fc_iN = celltypecompare_df %>% dplyr::filter(direction == "sig_enriched_iN") %>% pull(Genes),
  2880. ttest_fc_iDA = celltypecompare_df %>% dplyr::filter(direction == "sig_enriched_iDA") %>% pull(Genes),
  2881. lm_iN = celltypecompare_df %>% dplyr::filter(direction_lm == "sig_enriched_iN") %>% pull(Genes),
  2882. lm_iDA = celltypecompare_df %>% dplyr::filter(direction_lm == "sig_enriched_iDA") %>% pull(Genes)
  2883. )
  2884. # plot 1: ttest + log2FC
  2885. pdf(file.path(out_dir_d50_QC, "celltype_upset_ttest_fc.pdf"), width = 5, height = 4)
  2886. UpSetR::upset(UpSetR::fromList(upset_ttest_list),
  2887. sets = c("unchanged", "sig_enriched_iN", "sig_enriched_iDA"),
  2888. order.by = "freq", mainbar.y.label = "Protein Count", sets.x.label = "Set Size",
  2889. main.bar.color = "grey75", sets.bar.color = "grey75", text.scale = 1)
  2890. grid::grid.text("iN vs iDA – t-test + log2FC filter", x = 0.65, y = 0.97,
  2891. gp = grid::gpar(fontsize = 7))
  2892. dev.off()
  2893. # plot 2: linear model
  2894. pdf(file.path(out_dir_d50_QC, "celltype_upset_lm.pdf"), width = 5, height = 4)
  2895. UpSetR::upset(UpSetR::fromList(upset_lm_list),
  2896. sets = c("unchanged", "sig_enriched_iN", "sig_enriched_iDA"),
  2897. order.by = "freq", mainbar.y.label = "Protein Count", sets.x.label = "Set Size",
  2898. main.bar.color = "grey75", sets.bar.color = "grey75", text.scale = 1)
  2899. grid::grid.text("iN vs iDA – linear model", x = 0.65, y = 0.97,
  2900. gp = grid::gpar(fontsize = 7))
  2901. dev.off()
  2902. # plot 3: combined cross-method overlap
  2903. pdf(file.path(out_dir_d50_QC, "celltype_upset_combined.pdf"), width = 6, height = 4)
  2904. UpSetR::upset(UpSetR::fromList(upset_combined_list),
  2905. sets = c("ttest_fc_iN", "ttest_fc_iDA", "lm_iN", "lm_iDA"),
  2906. order.by = "freq", mainbar.y.label = "Protein Count", sets.x.label = "Set Size",
  2907. main.bar.color = "grey75", sets.bar.color = "grey75", text.scale = 1)
  2908. grid::grid.text("iN vs iDA – method comparison (ttest+FC vs lm)", x = 0.65, y = 0.97,
  2909. gp = grid::gpar(fontsize = 7))
  2910. dev.off()
  2911. # source files
  2912. readr::write_csv(
  2913. celltypecompare_df %>% dplyr::select(Genes, mean_iN, mean_iDA, log2_ratio_iN_iDA, ttest_p, ttest_padj, direction),
  2914. file.path(out_dir_d50_QC, "celltype_upset_ttest_fc_sourcedata.csv")
  2915. )
  2916. readr::write_csv(
  2917. celltypecompare_df %>% dplyr::select(Genes, mean_iN, mean_iDA, log2_ratio_iN_iDA, lm_coef_iN, lm_p, lm_padj, direction_lm),
  2918. file.path(out_dir_d50_QC, "celltype_upset_lm_sourcedata.csv")
  2919. )
  2920. # -------------------------------------------------- #
  2921. # Stacked barplots: count + fraction per annotation
  2922. # run for both methods via helper function
  2923. # -------------------------------------------------- #
  2924. direction_colors_ct <- c(sig_enriched_iN = "grey45", unchanged = "grey80", sig_enriched_iDA = "dodgerblue3")
  2925. direction_labels_ct <- c(sig_enriched_iN = "Enriched iN", unchanged = "Unchanged", sig_enriched_iDA = "Enriched iDA")
  2926. all_dirs_ct <- c("sig_enriched_iDA", "unchanged", "sig_enriched_iN")
  2927. make_stacked_ct <- function(celltypecompare_df, direction_col, method_label, out_dir) {
  2928. stacked_list <- lapply(annot_cols_ct, function(ann) {
  2929. genes_in_ann <- binary_matrix %>% dplyr::filter(.data[[ann]] == TRUE) %>% pull(Genes)
  2930. sub_df <- celltypecompare_df %>% dplyr::filter(Genes %in% genes_in_ann)
  2931. if (nrow(sub_df) == 0) return(NULL)
  2932. n_total <- nrow(sub_df)
  2933. counts <- table(factor(sub_df[[direction_col]], levels = all_dirs_ct))
  2934. data.frame(Annotation = ann, direction = names(counts),
  2935. n = as.integer(counts), n_total = n_total,
  2936. fraction = as.numeric(counts) / n_total)
  2937. })
  2938. stacked_data <- dplyr::bind_rows(stacked_list)
  2939. annot_order <- stacked_data %>%
  2940. dplyr::filter(direction == "sig_enriched_iN") %>%
  2941. dplyr::arrange(desc(fraction)) %>%
  2942. pull(Annotation)
  2943. stacked_data <- stacked_data %>%
  2944. dplyr::mutate(Annotation = factor(Annotation, levels = annot_order),
  2945. direction = factor(direction, levels = all_dirs_ct))
  2946. # absolute barplot numbers
  2947. p_count <- ggplot(stacked_data, aes(x = Annotation, y = n, fill = direction)) +
  2948. geom_col(width = 0.95, color = "black", linewidth = 0.25) +
  2949. scale_fill_manual(values = direction_colors_ct, labels = direction_labels_ct, name = NULL) +
  2950. labs(x = NULL, y = "Protein count",
  2951. title = paste0("Cell type proteins per annotation – count (", method_label, ")")) +
  2952. theme_bw(base_size = 6) +
  2953. theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5),
  2954. panel.grid = element_blank(), legend.position = "bottom")
  2955. # fraction barplot
  2956. p_frac <- ggplot(stacked_data, aes(x = Annotation, y = fraction, fill = direction)) +
  2957. geom_col(width = 0.95, color = "black", linewidth = 0.25) +
  2958. geom_text(aes(label = dplyr::if_else(n > 0, paste0(n, "\n(", round(fraction * 100, 1), "%)"), ""),
  2959. group = direction),
  2960. position = position_stack(vjust = 0.5), size = 1.8, color = "black", angle = 90) +
  2961. scale_fill_manual(values = direction_colors_ct, labels = direction_labels_ct, name = NULL) +
  2962. scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  2963. labs(x = NULL, y = "Fraction of proteins",
  2964. title = paste0("Cell type proteins per annotation – fraction (", method_label, ")")) +
  2965. theme_bw(base_size = 6) +
  2966. theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5),
  2967. panel.grid = element_blank(), legend.position = "bottom")
  2968. ggsave(file.path(out_dir, paste0("celltype_stacked_barplot_count_", method_label, ".pdf")), p_count, width = 7, height = 4, device = cairo_pdf)
  2969. ggsave(file.path(out_dir, paste0("celltype_stacked_barplot_fraction_", method_label, ".pdf")), p_frac, width = 7, height = 4, device = cairo_pdf)
  2970. # summary source file csv
  2971. readr::write_csv(stacked_data, file.path(out_dir, paste0("celltype_stacked_barplot_", method_label, "_sourcedata.csv")))
  2972. # gene-level source file
  2973. stacked_genes_list <- lapply(annot_cols_ct, function(ann) {
  2974. genes_in_ann <- binary_matrix %>% dplyr::filter(.data[[ann]] == TRUE) %>% pull(Genes)
  2975. celltypecompare_df %>%
  2976. dplyr::filter(Genes %in% genes_in_ann) %>%
  2977. dplyr::select(Genes, mean_iN, mean_iDA, log2_ratio_iN_iDA, ttest_p, ttest_padj,
  2978. lm_coef_iN, lm_p, lm_padj, all_of(direction_col)) %>%
  2979. dplyr::mutate(Annotation = ann)
  2980. })
  2981. stacked_genes_df <- dplyr::bind_rows(stacked_genes_list) %>%
  2982. dplyr::rename(direction = all_of(direction_col)) %>%
  2983. dplyr::select(Annotation, Genes, direction, mean_iN, mean_iDA, log2_ratio_iN_iDA,
  2984. ttest_p, ttest_padj, lm_coef_iN, lm_p, lm_padj)
  2985. readr::write_csv(stacked_genes_df, file.path(out_dir, paste0("celltype_stacked_barplot_", method_label, "_genes_sourcedata.csv")))
  2986. }
  2987. # run for ttest + log2FC method
  2988. make_stacked_ct(celltypecompare_df, "direction","ttest_fc", out_dir_d50_QC)
  2989. # run for linear model method
  2990. make_stacked_ct(celltypecompare_df, "direction_lm", "lm", out_dir_d50_QC)
  2991. # gene vectors for downstream use
  2992. genes_ct_sig_iN <- celltypecompare_df %>% dplyr::filter(direction == "sig_enriched_iN") %>% pull(Genes)
  2993. genes_ct_sig_iDA <- celltypecompare_df %>% dplyr::filter(direction == "sig_enriched_iDA") %>% pull(Genes)
  2994. # -------------------------------------------------- #
  2995. # Annotation-level analysis: pie chart + GO enrichment
  2996. # direction_lm used for significance; sig_enriched_iN and sig_enriched_iDA only
  2997. # GO background: all proteins in celltypecompare_df
  2998. # call: plot_annotation_analysis("Mito", ...) or any binary_matrix annotation
  2999. # -------------------------------------------------- #
  3000. library(clusterProfiler)
  3001. library(org.Hs.eg.db)
  3002. plot_annotation_analysis <- function(annotation, celltypecompare_df, binary_matrix, out_dir) {
  3003. # proteins in this annotation
  3004. genes_in_ann <- binary_matrix %>%
  3005. dplyr::filter(.data[[annotation]] == TRUE) %>%
  3006. pull(Genes)
  3007. # sig proteins only (direction_lm)
  3008. ann_sig_df <- celltypecompare_df %>%
  3009. dplyr::filter(Genes %in% genes_in_ann,
  3010. direction_lm %in% c("sig_enriched_iN", "sig_enriched_iDA"))
  3011. if (nrow(ann_sig_df) == 0) {
  3012. message("No significant proteins for annotation: ", annotation)
  3013. return(invisible(NULL))
  3014. }
  3015. # --- Pie chart
  3016. pie_data <- ann_sig_df %>%
  3017. dplyr::count(direction_lm) %>%
  3018. dplyr::mutate(
  3019. pct = round(n / sum(n) * 100, 1),
  3020. pie_label = paste0(n, "\n(", pct, "%)"),
  3021. direction_lm = factor(direction_lm, levels = c("sig_enriched_iN", "sig_enriched_iDA"))
  3022. )
  3023. pie_colors_ann <- c(sig_enriched_iN = "grey45", sig_enriched_iDA = "dodgerblue3")
  3024. p_pie <- ggplot(pie_data, aes(x = "", y = n, fill = direction_lm)) +
  3025. geom_col(width = 1, color = "black", linewidth = 0.25) +
  3026. coord_polar(theta = "y") +
  3027. geom_text(aes(label = pie_label), position = position_stack(vjust = 0.5),
  3028. size = 2, color = "white") +
  3029. scale_fill_manual(values = pie_colors_ann,
  3030. labels = c(sig_enriched_iN = "Enriched iN", sig_enriched_iDA = "Enriched iDA"),
  3031. name = NULL) +
  3032. labs(title = paste0(annotation, " – significant proteins (lm, log2FC \u2265 1)")) +
  3033. theme_void(base_size = 6) +
  3034. theme(legend.position = "bottom", aspect.ratio = 1)
  3035. ggsave(file.path(out_dir, paste0("celltype_pie_", annotation, "_lm.pdf")), p_pie, width = 4, height = 4, device = cairo_pdf)
  3036. readr::write_csv(pie_data %>% dplyr::select(direction_lm, n, pct), file.path(out_dir, paste0("celltype_pie_", annotation, "_lm_sourcedata.csv"))
  3037. )
  3038. # --- GO enrichment per direction
  3039. bg_genes_ann <- unique(celltypecompare_df$Genes)
  3040. run_go_ann <- function(gene_set, label) {
  3041. if (length(gene_set) < 5) {
  3042. message("Too few genes for GO (", length(gene_set), "): ", label)
  3043. return(invisible(NULL))
  3044. }
  3045. for (ont in c("BP", "MF")) {
  3046. ego <- clusterProfiler::enrichGO(
  3047. gene = gene_set,
  3048. universe = bg_genes_ann,
  3049. OrgDb = org.Hs.eg.db,
  3050. keyType = "SYMBOL",
  3051. ont = ont,
  3052. pAdjustMethod = "BH",
  3053. pvalueCutoff = 0.05,
  3054. qvalueCutoff = 0.2,
  3055. readable = TRUE
  3056. )
  3057. if (is.null(ego) || nrow(as.data.frame(ego)) == 0) {
  3058. message("No GO ", ont, " results for: ", label)
  3059. next
  3060. }
  3061. readr::write_csv(as.data.frame(ego),
  3062. file.path(out_dir, paste0("GO_", ont, "_", label, "_sourcedata.csv")))
  3063. p_go <- clusterProfiler::dotplot(ego, showCategory = 5, font.size = 6) +
  3064. scale_color_gradient(low = "dodgerblue4", high = "lightblue", name = "p.adjust") +
  3065. labs(title = paste0("GO ", ont, " | ", label)) +
  3066. theme_bw(base_size = 6) +
  3067. theme(panel.grid = element_blank(),
  3068. axis.text.y = element_text(size = 5),
  3069. aspect.ratio = 1)
  3070. ggsave(file.path(out_dir, paste0("GO_", ont, "_", label, "_dotplot.pdf")), p_go, width = 4, height = 4, device = cairo_pdf)
  3071. }
  3072. }
  3073. genes_ann_iN <- ann_sig_df %>% dplyr::filter(direction_lm == "sig_enriched_iN") %>% pull(Genes)
  3074. genes_ann_iDA <- ann_sig_df %>% dplyr::filter(direction_lm == "sig_enriched_iDA") %>% pull(Genes)
  3075. run_go_ann(genes_ann_iN, paste0(annotation, "_sig_iN"))
  3076. run_go_ann(genes_ann_iDA, paste0(annotation, "_sig_iDA"))
  3077. }
  3078. # run for selected annotations
  3079. plot_annotation_analysis("Mito", celltypecompare_df, binary_matrix, out_dir_d50_QC)
  3080. plot_annotation_analysis("SynapseSVs", celltypecompare_df, binary_matrix, out_dir_d50_QC)
  3081. ```
  3082. # =================================================
  3083. # Module 3: Neuro QC & Synaptic Markers
  3084. # =================================================
  3085. Aimed to plot neuro markers to plot differences between neuron types
  3086. # Plot pre/post synaptic marker
  3087. ```{r}
  3088. # --------------------------------------------------- #
  3089. # neuro QC plots
  3090. # plot pre / post synaptic markers of ctrl iN vs iDA & plot
  3091. # --------------------------------------------------- #
  3092. # --- Step 1. add annotation to cleaned_df (from PCA calculation, since has raw intensities for each gene in it)
  3093. PCAclean_d50_transformed_df <- cleaned_df %>%
  3094. dplyr::filter(!Genes %in% contaminant_genes)
  3095. # --- Step 2. Join localization annotations into the filtered df
  3096. PCAclean_d50_annotated <- PCAclean_d50_transformed_df %>%
  3097. left_join(binary_matrix, by = "Genes")
  3098. # --- Step 3. Fill NAs with FALSE for localization columns
  3099. PCAclean_d50_annotated[is.na(PCAclean_d50_annotated)] <- FALSE
  3100. # --- Step 4. Dunction for plotting proteins of Interest
  3101. plot_multiple_genes <- function(genes, data = PCAclean_d50_annotated, filename = NULL) {
  3102. gene_df <- data %>%
  3103. dplyr::filter(Genes %in% genes, genotype == "ctrl", neuron %in% c("iN", "iDA"), day == 50)
  3104. if (!is.null(filename)) {
  3105. write.csv(gene_df, file = filename, row.names = FALSE)
  3106. }
  3107. ggplot(gene_df, aes(x = neuron, y = quan, fill = neuron)) +
  3108. geom_bar(stat = "summary", fun = mean, position = position_dodge(width = 0.6), width = 0.5) +
  3109. geom_jitter(aes(color = neuron), width = 0.15, size = 1.8, alpha = 0.6) +
  3110. geom_errorbar(
  3111. stat = "summary",
  3112. fun.data = mean_se,
  3113. width = 0.2,
  3114. position = position_dodge(width = 0.6)
  3115. ) +
  3116. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80")) +
  3117. scale_color_manual(values = c("iN" = "grey30", "iDA" = "grey10")) +
  3118. facet_wrap(~Genes, scales = "free_y") +
  3119. labs(
  3120. title = "Expression of Selected Proteins in Ctrl Neurons day 50",
  3121. x = "Neuron Type",
  3122. y = "Quantified Intensity (quan)"
  3123. ) +
  3124. theme_bw() +
  3125. theme(
  3126. legend.position = "none",
  3127. panel.grid = element_blank()
  3128. )
  3129. }
  3130. # --- Step 5. Call function and plot proteins of Interest
  3131. Neuro_QC_Proteins <- plot_multiple_genes(c("SYN1", "NEFH"), filename = file.path(out_dir_d50_QC, "diff132_d50_Neuro_QC_Proteins_sourcedata.csv"))
  3132. ggsave(file.path(out_dir_d50_QC, "diff132_d50_Neuro_QC_Proteins.pdf"), Neuro_QC_Proteins, width = 4, height = 4, device = cairo_pdf)
  3133. Neuro_QC_Proteins <- plot_multiple_genes(c("TH", "KCNJ6"), filename = file.path(out_dir_d50_QC, "diff132_d50_Neuro_QC_Proteins_DA_sourcedata.csv"))
  3134. ggsave(file.path(out_dir_d50_QC, "diff132_d50_Neuro_QC_Proteins_DA.pdf"), Neuro_QC_Proteins, width = 4, height = 4, device = cairo_pdf)
  3135. ```
  3136. # Neuro annotation plot by neuron x genotype
  3137. Plotted in ascending order
  3138. ```{r}
  3139. # --------------------------------------------------- #
  3140. # neuro QC plots
  3141. # Function for all annotations. By neuron type, in ascending order
  3142. # --------------------------------------------------- #
  3143. # --- Define annotation list ---
  3144. # all_ann <- c("Mito", "MitoIMS", "MitoMatrix", "MitoMIM", "MitoMOM", "OXPHOS",
  3145. # "mtComplexI", "Golgi", "Lysosome", "ER", "Cytoplasm", "Nucleus",
  3146. # "Autophagy", "SynGO", "Endo_iN_curated_Hundley", "SynapseSVs",
  3147. # "Presynaptic", "Postsynaptic", "NeuroDev", "EndoLyso", "EarlyEndosome",
  3148. # "RecyclingEndosome", "SVfusion", "Svendocytosis", "Svexocytosis", "vATPase")
  3149. all_ann <- c("Svexocytosis")
  3150. # --- 1. Reshape log2FC data into long format ---
  3151. ascd_ann_long_df <- complete_data_annotated_day50 %>%
  3152. tidyr::pivot_longer(
  3153. cols = matches("_log2_fold_change$"),
  3154. names_to = "Condition",
  3155. values_to = "log2FC"
  3156. ) %>%
  3157. dplyr::mutate(
  3158. neuron = ifelse(grepl("_iDA_", Condition), "iDA", "iN"),
  3159. genotype = sub("_.*", "", Condition), # everything before first "_" is genotype
  3160. group = paste(genotype, neuron, sep = "_")
  3161. ) %>%
  3162. # pivot annotations
  3163. tidyr::pivot_longer(cols = all_of(all_ann), names_to = "Annotation", values_to = "is_annotated") %>%
  3164. dplyr::filter(is_annotated == 1)
  3165. # --- 2. Compute average log2FC per annotation per group ---
  3166. ascd_avg_ann_fc_summary <- ascd_ann_long_df %>%
  3167. dplyr::group_by(group, neuron, Annotation) %>%
  3168. dplyr::summarise(
  3169. avg_log2FC = mean(log2FC, na.rm = TRUE),
  3170. sd_log2FC = sd(log2FC, na.rm = TRUE),
  3171. .groups = "drop")
  3172. # --- 3. Order groups within each neuron ---
  3173. ascd_avg_ann_fc_summary <- ascd_avg_ann_fc_summary %>%
  3174. dplyr::arrange(neuron, avg_log2FC) %>%
  3175. group_by(neuron) %>%
  3176. dplyr::mutate(group = factor(group, levels = unique(group)))
  3177. # --- 4. Merge ordering back into main df ---
  3178. ascd_ann_long_df <- ascd_ann_long_df %>%
  3179. inner_join(ascd_avg_ann_fc_summary %>% dplyr::select(group, neuron),
  3180. by = c("group", "neuron")) %>%
  3181. dplyr::mutate(group = factor(group, levels = levels(ascd_avg_ann_fc_summary$group)))
  3182. # --- 5. Plot: mean ± SD from summary ---
  3183. d50_ascd_ann_plot <- ggplot(ascd_avg_ann_fc_summary, aes(x = group, y = avg_log2FC)) +
  3184. geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  3185. geom_linerange(aes(ymin = avg_log2FC - sd_log2FC,
  3186. ymax = avg_log2FC + sd_log2FC),
  3187. color = "grey70", linewidth = 1.2, alpha = 0.8) +
  3188. geom_line(aes(group = 1), color = "red", linewidth = 0.9) +
  3189. geom_point(color = "red", size = 0.8) +
  3190. facet_wrap(~ neuron, scales = "free_x") +
  3191. theme_bw(base_size = 6) +
  3192. theme(
  3193. axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
  3194. panel.grid = element_blank()
  3195. ) +
  3196. labs(
  3197. title = "Functional Annotations: mean ± SD by Genotype x Neuron",
  3198. x = "Genotype x Neuron (sorted by mean log2FC)",
  3199. y = expression(log[2]*"(KO / Ctrl)")
  3200. )
  3201. d50_ascd_ann_plot
  3202. # --- 6. Save PDF ---
  3203. ggsave(file.path(out_dir_d50_ascd_avg, "diff132_d50_ascending_Avg_Svexocytosis.pdf"), d50_ascd_ann_plot, width = 4, height = 3, device = cairo_pdf)
  3204. # --- 7. Zoom into first 6 groups per neuron ---
  3205. ascd_top6_summary <- ascd_avg_ann_fc_summary %>%
  3206. dplyr::group_by(neuron) %>%
  3207. slice_head(n = 6) %>%
  3208. ungroup() %>%
  3209. dplyr::mutate(group = factor(group, levels = levels(ascd_avg_ann_fc_summary$group)))
  3210. # --- 8. Plot top 6
  3211. d50_ascd_ann_plot_top6 <- ggplot(ascd_top6_summary, aes(x = group, y = avg_log2FC)) +
  3212. geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  3213. geom_linerange(aes(ymin = avg_log2FC - sd_log2FC,
  3214. ymax = avg_log2FC + sd_log2FC),
  3215. color = "grey70", linewidth = 1.2, alpha = 0.8) +
  3216. geom_line(aes(group = 1), color = "red", linewidth = 0.9) +
  3217. geom_point(color = "red", size = 0.8) +
  3218. facet_wrap(~ neuron, scales = "free_x") +
  3219. theme_bw(base_size = 6) +
  3220. theme(
  3221. axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
  3222. panel.grid = element_blank()
  3223. ) +
  3224. labs(
  3225. title = "Top Genotypes: mean ± SD",
  3226. x = "Genotype x Neuron (Top 6)",
  3227. y = expression(log[2]*"(KO / Ctrl)")
  3228. )
  3229. # --- 9. Save second PDF ---
  3230. ggsave(file.path(out_dir_d50_ascd_avg, "diff132_d50_Top5_Avg_Svexocytosis.pdf"), d50_ascd_ann_plot_top6, width = 2, height = 3, device = cairo_pdf)
  3231. ```
  3232. # Plot differentiation markers for all KOs
  3233. ```{r}
  3234. # =================================================
  3235. # Differentiation QC
  3236. # =================================================
  3237. # --- Setup output directory
  3238. out_dir_diffQC <- file.path(out_dir_d50_QC, "DifferentiationQC")
  3239. dir.create(out_dir_diffQC, recursive = TRUE, showWarnings = FALSE)
  3240. # --- Create differentiation_df from clean_d50_df
  3241. # Goal: reshape data so ctrl is a genotype alongside KOs for barplot comparison
  3242. # clean_d50_df has mean_ctrl/sd_ctrl (same for all KOs) and mean_ko/sd_ko per condition
  3243. # We extract ctrl as its own rows, then bind with KO rows
  3244. # Ctrl: take one row per gene x neuron (mean_ctrl is identical across KO rows)
  3245. ctrl_df <- clean_d50_df %>%
  3246. dplyr::filter(day == 50) %>%
  3247. distinct(Genes, neuron, .keep_all = TRUE) %>%
  3248. transmute(Genes, neuron, genotype = "ctrl", mean_intensity = mean_ctrl, sd_intensity = sd_ctrl)
  3249. # KO: rename mean_ko/sd_ko to match ctrl structure
  3250. ko_df <- clean_d50_df %>%
  3251. dplyr::filter(day == 50) %>%
  3252. transmute(Genes, neuron, genotype, mean_intensity = mean_ko, sd_intensity = sd_ko)
  3253. # Set genotype factor with ctrl first, then alphabetical
  3254. all_genotypes <- sort(unique(c("ctrl", ko_df$genotype)))
  3255. all_genotypes <- c("ctrl", all_genotypes[all_genotypes != "ctrl"])
  3256. differentiation_df <- bind_rows(ctrl_df, ko_df) %>%
  3257. dplyr::mutate(genotype = factor(genotype, levels = all_genotypes))
  3258. # --- Gene lists
  3259. neuron_drivers <- c("BDNF", "CAMK2B", "CRTC1", "DCX", "JUN", "MAP2", "NCAM1", "NEFH", "NEFL", "NEFM", "NES", "POU3F2", "SLC17A7", "SYN1", "SYP", "TUBB3", "TH", "BSN", "SYNJ1", "GAP43", "SYN2", "SYN3")
  3260. presyn_genes <- c("BSN", "CPLX1", "CPLX2", "PCLO", "RAB3A", "SNAP25", "STX1A", "STX1B", "SV2A", "SV2B", "SV2C", "SYN1", "SYN2", "SYP", "SYT1", "SYT2", "SYT7", "VAMP1", "VAMP2")
  3261. postsyn_genes <- c("DLG4", "DLG3", "DLG2", "SHANK1", "SHANK2", "SHANK3", "HOMER1", "HOMER2", "HOMER3", "GPHN", "SYNGAP1", "GRIN1", "GRIN2A", "GRIN2B", "GRIA1", "GRIA2", "GRIA3", "GRIA4", "CAMK2A", "CAMK2B", "NRGN", "NPTX2")
  3262. # --- Plot function for marker genes (individual facets)
  3263. plot_diff_markers <- function(genes, data, title_text) {
  3264. plot_df <- data %>% dplyr::filter(Genes %in% genes)
  3265. if (nrow(plot_df) == 0) return(NULL)
  3266. ggplot(plot_df, aes(x = genotype, y = mean_intensity, fill = neuron)) +
  3267. geom_bar(stat = "identity", position = position_dodge(width = 1), width = 0.7) +
  3268. geom_errorbar(aes(ymin = mean_intensity - sd_intensity, ymax = mean_intensity + sd_intensity), position = position_dodge(width = 1), width = 0.2) +
  3269. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80")) +
  3270. facet_wrap(~Genes, scales = "free_y") +
  3271. labs(title = title_text, x = "Genotype", y = "Mean Intensity") +
  3272. theme_bw(base_size = 6) +
  3273. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1), panel.grid = element_blank())
  3274. }
  3275. # --- Plot 1: Neuronal markers (individual)
  3276. p_neuro <- plot_diff_markers(neuron_drivers, differentiation_df, "Neuronal Markers across all KOs")
  3277. ggsave(file.path(out_dir_diffQC, "diff132_d50_Neuronal_markers.pdf"), p_neuro, width = 12, height = 10, device = cairo_pdf)
  3278. # --- Plot 2: TH for iDA only (log2)
  3279. th_df <- differentiation_df %>%
  3280. dplyr::filter(Genes == "TH", neuron == "iDA") %>%
  3281. dplyr::mutate(log2_intensity = log2(mean_intensity), log2_sd_upper = log2(mean_intensity + sd_intensity) - log2_intensity, log2_sd_lower = log2_intensity - log2(pmax(mean_intensity - sd_intensity, 1)))
  3282. p_th <- ggplot(th_df, aes(x = genotype, y = log2_intensity, fill = genotype)) +
  3283. geom_bar(stat = "identity", width = 0.7) +
  3284. geom_errorbar(aes(ymin = log2_intensity - log2_sd_lower, ymax = log2_intensity + log2_sd_upper), width = 0.2) +
  3285. scale_fill_manual(values = c("ctrl" = "grey50", setNames(rep("grey80", length(all_genotypes) - 1), all_genotypes[all_genotypes != "ctrl"]))) +
  3286. labs(title = "TH Expression in iDA Neurons", x = "Genotype", y = expression(log[2](Mean~Intensity))) +
  3287. theme_bw(base_size = 6) +
  3288. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1), panel.grid = element_blank(), legend.position = "none")
  3289. ggsave(file.path(out_dir_diffQC, "diff132_d50_TH_iDA.pdf"), p_th, width = 6, height = 4, device = cairo_pdf)
  3290. ######
  3291. # --- Helper function: calculate annotation average with log2 transform and SEM
  3292. calc_annotation_avg_log2 <- function(genes, data) {
  3293. data %>%
  3294. dplyr::filter(Genes %in% genes) %>%
  3295. dplyr::group_by(genotype, neuron) %>%
  3296. dplyr::summarise(avg_intensity = mean(mean_intensity, na.rm = TRUE), sem_intensity = stats::sd(mean_intensity, na.rm = TRUE) / sqrt(dplyr::n()), .groups = "drop") %>%
  3297. dplyr::mutate(log2_intensity = log2(avg_intensity), log2_sem_upper = log2(avg_intensity + sem_intensity) - log2_intensity, log2_sem_lower = log2_intensity - log2(pmax(avg_intensity - sem_intensity, 1)))
  3298. }
  3299. # --- Helper function: plot annotation average (log2)
  3300. plot_annotation_avg_log2 <- function(data, title_text) {
  3301. ggplot(data, aes(x = genotype, y = log2_intensity, fill = neuron)) +
  3302. geom_bar(stat = "identity", position = position_dodge(width = 1), width = 0.7) +
  3303. geom_errorbar(aes(ymin = log2_intensity - log2_sem_lower, ymax = log2_intensity + log2_sem_upper), position = position_dodge(width = 1), width = 0.2) +
  3304. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80")) +
  3305. labs(title = title_text, x = "Genotype", y = expression(log[2](Mean~Intensity))) +
  3306. theme_bw(base_size = 6) +
  3307. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1), panel.grid = element_blank())
  3308. }
  3309. # --- Plot 3: Presynaptic average (log2)
  3310. presyn_avg <- calc_annotation_avg_log2(presyn_genes, differentiation_df)
  3311. p_presyn <- plot_annotation_avg_log2(presyn_avg, "Presynaptic Markers (Average)")
  3312. ggsave(file.path(out_dir_diffQC, "diff132_d50_Presynaptic_avg.pdf"), p_presyn, width = 6, height = 4, device = cairo_pdf)
  3313. # --- Plot 4: Postsynaptic average (log2)
  3314. postsyn_avg <- calc_annotation_avg_log2(postsyn_genes, differentiation_df)
  3315. p_postsyn <- plot_annotation_avg_log2(postsyn_avg, "Postsynaptic Markers (Average)")
  3316. ggsave(file.path(out_dir_diffQC, "diff132_d50_Postsynaptic_avg.pdf"), p_postsyn, width = 6, height = 4, device = cairo_pdf)
  3317. # --- Plot 5: Neuronal markers average (log2)
  3318. neuro_avg <- calc_annotation_avg_log2(neuron_drivers, differentiation_df)
  3319. p_neuro_avg <- plot_annotation_avg_log2(neuro_avg, "Neuronal Markers (Average)")
  3320. ggsave(file.path(out_dir_diffQC, "diff132_d50_Neuronal_avg.pdf"), p_neuro_avg, width = 6, height = 4, device = cairo_pdf)
  3321. ```
  3322. # vATPase by neuron x genotype
  3323. Plotted in ascending order
  3324. ```{r}
  3325. # --------------------------------------------------- #
  3326. # neuro QC plots
  3327. # vATPase plots
  3328. # --------------------------------------------------- #
  3329. vatpase_genes <- c("ATP6V1G1", "ATP6AP2", "ATP6V1B2", "ATP6V1C1", "ATP6V1E1", "ATP6V1A", "ATP6V0D1",
  3330. "ATP6AP1", "ATP6V1F", "ATP6V0A1", "ATP6V0A2", "ATP6V1H", "ATP6V1D", "ROGDI",
  3331. "SYP", "DMXL1", "DMXL2", "PODXL", "PODXL2")
  3332. # --- 1. Filter and compute log2FC ---
  3333. vatpase_long_df <- clean_d50_transformed_df %>%
  3334. dplyr::filter(Genes %in% vatpase_genes, day == 50) %>%
  3335. dplyr::mutate(log2FC = log2(mean_ko / mean_ctrl)) %>%
  3336. dplyr::mutate(group = paste(genotype, neuron, sep = "_"))
  3337. # --- 2. Compute average log2FC per group ---
  3338. avg_fc_summary <- vatpase_long_df %>%
  3339. dplyr::group_by(group, neuron) %>%
  3340. dplyr::summarise(avg_log2FC = mean(log2FC, na.rm = TRUE), .groups = "drop")
  3341. # --- 3. Order by increasing avg log2FC ---
  3342. avg_fc_summary <- avg_fc_summary %>%
  3343. dplyr::arrange(neuron, avg_log2FC) %>%
  3344. dplyr::group_by(neuron) %>%
  3345. dplyr::mutate(group = factor(group, levels = unique(group)))
  3346. # --- 4. Merge ordering into main data frame ---
  3347. vatpase_long_df <- vatpase_long_df %>%
  3348. inner_join(avg_fc_summary %>% dplyr::select(group, neuron), by = c("group", "neuron")) %>%
  3349. dplyr::mutate(group = factor(group, levels = levels(avg_fc_summary$group)))
  3350. # --- 5. Plot ---
  3351. d50_vATPase_plot <- ggplot(vatpase_long_df, aes(x = group, y = log2FC, group = Genes)) +
  3352. geom_line(color = "grey70", size = 0.3, alpha = 0.8) +
  3353. geom_hline(yintercept =0, linetype = "dashed", color = "black") +
  3354. geom_point(color = "grey70", size = 0.5, alpha = 0.8) +
  3355. geom_line(data = avg_fc_summary,
  3356. aes(x = group, y = avg_log2FC, group = 1),
  3357. color = "red", size = 1.2, inherit.aes = FALSE) +
  3358. facet_wrap(~neuron, scales = "free_x") +
  3359. theme_bw(base_size = 6) +
  3360. theme(
  3361. axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
  3362. panel.grid = element_blank()
  3363. ) +
  3364. labs(
  3365. title = "v-ATPase Genes: log2FC Summary by Genotype × Neuron",
  3366. x = "Genotype × Neuron (sorted by mean log2FC)",
  3367. y = expression(log[2]*"(KO / Ctrl)")
  3368. )
  3369. d50_vATPase_plot
  3370. # --- 6. save pdf ---
  3371. ggsave(file.path(out_dir_d50_ascd_avg, "diff132_d50_Neuro_vATPase.pdf"), d50_vATPase_plot, width = 4, height = 3, device = cairo_pdf)
  3372. # --------------------------------------------------- #
  3373. # Function: vATPase color table by genotype (iN and iDA)
  3374. # --------------------------------------------------- #
  3375. make_vatpase_color_table <- function(
  3376. user_genotype,
  3377. input_df = clean_d50_transformed_df,
  3378. vatpase_gene_set = c(
  3379. # V1 sector
  3380. "ATP6V1A",
  3381. "ATP6V1B1","ATP6V1B2",
  3382. "ATP6V1C1","ATP6V1C2",
  3383. "ATP6V1D",
  3384. "ATP6V1E1","ATP6V1E2",
  3385. "ATP6V1F",
  3386. "ATP6V1G1","ATP6V1G2","ATP6V1G3",
  3387. "ATP6V1H",
  3388. # V0 sector (includes full c-ring and small subunits)
  3389. "ATP6V0A1","ATP6V0A2","ATP6V0A3","ATP6V0A4",
  3390. "ATP6V0C", "ATP6V0B",
  3391. "ATP6V0D1","ATP6V0D2",
  3392. "ATP6V0E1","ATP6V0E2",
  3393. "RNASEK",
  3394. # Accessory subunits
  3395. "ATP6AP1","ATP6AP2",
  3396. # Assembly or regulatory factors
  3397. "DMXL1","DMXL2","WDR7","ROGDI"
  3398. ),
  3399. out_dir = out_dir_d50_ascd_avg,
  3400. pseudocount = 0
  3401. ) {
  3402. stopifnot(dir.exists(out_dir))
  3403. # helper to compute log2FC robustly
  3404. safe_log2fc <- function(ko, ctrl, pc = 0) {
  3405. x <- log2((ko + pc) / (ctrl + pc))
  3406. x[!is.finite(x)] <- NA_real_
  3407. x
  3408. }
  3409. # 6WM2 chain→gene mapping (human subunits only; SidK chains X/Y/Z omitted)
  3410. chain_gene_map <- dplyr::bind_rows(
  3411. tibble::tibble(chain = "0", gene = "ATP6V0B"),
  3412. tibble::tibble(chain = as.character(1:9), gene = "ATP6V0C"),
  3413. tibble::tibble(chain = c("A","B","C"), gene = "ATP6V1A"),
  3414. tibble::tibble(chain = c("D","E","F"), gene = "ATP6V1B2"),
  3415. tibble::tibble(chain = "G", gene = "ATP6V1D"),
  3416. tibble::tibble(chain = c("H","I","J"), gene = "ATP6V1E1"),
  3417. tibble::tibble(chain = c("K","L","M"), gene = "ATP6V1G1"),
  3418. tibble::tibble(chain = "N", gene = "ATP6V1F"),
  3419. tibble::tibble(chain = "O", gene = "ATP6V1C1"),
  3420. tibble::tibble(chain = "P", gene = "ATP6V1H"),
  3421. tibble::tibble(chain = "Q", gene = "ATP6V0D1"),
  3422. tibble::tibble(chain = "R", gene = "ATP6V0A1"),
  3423. tibble::tibble(chain = "S", gene = "ATP6V0E1"),
  3424. tibble::tibble(chain = "T", gene = "RNASEK"),
  3425. tibble::tibble(chain = "U", gene = "ATP6V1S1"),
  3426. tibble::tibble(chain = "V", gene = "ATP6AP2")
  3427. )
  3428. # --- 1. Build long table with log2FC at day 50
  3429. global_long_df <- input_df %>%
  3430. dplyr::filter(Genes %in% vatpase_gene_set, day == 50) %>%
  3431. dplyr::transmute(
  3432. Genes,
  3433. genotype,
  3434. neuron,
  3435. log2FC = safe_log2fc(mean_ko, mean_ctrl, pseudocount)
  3436. )
  3437. # --- 2. Genotype-only symmetric domain across iN and iDA
  3438. geno_vals_vec <- global_long_df %>%
  3439. dplyr::filter(genotype == user_genotype, neuron %in% c("iN","iDA")) %>%
  3440. dplyr::pull(log2FC)
  3441. geno_vals_vec <- geno_vals_vec[is.finite(geno_vals_vec)]
  3442. if (length(geno_vals_vec) == 0) stop("No finite log2FC values for this genotype.")
  3443. geno_lim_val <- max(abs(range(geno_vals_vec, na.rm = TRUE)))
  3444. domain_vec <- c(-geno_lim_val, geno_lim_val)
  3445. # --- 3. Color mapper blue white red using genotype-only domain
  3446. col_fn_local <- scales::col_numeric(
  3447. palette = colorRampPalette(c("#2c7bb6", "white", "#d7191c"))(201),
  3448. domain = domain_vec,
  3449. na.color = "#B0B0B0"
  3450. )
  3451. # --- 3a. Save color scale CSV
  3452. colorscale_df <- tibble::tibble(
  3453. value = seq(domain_vec[1], domain_vec[2], length.out = 201),
  3454. color = toupper(col_fn_local(value))
  3455. )
  3456. colorscale_file <- file.path(out_dir, paste0("vATPase_colorscale_", user_genotype, ".csv"))
  3457. write.csv(colorscale_df, colorscale_file, row.names = FALSE)
  3458. # --- 3b. Save color scale PDF
  3459. cs_plot_df <- tibble::tibble(
  3460. x = seq(domain_vec[1], domain_vec[2], length.out = 500),
  3461. y = 1,
  3462. z = x
  3463. )
  3464. colorscale_pdf <- file.path(out_dir, paste0("vATPase_colorscale_", user_genotype, ".pdf"))
  3465. grDevices::cairo_pdf(colorscale_pdf, width = 4, height = 0.6)
  3466. print(
  3467. ggplot(cs_plot_df, aes(x = x, y = y, fill = z)) +
  3468. geom_raster() +
  3469. scale_fill_gradient2(
  3470. low = "#2c7bb6", mid = "white", high = "#d7191c",
  3471. midpoint = 0, limits = domain_vec, guide = "none"
  3472. ) +
  3473. scale_x_continuous(breaks = c(domain_vec[1], 0, domain_vec[2])) +
  3474. labs(x = "log2FC", y = NULL, title = NULL) +
  3475. theme_minimal(base_size = 6) +
  3476. theme(
  3477. panel.grid = element_blank(),
  3478. axis.text.y = element_blank(),
  3479. axis.ticks.y = element_blank(),
  3480. plot.margin = margin(4, 8, 4, 8)
  3481. )
  3482. )
  3483. grDevices::dev.off()
  3484. # --- 4. Build per-neuron tables and save CSVs
  3485. build_one_neuron_tbl <- function(neuron_label_val) {
  3486. df_neuron_df <- global_long_df %>%
  3487. dplyr::filter(genotype == user_genotype, neuron == neuron_label_val) %>%
  3488. dplyr::select(Genes, log2FC)
  3489. df_full_df <- tibble::tibble(Genes = vatpase_gene_set) %>%
  3490. dplyr::left_join(df_neuron_df, by = "Genes") %>%
  3491. dplyr::mutate(
  3492. Color = toupper(col_fn_local(log2FC))
  3493. ) %>%
  3494. dplyr::rename(Gene = Genes)
  3495. out_with_chains_df <- chain_gene_map %>%
  3496. dplyr::left_join(df_full_df, by = c("gene" = "Gene")) %>%
  3497. dplyr::mutate(
  3498. chimerax_command = paste0("color /", chain, " ", Color)
  3499. ) %>%
  3500. dplyr::select(Gene = gene, Chain = chain, Color, log2FC, chimerax_command)
  3501. chain_levels_vec <- c(as.character(0:9), LETTERS)
  3502. out_with_chains_df <- out_with_chains_df %>%
  3503. dplyr::mutate(Chain = factor(Chain, levels = chain_levels_vec)) %>%
  3504. dplyr::arrange(Chain)
  3505. out_file_path <- file.path(out_dir, paste0("vATPase_colors_", neuron_label_val, "_", user_genotype, "_6wm2.csv"))
  3506. write.csv(out_with_chains_df, out_file_path, row.names = FALSE)
  3507. out_with_chains_df
  3508. }
  3509. tbl_iN <- build_one_neuron_tbl("iN")
  3510. tbl_iDA <- build_one_neuron_tbl("iDA")
  3511. list(
  3512. iN = tbl_iN,
  3513. iDA = tbl_iDA,
  3514. scale_limits = domain_vec,
  3515. colorscale_file = colorscale_file,
  3516. colorscale_pdf = colorscale_pdf
  3517. )
  3518. }
  3519. # Call function for genotype of choice:
  3520. make_vatpase_color_table("MCOLN1")
  3521. ```
  3522. # SV & SNARE plot by neuron x genotype
  3523. Plotted in ascending order
  3524. ```{r}
  3525. # --------------------------------------------------- #
  3526. # neuro QC plots
  3527. # SV snare plots
  3528. # --------------------------------------------------- #
  3529. svsnare_genes <- c("SYP", "VAMP2", "STXBP1", "CPLX1", "SYT1", "SNAP25","SNAP","STX1A", "STX1")
  3530. # --- 1. Filter and compute log2FC
  3531. svsnare_long_df <- clean_d50_transformed_df %>%
  3532. dplyr::filter(Genes %in% svsnare_genes, day == 50) %>%
  3533. dplyr::mutate(log2FC = log2(mean_ko / mean_ctrl)) %>%
  3534. dplyr::mutate(group = paste(genotype, neuron, sep = "_"))
  3535. # --- 2. Compute average log2FC per group
  3536. avg_svsnare_fc_summary <- svsnare_long_df %>%
  3537. dplyr::group_by(group, neuron) %>%
  3538. dplyr::summarise(avg_log2FC = mean(log2FC, na.rm = TRUE), .groups = "drop")
  3539. # --- 3. Order by increasing avg log2FC
  3540. avg_svsnare_fc_summary <- avg_svsnare_fc_summary %>%
  3541. dplyr::arrange(neuron, avg_log2FC) %>%
  3542. dplyr::group_by(neuron) %>%
  3543. dplyr::mutate(group = factor(group, levels = unique(group)))
  3544. # --- 4. Merge ordering into main data frame
  3545. svsnare_long_df <- svsnare_long_df %>%
  3546. inner_join(avg_svsnare_fc_summary %>% dplyr::select(group, neuron), by = c("group", "neuron")) %>%
  3547. dplyr::mutate(group = factor(group, levels = levels(avg_svsnare_fc_summary$group)))
  3548. # --- 5. Plot
  3549. d50_svsnare_plot <- ggplot(svsnare_long_df, aes(x = group, y = log2FC, group = Genes)) +
  3550. geom_line(color = "grey70", size = 0.3, alpha = 0.8) +
  3551. geom_hline(yintercept =0, linetype = "dashed", color = "black") +
  3552. geom_point(color = "grey70", size = 0.5, alpha = 0.8) +
  3553. geom_line(data = avg_svsnare_fc_summary,
  3554. aes(x = group, y = avg_log2FC, group = 1),
  3555. color = "red", size = 1.2, inherit.aes = FALSE) +
  3556. facet_wrap(~neuron, scales = "free_x") +
  3557. theme_bw(base_size = 6) +
  3558. theme(
  3559. axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
  3560. panel.grid = element_blank()
  3561. ) +
  3562. labs(
  3563. title = "SV SNARE Genes: log2FC Summary by Genotype × Neuron",
  3564. x = "Genotype × Neuron (sorted by mean log2FC)",
  3565. y = expression(log[2]*"(KO / Ctrl)")
  3566. )
  3567. d50_svsnare_plot
  3568. # --- 6. save pdf ---
  3569. ggsave(file.path(out_dir_d50_ascd_avg, "diff132_d50_Neuro_SVSNSARE.pdf"), d50_svsnare_plot, width = 4, height = 3, device = cairo_pdf)
  3570. ```
  3571. # SV docking plot by neuron x genotype
  3572. Plotted in ascending order
  3573. ```{r}
  3574. # --------------------------------------------------- #
  3575. # neuro QC plots
  3576. # SV docking plots
  3577. # --------------------------------------------------- #
  3578. svdocking_genes <- c("SYP","VAMP2","SYB2","STXBP1","UNC18A","CPLX1","SYT1","SNAP25","SNAP","STX1A","STX1A","UNC13B")
  3579. # --- 1. Filter and compute log2FC
  3580. svdocking_long_df <- clean_d50_transformed_df %>%
  3581. dplyr::filter(Genes %in% svdocking_genes, day == 50) %>%
  3582. dplyr::mutate(log2FC = log2(mean_ko / mean_ctrl)) %>%
  3583. dplyr::mutate(group = paste(genotype, neuron, sep = "_"))
  3584. # --- 2. Compute mean and SD per group
  3585. svdocking_summary <- svdocking_long_df %>%
  3586. dplyr::group_by(group, neuron) %>%
  3587. dplyr::summarise(
  3588. avg_log2FC = mean(log2FC, na.rm = TRUE),
  3589. sd_log2FC = sd(log2FC, na.rm = TRUE),
  3590. .groups = "drop"
  3591. )
  3592. # --- 3. Order by increasing mean per neuron
  3593. svdocking_summary <- svdocking_summary %>%
  3594. dplyr::arrange(neuron, avg_log2FC) %>%
  3595. dplyr::group_by(neuron) %>%
  3596. dplyr::mutate(group_order = dplyr::row_number()) %>%
  3597. dplyr::ungroup()
  3598. # --- 4. Plot: SD in grey70, mean in red
  3599. d50_svdocking_plot <- ggplot(svdocking_summary,
  3600. aes(x = reorder(group, group_order), y = avg_log2FC, group = 1)) +
  3601. geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
  3602. # SD band per group
  3603. geom_linerange(aes(ymin = avg_log2FC - sd_log2FC,
  3604. ymax = avg_log2FC + sd_log2FC),
  3605. color = "grey70", linewidth = 1.2, alpha = 0.8) +
  3606. # mean line and points
  3607. geom_line(color = "red", linewidth = 0.9) +
  3608. geom_point(color = "red", size = 0.8) +
  3609. facet_wrap(~ neuron, scales = "free_x") +
  3610. theme_bw(base_size = 6) +
  3611. theme(
  3612. axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
  3613. panel.grid = element_blank()
  3614. ) +
  3615. labs(
  3616. title = "SV Docking Genes: mean ± SD by Genotype x Neuron",
  3617. x = "Genotype x Neuron (sorted by mean log2FC)",
  3618. y = expression(log[2]*"(KO / Ctrl)")
  3619. )
  3620. # --- 5. print and save
  3621. d50_svdocking_plot
  3622. ggsave(file.path(out_dir_d50_ascd_avg, "diff132_d50_Neuro_SVdocking.pdf"), d50_svdocking_plot, width = 4, height = 3, device = cairo_pdf)
  3623. ```
  3624. # =================================================
  3625. # Module 4: Time Course Analysis (d30 vs d50)
  3626. # =================================================
  3627. # Abundance of neuromarkers in Ctrl iN/iDA
  3628. ```{r}
  3629. # --------------------------------------------------- #
  3630. # neuro QC plots
  3631. # Abundance of neuromarkers in Ctrl day 30 & day 50
  3632. # --------------------------------------------------- #
  3633. # --- Step 1. Define Developmental Marker Gene List ----
  3634. dev_genes <- c("BDNF", "CAMK2B", "CRTC1", "DCX", "JUN", "MAP2", "NCAM1",
  3635. "NEFH", "NEFL", "NEFM", "NES", "POU3F2", "SLC17A7", "SYN1",
  3636. "SYP", "TUBB3", "TH", "BSN", "SYNJ1", "GAP43", "PSD95", "VGLUT",
  3637. "SYN2", "SYN3", "NGN")
  3638. # --- Step 2. Prepare Data ----
  3639. dev_plot_df <- clean_d50_transformed_df %>%
  3640. dplyr::filter(Genes %in% dev_genes) %>%
  3641. dplyr::select(Genes, neuron, day, mean_ctrl) %>%
  3642. dplyr::mutate(day = as.factor(day)) # Ensure day is treated as categorical for plotting
  3643. # --- Step 3.Plot ----
  3644. Dev_Marker_Abundance <- ggplot(dev_plot_df, aes(x = neuron, y = mean_ctrl, fill = day)) +
  3645. geom_bar(stat = "summary", fun = mean, position = position_dodge(width = 0.6), width = 0.6) +
  3646. facet_wrap(~Genes, scales = "free_y") +
  3647. scale_fill_manual(values = c("30" = "grey70", "50" = "steelblue")) +
  3648. labs(
  3649. title = "Neuronal Marker Abundance (Control, day 30 vs 50)",
  3650. x = "Neuron Type",
  3651. y = "Abundnace (mean_ctrl)",
  3652. fill = "Day"
  3653. ) +
  3654. theme_bw() +
  3655. theme(
  3656. legend.position = "bottom",
  3657. panel.grid = element_blank()
  3658. )
  3659. # --- Step 4. Display ----
  3660. Dev_Marker_Abundance
  3661. # --- Step 5. Save ----
  3662. ggsave(file.path(out_dir_d50_timecourse,
  3663. "diff132_NeuroMarkers_ctrl_d30_d50.pdf"),
  3664. Dev_Marker_Abundance,
  3665. width = 12, height = 12, device = cairo_pdf
  3666. )
  3667. # --------------------------------------------------- #
  3668. # Neuro QC plots
  3669. # Abundance of neuromarkers (lineplot, normalized to day 30)
  3670. # --------------------------------------------------- #
  3671. # --- Step 1. Define Marker Gene List of Interest
  3672. marker_genes_line <- c("BSN", "NCAM1", "GAP43", "SYN1", "SYN3", "NEFL", "SYP")
  3673. # --- Step 2. Define function: plots Ctrl trajectories normalized to Day 30
  3674. plot_ctrl_markers_norm <- function(data, out_dir, genes = marker_genes_line) {
  3675. # Filter the dataset for Ctrl, iN/iDA, Day 30 & 50, and selected genes
  3676. ctrl_df <- data %>%
  3677. dplyr::filter(Genes %in% genes,
  3678. genotype == "ctrl",
  3679. neuron %in% c("iN", "iDA"),
  3680. day %in% c(30, 50)) %>%
  3681. dplyr::mutate(day = factor(day, levels = c(30, 50)))
  3682. # Normalize quan to Day 30 mean within each neuron × gene group
  3683. ctrl_norm <- ctrl_df %>%
  3684. dplyr::group_by(Genes, neuron) %>%
  3685. dplyr::mutate(norm_quan = quan / mean(quan[day == 30], na.rm = TRUE)) %>%
  3686. ungroup()
  3687. # Compute mean normalized intensity for each group
  3688. ctrl_means <- ctrl_norm %>%
  3689. dplyr::group_by(Genes, neuron, day) %>%
  3690. dplyr::summarise(mean_norm = mean(norm_quan, na.rm = TRUE), .groups = "drop")
  3691. # Get Day 50 values for end-of-line labels
  3692. labels_df <- ctrl_means %>% dplyr::filter(day == 50)
  3693. # Create the line plot
  3694. p <- ggplot(ctrl_means,aes(x = day, y = mean_norm, group = interaction(Genes, neuron), color = neuron)) +
  3695. geom_line(linewidth = 1) +
  3696. geom_point(size = 2) +
  3697. ggrepel::geom_text_repel(data = labels_df,
  3698. aes(label = Genes),
  3699. nudge_x = 0.25, hjust = 0,
  3700. segment.color = NA, size = 3,
  3701. show.legend = FALSE) +
  3702. facet_wrap(~neuron, ncol = 2) +
  3703. scale_color_manual(values = c("iN" = "grey70", "iDA" = "dodgerblue3")) +
  3704. scale_x_discrete(labels = c("30" = "Day 30", "50" = "Day 50")) +
  3705. labs(title = "Ctrl NeuroDev Markers Normalized to Day 30",
  3706. x = "Day", y = "Normalized Intensity (Day 30 = 1)") +
  3707. theme_bw() +
  3708. theme(panel.grid = element_blank(),
  3709. strip.text = element_text(face = "bold"),
  3710. legend.position = "none")
  3711. # Save as PDF
  3712. ggsave(file.path(out_dir, "ctrl_d30d50_Neuro_QC_Markers_norm_facet.pdf"),
  3713. p, width = 4, height = 4, device = cairo_pdf)
  3714. # save sourcedata
  3715. readr::write_csv(ctrl_means, file.path(out_dir, "ctrl_d30d50_Neuro_QC_Markers_norm_sourcedata.csv"))
  3716. return(p)
  3717. }
  3718. # --- Step 3. Call function and Save
  3719. Neuro_QC_Lineplot_norm <- plot_ctrl_markers_norm(cleaned_df, out_dir_d50_timecourse)
  3720. ```
  3721. # Pre/Post Synaptic Markers in Ctrl iN/iDA
  3722. ``` {r}
  3723. # --------------------------------------------------- #dev_plot_line_df
  3724. # neuro QC plots
  3725. # Abundance of Pre/Post Synaptic Markers in Ctrl day 30 & day 50
  3726. # --------------------------------------------------- #
  3727. # --- Step 1. Define Developmental Marker Gene List ----
  3728. presyn_genes <- c("BSN", "CPLX1", "CPLX2", "ELKS1", "MUNC13A", "MUNC13B", "MUNC18", "NRXN1",
  3729. "NRXN2", "NRXN3", "PCLO", "RAB3A", "RIM1", "RIM2", "RIMBP2", "SNAP25", "STX1A",
  3730. "STX1B", "SV2A", "SV2B", "SV2C", "SYN1", "SYN2", "SYP", "SYT1", "SYT2", "SYT7",
  3731. "VAMP1", "VAMP2", "VGLUT1", "VGLUT2", "VGLUT3"
  3732. )
  3733. postsyn_genes <- c(
  3734. "DLG4", "DLG3", "DLG2", "SHANK1", "SHANK2", "SHANK3", "HOMER1", "HOMER2", "HOMER3", "NLGN1",
  3735. "NLGN2", "NLGN3", "NLGN4X", "GPHN", "SYNGAP1", "GRIN1", "GRIN2A", "GRIN2B", "GRIA1", "GRIA2",
  3736. "GRIA3", "GRIA4", "GABRA1", "GABRA2", "GABRA3", "GABRA5", "GABRB1", "GABRB3", "GABRG2",
  3737. "CAMK2A", "CAMK2B", "PPP1R9B", "NRGN", "NPTX2"
  3738. )
  3739. # --- Step 2. Define presynaptic and postsynaptic gene lists ---
  3740. presyn_df <- tibble::tibble(Genes = presyn_genes, marker_type = "Presynaptic")
  3741. postsyn_df <- tibble::tibble(Genes = postsyn_genes, marker_type = "Postsynaptic")
  3742. synapse_genes_df <- bind_rows(presyn_df, postsyn_df)
  3743. # --- Step 3. Filter and transform data for control samples at Day 30/50 ---
  3744. synapse_long <- clean_d50_transformed_df %>%
  3745. dplyr::filter(Genes %in% synapse_genes_df$Genes) %>%
  3746. dplyr::select(Genes, neuron, day, mean_ctrl) %>%
  3747. dplyr::inner_join(synapse_genes_df, by = "Genes") %>%
  3748. dplyr::mutate(
  3749. day = paste("Day", day), # Convert numeric to "Day X"
  3750. log2_abundance = log2(mean_ctrl) # Apply log2 transform
  3751. )
  3752. # --- Step 4. Summarize per gene and reshape to wide for change calculation ---
  3753. delta_df <- synapse_long %>%
  3754. dplyr::group_by(Genes, neuron, marker_type, day) %>%
  3755. dplyr::summarise(log2_abundance = mean(log2_abundance, na.rm = TRUE), .groups = "drop") %>%
  3756. tidyr::pivot_wider(names_from = day, values_from = log2_abundance) %>%
  3757. dplyr::filter(!is.na(`Day 30`) & !is.na(`Day 50`)) %>%
  3758. dplyr::mutate(direction = ifelse(`Day 50` > `Day 30`, "up", "down"))
  3759. # --- Step 5. Reshape back to long format for plotting ---
  3760. long_with_direction <- delta_df %>%
  3761. tidyr::pivot_longer(cols = c(`Day 30`, `Day 50`), names_to = "day", values_to = "log2_abundance") %>%
  3762. dplyr::mutate(day = factor(day, levels = c("Day 30", "Day 50")))
  3763. # --- Step 6. Plot function with per-gene trajectories and group average overlays ---
  3764. SynapseTrajectoryPlot <- function(df) {
  3765. ggplot(df, aes(x = day, y = log2_abundance)) +
  3766. # Individual gene lines by direction
  3767. geom_line(aes(group = Genes, color = direction), alpha = 0.3, linewidth = 0.4) +
  3768. # Overlay group mean lines
  3769. stat_summary(
  3770. aes(group = interaction(marker_type, neuron)),
  3771. fun = mean, geom = "line",
  3772. color = "grey40", linewidth = 2
  3773. ) +
  3774. stat_summary(
  3775. aes(group = interaction(marker_type, neuron)),
  3776. fun = mean, geom = "point",
  3777. color = "grey30", size = 4
  3778. ) +
  3779. facet_grid(marker_type ~ neuron) +
  3780. scale_color_manual(values = c("up" = "#e31a1c", "down" = "#1f78b4")) +
  3781. theme_bw() +
  3782. labs(
  3783. title = "log2 Abundance Trajectories of \n Synaptic Genes (Control Neurons)",
  3784. x = "Day", y = "log2(Abundance)",
  3785. color = "Direction"
  3786. ) +
  3787. theme(
  3788. panel.grid = element_blank(),
  3789. legend.position = "bottom",
  3790. )
  3791. }
  3792. # --- Step 7. Generate and save plot ---
  3793. Synaptic_Marker_Abundance <- SynapseTrajectoryPlot(long_with_direction)
  3794. Synaptic_Marker_Abundance
  3795. ggsave(file.path(out_dir_d50_timecourse,"diff132_SynapticMarkers_ctrl_d30_d50.pdf"), Synaptic_Marker_Abundance, width = 4, height = 5, device = cairo_pdf)
  3796. ```
  3797. # NeuroDev protein-list trajectory d30 to d50
  3798. ```{r}
  3799. # --------------------------------------------------- #
  3800. # neuro QC plots
  3801. # NeuroDev protein trajectory for day 30 & day 50 for select genotypes
  3802. # --------------------------------------------------- #
  3803. # For Ctrl, GRN, ASAH1, GBA1 and SMPD1 have both day30 and day50 data. plot neuronal markers etc and see if they increase.
  3804. # Define your gene sets
  3805. es_drivers <- c("ATF1", "CUX1", "MKI67", "NANOG", "POU5F1", "SOX2")
  3806. neuron_drivers <- c(
  3807. "BDNF", "CAMK2B", "CRTC1", "DCX", "JUN", "MAP2", "NCAM1",
  3808. "NEFH", "NEFL", "NEFM", "NES", "POU3F2", "SLC17A7", "SYN1",
  3809. "SYP", "TUBB3", "TH", "BSN", "SYNJ1", "GAP43", "PSD95", "VGLUT",
  3810. "SYN2", "SYN3", "NGN"
  3811. )
  3812. # --- Step 1. Create marker label dataframe
  3813. marker_genes_df <- tibble(
  3814. Genes = c(es_drivers, neuron_drivers),
  3815. marker_type = c(rep("ES-driver", length(es_drivers)),
  3816. rep("Neuron-driver", length(neuron_drivers)))
  3817. )
  3818. # --- Step 2. Filter for NeuroDev == TRUE
  3819. d30_neurodev <- complete_data_annotated_day30 %>% dplyr::filter(NeuroDev == TRUE)
  3820. d50_neurodev <- complete_data_annotated_day50 %>% dplyr::filter(NeuroDev == TRUE)
  3821. # --- Step 3. Reshape to long format and add 'day'
  3822. pivot_neurodev <- function(df, day_label) {
  3823. df %>%
  3824. dplyr::select(Genes, matches("_log2_fold_change$")) %>%
  3825. tidyr::pivot_longer(
  3826. cols = -Genes,
  3827. names_to = "sample",
  3828. values_to = "log2FC"
  3829. ) %>%
  3830. dplyr::mutate(
  3831. day = day_label,
  3832. sample = str_remove(sample, "_log2_fold_change$"),
  3833. genotype = str_extract(sample, "^[^_]+"),
  3834. neuron = str_extract(sample, "(?<=_)(iDA|iN)$")
  3835. )
  3836. }
  3837. d30_long <- pivot_neurodev(d30_neurodev, "Day 30")
  3838. d50_long <- pivot_neurodev(d50_neurodev, "Day 50")
  3839. # --- Step 4. Combine
  3840. combined_df <- bind_rows(d30_long, d50_long)
  3841. # Join annotation info
  3842. combined_df_annotated <- combined_df %>%
  3843. dplyr::inner_join(marker_genes_df, by = "Genes")
  3844. # Now you can summarize or plot
  3845. summary_df <- combined_df_annotated %>%
  3846. dplyr::filter(genotype %in% c("ctrl", "ASAH1", "GBA1", "SMPD1", "GRN")) %>%
  3847. dplyr::group_by(marker_type, genotype, neuron, day) %>%
  3848. dplyr::summarise(mean_log2FC = mean(log2FC, na.rm = TRUE), .groups = "drop")
  3849. # --- Step 5. Plot
  3850. NeuoDevd30d50 <- ggplot(
  3851. combined_df_annotated %>% dplyr::filter(genotype %in% c("ctrl", "ASAH1", "GBA1", "SMPD1", "GRN")),
  3852. aes(x = day, y = log2FC, group = interaction(Genes, neuron), marker_type = marker_type)
  3853. ) +
  3854. # Individual traces
  3855. geom_line(aes(color = neuron), alpha = 0.2, linewidth = 0.5) +
  3856. geom_point(aes(color = neuron), alpha = 0.4, size = 1) +
  3857. # Mean lines
  3858. stat_summary(
  3859. aes(group = neuron, color = neuron),
  3860. fun = mean, geom = "line", linewidth = 1.2
  3861. ) +
  3862. stat_summary(
  3863. aes(group = neuron, fill = neuron),
  3864. fun = mean, geom = "point", size = 2.5, color = "black", shape = 21
  3865. ) +
  3866. facet_grid(rows = vars(marker_type), cols = vars(genotype)) +
  3867. scale_color_manual(values = c("iN" = "grey60", "iDA" = "grey80")) +
  3868. scale_fill_manual(values = c("iN" = "grey60", "iDA" = "grey80")) +
  3869. labs(
  3870. title = "log2FC of NeuroDev Genes by Marker Type and Genotype",
  3871. x = "Day", y = "log2 Fold Change"
  3872. ) +
  3873. theme_bw() +
  3874. theme(
  3875. strip.text = element_text(face = "bold"),
  3876. panel.grid = element_blank()
  3877. )
  3878. NeuoDevd30d50
  3879. # --- Step 6. Save
  3880. ggsave(file.path(out_dir_d50_timecourse, "diff132_NeuoDevd30d50.pdf"), NeuoDevd30d50, width = 8, height = 6, device = cairo_pdf)
  3881. ```
  3882. # Summary plots for neuronal markers Ctrl d30 / d50
  3883. ```{r}
  3884. # --- Summary barplots: NeuroDev marker lists in ctrl (d30 vs d50)
  3885. # dev_genes, neuron_drivers, presyn_genes, postsyn_genes
  3886. # two versions per list: raw quan and log2(quan)
  3887. # Step 1: combine all marker lists into one long df
  3888. marker_lists_nd <- list(
  3889. dev_genes = dev_genes,
  3890. neuron_drivers = neuron_drivers,
  3891. presyn_genes = presyn_genes,
  3892. postsyn_genes = postsyn_genes
  3893. )
  3894. summary_neurodev_df <- lapply(names(marker_lists_nd), function(lst) {
  3895. clean_d50_transformed_df %>%
  3896. dplyr::filter(Genes %in% marker_lists_nd[[lst]]) %>%
  3897. dplyr::select(Genes, neuron, day, mean_ctrl) %>%
  3898. dplyr::mutate(marker_list = lst)
  3899. }) %>% dplyr::bind_rows()
  3900. readr::write_csv(summary_neurodev_df, file.path(out_dir_d50_timecourse, "neurodev_markerlist_sourcedata.csv"))
  3901. # Step 2: normalize per protein per neuron to d30 = 1, then mean ± SEM across proteins
  3902. summary_neurodev_norm <- summary_neurodev_df %>%
  3903. dplyr::group_by(Genes, neuron, day, marker_list) %>%
  3904. dplyr::summarise(mean_ctrl = mean(mean_ctrl, na.rm = TRUE), .groups = "drop") %>%
  3905. tidyr::pivot_wider(names_from = day, values_from = mean_ctrl, names_prefix = "d") %>%
  3906. dplyr::filter(!is.na(d30), !is.na(d50)) %>%
  3907. dplyr::mutate(ratio_d50 = d50 / d30) %>%
  3908. tidyr::pivot_longer(cols = c(d30, ratio_d50),
  3909. names_to = "day_norm", values_to = "norm_val") %>%
  3910. dplyr::mutate(day_norm = dplyr::recode(day_norm, "d30" = "d30", "ratio_d50" = "d50"),
  3911. norm_val = dplyr::if_else(day_norm == "d30", 1, norm_val))
  3912. summary_neurodev_stats <- summary_neurodev_norm %>%
  3913. dplyr::group_by(marker_list, neuron, day_norm) %>%
  3914. dplyr::summarise(
  3915. mean_val = mean(norm_val, na.rm = TRUE),
  3916. sem_val = sd(norm_val, na.rm = TRUE) / sqrt(dplyr::n()),
  3917. n = dplyr::n(),
  3918. .groups = "drop"
  3919. ) %>%
  3920. dplyr::mutate(group = factor(paste0(neuron, "_", day_norm),
  3921. levels = c("iN_d30", "iN_d50", "iDA_d30", "iDA_d50")))
  3922. # save source data csv
  3923. readr::write_csv(summary_neurodev_stats, file.path(out_dir_d50_timecourse, "neurodev_markerlist_stats_sourcedata.csv"))
  3924. # Step 3: barplot function
  3925. plot_nd_bar <- function(lst_name, stats_df, out_dir) {
  3926. dfp <- dplyr::filter(stats_df, marker_list == lst_name)
  3927. p <- ggplot(dfp, aes(x = day_norm, y = mean_val, fill = neuron)) +
  3928. geom_hline(yintercept = 1, linetype = "dashed", linewidth = 0.3, color = "black") +
  3929. geom_bar(stat = "identity", width = 1.0, color = "black", linewidth = 0.3) +
  3930. geom_errorbar(aes(ymin = mean_val - sem_val, ymax = mean_val + sem_val),
  3931. width = 0.3, linewidth = 0.3) +
  3932. scale_fill_manual(values = c("iN" = "grey50", "iDA" = "grey80")) +
  3933. facet_wrap(~ neuron, nrow = 1) +
  3934. labs(title = paste0(lst_name, " – ctrl, normalized to d30"),
  3935. x = NULL, y = "Relative abundance (d30 = 1)") +
  3936. theme_bw(base_size = 6) +
  3937. theme(panel.grid = element_blank(),
  3938. legend.position = "none",
  3939. axis.text.x = element_text(angle = 90, hjust = 1),
  3940. strip.background = element_rect(fill = "grey90", color = NA))
  3941. ggsave(file.path(out_dir, paste0("neurodev_

diff132_d50_nDIA.Rmd at commit 6587ebc, under MIT · at the source

Overview

Authors: Felix Kraus1, Yuchen He1, Yizhi Jiang1,2, Delong Li2,3, Yohannes A. Ambaw4, Federico M. Gasparoli1, Joao A. Paulo1, Tobias C. Walther4,5, Robert V. Farese Jr.4, Steven P. Gygi1, Florian Wilfling2,3, J. Wade Harper1,2
  1. Department of Cell Biology, Harvard Medical School, Boston, MA 02115
  2. Aligning Science Across Parkinson’s Collaborative Research Network, Chevy Chase, MD 20815
  3. Mechanisms of Cellular Quality Control, Max Planck Institute of Biophysics, Frankfurt 60438, Germany
  4. Cell Biology Program, Sloan Kettering Institute, New York, NY 10065
  5. Howard Hughes Medical Institute, New York, NY 10065
Institutions: Harvard University (United States); Max Planck Institute of Biophysics (Germany); Howard Hughes Medical Institute (United States)
Dates: received 20 March 2026; accepted 7 June 2026; published online 1 July 2026; in print 7 July 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1073/pnas.2609132123 · PMID 42384675 · PMCID PMC13342943 · OpenAlex W7166882155
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), human (organism), cellular / molecular (subfield)
Keywords: lysosome, proteomics, iNeurons, organelle, protein interactions
MeSH: Dopaminergic Neurons*, Lysosomal Storage Diseases*, Proteome*, Animals, Cerebral Cortex, Glucosylceramidase, Human Embryonic Stem Cells, Humans, Lysosomes, Mitochondria, Proteomics (* major topic)
Journal subjects: Biological Sciences, Cell Biology
Topic: Lysosomal Storage Disorders Research (Physiology, Medicine), according to OpenAlex
Funding: NIH (NS110395, GM132129); Aligning Science Across Parkinson’s (024268)
Citations: not cited yet (Europe PMC); 51 references in the paper

Abstract

Lysosomes maintain cellular homeostasis by degrading proteins delivered via endocytosis and autophagy and by recycling building blocks for organelle biogenesis. Lysosomal storage disorders (LSDs) comprise a group of diseases affecting diverse lysosomal functions. To facilitate molecular phenotyping across diverse LSD gene classes, we are developing a library of human embryonic stem cells engineered to lack individual LSD genes as a resource for the field. Here, we report our initial stem cell toolkit lacking one of 23 LSD genes, including the majority of genes associated with sphingolipidoses and neuronal ceroid lipofuscinoses, and its use in the generation of a proteomic resource for induced cortical-like and midbrain dopaminergic-like neurons. In-depth abundance and correlation profiling across organelles and suborganelle components revealed potential vulnerabilities that reflect distinct patterns of proteome alterations across both genotypes and neuronal cell types. We characterize alterations in the mitochondrial proteome associated with GBA1 and ASAH1 deficiency and identify synaptic and mitochondrial defects in ASAH1−/− induced neurons that correlate with defects in neuronal firing rates. Moreover, we developed an informatic pipeline for proteome-wide identification of individual protein-protein interactions and protein complexes that may be disrupted as a result of LSD gene deficiency. Finally, we visualized structural alterations of ASAH1-deficient endolysosomes in situ using cryoelectron tomography, revealing swollen organelles that were largely devoid of dense internal membranes characteristic of wild-type cells, but containing numerous intralumenal vesicle compartments. This toolkit and associated proteomic landscapes provide a resource for defining molecular signatures associated with LSD gene dysfunction and organelle vulnerability.

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

sauerkrausi/neuroLSD

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 6587ebc16686d3c60f7338de524fc7344c1d929f, 1 July 2026
Languages: R (11), Python (9)
Size: 41 files, 20 scripts
Software Heritage: not archived
Found in: “Data, Materials, and Software Availability”
Holds: README, license file, environment (imaging/Calcium/requirements.txt, imaging/Calcium/requirements_MPS.txt, imaging/endolyso/requirements_MPS.txt, imaging/TH/requirements_MPS.txt), 10 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: tidyverse (10 files), cowplot (8 files), patchwork (8 files), pheatmap (8 files), ggplot2 (7 files), NumPy (7 files), tifffile (7 files), ggpubr (6 files), Matplotlib (6 files), pandas (6 files), scikit-image (6 files), data.table (5 files), reshape2 (5 files), Cellpose (4 files), Plotly (3 files), PyTorch (3 files), broom (2 files), circlize (2 files), clusterProfiler (2 files), igraph (2 files), limma (2 files), rstatix (2 files), SciPy (2 files), ComplexHeatmap (1 file), seaborn (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
22 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;
  • 20 scripts, each with its path and the digest of its content;
  • 7 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

Datasets cited

Data, Materials, and Software Availability

Proteomic data (.RAW files) for nDIA of iN/iDA day 30, 50, and 70 iNeurons are deposited in the MASSIVE Database (https://massive.ucsd.edu/ProteoSAFe/static/massive.jsp): accession number MSV000099237 (https://massive.ucsd.edu/ProteoSAFe/dataset.jsp?task=cafb5f85380547faae2d32c7a9f7573f). HeLa LysoIP TMT data were deposited into ProteomeXchange (50): PXD067219 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD067219). Lipidomics data are deposited on Metabolomics Workbench (51): Study ID ST004217; 10.21228/M8Z556 (https://doi.org/10.21228/M8Z556). Raw Cryo-ET tomograms have been deposited at EMDB under accession numbers EMD-55210 (https://www.ebi.ac.uk/emdb/EMD-55210) (Control) and EMD-55211 (https://www.ebi.ac.uk/emdb/EMD-55211) (ASAH1−/−). A Key Resource Table containing reagents and materials, source data, segmentation models, and datasets associated with this publication are available from Zenodo.org: 10.5281/zenodo.17296003 (https://doi.org/10.5281/zenodo.17296003). The Key Resource Table also contains unique Cellosaurus (https://www.cellosaurus.org/) identifiers for all cell lines reported here, which are available upon request with requisite Material Transfer Agreements from WiCell. Scripts affiliated with this work are available on Github under the repository https://github.com/sauerkrausi/neuroLSD. Data viewer can be found at https://wren.hms.harvard.edu/ProteomeLSDNeuron/.

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

Versions

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

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 12 authors, 5 keywords, 11 MeSH terms, 2 funders, 51 references.

Cite

This paper

Kraus, F., He, Y., Jiang, Y., Li, D., Ambaw, Y. A., Gasparoli, F. M., Paulo, J. A., Walther, T. C., Farese, R. V., Gygi, S. P., Wilfling, F., & Harper, J. W. (2026). A human lysosomal storage disorder toolkit for decoding proteome landscapes in cortical-like and dopaminergic-like induced neurons. Proceedings of the National Academy of Sciences of the United States of America, 123(27), e2609132123. https://doi.org/10.1073/pnas.2609132123

BibTeX

@article{kraus2026human,
author = {Kraus, Felix and He, Yuchen and Jiang, Yizhi and Li, Delong and Ambaw, Yohannes A. and Gasparoli, Federico M. and Paulo, Joao A. and Walther, Tobias C. and Farese, Robert V. and Gygi, Steven P. and Wilfling, Florian and Harper, J. Wade},
title = {{A human lysosomal storage disorder toolkit for decoding proteome landscapes in cortical-like and dopaminergic-like induced neurons}},
journal = {Proceedings of the National Academy of Sciences of the United States of America},
year = {2026},
month = jul,
volume = {123},
number = {27},
pages = {e2609132123},
publisher = {National Academy of Sciences},
issn = {0027-8424},
doi = {10.1073/pnas.2609132123},
url = {https://doi.org/10.1073/pnas.2609132123},
pmid = {42384675},
pmcid = {PMC13342943}
}

RIS

TY - JOUR
AU - Kraus, Felix
AU - He, Yuchen
AU - Jiang, Yizhi
AU - Li, Delong
AU - Ambaw, Yohannes A.
AU - Gasparoli, Federico M.
AU - Paulo, Joao A.
AU - Walther, Tobias C.
AU - Farese, Robert V.
AU - Gygi, Steven P.
AU - Wilfling, Florian
AU - Harper, J. Wade
TI - A human lysosomal storage disorder toolkit for decoding proteome landscapes in cortical-like and dopaminergic-like induced neurons
T2 - Proceedings of the National Academy of Sciences of the United States of America
J2 - Proc Natl Acad Sci U S A
PY - 2026
DA - 2026/07/01
VL - 123
IS - 27
SP - e2609132123
SN - 0027-8424
PB - National Academy of Sciences
DO - 10.1073/pnas.2609132123
UR - https://doi.org/10.1073/pnas.2609132123
LA - en
ER -

CSL-JSON

{
"id": "10.1073/pnas.2609132123",
"type": "article-journal",
"title": "A human lysosomal storage disorder toolkit for decoding proteome landscapes in cortical-like and dopaminergic-like induced neurons",
"container-title": "Proceedings of the National Academy of Sciences of the United States of America",
"author": [
{
"family": "Kraus",
"given": "Felix"
},
{
"family": "He",
"given": "Yuchen"
},
{
"family": "Jiang",
"given": "Yizhi"
},
{
"family": "Li",
"given": "Delong"
},
{
"family": "Ambaw",
"given": "Yohannes A."
},
{
"family": "Gasparoli",
"given": "Federico M."
},
{
"family": "Paulo",
"given": "Joao A."
},
{
"family": "Walther",
"given": "Tobias C."
},
{
"family": "Farese",
"given": "Robert V."
},
{
"family": "Gygi",
"given": "Steven P."
},
{
"family": "Wilfling",
"given": "Florian"
},
{
"family": "Harper",
"given": "J. Wade"
}
],
"container-title-short": "Proc Natl Acad Sci U S A",
"volume": "123",
"issue": "27",
"page": "e2609132123",
"DOI": "10.1073/pnas.2609132123",
"PMID": "42384675",
"PMCID": "PMC13342943",
"ISSN": "0027-8424",
"publisher": "National Academy of Sciences",
"URL": "https://doi.org/10.1073/pnas.2609132123",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
1
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1093/bioinformatics/btag592 [code]
Network-based stratification of allele-specific expression reveals patient subgroups in Huntington's disease.
Journal: Bioinformatics (Oxford, England)
In common: limma, rstatix, igraph, 18 other tools, genetics / omics, 1 reference
[2] 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: tifffile, limma, igraph, 18 other tools, genetics / omics, cellular / molecular
[3] doi:10.1002/imt2.70163 [code]
Spatial multi-omics unveils sphingolipid metabolic reprogramming within the retinal pathological niche.
Journal: iMeta
In common: limma, rstatix, igraph, 18 other tools, genetics / omics, cellular / molecular
[4] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: limma, igraph, broom, 18 other tools, cellular / molecular
[5] doi:10.1038/s41467-026-74753-y [code]
A human-specific microRNA controls the timing of excitatory synaptogenesis.
Journal: Nature communications
In common: limma, rstatix, igraph, 11 other tools, cellular / molecular, 2 references
[6] doi:10.1016/j.xcrm.2026.102651 [code]
Integrative CSF profiling identifies disease-specific immune responses in leptomeningeal disease.
Journal: Cell reports. Medicine
In common: igraph, broom, circlize, 15 other tools, genetics / omics, cellular / molecular
[7] doi:10.1126/sciadv.aeg3223 [code]
The extreme diversity of retinal amacrine cells has deep evolutionary roots.
Journal: Science advances
In common: rstatix, igraph, circlize, 14 other tools, genetics / omics, cellular / molecular, 1 reference
[8] doi:10.1093/neuonc/noag128 [code]
Spatially-resolved single-cell imaging of melanoma brain metastases identifies localized immune patterns predictive of immune checkpoint blockade response.
Journal: Neuro-oncology
In common: Cellpose, tifffile, rstatix, 13 other tools, genetics / omics
[9] doi:10.1038/s41586-026-10214-2 [code]
Multidimensional profiling of heterogeneity in supratentorial ependymomas.
Journal: Nature
In common: igraph, circlize, clusterProfiler, 14 other tools, genetics / omics, 1 reference
[10] doi:10.1016/j.cpblue.2026.100007 [code]
An integrated single-cell and spatial proteotranscriptomics atlas of fibroblast-driven immunoregulation within the human adult oral cavity.
Journal: Cell press blue
In common: limma, igraph, circlize, 15 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.