05 轨迹推断与拟时序分析
聚类把细胞分成若干离散的 cluster,但发育、分化、应激响应这类现象更自然的描述是"细胞沿着一条连续路径逐渐变化"。轨迹推断(trajectory inference)的目标就是把单细胞数据里的连续结构显式地建模成图或曲线,然后给每个细胞一个在这条路径上的位置 —— 就是拟时序(pseudotime)。
这是一个"真正的时间轴"在单细胞里其实不可观测的近似:所有细胞都是同一刻被测的,我们只是用基因表达的变化推测出它们在分化进程里前后的相对位置。本章走三种主流方法的典型用法:Monocle3、Slingshot、以及基于 RNA velocity 的 scVelo。
这类分析能回答什么
- 分化路径上关键的基因开关在哪个拟时点打开
- 某个 cluster 在分化树上处在分支点还是端点
- 两个最终命运(比如 T vs B)的分叉什么时候发 生
- 哪些基因是"驱动基因"、哪些只是被动跟随
如果你的项目里没有明显的连续过程(比如只是健康样本的静态图谱),拟时序分析往往得不出有用的结论。先想清楚要回答什么问题再选方法。
先确认你的数据该不该做轨迹
不是所有数据都适合做轨迹。先问自己三个问题:
1. 系统里真有连续过程吗?
适合:发育(胚胎、造血)、分化(干细胞 → 成熟细胞)、应激响应(药物处理时间序列)、肿瘤进展。
不适合:健康成人的稳态组织(PBMC、肾、肝),cluster 之间多数是平行关系而不是前后关系。强行做出来的"轨迹"是数学拟合的副产品,不反映生物学。
2. 起点选得出来吗?
任何拟时序方法都需要一个起点,错的起点出错的轨迹。判断标准:
- 有早期分化 marker(干细胞标志:CD34、PROM1、SOX2 等)
- 有 RNA velocity(spliced/unspliced 比例能看出方向)
- 有外部时间信息(Day0 / Day3 / Day7 这种采样设计)
三者都没有时,做轨迹基本是凭经验在猜。
3. 数据深度够吗?
拟时序对 dropout 特别敏感。每个细胞 nFeature_RNA < 1000 时,分支点判断会很不稳。深度不够时先增加测序深度再做,不要硬上。
方法选择
| 方法 | 语言 | 擅长 | 备注 |
|---|---|---|---|
| Monocle3 | R | 复杂分支结构 | 现在单细胞轨迹分析的默认选择 |
| Slingshot | R | 简单线性 / 少分支 | 需要先聚类,但接口简单 |
| scVelo | Python | 方向性强的分化路径 | 需要 spliced / unspliced counts,通常从 velocyto 或 Cell Ranger 输出生成 |
| PAGA | Python (Scanpy) | 大规模数据的拓扑结构 | 抽象程度高,适合做总览图 |
| CytoTRACE | R | 估计每个细胞的分化程度 | 不给出显式轨迹,只给出相对排序 |
实际选择建议:
- 新项目从 Monocle3 开始。它是现在最标准的选择。
- 需要 RNA velocity 的"方向性"(看细胞正在往哪变)时用 scVelo,前提是你有 BAM 文件或能跑 velocyto。
- 只是想给细胞一个"早-晚"排序、不关心轨迹形状的话 CytoTRACE 最简单。
Monocle3:完整流程
安装
if (!requireNamespace("BiocManager", quietly = TRUE)) install.packages("BiocManager")
BiocManager::install(c(
"BiocGenerics", "DelayedArray", "DelayedMatrixStats", "limma",
"S4Vectors", "SingleCellExperiment", "SummarizedExperiment",
"batchelor", "HDF5Array", "terra", "ggrastr"
))
install.packages("devtools")
devtools::install_github("cole-trapnell-lab/monocle3")
从 Seurat 对象出发
Monocle3 的核心对象叫 cell_data_set(CDS)。如果前面用 Seurat 分析过,可以直接转:
library(Seurat)
library(monocle3)
library(SeuratWrappers)
cds <- as.cell_data_set(seurat_obj)
也可以手动从 counts 矩阵建:
cds <- new_cell_data_set(
expression_data = seurat_obj@assays$RNA@counts,
cell_metadata = seurat_obj@meta.data,
gene_metadata = data.frame(
gene_short_name = rownames(seurat_obj),
row.names = rownames(seurat_obj)
)
)
预处理、降维和聚类
cds <- preprocess_cds(cds, num_dim = 50)
cds <- reduce_dimension(cds, reduction_method = "UMAP")
cds <- cluster_cells(cds, resolution = 1e-3)
plot_cells(cds, color_cells_by = "cell_type")
plot_cells(cds, color_cells_by = "cluster")
Monocle3 自己有一套 UMAP 和聚类。如果你已经在 Seurat 里做过一套,结果通常很相似,但 Monocle3 的 UMAP 会是后续 learn_graph 的输入,不要跳过 这一步。
学习轨迹图
cds <- learn_graph(cds)
plot_cells(cds,
color_cells_by = "cell_type",
label_groups_by_cluster = FALSE,
label_leaves = FALSE,
label_branch_points = FALSE
)
learn_graph 会在 UMAP 上拟合一条主干曲线(principal graph),分支点和叶子结点由算法自动判断。
选择起点并排序
拟时序需要一个起点("分化程度最低的那个 cluster")。两种方式:
# 方式 1:点图里手动点
cds <- order_cells(cds)
# 方式 2:根据已有注释自动选
cds <- order_cells(cds, root_cells = colnames(cds)[cds$cell_type == "Stem"])
plot_cells(cds,
color_cells_by = "pseudotime",
label_cell_groups = FALSE,
label_leaves = FALSE,
label_branch_points = FALSE
)