4. 实验数据到RDF的映射
实验数据进入知识图谱的第一道关口,是把异构的原始记录转换成统一的三元组形式。实验室里常见的三类数据源——关系型数据库中的实验表、CSV/Excel 记录表、JSON 格式的仪器日志——结构差异很大,但都可以通过“确定主语—抽取谓词—绑定宾语”的思路映射为 RDF。本节围绕映射规则、R2RML 与直接映射工具的使用,以及一次完整的映射实战展开。
4.1 三类数据源的映射规则
映射的核心是回答三个问题:一行记录代表什么实体(主语)、字段名对应什么属性(谓词)、字段值如何变成 RDF 术语(宾语)。
关系型实验数据通常以主键标识实体。例如一张 experiment 表,字段为 exp_id, sample_id, temperature, duration, operator。映射时以主键构造 IRI,如 http://lab.example.org/exp/{exp_id},每个非主键字段映射为一个谓词,值按数据类型转为 xsd:decimal、xsd:integer 或字符串字面量。外键 sample_id 不应作为普通字符串,而应生成指向样品实体的对象属性,形成表间关联。
CSV/Excel 实验记录没有主键约束,需要人为构造标识。常见做法是用“文件名+行号”或“实验编号+重复序号”生成稳定 IRI。CSV 的列名往往不规范(含空格、中文、单位),映射前需建立列名到谓词的对照表,例如把“反应温度(℃)”映射为 ex:reactionTemperature,并在值上附加单位或统一换算为摄氏度的 xsd:decimal。Excel 多工作表时,每个工作表可视为一个类,表名映射为 rdf:type。
JSON 实验日志是嵌套结构,仪器常按时间戳输出多条记录。映射时把日志文件或设备会话作为主语,嵌套对象展开为子资源,数组元素生成多个三元组。时间戳统一转为 xsd:dateTime,数值字段保留原始精度。若同一字段在不同日志中命名不一致(如 temp 与 temperature),需在映射层做别名归一。
4.2 R2RML 与直接映射工具
R2RML 是 W3C 标准的关系数据到 RDF 映射语言,用 RDF 本身描述映射规则。一个典型的 R2RML 映射包含 rr:logicalTable(逻辑表或 SQL 查询)、rr:subjectMap(主语模板)、rr:predicateObjectMap(谓词-宾语映射)。下面是一个针对 experiment 表的映射片段:
@prefix rr: <http://www.w3.org/ns/r2rml#> .
@prefix ex: <http://lab.example.org/> .
<#ExpMap> a rr:TriplesMap ;
rr:logicalTable [ rr:tableName "experiment" ] ;
rr:subjectMap [
rr:template "http://lab.example.org/exp/{exp_id}" ;
rr:class ex:Experiment
] ;
rr:predicateObjectMap [
rr:predicate ex:temperature ;
rr:objectMap [ rr:column "temperature" ;
rr:datatype xsd:decimal ]
] ;
rr:predicateObjectMap [
rr:predicate ex:hasSample ;
rr:objectMap [ rr:template "http://lab.example.org/sample/{sample_id}" ]
] .
R2RML 适合结构稳定、需要精细控制的关系型数据。对于 CSV/Excel 和 JSON,直接映射工具更高效:它们按“列名即谓词、行即主语”的默认规则自动生成三元组,再用少量配置覆盖特殊字段。常见工具包括基于 RML 的处理器、Apache Jena 的 rdfcat、以及 Python 生态中的 rdflib 与 csvw。选择依据是:数据源是否关系型、是否需要复杂连接、是否需要复用映射规则。
4.3 映射实战:从一份实验记录到三元组
假设有一份 CSV 实验记录 exp_2024.csv,内容如下(示例数据):
exp_id,sample_id,reaction_temp_C,duration_min,operator
E001,S-A,85.0,120,张工
E002,S-B,90.5,90,李工
E003,S-A,78.0,150,张工
目标:生成以 ex:Experiment 为类、以实验编号构造 IRI 的三元组,并把温度统一为 xsd:decimal、时长统一为 xsd:integer,样品外键转为对象属性。
使用 Python + rdflib 的实现思路如下:读取 CSV,逐行构造主语 IRI;对每一列建立谓词映射字典;对数值列做类型转换;对 sample_id 生成样品 IRI 并作为对象属性。关键代码骨架:
import csv
from rdflib import Graph, Namespace, Literal, URIRef
from rdflib.namespace import RDF, XSD
EX = Namespace("http://lab.example.org/")
g = Graph()
pred_map = {
"reaction_temp_C": (EX.reactionTemperature, XSD.decimal),
"duration_min": (EX.duration, XSD.integer),
"operator": (EX.operator, None),
}
with open("exp_2024.csv", encoding="utf-8") as f:
for row in csv.DictReader(f):
subj = URIRef(EX[f"exp/{row['exp_id']}"])
g.add((subj, RDF.type, EX.Experiment))
g.add((subj, EX.hasSample, URIRef(EX[f"sample/{row['sample_id']}"])))
for col, (pred, dtype) in pred_map.items():
val = row[col]
obj = Literal(val, datatype=dtype) if dtype else Literal(val)
g.add((subj, pred, obj))
g.serialize("exp_2024.ttl", format="turtle")
运行后得到的三元组(节选)为:
ex:exp/E001 a ex:Experiment ;
ex:hasSample ex:sample/S-A ;
ex:reactionTemperature "85.0"^^xsd:decimal ;
ex:duration "120"^^xsd:integer ;
ex:operator "张工" .
判断映射是否成功,可以检查三点:一是每个实验实体都有 rdf:type;二是数值字段的数据类型正确,没有变成字符串;三是外键确实生成了对象属性而非字面量。若 CSV 中存在空值,应决定是跳过该三元组还是写入 ex:missingValue 标记,避免产生无意义的三元组。
对于 JSON 日志,映射思路类似,但需要先展开嵌套结构。例如一条日志 {"device":"D1","ts":"2024-03-01T10:00:00","readings":[{"temp":85.0},{"temp":86.2}]},可把设备会话作为主语,readings 数组的每个元素生成一个带时间戳的观测子资源,temp 映射为 ex:temperature。这样既保留了原始时序,又能在图谱中按设备、时间、温度三个维度查询。
三类数据源的映射最终都汇聚到同一套本体词汇上,这是后续实体对齐与关联推理的基础。映射规则一旦稳定,就应固化为可版本管理的配置文件或脚本,而不是每次手工重写。