#read data file
library(readxl)
<- read_excel("DEG_All_Condition_All_CellType 1.xlsx", sheet = 1) DEG_data
Exploring Age-Related Immune–Vascular Changes in the Brain Using Single-Cell RNA-seq
Introduction
The brain, once thought to be immune privileged, is now recognized as a dynamic immune environment with a diverse array of resident and infiltrating immune cells. Microglia, the brain’s resident macrophages, are central to maintaining homeostasis, while border-associated immune populations—including perivascular macrophages, meningeal dendritic cells, and infiltrating T cells—play critical roles in surveillance and neuroimmune interactions.
Dataset Overview
For my final project, I analyzed single-cell RNA-seq data generated by our lab from young and aged mouse brains. The dataset includes ~20,000 cells per age group, derived from whole-brain single-cell suspensions sorted for live cells and sequenced using the 10x Genomics platform.After sequencing, the data underwent standard quality control (QC) and preprocessing using the Seurat package in R. This included filtering low-quality cells, normalizing gene expression, identifying highly variable genes, and clustering cell populations.This analysis focuses on endothelial cells, macrophages , and T cells— some of the key populations at the brain’s immune–vascular interface. By comparing age-related changes in the expression of adhesion molecules (e.g., ICAM1, VCAM1) and inflammatory mediators (e.g., CXCL10), the project aims to uncover how aging alters immune signaling and vascular function.
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.2 ✔ tibble 3.3.0
✔ lubridate 1.9.4 ✔ tidyr 1.3.1
✔ purrr 1.1.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)
library(ggplot2)
#head
head (DEG_data)
# A tibble: 6 × 9
gene_symbol p_val avg_log2FC pct.1 pct.2 p_val_adj Cluster ident.1 ident.2
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <chr>
1 Gm42418 0 -22.2 0 1 0 Endoth… G2_old G1_you…
2 ENSMUSG0000012… 0 18.7 0.977 0 0 Endoth… G2_old G1_you…
3 AY036118 0 -19.2 0 0.962 0 Endoth… G2_old G1_you…
4 Ptprg 0 -19.1 0 0.957 0 Endoth… G2_old G1_you…
5 Qk 0 -17.0 0 0.857 0 Endoth… G2_old G1_you…
6 Col4a3bp 0 -17.0 0 0.819 0 Endoth… G2_old G1_you…
# Define the cell types you want to keep
<- c("Endothelial_cells", "Macrophages", "T_memory_cells")
cell_types
# Filter the DEG dataset to keep only these 2 cell types
<- DEG_data %>%
filtered_deg filter(Cluster %in% cell_types)
# Check how many gene comparisons exist for each selected cell type
table(filtered_deg$Cluster)
Endothelial_cells Macrophages T_memory_cells
64413 16803 6767
<- filtered_deg %>%
young_vs_aged filter(ident.1 == "G2_old", ident.2 == "G1_young")
table(young_vs_aged$Cluster)
Endothelial_cells Macrophages T_memory_cells
10813 477 524
# Step 1: Broader inflammation/immune gene panel
<- c(
inflammatory_genes "Il1b", "Il6", "Tnf", "Ifng",
"Ccl2", "Ccl3", "Ccl4", "Cxcl9", "Cxcl10",
"Cd74", "Cd80", "Cd86", "Cd274", "Pdcd1lg2",
"H2-Ab1", "H2-Aa", "H2-Eb1", "Tap1", "Tapbp",
"Icam1", "Vcam1", "Sele", "Sell",
"Lyz2", "Nos2", "Ptprc"
)
# Step 2: Filter for the genes and relevant cell types
<- c("Endothelial_cells", "Macrophages", "T_memory_cells")
cell_types_focus
<- DEG_data %>%
inflamm_subset filter(Cluster %in% cell_types_focus,
%in% inflammatory_genes,
gene_symbol .1 == "G2_old", ident.2 == "G1_young") # ensure aged vs young only
ident
# Step 3: Plot as ggplot heatmap
ggplot(inflamm_subset, aes(x = Cluster, y = gene_symbol, fill = avg_log2FC)) +
geom_tile(color = "white") +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0,
name = "log2FC") +
labs(title = "Log2 Fold Change of Broad Inflammatory Genes (Aged vs. Young)",
x = "Cell Type", y = "Gene") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())
Gene Function Summary
These genes were selected for their roles in inflammation, immune regulation, or endothelial interactions relevant to brain aging:
Gene | Function |
---|---|
Vcam1 | Endothelial adhesion molecule involved in leukocyte binding and transmigration |
Tnf | Master pro-inflammatory cytokine activating NF-κB and other immune pathways |
Tapbp | Tapasin; involved in MHC class I antigen processing and presentation |
Sell | L-selectin; mediates immune cell migration to lymphoid and inflamed tissues |
Sele | E-selectin; recruits neutrophils and monocytes to sites of inflammation |
Ptprc | CD45; essential for T and B cell activation and signaling |
Nos2 | iNOS; produces nitric oxide in macrophages for microbial defense |
Lyz2 | Antimicrobial enzyme produced by macrophages and neutrophils |
Icam1 | Endothelial adhesion molecule involved in immune-endothelial interactions |
Cd274 | PD-L1; immune checkpoint molecule that suppresses T cell activation |
Ccl4 | Chemokine that recruits monocytes, T cells, and NK cells to inflamed tissue |
# Filter for endothelial cells aged vs young
<- DEG_data %>%
endo_deg filter(Cluster == "Endothelial_cells",
.1 == "G2_old", ident.2 == "G1_young") %>%
identmutate(log10_pval = -log10(p_val_adj),
regulation = case_when(
< 0.05 & avg_log2FC > 1 ~ "Upregulated",
p_val_adj < 0.05 & avg_log2FC < -1 ~ "Downregulated",
p_val_adj TRUE ~ "Not significant"
))
# Plot
ggplot(endo_deg, aes(x = avg_log2FC, y = log10_pval)) +
geom_point(aes(color = regulation), alpha = 0.7, size = 2) +
scale_color_manual(values = c(
"Upregulated" = "firebrick2",
"Downregulated" = "dodgerblue3",
"Not significant" = "grey70"
+
)) geom_vline(xintercept = c(-1, 1), linetype = "dashed", color = "gray50") +
geom_hline(yintercept = -log10(0.05), linetype = "dashed", color = "gray50") +
labs(title = "Volcano Plot: Endothelial Cells (Aged vs Young)",
x = "log2 Fold Change",
y = "-log10 Adjusted p-value",
color = "Regulation") +
theme_minimal(base_size = 13) +
theme(legend.position = "top")
library(dplyr)
library(tidyr)
library(ggplot2)
library(forcats)
<- c("Endothelial_cells", "Macrophages", "T_memory_cells")
cell_types_focus
<- DEG_data %>%
sig_deg filter(Cluster %in% cell_types_focus,
.1 == "G2_old", ident.2 == "G1_young",
ident< 0.05)
p_val_adj
<- sig_deg %>%
top_up group_by(Cluster) %>%
slice_max(order_by = avg_log2FC, n = 10, with_ties = FALSE) %>%
mutate(direction = "Up (old vs young)")
<- sig_deg %>%
top_down group_by(Cluster) %>%
slice_min(order_by = avg_log2FC, n = 10, with_ties = FALSE) %>%
mutate(direction = "Down (old vs young)")
<- bind_rows(top_up, top_down) %>%
top10_both arrange(Cluster, desc(avg_log2FC))
top10_both
# A tibble: 60 × 10
# Groups: Cluster [3]
gene_symbol p_val avg_log2FC pct.1 pct.2 p_val_adj Cluster ident.1 ident.2
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <chr>
1 ENSMUSG000001… 0 18.7 0.977 0 0 Endoth… G2_old G1_you…
2 Qki 0 16.3 0.761 0 0 Endoth… G2_old G1_you…
3 Cert1 0 16.2 0.715 0 0 Endoth… G2_old G1_you…
4 Garre1 0 15.3 0.545 0 0 Endoth… G2_old G1_you…
5 Nherf2 0 15.0 0.468 0 0 Endoth… G2_old G1_you…
6 Septin7 0 14.7 0.422 0 0 Endoth… G2_old G1_you…
7 Atp10d 0 14.2 0.323 0 0 Endoth… G2_old G1_you…
8 6820431F20Rik 0 14.0 0.302 0 0 Endoth… G2_old G1_you…
9 Bltp1 0 13.9 0.282 0 0 Endoth… G2_old G1_you…
10 Dennd2b 0 13.8 0.244 0 0 Endoth… G2_old G1_you…
# ℹ 50 more rows
# ℹ 1 more variable: direction <chr>
library(knitr)
%>%
top10_both select(Cluster, gene_symbol, avg_log2FC, p_val_adj, direction) %>%
mutate(p_val_adj = signif(p_val_adj, 3)) %>%
kable(format = "html", digits = 2,
caption = "Top 10 Up & Down Genes per Cell Type (Aged vs Young)")
Cluster | gene_symbol | avg_log2FC | p_val_adj | direction |
---|---|---|---|---|
Endothelial_cells | ENSMUSG00000121513 | 18.68 | 0 | Up (old vs young) |
Endothelial_cells | Qki | 16.30 | 0 | Up (old vs young) |
Endothelial_cells | Cert1 | 16.23 | 0 | Up (old vs young) |
Endothelial_cells | Garre1 | 15.30 | 0 | Up (old vs young) |
Endothelial_cells | Nherf2 | 15.01 | 0 | Up (old vs young) |
Endothelial_cells | Septin7 | 14.71 | 0 | Up (old vs young) |
Endothelial_cells | Atp10d | 14.24 | 0 | Up (old vs young) |
Endothelial_cells | 6820431F20Rik | 13.97 | 0 | Up (old vs young) |
Endothelial_cells | Bltp1 | 13.89 | 0 | Up (old vs young) |
Endothelial_cells | Dennd2b | 13.81 | 0 | Up (old vs young) |
Endothelial_cells | Sept11 | -14.39 | 0 | Down (old vs young) |
Endothelial_cells | 4932438A13Rik | -14.50 | 0 | Down (old vs young) |
Endothelial_cells | Sept7 | -15.17 | 0 | Down (old vs young) |
Endothelial_cells | Slc9a3r2 | -15.65 | 0 | Down (old vs young) |
Endothelial_cells | 4931406P16Rik | -15.98 | 0 | Down (old vs young) |
Endothelial_cells | Col4a3bp | -16.95 | 0 | Down (old vs young) |
Endothelial_cells | Qk | -16.96 | 0 | Down (old vs young) |
Endothelial_cells | Ptprg | -19.14 | 0 | Down (old vs young) |
Endothelial_cells | AY036118 | -19.19 | 0 | Down (old vs young) |
Endothelial_cells | Gm42418 | -22.23 | 0 | Down (old vs young) |
Macrophages | Qki | 12.17 | 0 | Up (old vs young) |
Macrophages | Cyrib | 11.80 | 0 | Up (old vs young) |
Macrophages | ENSMUSG00000095041 | 10.93 | 0 | Up (old vs young) |
Macrophages | Marchf1 | 10.77 | 0 | Up (old vs young) |
Macrophages | Niban1 | 10.68 | 0 | Up (old vs young) |
Macrophages | Lilrb4b | 10.62 | 0 | Up (old vs young) |
Macrophages | Gm56663 | 10.59 | 0 | Up (old vs young) |
Macrophages | H2az1 | 10.12 | 0 | Up (old vs young) |
Macrophages | Clec7a | 9.97 | 0 | Up (old vs young) |
Macrophages | Vis1 | 9.88 | 0 | Up (old vs young) |
Macrophages | H2afz | -10.43 | 0 | Down (old vs young) |
Macrophages | March1 | -10.80 | 0 | Down (old vs young) |
Macrophages | Fam129a | -11.06 | 0 | Down (old vs young) |
Macrophages | Lilr4b | -11.19 | 0 | Down (old vs young) |
Macrophages | AC149090.1 | -11.21 | 0 | Down (old vs young) |
Macrophages | Gm26917 | -11.27 | 0 | Down (old vs young) |
Macrophages | Fam49b | -12.36 | 0 | Down (old vs young) |
Macrophages | Qk | -12.63 | 0 | Down (old vs young) |
Macrophages | AY036118 | -14.05 | 0 | Down (old vs young) |
Macrophages | Gm42418 | -18.06 | 0 | Down (old vs young) |
T_memory_cells | Cyrib | 9.26 | 0 | Up (old vs young) |
T_memory_cells | Bltp1 | 8.60 | 0 | Up (old vs young) |
T_memory_cells | Abtb3 | 8.59 | 0 | Up (old vs young) |
T_memory_cells | Niban1 | 8.38 | 0 | Up (old vs young) |
T_memory_cells | Mideas | 8.33 | 0 | Up (old vs young) |
T_memory_cells | Qki | 8.29 | 0 | Up (old vs young) |
T_memory_cells | Gm56906 | 8.26 | 0 | Up (old vs young) |
T_memory_cells | ENSMUSG00000095041 | 8.18 | 0 | Up (old vs young) |
T_memory_cells | Septin7 | 8.08 | 0 | Up (old vs young) |
T_memory_cells | H2az1 | 7.96 | 0 | Up (old vs young) |
T_memory_cells | Fam129a | -11.70 | 0 | Down (old vs young) |
T_memory_cells | H2afz | -11.91 | 0 | Down (old vs young) |
T_memory_cells | AC149090.1 | -11.94 | 0 | Down (old vs young) |
T_memory_cells | 4932438A13Rik | -11.94 | 0 | Down (old vs young) |
T_memory_cells | Sept11 | -11.96 | 0 | Down (old vs young) |
T_memory_cells | Btbd11 | -12.03 | 0 | Down (old vs young) |
T_memory_cells | Gm26917 | -12.97 | 0 | Down (old vs young) |
T_memory_cells | Fam49b | -13.06 | 0 | Down (old vs young) |
T_memory_cells | AY036118 | -14.40 | 0 | Down (old vs young) |
T_memory_cells | Gm42418 | -17.92 | 0 | Down (old vs young) |
ggplot(top10_both,
aes(x = fct_reorder(gene_symbol, avg_log2FC),
y = avg_log2FC,
color = direction)) +
geom_point(size = 3) +
geom_segment(aes(xend = gene_symbol, y = 0, yend = avg_log2FC),
linewidth = 0.6) +
facet_wrap(~ Cluster, scales = "free_y") +
coord_flip() +
scale_color_manual(values = c("Up (old vs young)" = "firebrick2",
"Down (old vs young)" = "dodgerblue3")) +
labs(title = "Top 10 Up & Down DEGs per Cell Type (Aged vs Young)",
x = "Gene", y = "log2FC (old vs young)", color = "") +
theme_minimal(base_size = 12) +
theme(legend.position = "top")