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, RunUMAPPCA 和 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.umapPCA、UMAP
聚类sc.pp.neighbors, sc.tl.leiden建 KNN 图 + Leiden 聚类
标记基因sc.tl.rank_genes_groupsWilcoxon 或 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 核心差异对比

我把两个框架的关键差异列成表格,方便你对比选型。

维度SeuratScanpy
语言RPython
设计风格面向对象,所有操作封装在对象内部函数式管道,操作显式作用于 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

我个人建议:如果你刚接触单细胞,且团队用 R,直接上 Seurat,生态完整。如果你团队用 Python,或者需要灵活整合其他 Python 工具(比如 sclc、scvi-tools),选 Scanpy 更顺手。

警告:不要轻易转换两个框架的对象。我曾用 SeuratDisk 把 Seurat 对象转成 h5ad,再导入 Scanpy,结果降维坐标和邻里图全部对不上。两个框架的标准化顺序、图构建方式有差异,跨框架迁移容易引入 bug。

4.4 框架知识体系图

下面我用一张 SVG 图梳理 Seurat 和 Scanpy 的核心数据流与函数作用层次。你可以看到,Seurat 的数据嵌套更“厚”,而 Scanpy 的数据平铺更“薄”。

Seurat vs Scanpy 核心框架对比 Seurat 框架 SeuratObject Assay (RNA) Assay (ADT) DimReduc Graph meta.data misc 核心函数:NormalizeData, RunPCA, FindClusters Scanpy 框架 AnnData .X (主矩阵) .obs / .var .obsm / .varm .obsp / .varp .uns 核心函数:sc.pp.normalize_total, sc.tl.pca, sc.tl.leiden 完全不同

4.5 小结

到此,你应该能感觉到:Seurat 把所有东西打包成一个“大对象”,你直接调用函数就行,不容易漏步骤;Scanpy 则把数据拆成扁平的 AnnData,每一步都暴露在眼前,更适合需要精细控制的高级用户。

我在做实际项目时,通常是这样选的:如果只需要跑标准流程(QC → 降维 → 聚类 → 找 marker),Seurat 上手更快;如果要做自定义算法、整合多组学或对接深度学习模型,Scanpy 更方便。选择没有对错,看你的需求和习惯。