您可以使用 SPARQL 计算这种度量,尽管它有点难看。让我们假设一些这样的数据:
prefix dcterms: <http://purl.org/dc/terms/>
prefix : <http://example.org/books/>
:book1 a :Book ; dcterms:subject :subject1 , :subject2, :subject3 .
:book2 a :Book ; dcterms:subject :subject2 , :subject3, :subject4 .
:book3 a :Book ; dcterms:subject :subject4 , :subject5 .
有三本书。第 1 册和第 2 册有两个共同的主题,并且各有一个,而另一个则没有。第 2 册和第 3 册有一个共同主题,但第 2 册有第 3 册没有的 2 个主题,而第 3 册只有第 2 册没有的主题,第 1 册和第 3 册没有共同主题。
这里的技巧是使用一些嵌套的子查询,并在嵌套的不同级别获取不同的值(C10、C01 和 C11)。最里面的查询是
select ?book1 ?book2 (count(?left) as ?c10) where {
:Book ^a ?book1, ?book2 .
FILTER( !sameTerm(?book1,?book2) )
OPTIONAL {
?book1 dcterms:subject ?left .
FILTER NOT EXISTS { ?book2 dcterms:subject ?left }
}
}
group by ?book1 ?book2
它抓取每一对不同的书,并计算左书有而右书没有的科目数量。通过将其包装在另一个查询中,我们可以获取右侧书籍具有而左侧书籍没有的主题数量。这使得查询:
select ?book1 ?book2 (count(?right) as ?c01x) (sample(?c10) as ?c10x) where {
{
select ?book1 ?book2 (count(?left) as ?c10) where {
:Book ^a ?book1, ?book2 .
FILTER( !sameTerm(?book1,?book2) )
OPTIONAL {
?book1 dcterms:subject ?left .
FILTER NOT EXISTS { ?book2 dcterms:subject ?left }
}
}
group by ?book1 ?book2
}
OPTIONAL {
?book2 dcterms:subject ?right .
FILTER NOT EXISTS { ?book1 dcterms:subject ?right }
}
}
group by ?book1 ?book2
请注意,我们仍然必须选择?book1
and ?book2
,sample(?c10) as ?c10x
以便向外传递值。(我们必须使用该名称,?c10x
因为该名称?c10
已在此范围内使用。最后,我们将其包装在另一个查询中以获取常见主题并进行计算,这给了我们:
prefix dcterms: <http://purl.org/dc/terms/>
prefix : <http://example.org/books/>
select ?book1 ?book2
(count(?both) as ?c11)
(sample(?c10x) as ?c10)
(sample(?c01x) as ?c01)
(count(?both) / (count(?both) + sample(?c10x) + sample(?c01x)) as ?sim)
where {
{
select ?book1 ?book2 (count(?right) as ?c01x) (sample(?c10) as ?c10x) where {
{
select ?book1 ?book2 (count(?left) as ?c10) where {
:Book ^a ?book1, ?book2 .
FILTER( !sameTerm(?book1,?book2) )
OPTIONAL {
?book1 dcterms:subject ?left .
FILTER NOT EXISTS { ?book2 dcterms:subject ?left }
}
}
group by ?book1 ?book2
}
OPTIONAL {
?book2 dcterms:subject ?right .
FILTER NOT EXISTS { ?book1 dcterms:subject ?right }
}
}
group by ?book1 ?book2
}
OPTIONAL {
?both ^dcterms:subject ?book1, ?book2 .
}
}
group by ?book1 ?book2
order by ?book1 ?book2
这个相当可怕的查询,应用于我们的数据,计算出以下结果:
$ arq --data data.n3 --query similarity.sparql
--------------------------------------------
| book1 | book2 | c11 | c10 | c01 | sim |
============================================
| :book1 | :book2 | 2 | 1 | 1 | 0.5 |
| :book1 | :book3 | 0 | 3 | 2 | 0.0 |
| :book2 | :book1 | 2 | 1 | 1 | 0.5 |
| :book2 | :book3 | 1 | 2 | 1 | 0.25 |
| :book3 | :book1 | 0 | 2 | 3 | 0.0 |
| :book3 | :book2 | 1 | 1 | 2 | 0.25 |
--------------------------------------------
如果FILTER( !sameTerm(?book1,?book2) )
删除该行,从而计算每本书与其自身的相似度,我们会看到正确的值 (1.0):
$ arq --data data.n3 --query similarity.sparql
--------------------------------------------
| book1 | book2 | c11 | c10 | c01 | sim |
============================================
| :book1 | :book1 | 3 | 0 | 0 | 1.0 |
| :book1 | :book2 | 2 | 1 | 1 | 0.5 |
| :book1 | :book3 | 0 | 3 | 2 | 0.0 |
| :book2 | :book1 | 2 | 1 | 1 | 0.5 |
| :book2 | :book2 | 3 | 0 | 0 | 1.0 |
| :book2 | :book3 | 1 | 2 | 1 | 0.25 |
| :book3 | :book1 | 0 | 2 | 3 | 0.0 |
| :book3 | :book2 | 1 | 1 | 2 | 0.25 |
| :book3 | :book3 | 2 | 0 | 0 | 1.0 |
--------------------------------------------
如果您不需要保留各种 Cmn 值,那么您可以优化这一点,例如,通过计算最里面的查询中的 C01 和中间查询的下一个中的 C10 ,而不是单独投影两者, 只乘以它们的总和 (C10+C01),以便在计算 C11 的最外层查询中,只需执行 (C11 / (C11 + (C10+C01)))。