跳转至

747. 单细胞批次整合Harmony/scVI

一句话概述:将来自不同实验批次、不同平台、不同样本的单细胞数据"对齐"到一起分析——就像把不同摄影师用不同相机拍的照片调成统一的色调,让你能公平地比较照片内容。


核心知识点速查表

工具方法类型速度适用场景关键特点
Harmony迭代软聚类极快简单批次效应在PCA空间操作
scVI变分自编码器中等复杂批次+生物差异深度学习,保留生物学
Scanorama全景拼接多数据集合并类似图像拼接
BBKNN批次平衡KNN温和批次效应修改邻居图
MNN互相最近邻中等小规模数据经典方法
CCA典范相关分析中等Seurat生态Seurat默认

一、原理(白话版)

1.1 什么是批次效应?

做单细胞实验时,即使是同一个样本: - 不同天做的实验 → 试剂批次不同 → 表达量有系统偏差 - 不同平台测的(10x vs Drop-seq) → 基因检出率不同 - 不同实验室做的 → 操作差异 - 不同样本(病人1 vs 病人2) → 个体差异 + 真实生物学差异

问题:在UMAP上,细胞按"批次"聚在一起,而不是按"细胞类型"。

1.2 整合的核心挑战

要去掉的:技术差异(批次效应)
  → 不同天实验导致的系统偏差
  → 不同平台的测序深度差异

要保留的:生物学差异(真实信号)
  → 疾病vs健康的基因表达差异
  → 不同组织的细胞类型差异

难点:这两种差异混在一起!
  过度校正 → 抹掉了真实的生物学差异
  校正不足 → 批次效应还在

二、Harmony

2.1 Harmony原理

Harmony的核心思想:
1. 先做PCA降维
2. 在PCA空间中,找到批次混合不好的地方
3. 迭代地"推"细胞,让每个聚类里各批次的比例均衡
4. 不改变原始表达矩阵,只修正PCA坐标

类比:
  不同班级(批次)的学生混坐在教室里
  Harmony就是"调座位",让每个小组(聚类)里每班人数差不多
  但不改变学生的成绩(原始表达)

2.2 Python实现

# ===== Python Harmony =====
import scanpy as sc  # 导入scanpy
import harmonypy  # 导入harmony
import matplotlib.pyplot as plt

# 读取多批次数据(已合并但未整合的)
adata = sc.read_h5ad("merged_data.h5ad")  # 读取合并数据
# adata.obs["batch"]列包含批次信息

# 标准预处理
sc.pp.normalize_total(adata, target_sum=1e4)  # 归一化
sc.pp.log1p(adata)  # log转换
sc.pp.highly_variable_genes(adata, n_top_genes=3000,
                             batch_key="batch")  # 按批次找高变基因
adata = adata[:, adata.var.highly_variable]  # 只保留高变基因
sc.pp.scale(adata, max_value=10)  # 标准化
sc.tl.pca(adata, n_comps=50)  # PCA降维

# ===== 运行Harmony =====
sc.external.pp.harmony_integrate(
    adata,
    key="batch",  # 批次变量名
    basis="X_pca",  # 在PCA空间上操作
    adjusted_basis="X_pca_harmony",  # 输出的校正后PCA
    max_iter_harmony=20  # 最大迭代次数
)

# 用校正后的PCA做下游分析
sc.pp.neighbors(adata, use_rep="X_pca_harmony")  # 用harmony后的PCA
sc.tl.umap(adata)  # UMAP降维
sc.tl.leiden(adata, resolution=0.5)  # Leiden聚类

# 对比整合前后
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# 整合前(用原始PCA)
sc.pp.neighbors(adata, use_rep="X_pca", key_added="neighbors_raw")
sc.tl.umap(adata, neighbors_key="neighbors_raw")
sc.pl.umap(adata, color="batch", title="Before Harmony",
           ax=axes[0], show=False)

# 整合后(用harmony PCA)
sc.pp.neighbors(adata, use_rep="X_pca_harmony", key_added="neighbors_harmony")
sc.tl.umap(adata, neighbors_key="neighbors_harmony")
sc.pl.umap(adata, color="batch", title="After Harmony",
           ax=axes[1], show=False)

plt.tight_layout()
plt.savefig("harmony_comparison.png", dpi=300)
plt.show()

2.3 R/Seurat实现

# ===== R/Seurat中使用Harmony =====
library(Seurat)
library(harmony)

# 读取并合并多个样本的Seurat对象
seurat_list <- list(
  sample1 = readRDS("sample1.rds"),
  sample2 = readRDS("sample2.rds"),
  sample3 = readRDS("sample3.rds")
)

# 合并
merged <- merge(seurat_list[[1]], seurat_list[-1],
                add.cell.ids=names(seurat_list))

# 标准预处理
merged <- NormalizeData(merged)
merged <- FindVariableFeatures(merged)
merged <- ScaleData(merged)
merged <- RunPCA(merged, npcs=50)

# 运行Harmony
merged <- RunHarmony(
  merged,
  group.by.vars = "batch",  # 批次变量
  reduction = "pca",        # 输入降维
  assay.use = "RNA",        # 使用的assay
  plot_convergence = TRUE   # 显示收敛曲线
)

# 用Harmony结果做下游分析
merged <- FindNeighbors(merged, reduction="harmony", dims=1:30)
merged <- FindClusters(merged, resolution=0.5)
merged <- RunUMAP(merged, reduction="harmony", dims=1:30)

# 可视化
DimPlot(merged, group.by="batch", label=FALSE)
ggsave("harmony_umap.png", width=10, height=8, dpi=300)

三、scVI

3.1 scVI原理

