数据获取:从公开数据库获取药物相关数据

做药物图谱,数据是基础。光有工具,没有数据,一切都是白搭。

我之前遇到个项目,团队花了三周设计前端交互,结果发现连基本的药物-靶点关系都没拉全。嗯,从那以后我就把这一步放到了最前面。

这一节,我带大家从 DrugBank 和 CTD 两个公开源把数据搞到手。

3.1 数据从哪里来

公开的药物数据库不少。但我个人最常用的就两个:

  • DrugBank:药物与靶点关系的权威来源,数据质量高。
  • CTD(Comparative Toxicogenomics Database):药物-基因-疾病三者关联,覆盖面广。

为什么不选别的?说白了,DrugBank 的靶点数据更“干净”,CTD 的基因关联更“完整”。两者搭配,基本够用。

3.2 获取原始数据

有两种方式拿到数据:网页下载、API 调用。

3.2.1 从 DrugBank 下载

DrugBank 提供 XML 格式的完整数据包。你想想看,光下载就需要一个晚上释放。我当时第一次跑,愣是等了四个小时。

登录 DrugBank 官网,申请学术账号,下载 XML 文件:

# 文件路径示例
drugbank_all_full_database.xml   # 完整数据包,约 2GB

下载后,用 Python 解析 XML:

import xml.etree.ElementTree as ET

tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()

namespace = '{http://www.drugbank.ca}'
for drug in root.findall(f'{namespace}drug'):
    drugbank_id = drug.find(f'{namespace}drugbank-id')
    name = drug.find(f'{namespace}name')
    print(drugbank_id.text, name.text)

这个 XML 结构嵌套很深。我个人习惯,先只拉出药物 ID 和名称做索引。

3.2.2 从 CTD 抓取

CTD 提供 CSV 文件,按类别分:

# 下载化学-基因相互作用数据
wget http://ctdbase.org/reports/CTD_chem_gene_ixns.csv.gz
gunzip CTD_chem_gene_ixns.csv.gz

CSV 格式,直接 Pandas 加载:

import pandas as pd

df = pd.read_csv('CTD_chem_gene_ixns.csv')
print(df.columns.tolist())
print(df.head())

CTD 的数据比 DrugBank 粗糙,但胜在量大。很多时候,药物图谱的“疾病”节点就靠它补全。

3.2.3 用 API 增量查询

全量下载一次就够了。后续更新用 API 更灵活。

DrugBank API 示例:

# 查询特定药物信息
curl -X GET "https://api.drugbank.com/v1/drugs/DB00001" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

返回 JSON:

{
  "drugbank_id": "DB00001",
  "name": "Leptirudin",
  "type": "Biotech",
  "targets": [
    {
      "id": "BE00001",
      "name": "Thrombin",
      "action": "inhibitor"
    }
  ]
}

CTD API 也可以:

# 查询化学物质
curl -X GET "https://ctdbase.org/v1/chemical/C00001" \
  -H "Accept: application/json"

API 模式适合你只更新增量数据的时候。全量建库,还是建议用文件。

3.3 数据的结构长什么样

两个数据源的结构很不一样。我整理了个对比表,方便大家对照:

数据源 核心实体 关系类型 关键字段
DrugBank 药物、靶点 药物→靶点(抑制/激活) drugbank_id, name, target_id, action
CTD 化学物、基因、疾病 化学物→基因(相互作用);化学物→疾病 chemical_id, gene_id, disease_id, interaction
对比点 DrugBank 靶点更精确 CTD 疾病关联更丰富 需做 ID 映射对齐

你看,数据源之间天然有缝隙。后续做图谱对齐时,这是必须处理的。

3.4 清洗入库

数据拿到手,不能直接用。脏数据是家常便饭。

3.4.1 处理命名冲突

同一种药物,在两个库叫法不一样。比如“乙酰水杨酸”和“阿司匹林”。

我的处理方式:统一用 DrugBank 的药物名为标准。

def standardize_drug_name(name_from_ctd):
    mapping = {
        'Acetylsalicylic Acid': 'Aspirin',
        'Aspirin': 'Aspirin',
        'Paracetamol': 'Acetaminophen'
    }
    return mapping.get(name_from_ctd, name_from_ctd)

3.4.2 合并去重

两个库有很多重复记录。合并前要先去掉 ID 相同的项。

# 合并 DrugBank 和 CTD 的靶点数据
combined = pd.concat([drugbank_targets, ctd_targets])
combined.drop_duplicates(subset=['drugbank_id', 'target_id'], inplace=True)

3.4.3 填充缺失字段

CTD 的数据很多字段为空。我遇到过一个案例,疾病字段有 30% 是空值。

这时候就要靠 DrugBank 的关联数据往回推。或者直接标记为“未知”。

# 填充缺失值
combined['disease_name'] = combined['disease_name'].fillna('unknown')

3.5 构建基础关系矩阵

数据清洗干净后,我习惯先建一张“药物-靶点关系表”。说白了,就是把化合物和药靶之间的作用关系梳理清楚。

举个例子:

drug_id    target_id    action      source
DB00001    BE00001      inhibitor   DrugBank
DB00001    BE00002      agonist     DrugBank
C00001     GENE-1234    increase    CTD
C00001     GENE-5678    decrease    CTD

这张表就是药物图谱的“边”数据。可视化时,它就是连接药物和靶点的线条。

小技巧:我习惯把 action 字段做缩写映射:inhibitor→-1, agonist→+1, unknown→0。这样后续可视化时,可以直接用颜色或箭头方向表示作用类型。

3.6 这一章的核心逻辑

说了这么多,我们把整体流程理一下:

数据获取与清洗流程 DrugBank 数据库 CTD 数据库 文件下载 API 增量查询 解析与清洗 合并与去重 关系矩阵 两个源都走一遍 再合并清洗

我早期做这个流程时,经常直接跳过清洗步骤,觉得“反正数据能跑”。结果可视化时发现节点和连线全是错的。嗯,从那以后我再也不敢跳了。

避坑指南:我曾经把 DrugBank 和 CTD 的 ID 当作同一套体系去匹配,结果发现靶点 ID 不兼容。两个数据库的 ID 体系是隔离的,需要额外映射表。切记!

3.7 数据质量的检查清单

每次建完基础数据,我都跑一遍这个清单:

  1. 缺失率:关键字段(药物名、靶点名)缺失率 < 5%。
  2. 重复率:主键去重后,重复 < 2%。
  3. 类型对齐:inhibitor / agonist 等操作类型已经统一缩写。
  4. ID 映射:跨库 ID 已经做了映射表。

数据质量决定了可视化的下限。数据烂,再炫的图也是骗人。我一般把检查清单写到自动化脚本里,每次更新数据自动跑一遍。

好了,数据拿到,清洗干净,下一步就该想想怎么用这些数据画图了。