4. 数据预处理与标准化归纳总结

数据预处理这事,说实话很多新手都不当回事。拿到原始数据直接往模型里塞,结果跑出来的东西一塌糊涂。我以前带的一个实习生就踩过这个坑,他用的是从ChEMBL扒下来的IC50数据,啥也没处理就往深度学习网络里送……嗯,结果loss死活降不下去。后来才发现,有个样本的分子量写成负数了。

干这行越久,越觉得数据预处理比调模型参数重要太多。今天我就把这部分经验好好捋一捋。

4.1 预处理到底在干什么?

简单说,就是把原始数据从不规范、有噪声、量纲不统一的状态,变成干净、一致、可以直接喂给建模算法的状态。下面这张图能帮你对整个流程有个整体印象:

药物靶点建模 – 数据预处理标准化流程 原始数据 数据清洗 缺失值处理 + 异常值检测 标准化 / 归一化 分类特征编码 标准化数据集 关键检查点 量纲统一 · 无异常值 · 完整 常用方法 Z-score / Min-Max RobustScaler One-Hot Encoding Label Encoding PCA降噪(可选)

4.2 数据清洗——别看它简单,坑最多

数据清洗第一步是统一格式。比如IC50值有的写“1.2 µM”,有的写“1200 nM”,还有的直接写“0.0012 mM”。你不统一单位,计算出来的pIC50肯定乱套。我个人习惯上来就先做两件事:

  • 单位换算:全部转成标准单位(µM、nM建议统一为nM再取负对数)
  • 去除冗余列:像“Compound ID”这种标识符,建模时千万别放进去

经验教训:我曾经接手一个公开数据集,发现里边有两列“SMILES_clean”和“SMILES_raw”,但有一半样本两列内容不一致。后来查原始文献才知道是清洗过程中混了不同批次的数据。这种事在药物数据库里特别常见,每个批次的处理脚本都不一样,最后合到一起就出乱子。

4.3 缺失值处理——别一刀切

很多药物靶点数据集里会有缺失的活性值。比如一个化合物只测了对A靶点的活性,对B靶点没测。这种缺失不是随机缺失,而是“结构性缺失”。你想想看,如果直接填充平均值,等于硬塞了一个不该存在的假数据。

我的做法分三种情况:

  • 缺失率 < 5%:用中位数填充(IC50分布一般是长尾,中位数比均值靠谱)
  • 缺失率 5% ~ 20%:用KNN插补(基于结构相似性),或者用随机森林预测
  • 缺失率 > 20%:建议剔除该特征或该样本,别勉强

技巧:对于活性数据,还有一种思路是用“未测”标记,然后在模型里单独作为一个类别处理。不过这样做会让模型学到“未测即无效”的偏见,慎用。

4.4 异常值检测——你总得亲眼看看数据分布

我刚开始做靶点建模时,总觉得异常值就是离群点,搞个3σ剔除就行了。后来发现完全不是这么回事。药物数据里的异常值分两类:

  1. 记录错误:比如IC50写成了-5.2(这显然是录入错误)
  2. 真实的活性异常:比如某个化合物的pIC50=12(纳摩尔级),但它就是那么强

对第一类直接删;第二类你要谨慎,它可能是个非常重要的先导化合物。我一般先用箱线图+Z-score定位,再人工回查原始文献。

# Python 示例:用 Z-score 检测异常活性值
import numpy as np
from scipy import stats

activity = np.array([7.2, 6.8, 8.1, 9.5, 5.3, 12.0, 7.0])
z_scores = np.abs(stats.zscore(activity))
outliers = np.where(z_scores > 3)

print("异常值索引:", outliers)
# 注意:阈值 3 可以按需调整

4.5 标准化与归一化——量纲统一的两种武器

很多机器学习算法对特征尺度敏感,比如SVM、KNN、神经网络。如果不做标准化,数值范围大的特征会主导模型。下面这张表能帮你快速分清两个概念:

指标Z-score 标准化Min-Max 归一化
公式z = (x - μ) / σz = (x - min) / (max - min)
输出范围均值为0,标准差为1,无固定范围[0, 1] 固定区间
对异常值敏感度敏感(异常值影响μ和σ)非常敏感(异常值影响min和max)
适用场景数据近似正态分布,或使用线性模型、PCA数据有明确上下界,或用于深度学习(图像像素等)
药物数据推荐常用(pIC50、logP、分子量等)当特征值有物理范围时使用(如电荷密度)

警告:这两个方法都假设特征值在训练集和测试集上采用同样的缩放参数。一定要用训练集的μ、σ或min、max去变换测试集,千万别重新计算测试集的统计量!否则数据泄露会让你模型评估结果虚高。

我习惯在药物靶点建模中优先使用Z-score标准化。为什么呢?因为很多描述符(如分子量、TPSA、logP)并不固定在[0,1]之间,而且pIC50本身就是对数变换后的值,再标准化一次依然保持距离意义。如果你用了RobustScaler(基于中位数和IQR),它对异常值更有抵抗力,但需要多调一个参数。

# Python 示例:在训练/测试集上正确应用标准化
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
scaler.fit(X_train)   # 只拟合训练集

X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)   # 使用相同的scaler

4.6 分类特征的编码

药物数据里常有分类特征,比如“物种类型(人/鼠/狗)”、“靶点家族(GPCR/激酶/离子通道)”。直接给它们编号1、2、3是不对的——模型会认为3比1大两倍,这毫无意义。

我常用的策略:

  • One-Hot Encoding:类别不多(<10)时首选,无顺序假定
  • Label Encoding:类别有顺序关系时才用,比如药效等级(无活性 < 低 < 中 < 高)
  • Target Encoding:用目标变量的均值替换类别,适合树模型,但容易过拟合,必须配合交叉验证

这里提一句,如果你用深度学习做分子图卷积,编码方式完全不同——通常会直接嵌入节点特征。但传统的机器学习还是离不开上面的方法。

4.7 实用避坑指南

  • 不要先做标准化再拆分数据集。一定要先拆分训练集/测试集,再在训练集上fit scaler。顺序错了等于窥视了测试集,这是数据泄露最典型的错误。
  • 注意pIC50的方向:pIC50 = -log10(IC50),数值越大表示活性越强。标准化后方向不变,但单位变了,解读模型系数时别搞混。
  • 多批次数据合并时:同一实验室不同批次的IC50测量可能会有系统偏差(比如批次1整体偏低0.3个log单位)。我遇到过一个项目,直接合并没校正,模型在交叉验证时波动特别大。后来用ComBat工具做了批次校正才稳住。
  • 高维稀疏特征:如果做了One-Hot后特征维度暴增(比如指纹位点有1024个),可以考虑PCA降维或直接使用随机森林/GBDT这类对稀疏不敏感的模型。

我的小建议:每次做完预处理,都保存一份预处理前的原始副本,同时保留缩放参数(scaler对象或pickle文件)。这样你复现结果时可以直接重用,不用重新做一遍清洗逻辑。

4.8 本章小结(核心要点)

数据预处理与标准化,是药物靶点建模中被严重低估的一步。我见过太多人把时间花在调超参数上,结果模型表现一直上不去——回头一看,根本是数据没洗干净。我自己现在养成的习惯是:花70%的时间做数据理解与清洗,30%的时间做建模。你试试看,效果会很不一样。

别忘了,标准化的参数要锁定在训练集上。嗯,这句话值得再看一遍。