3. 药物数据基础与工程化读取
药物数据融合建模的第一步,不是训练模型,而是把散落在不同数据库、不同文件格式中的药物信息,整理成可被程序稳定读取、可被模型统一消费的结构化对象。药物数据天然是多模态的:结构是图,靶点是集合,活性是带单位的数值,ADMET 是预测或实验标签,临床试验是嵌套的事件记录。如果读取阶段没有把这些模态对齐到统一的键和语义上,后续任何融合都只是在错误的基础上叠加复杂度。
药物结构:从 SMILES 到可计算的分子图
药物结构最常见的工程化表示是 SMILES 字符串,例如阿司匹林可写作 CC(=O)Oc1ccccc1C(=O)O。SMILES 紧凑、易存储,但它本身不是模型友好的输入。工程化读取时通常要做三件事:一是用 RDKit 等工具解析为分子对象,检查是否合法;二是标准化,包括去盐、中和电荷、统一互变异构体,避免同一分子因写法不同被当成两个实体;三是生成规范 SMILES 或 InChIKey 作为唯一键。InChIKey 是 27 位定长字符串,前 14 位对应骨架连接,后段区分立体与质子化状态,非常适合做跨库主键。
解析后可从分子对象派生特征:分子量、LogP、氢键供体/受体数、可旋转键数、TPSA,以及 Morgan 指纹或分子图邻接矩阵。这些特征既可直接作为传统模型输入,也可作为图神经网络的节点与边。需要注意,SMILES 解析失败在真实数据中并不罕见,读取流程必须记录失败分子并输出日志,而不是静默丢弃。
靶点、活性与 ADMET:键、单位与阈值
靶点数据通常以基因名、UniProt 或 ChEMBL target ID 表示。跨库整合时,基因别名是主要噪声源,建议以 UniProt AC 为主键,基因名仅作展示。药物—靶点关系可表示为三元组 (drug_id, target_id, action_type),其中 action_type 可为 agonist、antagonist、inhibitor 等。
活性数据最核心的字段是标准类型、标准关系和标准单位。常见标准类型有 IC50、Ki、Kd、EC50,标准关系有 =、>、<。工程化读取时必须把不同单位统一换算,例如把 nM 统一为 nM 存储、展示时再转换。活性值常做 p 值变换:pIC50 = 9 - log10(IC50 / 1e-9),当 IC50 以 mol/L 表示时,pIC50 = -log10(IC50)。阈值判断是建模标签化的关键,例如常把 pIC50 ≥ 6 视为活性、≤ 5 视为非活性,中间区间可标记为不确定或丢弃。
ADMET 数据包括吸收、分布、代谢、排泄、毒性五类终点,常见字段有 Caco-2 渗透性、血浆蛋白结合率、CYP450 抑制、hERG 阻断、肝毒性、 Ames 致突变性等。ADMET 数据来源混杂,既有实验测定也有预测值,读取时必须保留 source 与 assay_type 字段,否则后续无法区分证据强度。
临床试验数据:嵌套结构的扁平化
临床试验数据常以 XML 或 JSON 形式提供,典型如 ClinicalTrials.gov 的 study 记录。一条记录包含研究标识、阶段、状态、干预措施、入组人数、主要终点、不良事件等,层级较深。工程化读取的策略是“先保留原始嵌套,再按分析需求扁平化”。例如把干预措施展开为 (nct_id, intervention_name, intervention_type),把终点展开为 (nct_id, endpoint_type, measure, time_frame)。这样既能保留完整上下文,又能方便与药物、靶点表做连接。
常用数据库与读取要点
结构数据常用 PubChem、ChEMBL、DrugBank。PubChem 提供 PUG-REST 接口,可按 CID 或名称查询;ChEMBL 提供 Web 服务和批量下载,活性数据字段规范度较高;DrugBank 学术使用需注意许可。靶点数据以 UniProt 为主,活性与靶点关系可从 ChEMBL 的 activities 表获取。ADMET 可用 TDC(Therapeutics Data Commons)提供的标准数据集,字段和划分相对统一。临床试验以 ClinicalTrials.gov API v2 为主,返回 JSON,适合程序化解析。
工程化读取时建议遵循几条原则:第一,所有外部请求都做本地缓存,避免重复拉取;第二,解析失败、单位缺失、主键冲突都要写入日志表;第三,保留原始字段与标准化字段两套,便于回溯;第四,用版本号或快照日期标记数据批次,保证实验可复现。
一个可执行的最小读取流程
下面给出一个简化的读取片段,展示如何把 SMILES 解析、活性换算和主键生成串起来。示例使用 Python 与 RDKit,参数为教学假设值。
from rdkit import Chem
from rdkit.Chem import Descriptors, inchi
import math
def parse_drug(smiles):
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return {"status": "parse_failed", "smiles": smiles}
inchi_key = inchi.MolToInchiKey(mol)
return {
"status": "ok",
"inchi_key": inchi_key,
"mw": round(Descriptors.MolWt(mol), 2),
"logp": round(Descriptors.MolLogP(mol), 2),
"hbd": Descriptors.NumHDonors(mol),
"hba": Descriptors.NumHAcceptors(mol),
}
def to_pic50(ic50_nm, relation="="):
if ic50_nm is None or ic50_nm <= 0:
return None
pic50 = 9 - math.log10(ic50_nm)
return {"pIC50": round(pic50, 3), "relation": relation,
"label": "active" if pic50 >= 6 else ("inactive" if pic50 <= 5 else "uncertain")}
print(parse_drug("CC(=O)Oc1ccccc1C(=O)O"))
print(to_pic50(250, "="))
运行后应得到类似 {'status': 'ok', 'inchi_key': 'BSYNRYMUTXBXSQ-UHFFFAOYSA-N', 'mw': 180.16, ...} 与 {'pIC50': 6.602, 'relation': '=', 'label': 'active'} 的结果。判断读取是否成功,关键看三点:解析失败率是否在可接受范围、主键是否唯一且稳定、活性标签分布是否合理。若大量分子解析失败,通常说明源数据里混入了盐、聚合物或非标准写法,需要回到标准化步骤处理。
把上述流程固化为可重复执行的脚本后,药物结构、靶点、活性、ADMET 与临床试验数据就能以统一主键汇入同一张宽表或关系表,为后续的融合建模提供干净、可追溯的输入。