OSCR

Functional genomic profiling of schizophrenia-associated genes reveals key microglial regulators.

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 › Transcriptomic profiling reveals diverse gene targeting effects on microglial state ↔ image_analysis/Crispr30_publication_final.Rmd, lines 393–469 · score 0.86 · HLA DMB, SLC11A1, FCGR3B, NCKAP1L, ARHGAP25, BIN2
  2. [2] § Results › Transcriptomic profiling reveals diverse gene targeting effects on microglial state ↔ drug_seq/generate_string_plot_final.Rmd, lines 32–48 · score 0.77 · HLA DMB, SLC11A1, NCKAP1L, ARHGAP25, BIN2, TLR6
  3. [3] § Results › Functional programs induced by targeted genes ↔ drug_seq/03_01_analyze_deg_go.R, lines 441–500 · score 0.66 · cell adhesion, complement activation, protein production, cytokine, GO, chemokine
  4. [4] § Results › Functional programs induced by targeted genes ↔ image_analysis/Crispr30_publication_final.Rmd, lines 393–469 · score 0.59 · CLEC7A, FCGR2A, CD14, IRF8, SYK, TREM2
  5. [5] § Results › Functional programs induced by targeted genes ↔ drug_seq/generate_string_plot_final.Rmd, lines 32–48 · score 0.58 · CLEC7A, FCGR2A, CD14, IRF8, SYK, TREM2
  6. [6] § Results › Transcriptomic profiling reveals diverse gene targeting effects on microglial state ↔ drug_seq/03_01_analyze_deg_go.R, lines 113–229 · score 0.52 · log2 fold change, log2FC, Asterisks, Pairwise, pvalue, DEGs
  7. [7] § Results › Functional programs induced by targeted genes ↔ drug_seq/03_01_analyze_deg_go.R, lines 113–229 · score 0.51 · log2 fold change, log2FC, Asterisks, GO, heatmaps, DEG

Paper

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

The paper is loaded when this pane is shown.

The authors' code

