obtain the matrix in protege

2020-02-06 10:14发布

问题:

My work is about library book of recommendation systems . that as input I need book Classification ontology . in my ontology classify library books. this classification has 14 categories, beside the sibling classes Author, book, Isbn. Individuals in book class are book’s subject(about 600 subjects) , and individuals in author class are name’s author and also isbn class. I design this ontology with protege 4.1.

also I collected and Have got in part of belong book to categories manually. That a object properties is name “hasSubject” related individual book class with categories. Example book “A” hasSubject Categories “S” and “F” and... As result I want to get the matrix belonging to Book Categories. This is the way that if the book belongs to a categories then get 1 and Otherwise Takes the value 0. Like this:

     cat1   cat2   cat3   
book1   1      0      0   
book2   1      0      1   
book3   1      1      0  

In this example Expresses that book1 belong to category 1 and Does not belong category 2 and 3. How can I do this work with sparql in protege?

回答1:

Handling a fixed number of categories

Given data like

@prefix : <http://example.org/books/> .

:book1 a :Book, :Cat1 .
:book2 a :Book, :Cat1, :Cat3 .
:book3 a :Book, :Cat1, :Cat2 .

you can use a query like

prefix : <http://example.org/books/>

select ?individual
       (if(bound(?cat1),1,0) as ?Cat1)
       (if(bound(?cat2),1,0) as ?Cat2)
       (if(bound(?cat3),1,0) as ?Cat3)
where {
  ?individual a :Book .
  OPTIONAL { ?individual a :Cat1 . bind( ?individual as ?cat1 ) } 
  OPTIONAL { ?individual a :Cat2 . bind( ?individual as ?cat2 ) }
  OPTIONAL { ?individual a :Cat3 . bind( ?individual as ?cat3 ) }
}
order by ?book

in which certain variables are bound (the particular value to which they are bound doesn't really matter though) based on the whether certain triples are present to get results like these:

$ arq --data data.n3 --query matrix.sparql
-----------------------------------
| individual | Cat1 | Cat2 | Cat3 |
===================================
| :book1     | 1    | 0    | 0    |
| :book2     | 1    | 0    | 1    |
| :book3     | 1    | 1    | 0    |
-----------------------------------

Handling an arbitrary number of categories

Here's a solution that seems to work in Jena, though I'm not sure that the specific results are guaranteed. (Update: Based on this answers.semanticweb.com question and answer, it seems that this behavior is not guaranteed by the SPARQL specification.) If we have a little bit more data, e.g., about which things are categories and which are books, e.g.,

@prefix : <http://example.org/books/> .

:book1 a :Book, :Cat1 .
:book2 a :Book, :Cat1, :Cat3 .
:book3 a :Book, :Cat1, :Cat2 .

:Cat1 a :Category .
:Cat2 a :Category .
:Cat3 a :Category .

then we can run a subquery that selects all the categories in order, and then for each book computes a string indicating whether or not the book is in each category.

prefix : <http://example.org/books/>

select ?book (group_concat(?isCat) as ?matrix) where { 
  { 
    select ?category where { 
      ?category a :Category 
    }
    order by ?category 
  }
  ?book a :Book .
  OPTIONAL { bind( 1 as ?isCat )              ?book a ?category . }
  OPTIONAL { bind( 0 as ?isCat ) NOT EXISTS { ?book a ?category } }
}
group by ?book
order by ?book

This has the output:

$ arq --data data.n3 --query matrix2.query
--------------------
| book   | matrix  |
====================
| :book1 | "1 0 0" |
| :book2 | "1 0 1" |
| :book3 | "1 1 0" |
--------------------

which is much closer to the output in the question, and handles an arbitrary number categories. However, it depends on the values of ?category being processed in the same order for each ?book, and I'm not sure whether that's guaranteed or not.

We can even use this approach to generate a header row for the table. Again, this depends on the ?category values being processed in the same order for each ?book, which might not be guaranteed, but seems to work in Jena. To get a category header, all we need to do is create a row where ?book is unbound, and the value of the ?isCat indicates the particular category:

prefix : <http://example.org/books/>

select ?book (group_concat(?isCat) as ?matrix) where { 
  { 
    select ?category where { 
      ?category a :Category 
    }
    order by ?category 
  }

  # This generates the header row where ?isCat is just
  # the category, so the group_concat gives headers.
  { 
    bind(?category as ?isCat) 
  }
  UNION 
  # This is the table as before
  {
    ?book a :Book .
    OPTIONAL { bind( 1 as ?isCat )              ?book a ?category . }
    OPTIONAL { bind( 0 as ?isCat ) NOT EXISTS { ?book a ?category } }
  }
}
group by ?book
order by ?book

We get this output:

--------------------------------------------------------------------------------------------------------
| book   | matrix                                                                                      |
========================================================================================================
|        | "http://example.org/books/Cat1 http://example.org/books/Cat2 http://example.org/books/Cat3" |
| :book1 | "1 0 0"                                                                                     |
| :book2 | "1 0 1"                                                                                     |
| :book3 | "1 1 0"                                                                                     |
--------------------------------------------------------------------------------------------------------

Using some string manipulation, you could shorten the URIs used for the categories, or widen the array entries to get correct alignment. One possibility is this:

prefix : <http://example.org/books/>

select ?book (group_concat(?isCat) as ?categories) where { 
  { 
    select ?category
           (strafter(str(?category),"http://example.org/books/") as ?name)
     where { 
      ?category a :Category 
    }
    order by ?category 
  }

  { 
    bind(?name as ?isCat)
  }
  UNION 
  {
    ?book a :Book .
    # The string manipulation here takes the name of the category (which should
    # be at least two character), trims off the first character (string indexing
    # in XPath functions starts at 1), and replaces the rest with " ". The resulting
    # spaces are concatenated with "1" or "0" depending on whether the book is a
    # member of the category.  The resulting string has the same width as the
    #  category name, and makes for a nice table.
    OPTIONAL { bind( concat(replace(substr(?name,2),"."," "),"1") as ?isCat )              ?book a ?category . }
    OPTIONAL { bind( concat(replace(substr(?name,2),"."," "),"0") as ?isCat ) NOT EXISTS { ?book a ?category } }
  }
}
group by ?book
order by ?book

which produces this output:

$ arq --data data.n3 --query matrix3.query
-----------------------------
| book   | categories       |
=============================
|        | "Cat1 Cat2 Cat3" |
| :book1 | "   1    0    0" |
| :book2 | "   1    0    1" |
| :book3 | "   1    1    0" |
-----------------------------

which is almost exactly what you had in the question.