2. 数据获取:公共ADMET数据库与格式转换
做ADMET建模,第一步不是调参,而是找数据。我见过太多同学上来就怼模型,结果发现数据格式不对,或者压根就是从二手渠道抄来的SMILES。嗯,这步走稳了,后面至少省一半时间。
咱们这章聚焦三个公共数据库:ChEMBL、PubChem和DrugBank。我会告诉你每个库怎么下载、怎么转换格式。说白了,你得学会把数据从「别人家的仓库」搬到你自己的工作台上。
2.1 为什么选这三个数据库?
市面上生物活性数据库几十个,为什么我专挑这三个?原因很简单:
- ChEMBL:手工 curated 过的药物化学数据,质量最高。我当年做CYP450模型,全是从ChEMBL拉的。
- PubChem:量大管饱。BioAssay板块有近300万条测试数据,做训练集的好选择。
- DrugBank:已上市或临床阶段的药物数据。做ADMET预测,你就是想预测这类分子嘛。
2.2 数据下载实操:以ChEMBL为例
先讲ChEMBL。它提供了官方Python API——chembl_webresource_client。安装只有一行:
pip install chembl_webresource_client
然后我们就能通过REST接口拉数据了。举个具体例子,假设你想拉关于「logD」这个ADMET属性的数据:
from chembl_webresource_client.new_client import new_client
# 搜索目标属性
properties = new_client.component_property
results = properties.filter(
property_name__icontains='logD'
).only(['component_id', 'property_name', 'value', 'units'])
# 获取前5条看看
for r in results[:5]:
print(r['component_id'], r['property_name'], r['value'])
注意这里有个坑:ChEMBL返回的是嵌套JSON。你需要自己flatten成表格。我习惯直接用pandas一把梭:
import pandas as pd
df = pd.json_normalize(results)
print(df.shape)
molregno加assay_id做唯一键去重的。
2.3 PubChem的数据获取
PubChem比ChEMBL更大更杂。它的官方接口是PUG REST。我一般这么用:
import requests
# 获取化合物的SDF格式
url = "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/2244/SDF"
resp = requests.get(url)
# 保存到本地
with open("aspirin.sdf", "wb") as f:
f.write(resp.content)
但如果你要批量拉,千万别一个一个请求。用listkey机制:
# 先创建一个化合物列表
identifiers = {"cid": [2244, 3672, 5161]}
resp = requests.post(
"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/SDF",
json=identifiers
)
# 保存
with open("batch.sdf", "wb") as f:
f.write(resp.content)
PubChem的BioAssay数据则是通过AID(Assay ID)获取。我建议你从BioAssay Browser先搜adhoc条件,再复制AID下来用。
2.4 DrugBank的下载与解析
DrugBank有点特殊。它限制免费用户一年只能下载一次XML格式的全库。我个人习惯每年初下载一次,然后本地维护。
解析XML是重头戏。DrugBank的XML结构里埋着SMILES、logP、pKa等ADMET属性。我用xml.etree.ElementTree解析:
import xml.etree.ElementTree as ET
tree = ET.parse("drugbank_all_full_database.xml")
root = tree.getroot()
ns = {"db": "http://www.drugbank.ca"}
# 提取每个药物
drugs = []
for drug in root.findall("db:drug", ns):
name = drug.find("db:name", ns).text
smiles = drug.find("db:calculated-properties/db:property[db:kind='SMILES']/db:value", ns)
if smiles is not None:
drugs.append({"name": name, "smiles": smiles.text})
print(f"一共解析了 {len(drugs)} 个药物")
calculated-properties下,也可能在experimental-properties下。我写代码时两种都查一遍,取非空值。
2.5 格式转换:SMILES vs SDF vs CSV
数据拿下来了,接下来就是格式问题。做ADMET建模,三种格式你必须会互转:
| 格式 | 优点 | 缺点 | 我什么时候用它 |
|---|---|---|---|
| SMILES | 轻量,一行一条,适合存文本 | 丢失立体结构、不能存3D坐标 | 数据清洗、建模标签匹配 |
| SDF | 保存完整分子结构、3D坐标 | 文件大,解析慢 | 分子指纹生成、3D描述符计算 |
| CSV | 最通用,pandas直接读 | 会丢失分子结构信息 | 最终训练集、测试集 |
转换工具我用RDKit,没有之一。来看看最常用的几个场景:
场景1:SMILES → SDF
from rdkit import Chem
from rdkit.Chem import AllChem
# 读CSV中的SMILES
import pandas as pd
df = pd.read_csv("molecules.csv")
writer = Chem.SDWriter("molecules.sdf")
for idx, row in df.iterrows():
mol = Chem.MolFromSmiles(row["SMILES"])
if mol:
# 生成3D坐标
mol = Chem.AddHs(mol)
AllChem.EmbedMolecule(mol)
AllChem.MMFFOptimizeMolecule(mol)
writer.write(mol)
writer.close()
场景2:SDF → CSV
supplier = Chem.SDMolSupplier("batch.sdf")
data = []
for mol in supplier:
if mol is None:
continue
smiles = Chem.MolToSmiles(mol)
# 提取属性
props = mol.GetPropsAsDict()
data.append({"SMILES": smiles, **props})
df = pd.DataFrame(data)
df.to_csv("batch.csv", index=False)
场景3:批量SMILES标准化
同一个分子在不同数据库里可能写成不同的SMILES。我去年做hERG模型时就吃过这个亏,后来加了标准化步骤:
def standardize_smiles(smiles):
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
# 移除氢、去重、规范映射
mol = Chem.RemoveHs(mol)
return Chem.MolToSmiles(mol, canonical=True)
df["SMILES_std"] = df["SMILES"].apply(standardize_smiles)
2.6 数据整合:多库联合查询
实际项目中,你不会只用单一数据库。我做过一个P-gp底物预测模型,数据源来自三个库:ChEMBL(人工标注)、PubChem(高通量筛选)、DrugBank(临床药物)。
整合流程如下图:
数据获取 → SMILES标准化 → 去重(基于InChIKey) → 标签合并 → 质量控制 → 最终CSV
这里说下去重。去重不能用SMILES字符串,得用InChIKey。它比SMILES更可靠:
import hashlib
def get_inchikey(smiles):
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
return Chem.MolToInchiKey(mol)
# 合并数据并用InChIKey去重
combined_df = pd.concat([chembl_df, pubchem_df, drugbank_df], ignore_index=True)
combined_df["inchikey"] = combined_df["SMILES_std"].apply(get_inchikey)
combined_df = combined_df.drop_duplicates(subset=["inchikey"])
标签合并更头疼:一个分子在ChEMBL中标「active」,在PubChem里标「inactive」。我一般采取多数投票法,如果冲突则取体外数据优先级高的那个。
2.7 一个完整的端到端脚本
说了这么多,我直接给你一个能用的脚本,跑通从ChEMBL拉到CSV的整个流程:
#!/usr/bin/env python3
"""
pull_chembl_admet.py
从ChEMBL拉取ADMET数据并保存为CSV
"""
from chembl_webresource_client.new_client import new_client
import pandas as pd
from rdkit import Chem
def pull_chembl(property_name, limit=5000):
"""拉取指定ADMET属性数据"""
# 1. 查询属性
props = new_client.component_property
results = props.filter(property_name__icontains=property_name)
# 2. 构建DataFrame
data = []
for i, r in enumerate(results):
if i >= limit:
break
# 获取化合物信息
comp = new_client.component_property.get(r['component_property_id'])
smiles = comp.get('smiles', '')
data.append({
'molecule_chembl_id': r['molecule_chembl_id'],
'property_name': r['property_name'],
'value': r['value'],
'units': r['units'],
'SMILES': smiles
})
df = pd.DataFrame(data)
# 3. SMILES标准化
def std_smiles(s):
mol = Chem.MolFromSmiles(s)
return Chem.MolToSmiles(mol, canonical=True) if mol else None
df['SMILES_std'] = df['SMILES'].apply(std_smiles)
df = df.dropna(subset=['SMILES_std'])
# 4. InChIKey去重
df['inchikey'] = df['SMILES_std'].apply(lambda x: Chem.MolToInchiKey(Chem.MolFromSmiles(x)))
df = df.drop_duplicates(subset=['inchikey'])
return df
# 使用示例
df_logd = pull_chembl('logD', limit=2000)
df_logd.to_csv('chembl_logd.csv', index=False)
print(f"拉取完成,共 {len(df_logd)} 条数据")
这个脚本你直接拷贝就能跑。注意第一次运行会下载缓存,需要等一会儿。
2.8 本章小结
- 三个数据库各有侧重:ChEMBL精、PubChem大、DrugBank临床。
- 格式转换的核心工具是RDKit:SMILES转SDF、SDF转CSV、SMILES标准化,一个库搞定。
- 去重要用InChIKey:别用SMILES字符串,会出现假阳性去重。
- 标签冲突用投票:数据源多不是坏事,关键是定好优先级规则。