R · 1,454 lines · 49 KB · no license · 3 matches

  1. LIB='/cluster/tufts/patralab/rbator01/R_libs/4.4.0'
  2. .libPaths(c("",LIB))
  3. library(pheatmap)
  4. library(tidyverse)
  5. library(ComplexHeatmap)
  6. library(openxlsx)
  7. library(ggforce)
  8. library(data.table)
  9. library(clusterProfiler)
  10. library('org.Hs.eg.db')
  11. library(data.table)
  12. library(circlize)
  13. library(grid)
  14. library(forcats)
  15. library(stringr)
  16. library(janitor)
  17. library(DESeq2)
  18. library(ggbreak)
  19. select = dplyr::select
  20. rename = dplyr::rename
  21. filter=dplyr::filter
  22. # paths and setup ----
  23. setwd('/cluster/tufts/patralab/rbator01/perlis_lab/cspr_apr25/')
  24. # DEG thresholds
  25. padj_thresh = 0.1
  26. praw_thresh = 0.1
  27. # load metadata and counts ----
  28. meta = read.xlsx("analysis/metadata_batch1/241210_Joy30_Metadata_format.xlsx")
  29. ctrl_counts = read.xlsx("analysis/nct1_expression/nct1_cpm_ko_genes.xlsx")
  30. rm_samples = fread("analysis/de/removed_samples_control_batch_q1q3_filtered_batch1_batch2.csv") %>%
  31. mutate(tmp = name) %>%
  32. separate(tmp, into=c("ko_gene","well","batch"), sep="\\|")
  33. n_rm = rm_samples %>%
  34. filter(batch == "b1") %>%
  35. group_by(ko_gene) %>%
  36. mutate(n_rm = n()) %>%
  37. ungroup() %>%
  38. as.data.frame()
  39. # deg results b1 -----
  40. b1 = fread("analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv")
  41. head(b1)
  42. res_sig_b1 = b1 %>%
  43. filter(pvalue < praw_thresh)
  44. # ko_gene in gene_name at praw < praw_thresh
  45. ko_in_praw_df_b1 <- res_sig_b1 %>%
  46. group_by(ko_gene) %>%
  47. summarise(ko_in_praw_deg_b1 = as.integer(ko_gene %in% gene_name), .groups = "drop")%>%
  48. distinct()
  49. # ko_gene in gene_name at padj < padj_thresh
  50. ko_in_padj_df_b1 <- res_sig_b1 %>%
  51. filter(padj < padj_thresh) %>%
  52. group_by(ko_gene) %>%
  53. summarise(ko_in_padj_deg_b1 = as.integer(ko_gene %in% gene_name), .groups = "drop")%>%
  54. distinct()
  55. # count DEGs
  56. t_b1 = as.data.frame(table((b1 %>% filter(padj < padj_thresh))$ko_gene))
  57. colnames(t_b1) = c("ko_gene","ndeg_padj_0.1_b1")
  58. top_b1 <- b1 %>%
  59. group_by(ko_gene) %>%
  60. arrange(padj) %>%
  61. slice_min(order_by = padj, n = 10, with_ties = FALSE) %>%
  62. select(ko_gene, gene_name) %>%
  63. rename(top_b1 = gene_name)
  64. top_b1_collapsed <- aggregate(top_b1 ~ ko_gene, data = top_b1, FUN = function(x) paste(x, collapse = ","))
  65. # assemble summary workbook ---------
  66. final = ctrl_counts %>%
  67. rename(ko_gene=gene_name) %>%
  68. mutate(coverage_pass = ifelse(mean_cpm > 5, 1,0)) %>%
  69. full_join(n_rm %>% dplyr::select(ko_gene, n_rm), by="ko_gene") %>%
  70. left_join(t_b1, by = "ko_gene") %>%
  71. left_join(ko_in_padj_df_b1, by = "ko_gene") %>%
  72. left_join(ko_in_praw_df_b1, by = "ko_gene") %>%
  73. left_join(top_b1_collapsed, by = "ko_gene")
  74. # Replace NA with 0
  75. final <- final %>%
  76. mutate(across(starts_with("ko_in_padj_deg_"), ~ if_else(is.na(.), 0L, .))) %>%
  77. mutate(across(starts_with("ko_in_praw_deg_"), ~ if_else(is.na(.), 0L, .))) %>%
  78. mutate(across(starts_with("ndeg_padj_0.1_"), ~ if_else(is.na(.), 0L, as.integer(.))))
  79. final = final %>%
  80. distinct()
  81. write.xlsx(final, "analysis/de/summary_batch1_q1q3_filter_rmlow_22aug25.xlsx")
  82. # Omics Fig 1A: DEG target overlap, batch 1 only =======
  83. dds = readRDS("analysis/processed/umi.trim.1mm_all_dedup.counts.dds.rds")
  84. genes_rm = c('RBFOX3','FCGR3B','PLD4','FCAR','NTC1')
  85. meta = colData(dds) %>%
  86. as.data.frame() %>%
  87. filter(!(KO.Target %in% genes_rm))
  88. all_targets = unique(meta$KO.Target)
  89. all_targets = factor(all_targets, levels = all_targets)
  90. ## Helper: build KO–KO log2FC heatmap with asterisks for significant entries
  91. make_heat <- function(deg_file,
  92. title_text,
  93. out_pdf,
  94. lfc_type = c("raw", "shrunken"),
  95. sig_col = c("padj", "pvalue"),
  96. sig_thresh = 0.1) {
  97. lfc_type <- match.arg(lfc_type) # "raw" or "shrunken"
  98. sig_col <- match.arg(sig_col) # "padj" or "pvalue"
  99. # Append info to title for clarity
  100. #title_text <- paste0(title_text, " ", lfc_type, " (", sig_col, " < ", sig_thresh, ")")
  101. # read & standardize column names
  102. deg <- data.table::fread(deg_file) %>%
  103. dplyr::rename(gene = gene_name)
  104. # ensure chosen significance column is present
  105. if (!sig_col %in% colnames(deg)) {
  106. stop(sprintf("Column '%s' not found in '%s'.", sig_col, deg_file))
  107. }
  108. # choose LFC column
  109. lfc_col <- if (lfc_type == "raw") "log2FoldChange_raw" else "log2FoldChange"
  110. # keep targets, aggregate duplicates:
  111. # - LFC columns: mean
  112. # - star_flag: TRUE if any chosen sig_col < sig_thresh
  113. agg <- deg %>%
  114. dplyr::select(gene, ko_gene, log2FoldChange_raw, log2FoldChange, pvalue, padj) %>%
  115. dplyr::filter(gene %in% all_targets, ko_gene %in% all_targets) %>%
  116. dplyr::group_by(gene, ko_gene) %>%
  117. dplyr::summarise(
  118. log2FoldChange_raw = mean(log2FoldChange_raw, na.rm = TRUE),
  119. log2FoldChange = mean(log2FoldChange, na.rm = TRUE),
  120. star_flag = any(.data[[sig_col]] < sig_thresh, na.rm = TRUE),
  121. .groups = "drop"
  122. ) %>%
  123. dplyr::mutate(dplyr::across(c(log2FoldChange_raw, log2FoldChange),
  124. ~ ifelse(is.nan(.x), NA_real_, .x)))
  125. # complete to full target grid; impute chosen LFC to 0
  126. heat_long <- agg %>%
  127. tidyr::complete(
  128. gene = all_targets,
  129. ko_gene = all_targets
  130. ) %>%
  131. dplyr::mutate(
  132. value = .data[[lfc_col]],
  133. value = tidyr::replace_na(value, 0),
  134. star_flag = tidyr::replace_na(star_flag, FALSE),
  135. gene = factor(gene, levels = all_targets),
  136. ko_gene = factor(ko_gene, levels = all_targets)
  137. )
  138. # value matrix
  139. heat_mat <- heat_long %>%
  140. dplyr::select(gene, ko_gene, value) %>%
  141. tidyr::pivot_wider(names_from = ko_gene, values_from = value) %>%
  142. tibble::column_to_rownames("gene") %>%
  143. as.matrix()
  144. # asterisk (logical) matrix
  145. star_mat <- heat_long %>%
  146. dplyr::select(gene, ko_gene, star_flag) %>%
  147. tidyr::pivot_wider(names_from = ko_gene, values_from = star_flag) %>%
  148. tibble::column_to_rownames("gene") %>%
  149. as.matrix()
  150. ## cluster columns (correlation distance)
  151. d_col <- as.dist(1 - stats::cor(heat_mat, use = "pairwise.complete.obs"))
  152. col_clust <- stats::hclust(d_col, method = "average")
  153. col_order_names <- colnames(heat_mat)[col_clust$order]
  154. # row order aligned to clustered columns (append non-overlapping rows)
  155. common <- intersect(col_order_names, rownames(heat_mat))
  156. first_part <- match(common, rownames(heat_mat))
  157. row_order <- c(first_part, setdiff(seq_len(nrow(heat_mat)), first_part))
  158. row_gp <- grid::gpar(fontfamily = "Arial", fontsize = 8)
  159. col_gp <- grid::gpar(fontfamily = "Arial", fontsize = 8)
  160. title_gp <- grid::gpar(fontfamily = "Arial", fontsize = 12, fontface = "bold")
  161. legend_gp <- grid::gpar(fontfamily = "Arial")
  162. ht <- ComplexHeatmap::Heatmap(
  163. heat_mat,
  164. name = "log2FC", #ifelse(lfc_type == "raw", "log2FC_raw", "log2FC_shrunken"),
  165. col = circlize::colorRamp2(
  166. c(min(heat_mat, na.rm = TRUE), 0, max(heat_mat, na.rm = TRUE)),
  167. c("blue", "white", "red")
  168. ),
  169. cluster_columns = as.dendrogram(col_clust),
  170. cluster_rows = FALSE,
  171. row_order = row_order,
  172. show_row_names = TRUE,
  173. show_column_names = TRUE,
  174. row_names_gp = row_gp,
  175. column_names_gp = col_gp,
  176. column_title_gp = title_gp,
  177. heatmap_legend_param = list(
  178. labels_gp = legend_gp,
  179. title_gp = grid::gpar(fontfamily = "Arial", fontface = "bold")
  180. ),
  181. cell_fun = function(j, i, x, y, width, height, fill) {
  182. if (!is.na(star_mat[i, j]) && star_mat[i, j]) {
  183. grid::grid.text("*", x = x, y = y,
  184. gp = grid::gpar(fontfamily = "Arial", fontsize = 10, fontface = "bold"))
  185. }
  186. }
  187. )
  188. # Use Cairo to embed Arial cleanly in the PDF
  189. Cairo::CairoPDF(out_pdf, height = 5, width = 6, family = "Arial")
  190. on.exit(dev.off(), add = TRUE)
  191. ComplexHeatmap::draw(ht)
  192. }
  193. # KO–KO overlap using raw LFC and p-value < 0.1
  194. make_heat(
  195. deg_file = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  196. title_text = "",
  197. out_pdf = "analysis/de/plots/deg_target_overlap_b1_lfcraw_pval_0.1.pdf",
  198. lfc_type = "raw",
  199. sig_col = "pvalue",
  200. sig_thresh = 0.1 )
  201. make_heat(
  202. deg_file = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  203. title_text = "",
  204. out_pdf = "analysis/de/plots/deg_target_overlap_b1_lfcraw_padj_0.1.pdf",
  205. lfc_type = "raw",
  206. sig_col = "padj",
  207. sig_thresh = 0.1 )
  208. # Barplot NUMBER DEG =======
  209. tab <- b1 %>%
  210. group_by(ko_gene) %>%
  211. summarise(n = sum(padj < 0.1, na.rm = TRUE), .groups = "drop")
  212. # Color/category per KO from the *diagonal* row (ko_gene == gene_name)
  213. diag_map <- b1 %>%
  214. filter(ko_gene == gene_name) %>%
  215. transmute(
  216. ko_gene,
  217. p_raw = pvalue,
  218. lfc = dplyr::coalesce(log2FoldChange, log2FoldChange_raw),
  219. direction = case_when(
  220. is.finite(p_raw) & p_raw < 0.1 & is.finite(lfc) & lfc > 0 ~ "sig_up",
  221. is.finite(p_raw) & p_raw < 0.1 & is.finite(lfc) & lfc < 0 ~ "sig_down",
  222. TRUE ~ "other"
  223. )
  224. )
  225. tab <- tab %>%
  226. left_join(diag_map[, c("ko_gene", "direction")], by = "ko_gene") %>%
  227. mutate(direction = tidyr::replace_na(direction, "other"),
  228. direction = factor(direction, levels = c("sig_up", "sig_down", "other")))
  229. # Find outlier and compute a sensible break
  230. top_row <- tab %>% slice_max(n, n = 1, with_ties = FALSE)
  231. top_val <- top_row$n
  232. max_others <- max(tab$n[tab$ko_gene != top_row$ko_gene], na.rm = TRUE)
  233. gap <- max(5, floor(0.05 * (top_val - max_others))) # ~5% gap or ≥5
  234. lower_brk <- max_others + gap
  235. upper_brk <- top_val - gap
  236. # Axis ticks: show normal ticks below break + a single tick at 1100 above break
  237. lower_ticks <- pretty(c(0, max_others))
  238. upper_tick <- 1100
  239. all_breaks <- unique(c(lower_ticks, upper_tick))
  240. p <- ggplot(tab, aes(x = fct_reorder(ko_gene, n), y = n, fill = direction)) +
  241. geom_col(color = "black") +
  242. coord_flip(clip = "off") +
  243. labs(
  244. x = "Target Gene",
  245. y = "n (DEGs with padj < 0.1)",
  246. title = "",
  247. fill = "DEG status of Target Gene"
  248. ) +
  249. scale_fill_manual(
  250. values = c(sig_up = "red", sig_down = "blue", other = "black"),
  251. labels = c(
  252. sig_up = "p < 0.1 & log2FC > 0",
  253. sig_down = "p < 0.1 & log2FC < 0",
  254. other = "otherwise"
  255. )
  256. ) +
  257. # Broken axis
  258. ggbreak::scale_y_break(c(lower_brk, upper_brk)) +
  259. # Only label lower ticks + one tick (1100) in the upper segment
  260. scale_y_continuous(
  261. breaks = all_breaks,
  262. labels = function(b) ifelse(b == upper_tick, "1100", b)
  263. ) +
  264. theme_minimal(base_size = 12, base_family = "Arial") +
  265. theme(
  266. text = element_text(family = "Arial"),
  267. plot.title = element_text(face = "bold"),
  268. legend.position = "right",
  269. plot.margin = margin(5.5, 20, 5.5, 5.5)
  270. ) +
  271. # ensure the 1100 tick is in range even if top_val < 1100
  272. expand_limits(y = max(top_val, upper_tick) * 1.02)
  273. systemfonts::match_font("Arial")
  274. show(p)
  275. ggsave("analysis/de/plots/deg_per_ko_gene.pdf", p,width = 7.5, height = 5, device = cairo_pdf, family = "Arial",onefile = FALSE)
  276. # Same barplot but all blue (no categories in legend)
  277. p <- ggplot(tab, aes(x = fct_reorder(ko_gene, n), y = n)) +
  278. geom_col(color = "black", fill = "blue") +
  279. coord_flip(clip = "off") +
  280. labs(
  281. x = "Target Gene",
  282. y = "n (DEGs with padj < 0.1)",
  283. title = ""
  284. ) +
  285. ggbreak::scale_y_break(c(lower_brk, upper_brk)) +
  286. scale_y_continuous(
  287. breaks = all_breaks,
  288. labels = function(b) ifelse(b == upper_tick, "1100", b)
  289. ) +
  290. theme_minimal(base_size = 12, base_family = "Arial") +
  291. theme(
  292. text = element_text(family = "Arial"),
  293. plot.title = element_text(face = "bold"),
  294. legend.position = "none", # hide legend
  295. plot.margin = margin(5.5, 20, 5.5, 5.5)
  296. ) +
  297. expand_limits(y = max(top_val, upper_tick) * 1.02)
  298. print(p)
  299. ggsave("analysis/de/plots/deg_per_ko_gene_blue.pdf", p,
  300. width = 5, height = 5, device = cairo_pdf, family = "Arial", onefile = FALSE)
  301. # GO ENRICHMENT for targets that have >10 DEG =================
  302. b1_filter = fread("analysis/de/all_res_vs_ntc1_padj_0.1_10deg_batch1_q1q3_filter_rmlow.tsv")
  303. ck<- compareCluster(geneCluster = ens ~ ko_gene,
  304. data = b1_filter,
  305. OrgDb = org.Hs.eg.db,
  306. keyType="ENSEMBL",
  307. fun = "enrichGO",
  308. ont="BP",
  309. readable=T)
  310. saveRDS(ck, "analysis/de/go_bp_batch1_padj_0.1_10deg.rds")
  311. write.xlsx(ck, "analysis/de/go_bp_batch1_padj_0.1_10deg.xlsx")
  312. ck<- compareCluster(geneCluster = ens ~ ko_gene,
  313. data = b1_filter,
  314. OrgDb = org.Hs.eg.db,
  315. keyType="ENSEMBL",
  316. fun = "enrichGO",
  317. ont="CC",
  318. readable=T)
  319. saveRDS(ck, "analysis/de/go_cc_batch1_padj_0.1_10deg.rds")
  320. write.xlsx(ck, "analysis/de/go_cc_batch1_padj_0.1_10deg.xlsx")
  321. ck<- compareCluster(geneCluster = ens ~ ko_gene,
  322. data = b1_filter,
  323. OrgDb = org.Hs.eg.db,
  324. keyType="ENSEMBL",
  325. fun = "enrichGO",
  326. ont="MF",
  327. readable=T)
  328. saveRDS(ck, "analysis/de/go_mf_batch1_padj_0.1_10deg.rds")
  329. write.xlsx(ck, "analysis/de/go_mf_batch1_padj_0.1_10deg.xlsx")
  330. ## Quick sanity plots for GO enrichments (BP / MF / CC)
  331. ck = readRDS("analysis/de/go_bp_batch1_padj_0.1.rds")
  332. dotplot(ck, show=3)+
  333. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+
  334. scale_y_discrete(labels = ~ str_wrap(as.character(.x), 100))
  335. ck = readRDS("analysis/de/go_mf_batch1_padj_0.1.rds")
  336. dotplot(ck, show=3)+
  337. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+
  338. scale_y_discrete(labels = ~ str_wrap(as.character(.x), 100))
  339. ck = readRDS("analysis/de/go_cc_batch1_padj_0.1.rds")
  340. dotplot(ck, show=3)+
  341. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+
  342. scale_y_discrete(labels = ~ str_wrap(as.character(.x), 100))
  343. ## GO BP: filter by keyword families ----
  344. keywords <- c(
  345. "phago", "autophag",
  346. "neuro",
  347. "macrophage", "activation",
  348. "adhesion", "attachment", "integrin", "actin",
  349. "chemotaxis", "chemokine", "cytokine",
  350. "inflamm", "stress",
  351. "lyso",
  352. "metabolic", "oxydat",
  353. "mitochondria"
  354. )
  355. ck = readRDS("analysis/de/go_bp_batch1_padj_0.1_10deg.rds")
  356. # Collapse keywords into regex pattern
  357. pattern <- paste(keywords, collapse = "|")
  358. # Filter the dataframe based on keyword match in the specified column
  359. ck_filter = ck %>%
  360. filter(grepl(pattern, ck@compareClusterResult$Description, ignore.case = TRUE))
  361. dotplot(ck_filter, show=3)+
  362. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+
  363. scale_y_discrete(labels = ~ str_wrap(as.character(.x), 100))
  364. # Select terms from collaborators for initial combination -----
  365. #BP
  366. ck = readRDS("analysis/de/go_bp_batch1_padj_0.1_10deg.rds")
  367. s = read.xlsx("analysis/de/go_bp_batch1_padj_0.1_10deg_sds.xlsx") %>%
  368. mutate(select = ifelse(select == 1, 's',select)) %>%
  369. filter(select == 's') %>%
  370. select(ID, select)
  371. l = read.xlsx("analysis/de/go_bp_batch1_padj_0.1_10deg_LM.xlsx") %>%
  372. filter(!is.na(LM)) %>%
  373. rename(select = LM) %>%
  374. mutate(select = 'l') %>%
  375. select(ID, select)
  376. c = read.xlsx("analysis/de/go_bp_batch1_padj_0.1_10deg.xlsx") %>%
  377. filter(!is.na(CAMILLA_list)) %>%
  378. rename(select = CAMILLA_list ) %>%
  379. select(ID, select)
  380. all_select = rbind(s,l,c) %>%
  381. distinct()
  382. dedup_select <- all_select %>%
  383. mutate(
  384. is_num = grepl("^\\d+$", select),
  385. # convert only the numeric entries; letters -> NA (no warning)
  386. num_val = as.integer(replace(select, !is_num, NA_character_)),
  387. letter_score = case_when(
  388. !is_num & select == "s" ~ 0L, # "s" beats other letters
  389. !is_num ~ 1L,
  390. TRUE ~ NA_integer_
  391. ),
  392. key1 = if_else(is_num, 0L, 1L), # numbers (0) beat letters (1)
  393. key2 = if_else(is_num, num_val, letter_score) # among numbers: smallest wins; among letters: "s" wins
  394. ) %>%
  395. arrange(ID, key1, key2) %>%
  396. distinct(ID, .keep_all = TRUE) %>% # one row per GO ID
  397. select(ID, select)
  398. ck_select = ck@compareClusterResult %>%
  399. left_join(dedup_select, by="ID") %>%
  400. filter(p.adjust < 0.05)
  401. lab_map <- c(
  402. `1` = "Phagocytosis / Complement activation",
  403. `2` = "Cell adhesion",
  404. `3` = "Chemotaxis",
  405. `4` = "PH regulation",
  406. `5` = "Chemokines/Cytokines",
  407. `6` = "Protein production",
  408. l = "liam",
  409. s = "steve"
  410. )
  411. ck_select$select_label <- lab_map[as.character(ck_select$select)]
  412. write.xlsx(ck_select, "analysis/de/go_bp_batch1_padj_0.1_10deg_allselect.xlsx")
  413. # MF ------
  414. ck = readRDS("analysis/de/go_mf_batch1_padj_0.1")
  415. l = read.xlsx("analysis/de/go_mf_batch1_padj_0.1_10deg_LM.xlsx") %>%
  416. filter(!is.na(LM)) %>%
  417. rename(select = LM) %>%
  418. mutate(select = 'l') %>%
  419. select(ID, select)
  420. c = read.xlsx("analysis/de/go_mf_batch1_padj_0.1_10deg.xlsx") %>%
  421. filter(!is.na(Camilla_list)) %>%
  422. rename(select = Camilla_list ) %>%
  423. select(ID,select)
  424. all_select = rbind(l,c) %>%
  425. distinct()
  426. dedup_select <- all_select %>%
  427. mutate(
  428. is_num = grepl("^\\d+$", select),
  429. # convert only the numeric entries; letters -> NA (no warning)
  430. num_val = as.integer(replace(select, !is_num, NA_character_)),
  431. letter_score = case_when(
  432. !is_num & select == "s" ~ 0L, # "s" beats other letters
  433. !is_num ~ 1L,
  434. TRUE ~ NA_integer_
  435. ),
  436. key1 = if_else(is_num, 0L, 1L), # numbers (0) beat letters (1)
  437. key2 = if_else(is_num, num_val, letter_score) # among numbers: smallest wins; among letters: "s" wins
  438. ) %>%
  439. arrange(ID, key1, key2) %>%
  440. distinct(ID, .keep_all = TRUE) %>% # one row per GO ID
  441. select(ID, select)
  442. ck_select = ck %>%
  443. filter(ID %in% dedup_select$ID)
  444. ck_select_sim <- simplify(ck_select, cutoff = 0.7, by = "p.adjust", select_fun = min)
  445. dotplot(ck_select_sim, show=200)+
  446. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+
  447. scale_y_discrete(labels = ~ str_wrap(as.character(.x), 100))
  448. ck_select_sim_df = ck_select_sim@compareClusterResult %>%
  449. left_join(dedup_select, by="ID")
  450. lab_map <- c(
  451. `1` = "Phagocytosis / Complement activation",
  452. `2` = "Cell adhesion",
  453. `3` = "Chemotaxis",
  454. `4` = "PH regulation",
  455. `5` = "Chemokines/Cytokines",
  456. `6` = "Protein production",
  457. l = "liam",
  458. s = "steve"
  459. )
  460. ck_select_sim_df$select_label <- lab_map[as.character(ck_select_sim_df$select)]
  461. write.xlsx(ck_select_sim_df, "analysis/de/go_mf_batch1_padj_0.1_10deg_allselect.xlsx")
  462. # CC -----
  463. ck = readRDS("analysis/de/go_cc_batch1_padj_0.1_10deg.rds")
  464. l = read.xlsx("analysis/de/go_cc_batch1_padj_0.1_10deg_LM.xlsx") %>%
  465. filter(!is.na(LM)) %>%
  466. rename(select = LM) %>%
  467. mutate(select = 'l') %>%
  468. select(ID, select)
  469. c = read.xlsx("analysis/de/go_cc_batch1_padj_0.1_10deg.xlsx") %>%
  470. filter(!is.na(Camilla_list)) %>%
  471. rename(select = Camilla_list ) %>%
  472. select(ID,select)
  473. all_select = rbind(l,c) %>%
  474. distinct()
  475. dedup_select <- all_select %>%
  476. mutate(
  477. is_num = grepl("^\\d+$", select),
  478. # convert only the numeric entries; letters -> NA (no warning)
  479. num_val = as.integer(replace(select, !is_num, NA_character_)),
  480. letter_score = case_when(
  481. !is_num & select == "s" ~ 0L, # "s" beats other letters
  482. !is_num ~ 1L,
  483. TRUE ~ NA_integer_
  484. ),
  485. key1 = if_else(is_num, 0L, 1L), # numbers (0) beat letters (1)
  486. key2 = if_else(is_num, num_val, letter_score) # among numbers: smallest wins; among letters: "s" wins
  487. ) %>%
  488. arrange(ID, key1, key2) %>%
  489. distinct(ID, .keep_all = TRUE) %>% # one row per GO ID
  490. select(ID, select)
  491. ck_select = ck %>%
  492. filter(ID %in% dedup_select$ID)
  493. ck_select_sim <- simplify(ck_select, cutoff = 0.7, by = "p.adjust", select_fun = min)
  494. dotplot(ck_select_sim, show=200)+
  495. theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))+
  496. scale_y_discrete(labels = ~ str_wrap(as.character(.x), 100))
  497. ck_select_sim_df = ck_select_sim@compareClusterResult %>%
  498. left_join(dedup_select, by="ID")
  499. lab_map <- c(
  500. `1` = "Phagocytosis / Complement activation",
  501. `2` = "Cell adhesion",
  502. `3` = "Chemotaxis",
  503. `4` = "PH regulation",
  504. `5` = "Chemokines/Cytokines",
  505. `6` = "Protein production",
  506. l = "liam",
  507. s = "steve"
  508. )
  509. ck_select_sim_df$select_label <- lab_map[as.character(ck_select_sim_df$select)]
  510. write.xlsx(ck_select_sim_df, "analysis/de/go_cc_batch1_padj_0.1_10deg_allselect.xlsx")
  511. # make a plot of the CC individual results with collaborator selections
  512. # ---------- helpers ----------
  513. safe_min <- function(x) if (all(is.na(x))) NA_real_ else min(x, na.rm = TRUE)
  514. strip_go_brackets <- function(x) sub("\\s*\\[[^\\]]+\\]$", "", x)
  515. # choose one 'select' per y_id:
  516. # - if any numbers → pick the smallest number
  517. # - else letters only → prefer "s", otherwise first other letter
  518. choose_select <- function(x) {
  519. xo <- as.character(x)
  520. nums <- suppressWarnings(as.integer(xo))
  521. if (any(!is.na(nums))) return(as.character(min(nums, na.rm = TRUE)))
  522. if (any(xo == "s")) return("s")
  523. setdiff(unique(xo), "s")[1]
  524. }
  525. # Start from ck_select_sim_df; map columns ----------
  526. base_df <- ck_select_sim_df %>%
  527. transmute(
  528. comparison = Cluster,
  529. GO_ID = ID,
  530. GO_Term = Description,
  531. padj = p.adjust,
  532. Significant_genes_count = Count,
  533. select
  534. )
  535. # OPTIONAL: union of Top-10 terms per comparison by padj
  536. top_ids <- base_df %>%
  537. group_by(comparison) %>%
  538. slice_min(order_by = padj, n = 100, with_ties = FALSE) %>%
  539. pull(GO_ID) %>% unique()
  540. base_df <- base_df %>% filter(GO_ID %in% top_ids)
  541. # Build plot_df ----------
  542. eps <- 1e-300
  543. cap <- 10
  544. # y_id is now JUST the term
  545. plot_df <- base_df %>%
  546. mutate(
  547. mlog10 = pmin(-log10(pmax(padj, eps)), cap),
  548. y_id = GO_Term
  549. ) %>%
  550. group_by(y_id, comparison) %>%
  551. summarise(
  552. mlog10 = if (all(is.na(mlog10))) NA_real_ else max(mlog10, na.rm = TRUE),
  553. Significant_genes_count = if (all(is.na(Significant_genes_count)))
  554. NA_real_ else max(Significant_genes_count, na.rm = TRUE),
  555. .groups = "drop"
  556. ) %>%
  557. # annotation: collapse select per DESCRIPTION
  558. left_join(
  559. base_df %>%
  560. transmute(y_id = GO_Term, select) %>%
  561. group_by(y_id) %>%
  562. summarise(select = choose_select(select), .groups = "drop"),
  563. by = "y_id"
  564. )
  565. # order y by best padj across comparisons
  566. y_levels <- base_df %>%
  567. group_by(GO_Term) %>%
  568. summarise(best_padj = safe_min(padj), .groups = "drop") %>%
  569. arrange(best_padj) %>%
  570. pull(GO_Term) %>% unique()
  571. plot_df <- plot_df %>% mutate(y_id = factor(y_id, levels = y_levels))
  572. # DOTPLOT of −log10(padj) ----------
  573. gg_dot <- ggplot(plot_df, aes(x = comparison, y = y_id)) +
  574. geom_point(aes(size = Significant_genes_count, color = mlog10), na.rm = TRUE) +
  575. scale_size_continuous(name = "Significant genes", range = c(1.8, 8)) +
  576. scale_color_viridis_c(name = expression(-log[10](padj)), option = "C",
  577. direction = 1, na.value = "grey85") +
  578. labs(title = "Selected GO Terms",
  579. subtitle = sprintf("Colored by −log10(padj), capped at %s;\n size = Significant_genes_count", cap),
  580. x = "Comparison", y = NULL) +
  581. theme_bw() +
  582. theme(axis.text.y = element_text(size = 9),
  583. axis.text.x = element_text(angle = 45, hjust = 1))
  584. print(gg_dot)
  585. # FINAL plot with all annotations. =====
  586. # Target order from functional centered on NEG
  587. well_summary=read.xlsx('20250415_Joshua_Bowen_8511/2025822_functionaldata_celllevel_exp31_only_to_show_ipa_go.xlsx')
  588. exp31 = well_summary %>%
  589. filter(Experiment == "EXP31")
  590. control <- "NEG"
  591. average_by_experiment_treatment_well <- function(cell_data) {
  592. cell_data %>%
  593. group_by(Experiment, Treatment, Metadata_Well) %>%
  594. summarise(
  595. across(AreaShape_Area:Cell_Level_Phago_Index, mean, na.rm = TRUE),
  596. .groups = "drop"
  597. )
  598. }
  599. neg_mean <- well_summary %>%
  600. filter(Treatment == control) %>%
  601. summarise(m = mean(Cell_Level_Phago_Index, na.rm = TRUE), .groups = "drop") %>%
  602. pull(m)
  603. centered_order_tbl <- well_summary %>%
  604. group_by(Treatment) %>%
  605. summarise(mean_clpi = mean(Cell_Level_Phago_Index, na.rm = TRUE), .groups = "drop") %>%
  606. mutate(delta_clpi = mean_clpi - neg_mean) %>%
  607. filter(Treatment != control) %>%
  608. arrange(desc(delta_clpi))
  609. # read GO dataframe ------
  610. df_ann <- openxlsx::read.xlsx("analysis/de/go_bp_batch1_padj_0.1_10deg_allselect_rebeccaedit.xlsx")
  611. df <- df_ann %>%
  612. janitor::clean_names() %>%
  613. mutate(
  614. select_label = as.character(select_label),
  615. select_label = stringr::str_squish(select_label),
  616. select_label = dplyr::na_if(select_label, "") # treat "" as NA
  617. ) %>%
  618. filter(!is.na(select_label)) %>%
  619. mutate(
  620. padj_safe = pmax(p_adjust, 1e-300) # guard tiny zeros
  621. )
  622. df_plot0 <- df %>%
  623. transmute(
  624. Pathway = description,
  625. Target = ko_gene,
  626. Count = count,
  627. Group = select_label,
  628. colval = padj_safe
  629. )
  630. # Keep only targets present in well_summary order
  631. go_targets <- unique(df_plot0$Target)
  632. target_levels <- centered_order_tbl %>%
  633. filter(Treatment %in% go_targets) %>%
  634. pull(Treatment)
  635. df_plot0 <- df_plot0 %>%
  636. filter(Target %in% target_levels)
  637. # count filter
  638. df_plot0 <- df_plot0 %>% filter(Count > 2)
  639. # ----------------------------
  640. # Collapse "X" with response/regulation variants
  641. # Keep, per base X: most treatments, then strongest mean -log10(padj),
  642. # then preference: exact < response < cellular response < positive < negative < regulation
  643. # ----------------------------
  644. strip_variants_to_base <- function(s) {
  645. s %>%
  646. # response variants
  647. stringr::str_replace(regex("^cellular\\s+response\\s+to\\s+", ignore_case = TRUE), "") %>%
  648. stringr::str_replace(regex("^response\\s+to\\s+", ignore_case = TRUE), "") %>%
  649. # regulation variants
  650. stringr::str_replace(regex("^positive\\s+regulation\\s+of\\s+", ignore_case = TRUE), "") %>%
  651. stringr::str_replace(regex("^negative\\s+regulation\\s+of\\s+", ignore_case = TRUE), "") %>%
  652. stringr::str_replace(regex("^regulation\\s+of\\s+", ignore_case = TRUE), "") %>%
  653. stringr::str_squish()
  654. }
  655. variant_type <- function(s) {
  656. if (stringr::str_detect(s, regex("^cellular\\s+response\\s+to\\s+", TRUE))) return("resp_cellular")
  657. if (stringr::str_detect(s, regex("^response\\s+to\\s+", TRUE))) return("response")
  658. if (stringr::str_detect(s, regex("^positive\\s+regulation\\s+of\\s+", TRUE))) return("pos")
  659. if (stringr::str_detect(s, regex("^negative\\s+regulation\\s+of\\s+", TRUE))) return("neg")
  660. if (stringr::str_detect(s, regex("^regulation\\s+of\\s+", TRUE))) return("reg")
  661. return("exact")
  662. }
  663. df_plot0 <- df_plot0 %>%
  664. mutate(
  665. base_name = strip_variants_to_base(Pathway),
  666. var_type = vapply(Pathway, variant_type, character(1))
  667. )
  668. # metrics per GO term across all targets
  669. path_metrics <- df_plot0 %>%
  670. group_by(Pathway, base_name) %>%
  671. summarise(
  672. n_treat = n_distinct(Target),
  673. mean_sig = mean(colval, na.rm = TRUE), # smaller is more significant
  674. .groups = "drop"
  675. )
  676. # preference tier: exact (1) < response (2) < cellular response (3) < positive (4) < negative (5) < regulation (6)
  677. path_metrics <- path_metrics %>%
  678. mutate(tier = dplyr::case_when(
  679. grepl("^cellular\\s+response\\s+to\\s+", Pathway, ignore.case = TRUE) ~ 3L,
  680. grepl("^response\\s+to\\s+", Pathway, ignore.case = TRUE) ~ 2L,
  681. grepl("^positive\\s+regulation\\s+of\\s+", Pathway, ignore.case = TRUE) ~ 4L,
  682. grepl("^negative\\s+regulation\\s+of\\s+", Pathway, ignore.case = TRUE) ~ 5L,
  683. grepl("^regulation\\s+of\\s+", Pathway, ignore.case = TRUE) ~ 6L,
  684. TRUE ~ 1L
  685. ))
  686. winners <- path_metrics %>%
  687. arrange(base_name, desc(n_treat), mean_sig, tier, Pathway) %>%
  688. group_by(base_name) %>%
  689. dplyr::slice(1) %>%
  690. ungroup()
  691. # keep only the winning GO term for each base X
  692. df_plot0 <- df_plot0 %>%
  693. semi_join(winners %>% dplyr::select(Pathway), by = "Pathway") %>%
  694. mutate(Group = factor(Group, levels = unique(Group)))
  695. # Keep only certain "metabolic process" categories
  696. keep_processes <- c(
  697. "ATP metabolic process",
  698. "regulation of lipid metabolic process",
  699. "superoxide metabolic process"
  700. )
  701. df_plot0 <- df_plot0 %>%
  702. filter(
  703. # keep all terms that don't end in "metabolic process"
  704. !stringr::str_detect(Pathway, "metabolic process$") |
  705. # OR are one of the specific ones you care about
  706. Pathway %in% keep_processes
  707. )
  708. # Keep union of top 10 per target
  709. n_keep <- 12
  710. top10_per_treat <- df_plot0 %>%
  711. group_by(Target) %>%
  712. arrange(colval, Pathway, .by_group = TRUE) %>% # <- ASC padj
  713. slice_head(n = n_keep) %>%
  714. ungroup()
  715. # Collect the unique GO terms that are top-10 in at least one target
  716. keepers <- unique(top10_per_treat$Pathway)
  717. # Filter the *full* table to those GO terms for *all* Treatments where they exist
  718. df_plot0 <- df_plot0 %>%
  719. dplyr::filter(Pathway %in% keepers)
  720. # Y ordering by max -log10(padj) within Group + dividers
  721. ord <- df_plot0 %>%
  722. group_by(Group, Pathway) %>%
  723. summarise(ordkey = suppressWarnings(min(colval, na.rm = TRUE)), .groups = "drop") %>% # <- MIN padj
  724. mutate(ordkey = ifelse(is.infinite(ordkey), NA_real_, ordkey)) %>%
  725. group_by(Group) %>%
  726. arrange(ordkey, Pathway, .by_group = TRUE) %>% # <- ASC padj
  727. mutate(y_in_group = row_number()) %>%
  728. ungroup()
  729. if (nrow(ord) == 0) stop("No rows left to plot after filters and collapsing.")
  730. sizes <- ord %>%
  731. dplyr::count(Group, name = "n") %>%
  732. dplyr::mutate(offset = dplyr::lag(cumsum(n), default = 0))
  733. ord <- ord %>% left_join(sizes, by = "Group") %>%
  734. mutate(y = y_in_group + offset)
  735. y_top <- max(ord$y) + 0.6
  736. y_bottom <- min(ord$y) - 0.4
  737. # positions for right-side labels and horizontal dividers
  738. label_pos <- ord %>%
  739. dplyr::group_by(Group) %>%
  740. dplyr::summarise(y = (min(y) + max(y))/2, .groups = "drop")
  741. # compute one boundary after each group, then drop ONLY the very last boundary by y
  742. dividers <- ord %>%
  743. dplyr::group_by(Group) %>%
  744. dplyr::summarise(y = max(y) + 0.5, .groups = "drop") %>%
  745. dplyr::filter(y < max(y)) # <- robust: keep all but the largest boundary
  746. df_plot <- df_plot0 %>%
  747. left_join(ord %>% dplyr::select(Group, Pathway, y), by = c("Group","Pathway"))
  748. write.xlsx(df_plot, "analysis/de/go_bp_batch1_padj_0.1_10deg_allselect_rebeccaedit_finalcatstoplot.xlsx")
  749. # Plot (x order = ΔCLPI-descending; size=count; color=-log10(padj))
  750. left_tag <- ".LEFT"
  751. right_tag <- ".RIGHT"
  752. x_levels <- target_levels
  753. df_plot <- df_plot %>%
  754. mutate(Target2 = factor(Target, levels = c(left_tag, x_levels, right_tag)))
  755. label_pos <- label_pos %>% mutate(x_lab = right_tag)
  756. p <- ggplot(df_plot, aes(x = Target2, y = y, size = Count, colour = colval)) +
  757. geom_hline(data = dividers, aes(yintercept = y),
  758. inherit.aes = FALSE, linewidth = 0.4, colour = "grey70") +
  759. geom_point(alpha = 0.9) +
  760. geom_text(data = label_pos, aes(x = x_lab, y = y, label = Group),
  761. inherit.aes = FALSE, hjust = 0, vjust = 0.5, fontface = "bold") +
  762. scale_x_discrete(
  763. drop = FALSE,
  764. limits = c(left_tag, x_levels, right_tag),
  765. breaks = x_levels,
  766. labels = x_levels,
  767. expand = expansion(add = c(0, 0.65))
  768. ) +
  769. scale_y_reverse(
  770. limits = c(y_top, y_bottom),
  771. breaks = ord$y,
  772. labels = ord$Pathway,
  773. expand = c(0, 0)
  774. ) +
  775. scale_size_area(max_size = 11, name = "Gene Count") +
  776. # clusterProfiler-like red↔blue (red = more significant)
  777. scale_colour_gradient(
  778. name = "padj",
  779. low = "red", # smaller padj = red
  780. high = "blue",
  781. oob = scales::squish,
  782. guide = guide_colorbar(reverse = TRUE) # <- lower numbers appear at the top
  783. ) +
  784. labs(x = "Target Gene", y = NULL) +
  785. coord_cartesian(clip = "off") +
  786. theme_bw(base_size = 12) +
  787. theme(
  788. plot.margin = margin(5.5, 70, 5.5, 5.5),
  789. axis.text.x = element_text(angle = 45, hjust = 1),
  790. legend.position = "right"
  791. )
  792. p
  793. # make a dotplot annotated with a boxplot --------
  794. # Pick a global font family
  795. base_family <- "Arial"
  796. # Keep the SAME x limits as dotplot (including sentinel columns)
  797. x_limits_full <- c(left_tag, x_levels, right_tag)
  798. # Compute Treatment-level mean (across wells), subtract NEG mean
  799. .delta_bar_data <- function(data, feature, control = "NEG") {
  800. # Only keep NEG + treatments that appear in the dotplot order
  801. present <- intersect(c(control, x_levels), unique(data$Treatment))
  802. means <- data %>%
  803. filter(Treatment %in% present) %>%
  804. group_by(Treatment) %>%
  805. summarise(mean_val = mean(.data[[feature]], na.rm = TRUE), .groups = "drop")
  806. neg_mean <- means %>% filter(Treatment == control) %>% pull(mean_val)
  807. if (length(neg_mean) == 0 || is.na(neg_mean)) {
  808. stop("NEG control not found in the provided data for feature: ", feature)
  809. }
  810. # Delta = gene mean - NEG mean, for non-NEG treatments in the dotplot order
  811. bars <- tibble(Treatment = x_levels) %>%
  812. left_join(means, by = "Treatment") %>%
  813. mutate(delta = mean_val - neg_mean)
  814. # Build a full x vector including sentinels (no bars for them)
  815. df <- tibble(Treatment2 = factor(x_limits_full, levels = x_limits_full)) %>%
  816. left_join(
  817. bars %>%
  818. transmute(Treatment2 = factor(Treatment, levels = x_limits_full),
  819. delta),
  820. by = "Treatment2"
  821. ) %>%
  822. mutate(sign = case_when(
  823. is.na(delta) ~ "zero", # nothing drawn
  824. delta > 0 ~ "pos",
  825. delta < 0 ~ "neg",
  826. TRUE ~ "zero"
  827. ))
  828. df
  829. }
  830. # Use Arial in the two delta bar panels
  831. make_delta_bar_layer <- function(data, feature, panel_title, control = "NEG") {
  832. df <- .delta_bar_data(data, feature, control = control)
  833. ggplot(df, aes(x = Treatment2, y = delta, fill = sign)) +
  834. geom_col(width = 0.7, na.rm = TRUE) +
  835. geom_hline(yintercept = 0, linewidth = 0.4, color = "grey50") +
  836. scale_fill_manual(values = c(pos = "red", neg = "blue", zero = "grey80"), guide = "none") +
  837. scale_x_discrete(drop = FALSE, limits = x_limits_full, breaks = NULL, expand = expansion(add = c(0, 0.65))) +
  838. labs(title = panel_title, x = NULL, y = NULL) +
  839. theme_minimal(base_size = 12, base_family = base_family) + # <- Arial
  840. theme(
  841. plot.title = element_text(hjust = 0, face = "bold", size = 11, margin = margin(b = 2)),
  842. axis.text.x = element_blank(),
  843. axis.title.x = element_blank(),
  844. axis.ticks.x = element_blank(),
  845. legend.position = "none",
  846. plot.margin = margin(5.5, 70, 0, 5.5)
  847. )
  848. }
  849. p <- p +
  850. theme_bw(base_size = 12, base_family = base_family) + # <- Arial
  851. theme(plot.margin = margin(5.5, 70, 5.5, 5.5))
  852. p <- p +
  853. geom_text(
  854. data = label_pos,
  855. aes(x = x_lab, y = y, label = Group),
  856. inherit.aes = FALSE,
  857. hjust = 0, vjust = 0.5,
  858. fontface = "bold",
  859. family = base_family,
  860. size = 3
  861. )
  862. # Rebuild your two annotation bars with plotmath titles using Δ (delta)
  863. p_bar_pi <- make_delta_bar_layer(
  864. exp31, "Cell_Level_Phago_Index",
  865. panel_title = expression(Delta~" well averaged PI"),
  866. control = "NEG"
  867. )
  868. p_bar_ecc <- make_delta_bar_layer(
  869. exp31, "AreaShape_Eccentricity",
  870. panel_title = expression(Delta~" well-averaged Eccentricity"),
  871. control = "NEG"
  872. )
  873. # make sure label_pos has the x column for the right margin labels
  874. label_pos <- label_pos %>% mutate(x_lab = right_tag)
  875. # 1) Remove any existing text/label geoms from the dotplot
  876. strip_text_layers <- function(g) {
  877. g$layers <- Filter(function(lyr) !inherits(lyr$geom, c("GeomText", "GeomLabel")), g$layers)
  878. g
  879. }
  880. p <- strip_text_layers(p)
  881. # Add ONE right-side label layer (Arial + smaller size)
  882. base_family <- "Arial"
  883. p <- p +
  884. geom_text(
  885. data = label_pos,
  886. aes(x = x_lab, y = y, label = Group),
  887. inherit.aes = FALSE,
  888. hjust = 0, vjust = 0.5,
  889. fontface = "bold",
  890. family = base_family,
  891. size = 3.2
  892. )
  893. p <- p +
  894. theme(
  895. axis.text.x = element_text(
  896. angle = 45, hjust = 1, vjust = 1,
  897. family = "Arial", size = 12
  898. ),
  899. # give a bit more bottom margin so angled labels don't clip
  900. plot.margin = margin(5.5, 70, 10, 5.5)
  901. )
  902. # combine and save
  903. combined <- p_bar_pi / p_bar_ecc / p + patchwork::plot_layout(heights = c(0.7, 0.7, 5))
  904. dir.create("analysis/de", recursive = TRUE, showWarnings = FALSE)
  905. dev <- if (capabilities("cairo")) grDevices::cairo_pdf else grDevices::pdf
  906. ggplot2::ggsave(
  907. filename = "analysis/de/go_final_dotplot_barplot.pdf",
  908. plot = combined,
  909. width = 10, height = 14, units = "in",
  910. device = dev
  911. )
  912. # SINGLE CATEGORY gene heatmap ======
  913. plot_group_heatmaps <- function(df,
  914. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  915. group = "Lysosome",
  916. out_dir = "analysis/plots/single_cat/",
  917. alpha = 0.1,
  918. lfc_thresh = 0.2,
  919. centered_order_tbl = NULL,
  920. min_genes_after_deg = 1,
  921. min_treats_after_filter = 1,
  922. cluster_rows = TRUE,
  923. cluster_cols = TRUE,
  924. plot_height = 17,
  925. plot_width = 5,
  926. base_family = "Arial",
  927. lfc_limits = c(-4, 2)
  928. ) {
  929. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv"
  930. out_dir = "analysis/plots/single_cat/"
  931. alpha = 0.1
  932. lfc_thresh = 0.2
  933. min_genes_after_deg = 1
  934. min_treats_after_filter = 1
  935. cluster_rows = TRUE
  936. cluster_cols = TRUE
  937. base_family = "Arial"
  938. lfc_limits = c(-4, 2)
  939. df = df_phago
  940. group = "Phagocytosis"
  941. centered_order_tbl = centered_order_tbl
  942. plot_width = sz$width_in
  943. plot_height = sz$height_in
  944. # --- Safety & inputs ---
  945. stopifnot(all(c("select_label","description","gene_id","ko_gene") %in% names(df)))
  946. if (!dir.exists(out_dir)) dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)
  947. # Read DEG (only once)
  948. deg <- data.table::fread(deg_path) %>%
  949. dplyr::mutate(gene = gene_name)
  950. # Terms in this group
  951. terms <- df %>%
  952. dplyr::filter(select_label == group) %>%
  953. dplyr::pull(description) %>%
  954. unique()
  955. if (length(terms) == 0) stop(sprintf("No terms found with select_label == '%s'.", group))
  956. # Helper to sanitize filenames
  957. sanitize <- function(x) gsub("[^A-Za-z0-9_\\-]+", "_", x)
  958. # PDF device that embeds fonts
  959. .pdf_device <- if (capabilities("cairo")) grDevices::cairo_pdf else grDevices::pdf
  960. # Plot each term
  961. out_files <- list()
  962. for (term_desc in terms) {
  963. message("Plotting [", group, "]: ", term_desc)
  964. # genes for this term
  965. genes <- df %>%
  966. dplyr::filter(select_label == group, description == term_desc) %>%
  967. dplyr::pull(gene_id) %>%
  968. stringr::str_split("/", simplify = FALSE) %>%
  969. unlist(use.names = FALSE) %>%
  970. stringr::str_trim() %>%
  971. unique()
  972. genes <- genes[genes != ""]
  973. if (!length(genes)) { warning("No genes parsed for: ", term_desc); next }
  974. # treatments that actually list this term in df
  975. treats_in_term_df <- df %>%
  976. dplyr::filter(select_label == group, description == term_desc) %>%
  977. dplyr::pull(ko_gene) %>%
  978. unique()
  979. if (!length(treats_in_term_df)) { warning("No treatments listed for this term in df: ", term_desc); next }
  980. # subset DEG
  981. deg_sel <- deg %>%
  982. dplyr::filter(gene %in% genes) %>%
  983. dplyr::select(gene, log2FoldChange, padj, ko_gene)
  984. if (!nrow(deg_sel)) { warning("No DEG rows matched for: ", term_desc); next }
  985. # wide -> matrices
  986. lfc_wide <- deg_sel %>%
  987. dplyr::select(gene, ko_gene, log2FoldChange) %>%
  988. tidyr::pivot_wider(names_from = ko_gene, values_from = log2FoldChange)
  989. p_wide <- deg_sel %>%
  990. dplyr::select(gene, ko_gene, padj) %>%
  991. tidyr::pivot_wider(names_from = ko_gene, values_from = padj)
  992. lfc_raw <- lfc_wide %>% tibble::column_to_rownames("gene") %>% as.matrix()
  993. p_raw <- p_wide %>% tibble::column_to_rownames("gene") %>% as.matrix()
  994. # drop cols not present in df for this term
  995. lfc_raw <- lfc_raw[, colnames(lfc_raw) %in% treats_in_term_df, drop = FALSE]
  996. p_raw <- p_raw[, colnames(p_raw) %in% treats_in_term_df, drop = FALSE]
  997. # drop all-NA columns, then order by ΔCLPI (only remaining)
  998. nonempty_cols <- colSums(!is.na(lfc_raw)) > 0
  999. lfc_raw <- lfc_raw[, nonempty_cols, drop = FALSE]
  1000. p_raw <- p_raw[, nonempty_cols, drop = FALSE]
  1001. desired_order <- intersect(centered_order_tbl$Treatment, colnames(lfc_raw))
  1002. lfc_raw <- lfc_raw[, desired_order, drop = FALSE]
  1003. p_raw <- p_raw[, desired_order, drop = FALSE]
  1004. if (ncol(lfc_raw) < min_treats_after_filter) { warning("Too few treatments after filtering for: ", term_desc); next }
  1005. # fill NA (after pruning)
  1006. lfc_mat <- lfc_raw; lfc_mat[is.na(lfc_mat)] <- 0
  1007. p_mat <- p_raw; p_mat[is.na(p_mat)] <- 1
  1008. # drop genes with no data across remaining treatments
  1009. nonempty_rows <- rowSums(!is.na(lfc_raw)) > 0
  1010. lfc_mat <- lfc_mat[nonempty_rows, , drop = FALSE]
  1011. p_mat <- p_mat[nonempty_rows, , drop = FALSE]
  1012. if (nrow(lfc_mat) < min_genes_after_deg) { warning("Too few genes with data left for: ", term_desc); next }
  1013. heatmap_max <- suppressWarnings(max(lfc_mat, na.rm = TRUE)); if (!is.finite(heatmap_max)) heatmap_max <- 1
  1014. heatmap_min <- suppressWarnings(min(lfc_mat, na.rm = TRUE)); if (!is.finite(heatmap_min)) heatmap_min <- -1
  1015. # star overlay (use Arial)
  1016. cell_fun_star <- function(j, i, x, y, w, h, fill) {
  1017. if (!is.na(p_mat[i, j]) && p_mat[i, j] < alpha &&
  1018. !is.na(lfc_mat[i, j]) && abs(lfc_mat[i, j]) > lfc_thresh) {
  1019. grid::grid.text("*", x, y, gp = grid::gpar(fontfamily = base_family))
  1020. }
  1021. }
  1022. heatmap_min <- lfc_limits[1]
  1023. heatmap_max <- lfc_limits[2]
  1024. # keep white at 0 (diverging)
  1025. col_fun <- circlize::colorRamp2(
  1026. c(heatmap_min, 0, heatmap_max),
  1027. c("darkblue", "white", "red")
  1028. )
  1029. # legend ticks (top→bottom shows 2, 0, -2, -4). If yours renders bottom→top,
  1030. # swap to at = c(-4, -2, 0, 2) to match your preference.
  1031. legend_at <- c(2, 0, -2, -4)
  1032. legend_labels <- c("2", "0", "-2", "-4")
  1033. pdf_file <- file.path(out_dir, paste0(sanitize(group), "__", sanitize(term_desc), ".pdf"))
  1034. .pdf_device(pdf_file, height = plot_height, width = plot_width, family = base_family)
  1035. view(lfc_mat)
  1036. ht <- ComplexHeatmap::Heatmap(
  1037. lfc_mat,
  1038. name = "Log2FC",
  1039. col = col_fun,
  1040. cluster_columns = cluster_cols,
  1041. cluster_rows = cluster_rows,
  1042. column_names_gp = grid::gpar(fontsize = 10, fontfamily = base_family),
  1043. row_names_gp = grid::gpar(fontsize = 10, fontfamily = base_family),
  1044. heatmap_legend_param = list(
  1045. title = "Log2FoldChange\nKO vs. Control",
  1046. title_gp = grid::gpar(fontfamily = base_family, fontface = "bold"),
  1047. labels_gp = grid::gpar(fontfamily = base_family),
  1048. at = legend_at,
  1049. labels = legend_labels
  1050. ),
  1051. column_title = term_desc,
  1052. column_title_gp = grid::gpar(fontfamily = base_family, fontface = "bold"),
  1053. cell_fun = cell_fun_star
  1054. )
  1055. ComplexHeatmap::draw(ht)
  1056. grDevices::dev.off()
  1057. message("Saved: ", pdf_file)
  1058. out_files[[term_desc]] <- pdf_file
  1059. }
  1060. invisible(out_files)
  1061. }
  1062. estimate_heatmap_dims <- function(df_plot, deg_path, group, centered_order_tbl) {
  1063. # genes for this group/term set (split "gene_id" on '/')
  1064. genes <- df_plot %>%
  1065. filter(select_label == group) %>%
  1066. pull(gene_id) %>%
  1067. str_split("/", simplify = FALSE) %>%
  1068. unlist(use.names = FALSE) %>%
  1069. str_trim() %>%
  1070. unique()
  1071. genes <- genes[genes != ""]
  1072. # treatments mentioned for these terms
  1073. treats_in_term_df <- df_plot %>%
  1074. filter(select_label == group) %>%
  1075. pull(ko_gene) %>%
  1076. unique()
  1077. # read DEG and subset to these genes/treatments
  1078. deg <- data.table::fread(deg_path) %>% mutate(gene = gene_name)
  1079. deg_sel <- deg %>%
  1080. filter(gene %in% genes, ko_gene %in% treats_in_term_df) %>%
  1081. select(gene, ko_gene, log2FoldChange)
  1082. # wide, then drop all-NA cols and keep centered order
  1083. lfc_wide <- deg_sel %>%
  1084. pivot_wider(names_from = ko_gene, values_from = log2FoldChange)
  1085. if (nrow(lfc_wide) == 0) return(list(nr = 0L, nc = 0L))
  1086. lfc_raw <- lfc_wide %>% column_to_rownames("gene") %>% as.matrix()
  1087. nonempty_cols <- colSums(!is.na(lfc_raw)) > 0
  1088. lfc_raw <- lfc_raw[, nonempty_cols, drop = FALSE]
  1089. desired_order <- intersect(centered_order_tbl$Treatment, colnames(lfc_raw))
  1090. lfc_raw <- lfc_raw[, desired_order, drop = FALSE]
  1091. # drop genes with all NA across remaining treatments
  1092. nonempty_rows <- rowSums(!is.na(lfc_raw)) > 0
  1093. lfc_raw <- lfc_raw[nonempty_rows, , drop = FALSE]
  1094. list(nr = nrow(lfc_raw), nc = ncol(lfc_raw))
  1095. }
  1096. heatmap_device_size <- function(nr, nc,
  1097. cell_w_mm = 6,
  1098. cell_h_mm = 6.5,
  1099. row_names_mm = 35,
  1100. col_names_mm = 12,
  1101. row_dend_mm = 4,
  1102. col_dend_mm = 4,
  1103. legend_mm = 18,
  1104. gap_mm = 6) {
  1105. mm_to_in <- function(mm) mm / 25.4
  1106. heatmap_w_in <- mm_to_in(cell_w_mm * nc)
  1107. heatmap_h_in <- mm_to_in(cell_h_mm * nr)
  1108. width_in <- heatmap_w_in + mm_to_in(row_names_mm + row_dend_mm + gap_mm + legend_mm)
  1109. height_in <- heatmap_h_in + mm_to_in(col_names_mm + col_dend_mm + gap_mm)
  1110. list(width_in = width_in, height_in = height_in)
  1111. }
  1112. # Example: Phagocytosis panel
  1113. df_phago <- df %>% filter(description == "phagocytosis")
  1114. head(df_phago)
  1115. dims <- estimate_heatmap_dims(df_phago, deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1116. group = "Phagocytosis", centered_order_tbl = centered_order_tbl)
  1117. sz <- heatmap_device_size(dims$nr, dims$nc)
  1118. files_phago <- plot_group_heatmaps(
  1119. df_phago,
  1120. group = "Phagocytosis",
  1121. centered_order_tbl = centered_order_tbl,
  1122. plot_width = sz$width_in,
  1123. plot_height = sz$height_in
  1124. )
  1125. # Lysosome terms
  1126. df_plot = df %>%
  1127. filter(description == "lysosome organization")
  1128. dims <- estimate_heatmap_dims(df_plot,
  1129. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1130. group = "Lysosome",
  1131. centered_order_tbl = centered_order_tbl)
  1132. sz <- heatmap_device_size(dims$nr, dims$nc)
  1133. files_lyso <- plot_group_heatmaps(
  1134. df_plot,
  1135. group = "Lysosome",
  1136. centered_order_tbl = centered_order_tbl,
  1137. plot_width = sz$width_in,
  1138. plot_height = sz$height_in
  1139. )
  1140. # Activation terms
  1141. df_plot = df %>%
  1142. filter(description == "macrophage activation")
  1143. head(df_plot)
  1144. dims <- estimate_heatmap_dims(df_plot,
  1145. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1146. group = "Inflammation/Activation",
  1147. centered_order_tbl = centered_order_tbl)
  1148. sz <- heatmap_device_size(dims$nr, dims$nc)
  1149. files_act <- plot_group_heatmaps(
  1150. df_plot,
  1151. group = "Inflammation/Activation",
  1152. centered_order_tbl = centered_order_tbl,
  1153. plot_width = sz$width_in,
  1154. plot_height = sz$height_in
  1155. )
  1156. # Actin terms
  1157. df_plot = df %>%
  1158. filter(description == "regulation of cell shape")
  1159. dims <- estimate_heatmap_dims(df_plot,
  1160. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1161. group = "Actin/Cell Shape",
  1162. centered_order_tbl = centered_order_tbl)
  1163. sz <- heatmap_device_size(dims$nr, dims$nc)
  1164. files_act <- plot_group_heatmaps(
  1165. df_plot,
  1166. group = "Actin/Cell Shape",
  1167. centered_order_tbl = centered_order_tbl,
  1168. plot_width = sz$width_in,
  1169. plot_height = sz$height_in
  1170. )
  1171. # Actin term
  1172. df_plot = df %>%
  1173. filter(description == "actin crosslink formation")
  1174. dims <- estimate_heatmap_dims(df_plot,
  1175. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1176. group = "Actin/Cell Shape",
  1177. centered_order_tbl = centered_order_tbl)
  1178. sz <- heatmap_device_size(dims$nr, dims$nc)
  1179. files_act <- plot_group_heatmaps(
  1180. df_plot,
  1181. group = "Actin/Cell Shape",
  1182. centered_order_tbl = centered_order_tbl,
  1183. plot_width = sz$width_in,
  1184. plot_height = sz$height_in
  1185. )
  1186. df_plot = df %>%
  1187. filter(description == "actin filament bundle assembly")
  1188. dims <- estimate_heatmap_dims(df_plot,
  1189. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1190. group = "Actin/Cell Shape",
  1191. centered_order_tbl = centered_order_tbl)
  1192. sz <- heatmap_device_size(dims$nr, dims$nc)
  1193. files_act <- plot_group_heatmaps(
  1194. df_plot,
  1195. group = "Actin/Cell Shape",
  1196. centered_order_tbl = centered_order_tbl,
  1197. plot_width = sz$width_in,
  1198. plot_height = sz$height_in
  1199. )
  1200. df_plot = df %>%
  1201. filter(description == "regulation of actin cytoskeleton organization")
  1202. dims <- estimate_heatmap_dims(df_plot,
  1203. deg_path = "analysis/de/all_res_vs_ntc1_batch1_q1q3_filter_rmlow.tsv",
  1204. group = "Actin/Cell Shape",
  1205. centered_order_tbl = centered_order_tbl)
  1206. sz <- heatmap_device_size(dims$nr, dims$nc)
  1207. files_act <- plot_group_heatmaps(
  1208. df_plot,
  1209. group = "Actin/Cell Shape",
  1210. centered_order_tbl = centered_order_tbl,
  1211. plot_width = sz$width_in,
  1212. plot_height = sz$height_in
  1213. )

