382_单细胞时间序列分析
一句话说明
单细胞时间序列分析就是研究"细胞如何从一个状态变化到另一个状态",比如干细胞分化过程,通过RNA速度和拟时序推断细胞的"成熟程度"。
核心知识点
要点1:两类主要问题
- 拟时序分析(Pseudotime):用静态快照数据推断细胞"分化时间线"(细胞不同步,所以快照里有各阶段细胞)
- RNA速度(RNA Velocity):利用未剪接(unspliced)和剪接(spliced)mRNA的比例推断细胞状态的"变化方向和速度"
要点2:主要工具对比
| 工具 | 方法 | 特点 |
|---|
| Monocle3 | DDRTree 降维轨迹 | 经典,可处理复杂分叉 |
| Slingshot | 平滑样条曲线 | 简单,与Seurat无缝 |
| scVelo | RNA 速度(ODE模型) | 揭示动力学方向 |
| PAGA | 图抽象 + 轨迹 | 适合大数据集 |
| Palantir | 随机游走轨迹 | 细胞命运概率 |
要点3:RNA 速度原理
- 每个基因有:未剪接mRNA(nascent) 和 剪接mRNA(mature)
- 如果未剪接量 >> 剪接量:基因"正在被激活"(上调中)
- 如果未剪接量 << 剪接量:基因"正在被关闭"(下调中)
- 结合多基因的速度向量,推断细胞在状态空间中的运动方向
要点4:Monocle3 核心步骤
- 输入预处理好的 Seurat 对象
learn_graph():学习细胞轨迹图(主图)order_cells():手动指定根节点(起始细胞状态)- 提取拟时序值,可视化沿轨迹的基因表达变化
要点5:注意事项
- 拟时序≠真实时间,它只是"顺序"不是"小时数"
- 轨迹分析对细胞类型注释质量很敏感
- RNA速度需要 STARsolo 或 Alevin-fry 同时统计spliced/unspliced reads
实战代码
Slingshot 拟时序(R,与Seurat配合)
library(Seurat) # 单细胞分析
library(slingshot) # 拟时序分析
library(SingleCellExperiment) # SCE格式
library(ggplot2)
# 将 Seurat 对象转换为 SingleCellExperiment 格式
sce <- as.SingleCellExperiment(seurat_obj)
# 提取 UMAP 坐标作为降维结果
reducedDims(sce)$UMAP <- Embeddings(seurat_obj, "umap")
# 运行 Slingshot(指定起始细胞类型)
sce <- slingshot(
sce,
clusterLabels = "cell_type", # 细胞类型标签列
reducedDim = "UMAP", # 使用 UMAP 坐标拟合轨迹
start.clus = "Stem_cell", # 起始细胞类型(祖细胞/干细胞)
end.clus = c("Mature_cell_A", "Mature_cell_B") # 终点(可选)
)
# 提取拟时序值
pseudotime_matrix <- slingPseudotime(sce) # 每条轨迹一列
print(head(pseudotime_matrix))
# 取每个细胞的最小拟时序(如果有多条轨迹,取主轨迹时序)
seurat_obj$Pseudotime <- apply(pseudotime_matrix, 1, function(x) {
x[!is.na(x)][which.min(abs(x[!is.na(x)]))] # 取最近的非NA值
})
# 可视化:UMAP + 拟时序着色
FeaturePlot(
seurat_obj,
features = "Pseudotime",
reduction = "umap",
order = TRUE
) + scale_color_viridis_c(option = "B") + # 黄-橙-紫配色
ggtitle("Slingshot 拟时序分析")
# 分析沿拟时序的基因表达变化
# 找随拟时序显著变化的基因(用 tradeSeq 包)
library(tradeSeq)
# 过滤掉NA(未处于轨迹上的细胞)
keep_cells <- !is.na(seurat_obj$Pseudotime)
seurat_sub <- seurat_obj[, keep_cells]
# 拟合 GAM(广义加法模型)
sce_sub <- as.SingleCellExperiment(seurat_sub)
sce_sub <- fitGAM(
counts = assay(sce_sub, "counts"), # 使用raw counts
pseudotime = data.frame(
Lineage1 = seurat_sub$Pseudotime
),
nknots = 6 # 样条曲线节点数
)
# 检验哪些基因随时间显著变化
at_test <- associationTest(sce_sub) # 关联检验
sig_genes <- at_test[at_test$pvalue < 0.05, ]
sig_genes <- sig_genes[order(sig_genes$waldStat, decreasing = TRUE), ]
cat("随拟时序显著变化的基因数:", nrow(sig_genes), "\n")
scVelo RNA 速度分析(Python)
import scvelo as scv # RNA速度分析(pip install scvelo)
import scanpy as sc
import pandas as pd
import matplotlib.pyplot as plt
# 设置绘图参数
scv.settings.verbosity = 2 # 显示关键信息
scv.settings.presenter_view = True
# ============================================================
# 前置:需要 STARsolo/Alevin 输出的 spliced + unspliced counts
# ============================================================
# 读取 velocyto 输出的 loom 文件
loom_file = "sample.loom" # Alevin-fry 或 STARsolo 输出
adata = scv.read_loom(loom_file)
# 或者读取预计算好的 h5ad(已包含 spliced/unspliced 层)
adata = sc.read_h5ad("rna_velocity_data.h5ad")
# 检查必要的层是否存在
print(adata.layers.keys()) # 应该包含 'spliced' 和 'unspliced'
# 步骤1:预处理(过滤、归一化)
scv.pp.filter_and_normalize(
adata,
min_shared_counts=20, # 基因在至少20个细胞中有共享reads
n_top_genes=2000 # 保留2000个高变基因
)
# 步骤2:PCA 和 UMAP(如果还没做)
sc.pp.pca(adata, n_comps=50)
sc.pp.neighbors(adata, n_pcs=30)
sc.tl.umap(adata)
# 步骤3:计算 RNA 速度
scv.pp.moments(
adata,
n_pcs=30, # 使用前30个PC计算kNN
n_neighbors=30 # kNN邻居数
)
# 动态模型(推荐,比随机模型更准)
scv.tl.recover_dynamics(adata, n_jobs=4) # 4线程,计算较慢
scv.tl.velocity(adata, mode="dynamical") # 动态模型
# 构建速度图
scv.tl.velocity_graph(adata)
# 步骤4:可视化 RNA 速度
scv.pl.velocity_embedding_stream(
adata,
basis="umap", # 在 UMAP 上展示速度流
color="cell_type", # 按细胞类型着色
save="velocity_stream.pdf", # 保存图片
title="RNA Velocity"
)
# 查看速度置信度(confidence低的细胞轨迹预测不可信)
scv.tl.velocity_confidence(adata)
scv.pl.scatter(
adata,
c="velocity_confidence", # 用置信度着色
cmap="Greens",
perc=[5, 95] # 显示5%-95%范围(去极端值)
)
# 步骤5:细胞命运预测(latent time)
scv.tl.latent_time(adata)
scv.pl.scatter(
adata,
color="latent_time", # 潜在时间(类似拟时序)
color_map="gnuplot",
size=80,
title="Latent Time"
)
面试常问点
- 拟时序和真实时间有什么区别? 拟时序是基于细胞状态相似性排序,只反映分化顺序,不能换算为真实小时数
- RNA速度为什么需要未剪接reads? 未剪接mRNA代表"新转录",剪接mRNA代表"稳态",两者比值推断基因是上调还是下调趋势
- scVelo动态模型比随机模型好在哪? 动态模型对每个基因单独估计动力学参数(转录、剪接、降解速率),更准确但更慢
速查表
| 工具 | 分析类型 | 语言 | 优势 |
|---|
| Monocle3 | 拟时序 | R | 复杂分叉轨迹 |
| Slingshot | 拟时序 | R | 简单易用,Seurat友好 |
| scVelo | RNA速度 | Python | 揭示动力学方向 |
| PAGA | 轨迹 + 速度 | Python | 大数据集 |
| tradeSeq | 拟时序DEG | R | 沿轨迹的差异基因 |