pacman::p_load(Seurat, tidyverse, qs2, harmony, tictoc, cowplot, SCpubr, ggh4x)
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")
source("codes/plot_colors.R")Merge subtypes into final object
合并各谱系亚型,得到 annot_main(大类)和 annot_sub(亚型),并重画官方 UMAP。
从 05 各 lineage 的 annot_subtype 拼出最终对象。Level-1 / Level-2 分别是 annot_main / annot_sub。seurat_object_first_annot.qs2 只提供 counts 和 QC 元数据,粗标 annot 留作对照,不用于后续分析。细胞集合变了,HVG / PCA / Harmony / UMAP 必须重做。不重新聚类。
res_path <- "results/final"
obj_path <- "data/rdata"
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)
}alpha <- .35
pt_size <- .1
raster <- FALSE
raster_dpi <- c(3000, 3000)
n_hvg <- 30001. Collect subtype labels
从 05 各篇把亚型名收齐,检查有没有细胞被漏标或标重。
subtype_files <- c(
hspc = "seurat_object_hspc.qs2",
b_plasma = "seurat_object_b_plasma.qs2",
tnk = "seurat_object_tnk.qs2",
myeloid = "seurat_object_myeloid.qs2",
neutrophil = "seurat_object_neutrophil.qs2",
erythrocyte = "seurat_object_erythrocyte.qs2"
)
subtype_to_main <- c(
HSC = "HSPC",
MEP = "HSPC",
GMP = "HSPC",
MDP = "HSPC",
"Pro-B" = "B",
"Large Pre-B" = "B",
"Small Pre-B" = "B",
"Trans B" = "B",
"Naive B" = "B",
"Memory B" = "B",
Plasma = "Plasma",
"CD4 Tnaive" = "T",
"CD4 Tcm" = "T",
"CD4 Treg" = "T",
"CD8 Tnaive" = "T",
"CD8 Tem" = "T",
"CD8 CTL" = "T",
"CD8 NKT" = "T",
MAIT = "T",
gdT = "T",
"CD56bright NK" = "NK",
"CD56dim NK" = "NK",
"CD14 Mono" = "Mono/DC",
"CD16 Mono" = "Mono/DC",
cDC2 = "Mono/DC",
pDC = "pDC",
Promyelocyte = "Pre-Neut",
"Pre-Neut" = "Pre-Neut",
"MMP8 Pre-Neut" = "Pre-Neut",
"MMP9 Pre-Neut" = "Pre-Neut",
"S100A12 Neut" = "Neut",
"SLC8A1 Neut" = "Neut",
"ISG15 Neut" = "Neut",
"Early Ery" = "Ery",
"Late Ery" = "Ery"
)
annot_main_levels <- c(
"HSPC", "B", "Plasma", "T", "NK",
"Mono/DC", "pDC", "Pre-Neut", "Neut", "Ery"
)
annot_sub_levels <- names(subtype_to_main)
label_tbl <- purrr::imap_dfr(subtype_files, function(file, lineage) {
obj <- qs_read(file.path("data/rdata/subtype", file), nthreads = 10)
stopifnot("annot_subtype" %in% colnames(obj[[]]))
out <- tibble(
barcode = colnames(obj),
lineage = lineage,
annot_sub = as.character(obj$annot_subtype)
)
rm(obj)
gc()
out
})
n_na <- sum(is.na(label_tbl$annot_sub) | label_tbl$annot_sub == "")
n_dup <- sum(duplicated(label_tbl$barcode))
unknown <- setdiff(unique(label_tbl$annot_sub), names(subtype_to_main))
if (n_na > 0) stop(sprintf("%s cells have empty annot_sub.", n_na))
if (n_dup > 0) {
stop(sprintf("%s barcodes appear in more than one subtype object.", n_dup))
}
if (length(unknown) > 0) {
stop(sprintf("Unmapped annot_sub: %s", paste(unknown, collapse = ", ")))
}
label_tbl$annot_main <- unname(subtype_to_main[label_tbl$annot_sub])
print(table(label_tbl$lineage))
b_plasma erythrocyte hspc myeloid neutrophil tnk
4859 5213 3366 4376 31175 12201
print(table(label_tbl$annot_main, useNA = "ifany"))
B Ery HSPC Mono/DC Neut NK pDC Plasma
4305 5213 3366 3910 17668 1747 466 554
Pre-Neut T
13507 10454
print(table(label_tbl$annot_sub, useNA = "ifany"))
CD14 Mono CD16 Mono CD4 Tcm CD4 Tnaive CD4 Treg
3428 205 1503 1450 316
CD56bright NK CD56dim NK CD8 CTL CD8 NKT CD8 Tem
255 1492 1804 1458 2284
CD8 Tnaive cDC2 Early Ery gdT GMP
778 277 3272 465 1524
HSC ISG15 Neut Large Pre-B Late Ery MAIT
404 867 539 1941 396
MDP Memory B MEP MMP8 Pre-Neut MMP9 Pre-Neut
1259 809 179 5990 5589
Naive B pDC Plasma Pro-B Promyelocyte
1014 466 554 371 1928
S100A12 Neut SLC8A1 Neut Small Pre-B Trans B
8056 8745 1059 513
2. Subset first-annot object
只留下通过亚型注释的细胞。
只保留完成亚型命名的细胞。04 的粗标留在 annot 作对照。
test_genes <- qs_read("data/rdata/test_genes.qs2", nthreads = 10)
seurat_object <- qs_read(
"data/rdata/seurat_object_first_annot.qs2",
nthreads = 10
)
n_parent <- ncol(seurat_object)
missing_parent <- setdiff(label_tbl$barcode, colnames(seurat_object))
if (length(missing_parent) > 0) {
stop(sprintf(
"%s subtype barcodes are not in first_annot.",
length(missing_parent)
))
}
dropped_cleanup <- setdiff(colnames(seurat_object), label_tbl$barcode)
message(sprintf(
"first_annot %s cells; keep %s from subtypes; drop %s not in any 05 object.",
n_parent, nrow(label_tbl), length(dropped_cleanup)
))
seurat_object <- subset(seurat_object, cells = label_tbl$barcode)
label_tbl <- label_tbl[match(colnames(seurat_object), label_tbl$barcode), ]
stopifnot(identical(colnames(seurat_object), label_tbl$barcode))
seurat_object$lineage <- label_tbl$lineage
seurat_object$annot_sub <- factor(
label_tbl$annot_sub,
levels = intersect(annot_sub_levels, unique(label_tbl$annot_sub))
)
seurat_object$annot_main <- factor(
label_tbl$annot_main,
levels = intersect(annot_main_levels, unique(label_tbl$annot_main))
)
print(table(first = seurat_object$annot, main = seurat_object$annot_main)) main
first HSPC B Plasma T NK Mono/DC pDC Pre-Neut Neut Ery
B 0 4289 2 0 0 0 0 0 0 0
Ery 0 0 0 0 0 0 0 0 0 5213
HSPC 3366 0 0 0 0 0 0 0 0 0
Mono/Mac/DC 0 0 0 0 0 3903 2 0 0 0
Neut 0 0 0 0 0 0 0 2367 17600 0
pDC 0 0 0 0 0 7 464 0 0 0
Plasma 0 16 552 0 0 0 0 0 0 0
Pre-Neut 0 0 0 0 0 0 0 11140 68 0
T/NK 0 0 0 10454 1747 0 0 0 0 0
print(table(seurat_object$annot_main, seurat_object$sample))
ACI-1 ACI-2 ACI-3 ACI-4 ACI-5 CTRL-1 CTRL-2 CTRL-3 CTRL-4 CTRL-5
HSPC 398 619 287 457 215 167 344 168 323 168
B 139 59 93 172 201 133 530 407 700 417
Plasma 11 22 62 10 4 52 177 90 72 25
T 685 722 952 410 791 810 1775 992 1260 340
NK 131 56 137 89 380 67 205 164 70 92
Mono/DC 193 257 653 406 341 163 486 296 532 254
pDC 28 32 15 17 11 67 60 47 56 44
Pre-Neut 3244 3390 1725 2017 1915 24 56 139 292 121
Neut 2352 1855 5051 3605 3920 2 5 83 192 143
Ery 23 234 23 34 9 157 1213 702 2118 252
CTRL-6
HSPC 220
B 1454
Plasma 29
T 1717
NK 356
Mono/DC 329
pDC 89
Pre-Neut 584
Neut 460
Ery 448
if ("umap_harmony" %in% Reductions(seurat_object)) {
seurat_object[["umap_first"]] <- seurat_object[["umap_harmony"]]
}
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"
3. 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] "IGHG1" "IGHA1" "HBG2" "IGKC" "IGHG2" "JCHAIN"
[7] "IGHG3" "IGKV3-20" "IGLC2" "IGHV3-7" "PTGDS" "IGHG4"
[13] "IGKV1-39" "IGLV1-44" "CCL4L2" "HBG1" "IGKV4-1" "IGHA2"
[19] "IGHV3-23" "IGHV3-30"
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] 289
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"
)
ElbowPlot(seurat_object, reduction = "pca", ndims = 50)4. Harmony
全数据再整合一次,给官方 UMAP 用。
tic("Harmony integration")
seurat_object <- RunHarmony(
seurat_object, c("sample"), ncores = 15, plot_convergence = TRUE
)toc()Harmony integration: 27.196 sec elapsed
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 = "annot_main",
cols = annot_main_col, label = TRUE,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
DimPlot(
umap_temp, reduction = "umap_harmony", group.by = "annot_sub",
cols = annot_sub_col, label = TRUE, label.size = 3,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1),
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",
cols = group_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi
) + theme(aspect.ratio = 1)
)
ggsave(file, plot = p, width = 16, height = 14, 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 后改这里。01 全局对象用 30;05 亚型官方 UMAP 用 20。
n_dims <- 20
seurat_object <- RunUMAP(
seurat_object, dims = 1:n_dims, reduction = "harmony",
reduction.name = "umap"
)5. Annotation UMAPs
大类和亚型上色。团分得开、名字和位置对得上,才说明注释站得住。
短坐标轴同 summary.qmd。颜色来自 codes/plot_colors.R。
plot_umap_short(
seurat_object, group.by = "annot_main", cols = annot_main_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = TRUE, label.size = 5
)ggsave(
file.path(res_path, "02_annot_main.pdf"),
width = 8, height = 8, dpi = 600
)
ggsave(
file.path(res_path, "02_annot_main.png"),
width = 8, height = 8, dpi = 600
)plot_umap_short(
seurat_object, group.by = "annot_sub", cols = annot_sub_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = TRUE, label.size = 3, legend = TRUE
)ggsave(
file.path(res_path, "02_annot_sub.pdf"),
width = 11, height = 8, dpi = 600
)
ggsave(
file.path(res_path, "02_annot_sub.png"),
width = 11, height = 8, dpi = 600
)p_main_group <- list(
plot_umap_short(
subset(seurat_object, group == "CTRL"),
group.by = "annot_main", cols = annot_main_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = FALSE, title = "CTRL"
),
plot_umap_short(
subset(seurat_object, group == "ACI"),
group.by = "annot_main", cols = annot_main_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = FALSE, title = "ACI"
)
)
plot_grid(plotlist = p_main_group, ncol = 2)ggsave(
file.path(res_path, "02_annot_main_group.pdf"),
width = 15, height = 8, units = "in", dpi = 600
)p_sub_group <- list(
plot_umap_short(
subset(seurat_object, group == "CTRL"),
group.by = "annot_sub", cols = annot_sub_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = FALSE, title = "CTRL"
),
plot_umap_short(
subset(seurat_object, group == "ACI"),
group.by = "annot_sub", cols = annot_sub_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = FALSE, title = "ACI"
)
)
plot_grid(plotlist = p_sub_group, ncol = 2)ggsave(
file.path(res_path, "02_annot_sub_group.pdf"),
width = 15, height = 8, units = "in", dpi = 600
)plot_grid(
ncol = 2,
plot_umap_short(
seurat_object, group.by = "annot_main", cols = annot_main_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = TRUE, label.size = 4
),
plot_umap_short(
seurat_object, group.by = "annot_sub", cols = annot_sub_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = TRUE, label.size = 3
),
plot_umap_short(
seurat_object, group.by = "sample", cols = NULL,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = FALSE, legend = TRUE
),
plot_umap_short(
seurat_object, group.by = "group", cols = group_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = FALSE, legend = TRUE
)
)ggsave(
file.path(res_path, "02_annot_overview.pdf"),
width = 16, height = 14, dpi = 600
)if ("umap_first" %in% Reductions(seurat_object)) {
plot_grid(
ncol = 2,
plot_umap_short(
seurat_object, reduction = "umap_first", group.by = "annot",
cols = NULL,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = TRUE, title = "first annot (old UMAP)"
),
plot_umap_short(
seurat_object, reduction = "umap_first", group.by = "annot_main",
cols = annot_main_col,
pt.size = pt_size, alpha = alpha, raster = raster, raster.dpi = raster_dpi,
label = TRUE, title = "annot_main on old UMAP"
)
)
ggsave(
file.path(res_path, "02_annot_first_umap.pdf"),
width = 16, height = 8, dpi = 600
)
}6. Marker DotPlots
用 marker 再核对一遍命名。
marker_groups_main <- list(
HSPC = c("CD34", "SPINK2", "AVP"),
B = c("MS4A1", "CD79A"),
Plasma = c("JCHAIN", "MZB1"),
T = c("CD3D", "CD4", "CD8A", "IL7R"),
NK = c("NKG7", "GNLY", "KLRF1"),
`Mono/DC` = c("CD14", "FCN1", "LYZ", "CD1C"),
pDC = c("CLEC4C", "LILRA4"),
`Pre-Neut` = c("AZU1", "MPO", "LTF", "CAMP"),
Neut = c("FCGR3B", "CSF3R", "NAMPT"),
Ery = c("CA1", "HBD", "GYPA")
)
Idents(seurat_object) <- "annot_main"
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups_main,
legend.length = 10
)ggsave(
file.path(res_path, "03_markers_main.pdf"),
width = 12, height = 5, dpi = 600
)Idents(seurat_object) <- "annot_sub"
do_DotPlot(
sample = seurat_object,
flip = FALSE,
legend.title = "Average Expression",
legend.title.face = "plain",
features = marker_groups_main,
legend.length = 10
)ggsave(
file.path(res_path, "03_markers_sub.pdf"),
width = 12, height = 10, dpi = 600
)7. Save
写出后面 07–11 都读的最终对象。
写出 seurat_object_final.qs2:annot_main 为大类,annot_sub 为亚型。后面组成和状态分析都读这份。
Idents(seurat_object) <- "annot_main"
qs_save(
seurat_object,
file = file.path(obj_path, "seurat_object_final.qs2"),
nthreads = 10
)
dim(seurat_object)[1] 38606 61190
table(seurat_object$annot_main, useNA = "ifany")
HSPC B Plasma T NK Mono/DC pDC Pre-Neut
3366 4305 554 10454 1747 3910 466 13507
Neut Ery
17668 5213
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] ggh4x_0.2.8 SCpubr_2.0.2.9000 cowplot_1.1.3 tictoc_1.2.1
[5] harmony_1.2.3 Rcpp_1.0.13-1 qs2_0.1.7 lubridate_1.9.3
[9] forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4 purrr_1.0.2
[13] readr_2.1.5 tidyr_1.3.1 tibble_3.2.1 ggplot2_3.5.1
[17] tidyverse_2.0.0 Seurat_5.2.1 SeuratObject_5.0.2 sp_2.1-4
loaded via a namespace (and not attached):
[1] RColorBrewer_1.1-3 jsonlite_1.8.9 magrittr_2.0.3
[4] spatstat.utils_3.1-1 farver_2.1.2 rmarkdown_2.29
[7] ragg_1.3.3 vctrs_0.6.5 ROCR_1.0-11
[10] spatstat.explore_3.3-3 htmltools_0.5.8.1 sctransform_0.4.1
[13] parallelly_1.39.0 KernSmooth_2.23-26 htmlwidgets_1.6.4
[16] ica_1.0-3 plyr_1.8.9 plotly_4.10.4
[19] zoo_1.8-12 igraph_2.1.2 mime_0.12
[22] lifecycle_1.0.4 pkgconfig_2.0.3 Matrix_1.7-3
[25] R6_2.5.1 fastmap_1.2.0 fitdistrplus_1.2-1
[28] future_1.34.0 shiny_1.9.1 digest_0.6.37
[31] colorspace_2.1-1 patchwork_1.3.0 tensor_1.5
[34] RSpectra_0.16-2 irlba_2.3.5.1 textshaping_0.4.0
[37] labeling_0.4.3 progressr_0.15.1 fansi_1.0.6
[40] spatstat.sparse_3.1-0 timechange_0.3.0 httr_1.4.7
[43] polyclip_1.10-7 abind_1.4-8 compiler_4.4.3
[46] withr_3.0.2 viridis_0.6.5 fastDummies_1.7.4
[49] MASS_7.3-65 tools_4.4.3 lmtest_0.9-40
[52] httpuv_1.6.15 future.apply_1.11.3 goftest_1.2-3
[55] glue_1.8.0 nlme_3.1-166 promises_1.3.2
[58] grid_4.4.3 Rtsne_0.17 cluster_2.1.8.1
[61] reshape2_1.4.4 generics_0.1.3 gtable_0.3.6
[64] spatstat.data_3.1-4 tzdb_0.4.0 data.table_1.16.2
[67] hms_1.1.3 stringfish_0.18.0 utf8_1.2.4
[70] spatstat.geom_3.3-4 RcppAnnoy_0.0.22 ggrepel_0.9.6
[73] RANN_2.6.2 pillar_1.9.0 spam_2.11-0
[76] RcppHNSW_0.6.0 later_1.4.1 splines_4.4.3
[79] lattice_0.22-6 survival_3.8-3 deldir_2.0-4
[82] tidyselect_1.2.1 miniUI_0.1.1.1 pbapply_1.7-2
[85] knitr_1.49 gridExtra_2.3 scattermore_1.2
[88] RhpcBLASctl_0.23-42 xfun_0.49 matrixStats_1.4.1
[91] stringi_1.8.4 lazyeval_0.2.2 yaml_2.3.10
[94] pacman_0.5.1 evaluate_1.0.1 codetools_0.2-20
[97] cli_3.6.3 uwot_0.2.2 RcppParallel_5.1.9
[100] systemfonts_1.1.0 xtable_1.8-4 reticulate_1.42.0
[103] munsell_0.5.1 globals_0.16.3 spatstat.random_3.3-2
[106] png_0.1-8 spatstat.univar_3.1-1 parallel_4.4.3
[109] assertthat_0.2.1 dotCall64_1.2 listenv_0.9.1
[112] viridisLite_0.4.2 scales_1.3.0 ggridges_0.5.6
[115] rlang_1.1.4