3、ADMET 数据中的批次效应识别:箱线图、变异系数、批次相关性矩阵
好,咱们进入实操环节。上一章聊了批次效应是什么、它怎么来的,这一章直接上手——怎么把它揪出来。你手头可能已经攒了几百个化合物的 ADMET 数据,做了好几批实验。这时你第一反应应该是:这批数据里,有没有“批次烙印”?
我个人习惯,不会一上来就套复杂模型。先用三种简单粗暴的方法扫一遍:箱线图看分布、变异系数看波动、相关性矩阵看亲疏。这三个工具组合起来,基本能把批次效应的“鬼影”照出来。下面一个个拆开讲。
3.1 箱线图:让分布“现原形”
箱线图,说白了就是把数据的“五个数”画出来——最小值、下四分位数、中位数、上四分位数、最大值。如果不同批次的箱体位置、箱体宽度差别很大,那八成是有批次效应。
我在项目中遇到过这样的情况:某公司做了一批 CYP 抑制实验,前两批数据的中位数高度重合,第三批的中位数突然跳了 30%。嗯,一看实验记录,第三批用的试剂盒批号不同。箱线图一眼就逮住了它。
怎么用 Python 画?我一般用 seaborn 的 boxplot。来看代码:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 加载你的 ADMET 数据
df = pd.read_csv("admet_data.csv")
# 只看某个关键指标,比如 Caco-2 渗透性
sns.boxplot(data=df, x='batch', y='caco2_permeability')
plt.title('Caco-2 渗透性按批次分布')
plt.show()
画出来以后怎么看关键点?三条线记一下:
- 中位数线:批次间中位数差异 > 20%,就要警惕了。
- 箱体高度(IQR):某个批次箱体异常宽,说明该批实验的变异度失控。
- 离群点:如果某一批离群点扎堆,也是批次效应的信号。
3.2 变异系数:量化“一致性”
箱线图靠眼力,那有没有一个数字能直接告诉我批次内的一致性好不好?有。用变异系数(CV)。
公式很简单:CV = (标准差 / 均值) × 100%。CV 越大,说明该批数据的相对波动越大。不同批次的 CV 如果差距悬殊,那批次效应就跑不掉了。
为什么用 CV 而不用标准差?因为不同指标的尺度不一样。比如 Caco-2 渗透性可能是 1-10 ×10⁻⁶ cm/s,而 LogD 可能是 0-5。直接比标准差没意义,用 CV 就把尺度归一化了。
看代码实现:
def calc_cv_by_batch(df, metric_col, batch_col='batch'):
"""按批次计算变异系数"""
cv_dict = {}
for batch in df[batch_col].unique():
batch_data = df[df[batch_col] == batch][metric_col]
cv = (batch_data.std() / batch_data.mean()) * 100
cv_dict[batch] = round(cv, 2)
return cv_dict
# 计算 Caco-2 渗透性各批次的 CV
cv_result = calc_cv_by_batch(df, 'caco2_permeability')
print(cv_result)
# 输出示例:{'Batch1': 12.34, 'Batch2': 11.87, 'Batch3': 28.56}
你想想看,如果 Batch3 的 CV 是 28.56%,而前两批只有 12% 左右,这到底意味着什么?意味着 Batch3 的数据内部一致性差了将近 2.5 倍。用它去做模型训练,模型对这批数据的泛化能力肯定打折扣。
一般来说,CV 的判定标准可以这样参考:
| CV 范围 | 一致性等级 | 建议动作 |
|---|---|---|
| < 10% | 优秀 | 不需要处理 |
| 10% - 20% | 良好 | 留意观察,暂不处理 |
| 20% - 30% | 临界 | 建议排查实验条件差异 |
| > 30% | 差 | 必须进行批次校正或剔除异常批次 |
3.3 批次相关性矩阵:看清“亲疏关系”
箱线图看分布,CV 看波动,那怎么判断不同批次的样本在 ADMET 空间里到底“像不像”?相关性矩阵最直观。
我会把每个批次的数据当成一个“整体”,计算批次之间的皮尔逊相关系数。如果同一化合物在不同批次之间的 ADMET 剖面高度相关(比如 r > 0.9),说明批次间一致性不错。如果相关系数只有 0.6、0.7,那就说明存在明显的批次效应。
代码写起来也不复杂:
import numpy as np
# 按批次和化合物 ID 重新整理数据
pivot_df = df.pivot_table(index='compound_id',
columns='batch',
values='caco2_permeability')
# 计算批次间相关性矩阵
corr_matrix = pivot_df.corr(method='pearson')
print(corr_matrix)
# 用热图可视化
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', vmin=0.5, vmax=1.0)
plt.title('批次间相关性矩阵')
plt.show()
怎么读这个矩阵?我一般按三步走:
- 看对角线:必须都是 1.0(同一批跟自己完全相关)。
- 看非对角线:值越高越好,一般要求 > 0.85。
- 找“离群批次”:比如 Batch1 和 Batch2 的相关系数是 0.95,但 Batch1 和 Batch3 只有 0.72。那 Batch3 就是“有问题”的那一个。
3.4 三种方法联用:三步判定法
好,现在你有三个工具了。不要一个一个孤立地用,要串联起来形成一个快速判定流程。我总结了一个“三步走”套路:
下面这张图把流程串起来,你可以对着它用:
你拿着这张图,对着数据跑一遍流程:
- 箱线图发现中位数差异大 → 进入第 2 步
- CV 也偏高(>20%)→ 进入第 3 步
- 相关性矩阵确认(r < 0.85)→ 判定批次效应存在
好了,三种方法讲完了。方法论有了,下一件事就是怎么从数据层面去“修正”这些效应,但那是后面章节的事了。先安心把这三种识别工具用起来,你手头的数据质量就能有一个清晰的诊断。