第三章 数据来源与收集:公共数据库与内部数据
做QSAR建模,说白了就是和数据打交道的一门手艺。模型好不好,七分靠数据。数据质量不行,算法再花哨也是白搭。我个人习惯把数据收集分成两条线:公共数据库和内部数据。这两条线各有各的坑,咱们今天就来聊聊怎么把它们捋清楚。
很多人一开始喜欢到处扒数据,结果发现来源不一致、单位不统一,甚至一个化合物的活性值都能差两个数量级。嗯,我早期有个项目就是吃了这个亏。今天我们系统的讲一讲,怎么搞到靠谱的数据,并且把源头管控好。
3.1 公共数据库:免费的午餐,但有门槛
公共数据库是QSAR领域最大的宝藏,但也是个陷阱。你要会挑、会查、会判断。我个人经常用这几个库,也踩过不少坑。
| 数据库名称 | 核心用途 | 数据特点 | 我常用的场景 |
|---|---|---|---|
| ChEMBL | 生物活性数据 | 标准化好,有配位信息 | 做靶点特异性模型首选 |
| PubChem | 化合物属性+活性 | 数据量大,噪声多 | 初筛和虚拟筛选 |
| DrugBank | 已上市/临床药 | 权威性高,数据稀疏 | 验证模型现实意义 |
| PDBbind | 蛋白-配体结合数据 | 实验值精准,覆盖窄 | 结合自由能预测 |
| BindingDB | 结合亲和力 | 文献来源,格式多样 | 补充ChEMBL缺失数据 |
从ChEMBL下载数据,我一般用他们的Python API。这样能批量拉取,还能加上质控筛选。下面是核心的拉取代码段,注意看注释部分——那是我用真金白银换来的经验。
from chembl_webresource_client.new_client import new_client
# 初始化客户端
molecule = new_client.molecule
activity = new_client.activity
# 查询特定靶点(比如EGFR)
target_query = new_client.target
target = target_query.search('EGFR').filter(
target_type__in=['SINGLE PROTEIN']
)
# 获取target_id
target_id = target[0]['target_chembl_id']
print(f"获取到的靶点ID: {target_id}")
# 拉取活性数据,这里我加了显式过滤
activities = activity.filter(
target_chembl_id=target_id,
standard_type__in=['IC50', 'Ki', 'Kd', 'EC50'],
standard_relation='=',
standard_value__isnull=False,
pchembl_value__isnull=False
).only(['molecule_chembl_id', 'standard_value', 'standard_units',
'pchembl_value', 'assay_chembl_id'])
# 转换为pandas DataFrame
import pandas as pd
df = pd.DataFrame.from_records(activities)
print(f"拉取到 {len(df)} 条记录")
print(df.head())
pchembl_value 非空,导致模型训练时有一大批数据是NaN。更坑的是,这些缺失值在后续归一化时全部被填充成了0,嗯,你猜怎么着?模型直接学成了一坨。记住:拉完数据先做 df.dropna(subset=['pchembl_value']) 和 df = df[df['standard_value'] > 0]。
3.2 内部数据:自己的家底要摸清
公共数据库再大,也比不上自己实验室或公司内部的数据珍贵。为什么?因为内部数据实验条件统一,仪器一致,操作人员的手法也相近。说白了,变异系数小得多。
我建议你把内部数据管起来。不要只在Excel里东一个文件西一个夹子。我见过最离谱的同事,把活性数据存在EMail正文里,还把化合物结构存成了图片。那简直就是灾难。
内部数据收集,核心要抓住这几点:
- 实验协议标准化:同一靶点用同一批试剂,一样的孵育时间。你想想看,如果A批次用30分钟孵育,B批次用60分钟,IC50差一两倍很正常。
- 元数据记录全:谁做的实验、哪天做的、用的哪台机器、样品纯度多少。这些信息平时用不上,但一旦数据出问题,靠它们溯源。
- 数据结构统一:存活性值一律转成pIC50或pKi,单位统一为nM。我见过有人用mM,有人用µM,还有人用%抑制率...混一起跑模型必出事。
给个典型的内部数据表结构,你可以直接照着搭建:
| 字段名 | 类型 | 说明 | 我要求的格式 |
|---|---|---|---|
| compound_id | 文本 | 内部唯一编号 | CMP-库号-年月-序号 |
| SMILES | 文本 | 规范化结构 | 用RDKit生成canonical SMILES |
| target_name | 文本 | 靶点全称 | 统一到UniProt推荐名 |
| pIC50 | 浮点 | 按-nM转换 | 如果IC50=100 nM,pIC50 = 7.0 |
| purity | 浮点 | 样品纯度 | 低于95%要加备注 |
| experiment_id | 文本 | 对应实验记录 | 可关联到LIMS系统 |
| operator | 文本 | 实验人 | 用login名,不要写中文花名 |
| assay_date | 日期 | 实验日期 | YYYY-MM-DD |
from rdkit import Chem
from rdkit.Chem import MolFromSmiles, MolToSmiles
def standardize_smiles(smiles):
mol = MolFromSmiles(smiles)
if mol is None:
return None # 无效SMILES
# 去掉盐和溶剂分子
mol = Chem.RemoveHs(mol)
# 中性化处理
mol = Chem.SanitizeMol(mol)
# 生成规范SMILES
return MolToSmiles(mol, canonical=True, isomericSmiles=True)
# 示例
raw_smiles = "C[C@@H](O)c1ccccc1.Cl" # 含盐酸盐
standard_smiles = standardize_smiles(raw_smiles)
print(f"标准化前: {raw_smiles}")
print(f"标准化后: {standard_smiles}")
3.3 数据整合与源头质控
公共数据和内部数据都拿回来了。下一步就是合并。但我建议你,千万别一把梭。你一合并,就再也分不清哪条数据是从哪来的了。一旦模型表现不好,你连锅都甩不出去。
我个人做法是保留数据来源标签。给公共数据加上来源库名和记录ID,内部数据标上项目编号。这样就算后期做数据审核,也能一条一条追溯回去。
数据合并前,我一定会做一次源头质控检查。检查清单如下:
- 结构去重:不同批次可能测了同一个化合物。用规范SMILES做键,留下pIC50最高的那条。或者取平均值,但要做好记录。
- 活性合理性:pIC50值小于2或者大于12的,基本可以打入冷宫。大于12,活性强到逆天,多半是测错了。小于2,基本是没活性,但也不能说完全没用,看你的模型目标。
- 单位统一:所有活性值一律转成pIC50。pIC50 = -log10(IC50 in M)。如果IC50以nM给出,算pIC50 = 9 - log10(IC50_nM)。这个公式最好写在注释里。
- 数据量级检查:每个靶点建议至少50条数据。少于30条,做出来的模型基本没法看。我一般低于50条直接标记为“低置信度”,暂不加入核心训练集。
3.4 知识体系总览
咱们这一章的核心思路,我画了个简图。你看一眼就能明白数据这条线是怎么走的。其实不复杂,关键就是不要跳步骤。
这个图你存着。以后每次做数据收集,就照着这个流程走。先从我提到的公共数据库里拉权威数据,再对内部数据做标准化入库。两者到齐后,别忘了做源头质控——这是你的护城河。最后汇入数据集,才能放心拿去建模。
记住,数据收集是整个QSAR项目的起点。起点偏了就回不了头了。我亲眼见过一个项目,因为用了批次混乱的内部数据,导致后续三个月全部白费。源头质控,怎么强调都不过分。