第4章 基因数的含义:检测到的基因数量如何反映细胞质量

好,我们继续往前走。前面聊了UMI是怎么捕获和计数RNA分子的,这节我重点讲另一个关键指标——基因数。说白了,检测到的基因数量就是在一个细胞中实际测到了多少种不同的基因。

你想想看,一个正常哺乳动物细胞大约能表达2万多种基因中的4000到6000种。这个数我记了很多年——因为一旦你的数据里出现了明显偏离这个范围的值,那大概率是样本里有猫腻。

我刚开始做单细胞分析时吃过一个亏。那时候我拿到一批数据,基因数分布看起来特别漂亮,中间高两头低,感觉没有任何问题。结果老板让我手工检查几个极端高值的barcode,一查发现全是双细胞融合的伪迹。嗯,从那以后我再也不只看分布形状了,开始自己手写过滤逻辑。

4000-6000到底怎么来的?

这里需要铺垫一下基础知识。我们先看正常细胞的特征。

一个典型的哺乳动物细胞:

  • 表达的基因种类通常在4000-6000之间(10x Genomics平台常见的结果)
  • 如果用Smart-seq2这类全长测序,这个数会更高,因为覆盖更全
  • 细胞类型越复杂,基因数范围越宽——比如T细胞和神经元就不一样
细胞类型 典型基因数范围(10x) 备注
PBMC淋巴细胞 3000-5000 T细胞、NK细胞普遍偏低
PBMC单核细胞 4000-7000 比淋巴细胞高,活性也强
肝细胞 5000-8000 代谢活跃,基因表达谱更广
神经元 6000-10000 复杂细胞类型,基因数自然高

为什么单核细胞基因数能到7000?因为它的功能更多样,吞噬、抗原呈递、信号转导都需要大量基因参与。你想想看,如果一个单核细胞样本里基因数只有1500,那八成是有问题。

我个人习惯的做法:拿到数据后,先按细胞类型参考值设一个相对宽泛的全局过滤区间(比如1000-8000),然后在每个细胞类型聚类后再做二次精细过滤。这样不会误伤个别特殊细胞。

双细胞:基因数为什么虚高?

双细胞是最常见的污染来源。一个液滴里包裹了两个细胞,很自然的,检测到的基因总数就会接近两个独立细胞的总和。

但这里有个细节你可能没注意过:双细胞的基因数并不是简单的1+1=2。为什么?

因为两个细胞如果来自同一种系,它们的表达谱可能相似度很高,部分共表达的基因只会被计一次。所以双细胞的基因数通常会落在单个细胞的1.3到1.8倍之间,而不是整两倍。

# 举例:假设一个T细胞基因数是3000
# 那么:
# T细胞 + T细胞(同种)→ 约4000-5000
# T细胞 + 单核细胞(异种)→ 约6000-9000
# 后者更容易被识别出来,因为基因数跳得高

我遇到过一个项目,样品始终有一只细胞异常高表达。排查了很久,最后发现是那个单细胞悬液里有细胞团块没完全分散开。细胞团块造成的多细胞效果很隐蔽,基因数分布只略高一点点,但后续聚类完全乱套。

避坑小提示:不要靠基因数一个指标判断双细胞。我建议你结合UMI总数和基因数做二维散点图。双细胞通常会在这两个维度上同时偏高,而且偏离程度一致。如果只有基因数异常高但UMI很普通,可能是RNA降解后的假信号。

空液滴:基因数极低的常见原因

空液滴的问题也很有意思。空液滴本质上是一个barcode结合了一些游离的RNA片段(来自细胞裂解释放的背景RNA),但没有包裹完整的细胞。

空液滴在基因数上的表现:

  • 基因数通常低于500
  • UMI总数也极低,一般不超过1000
  • 另一个特征是——表达谱往往是随机的,不成系统

为什么会随机?因为空液滴里的RNA来自环境中大量细胞的碎片,拼凑出来的基因组合没有任何生物学逻辑。你想想看,一个活细胞哪会同时表达线粒体基因和核糖体基因却几乎不表达其他关键通路基因?但空液滴就能做到。

我记得有一次合作者给我传来数据,说细胞数量明显偏多,而且全是些奇怪的细胞类型。我一查空液滴比例,发现竟然有20%的barcode特征和空液滴一模一样。后来重新设定了阈值,把那些基因数低于800的barcode全部过滤掉,聚类结果才恢复正常。

死亡细胞:基因数也下降但另有特征

死亡细胞和空液滴的区别在于:死亡细胞确实曾是一个完整的细胞,只是它的RNA已经降解得差不多了。

死亡细胞在基因数上的特征:

  • 基因数显著下降——可能从5000掉到1500甚至更低
  • 但是与此同时,线粒体基因占比升高
  • UMI总数下降,但不像空液滴那样少得离谱

这里有个陷阱:神经元本身的基因数就高(6000-10000)。如果某个神经元因为操作过程中的损伤导致基因数掉到3000,这个数值在PBMC的分析流程里可能被误判为正常值。实际上它已经是个需要剔除的死亡细胞了。

我以前建议在过滤时采用多模态阈值:不只看基因数绝对值,还要结合线粒体比例和UMI总数。

# 我常用的过滤条件(参考值)
filter_cells = (
    (adata.obs['n_genes_by_counts'] > 500) &
    (adata.obs['n_genes_by_counts'] < 7000) &
    (adata.obs['pct_counts_mt'] < 20) &
    (adata.obs['total_counts'] > 1000)
)

看到了吗?我把基因数的上下限都设了。上限7000是为了排除双细胞和多细胞,下限500是为了排除空液滴和严重受损的细胞。另外线粒体比例和UMI总数是加固防线。

关键总结:基因数是判断细胞质量的第一道防线,但不是唯一一道。它用来抓出明显异常的barcode,而双细胞和死亡细胞需要与其他指标联合判断。
基因数异常细胞的识别流程 原始细胞悬液 正常活细胞 双细胞/多细胞 基因数:4000-6000 UMI:5000-20000 线粒体比例:<10% 基因数:虚高(1.3-1.8倍) UMI:对应升高 表达谱:两类型混合 综合过滤:基因数+UMI+线粒体 高质量细胞矩阵 空液滴 基因数<500 表达谱随机 死亡细胞 基因数下降 线粒体比例>20%
一个常见误区:很多人以为基因数越高细胞质量就越好。不对!基因数过高往往意味着双细胞或细胞团,基因数过低才表示细胞死亡或空液滴。我们追求的是适中的、符合该细胞类型的基因数。别把极端值当成好东西。

实操建议:阈值怎么设?

我见过很多人把基因数阈值设成一个固定值(比如低于500就删除)。这种做法太死板了。

好的做法是基于数据分布动态设定:

# Python示例:基于3倍MAD(中位数绝对偏差)设定
import numpy as np

gene_counts = adata.obs['n_genes_by_counts']
median = np.median(gene_counts)
mad = np.median(np.abs(gene_counts - median))
lower = median - 3 * mad
upper = median + 3 * mad

print(f"自动阈值范围:{lower:.0f} - {upper:.0f}")

核心思路是:让数据自己决定阈值,而不是拍脑袋定个数。我觉得这样更稳妥。

最终建议:基因数是质控里最直观也最好使的指标。你在做单细胞分析时,第一件事就是看基因数和UMI的分布图。如果你在分布图上看到两个明显的高峰,那通常是空液滴和正常细胞的分离,恭喜你——数据质量不错。如果只有一个大峰而且位置很靠左,那你得考虑是不是样本解离过度或者酶活性有问题了。