376_单细胞数据标准化流程
一句话说明
单细胞数据标准化就是"消除每个细胞测序深度不同带来的假差异",让不同细胞之间的基因表达量可以直接比较。
核心知识点
要点1:为什么要标准化
- 不同细胞捕获的mRNA数量差异巨大(有的1000条,有的5000条)
- 不标准化直接比较基因表达,相当于"用不同大小的杯子量水"
- 标准化后才能判断某个基因是"真的高表达"还是"只是这个细胞测得多"
要点2:主流标准化方法
| 方法 | 原理 | 优缺点 | 工具 |
|---|
| CPM标准化 | 总reads数归一化到100万 | 简单,但忽略基因长度 | Seurat, Scanpy |
| SCTransform | 负二项回归去除测序深度效应 | 效果好,计算慢 | Seurat v5 |
| 中位数比率法 | DESeq2归一化思路 | 对批次稳定 | DESeq2 |
| scran pooling | 细胞池化后反推各细胞因子 | 专为scRNA-seq设计 | scran |
| Pearson残差 | 从负二项模型提取残差 | SCTransform v2默认 | Seurat v5 |
要点3:Seurat v5 标准推荐流程
- 步骤1:
NormalizeData()(CPM × 10000,再log变换) - 步骤2:
FindVariableFeatures()(筛选高变基因,默认2000个) - 步骤3:
ScaleData()(z-score标准化,去除线粒体基因比例等混杂) - 高级版:一步用
SCTransform() 替代上面三步
要点4:Scanpy 标准化流程
sc.pp.normalize_total():类似 CPM 归一化sc.pp.log1p():log(x+1) 变换,使分布更对称sc.pp.highly_variable_genes():筛选高变基因
要点5:常见误区
- 对raw count直接log变换:错!要先做总量归一化再log
- ScaleData包含所有基因:计算太慢,建议只scale高变基因
- 批次校正 ≠ 标准化:标准化处理测序深度;批次校正处理实验批次
实战代码
Seurat v5 标准流程
library(Seurat) # v5.5 单细胞分析包
# 加载10x Genomics数据(CellRanger输出目录)
counts <- Read10X(data.dir = "filtered_feature_bc_matrix/")
# 创建 Seurat 对象
seurat_obj <- CreateSeuratObject(
counts = counts,
project = "ScRNA_tutorial",
min.cells = 3, # 基因至少在3个细胞中检测到
min.features = 200 # 细胞至少表达200个基因
)
cat("细胞数:", ncol(seurat_obj), "\n")
# 质控:计算线粒体基因比例
seurat_obj[["percent.mt"]] <- PercentageFeatureSet(
seurat_obj,
pattern = "^MT-" # 人类线粒体基因以MT-开头(小鼠用mt-)
)
# 质控过滤
seurat_obj <- subset(
seurat_obj,
subset = nFeature_RNA > 200 & # 至少200个基因
nFeature_RNA < 6000 & # 不超过6000(可能是双细胞)
percent.mt < 15 # 线粒体比例不超过15%
)
cat("过滤后细胞数:", ncol(seurat_obj), "\n")
# ============================================================
# 方法A:经典三步法(适合理解原理)
# ============================================================
seurat_obj <- NormalizeData(
seurat_obj,
normalization.method = "LogNormalize", # log(CPM/10+1)
scale.factor = 10000 # 归一化到1万
)
seurat_obj <- FindVariableFeatures(
seurat_obj,
selection.method = "vst", # 方差稳定变换法(推荐)
nfeatures = 2000 # 保留2000个高变基因
)
# 查看最高变基因
top10 <- head(VariableFeatures(seurat_obj), 10)
cat("Top10 高变基因:", top10, "\n")
seurat_obj <- ScaleData(
seurat_obj,
vars.to.regress = c("percent.mt", "nCount_RNA") # 回归去除线粒体和测序深度
)
# ============================================================
# 方法B:SCTransform(更推荐,一步搞定)
# ============================================================
seurat_obj <- SCTransform(
seurat_obj,
vars.to.regress = "percent.mt", # 回归去除线粒体比例
verbose = FALSE, # 不显示进度条
vst.flavor = "v2" # 使用v2(Pearson残差,Seurat v5默认)
)
Scanpy 标准化流程(Python)
import scanpy as sc # 单细胞分析Python包
import numpy as np
# 设置随机种子(保证结果可重现)
sc.settings.verbosity = 2 # 显示关键步骤信息
sc.settings.n_jobs = 4 # 多线程
# 读取 10x 数据
adata = sc.read_10x_mtx(
"filtered_feature_bc_matrix/", # CellRanger输出目录
var_names="gene_symbols", # 用基因symbol命名(非Ensembl ID)
cache=True # 缓存加速二次读取
)
adata.var_names_make_unique() # 确保基因名唯一
# 质控
adata.var["mt"] = adata.var_names.str.startswith("MT-") # 标记线粒体基因
sc.pp.calculate_qc_metrics(
adata,
qc_vars=["mt"], # 计算线粒体比例
inplace=True # 结果写回 adata.obs
)
# 质控可视化
sc.pl.violin(
adata,
["n_genes_by_counts", "total_counts", "pct_counts_mt"],
jitter=0.4,
multi_panel=True,
save="_qc_violin.pdf"
)
# 过滤低质量细胞
sc.pp.filter_cells(adata, min_genes=200) # 至少200个基因
sc.pp.filter_genes(adata, min_cells=3) # 基因至少在3个细胞中表达
adata = adata[adata.obs.pct_counts_mt < 15] # 线粒体比例<15%
print(f"过滤后:{adata.n_obs} 细胞, {adata.n_vars} 基因")
# 保存原始 counts(后续DEG分析需要)
adata.raw = adata # 保存未标准化数据到 .raw 层
# 标准化:总量归一化
sc.pp.normalize_total(
adata,
target_sum=1e4 # 归一化到每细胞10000个reads
)
# log1p 变换(log(x+1),消除大值影响)
sc.pp.log1p(adata)
# 筛选高变基因
sc.pp.highly_variable_genes(
adata,
n_top_genes=2000, # 保留2000个高变基因
flavor="seurat_v3", # 使用Seurat v3方法(推荐)
batch_key=None # 如果有批次,填批次列名
)
print(f"高变基因数: {adata.var.highly_variable.sum()}")
# z-score 标准化
sc.pp.scale(adata, max_value=10) # 截断到最大值10,防止极端值
面试常问点
- 为什么用log变换? 单细胞数据高度右偏(大多数基因低表达,少数极高),log变换使分布更正态,利于下游分析
- SCTransform和NormalizeData哪个更好? SCTransform从统计上更严格(负二项模型),但计算慢;NormalizeData简单快速,对大样本更实用
- ScaleData是必须的吗? PCA之前必须做,否则高表达基因会主导PC1
速查表
| 步骤 | Seurat v5 | Scanpy |
|---|
| 总量归一化 | NormalizeData() | sc.pp.normalize_total() |
| log变换 | 内含于NormalizeData | sc.pp.log1p() |
| 高变基因 | FindVariableFeatures() | sc.pp.highly_variable_genes() |
| z-score | ScaleData() | sc.pp.scale() |
| 一步法 | SCTransform() | 无直接等价(可用scVI) |