拟时序分析:选Monocle2 还是 Monocle3?

一、写在前面

单细胞RNA-seq测的是某一瞬间每个细胞的转录组快照------但生物学过程(分化、极化、应激反应)是连续动态的。实验里我们拿不到"同一个细胞随时间变化"的纵向数据,只能拿到一群处于不同进程状态的细胞。

📌 通俗理解:把每个细胞想象成一张照片。你手里有几千张照片,拍的是同一场"细胞分化"的不同瞬间,但照片上没有时间戳。拟时序分析就是通过"照片内容的相似程度",把这些照片按剧情顺序重新排成一列,给每个细胞打上一个"拍摄于第几分钟"的标签------这个标签就是伪时间(Pseudotime)。

一个经典的生物学问题:心梗后,浸润的单核来源巨噬细胞(如Ccr2+炎症性巨噬)会逐渐向修复性表型(如Trem2+巨噬)转化。这不是一个"有/无"的开关,而是一条连续的分化轨迹。拟时序分析能回答:

  • 轨迹的起点在哪?终点在哪?中间有没有分叉?

  • 处理vs对照的细胞在这条轨迹上推进速度是否不同?

  • 哪些基因的表达随轨迹单调变化(轨迹驱动基因)?

目前做拟时序最常用的工具就是Monocle系列------准确说是Monocle2和 Monocle3。两者底层算法完全不同。

二、Monocle2:DDRTree时代

Monocle2(Qiu et al., Nature Methods, 2017)的核心是反向图嵌入(Reversed Graph Embedding, RGE)+ DDRTree:把高维表达数据"压"进一个二维的树状骨架,再沿树计算伪时间。

2.1 原理:经典三步走

|

步骤

|

函数

|

做了什么

|

| --- | --- | --- |

|

  1. 选排序基因

| setOrderingFilter |

挑出几百~几千个"有故事"的基因(高离散度或差异基因),轨迹的形态几乎完全由它们决定

|

|

  1. 学习树

| reduceDimension(method = "DDRTree") |

用 DDRTree 把高维空间嵌成一个二维树:树干+分叉点,细胞挂在树枝上

|

|

  1. 算伪时间

| orderCells |

沿树从"根"到"叶"给每个细胞算伪时间;根由 root_state 指定

|

Monocle2 里有两个容易混淆的核心概念:

  • State:树上的一段"树枝",每个细胞属于一个 State;

  • Pseudotime:从根 State 出发、沿树走到每个细胞的"路程"。

2.2 代码实战(以公开数据 Paul et al. 2015 髓系分化为例)

php 复制代码
library(monocle)
library(Seurat)
library(scRNAseq)
# ---- 数据准备:Paul et al. 2015 (Cell),小鼠髓系祖细胞分化数据 ----
sce <- PaulHSCData()                      # 返回 SingleCellExperiment 对象
head(colData(sce))                        # 查看注释列(细胞类型等,列名以实际为准)
counts_mat <- as.matrix(counts(sce))
pd <- new("AnnotatedDataFrame", data = as.data.frame(colData(sce)))
fd <- new("AnnotatedDataFrame", data = data.frame(
  gene_short_name = rownames(sce), row.names = rownames(sce)))
cds2 <- newCellDataSet(counts_mat,
                       phenoData = pd, featureData = fd,
                       expressionFamily = negbinomial.size())   # UMI 数据用负二项
# ---- 标准化 ----
cds2 <- estimateSizeFactors(cds2)
# ---- 第 1 步:选排序基因 -------
# 注意:博主没用 estimateDispersions() + dispersionTable()!
# Monocle2 内部仍调用 dplyr 0.6 时代的 group_by_(),在 dplyr >= 1.0 下会报错:
#   "group_by_() was deprecated in dplyr 0.7.0 and is now defunct"
# 改用 Seurat 的高变基因作为排序基因,效果一致且完全兼容现代环境:
seu <- CreateSeuratObject(counts = counts_mat)
seu <- NormalizeData(seu)
seu <- FindVariableFeatures(seu, selection.method = "vst", nfeatures = 2000)
ordering_genes <- VariableFeatures(seu)
cds2 <- setOrderingFilter(cds2, ordering_genes)
# ---- 第 2 步:DDRTree 降维 ----
cds2 <- reduceDimension(cds2, max_components = 2, method = 'DDRTree')
# ---- 第 3 步:排序(先看 State 图,再指定根)----
cds2 <- orderCells(cds2)
p1 <- plot_cell_trajectory(cds2, color_by = "State")   # 先看树长什么样
print(p1)
# 根据 State 图 + 生物学先验,把祖细胞所在的 State 指定为根
cds2 <- orderCells(cds2, root_state = 1)               # 数字换成你看到的 State
p2 <- plot_cell_trajectory(cds2, color_by = "Pseudotime")
print(p2)
ggsave("Monocle2_Pseudotime.pdf", p2, width = 6, height = 5)

