4. 批次效应检测与可视化
多批次的 ADMET 数据放一起,最怕什么?
不是数据量不够,也不是特征维度高。我最怕的是——批次效应。说白了,就是不同批次的数据,因为实验条件、仪器、操作人员的差异,产生了非生物学的系统性偏差。你想想看,如果这个偏差没被发现,模型学到的可能就是"哪个批次"而不是"哪个化合物有活性"。
4.1 批次效应从哪来
我刚开始做 ADMET 整合时,觉得把几个批次的数据直接拼接就完事了。结果模型在验证集上性能惨不忍睹。后来一查,是批次效应在作祟。
常见的来源有这些:
- 仪器漂移:同一台仪器不同时间段的信号会偏移
- 试剂批次差异:不同批次的试剂活性可能不同
- 操作人员:不同人做实验,手法总有差异
- 环境变化:温度、湿度对某些 ADMET 检测很敏感
- 数据预处理方式:归一化参数、基线校正方法不同
4.2 检测方法一览
检测批次效应,我习惯分成两类来看:无监督和有监督。下面这个表格对比了常用方法:
| 方法 | 类型 | 核心原理 | 适用场景 |
|---|---|---|---|
| PCA | 无监督 | 方差最大化投影 | 快速初筛,高维数据 |
| t-SNE | 无监督 | 概率分布匹配 | 复杂非线性结构 |
| UMAP | 无监督 | 流形学习 | 大规模数据,速度快 |
| 层次聚类 | 无监督 | 距离矩阵 + 链接 | 样本量较小时 |
| ANOVA | 有监督 | 方差分析 | 单变量,已知批次标签 |
| RLE (相对表达) | 半监督 | 中位数对齐偏差 | 归一化效果评估 |
我个人习惯是:先用 PCA 快速扫一遍。如果不同批次在主成分空间里明显分开,那批次效应基本实锤了。
下面这张图概括了批次效应检测与可视化的核心流程:
4.3 可视化诊断细节
光看数字不够直观。我习惯把检测结果画出来,一眼就能看出问题。
4.3.1 PCA 散点图
最经典的做法。用前两个主成分做散点图,按批次着色。如果不同批次各自抱团,那就是有批次效应。
4.3.2 热图
看样本间的距离矩阵。批次内部的样本距离近,批次之间距离远——热图上会形成明显的块状结构。
4.3.3 箱线图 / RLE 图
RLE(Relative Log Expression)图是我觉得最直观的方法之一。它看每个样本的表达值偏离整体中位数的程度。如果某个批次的样本整体偏离,那就有问题。
核心经验:RLE 图中,理想状态下所有箱线应该以 0 为中心对称分布。如果某个批次的箱子整体上移或下移,说明该批次存在系统性偏差。
4.4 量化评估指标
可视化靠肉眼,但最终做决策还是得看数字。我常用的指标有三个:
| 指标 | 全称 | 范围 | 说明 |
|---|---|---|---|
| ASW | Average Silhouette Width | [-1, 1] | 按批次标签计算,越接近 0 说明批次混合得越好 |
| PCR | Principal Component Regression | [0, 1] | 用主成分预测批次标签,R² 越高说明批次效应越强 |
| 批次熵 | Batch Entropy | [0, ∞) | 衡量批次混合程度,值越大混合越均匀 |
4.5 动手实践:用 Python 检测批次效应
说这么多,不如跑一段代码。下面是我常用的检测流程:
import pandas as pd
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
# 模拟三批 ADMET 数据
np.random.seed(42)
n_per_batch = 60
n_features = 12
batch1 = np.random.normal(0, 0.8, (n_per_batch, n_features))
batch2 = np.random.normal(1.2, 0.9, (n_per_batch, n_features))
batch3 = np.random.normal(0.5, 1.1, (n_per_batch, n_features))
X = np.vstack([batch1, batch2, batch3])
batches = ['Batch-A']*n_per_batch + ['Batch-B']*n_per_batch + ['Batch-C']*n_per_batch
# 标准化 → PCA
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(f'PC1 方差占比: {pca.explained_variance_ratio_[0]:.3f}')
print(f'PC2 方差占比: {pca.explained_variance_ratio_[1]:.3f}')
# 可视化
plt.figure(figsize=(8, 6))
colors = {'Batch-A': '#3498db', 'Batch-B': '#e74c3c', 'Batch-C': '#2ecc71'}
for b in ['Batch-A', 'Batch-B', 'Batch-C']:
idx = [i for i, v in enumerate(batches) if v == b]
plt.scatter(X_pca[idx, 0], X_pca[idx, 1],
c=colors[b], label=b, alpha=0.7, edgecolors='w', s=50)
plt.xlabel('PC1 ({:.1f}%)'.format(pca.explained_variance_ratio_[0]*100))
plt.ylabel('PC2 ({:.1f}%)'.format(pca.explained_variance_ratio_[1]*100))
plt.title('PCA 批次效应检测')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
运行这段代码,你会看到 Batch-B 明显向右偏移。这说明批次效应存在,需要进一步处理。
4.5.1 计算 ASW 指标
from sklearn.metrics import silhouette_score
# 用所有主成分计算轮廓系数
pca_full = PCA(n_components=min(X.shape[0], X.shape[1]))
X_pca_full = pca_full.fit_transform(X_scaled)
asw = silhouette_score(X_pca_full, batches)
print(f'ASW (按批次): {asw:.3f}')
if asw > 0.3:
print('⚠️ 批次效应明显,建议进行矫正')
else:
print('✅ 批次效应在可接受范围')
4.6 避坑指南
我曾经在一个项目中,用 PCA 看到三个批次完全重叠,就以为没有批次效应。结果模型上线后,预测结果按批次分层严重。后来才发现——PCA 对非线性批次效应不敏感。
- 不要只依赖一种方法。PCA + t-SNE + 统计检验,至少两种互相印证。
- 不要只看可视化,不看量化指标。肉眼容易骗自己,数字不会。
- 注意样本量差异。如果某个批次样本特别少,PCA 可能把它"吞"掉,看起来像是混合了,其实只是被淹没了。
- 批次标签要准确。我曾经遇到过批次标签搞混的情况,结果检测出来"批次效应"其实是错误标签导致的。
总结一句话:批次效应检测不是走形式,而是为后续的矫正或整合策略提供依据。检测做扎实了,后面的建模才能站得住脚。