数据预处理:药物‑靶点‑疾病关系数据清洗与标准化

做药物图谱分析,最耗时间的不是建模,而是洗数据。我个人习惯花 60% 的时间在预处理上,这样后续的图计算和可视化才能跑得顺。你想想看,如果数据里同一个药物叫了两个不同的名字,或者靶点 ID 混用,那画出来的图谱谁敢信?

这一章我们就来搞定三件事:去重、标准化、关系对齐。有了这一步,后面的分析才有地基。

数据来源与常见问题

先看下我们常打交道的几个数据源。

数据源 典型字段 常见坑
DrugBank DrugBank ID, 药物名, 靶点 UniProt ID 多商品名、盐基形式未拆分
UniProt Entry, Gene Name, Protein Name 同一基因多个剪接异构体
DisGeNET DOID, 疾病名, 基因关联分数 疾病层级混乱,父术语包含子术语
CTD 化学名, MeSH ID, 疾病名 同一化学物用多种命名规则

我在项目中遇到过最头疼的情况:同一个药物阿托伐他汀,在 DrugBank 里是 DB01076,在 PubChem 是 CID 60823,在文献里还叫“立普妥”。如果不统一,图谱里会出现两个独立节点,关系全错位。

清洗步骤:从原始数据到标准化关系表

下面是我的标准流程,每一步都踩过坑。

1. 去重与别名合并

先把同一实体的不同名字归并。我常用 药物名称词典 来合并商品名和通用名。

import pandas as pd

# 加载原始数据
drug_data = pd.read_csv("drug_target_raw.csv")

# 别名映射表(简化示例)
alias_map = {
    "Atorvastatin": "Atorvastatin",
    "Lipitor": "Atorvastatin",
    "立普妥": "Atorvastatin"
}

drug_data["standard_name"] = drug_data["drug_name"].map(alias_map).fillna(drug_data["drug_name"])
小提示: 别名映射最好维护一份 CSV,不要硬编码。我每次开始新项目都会先跑一遍别名检查,看看有没有漏掉的变体——比如“Cisplatin”和“Platinol”其实是同一个东西。

2. 实体 ID 标准化

药物、靶点、疾病都该有稳定 ID 体系。我个人偏好这套映射:

  • 药物 → DrugBank ID(或 ChEMBL ID)
  • 靶点 → UniProt Accession(或其 Entrez Gene ID)
  • 疾病 → DOID + OMIM(二选一,视用途)
# 药物ID映射示例
drug_id_map = {
    "Atorvastatin": "DB01076",
    "Metformin":  "DB00331"
}
drug_data["drugbank_id"] = drug_data["standard_name"].map(drug_id_map)

3. 靶点 ID 去歧义

靶点经常有多亚基、多 UniProt Entry 的情况。我的做法是保留所有亚基,并在关系表里加一列“target_role”。

# 靶点多亚基展开
target_list = []
for _, row in drug_data.iterrows():
    targets = row["target_uniprot"].split(";")
    for t in targets:
        target_list.append({
            "drugbank_id": row["drugbank_id"],
            "target_uniprot": t.strip()
        })

target_df = pd.DataFrame(target_list)
注意: 有些数据库把复合物靶点当成一个条目(如 EGFR‑HER2 二聚体),但我建议拆成单亚基再建边。不然图里会出现一个奇怪的多靶点节点,后期查询根本没法用。

4. 疾病层级标准化

疾病术语太容易混乱了。比如“糖尿病”在 DOID 下分 1 型和 2 型,如果你直接泛化到父节点,会丢失信息。我个人的原则:保留最细粒度疾病 ID,同时记录上级路径。

# 使用 DOID 层级文件,给每条疾病加上 is_a 链
doid_ancestor = {
    "DOID:9352": "DOID:0080200",   # 1型糖尿病→糖尿病
    "DOID:9351": "DOID:0080200"    # 2型糖尿病→糖尿病
}
drug_data["doid_ancestor"] = drug_data["doid"].map(doid_ancestor)

5. 关系冲突消解

同一药物‑靶点对在不同数据库里可能有不同方向(激活 vs 抑制),甚至互相矛盾。我会优先用来源评级(比如 ChEMBL 的置信度 > 文献挖掘),并保留来源字段。

# 合并多来源关系,保留最高置信度
df_sorted = df_combined.sort_values("confidence", ascending=False)
df_dedup = df_sorted.drop_duplicates(subset=["drugbank_id", "target_uniprot"])

标准化输出结构

洗完后,我习惯输出三张表,方便后续构建图数据库。

表名 字段 说明
entity_drug.csv drugbank_id, name, synonyms, molecular_formula 唯一节点表
entity_target.csv uniprot_id, gene_name, protein_name, taxon 靶点节点
entity_disease.csv doid, name, doid_ancestor, omim_id 疾病节点
relation_drug_target.csv drugbank_id, uniprot_id, relation_type, source, confidence 边表
relation_target_disease.csv uniprot_id, doid, relation_type, evidence_code 靶点‑疾病边

这三表可以直接导入 Neo4j 或 ArangoDB,连查询语句都不用改太多。我上次就靠这套标准结构,把一个散乱的多来源数据集在半天内揉成了可分析的图谱。

知识体系一览

下面这张图总结了整个预处理的核心流程。箭头方向代表数据流转。

药物‑靶点‑疾病数据预处理流程 药物靶点原始数据 疾病关联原始数据 别名/映射词典 去重 & 别名合并 ID 标准化 冲突消解 标准化节点表 关系边表 置信度标注 靶点‑疾病关系映射 层级标准化 三边统一图谱 Neo4j / CSV 核心原则 先去重 → 后标准化 → 再消冲突 每一步都要保留来源追溯字段

避坑指南(我曾踩过的雷)

  • 药物别名遗漏。 例如“阿司匹林”在有些数据库写“Acetylsalicylic acid”,别名表里没写就漏了。我曾因此少了一半关系。后来强制要求别名表覆盖所有常见拼写变体。
  • 靶点 ID 混淆。 同一基因在不同数据库里用了不同 Entry ID(比如 P00533 和 Q9HCM9 都是 EGFR 相关?其实不是,一个是人一个鼠)。必须检查物种。
  • 疾病层级合并过头。 我试过把所有“癌症”子类都合并到父节点,结果图谱里全是“癌症”,根本没区分度。后来改成保留最低层级,手动指定是否泛化。
一句话总结: 数据预处理不是体力活,而是图谱质量的生命线。每次我偷懒少做一步,后面必定花三倍时间返工。按上面的流程走,至少能保证数据可信、可复用。

嗯,到这里关系数据已经洗得足够干净了。你可以直接拿去构建图数据库,或者下一步做网络分析。这里不展开后续,我们先把这一关过好。