2.3 Monocle2的三大痛点

  1. 排序基因决定轨迹:轨迹形态几乎完全由setOrderingFilter选的基因决定,选择时需要慎重;

  2. 树是二维的:DDRTree 只给一个2D树状骨架,细胞过多时会导致挤压严重,分支结构丢失;

  3. 不维护了:该实验室已把重心转向Monocle3,Monocle2在较新的R环境下兼容性问题频发------典型的就是上面的 estimateDispersions() 报错(dplyr 1.0 移除了group_by_())。

三、Monocle3:UMAP + 主图学习

Monocle3(Cao et al., Nature, 2019)彻底换了思路:先降到UMAP,再在UMAP上"学习"出一条图(graph)作为轨迹。

3.1 原理:先降维,再连图

|

步骤

|

函数

|

做了什么

|

| --- | --- | --- |

|

  1. 预处理

| preprocess_cds |

PCA 降维

|

|

  1. UMAP

| reduce_dimension |

UMAP 降到 2D

|

|

  1. 聚类

| cluster_cells |

Louvain 聚类,帮助后续建图

|

|

  1. 学主图

| learn_graph |

在 UMAP 坐标上学习"主图(principal graph)"------细胞图的骨架

|

|

  1. 算伪时间

| order_cells |

计算每个细胞沿图走到根节点 的距离

|

Monocle3 与 Monocle2 的两个关键差异:

  1. 不依赖排序基因:learn_graph直接基于UMAP低维坐标上细胞的"距离"建图,轨迹形态由降维结果决定;

  2. 根可以精确到"节点":order_cells(root_pr_nodes = ...) 指定图上的节点为根,而不是Monocle2的State级别。

3.2 代码实战(同一份 Paul 数据)

php 复制代码
library(monocle3)
library(scRNAseq)
library(igraph)
sce <- PaulHSCData()
cds3 <- new_cell_data_set(counts(sce),
                          cell_metadata = as.data.frame(colData(sce)),
                          gene_metadata = data.frame(
                            gene_short_name = rownames(sce), row.names = rownames(sce)))
# ---- 预处理 + 降维 ----
cds3 <- preprocess_cds(cds3, num_dim = 50)      # 小数据 50 维即可,大数据可升到 100
cds3 <- reduce_dimension(cds3)                  # 默认 UMAP
# ---- 聚类 + 学主图 ----
cds3 <- cluster_cells(cds3, k = 25)             # k 越大图越稀疏
cds3 <- learn_graph(cds3, use_partition = FALSE)  # FALSE:全局一张图,保证起终点连通
# ---- 定位根节点:祖细胞群体所在的节点 ----
# 注意:PaulHSCData() 的 colData 只有 plate/mouse 等实验信息,没有自带细胞类型标签,
# 所以不能用"按标签找群体"的官方写法,改用"祖细胞标记基因"定位根细胞:
# 表达越高的细胞越接近轨迹起点。Gata2 是造血祖细胞标志(换成你自己的祖细胞标志基因)。
get_earliest_principal_node_marker <- function(cds, marker = "Gata2", top_frac = 0.2) {
  cnt <- SingleCellExperiment::counts(cds)
  if (!marker %in% rownames(cnt)) stop("marker 基因不在表达矩阵中: ", marker)
  g <- as.numeric(cnt[marker, , drop = TRUE])
  pos <- g[g > 0]
  if (length(pos) == 0) stop("marker 基因在所有细胞中表达均为 0: ", marker)
  thr <- sort(pos, decreasing = TRUE)[max(1, round(length(pos) * top_frac))]
  root_cells <- colnames(cds)[g >= thr]
  closest_vertex <- as.matrix(
    cds@principal_graph_aux[["UMAP"]]$pr_graph_cell_proj_closest_vertex[colnames(cds), ])
  igraph::V(principal_graph(cds)[["UMAP"]])$name[
    as.numeric(names(which.max(table(closest_vertex[root_cells, ]))))]
}
root_node <- get_earliest_principal_node_marker(cds3, marker = "Gata2")
cds3 <- order_cells(cds3, root_pr_nodes = root_node)
p3 <- plot_cells(cds3, color_cells_by = "pseudotime",
                 show_trajectory_graph = TRUE, label_groups_by_cluster = FALSE)
print(p3)
ggsave("Monocle3_Pseudotime.pdf", p3, width = 6, height = 5)

Monocle3 伪时间轨迹

▲ 图 1:Monocle3 伪时间轨迹(基于 Paul et al. 2015 髓系分化数据重绘)

散点是 UMAP 上的细胞,深灰色分支线是 learn_graph 学出的主图;颜色代表伪时间------深蓝为轨迹起点(Gata2+ 祖细胞所在区域),经紫红色过渡到橙黄的末端分化群体。可以看到轨迹并非一条直线,而是从祖细胞出发分出多条分支,分别通向不同终末谱系。如果研究目标只关心其中某一条路径(比如"祖细胞 → 单核/巨噬"),就需要用下一节的 choose_graph_segments 把这段切出来。
📌 数据没有细胞类型标签怎么办? PaulHSCData() 的 colData 不含细胞类型(只有 plate/mouse 等实验信息),所以根节点用"祖细胞标记基因"定位(见上方函数)。如果你的数据自带标签列,改回官方写法即可:cell_ids <- which(colData(cds)[, "cell.type"] == "GMP");想要现成的带标签造血分化数据,可用 scRNAseq::NestorowaHSCData()(colData 的 fine 列含细胞类型)。

