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: Erythrocyte
红系亚型。珠蛋白主导高变基因是预期现象。
Level-1 Ery(含偏成熟的 cluster 36)。HVG 会被珠蛋白主导,属预期。同时看 MK(PF4/PPBP)和其它谱系污染。
Subset 后重新 NormalizeData:细胞集合变了,HVG / PCA / Harmony 必须重做;丢掉全局 scale.data 后也需要从 counts 重建 data。
res_path <- "results/subtype/erythrocyte"
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("Ery"))
dim(seurat_object)[1] 38606 6456
table(seurat_object$annot)
Ery
6456
table(seurat_object$harmony_clusters_2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
0 0 0 0 0 0 0 0 2723 0 0 0 0 0 0 0
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
1746 0 0 0 0 1466 0 0 0 0 0 0 0 0 0 0
33 34 35 36 37 38
0 0 0 521 0 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] "HBG2" "IGHV4-34" "IGKV3-15" "IGLV3-19" "IGKV1-9" "IGKV3-20"
[7] "HBG1" "S100A8" "CAMP" "IGHV4-39" "LCN2" "DEFA1"
[13] "S100A12" "IGLV4-69" "IGHV2-5" "DEFA3" "LTF" "GNLY"
[19] "LYZ" "XACT"
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] 260
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] 34
num_pcs$co2[1] 15
num_pcs$pcs[1] 15
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: 3.137 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] 42
num_pcs$co2[1] 22
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(
MEP = c("GATA2", "KIT", "CD34", "ITGA2B"),
early = c("GATA1", "KLF1", "TFRC", "CD36", "EPOR", "MYB"),
intermediate = c("CA1", "HBD", "AHSP"),
late = c("GYPA", "HBB", "HBA1", "ALAS2", "SLC4A1"),
MK = c("GP9", "PPBP", "PF4"),
cycling = c("MKI67", "TOP2A"),
HSPC = c("CD34", "SPINK2", "AVP"),
gran_mono = c("MPO", "ELANE", "CAMP", "FCGR3B", "CD14", "FCN1"),
lymphoid = c("CD3D", "NKG7", "MS4A1", "CD79A", "JCHAIN")
)
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 = 10, dpi = 600
)Idents(seurat_object) <- "harmony_clusters_4"
table(Idents(seurat_object))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
356 317 309 308 293 284 259 249 237 229 228 227 215 209 206 194 188 182 181 174
21 22 23 24 25 26 27 28 29 30 31 32 33
172 171 165 147 132 132 131 131 123 93 92 73 49
erythrocyte_markers <- FindAllMarkers(seurat_object, only.pos = TRUE)
marker_table <-
erythrocyte_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 × 34
rank `1` `2` `3` `4` `5` `6` `7` `8` `9` `10` `11` `12`
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 Top_1 FAM1… RRM2 CENPA PTTG1 H4C3 PTH2R STAG1 MAN1… TMCC2 IL32 JCHA… CDC20
2 Top_2 APOC1 MYBL2 UBE2C ENSG… H3C3 FIRRE RABG… SLC2… IFIT… CD3E IGLV… CCNB1
3 Top_3 NME1 CARH… UBE2S ARL6… H3C2 XACT DLEU2 SOX6 ARL4A PTPR… IGKV… HMMR
4 Top_4 AKR1… H2AC… PLK1 UBE2S CDKN… CRACD SLC2… GPCP… XPO7 CD52 IGHV… CCNB2
5 Top_5 SRM CCNE2 KPNA2 CENPF TUBA… CDK6 JAZF1 TRAK2 TCP1… CCL5 IGHV… PLK1
6 Top_6 EPCAM TUBB ASPM HMMR H3C4 LINC… SPTB XPO7 TRIM… CD48 IGHV… DEPD…
7 Top_7 NMU PKMY… KIF14 CDC2… H1-2 RYR3 PBX3 TOP1 UBE2H FYN IGLL5 GAS2…
8 Top_8 ALDH… SAMD1 CDKN… ASPM NUSA… THRB ANK1 ABCC… MKRN1 CD247 IGHM PTTG1
9 Top_9 PEBP1 H3C3 CDK1 CCNB1 H1-5 CCDC… NSD2 SLC4… BNIP… ETS1 IGKV… CCND3
10 Top_… MYC TYMS CENPF CKAP2 H2AC… MYB SOX6 SPTA1 PIM1 IL7R IGKC PRDX2
11 Top_… PPP1… BTG3 CENPE PTMS H2AC… SLC3… FAF1 RAPG… TBCEL CD3D CD74 NEK2
12 Top_… LDHB DYNC… ARL6… NEK2 TUBB ENSG… CENPP RHAG UBR2 PLAA… IGKV… LMNA
13 Top_… SYNG… E2F8 TUBA… GPSM2 HMGB2 CCSE… DIAP… ENSG… LPIN2 GIMA… MZB1 CENPA
14 Top_… GCSH RBBP8 HMGB2 GYPE H2AC… LDLR… NCAP… TSPA… MAN1… PRKCH IGHA1 ARL6…
15 Top_… GIHCG H1-3 GTSE1 HEMGN H3C10 FAM1… MYO1D JAZF1 BPGM TRAF… TMSB… UROS
16 Top_… NME2 FEN1 KIF1… CDKN3 HBM HTR1F CIT TBCEL BCL2… PCED… TMSB… CDKN3
17 Top_… GLRX H3C7 CDKN3 GYPA SAMD1 PVT1 MKI67 GYPB ATG14 CD2 B2M TUBB…
18 Top_… PVT1 SLBP CCNB1 GYPB STAG1 RANB… EZH2 BNIP… DCAF… GZMA IGKV… LXN
19 Top_… CNRI… TUBA… PTTG1 TROAP MKI67 RREB1 CR1L HECT… RBM38 LCK HBG1 BIRC5
20 Top_… MIF TMEM… CDCA2 HBM H2AC… ABO NFIA SPEC… BSG CD96 CYBA MYL4
# ℹ 21 more variables: `13` <chr>, `14` <chr>, `15` <chr>, `16` <chr>,
# `17` <chr>, `18` <chr>, `19` <chr>, `20` <chr>, `21` <chr>, `22` <chr>,
# `23` <chr>, `24` <chr>, `25` <chr>, `26` <chr>, `27` <chr>, `28` <chr>,
# `29` <chr>, `30` <chr>, `31` <chr>, `32` <chr>, `33` <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(10, 11, 18, 20, 26, 32, 33)
# 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(15)
# 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: 2.485 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
)7. Markers for annotation
命名用的 marker 点图。对照教科书基因,给每个簇起亚型名。
人骨髓红系是 MEP → Early Ery → Intermediate Ery → Late Ery 连续轴。对照下面四组 marker:MEP 看 GATA2/KIT/CD34(珠蛋白应低);Early 看 TFRC/CD36/EPOR/GATA1/KLF1;Intermediate 看 CA1/HBD/AHSP;Late 看 HBB/GYPA/ALAS2/SLC4A1(TFRC/MKI67 下降、UMI 低)。
FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("GATA2", "KIT", "CD34", "ITGA2B", "GATA1", "KLF1"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_MEP.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("TFRC", "CD36", "KIT", "MYB", "EPOR", "MKI67"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_early.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("CA1", "HBD", "AHSP", "KLF1", "GATA1"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_inter.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("GYPA", "HBB", "HBA1", "HBA2", "ALAS2", "SLC4A1"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_late.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("GATA2", "KIT", "TFRC", "CD36", "CA1", "AHSP", "GYPA", "HBB", "ALAS2"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_ery.pdf"), width = 18, height = 15)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("KLF1", "GATA1", "GATA2"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_TF.pdf"), width = 18, height = 5)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("GP9", "PPBP", "PF4", "ITGA2B"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_MK.pdf"), width = 18, height = 10)FeaturePlot(
seurat_object, reduction = "umap", ncol = 2,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c("MKI67", "TOP2A"),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_cycling.pdf"), width = 12, height = 5)FeaturePlot(
seurat_object, reduction = "umap", ncol = 3,
min.cutoff = min_cutoff, max.cutoff = max_cutoff,
features = c(
"CD34", "SPINK2", "AVP",
"MPO", "ELANE", "CAMP", "FCGR3B", "CD14", "FCN1",
"CD3D", "NKG7", "MS4A1", "CD79A", "JCHAIN",
"CPA3", "TPSAB1"
),
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
)ggsave(file.path(res_path, "05_markers_contam.pdf"), width = 18, height = 25)8. Annotation
最终亚型名字。后面组成和通路分析用这里的标签。
命名用的分辨率须与 cluster 对照图一致。按 MEP / Early Ery / Intermediate Ery / Late Ery 填。
annot_cluster <- "harmony_clusters_0.1"
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[c("MEP", "early", "intermediate", "late")],
legend.length = 10
)ggsave(
file.path(res_path, "05_markers_stages.pdf"),
width = 14, height = 6, dpi = 600
)Idents(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
1686 1586 1436 505
erythrocyte_markers <- FindAllMarkers(seurat_object, only.pos = TRUE)
marker_table <-
erythrocyte_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 × 5
rank `1` `2` `3` `4`
<chr> <chr> <chr> <chr> <chr>
1 Top_1 S100A4 NUSAP1 XPO7 HBB
2 Top_2 SNHG29 MKI67 IFIT1B UBB
3 Top_3 S100A6 UBE2S ARL4A DCAF12
4 Top_4 NPM1 CENPF TMCC2 MKRN1
5 Top_5 RPL23 DLEU2 UBE2H R3HDM4
6 Top_6 ATP5MC2 LBR MAN1A1 ADIPOR1
7 Top_7 DUT KNL1 TRIM58 BNIP3L
8 Top_8 NME2 SMC4 BNIP3L TRIM58
9 Top_9 HSPD1 HMGB2 SLC25A37 ALAS2
10 Top_10 HNRNPA1 TUBA1B GPCPD1 BCL2L1
11 Top_11 EEF1B2 H1-0 TRAK2 NCOA4
12 Top_12 SYNGR1 HEMGN TBCEL BPGM
13 Top_13 NDUFS5 HBM HECTD4 IFIT1B
14 Top_14 RPL10A CDK1 TOP1 OAZ1
15 Top_15 GAPDH TOP2A TSPAN5 RIOK3
16 Top_16 HINT1 TUBB4B HBD RBM38
17 Top_17 LDHB H4C3 ABCC13 SLC25A39
18 Top_18 NASP ANP32E BSG TENT5C
19 Top_19 ATP5MC1 CDKN2D RAPGEF2 FKBP8
20 Top_20 EEF1G TMPO ALAS2 RNF10
cluster_map <- c(
"1" = "Early Ery",
"2" = "Early Ery",
"3" = "Late Ery",
"4" = "Late Ery"
)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])
annot_levels <- c("Early Ery", "Late Ery")
seurat_object$annot_subtype <- factor(
seurat_object$annot_subtype,
levels = intersect(annot_levels, unique(seurat_object$annot_subtype))
)
table(seurat_object$annot_subtype, useNA = "ifany")
}
Early Ery Late Ery
3272 1941
if (!"umap" %in% Reductions(seurat_object) &&
"umap_harmony" %in% Reductions(seurat_object)) {
seurat_object[["umap"]] <- seurat_object[["umap_harmony"]]
}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_erythrocyte.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 plotly_4.10.4
[16] rmarkdown_2.29 yaml_2.3.10 thisutils_0.5.2
[19] httpuv_1.6.15 sctransform_0.4.1 spam_2.11-0
[22] spatstat.sparse_3.1-0 reticulate_1.42.0 pbapply_1.7-2
[25] RColorBrewer_1.1-3 abind_1.4-8 zlibbioc_1.52.0
[28] Rtsne_0.17 GenomicRanges_1.58.0 BiocGenerics_0.52.0
[31] tweenr_2.0.3 circlize_0.4.16 GenomeInfoDbData_1.2.13
[34] IRanges_2.40.0 S4Vectors_0.44.0 ggrepel_0.9.6
[37] irlba_2.3.5.1 listenv_0.9.1 spatstat.utils_3.1-1
[40] goftest_1.2-3 RSpectra_0.16-2 spatstat.random_3.3-2
[43] fitdistrplus_1.2-1 parallelly_1.39.0 codetools_0.2-20
[46] RcppRoll_0.3.1 ggforce_0.4.2 tidyselect_1.2.1
[49] shape_1.4.6.1 UCSC.utils_1.2.0 farver_2.1.2
[52] viridis_0.6.5 matrixStats_1.4.1 stats4_4.4.3
[55] spatstat.explore_3.3-3 jsonlite_1.8.9 GetoptLong_1.0.5
[58] progressr_0.15.1 ggridges_0.5.6 survival_3.8-3
[61] iterators_1.0.14 systemfonts_1.1.0 foreach_1.5.2
[64] tools_4.4.3 ragg_1.3.3 ica_1.0-3
[67] glue_1.8.0 gridExtra_2.3 xfun_0.49
[70] GenomeInfoDb_1.42.0 withr_3.0.2 fastmap_1.2.0
[73] fansi_1.0.6 digest_0.6.37 timechange_0.3.0
[76] R6_2.5.1 mime_0.12 textshaping_0.4.0
[79] colorspace_2.1-1 scattermore_1.2 tensor_1.5
[82] spatstat.data_3.1-4 RhpcBLASctl_0.23-42 utf8_1.2.4
[85] generics_0.1.3 httr_1.4.7 htmlwidgets_1.6.4
[88] uwot_0.2.2 pkgconfig_2.0.3 gtable_0.3.6
[91] ComplexHeatmap_2.22.0 lmtest_0.9-40 XVector_0.46.0
[94] htmltools_0.5.8.1 dotCall64_1.2 clue_0.3-66
[97] scales_1.3.0 png_0.1-8 spatstat.univar_3.1-1
[100] knitr_1.49 Signac_1.16.0 tzdb_0.4.0
[103] reshape2_1.4.4 rjson_0.2.23 nlme_3.1-166
[106] zoo_1.8-12 GlobalOptions_0.1.2 KernSmooth_2.23-26
[109] vipor_0.4.7 parallel_4.4.3 miniUI_0.1.1.1
[112] ggrastr_1.0.2 pillar_1.9.0 grid_4.4.3
[115] vctrs_0.6.5 RANN_2.6.2 promises_1.3.2
[118] stringfish_0.18.0 xtable_1.8-4 cluster_2.1.8.1
[121] beeswarm_0.4.0 evaluate_1.0.1 cli_3.6.3
[124] compiler_4.4.3 Rsamtools_2.22.0 rlang_1.1.4
[127] crayon_1.5.3 leidenbase_0.1.31 future.apply_1.11.3
[130] labeling_0.4.3 ggbeeswarm_0.7.2 plyr_1.8.9
[133] stringi_1.8.4 BiocParallel_1.40.2 viridisLite_0.4.2
[136] deldir_2.0-4 assertthat_0.2.1 Biostrings_2.74.0
[139] munsell_0.5.1 lazyeval_0.2.2 spatstat.geom_3.3-4
[142] pacman_0.5.1 Matrix_1.7-3 RcppHNSW_0.6.0
[145] hms_1.1.3 patchwork_1.3.0 bit64_4.5.2
[148] future_1.34.0 shiny_1.9.1 ROCR_1.0-11
[151] igraph_2.1.2 RcppParallel_5.1.9 fastmatch_1.1-4
[154] bit_4.5.0