4. Seurat 与 Scanpy 框架简介:设计哲学、数据结构与核心函数
好,这一章我们聊聊两个工具的内在逻辑。很多人刚接触单细胞分析时,最头疼的不是算法,而是怎么理解这两个框架到底“长什么样”。我当初从 R 转到 Python 做单细胞时,就踩过不少坑。
说白了,Seurat 和 Scanpy 在干同一件事——把混乱的原始测序数据整理成可分析的矩阵,然后降维、聚类、找 marker。但它们的思路完全不同。一个像精装修的别墅,户型固定,拎包入住;另一个像积木仓库,随取随拼,自己画图纸。
4.1 Seurat 的设计哲学:“对象即一切”
Seurat 是 R 语言的老牌工具,由 Satija Lab 维护。它的核心思想是:你所有操作都在一个对象上完成。这个对象叫 SeuratObject,里面嵌套了表达矩阵、降维结果、元数据、邻接图等。
我记得第一次用 NormalizeData 函数时,心想这不就是改一下矩阵吗?后来才发现,它不光改了数据,还在对象内部打了个标签,记录这次标准化用了什么参数。你想想看,这种设计对重复性分析有多友好?
4.1.1 数据结构
SeuratObject 的核心层级如下:
- Assay:主分析层,默认是 RNA。里面包含 counts(原始计数)、data(标准化后)、scale.data(缩放后)三个槽。
- DimReduc:降维结果,如 PCA、UMAP。每个降维对象有 cell embeddings 和 feature loadings。
- Graph:细胞间邻接图,用于聚类和 UMAP。
- meta.data:细胞级注释,像 library、cluster、percent.mito 这些都丢在这。
- misc:杂项,存一些辅助信息。
这里有个坑——Assay 可以有很多个。比如你同时做了 RNA 和 ADT 数据,可以创建两个 Assay。我一度以为所有数据只能塞在 default assay 里,结果跑 CITE-seq 时总报错。
# 创建 SeuratObject 的典型流程
library(Seurat)
counts_matrix <- Read10X("filtered_feature_bc_matrix/")
seurat_obj <- CreateSeuratObject(counts = counts_matrix,
project = "PBMC",
min.cells = 3,
min.features = 200)
# seurat_obj 现在是一个标准的 SeuratObject
# 默认 assay 叫 "RNA"
# 可以通过 seurat_obj[["RNA"]] 访问
4.1.2 核心函数一览
Seurat 的函数命名非常直白:动词 + 对象。常用的有:
| 阶段 | 核心函数 | 作用 |
|---|---|---|
| 质控 | PercentageFeatureSet, subset | 计算指标,过滤细胞 |
| 标准化 | NormalizeData, ScaleData | 对数标准化 + 线性缩放 |
| 降维 | RunPCA, RunUMAP | PCA 和 UMAP |
| 聚类 | FindNeighbors, FindClusters | 建图 + Louvain/Leiden 聚类 |
| 标记基因 | FindAllMarkers, FindMarkers | 差异表达分析 |
| 可视化 | DimPlot, FeaturePlot, VlnPlot | 绘图 |
嗯,这里要注意:ScaleData 默认回归掉细胞周期等变量,但如果你不做回归,记得设 vars.to.regress = NULL。我曾经忘了这个,结果批次效应全被放大了。
4.2 Scanpy 的设计哲学:“管道与显式”
Scanpy 是 Python 生态的代表,由 Theis Lab 开发。它的风格完全是函数式管道:输入一个 AnnData 对象,返回一个新的或原地修改的 AnnData。不玩封装,每一步都看得清清楚楚。
我一开始用 Scanpy 时特别不适应:怎么没有“对象方法”?后来才明白,这种设计让每一步操作都透明,调试起来方便。你想想看,当你想单独跑一下 PCA 看看原始数据,直接 sc.tl.pca(adata) 就完事,不用管什么默认参数。
4.2.1 数据结构:AnnData
AnnData 是 Scanpy 的“通用容器”,它把数据拆成几个清晰的矩阵:
- adata.X:主表达矩阵(cells × genes),默认存储标准化后的数据。
- adata.obs:细胞元数据,类似 DataFrame 行是细胞。
- adata.var:基因元数据,类似 DataFrame 行是基因。
- adata.obsm:细胞的多维空间坐标,如 PCA、UMAP 结果。
- adata.varm:基因的加载矩阵,如 PCA loadings。
- adata.uns:非结构化数据,像聚类颜色、批次信息、通路富集结果等。
- adata.obsp / adata.varp:细胞间或基因间的配对矩阵(如邻接图)。
这个结构的好处是——你不必非要把所有东西塞进一个对象里。比如你想单独分析蛋白质和 RNA,可以创建两个 AnnData,共用 .obs 索引。我在做多模态整合时,这个设计帮了大忙。
# 读取数据到 AnnData
import scanpy as sc
import anndata
adata = sc.read_10x_h5("filtered_feature_bc_matrix.h5")
# 也可以用 sc.read_mtx + 基因名
# adata.var_names 是基因名
# adata.obs_names 是细胞 barcode
print(adata.shape) # (cells, genes)
# 质控指标
adata.var["mt"] = adata.var_names.str.startswith("MT-")
sc.calculate_qc_metrics(adata, qc_vars=["mt"], percent_top=None, log1p=False, inplace=True)
4.2.2 核心函数一览
Scanpy 的函数以 sc.pp.*(预处理)、sc.tl.*(工具)和 sc.pl.*(绘图)分类。
| 阶段 | 核心函数 | 作用 |
|---|---|---|
| 质控 | sc.pp.filter_cells, sc.pp.filter_genes, sc.pp.calculate_qc_metrics | 过滤与指标计算 |
| 标准化 | sc.pp.normalize_total, sc.pp.log1p, sc.pp.scale | 总计数归一、对数化、缩放 |
| 降维 | sc.tl.pca, sc.tl.umap | PCA、UMAP |
| 聚类 | sc.pp.neighbors, sc.tl.leiden | 建 KNN 图 + Leiden 聚类 |
| 标记基因 | sc.tl.rank_genes_groups | Wilcoxon 或 t-test 找差异基因 |
| 可视化 | sc.pl.umap, sc.pl.violin, sc.pl.heatmap | 快速绘图 |
避坑指南:sc.pp.scale 默认 center 但不 scale 到 unit variance?不,它默认 zero_center=True, max_value=10。我一开始没调 max_value,结果一些高表达基因的 z-score 飙到 20 多,UMAP 全扭曲了。后来我加上 max_value=10 才正常。
4.3 核心差异对比
我把两个框架的关键差异列成表格,方便你对比选型。
| 维度 | Seurat | Scanpy |
|---|---|---|
| 语言 | R | Python |
| 设计风格 | 面向对象,所有操作封装在对象内部 | 函数式管道,操作显式作用于 AnnData |
| 默认存储 | counts、data、scale.data 三个槽 | adata.X 一个主矩阵,可切换 |
| 标准化方法 | LogNormalize(默认) | normalize_total + log1p |
| 降维保存 | DimReduc 槽,可存多个 | adata.obsm 字典,键名区分 |
| 图层概念 | Assay 层,支持多层(RNA、ADT、ATAC) | AnnData 本身,可通过多个 AnnData 组合 |
| 邻里图 | Graph 槽 | adata.obsp['connectivities'] |
| 可视化 | ggplot2 风格,更美观,参数多 | matplotlib 风格,快速但需手动调参 |
| 扩展性 | 通过 AddMetaData、AddModuleScore 等函数 | 直接操作 AnnData 的 obs、uns |
4.4 框架知识体系图
下面我用一张 SVG 图梳理 Seurat 和 Scanpy 的核心数据流与函数作用层次。你可以看到,Seurat 的数据嵌套更“厚”,而 Scanpy 的数据平铺更“薄”。
4.5 小结
到此,你应该能感觉到:Seurat 把所有东西打包成一个“大对象”,你直接调用函数就行,不容易漏步骤;Scanpy 则把数据拆成扁平的 AnnData,每一步都暴露在眼前,更适合需要精细控制的高级用户。
我在做实际项目时,通常是这样选的:如果只需要跑标准流程(QC → 降维 → 聚类 → 找 marker),Seurat 上手更快;如果要做自定义算法、整合多组学或对接深度学习模型,Scanpy 更方便。选择没有对错,看你的需求和习惯。