ADMET 数据预处理:缺失值处理与标准化

拿到 ADMET 原始数据,第一件事别急着跑模型。我见过太多人上来就调包,结果模型跑完才发现数据里一堆 NaN,或者量级差了一千倍。说白了,数据预处理决定了下限,ComBat 校正再厉害,也填不了缺失的坑。这一章我们就专门聊怎么把数据收拾干净。

先看看数据长什么样

假设你已经从实验或公开数据库拿到了 ADMET 数据,通常是一个矩阵:行是化合物,列是不同 ADMET 指标(比如 LogD、溶解度、Caco‑2 渗透性、血浆蛋白结合率……)。批次信息藏在另一列里。我习惯第一步就用 pandas 扫一眼统计摘要:

import pandas as pd
df = pd.read_csv("admet_raw.csv")
print(df.info())
print(df.describe())

从 info() 里你能立马看出每列有多少非空值。如果某列缺失超过一半,嗯,这列要不要保留就得琢磨一下了。我个人经验是,超过 60% 缺失的指标,校正后也不可信,干脆删掉。

缺失值处理:三种常用思路

缺失值处理没有银弹。你想想看,ADMET 数据往往是少量化合物、多指标,随便删行可能损失宝贵样本。我这里整理了一个表格,帮你对号入座:

缺失场景 推荐方法 注意事项
随机缺失(总体 < 5%) 直接删除该行 样本量充足时可用
某指标缺失较多(10% - 30%) 简单插补(均值/中位数/众数) 需按批次分组插补,避免引入批次偏差
缺失有规律(比如某批次全部缺失) 模型插补(KNN / 回归) 交叉验证评估插补质量
我的小建议:对于 ADMET 数据,中位数插补比均值更稳健。因为个别化合物毒性异常值会拉偏均值,我踩过这个坑。

实战:按批次分组插补

下面这段代码演示了按批次分组,用中位数填充缺失值。注意我保留了一个标记列,方便后续追踪哪些值被插补过:

df["missing_flag"] = 0

for batch in df["batch"].unique():
    mask = df["batch"] == batch
    for col in admet_cols:
        median_val = df.loc[mask, col].median()
        missing_idx = df.loc[mask & df[col].isna()].index
        df.loc[missing_idx, col] = median_val
        df.loc[missing_idx, "missing_flag"] = 1
我曾经犯过的错:直接对整个数据框做中位数插补,结果把不同批次的差异抹平了。ComBat 校正时批次效应被低估,校正后的数据反而更差。所以务必记住:插补也要分批次。

标准化:统一尺度,拯救收敛

ADMET 指标单位五花八门:LogD 在 -2 到 6 之间,溶解度可能是 μg/mL(几十到几千),Caco‑2 渗透性又是 10⁻⁶ cm/s。假如不标准化,ComBat 模型里的迭代过程会被量级大的指标主导,小指标校正不动。

标准化的核心目的是让每个指标近似均值为 0、标准差为 1。我自己更喜欢用 RobustScaler,因为 ADMET 数据常有离群点——有些化合物溶解度特别低或者特别高,Z‑score 会被带跑偏。

三种标准化方法对比

  • StandardScaler: (x - μ) / σ — 对离群点敏感
  • RobustScaler: (x - Q2) / (Q3 - Q1) — 用中位数和四分位距,抗离群
  • MinMaxScaler: (x - min) / (max - min) — 压缩到 [0,1],但离群点会压缩正常值

在 ComBat 校正前,标准化要基于所有批次合并后的统计量来做。为什么?因为我们要保留批次间差异的原始结构,让 ComBat 去识别和校正。如果每个批次单独标准化,等于把批次效应先抹掉了一部分,校正会失真。

代码实现:分指标标准化

from sklearn.preprocessing import RobustScaler

scaler_dict = {}
for col in admet_cols:
    scaler = RobustScaler()
    # 注意:reshape(-1,1) 是因为 scikit-learn 要求 2D 输入
    scaled_values = scaler.fit_transform(df[[col]])
    df[col + "_scaled"] = scaled_values
    scaler_dict[col] = scaler

# 之后用 _scaled 列做 ComBat 校正
# 校正后可以 inverse_transform 回到原始量纲

代码里我额外保存了每个指标的 scaler 对象,因为校正完你可能想还原回去做结果解读。

本章知识体系

下面这张 SVG 图把预处理流程串起来了:从原始数据出发,先做缺失值处理(分策略),再做标准化(选方法),最后进入 ComBat 校正阶段。

ADMET 数据预处理流程 原始 ADMET 数据 缺失值处理 直接删除 / 分组插补 / 模型插补 标准化 Standard / Robust / MinMax ComBat 批次校正 避坑提示 • 插补按批次进行 • 标准化基于全数据 • 保留标记列便于追溯 • 离群点多时用 Robust 常见数据问题 • 缺失率 > 60% 删列 • 同一批次全缺?批次单独剔除

看完这张图你应该能感受到:预处理不是独立步骤,它跟后续校正方法紧密相关。插补不对,标准化方向错,ComBat 校正的结果就很难挽救。

实战中常见问题速查

  • 问:缺失值超过 80% 怎么办?
    答:直接删除该列。插补 80% 的数据等于编造,不会带来有用信息。
  • 问:不同批次缺失模式不一样,能统一插补吗?
    答:绝对不能。你想想看,如果批次1缺失了 LogD,批次2没有,统一插补会把批次2的信息“泄露”到批次1,人为缩小批次差异。ComBat 会被误导。
  • 问:标准化后是否需要反标准化?
    答:ComBat 校正一般不需要反标准化,它直接在标准化后的矩阵上操作。但如果你要输出原始量纲的报告,记得用之前保存的 scaler 做 inverse_transform。
一个小技巧:在保存预处理后数据时,把 scaler_dict 也序列化(pickle 或 joblib 存起来),这样后面重新分析时可以保持完全一致的处理流程。