03_01_analyze_deg_go.R at commit 7ea1019, no license · at the source

Overview

Authors: Joy E. Horng1,2, Liam T. McCrea1, Rebecca E. Batorsky3, Joshua J. Bowen1, Camilla Boschian1,4, Yoonjae Song1, Roy H. Perlis1,4,5, Steven D. Sheridan1,4,5
  1. Center for Genomic Medicine and Department of Psychiatry, Massachusetts General Hospital,Boston, MA USA
  2. Department of Molecular Biology, Massachusetts General Hospital,Boston, MA USA
  3. Tufts Institute for Artificial Intelligence, Tufts University,Medford, MA USA
  4. Department of Psychiatry, Harvard Medical School,Boston, MA USA
  5. Harvard Stem Cell Institute,Cambridge, MA USA
Institutions: Massachusetts General Hospital (United States); Tufts University (United States); Harvard University (United States); Harvard Stem Cell Institute (United States)
Dates: received 15 November 2025; accepted 31 March 2026; published online 16 April 2026; in print August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41386-026-02406-1 · PMID 41992032 · PMCID PMC13389400 · OpenAlex W4416337181
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), human (organism), schizophrenia / psychosis (population), cellular / molecular (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions
Keywords: Cellular neuroscience, Genetics of the nervous system
MeSH: Microglia*, Schizophrenia*, Gene Expression Profiling, Genomics, Humans, Phagocytosis (* major topic)
Topic: Neuroinflammation and Neurodegeneration Mechanisms (Neurology, Neuroscience), according to OpenAlex
Funding: HHS | NIH | National Institute of Mental Health (NIMH) (R01MH120227, R01MH131687)
Citations: not cited yet (Europe PMC); 75 references in the paper

Abstract

Microglia are increasingly recognized as key regulators of neural circuit development and putative contributors to the pathophysiology of neuropsychiatric disorders such as schizophrenia (SCZ). However, the functional impact of SCZ-associated genes in microglia remains largely unexplored. Here, we performed an arrayed CRISPR targeting screen of 30 SCZ-associated genes predicted to be differentially expressed in human microglia-like cells. Target genes were prioritized based on post-mortem transcriptomic relevance and predicted ontology-based roles in phagocytosis pathways. We quantified phagocytic activity and morphological changes following gene targeting using high-content confocal imaging. Key targets, including CYFIP1, MSR1, TREM2, SYK, ITGB2, ITGAM, and IRF8, modulated phagocytosis and altered morphological properties consistent with activation states, validating their functional roles in microglia. To elucidate transcriptional impact, we further applied a multiplexed RNA sequencing platform across gene targets. These analyses revealed gene-specific transcriptional signatures, implicating divergent pathways related to phagocytic, activation, cytoskeletal, and lysosomal function. Together, these findings demonstrate the utility of CRISPR-based functional genomics in characterizing microglia function and identifying new target genes and mechanisms that may underlie their contributions to SCZ pathophysiology.

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.

mgb-cqh/crispr30

License: none: the authors keep all their rights
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 7ea101924cd85986ebddc043eb9d661e32c241b3, 21 November 2025
Languages: R (6), Shell (5)
Size: 14 files, 11 scripts
Software Heritage: not archived
Found in: the text, “RNA-seq data analysis”
Holds: README, 2 notebooks
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: tidyverse (6 files), data.table (5 files), clusterProfiler (4 files), DESeq2 (4 files), pheatmap (4 files), ComplexHeatmap (3 files), ggpubr (3 files), SAMtools (2 files), broom (1 file), circlize (1 file), emmeans (1 file), FastQC (1 file), ggplot2 (1 file), igraph (1 file), limma (1 file), lme4 (1 file), multcomp (1 file), patchwork (1 file), STAR (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
12 files

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;
  • 11 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 availability

Gene expression data will be made available for download from the NCBI Gene Expression Omnibus (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE319209) upon publication. Additional data that support the findings of this study will be available from the corresponding authors upon reasonable request.

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, 29 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 2 keywords, 6 MeSH terms, 1 funder, 74 references.

Cite

This paper

Horng, J. E., McCrea, L. T., Batorsky, R. E., Bowen, J. J., Boschian, C., Song, Y., Perlis, R. H., & Sheridan, S. D. (2026). Functional genomic profiling of schizophrenia-associated genes reveals key microglial regulators. Neuropsychopharmacology : official publication of the American College of Neuropsychopharmacology, 51(9), 1718-1727. https://doi.org/10.1038/s41386-026-02406-1

BibTeX

@article{horng2026functional,
author = {Horng, Joy E. and McCrea, Liam T. and Batorsky, Rebecca E. and Bowen, Joshua J. and Boschian, Camilla and Song, Yoonjae and Perlis, Roy H. and Sheridan, Steven D.},
title = {{Functional genomic profiling of schizophrenia-associated genes reveals key microglial regulators}},
journal = {Neuropsychopharmacology : official publication of the American College of Neuropsychopharmacology},
year = {2026},
month = apr,
volume = {51},
number = {9},
pages = {1718--1727},
publisher = {Nature Publishing Group},
issn = {0893-133X},
doi = {10.1038/s41386-026-02406-1},
url = {https://doi.org/10.1038/s41386-026-02406-1},
pmid = {41992032},
pmcid = {PMC13389400}
}

RIS

TY - JOUR
AU - Horng, Joy E.
AU - McCrea, Liam T.
AU - Batorsky, Rebecca E.
AU - Bowen, Joshua J.
AU - Boschian, Camilla
AU - Song, Yoonjae
AU - Perlis, Roy H.
AU - Sheridan, Steven D.
TI - Functional genomic profiling of schizophrenia-associated genes reveals key microglial regulators
T2 - Neuropsychopharmacology : official publication of the American College of Neuropsychopharmacology
J2 - Neuropsychopharmacology
PY - 2026
DA - 2026/04/16
VL - 51
IS - 9
SP - 1718
EP - 1727
SN - 0893-133X
PB - Nature Publishing Group
DO - 10.1038/s41386-026-02406-1
UR - https://doi.org/10.1038/s41386-026-02406-1
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41386-026-02406-1",
"type": "article-journal",
"title": "Functional genomic profiling of schizophrenia-associated genes reveals key microglial regulators",
"container-title": "Neuropsychopharmacology : official publication of the American College of Neuropsychopharmacology",
"author": [
{
"family": "Horng",
"given": "Joy E."
},
{
"family": "McCrea",
"given": "Liam T."
},
{
"family": "Batorsky",
"given": "Rebecca E."
},
{
"family": "Bowen",
"given": "Joshua J."
},
{
"family": "Boschian",
"given": "Camilla"
},
{
"family": "Song",
"given": "Yoonjae"
},
{
"family": "Perlis",
"given": "Roy H."
},
{
"family": "Sheridan",
"given": "Steven D."
}
],
"container-title-short": "Neuropsychopharmacology",
"volume": "51",
"issue": "9",
"page": "1718-1727",
"DOI": "10.1038/s41386-026-02406-1",
"PMID": "41992032",
"PMCID": "PMC13389400",
"ISSN": "0893-133X",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41386-026-02406-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
16
]
]
}
}

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: FastQC, multcomp, STAR, 16 other tools, genetics / omics, 4 references
[2] doi:10.1038/s41593-026-02367-0 [code]
A reproducible three-dimensional model of human brain tissue to investigate physiological and disease-associated microglia phenotypes.
Journal: Nature neuroscience
In common: multcomp, limma, DESeq2, 10 other tools, cellular / molecular, 4 references
[3] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: SAMtools, limma, broom, 12 other tools, cellular / molecular
[4] doi:10.1038/s41467-026-74753-y [code]
A human-specific microRNA controls the timing of excitatory synaptogenesis.
Journal: Nature communications
In common: STAR, SAMtools, limma, 11 other tools, cellular / molecular, 1 reference
[5] doi:10.1016/j.isci.2026.116412 [code]
KOLF2.1J iTF-Microglia: A standardized platform to study microglial transcriptional regulatory networks in CNS disease.
Journal: iScience
In common: SAMtools, DESeq2, circlize, 7 other tools, genetics / omics, 6 references
[6] doi:10.1038/s41467-026-76675-1 [code]
Long-read proteogenomic atlas of human neuronal differentiation reveals isoform diversity informing neurodevelopmental risk mechanisms.
Journal: Nature communications
In common: STAR, SAMtools, limma, 9 other tools, genetics / omics, 3 references
[7] 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: limma, DESeq2, igraph, 10 other tools, genetics / omics, cellular / molecular, 2 references
[8] doi:10.1038/s41593-026-02300-5 [code]
Integrated single-cell and spatial transcriptomic profiling in ALS uncovers peripheral-to-central immune infiltration and reprogramming.
Journal: Nature neuroscience
In common: FastQC, SAMtools, DESeq2, 9 other tools, genetics / omics, cellular / molecular, 1 reference
[9] doi:10.21203/rs.3.rs-9927928/v1 [code]
Genome-wide and allele-resolved maps of the radial architecture of the mouse genome
Journal: Research Square (preprint)
In common: FastQC, STAR, SAMtools, 8 other tools, genetics / omics, 3 references
[10] doi:10.1126/sciadv.aed2952 [code]
Activation of transposable elements is linked to a region- and cell type-specific interferon response in Parkinson's disease.
Journal: Science advances
In common: STAR, SAMtools, limma, 8 other tools, cellular / molecular, 3 references

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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