pacman::p_load(Seurat, tidyverse, qs2, harmony, tictoc, cowplot, presto, SCpubr, scop)
knitr::opts_knit$set(root.dir = "/home/han/Desktop/guoqiang/stroke_bm/stroke_bm_Rproj")
setwd("/home/han/Desktop/guoqiang/stroke_bm/stroke_bm_Rproj")
source("codes/functions.R")Subtype: Neutrophil
中性粒亚型:早幼粒、Pre-Neut、成熟粒。后续组成和状态分析用这些名字。
第一轮只 subset Neut + Pre-Neut,不自动并入 HSPC cluster 21。GMP / promyelocyte barcode 确认后再另存。低质量 / doublet 留给 round 2。CellTypist 常把骨髓中性粒标成单核,只作参考。
Subset 后重新 NormalizeData:细胞集合变了,HVG / PCA / Harmony 必须重做;丢掉全局 scale.data 后也需要从 counts 重建 data。
res_path <- "results/subtype/neutrophil"
obj_path <- "data/rdata/subtype"
if (dir.exists(res_path)) {
old_res <- list.files(res_path, full.names = TRUE)
if (length(old_res) > 0) unlink(old_res, recursive = TRUE)
} else {
dir.create(res_path, recursive = TRUE)
}
if (!dir.exists(obj_path)) dir.create(obj_path, recursive = TRUE)alpha <- .5
pt_size <- .5
raster <- FALSE
raster_dpi <- c(3000, 3000)
min_cutoff <- "q10"
max_cutoff <- "q90"
n_hvg <- 3000test_genes <- qs_read("data/rdata/test_genes.qs2", nthreads = 10)
seurat_object <-
qs_read("data/rdata/seurat_object_first_annot.qs2", nthreads = 10)
table(seurat_object$annot)
B Ery HSPC Mono/Mac/DC Neut pDC
5201 6456 3705 5042 20978 570
Plasma Pre-Neut T/NK
780 13822 15792
seurat_object <-
subset(seurat_object, subset = annot %in% c("Neut", "Pre-Neut"))
dim(seurat_object)[1] 38606 34800
table(seurat_object$annot)
Neut Pre-Neut
20978 13822
table(seurat_object$harmony_clusters_2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0 4060 3900 0 3382 3210 0 3131 0 0 2664 2557 2501 2318 0 0
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
0 1583 1551 0 0 0 0 0 1266 1262 0 0 0 989 0 0
33 34 35 36 37 38
0 0 0 0 426 0
if (length(Layers(seurat_object, search = "counts")) > 1) {
seurat_object <- JoinLayers(seurat_object)
}
seurat_object[["RNA"]]$data <- NULL
seurat_object[["RNA"]]$scale.data <- NULL
DefaultLayer(seurat_object@assays$RNA)[1] "counts"
Layers(seurat_object)[1] "counts"
1. Normalize, HVG, Scale, PCA
细胞集合变了,重新找高变基因和降维。PCA 肘图是在选后面用几个主成分,不是生物学结论。
seurat_object <- NormalizeData(seurat_object, verbose = TRUE)
seurat_object <- FindVariableFeatures(
seurat_object,
nfeatures = n_hvg,
verbose = TRUE
)
head(VariableFeatures(seurat_object), 20) [1] "DEFA1" "IGKC" "DEFA3" "H4C3" "HBB" "IGLC3" "DEFA4" "IGLC2"
[9] "HSPA1B" "HBG2" "CCL4" "JCHAIN" "IGHA1" "MPO" "CCL4L2" "OLFM4"
[17] "IGHG1" "CAMP" "PRG2" "CLC"
LabelPoints(
plot = VariableFeaturePlot(seurat_object),
points = head(VariableFeatures(seurat_object), 20),
repel = TRUE
)pca_features <- setdiff(
VariableFeatures(seurat_object),
test_genes$gene[test_genes$unwant | test_genes$ig | test_genes$tr]
)
sum(VariableFeatures(seurat_object) %in% test_genes$gene[test_genes$unwant | test_genes$ig | test_genes$tr])[1] 152
seurat_object <- ScaleData(
seurat_object,
features = pca_features,
assay = "RNA"
)
seurat_object <- RunPCA(
seurat_object,
npcs = 50,
features = pca_features,
verbose = TRUE,
reduction.name = "pca"
)num_pcs <- list()
num_pcs$pct <-
seurat_object[["pca"]]@stdev / sum(seurat_object[["pca"]]@stdev) * 100
num_pcs$cumu <- cumsum(num_pcs$pct)
num_pcs$co1 <- which(num_pcs$cumu > 80 & num_pcs$pct < 5)[1]
num_pcs$co2 <- sort(
which(
(num_pcs$pct[seq_along(num_pcs$pct)[-length(num_pcs$pct)]] -
num_pcs$pct[seq_along(num_pcs$pct)[-1]]) > 0.1
),
decreasing = TRUE
)[1] + 1
num_pcs$pcs <- min(num_pcs$co1, num_pcs$co2)
num_pcs$co1[1] 36
num_pcs$co2[1] 14
num_pcs$pcs[1] 14
ElbowPlot(seurat_object, reduction = "pca", ndims = 50)2. Harmony
再按样本整合一次。看各样本是否混开。
tic("Harmony integration")
seurat_object <- RunHarmony(
seurat_object,
c("sample"),
ncores = 15,
plot_convergence = TRUE
)toc()Harmony integration: 16.562 sec elapsed
num_pcs <- list()
num_pcs$pct <-
seurat_object[["harmony"]]@stdev /
sum(seurat_object[["harmony"]]@stdev) * 100
num_pcs$cumu <- cumsum(num_pcs$pct)
num_pcs$co1 <- which(num_pcs$cumu > 90 & num_pcs$pct < 5)[1]
num_pcs$co2 <- sort(
which(
(num_pcs$pct[seq_along(num_pcs$pct)[-length(num_pcs$pct)]] -
num_pcs$pct[seq_along(num_pcs$pct)[-1]]) > 0.1
),
decreasing = TRUE
)[1] + 1
num_pcs$co1[1] 44
num_pcs$co2[1] 27
plot_harmony_umap <- function(obj, dims, file) {
umap_temp <- RunUMAP(
obj,
dims = 1:dims,
reduction = "harmony",
reduction.name = "umap_harmony"
)
p <- plot_grid(
ncol = 2,
DimPlot(
umap_temp, reduction = "umap_harmony", group.by = "sample",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
umap_temp, reduction = "umap_harmony", group.by = "group",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
umap_temp, reduction = "umap_harmony", group.by = "phase",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
umap_temp, reduction = "umap_harmony", group.by = "is_doublet",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
)
ggsave(file, plot = p, width = 14, height = 12, dpi = 600)
p
}plot_harmony_umap(seurat_object, 20, file.path(res_path, "01_dim_umap20.pdf"))plot_harmony_umap(seurat_object, 30, file.path(res_path, "01_dim_umap30.pdf"))plot_harmony_umap(seurat_object, 40, file.path(res_path, "01_dim_umap40.pdf"))# 看过 20/30/40 后改这里
n_dims <- 40
seurat_object <- RunUMAP(
seurat_object,
dims = 1:n_dims,
reduction = "harmony",
reduction.name = "umap_harmony"
)3. Clustering
分辨率扫描。这里主要用于找低质量或明显混合的簇。
seurat_object <- FindNeighbors(
seurat_object,
reduction = "harmony",
dims = 1:n_dims
)
for (res in c(1, 2, 3, 4)) {
print(res)
set.seed(123)
seurat_object <- FindClusters(
seurat_object,
cluster.name = paste0("harmony_clusters_", res),
resolution = res,
algorithm = 4
)
gc()
}[1] 1
[1] 2
[1] 3
[1] 4
plot_grid(
ncol = 2,
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_1", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_2", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_3", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_4", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend()
)ggsave(
file.path(res_path, "02_qc_clusters.pdf"),
width = 14, height = 18, dpi = 600
)plot_grid(
ncol = 2,
FeaturePlot(
seurat_object, reduction = "umap_harmony", features = "nFeature_RNA",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
FeaturePlot(
seurat_object, reduction = "umap_harmony", features = "percent_mt",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
seurat_object, reduction = "umap_harmony", group.by = "is_doublet",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_4", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
)Idents(seurat_object) <- "harmony_clusters_4"
VlnPlot(
seurat_object,
features = c("nFeature_RNA", "nCount_RNA", "percent_mt", "doublet_score"),
ncol = 2, pt.size = 0
)4. Markers
点图:点越大、颜色越深,表示该簇里越多细胞、表达越高。用来判断要不要丢掉某些簇。
marker_groups <- list(
promyelocyte = c("AZU1", "MPO", "ELANE", "CTSG", "PRTN3", "CD34"),
pre_neut = c("LTF", "LCN2", "CAMP", "MNDA", "RETN"),
mature_neut = c("FCGR3B", "CXCR2", "NAMPT", "CSF3R", "S100A8", "S100A9"),
cycling = c("MKI67", "TOP2A", "PCNA"),
HSPC = c("CD34", "SPINK2", "AVP"),
mono = c("CD14", "FCN1", "CD68"),
lymphoid = c("CD3D", "MS4A1", "CA1", "HBB"),
mast = c("CPA3", "TPSAB1")
)
Idents(seurat_object) <- "harmony_clusters_4"
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups,
legend.length = 10
)ggsave(
file.path(res_path, "02_qc_dotplot.pdf"),
width = 16, height = 8, dpi = 600
)Idents(seurat_object) <- "harmony_clusters_4"
table(Idents(seurat_object))
1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23
1990 1266 1222 1189 1089 1062 994 990 954 951 926 1842 865 825 741 719
24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 4
714 654 646 619 607 579 1736 443 406 397 389 301 267 231 208 1693
5 6 7 8 9
1499 1484 1484 1459 1359
neutrophil_markers <- FindAllMarkers(seurat_object, only.pos = TRUE)
marker_table <-
neutrophil_markers |>
group_by(cluster) |>
slice_head(n = 20) |>
mutate(rank = paste0("Top_", row_number())) |>
pivot_wider(id_cols = rank, names_from = cluster, values_from = gene)
marker_table# A tibble: 20 × 38
rank `1` `10` `11` `12` `13` `14` `15` `16` `17` `18` `19` `2`
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 Top_1 MMP9 NAMPT NAMPT S100… SLC2… NAMPT DEFA1 MMP8 DEFA3 CXCL8 LTF ENSG…
2 Top_2 TENM1 NEAT1 DUSP1 ACSL1 IFIT… G0S2 DEFA3 CRIS… DEFA1 NFKB… ABCA… CXCR2
3 Top_3 S100… LUCA… S100… IFIT… RBM47 RUBC… DEFA4 S100… RPL10 TNFA… LCN2 TXNIP
4 Top_4 S100… MALA… IFIT… S100… NIBA… ACSL1 SLPI S100… RPS12 ICAM1 MMP8 LUCA…
5 Top_5 S100… IFIT… FOS CMTM2 NEAT1 SRGN MS4A3 LCN2 ENSG… LINC… RETN CAMK…
6 Top_6 LGAL… TREM1 RGS2 RIPO… ACSL4 RGS2 ABCA… PGLY… RPL32 CD83 CEAC… AIF1
7 Top_7 HMGB2 SLC2… CMTM2 S100… SLC8… PROK2 PDE4D LTF RPS27 RELB CAMP SEC1…
8 Top_8 MIR6… FOS SRGN MME CSF3R IFIT… ATP8… MMP9 RPLP1 NEAT1 S100… SLC2…
9 Top_9 PADI4 KCNJ… FCGR… RGS2 RIPO… CR1 BPI HP RPL28 SOD2 LYZ NIBA…
10 Top_… SH3R… SRGN CSF3R FCGR… LYN BCL2… PTMA CYBB RPL41 CAMK… CYBB CPPE…
11 Top_… S100P RGS2 ACSL1 NAMPT ARHG… OSM AZU1 PADI4 RPS23 SLC2… CRIS… ADGR…
12 Top_… RBP7 MME SAT1 H3-3A VMP1 AQP9 LRMDA ENSG… RPS14 MALA… CPNE3 VMP1
13 Top_… SYNE1 FTH1 MME CR1 ITM2B PTGS2 RPL22 S100… RPL1… FTH1 LTA4H CLEC…
14 Top_… ALOX… FCGR… S100… CSF3R CAMK… MCTP2 GSTP1 ADSS2 RPS29 NFKB… RPL39 SCLT1
15 Top_… IL1R2 CSF3R NEAT1 FKBP5 FTH1 FOS CD24 SYNE1 RPL13 NIBA… PGLY… SMAP2
16 Top_… SAMS… SAT1 FTH1 VNN2 CXCR2 KCNJ… CEAC… RETN RPS21 NAMPT OLR1 ENSG…
17 Top_… TXN DUSP1 H3-3A XPO6 SMAP2 PLAUR CEAC… SVIP RPS18 LYN ADSS2 LITAF
18 Top_… RGL4 G0S2 RIPO… SRGN KCNJ… S100… RNAS… PLBD1 RPL19 ENSG… CD24 RIPO…
19 Top_… HIPK2 RIPO… S100… LINC… ALPL MME NUCB2 RPL39 RPS28 PDE4B ANXA1 PPCDC
20 Top_… STK1… CMTM2 FPR1 SELL LITAF TREM1 ELANE STOM RPL11 SAT1 RPS8 NEAT1
# ℹ 25 more variables: `20` <chr>, `21` <chr>, `22` <chr>, `23` <chr>,
# `24` <chr>, `25` <chr>, `26` <chr>, `27` <chr>, `28` <chr>, `29` <chr>,
# `3` <chr>, `30` <chr>, `31` <chr>, `32` <chr>, `33` <chr>, `34` <chr>,
# `35` <chr>, `36` <chr>, `37` <chr>, `4` <chr>, `5` <chr>, `6` <chr>,
# `7` <chr>, `8` <chr>, `9` <chr>
pred_file <- "celltypist/predictions_low.tsv"
if (file.exists(pred_file)) {
pred <- read_tsv(pred_file, show_col_types = FALSE)
barcode_col <- names(pred)[1]
pred_vec <- setNames(pred$majority_voting, pred[[barcode_col]])
seurat_object$celltypist_low <-
unname(pred_vec[colnames(seurat_object)])
DimPlot(
seurat_object,
reduction = "umap_harmony",
group.by = "celltypist_low",
label = TRUE, repel = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
}5. Round 2 (optional)
去掉低质量或混合簇后重新降维。
按 QC 和 marker 去掉低质量或混合簇,再从 counts 重新 Normalize、高变基因、PCA、Harmony 和聚类。
drop_clusters <- c(17, 30, 35, 36)
# drop_clusters <- c("3", "7")if (length(drop_clusters) == 0) {
message("No clusters listed to drop; skip round 2.")
} else {
n_before <- ncol(seurat_object)
keep_cells <- colnames(seurat_object)[
!(as.character(seurat_object$harmony_clusters_4) %in%
as.character(drop_clusters))
]
seurat_object <- subset(seurat_object, cells = keep_cells)
message(
sprintf("Dropped %s cells; %s remain.", n_before - ncol(seurat_object), ncol(seurat_object))
)
Idents(seurat_object) <- "sample"
drop_cols <- grep(
"^harmony_clusters_|^seurat_clusters$",
colnames(seurat_object[[]]),
value = TRUE
)
seurat_object@meta.data <-
seurat_object@meta.data[, setdiff(colnames(seurat_object[[]]), drop_cols), drop = FALSE]
seurat_object[["RNA"]]$data <- NULL
seurat_object[["RNA"]]$scale.data <- NULL
seurat_object <- NormalizeData(seurat_object, verbose = TRUE)
seurat_object <- FindVariableFeatures(
seurat_object, nfeatures = n_hvg, verbose = TRUE
)
pca_features <- setdiff(
VariableFeatures(seurat_object),
test_genes$gene[test_genes$unwant | test_genes$ig | test_genes$tr]
)
seurat_object <- ScaleData(seurat_object, features = pca_features, assay = "RNA")
seurat_object <- RunPCA(
seurat_object, npcs = 50, features = pca_features,
verbose = TRUE, reduction.name = "pca"
)
seurat_object <- RunHarmony(
seurat_object, c("sample"), ncores = 15, plot_convergence = TRUE
)
seurat_object <- RunUMAP(
seurat_object, dims = 1:n_dims, reduction = "harmony",
reduction.name = "umap_harmony"
)
seurat_object <- FindNeighbors(
seurat_object, reduction = "harmony", dims = 1:n_dims
)
for (res in c(1, 2, 3, 4)) {
set.seed(123)
seurat_object <- FindClusters(
seurat_object,
cluster.name = paste0("harmony_clusters_", res),
resolution = res,
algorithm = 4
)
}
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_4", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
}5b. Check cleanup
检查清理后 UMAP 是否还剩明显的垃圾团或双细胞团。
确认清理后的 UMAP 不再有明显的低质量或双细胞团。
plot_grid(
ncol = 2,
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_1", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_2", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_3", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap_harmony",
group.by = "harmony_clusters_4", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend()
)ggsave(
file.path(res_path, "03_postqc_clusters.pdf"),
width = 14, height = 12, dpi = 600
)plot_grid(
ncol = 2,
FeaturePlot(
seurat_object, reduction = "umap_harmony", features = "nFeature_RNA",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
FeaturePlot(
seurat_object, reduction = "umap_harmony", features = "nCount_RNA",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
FeaturePlot(
seurat_object, reduction = "umap_harmony", features = "percent_mt",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
seurat_object, reduction = "umap_harmony", group.by = "is_doublet",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
)ggsave(
file.path(res_path, "03_postqc_metrics.pdf"),
width = 14, height = 12, dpi = 600
)Idents(seurat_object) <- "harmony_clusters_4"
VlnPlot(
seurat_object,
features = c("nFeature_RNA", "nCount_RNA", "percent_mt", "doublet_score"),
ncol = 2, pt.size = 0
)Idents(seurat_object) <- "harmony_clusters_4"
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups,
legend.length = 10
)ggsave(
file.path(res_path, "03_postqc_dotplot.pdf"),
width = 16, height = 8, dpi = 600
)若仍有低质量簇,记下当前高分辨率 ID,下一节 subset 后重跑降维。
drop_clusters_r3 <- c()
# drop_clusters_r3 <- c("2", "5")if (length(drop_clusters_r3) == 0) {
message("No extra clusters to drop.")
} else {
n_before <- ncol(seurat_object)
keep_cells <- colnames(seurat_object)[
!(as.character(seurat_object$harmony_clusters_4) %in%
as.character(drop_clusters_r3))
]
seurat_object <- subset(seurat_object, cells = keep_cells)
message(
sprintf("Dropped %s cells; %s remain.", n_before - ncol(seurat_object), ncol(seurat_object))
)
}6. Recluster for annotation
用清理后的细胞重新聚类,供最后命名。
用清理后的细胞做最终降维,并在 0.1 / 0.2 / 0.5 / 1 分辨率下聚类,供命名。
Idents(seurat_object) <- "sample"
drop_cols <- grep(
"^harmony_clusters_|^seurat_clusters$",
colnames(seurat_object[[]]),
value = TRUE
)
if (length(drop_cols) > 0) {
seurat_object@meta.data <-
seurat_object@meta.data[, setdiff(colnames(seurat_object[[]]), drop_cols), drop = FALSE]
}
seurat_object[["RNA"]]$data <- NULL
seurat_object[["RNA"]]$scale.data <- NULL
seurat_object <- NormalizeData(seurat_object, verbose = TRUE)
seurat_object <- FindVariableFeatures(
seurat_object, nfeatures = n_hvg, verbose = TRUE
)
pca_features <- setdiff(
VariableFeatures(seurat_object),
test_genes$gene[test_genes$unwant | test_genes$ig | test_genes$tr]
)
seurat_object <- ScaleData(seurat_object, features = pca_features, assay = "RNA")
seurat_object <- RunPCA(
seurat_object, npcs = 50, features = pca_features,
verbose = TRUE, reduction.name = "pca"
)
tic("Harmony integration")
seurat_object <- RunHarmony(
seurat_object, c("sample"), ncores = 15, plot_convergence = TRUE
)toc()Harmony integration: 16.174 sec elapsed
seurat_object <- RunUMAP(
seurat_object, dims = 1:20, reduction = "harmony",
reduction.name = "umap"
)
seurat_object <- FindNeighbors(
seurat_object, reduction = "harmony", dims = 1:20
)
for (res in c(0.1, 0.2, 0.5, 1)) {
print(res)
set.seed(123)
seurat_object <- FindClusters(
seurat_object,
cluster.name = paste0("harmony_clusters_", res),
resolution = res,
algorithm = 4
)
gc()
}[1] 0.1
[1] 0.2
[1] 0.5
[1] 1
plot_grid(
ncol = 2,
DimPlot(
seurat_object, reduction = "umap",
group.by = "harmony_clusters_0.1", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap",
group.by = "harmony_clusters_0.2", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap",
group.by = "harmony_clusters_0.5", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend(),
DimPlot(
seurat_object, reduction = "umap",
group.by = "harmony_clusters_1", label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1) + NoLegend()
)ggsave(
file.path(res_path, "04_recluster.pdf"),
width = 14, height = 12, dpi = 600
)Idents(seurat_object) <- "harmony_clusters_1"
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups,
legend.length = 10
)# 改 ident.1 为要检查是否该拆开的 cluster
FindMarkers(
seurat_object,
group.by = "harmony_clusters_1",
ident.1 = 2,
only.pos = TRUE
) |>
head(n = 20) p_val avg_log2FC pct.1 pct.2 p_val_adj
MME 0 1.5195838 0.619 0.247 0
CMTM2 0 1.1172016 0.764 0.401 0
RGS2 0 0.8813532 0.866 0.517 0
AQP9 0 0.7338901 0.691 0.375 0
FCGR3B 0 0.6716172 0.809 0.494 0
CR1 0 1.1442153 0.630 0.320 0
KCNJ15 0 0.9230008 0.647 0.365 0
VNN2 0 1.0294321 0.581 0.303 0
RIPOR2 0 0.6215734 0.923 0.646 0
XPO6 0 0.7139534 0.745 0.469 0
PROK2 0 0.8827371 0.579 0.304 0
G0S2 0 0.6653163 0.745 0.476 0
PLXDC2 0 0.8420498 0.622 0.354 0
NAMPT 0 0.9161506 0.990 0.723 0
IL1R2 0 1.0208401 0.841 0.580 0
ACSL1 0 0.9579170 0.931 0.673 0
SELL 0 0.9122509 0.658 0.402 0
LRRK2 0 0.9606403 0.640 0.390 0
S100A4 0 1.0487408 0.968 0.732 0
FTH1 0 0.2727408 0.991 0.769 0
7. Markers for annotation
命名用的 marker 点图。对照教科书基因,给每个簇起亚型名。
骨髓中性粒按发育轴标,不必硬套肝癌 11 群:
- Promyelocyte:
DEFA1/3/4AZU1MPO高,还没有LTF/CAMP。 - Pre-Neut:二次颗粒
LTFLCN2CAMPRETN高,MMP9/S100A12还低。这批细胞多,就是 NP / 带状核前体。 - Mature:
LTF下来,开始出现MMP9SLC8A1S100A12TXNIPISG15。能拆开就按优势基因标成 MMP9 / SLC8A1 / S100A12 / TXNIP / ISG15 Neut;糊在一起就并成 Mature Neut。
LTF+ 又略有 MMP9 的中间态并进 Pre-Neut。Xue 的 TAN 基因(CCL4 SPP1 CD74)骨髓里可以没有。污染看 NKG7/CD3D。
FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("CD34", "SPINK2", "AVP", "AZU1", "MPO", "ELANE", "CTSG", "PRTN3"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_GMP.pdf"), width = 18, height = 15)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("DEFA1", "DEFA3", "DEFA4", "AZU1", "MPO", "ELANE", "CTSG", "PRTN3"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_promyelo.pdf"), width = 18, height = 15)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("LTF", "LCN2", "CAMP", "MNDA", "RETN"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_preNeut.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("FCGR3B", "CXCR2", "NAMPT", "CSF3R", "S100A8", "S100A9"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_mature.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("MMP8", "S100A12", "ISG15", "TXNIP", "ELL2", "PTGS2"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_xueBlood.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("APOA2", "CD74", "IFIT1", "SPP1", "CCL4", "CD274"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_xueTAN.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("MMP9", "SLC8A1", "S100A12", "TXNIP", "ISG15"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_states.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("MKI67", "TOP2A", "PCNA"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_cycling.pdf"), width = 18, height = 5)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c(
"CD3D", "NKG7", "GNLY", "KLRF1",
"MS4A1", "CD79A", "CD14", "FCN1",
"CD34", "CA1", "HBB", "PF4"
),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_contam.pdf"), width = 18, height = 20)8. Annotation
最终亚型名字。后面组成和通路分析用这里的标签。
命名用的分辨率须与 cluster 对照图一致。
annot_cluster <- "harmony_clusters_0.5"
Idents(seurat_object) <- annot_clusterIdents(seurat_object) <- annot_cluster
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups,
legend.length = 10
)ggsave(
file.path(res_path, "05_markers_dotplot.pdf"),
width = 16, height = 8, dpi = 600
)Idents(seurat_object) <- annot_cluster
table(Idents(seurat_object))
1 2 3 4 5 6 7 8
9013 8301 5934 5740 2102 911 533 371
neutrophil_markers <- FindAllMarkers(seurat_object, only.pos = TRUE)
marker_table <-
neutrophil_markers |>
group_by(cluster) |>
slice_head(n = 20) |>
mutate(rank = paste0("Top_", row_number())) |>
pivot_wider(id_cols = rank, names_from = cluster, values_from = gene)
marker_table# A tibble: 20 × 9
rank `1` `2` `3` `4` `5` `6` `7` `8`
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 Top_1 SLC25A37 MMP9 MMP8 MMP9 DEFA1 IFITM3 LTF CD247
2 Top_2 LUCAT1 CMTM2 LTF PGLYRP1 DEFA3 MX1 MMP8 S100…
3 Top_3 CXCR2 MME LCN2 S100P SLPI IFIT3 CAMP HLA-…
4 Top_4 NIBAN1 CR1 CEACAM8 CRISP3 MS4A3 IFIT2 RETN FTL
5 Top_5 CAMK1D RGS2 RETN FCN1 PTMA ISG15 LCN2 DDIT4
6 Top_6 AQP9 IL1R2 ABCA13 RGL4 ABCA13 IFIT1 ABCA13 HLA-…
7 Top_7 SLC8A1 NAMPT CYBB PADI4 PDE4D HERC5 S100A9 GNLY
8 Top_8 FCGR3B RIPOR2 CAMP GAPDH BPI IFI6 CYBB CCL5
9 Top_9 RBM47 SELL ATP8B4 CLC HMGN2 PARP14 CRISP3 SAP30
10 Top_10 TXNIP G0S2 CRISP3 TFF3 ATP8B4 XAF1 CEACAM8 RPS3
11 Top_11 TAGLN2 PLXDC2 PDE4D TSPO RPL22 RSAD2 PGLYRP1 CRIP1
12 Top_12 VMP1 LRRK2 ADSS2 TENM1 CD24 EPSTI1 UGCG CST3
13 Top_13 TREM1 VNN2 CPNE3 SYNE1 CEACAM8 LY6E SVIP CD74
14 Top_14 RGS2 S100A4 CD24 S100A12 LRMDA IFI44 ENSG00000… LTB
15 Top_15 ENSG00000286076 ACSL1 RPL22 PFN1 RNASE2 OAS3 ADSS2 ENSG…
16 Top_16 XPO6 MAP4K4 LTA4H VIM DEFA4 CMPK2 LTA4H CD99
17 Top_17 CREB5 FPR1 ANXA1 S100A6 RPL36A DDX60 CEACAM1 FYN
18 Top_18 ZFP36L1 RBP7 STOM ACTB RPS21 TRIM22 LYZ LIMD2
19 Top_19 SMAP2 SYTL3 PGLYRP1 S100A9 NUCB2 IFITM1 ATP8B4 CARH…
20 Top_20 RIPOR2 DUSP1 DIAPH2 S100A8 H4C3 RIGI CD24 G0S2
cluster_map 的 key 必须对应 annot_cluster。反复核对 FeaturePlot / DotPlot / marker 表后再填。
cluster_map <- c(
"1" = "SLC8A1 Neut",
"2" = "S100A12 Neut",
"3" = "MMP8 Pre-Neut",
"4" = "MMP9 Pre-Neut",
"5" = "Promyelocyte",
"6" = "ISG15 Neut",
"7" = "MMP8 Pre-Neut",
"8" = "remove"
)if (length(cluster_map) == 0) {
message("cluster_map empty; skip subtype annotation.")
} else {
cluster_ids <- as.character(
FetchData(seurat_object, vars = annot_cluster)[, 1]
)
seurat_object$annot_subtype <- unname(cluster_map[cluster_ids])
table(seurat_object$annot_subtype, useNA = "ifany")
}
ISG15 Neut MMP8 Pre-Neut MMP9 Pre-Neut Promyelocyte remove
911 6467 5740 2102 371
S100A12 Neut SLC8A1 Neut
8301 9013
DimPlot(
seurat_object, reduction = "umap", group.by = "is_doublet",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)ggsave(
file.path(res_path, "06_annot_doublets.pdf"),
width = 8, height = 8, dpi = 600
)丢掉 remove 和 doublet,再 Normalize → HVG → PCA → Harmony → UMAP(dims = 1:20),不再聚类。
n_before <- ncol(seurat_object)
keep_cells <- colnames(seurat_object)[
!(seurat_object$annot_subtype %in% "remove" |
seurat_object$is_doublet %in% "doublet")
]
seurat_object <- subset(seurat_object, cells = keep_cells)
message(sprintf(
"Dropped remove/doublet: %s cells; %s remain.",
n_before - ncol(seurat_object),
ncol(seurat_object)
))
annot_levels <- c(
"Promyelocyte", "MMP8 Pre-Neut", "MMP9 Pre-Neut",
"S100A12 Neut", "SLC8A1 Neut", "ISG15 Neut"
)
seurat_object$annot_subtype <- factor(
seurat_object$annot_subtype,
levels = intersect(annot_levels, unique(as.character(seurat_object$annot_subtype)))
)Idents(seurat_object) <- "sample"
seurat_object[["RNA"]]$data <- NULL
seurat_object[["RNA"]]$scale.data <- NULL
seurat_object <- NormalizeData(seurat_object, verbose = TRUE)
seurat_object <- FindVariableFeatures(
seurat_object, nfeatures = n_hvg, verbose = TRUE
)
pca_features <- setdiff(
VariableFeatures(seurat_object),
test_genes$gene[test_genes$unwant | test_genes$ig | test_genes$tr]
)
seurat_object <- ScaleData(seurat_object, features = pca_features, assay = "RNA")
seurat_object <- RunPCA(
seurat_object, npcs = 50, features = pca_features,
verbose = TRUE, reduction.name = "pca"
)
tic("Harmony integration")
seurat_object <- RunHarmony(
seurat_object, c("sample"), ncores = 15, plot_convergence = TRUE
)toc()Harmony integration: 15.358 sec elapsed
seurat_object <- RunUMAP(
seurat_object, dims = 1:20, reduction = "harmony",
reduction.name = "umap"
)DimPlot(
seurat_object,
reduction = "umap",
group.by = "annot_subtype",
pt.size = pt_size,
alpha = alpha,
raster = raster,
raster.dpi = raster_dpi,
label = TRUE
) + theme(aspect.ratio = 1)ggsave(
file.path(res_path, "06_annot_umap.pdf"),
width = 8, height = 8, dpi = 600
)plot_grid(
ncol = 2,
DimPlot(
seurat_object, reduction = "umap", group.by = "annot_subtype",
label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
seurat_object, reduction = "umap", group.by = "sample",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
seurat_object, reduction = "umap", group.by = "group",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
seurat_object, reduction = "umap", group.by = "phase",
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
)ggsave(
file.path(res_path, "06_annot_overview.pdf"),
width = 14, height = 12, dpi = 600
)Idents(seurat_object) <- "annot_subtype"
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups,
legend.length = 10
)ggsave(
file.path(res_path, "06_annot_dotplot.pdf"),
width = 16, height = 8, dpi = 600
)qs_save(
seurat_object,
file = file.path(obj_path, "seurat_object_neutrophil.qs2"),
nthreads = 10
)sessionInfo()R version 4.4.3 (2025-02-28)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.3 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
time zone: Asia/Shanghai
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] scop_0.9.2 SCpubr_2.0.2.9000 presto_1.0.0 data.table_1.16.2
[5] cowplot_1.1.3 tictoc_1.2.1 harmony_1.2.3 Rcpp_1.0.13-1
[9] qs2_0.1.7 lubridate_1.9.3 forcats_1.0.0 stringr_1.5.1
[13] dplyr_1.1.4 purrr_1.0.2 readr_2.1.5 tidyr_1.3.1
[17] tibble_3.2.1 ggplot2_3.5.1 tidyverse_2.0.0 Seurat_5.2.1
[21] SeuratObject_5.0.2 sp_2.1-4
loaded via a namespace (and not attached):
[1] RcppAnnoy_0.0.22 splines_4.4.3 later_1.4.1
[4] bitops_1.0-9 polyclip_1.10-7 fastDummies_1.7.4
[7] lifecycle_1.0.4 thisplot_0.4.6 doParallel_1.0.17
[10] vroom_1.6.5 globals_0.16.3 lattice_0.22-6
[13] MASS_7.3-65 magrittr_2.0.3 limma_3.62.1
[16] plotly_4.10.4 rmarkdown_2.29 yaml_2.3.10
[19] thisutils_0.5.2 httpuv_1.6.15 sctransform_0.4.1
[22] spam_2.11-0 spatstat.sparse_3.1-0 reticulate_1.42.0
[25] pbapply_1.7-2 RColorBrewer_1.1-3 abind_1.4-8
[28] zlibbioc_1.52.0 Rtsne_0.17 GenomicRanges_1.58.0
[31] BiocGenerics_0.52.0 tweenr_2.0.3 circlize_0.4.16
[34] GenomeInfoDbData_1.2.13 IRanges_2.40.0 S4Vectors_0.44.0
[37] ggrepel_0.9.6 irlba_2.3.5.1 listenv_0.9.1
[40] spatstat.utils_3.1-1 goftest_1.2-3 RSpectra_0.16-2
[43] spatstat.random_3.3-2 fitdistrplus_1.2-1 parallelly_1.39.0
[46] codetools_0.2-20 RcppRoll_0.3.1 ggforce_0.4.2
[49] tidyselect_1.2.1 shape_1.4.6.1 UCSC.utils_1.2.0
[52] farver_2.1.2 viridis_0.6.5 matrixStats_1.4.1
[55] stats4_4.4.3 spatstat.explore_3.3-3 jsonlite_1.8.9
[58] GetoptLong_1.0.5 progressr_0.15.1 ggridges_0.5.6
[61] survival_3.8-3 iterators_1.0.14 systemfonts_1.1.0
[64] foreach_1.5.2 tools_4.4.3 ragg_1.3.3
[67] ica_1.0-3 glue_1.8.0 gridExtra_2.3
[70] xfun_0.49 GenomeInfoDb_1.42.0 withr_3.0.2
[73] fastmap_1.2.0 fansi_1.0.6 digest_0.6.37
[76] timechange_0.3.0 R6_2.5.1 mime_0.12
[79] textshaping_0.4.0 colorspace_2.1-1 scattermore_1.2
[82] tensor_1.5 spatstat.data_3.1-4 RhpcBLASctl_0.23-42
[85] utf8_1.2.4 generics_0.1.3 httr_1.4.7
[88] htmlwidgets_1.6.4 uwot_0.2.2 pkgconfig_2.0.3
[91] gtable_0.3.6 ComplexHeatmap_2.22.0 lmtest_0.9-40
[94] XVector_0.46.0 htmltools_0.5.8.1 dotCall64_1.2
[97] clue_0.3-66 scales_1.3.0 png_0.1-8
[100] spatstat.univar_3.1-1 knitr_1.49 Signac_1.16.0
[103] tzdb_0.4.0 reshape2_1.4.4 rjson_0.2.23
[106] nlme_3.1-166 zoo_1.8-12 GlobalOptions_0.1.2
[109] KernSmooth_2.23-26 vipor_0.4.7 parallel_4.4.3
[112] miniUI_0.1.1.1 ggrastr_1.0.2 pillar_1.9.0
[115] grid_4.4.3 vctrs_0.6.5 RANN_2.6.2
[118] promises_1.3.2 stringfish_0.18.0 xtable_1.8-4
[121] cluster_2.1.8.1 beeswarm_0.4.0 evaluate_1.0.1
[124] cli_3.6.3 compiler_4.4.3 Rsamtools_2.22.0
[127] rlang_1.1.4 crayon_1.5.3 leidenbase_0.1.31
[130] future.apply_1.11.3 labeling_0.4.3 ggbeeswarm_0.7.2
[133] plyr_1.8.9 stringi_1.8.4 BiocParallel_1.40.2
[136] viridisLite_0.4.2 deldir_2.0-4 assertthat_0.2.1
[139] Biostrings_2.74.0 munsell_0.5.1 lazyeval_0.2.2
[142] spatstat.geom_3.3-4 pacman_0.5.1 Matrix_1.7-3
[145] RcppHNSW_0.6.0 hms_1.1.3 patchwork_1.3.0
[148] bit64_4.5.2 future_1.34.0 statmod_1.5.0
[151] shiny_1.9.1 ROCR_1.0-11 igraph_2.1.2
[154] RcppParallel_5.1.9 fastmatch_1.1-4 bit_4.5.0