药物图谱核心概念:实体、关系、属性与三元组
搞药物图谱,说白了就是建一张大网。网里的节点是各种实体——药物、疾病、靶点、基因……但光有节点不行,还得知道它们之间怎么连、连上后带什么信息。我记得第一次做大规模实体对齐时,就因为疾病命名不统一,差点搞崩了整个项目。所以这一章,咱们先把核心概念理清楚。
实体:图谱里的“主角”
实体是知识图谱的最小单位。在药物领域,最常遇到的实体有哪几类?我给你列个清单:
- 药物实体——包括化学药、生物药、中药等。每个药物有自己的通用名、商品名、化学结构、ATC编码。
- 疾病实体——涵盖ICD编码、症状、疾病亚型、罕见病等。我习惯把疾病按“表型-病因-严重程度”分层存储。
- 靶点实体——蛋白质、酶、受体、离子通道等。注意同一靶点可能有多个别名,比如EGFR也叫HER1。
- 基因实体——与靶点相关但不同,基因是编码层,靶点是产物层。
- 表型实体——症状、体征、实验室指标。这个是连接疾病和患者的关键。
为什么要把实体分这么细?有一次我把“高血压”和“原发性高血压”当成两个疾病,结果治疗关系图里出现了两条不同路径——后来才发现是同一个病。所以你想想看,实体粒度必须统一,否则图谱自己就打架。
关系:连接实体的“桥”
实体之间靠关系才能形成网络。我常用的关系类型有这些:
| 关系类型 | 示例 | 说明 |
|---|---|---|
| 治疗关系 | 阿司匹林 → 治疗 → 冠心病 | 药物-疾病,直接关联 |
| 靶点作用 | 奥美拉唑 → 抑制 → H+/K+ ATP酶 | 药物-靶点,作用机制 |
| 基因关联 | BRCA1 → 相关 → 乳腺癌 | 基因-疾病,风险或致病 |
| 副作用关系 | 华法林 → 导致 → 出血 | 药物-表型,不良反应 |
| 相互作用 | 克拉霉素 → 增强 → 他克莫司血药浓度 | 药物-药物,影响药效 |
这些关系里,我最头疼的是“靶点作用”的粒度。比如“抑制”和“拮抗”到底算不算同一个关系?我曾在项目中统一成“调节(调节类型:抑制)”,这样既保留了语义,又方便查询。嗯,这里要注意:关系类型不宜超过20种,太多会降低图谱的可维护性。
属性:实体的“身份证”
属性描述实体自身的特征,不涉及其他实体。举个例子,一个药物实体可能包含以下属性:
{
"drug_id": "DB00001",
"name": "伊马替尼",
"molecular_formula": "C29H31N7O",
"molecular_weight": 493.6,
"route_admin": "口服",
"half_life": "18h",
"atc_code": "L01EA01"
}
属性的选择要有标准。我建议每个实体类型至少保留3个核心属性:唯一标识符、标准名称、所属类型。其他属性按业务需求增减。曾经有个同事把所有属性都塞进一个节点,导致查询时返回几百个键值对——图库都卡住了。所以属性要精简,只存那些在推理或搜索中会被用到的。
三元组:知识的最小单元
实体+关系+实体,或者实体+属性+值,就构成了三元组。这是知识图谱的基本存储单位。看个例子:
<伊马替尼> <治疗> <慢性粒细胞白血病> .
或者带属性的三元组:
<伊马替尼> <分子量> "493.6"^^xsd:float .
在实际的图谱数据库里,三元组会被存储为一条记录。RDF、Neo4j的边属性、以及JanusGraph的边标签,本质都是三元组的变体。我习惯用SPO(Subject-Predicate-Object)模式来设计所有关系,这样后续做图查询和规则推理都非常直接。
为什么会这样?因为三元组是图的“原子”。如果再拆,就失去关联;如果再合并,就变成平铺表格,失去了图的灵活性。
我的小技巧:新建三元组时,先问自己三个问题:
① 主语和宾语是不是同一个类型的实体?
② 谓语是不是可以用唯一的URI表示?
③ 属性能不能放到节点里而不是单独成边?
都回答“是”,再落库。
核心逻辑一图看懂
下面这张图把药物图谱的核心概念串起来了。你可以看到实体、关系、属性和三元组是怎么组合成一张可查询的知识网络的。
要点总结:
- 实体是图中的节点,必须定义唯一标识和类型。
- 关系是边,要控制粒度,不要过细或过粗。
- 属性只存必要信息,避免图模型臃肿。
- 三元组是基本存储单元,SPO结构是通用法则。
避坑指南
我曾经在同一个图谱里混用了rdf:type和自定义的hasType来表示实体类型,结果查询时有的用?s rdf:type :Drug,有的用?s :hasType "Drug",推理完全对不上。后来统一用rdf:type,才彻底解决。
注意:不要随意发明新的关系谓词。尽量复用已有的标准词汇,比如skos:related、biopax:controls。如果必须自己定义,一定要写进元数据文档里,不然半年后你自己都看不懂。
还有,属性值的数据类型要严格。我以前遇到过“1.5”毫克的字符串存成“1.5”,结果排序时按字符串排,10mg排在了1.5mg前面。所以数值属性一定要用xsd:float或xsd:integer,不能偷懒。
实际应用小场景
假设你要查“所有与EGFR相互作用且用于肺癌的口服药”。如果用三元组来表达,大致是这样的推理链条:
?drug rdf:type :Drug .
?drug :targets ?target .
?target :gene "EGFR" .
?drug :treats ?disease .
?disease :name "肺癌" .
?drug :route_admin "口服" .
看到没?每一步都是一个小三元组。把这些三元组攒起来,就是整个知识图谱。你想想看,如果没有清晰的核心概念,这堆三元组就会变成一团乱麻。
好,这一章就到这儿。把实体、关系、属性、三元组这四个概念吃透,后面讲图谱构建和更新维护的时候,你自然就知道该怎么下手了。