数据预处理:药物‑靶点‑疾病关系数据清洗与标准化
做药物图谱分析,最耗时间的不是建模,而是洗数据。我个人习惯花 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"])
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)
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,连查询语句都不用改太多。我上次就靠这套标准结构,把一个散乱的多来源数据集在半天内揉成了可分析的图谱。
知识体系一览
下面这张图总结了整个预处理的核心流程。箭头方向代表数据流转。
避坑指南(我曾踩过的雷)
- 药物别名遗漏。 例如“阿司匹林”在有些数据库写“Acetylsalicylic acid”,别名表里没写就漏了。我曾因此少了一半关系。后来强制要求别名表覆盖所有常见拼写变体。
- 靶点 ID 混淆。 同一基因在不同数据库里用了不同 Entry ID(比如 P00533 和 Q9HCM9 都是 EGFR 相关?其实不是,一个是人一个鼠)。必须检查物种。
- 疾病层级合并过头。 我试过把所有“癌症”子类都合并到父节点,结果图谱里全是“癌症”,根本没区分度。后来改成保留最低层级,手动指定是否泛化。
嗯,到这里关系数据已经洗得足够干净了。你可以直接拿去构建图数据库,或者下一步做网络分析。这里不展开后续,我们先把这一关过好。