3.3 真正的"起点 → 终点"限定轨迹:choose_graph_segments

很多项目需要定向轨迹------不是全图伪时间,而是"从A到B的这一段"。典型需求:只看Ccr2+炎症性巨噬 → Trem2+修复性巨噬这条分化路径。

Monocle2 没有原生手段做这件事(只能靠截取细胞子集重新跑);Monocle3用choose_graph_segments直接"切段":

php 复制代码
# 终点节点:换成"终末分化标志基因"(Mpo 是髓系终末标志;换成你研究的终点基因)
end_node <- get_earliest_principal_node_marker(cds3, marker = "Mpo")
# 提取 root_node -> end_node 这一段轨迹
cds_sub <- choose_graph_segments(cds3,
                                 starting_pr_node = root_node,
                                 ending_pr_nodes = end_node)
# 段内伪时间自动重新归一化到 0~1
p4 <- plot_cells(cds_sub, color_cells_by = "pseudotime",
                 show_trajectory_graph = TRUE)
print(p4)
# 轨迹驱动基因:Moran's I 检验(Monocle2 对应 differentialGeneTest)
pr_test <- graph_test(cds_sub, neighbor_graph = "principal_graph", cores = 4)
head(pr_test[order(pr_test$morans_I, decreasing = TRUE), ])

四、核心差异对比表

五、到底该怎么选?

✅ 仍然用 Monocle2 的场景

  • 复现/对照早期文献的流程与图(2014--2019年的文章几乎全是 Monocle2的DDRTree图);

  • 细胞数少(几百~几千)、追求整洁的树状分叉图。

✅ 优先用Monocle3的场景

  • 新项目起步:维护活跃、文档全、与Seurat生态无缝衔接;

  • 需要定向轨迹(如"炎症巨噬→ 修复巨噬"这一段),用 choose_graph_segments;

  • 细胞量大;

  • 想在UMAP上直接看轨迹,保留与分群图一致的视觉语言。

六、避坑清单(汇总)

  1. Monocle2 的排序基因不要用"全部基因":用 dispersionTable 选高离散基因或差异基因(几百到两三千个),全基因会让树失去结构。

  2. 根 State 不能盲选 root_state = 1:先画color_by = "State" 的图,结合生物学先验(祖细胞 / 起始群体)指定根,否则伪时间方向可能完全相反。

  3. Monocle3 默认 use_partition = TRUE:细胞会被拆成多个不连通的partition,根和终点可能不在同一张图里。要做全局/定向轨迹,记得 learn_graph(..., use_partition = FALSE)。

  4. 全图伪时间 ≠ 轨迹段伪时间:order_cells给的是"到根节点的图距离";定向轨迹必须用choose_graph_segments重新切段,段内伪时间才是0→1 的"A 到B"进程。

  5. 根节点的选择必须有生物学依据:不要图省事选"最左边"的节点。用祖细胞标记群体(如GMP / Ccr2+起始群体)定位节点。

  6. 复用 Seurat 的 UMAP 要谨慎:Seurat的UMAP是为分群优化的,未必适合画轨迹。若轨迹形态奇怪,改用Monocle3自行preprocess_cds的UMAP(两套口径统一再比较)。

  7. 细胞数太少学不出轨迹:主图学习至少需要几百个细胞,样本少时建议把同组织各样本合并(并注意去批次)。

总结

|

场景

|

推荐工具

|

说明

|

| --- | --- | --- |

|

新项目、大细胞数、定向轨迹

| Monocle3 | choose_graph_segments

切段 + 根节点精确控制

|

|

复现旧文献、整洁树图

| Monocle2 |

DDRTree 经典风格,但需手动选排序基因

|

🚫 最重要的提醒 :无论用哪个工具,根节点的选择很关键。伪时间是"从根出发的距离",根选错,整条轨迹方向就是反的------工具只是画图的手,生物学先验才是定方向的大脑。

相关推荐
Bioinfo Guy8 天前
Sci. Adv. | PINK1敲除为何有时有效、有时无效?肺癌研究揭示阶段与用药依赖
生物信息学
比特酶生物计算9 天前
口癌发Adv Sci:单细胞锁定中性粒细胞耐药新靶点
生物信息学·医学科研·口咽癌
Biomamba生信基地10 天前
“AI与生信”公开课,下周五药科大学见
ai·生物信息学
Bioinfo Guy11 天前
30天免费试用、100 BIC额度!Illumina推出BioInsight,基因组分析与AI怎么用?
生物信息学
Bioinfo Guy12 天前
Nat. Genet. | 癌基因还在,为何失去优势?ecDNA困入微核,削弱肿瘤细胞适应度
生物信息学
Bioinfo Guy15 天前
NPJ Precis. Oncol. | 新抗原疫苗不只看肿瘤缩小:用PKPD模型拆解抗原暴露、T细胞扩增和疗效
生物信息学