单细胞分析进阶指南 | 从标准化到聚类的关键步骤解析
1. 单细胞分析标准化:消除技术噪音的关键一步
当你拿到单细胞测序数据时,第一眼看到的原始计数矩阵就像是一堆未经加工的原材料。这些数字不仅包含了真实的生物学信号,还混杂着各种技术噪音。标准化就是要把这些"原材料"加工成可比较的数据形式,让后续分析能够真实反映生物学差异。
我刚开始接触单细胞分析时,常常困惑为什么不能直接用原始计数做分析。后来在实际项目中踩过坑才明白,如果不做标准化,测序深度高的样本会完全主导分析结果。比如一个细胞因为技术原因测到了两倍的reads,它的所有基因表达量看起来都会翻倍,但这与真实的基因调控毫无关系。
在Scanpy中,最常用的标准化方法是CPM(Counts Per Million)的变体:
import scanpy as sc
# 将每个细胞的计数总和标准化为10,000
sc.pp.normalize_total(adata, target_sum=1e4)
# 对数据进行log1p转换(log(x+1))
sc.pp.log1p(adata)
这里有几个关键点需要注意:
- target_sum的选择:10,000是个常用值,相当于把每个细胞的reads数缩放到相同的"总量尺"。但如果你处理的是超高测序深度的数据,可能需要适当调高这个值。
- log转换的重要性:单细胞数据的方差通常会随着均值增大而增大(异方差性)。log转换可以稳定方差,使高表达基因不至于完全主导下游分析。
- 保存原始数据:标准化后的数据会被覆盖,所以最好先把原始数据保存到adata.raw中,这对后续的差异表达分析特别重要。
在实际操作中,我遇到过标准化后某些细胞群仍然特别分散的情况。这时候可能需要考虑更复杂的标准化方法,如SCTransform或基于深度学习的归一化。不过对于大多数新手来说,CPM+log1p的组合已经能解决80%的问题了。
2. 特征选择:从基因海洋中捞出真正的信号
做完标准化后,我们通常会面对一个包含2-3万个基因的数据矩阵。但你知道吗?其实只有其中10-20%的基因真正携带了区分细胞类型的信息。特征选择就是要找出这些"信息量最大"的基因,也就是高变基因(HVG)。
为什么不能直接用所有基因?我举个生活中的例子:假设你要从1000个应聘者中挑选程序员,如果对所有专业(美术、音乐、体育等)的人都用同样的编程题测试,效率会很低。更好的做法是先筛选出计算机相关专业的应聘者,再重点考察他们的编程能力。
在单细胞分析中,Scanpy提供了非常方便的高变基因筛选功能:
# 识别高变基因
sc.pp.highly_variable_genes(
adata,
min_mean=0.0125,
max_mean=3,
min_disp=0.5
)
# 可视化高变基因
sc.pl.highly_variable_genes(adata)
# 只保留高变基因
adata = adata[:, adata.var.highly_variable].copy()
这三个参数控制着筛选的严格程度:
- min_mean/max_mean:定义了基因平均表达量的上下限。太低的可能测不准,太高的可能看不太出差异。
- min_disp:控制离散度(dispersion)的阈值,离散度越高说明基因在不同细胞间的表达差异越大。
我常用的调试技巧是:先跑一遍默认参数,看看高变基因的散点图分布。如果发现高变基因太少(比如不到1000个),可以适当降低min_disp;如果太多(比如超过5000个),可能需要提高min_disp或缩小mean的范围。
记住,特征选择的质量直接影响后续的降维和聚类效果。如果发现聚类结果不理想,不妨回到这一步重新调整参数。
3. 降维的艺术:把高维数据压缩到人类可理解的维度
当我们筛选出2000-3000个高变基因后,数据仍然处于一个人类无法直接理解的高维空间。降维的目的就是把这些信息压缩到2-3维,同时尽可能保留重要的生物学差异。
主成分分析(PCA)是最常用的线性降维方法。它就像是用一个特殊的相机,从最能显示差异的角度给数据拍照。在Scanpy中运行PCA非常简单:
# 数据缩放(零均值,单位方差)
sc.pp.scale(adata, max_value=10)
# 运行PCA
sc.tl.pca(adata, svd_solver='arpack')
# 查看方差解释比例
sc.pl.pca_variance_ratio(adata, log=True)
这里有几个实用技巧:
- max_value=10:这个参数可以防止极端值对缩放结果产生过大影响。相当于设置了一个上限,超过10的值都会被截断。
- svd_solver的选择:'arpack'适合大多数情况,如果数据特别大(>10万细胞),可以考虑使用'randomized'。
- 主成分数的选择:通常看PCA方差解释率的"拐点"(elbow point)。我一般会保留到累计解释率达到70-80%的主成分。
PCA之后,我们通常会使用UMAP或t-SNE进行非线性降维,以便可视化:
# 计算邻域图
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
# 运行UMAP
sc.tl.umap(adata)
# 可视化
sc.pl.umap(adata, color=['n_counts', 'percent_mt'])
UMAP有两个关键参数需要关注:
- n_neighbors:控制局部结构的保留程度。值越小,局部结构越精细;值越大,全局结构越完整。对于异质性强的数据集(如免疫细胞),我通常用较小的值(10-15);对于较均质的数据(如某种肿瘤细胞),可能用较大的值(30-50)。
- n_pcs:指定使用多少个PCA主成分作为输入。一般用前面确定的PC数,通常30-50就够了。
记住,降维可视化只是工具,不同参数会呈现数据的不同侧面。我建议新手多尝试几组参数,对比观察结果。
4. 聚类分析:发现细胞群体的自然分组
聚类是单细胞分析最激动人心的环节——我们终于能看到数据中隐藏的细胞亚群了!Scanpy默认使用Leiden算法,这是对经典Louvain算法的改进版,能产生更稳定的聚类结果。
基础聚类代码非常简单:
# 运行Leiden聚类
sc.tl.leiden(adata, resolution=0.8)
# 可视化
sc.pl.umap(adata, color='leiden', legend_loc='on data')
但魔鬼藏在参数里:
- resolution参数:这是控制聚类细粒度的关键。resolution越小,得到的cluster越少、越大;resolution越大,cluster越多、越小。我常用的调参策略是:从0.5开始,每次增加0.2,直到cluster数量与预期相符。
如何判断聚类质量?我有几个实用checklist:
- 观察UMAP图上cluster的分离程度:好的聚类应该在各cluster间有明显的间隙。
- 检查每个cluster的细胞数量:要警惕出现大量极小cluster(<10个细胞),这可能是过度聚类。
- 查看已知标记基因的表达模式:同一cluster的细胞应该有相似的标记基因表达。
当聚类结果不理想时,可以尝试以下调整:
- 回到特征选择步骤,增加或减少高变基因数量
- 调整降维使用的PC数
- 改变聚类算法(如尝试Louvain或层次聚类)
- 使用不同的resolution值
5. 可视化与初步注释:从聚类到生物学意义
得到聚类结果后,我们需要回答一个关键问题:这些cluster对应什么细胞类型?这时候就需要结合标记基因进行注释。
假设我们分析的是PBMC(外周血单个核细胞)数据,可以这样定义常见免疫细胞的标记基因:
marker_genes = {
'T细胞': ['CD3D', 'CD3E'],
'B细胞': ['CD79A', 'MS4A1'],
'NK细胞': ['NKG7', 'GNLY'],
'单核细胞': ['CD14', 'LYZ'],
'树突状细胞': ['FCER1A', 'CST3']
}
Scanpy提供了多种可视化方式:
# 在UMAP上叠加标记基因表达
sc.pl.umap(adata, color=['leiden'] + marker_genes['T细胞'])
# 点图展示各cluster的标记基因表达模式
sc.pl.dotplot(adata, marker_genes, groupby='leiden')
# 热图展示
sc.pl.heatmap(adata, marker_genes, groupby='leiden')
在实际项目中,我通常会:
- 先用已知标记基因进行初步注释
- 对无法明确注释的cluster,使用sc.tl.rank_genes_groups找出差异表达基因
- 将这些差异基因与数据库(如CellMarker或PanglaoDB)比对
- 必要时使用自动注释工具(如SingleR或scPred)辅助判断
记住,细胞注释是一个迭代过程。随着分析的深入,你可能需要调整聚类resolution,甚至回到前面的标准化步骤重新处理数据。
更多推荐


所有评论(0)