scVI使用变分自编码器(VAE):
1. 编码器:将高维表达数据压缩到低维潜在空间
2. 在潜在空间中,显式建模"批次效应"
3. 解码器:从潜在空间重建表达数据
4. 批次作为条件变量,在潜在空间中"消除"批次效应

优势:
  - 直接建模count数据(负二项分布)
  - 可以处理复杂的批次效应
  - 更好地保留生物学差异
  - 可以做差异表达、去卷积等下游分析

3.2 scVI实现

# ===== scVI分析流程 =====
import scvi  # 导入scvi-tools
import scanpy as sc

# 读取数据
adata = sc.read_h5ad("merged_data.h5ad")

# 预处理(scVI需要原始counts)
# 确保adata.X是原始counts或adata.layers["counts"]有counts
sc.pp.highly_variable_genes(
    adata,
    n_top_genes=3000,
    batch_key="batch",  # 按批次计算高变基因
    flavor="seurat_v3",  # 使用Seurat v3方法
    layer="counts"  # 从counts层计算
)

# 设置scVI模型
scvi.model.SCVI.setup_anndata(
    adata,
    layer="counts",  # 使用原始counts
    batch_key="batch",  # 批次变量
    categorical_covariate_keys=["sample"],  # 额外的分类协变量
    continuous_covariate_keys=["percent_mt"]  # 连续协变量(如线粒体比例)
)

# 创建并训练模型
model = scvi.model.SCVI(
    adata,
    n_latent=30,  # 潜在空间维度
    n_layers=2,   # 神经网络层数
    gene_likelihood="nb"  # 负二项分布(适合count数据)
)

# 训练
model.train(
    max_epochs=200,  # 最大训练轮数
    early_stopping=True,  # 早停
    train_size=0.9  # 90%训练,10%验证
)

# 查看训练历史
model.history["elbo_train"].plot()  # 绘制训练损失曲线

# 获取整合后的潜在表示
adata.obsm["X_scVI"] = model.get_latent_representation()

# 用scVI表示做下游分析
sc.pp.neighbors(adata, use_rep="X_scVI")
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)

# 可视化
sc.pl.umap(adata, color=["batch", "cell_type"], ncols=2)

# 保存模型(以后可以加载继续分析)
model.save("scvi_model/")

四、整合质量评估

# ===== 定量评估整合效果 =====
import scib  # 导入scib(单细胞整合基准测试工具)

# 安装:pip install scib

# 计算整合指标
# 1. 批次混合指标(高=整合好)
ari = scib.metrics.ari(adata, cluster_key="leiden",
                        label_key="cell_type")
nmi = scib.metrics.nmi(adata, cluster_key="leiden",
                        label_key="cell_type")
asw_batch = scib.metrics.silhouette_batch(
    adata, batch_key="batch", label_key="cell_type",
    embed="X_scVI"
)

# 2. 生物学保留指标(高=保留好)
asw_cell = scib.metrics.silhouette(
    adata, label_key="cell_type", embed="X_scVI"
)

print(f"ARI (聚类与真实标签的一致性): {ari:.3f}")
print(f"NMI (归一化互信息): {nmi:.3f}")
print(f"ASW_batch (批次混合): {asw_batch:.3f}")
print(f"ASW_cell (细胞类型分离): {asw_cell:.3f}")

# 理想结果:ASW_batch高(批次混合好)+ ASW_cell高(类型分离好)

五、常见报错与解决

报错信息原因解决方案
Harmony: not converging批次效应太强增加max_iter或降低theta参数
scVI: CUDA out of memoryGPU显存不足减少batch_size或用CPU
scVI: loss is NaN学习率太高降低learning_rate(如1e-4)
Overcorrection真实的生物学差异被消除减小校正强度(Harmony降theta, scVI增n_latent)
Undercorrection批次效应还在增加校正强度或换更强的方法
Different gene sets不同批次的基因不同取交集基因再整合

六、面试高频问题

Q1: Harmony和scVI怎么选?

A: Harmony:快速、简单,适合批次效应轻到中等的数据,在PCA空间操作。scVI:更强大,适合复杂批次效应、多样本、多平台,建模原始counts,但需要GPU且速度慢。一般先试Harmony,不行再用scVI。

Q2: 如何判断是否过度校正?

A: 看校正后已知的生物学差异是否还在。例如:已知的不同组织类型的细胞是否还能分开。如果"健康vs疾病"的差异被整合消除了,说明过度校正。定量可用scib的ASW_cell指标。

Q3: 整合前的关键预处理步骤?

A: ①每个批次独立做质控(过滤低质量细胞);②取所有批次的基因交集;③每个批次独立归一化;④在合并的数据上找高变基因(按批次计算,取并集)。


七、速查表

# ===== 批次整合速查 =====

# Harmony(Python)
sc.external.pp.harmony_integrate(adata, key="batch")
sc.pp.neighbors(adata, use_rep="X_pca_harmony")

# Harmony(R/Seurat)
merged <- RunHarmony(merged, group.by.vars="batch")
merged <- FindNeighbors(merged, reduction="harmony")

# scVI(Python)
scvi.model.SCVI.setup_anndata(adata, layer="counts", batch_key="batch")
model = scvi.model.SCVI(adata, n_latent=30)
model.train(max_epochs=200)
adata.obsm["X_scVI"] = model.get_latent_representation()

# 整合方法选择指南:
# 批次效应轻 → Harmony/BBKNN
# 批次效应重 → scVI/Scanorama
# 速度优先 → Harmony
# 精度优先 → scVI
# Seurat生态 → CCA/rpca

# 评估指标:
# ASW_batch: 高=批次混合好(去批次效应成功)
# ASW_cell: 高=细胞类型分离好(保留了生物学)