2、单细胞转录组数据简介:数据矩阵、表达量、细胞与基因的基本概念

说到单细胞转录组,你可能会想:“这跟普通转录组有啥区别?”其实说白了,普通转录组测的是组织里一堆细胞的平均表达,而单细胞转录组能把每个细胞单独揪出来看。我刚开始接触这个领域时,最大的震撼就是——原来同一个组织里的细胞,基因表达可以差这么多。

2.1 表达矩阵:一张表装下整个实验

单细胞转录组数据的核心是一张二维矩阵。行是基因,列是细胞,每个格子里的数值就是该基因在那个细胞里的表达量。你可以把它想象成一张Excel表——嗯,实际上很多工具也确实把它存成CSV或HDF5格式。

我个人习惯把表达矩阵称为“数字宇宙”,每一列是一个独立的细胞,每一行是一条基因在全部细胞里的转录轨迹。来看看它长什么样:

# 用pandas加载一个小型表达矩阵
import pandas as pd

# 假设我们有一个10个基因、5个细胞的矩阵
matrix = pd.DataFrame({
    'Cell1': [12, 0, 34, 0, 0, 5, 0, 0, 0, 1],
    'Cell2': [0, 0, 29, 0, 0, 0, 0, 0, 2, 0],
    'Cell3': [8, 0, 0, 0, 0, 1, 0, 0, 0, 0],
    'Cell4': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    'Cell5': [15, 0, 40, 0, 1, 8, 0, 0, 0, 0]
}, index=['GeneA', 'GeneB', 'GeneC', 'GeneD', 'GeneE',
           'GeneF', 'GeneG', 'GeneH', 'GeneI', 'GeneJ'])

print(matrix)

你看,矩阵里大部分是0。这在单细胞数据里太正常了——每个细胞只表达几百到几千个基因,大部分基因都不活跃。我记得第一次处理10x Genomics数据时,看到矩阵里99%都是零,还以为数据出错了……后来才知道这叫“稀疏矩阵”,是单细胞数据的常态。

2.2 表达量的含义:从UMI到归一化

表达量不是直接拿测序仪吐出来的原始counts就用的。你想想看,不同细胞捕获的mRNA数量不一样,测序深度也不一样,必须做标准化才能比较。

最常见的表达量单位是UMI(Unique Molecular Identifier)计数。每个mRNA分子都带一个独特的条形码,测序后统计有多少种不同的条形码,就是该基因的UMI count。UMI能有效消除PCR扩增偏差。我曾经犯过一个错误:直接拿原始counts做聚类,结果细胞分组全被测序深度带偏了……嗯,从那以后我每次都会检查UMI分布。

表达量类型 含义 典型范围
Raw counts 直接比对到基因的读段数 0 ~ 几千
UMI counts 绕过PCR偏差的独特分子计数 0 ~ 几百(10x数据)
CPM / TPM 每百万 reads 的表达量 0 ~ 几十
Log-normalized log(CPM+1) 或 log(TPM+1) 0 ~ 10
实用建议:我一般看到原始表达矩阵的第一件事就是打印每列的总counts。如果某个细胞的total counts远低于其他细胞,它可能是空液滴或死细胞,通常我会在后续分析前先过滤掉。

2.3 细胞与基因:两个维度的故事

矩阵的行和列分别代表什么?

  • 细胞(列):每个细胞是一个独立的“个体”,有自己的转录状态。细胞之间可能属于不同细胞类型(T细胞、B细胞、上皮细胞等),也可能处于不同分化阶段。
  • 基因(行):每个基因是一个特征(feature)。单细胞数据里基因数通常超过两万个,但真正有表达信息的往往只有几千个。

在实际分析中,我们会先过滤掉表达量过低的基因(比如在少于3个细胞中表达的基因),也会过滤掉总UMI太少的细胞。这些操作都是为了让矩阵更“干净”。

关键指标:

  • 每个细胞检测到的基因数(n_genes)
  • 每个细胞的总UMI数(n_counts)
  • 线粒体基因占比(percent_mito)—— 过高表示细胞破裂

2.4 核心知识结构:表达矩阵的骨架

下面这张图展示了单细胞转录组数据的基本骨架。你可以看到数据如何从测序仪经过几个步骤变成可分析的矩阵:

单细胞转录组表达矩阵结构 测序仪 原始序列 比对 → 计数 表达矩阵 行:基因 ~20k 列:细胞 ~1k-10k 值:UMI counts 0 34 0 5 0 0 注意 稀疏矩阵:大多数格子为0 需过滤低质量细胞和基因

2.5 实战:用Python快速探索矩阵

光看概念不过瘾,我们动手加载一个真实的表达矩阵玩玩。我这里用Scanpy自带的PBMC数据集做演示:

import scanpy as sc

# 加载内置数据集
adata = sc.datasets.pbmc3k_processed()

# 查看表达矩阵形状
print(adata.X.shape)  # (2638, 1838) → 2638个细胞,1838个基因

# 查看前5个细胞的前5个基因表达
print(adata[:5, :5].X.toarray())

输出类似:

(2638, 1838)
[[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]

全是0?别慌!这是因为Scanpy默认用了稀疏存储,而且我选的前5个基因可能恰好都不在这几个细胞里表达。实际上只要换几个基因就能看到非零值:

# 查看CD3D和CD14的表达
print(adata[:, 'CD3D'].X.toarray()[:10])   # 前10个细胞在CD3D上的表达
print(adata[:, 'CD14'].X.toarray()[:10])
⚠️ 常见坑:千万不要直接用原始counts做PCA或t-SNE!我遇到过有人把counts直接扔给sklearn的PCA,结果第一主成分完全是测序深度。正确的做法是先做library size归一化(比如CPM),再log转换。Scanpy的sc.pp.normalize_total和sc.pp.log1p就是干这个的。

2.6 细胞与基因的元数据

表达矩阵本身只是数值,真正理解数据还得靠元数据:

  • 细胞元数据(obs):每个细胞的相关信息,比如细胞类型标签、实验批次、患者ID、测序深度等。
  • 基因元数据(var):每个基因的信息,比如基因名、染色体位置、是否是线粒体基因等。

在Scanpy的AnnData对象里,这些信息都放在adata.obs和adata.var数据框里。我习惯做完QC后,先把n_genes_by_counts和total_counts画个直方图,快速了解数据质量。

一句话总结:单细胞转录组数据的核心就是一张稀疏的计数矩阵,搭配细胞和基因的元数据。理解了这张矩阵,后面所有分析(降维、聚类、差异表达)才有了落脚点。