跳转至

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,防止极端值

面试常问点

  1. 为什么用log变换? 单细胞数据高度右偏(大多数基因低表达,少数极高),log变换使分布更正态,利于下游分析
  2. SCTransform和NormalizeData哪个更好? SCTransform从统计上更严格(负二项模型),但计算慢;NormalizeData简单快速,对大样本更实用
  3. ScaleData是必须的吗? PCA之前必须做,否则高表达基因会主导PC1

速查表

步骤Seurat v5Scanpy
总量归一化NormalizeData()sc.pp.normalize_total()
log变换内含于NormalizeDatasc.pp.log1p()
高变基因FindVariableFeatures()sc.pp.highly_variable_genes()
z-scoreScaleData()sc.pp.scale()
一步法SCTransform()无直接等价(可用scVI)