4、整合前的必要流程:双细胞检测、质量过滤、标准化方法
数据拿到手,别急着整合。你想想看,如果原始数据里混着双细胞、低质量的空液滴、或者线粒体污染严重,整合后只会得到一堆假信号。
我自己刚做单细胞那会儿,就踩过这个坑。一批Drop-seq数据直接跑整合,结果聚类出来一群“中间态”细胞,后来才发现是没过滤干净的双细胞。从那以后,我把这三个步骤写进了我的固定流程:双细胞检测 → 质量过滤 → 标准化。
本章核心:
- 双细胞检测(Doublet Detection)—— 用算法揪出两个细胞被一个液滴捕获的案例
- 质量过滤(Quality Filtering)—— 按UMI、gene数、线粒体比例切掉坏细胞
- 标准化(Normalization)—— 消除测序深度差异,让不同批次可比
双细胞检测:别让一个“细胞”变成两个谎言
双细胞是什么?说白了就是两个细胞挤进了同一个液滴或微孔,结果测出来的表达谱是两个细胞的混合物。在聚类时,这类假细胞会形成过渡状态,干扰你对细胞类型的判断。
常用的检测方法
- scrublet(Python,适合10x数据,基于模拟双细胞+聚类)
- DoubletFinder(R,依赖Seurat,适合多种平台)
- DoubletDetection(Python,较老但稳定)
我个人习惯用scrublet。它在单样本上跑很快,而且不需要事先知道真正的双细胞比例。下面是一段典型用法:
import scrublet as scr
import scanpy as sc
adata = sc.read_10x_h5("filtered_feature_bc_matrix.h5")
scrub = scr.Scrublet(adata.X)
doublet_scores, predicted_doublets = scrub.scrub_doublets()
adata.obs['doublet_scores'] = doublet_scores
adata.obs['predicted_doublets'] = predicted_doublets
# 过滤双细胞
adata = adata[~adata.obs['predicted_doublets']].copy()
我曾经遇到过一个坑:如果样本里本来就存在密度很高的同类型细胞簇(比如CD8+ T细胞),scrublet可能会把这些真实细胞误判为双细胞。解决办法是调低 expected_doublet_rate 或配合人工检查。
质量过滤:切掉坏数据,让好细胞说话
拿到UMI计数矩阵后,你需要砍掉三类“问题细胞”:
- UMI总数太低 —— 可能是空液滴或死细胞,未捕获到足够RNA
- 检测到的基因数太少 —— 同样提示细胞状态差
- 线粒体基因比例太高 —— 细胞膜破裂,线粒体泄漏,典型损伤标志
阈值怎么定?我一般先画密度图,找拐点。比如:
import matplotlib.pyplot as plt
import numpy as np
sc.pp.calculate_qc_metrics(adata, percent_top=None, log1p=False)
# 画n_genes_by_counts分布
plt.hist(adata.obs['n_genes_by_counts'], bins=100, alpha=0.7)
plt.axvline(x=500, color='red', linestyle='--') # 目测拐点
plt.show()
常见的经验范围(不同平台略有差异):
| 指标 | 10x v3 典型下限 | Drop-seq 典型下限 | 说明 |
|---|---|---|---|
| UMI 总数 | 500 ~ 1000 | 300 ~ 500 | Drop-seq 捕获效率更低 |
| 基因数 | 200 ~ 500 | 150 ~ 300 | 剔除空液滴 |
| 线粒体比例 | < 20% | < 25% | 可放宽但别超过30% |
过滤操作很简单:
# Scanpy 示例
adata.obs['percent_mt'] = adata[:, adata.var['mt']].X.sum(axis=1).A1 / adata.X.sum(axis=1).A1 * 100
adata = adata[adata.obs['n_genes_by_counts'] >= 200, :]
adata = adata[adata.obs['total_counts'] >= 500, :]
adata = adata[adata.obs['percent_mt'] < 20, :]
小技巧:建议分批次(sample)各自画分布图,因为不同样本的测序深度和技术平台差异很大。一刀切阈值反而把好数据切掉了。
标准化方法:让不同批次站在同一个起跑线
标准化是为了消除测序深度差异——说白了,一个细胞被测了5万条UMI,另一个只测了5000条,但不代表那个细胞真的“活性高10倍”。我们需要把深度归一化,才能比较表达模式。
我常用的三种方法,这里列个对比:
| 方法 | 原理 | 适用场景 | 我的评价 |
|---|---|---|---|
| LogNormalize (Seurat) | 总计数归一化 (CPM) → log(1+ x) | 通用,快速,入门首选 | 简单直白,但对高表达基因压得太狠 |
| SCTransform (Seurat) | 正则化负二项回归,残余方差挑选高变基因 | 数据量中等,需要更好的方差稳定 | 效果优于LogNormalize,但慢一些 |
| scanpy's normalize_total | 总计数归一化 (target_sum) → log1p | Scanpy pipeline标配 | 和LogNormalize本质一致,但接口灵活 |
LogNormalize 示例(Seurat):
pbmc <- NormalizeData(pbmc, normalization.method = "LogNormalize", scale.factor = 10000)
SCTransform 示例(Seurat):
pbmc <- SCTransform(pbmc, vars.to.regress = "percent.mt", verbose = FALSE)
scanpy's normalize_total 示例:
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
我个人最推荐:如果你用Seurat,直接上SCTransform——它内部已经整合了标准化和方差稳定化,后续找高变基因都不用单独再做一遍。如果你用Scanpy,normalize_total+log1p就够用,等整合时再用harmony或bbknn,没必要在标准化上花太多心思。
本章小结
记得按顺序执行:先双细胞检测,再质量过滤,最后标准化。顺序错了会引入额外偏差。比如先标准化再过滤,双细胞留下的表达量可能会被放大,影响后续去双细胞的判断。
嗯,这套流程跑下来,你的数据就干净了,可以放心做后续的批次整合和聚类了。我自己每次换新数据集都会重新跑一遍QC图,不偷懒——因为偷懒的代价就是花更多时间去排查奇怪的下游结果。