4. 探索性数据分析:可视化批次效应归纳总结

拿到一批多批次数据,第一件事千万别直接建模。我个人的习惯是:先看分布,再谈调参。批次效应藏得很深,光靠数值统计不一定能揪出来。可视化反而最直观,一眼就能看出哪些批次在「抱团」,哪些样本像「孤儿」一样飘在外面。

4.1 为什么要先可视化?

说白了,可视化能让你在五分钟内回答三个关键问题:

  • 批次之间有没有明显的分离?
  • 有没有某个批次的方差特别大?
  • 技术偏差和生物学变异,哪个主导了数据的主轴?

我记得有一次,一个同事跑完校正算法,只看指标发现P值好看了,结果我帮他画了个PCA图——校正后批次被强行揉在一起,但生物学信号也被压平了。所以他后面做分类一直过拟合。所以说,可视化不是「锦上添花」,是「上路之前先看导航」。

技巧: 每次校正前后都保存一组可视化快照,方便对比效果。我自己会在 pipeline 里自动生成 PNG,命名格式为 batch_effect_{method}_{before|after}.png。

4.2 主流可视化方法对比

不同方法各有侧重。下面这个表格是我自己总结的,帮你快速选工具。

方法 目标 适用场景 注意事项
PCA(主成分分析) 降维 + 看整体簇结构 高维数据初探,批次效应明显时一眼可见 对异常值敏感,需要先做标准化
t-SNE 保留局部邻域结构 发现细分子群,适合展示校正后效果 随机性大,不能跨运行比较;困惑度参数需要调
UMAP 平衡全局与局部结构 大规模数据,比 t-SNE 快很多 超参数 n_neighbors 影响全局/局部权衡
箱线图 + 密度图 检查单个基因/特征的分布偏移 小规模特征集,或验证某个可疑基因 只能展示一维,多特征需要堆叠
踩坑回忆: 我曾经用 t-SNE 直接做批次校正前的探索,结果因为 perplexity 设太小,把同批次内部的随机噪声放大了,误以为批次效应很严重。后来改用 UMAP 对比才纠正过来。建议:先用 PCA 打底,再用 UMAP 或 t-SNE 当辅助。

4.3 实操:用 PCA 快速捕捉批次效应

下面这组代码是我实际项目中用的模板。假设数据是 X(样本×特征),batch_labels 是批次标签。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

def plot_pca_batch(X, batch_labels, title="PCA - Batch Effect"):
    # 标准化:每个特征零均值单位方差
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X)
    
    # 降维到2D
    pca = PCA(n_components=2)
    X_pca = pca.fit_transform(X_scaled)
    
    # 计算解释方差比例
    explained = pca.explained_variance_ratio_
    
    # 绘图
    plt.figure(figsize=(8, 6))
    unique_batches = np.unique(batch_labels)
    colors = plt.cm.tab10(np.linspace(0, 1, len(unique_batches)))
    
    for i, batch in enumerate(unique_batches):
        mask = batch_labels == batch
        plt.scatter(X_pca[mask, 0], X_pca[mask, 1],
                    c=[colors[i]], label=f'Batch {batch}',
                    alpha=0.7, edgecolors='k', linewidth=0.5)
    plt.xlabel(f'PC1 ({explained[0]:.1f}%)')
    plt.ylabel(f'PC2 ({explained[1]:.1f}%)')
    plt.title(title)
    plt.legend()
    plt.grid(True, linestyle='--', alpha=0.4)
    plt.tight_layout()
    plt.show()

# 使用示例
# plot_pca_batch(X, batch_labels)

运行后,如果不同颜色的点各自聚成一团,那就说明批次效应很明显。如果所有颜色混在一起,恭喜你,数据本身一致性不错。

4.4 可视化流程总结

下面这张图是我梳理的标准操作流程,每次做批次效应分析时都照着走一遍。

批次效应可视化探索工作流 原始数据 标准化 / 归一化 多维降维:PCA / UMAP / t-SNE 存在批次分离? 需要进行批次校正 数据可直接使用 可视化报告 + 后续建模 建议:每次校正前后都重复此流程,用同样的可视化参数保证对比公平

你想想看,如果没有这个流程,你可能会跳过标准化步骤——那PCA的结果就会被方差最大的基因带偏,批次效应反而被夸大了。标准化虽然简单,但这一步我见过无数次翻车。

4.5 深入:一维分布检查

降维图能看到整体趋势,但有时候你会怀疑某个基因的表达量在批次间整体偏移了。这时候我习惯画个并列箱线图。

import seaborn as sns

def plot_gene_batch_boxplot(X, batch_labels, gene_idx, gene_name):
    df = pd.DataFrame({
        'expression': X[:, gene_idx],
        'batch': batch_labels
    })
    plt.figure(figsize=(8, 4))
    sns.boxplot(x='batch', y='expression', data=df, palette='Set2')
    plt.title(f'Gene {gene_name} expression across batches')
    plt.tight_layout()
    plt.show()

有一次,我遇到一个基因在批次2里整体比批次1高了0.5个log倍,但校正算法没把它拉回来,导致后续差异分析出现了假阳性。箱线图一出,立马发现——所以要结合多维和一维,老司机都懂。

4.6 什么时候该相信可视化?

可视化也不是万能的。我个人经验里,有几个场景要特别小心:

  • 样本量太小(每批次少于5个):PCA会过度拟合,点之间距离没有统计意义。
  • 存在离群样本:一个离群点可能把两个批次「拉」到一起,造成批次效应被掩盖。
  • 维度爆炸(特征数远大于样本数):先做特征筛选或使用正则化PCA(比如scikit-learn的PCA默认不做奇异值截断,要注意)。

总结一句话: 可视化是你的眼睛,但别只用一只眼。多角度、多方法交叉验证,再决定要不要上校正算法。

好了,这一章的内容就是这些。接下来你可以用上面的代码和流程,在你自己数据上试试。记得先跑一遍标准化,再画PCA,看看批次之间是不是「井水不犯河水」。如果是,那就省事了;不是的话,后面的校正章节会帮你解决。