第2章 药物数据资源:公共数据库的使用
2.1 三大数据库,我拿来做什么
做AI药物发现,数据就是燃料。巧妇难为无米之炊,没有高质量的结构化数据,你模型再花哨也白搭。我个人习惯把公共数据库分成三类:ChEMBL管活性,DrugBank管成药,PubChem管化合物。听起来简单,但实际用起来坑不少。
我刚开始接触这个领域时,以为随便爬一个库就能训模型。结果呢?IC50和Ki混在一起,单位有的是nM有的是μM,一个批次下来数据全是乱的。后来学乖了,先摸清每个库的脾气。
2.2 ChEMBL:活性数据的金标准
ChEMBL由欧洲生物信息学研究所维护,收录了从文献中人工提取的小分子-靶点亲和力数据。说白了,它是目前最干净的活性数据集之一。
- 核心内容:化合物结构、靶点ID、活性值(IC50 / Ki / Kd / EC50)、实验条件
- 数据特点:人工校验,质量高,但更新较慢
- 访问方式:Web界面、RESTful API、Python包
chembl_webresource_client
我个人用得最多的操作就是按靶点名拉取所有活性数据。下面这段代码我在三个项目里都复用过了:
from chembl_webresource_client.new_client import new_client
target = new_client.target
target_name = "Serotonin transporter"
targets = target.filter(pref_name__icontains=target_name)
activity = new_client.activity
if targets:
target_id = targets[0]['target_chembl_id']
res = activity.filter(target_chembl_id=target_id)
print(f"Get {len(res)} activities")
for r in res[:3]:
print(r['molecule_chembl_id'], r['standard_type'], r['standard_value'])
standard_type可能是IC50、Ki或EC50。同一个靶点可能混用多种类型,建议只保留一种。我一般统一用IC50,单位全转成nM。
2.3 DrugBank:从分子到药物
DrugBank是个宝藏。它收录了FDA批准的药物以及临床试验中的候选药物,不光有靶点,还有药代动力学、相互作用、通路信息。做虚拟筛选或ADMET预测时,它经常被我当正样本库用。
| 字段 | 内容 | 我的用法 |
|---|---|---|
| Drug ID | DB开头的唯一标识 | 关联外部数据 |
| 靶点列表 | 靶点名称 + Uniprot ID + 作用类型 | 构建靶点-药物二分图 |
| SMILES | 2D结构 | 输入分子指纹/图模型 |
| LogP / MW / HBD | 理化性质 | 基于规则的类药性过滤 |
要注意的是,DrugBank的完整数据需要申请学术许可才能下载XML/JSON。没许可时可以在Web上搜索,但批量抓取要遵守robots.txt。
2.4 PubChem:海量化合物目录
PubChem由美国NIH管理,覆盖1亿+化合物,包括从供应商采购、文献提取和用户提交的数据。它很适合做负样本库,或者用来补充ChEMBL没有覆盖的化合物空间。
- 三大子库:Substance(提交物)、Compound(标准化后)、BioAssay(生物实验)
- 我最常用:通过PCID or CID和SMILES之间的转换,以及BioAssay数据
- API:Power User Gateway (PUG) 支持REST和SOAP
下面是从PubChem批量获取SMILES的简洁写法:
import requests
cid_list = ["2244", "3676", "5311"] # 阿司匹林、布洛芬、对乙酰氨基酚
base_url = "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/property/MolecularFormula,MolecularWeight,CanonicalSMILES/JSON"
for cid in cid_list:
r = requests.get(base_url.format(cid=cid))
if r.status_code == 200:
data = r.json()
props = data['PropertyTable']['Properties'][0]
print(cid, props['MolecularFormula'], props['CanonicalSMILES'])
time.sleep(0.2)后稳如老狗。
2.5 如何选择与组合?
你可能会问:三个库看着重叠,到底用哪个?我的经验是这样的:
| 场景 | 推荐数据源 | 理由 |
|---|---|---|
| 构建活性预测模型 | ChEMBL | 活性值标准化程度高 |
| 药物-靶点相互作用网络 | DrugBank + ChEMBL | DrugBank有获批标签,ChEMBL有活性值 |
| 大规模负样本采样 | PubChem | 化合物空间大,且可关联BioAssay的无效结果 |
| ADMET预测训练 | DrugBank + PubChem | DrugBank有体内数据,PubChem有体外 |
组合的时候要注意去重。不同库对同一化合物的描述可能略有差异——比如阿司匹林的CAS号、SMILES、InChI都一致,但Chembl ID和PubChem CID不同。我个人习惯先用InChI或标准SMILES做统一标识符。
2.6 数据获取 pipelines 实战
光说理论不过瘾,我直接给个能跑通的流程。假设你要做某个靶点(比如EGFR)的活性预测模型,你可以这样搭:
- 从ChEMBL拉EGFR的活性数据:只保留IC50(nM),去除重复分子。
- 从DrugBank拿已知的EGFR抑制剂做正样本增强:虽然ChEMBL里也有,但DrugBank有获批状态标记。
- 从PubChem随机选一批结构不同的化合物做负样本:注意排除在ChEMBL里已标注有活性的。
- 统一用标准SMILES去重:用RDKit的
MolFromSmiles+MolToSmiles标准化。
下面是整合三类API的伪代码框架:
import pandas as pd
from chembl_webresource_client.new_client import new_client
import requests
from rdkit import Chem
def fetch_chembl(target_name, activity_type='IC50'):
acts = new_client.activity
targs = new_client.target
t = targs.filter(pref_name__icontains=target_name)
if not t: return []
tid = t[0]['target_chembl_id']
res = acts.filter(target_chembl_id=tid, standard_type=activity_type)
return [(r['molecule_chembl_id'], r['standard_value'], r['standard_units']) for r in res]
def fetch_drugbank_by_target(target_name):
# 模拟:实际需通过XML或官方API
# 这里展示数据结构
pass
def pubchem_random_negative(count=1000):
# 随机抽取PubChem CID
# 用PUG REST轮训
pass
# 主流程
chembl_egfr = fetch_chembl('EGFR')
smiles_standard = []
for chembl_id, val, unit in chembl_egfr:
mol = Chem.MolFromSmiles(some_func_to_get_smiles(chembl_id))
if mol:
smiles_standard.append(Chem.MolToSmiles(mol))
2.7 避坑指南(我摔过的跤)
- 曾有一次,我从DrugBank下载XML时没注意版本号,结果解析字段全是旧标签,白白浪费一整天。建议每次使用前先看官方Changelog。
- PubChem的Substance和Compound傻傻分不清。Substance是原始提交物,可能包含多个副本;Compound是标准化后的唯一结构。建模一定用Compound,别问我怎么知道的。
- ChEMBL的unit字段经常是nM,但也有μM甚至pM(很少)。做回归任务前务必统一转换。我写了段小函数,见下面的check:
def normalize_to_nm(value, unit):
unit = unit.strip().lower()
if unit in ['nm', 'nanomolar']:
return float(value)
elif unit in ['μm', 'micromolar', 'um']:
return float(value) * 1000
elif unit in ['mm', 'millimolar']:
return float(value) * 1_000_000
else:
raise ValueError(f"Unknown unit: {unit}")
记住:数据无小事。你训练集里一个nM被当成μM,模型在临床上可能推荐一个无效分子,那可不是闹着玩的。
2.8 本章小结
这个章节我讲了不少实操层面的东西,其实核心就三句话:ChEMBL是活性主力,DrugBank是成药参考,PubChem是大海捞针。这三个库的API都不难调,难的是数据清洗和去重。后面章节我们会聊到RDKit处理分子结构,以及怎么把这些数据喂进图神经网络。现在先把数据源搭好,磨刀不误砍柴工。
嗯,记住我前面说的取数流程,动手写个demo跑一遍。代码是练会的,不是看会的。