Posted:August 31, 2020

Two Standards Come Pre-packaged with Owlready2

We introduce OWL (knowledge graph) reasoners in this installment of the Cooking with Python and KBpedia series. A reasoner has two purposes. First, based on deductive reasoning, a reasoner can infer new class and property assignments that are logically entailed by the assertions in an ontology (that is, from its axioms) but not otherwise explicitly stated. Once inferred, these additional assignments can be written to an inferred version of the ontology for faster lookups and analysis. Second, reasoners can evaluate the stated axioms to determine if the ontology is consistent or satisfiable. This second purpose is a key step when building or modifying a knowledge graph to ensure that illogical assertions are not introduced into the system. Reasoners thus often have explanation routines that point out where the inconsistencies or problems occur, thus helping the analyst to fix the errors before committing to productive use. In later installments we will focus especially on these coherency tests when we discuss the build procedures for KBpedia.

Consistency is when none of the assertions (axioms) in the knowledge graph contradicts another one. If there is a contradiction the graph is termed to be inconsistent. Satisfiability individually evaluates the classes in the graph and checks to see if they can have instances without contradicting other asserted axioms. Unsatisfiability indicates there is a conflicting or missing assignment that needs to be corrected. It is a particularly useful check when there are disjoint assertions made between classes, one of the design pillars of KBpedia.

Owlready2 is distributed with two OWL reasoners:

  • HermiT, developed by the department of Computer Science of the University of Oxford, and
  • Pellet, a reasoner developed specifically to support the OWL language.

Both HermiT and Pellet are written in Java, so require access to a JVM on your system. If you have difficulty running these systems it is likely because you: 1) do not have a recent version of Java installed on your system; or 2) do not have a proper PATH statement in your environmental variables to find the Java executable. If you encounter such problems, please consult third-party sources to get Java properly configured for your system before continuing with this installment.

Test Ontology

To make sure that your system is configured properly, go ahead and shift+enter or Run this cell that enters a small example ontology from the owlready2 documentation:

from owlready2 import *

onto = get_ontology("http://test.org/onto.owl")

with onto:
    class Drug(Thing):
        def take(self): print("I took a drug")

    class ActivePrinciple(Thing):
        pass

    class has_for_active_principle(Drug >> ActivePrinciple):
        python_name = "active_principles"

    class Placebo(Drug):
        equivalent_to = [Drug & Not(has_for_active_principle.some(ActivePrinciple))]
        def take(self): print("I took a placebo")

    class SingleActivePrincipleDrug(Drug):
        equivalent_to = [Drug & has_for_active_principle.exactly(1, ActivePrinciple)]
        def take(self): print("I took a drug with a single active principle")

    class DrugAssociation(Drug):
        equivalent_to = [Drug & has_for_active_principle.min(2, ActivePrinciple)]
        def take(self): print("I took a drug with %s active principles" % len(self.active_principles))

acetaminophen   = ActivePrinciple("acetaminophen")
amoxicillin     = ActivePrinciple("amoxicillin")
clavulanic_acid = ActivePrinciple("clavulanic_acid")

AllDifferent([acetaminophen, amoxicillin, clavulanic_acid])

drug1 = Drug(active_principles = [acetaminophen])
drug2 = Drug(active_principles = [amoxicillin, clavulanic_acid])
drug3 = Drug(active_principles = [])

close_world(Drug)

Then, run the HermiT reasoner with the single command:

sync_reasoner()
* Owlready2 * Running HermiT...
java -Xmx2000M -cp C:\1-PythonProjects\Python\lib\site-packages\owlready2\hermit;C:\1-PythonProjects\Python\lib\site-packages\owlready2\hermit\HermiT.jar org.semanticweb.HermiT.cli.CommandLine -c -O -D -I file:///C:/Users/mike/AppData/Local/Temp/tmpbnxf7755
* Owlready2 * HermiT took 0.4851553440093994 seconds
* Owlready * Reparenting onto.drug2: {onto.Drug} => {onto.DrugAssociation}
* Owlready * Reparenting onto.drug1: {onto.Drug} => {onto.SingleActivePrincipleDrug}
* Owlready * Reparenting onto.drug3: {onto.Drug} => {onto.Placebo}
* Owlready * (NB: only changes on entities loaded in Python are shown, other changes are done but not listed)

The feedback you get to screen should indicate that you are ‘Reparenting’ the three drugs from one class (Drug) to their appropriate sublasses. By the way, you could also place this argument in the command to turn off the debug reports to screen: $ sync_reasoner(debug = 0).

You can also confirm this move for drug2:

print("drug2 new Classes:", drug2.__class__)
drug2 new Classes: onto.DrugAssociation

And, then, in the next three cells, confirm how you took those three drugs:

drug1.take()
I took a drug with a single active principle
drug2.take()
I took a drug with 2 active principles
drug3.take()
I took a placebo

And, last, in the next two cells discover if any inconsistent classes remain (they do not), which is equivalent to a class being assigned to the Nothing class in OWL:

list(default_world.inconsistent_classes())
[]
if Nothing in Drug.equivalent_to:
       print("Drug is inconsistent!")

General Load Method

OK, so now we see the HermiT reasoner is configured properly and working, we are now ready to test our KBpedia knowledge graph. Go ahead and select Kernel → Restart & Clear Output from the main menu to begin the next activities from a clean slate.

Then execute what has become our standard load procedure:

Which environment? The specific load routine you should choose below depends on whether you are using the online MyBinder service (the ‘raw’ version) or local files. The example below is based on using local files (though replace with your own local directory specification). If loading from MyBinder, replace with the lines that are commented (#) out.
main = 'C:/1-PythonProjects/kbpedia/sandbox/kbpedia_reference_concepts.owl'
# main = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kbpedia_reference_concepts.owl'
skos_file = 'http://www.w3.org/2004/02/skos/core' 
kko_file = 'C:/1-PythonProjects/kbpedia/sandbox/kko.owl'
# kko_file = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kko.owl'

from owlready2 import *
world = World()
kb = world.get_ontology(main).load()
rc = kb.get_namespace('http://kbpedia.org/kko/rc/')

skos = world.get_ontology(skos_file).load()
kb.imported_ontologies.append(skos)

kko = world.get_ontology(kko_file).load()
kb.imported_ontologies.append(kko)

HermiT Reasoner

We again invoke the HermiT reasoner:

sync_reasoner()
* Owlready2 * Running HermiT...
java -Xmx2000M -cp C:\1-PythonProjects\Python\lib\site-packages\owlready2\hermit;C:\1-PythonProjects\Python\lib\site-packages\owlready2\hermit\HermiT.jar org.semanticweb.HermiT.cli.CommandLine -c -O -D -I file:///C:/Users/mike/AppData/Local/Temp/tmpxglvdub2
* Owlready2 * HermiT took 0.42046189308166504 seconds
* Owlready * (NB: only changes on entities loaded in Python are shown, other changes are done but not listed)

There is also an argument to infer_property_values = True:

sync_reasoner(infer_property_values = True)
* Owlready2 * Running HermiT...
java -Xmx2000M -cp C:\1-PythonProjects\Python\lib\site-packages\owlready2\hermit;C:\1-PythonProjects\Python\lib\site-packages\owlready2\hermit\HermiT.jar org.semanticweb.HermiT.cli.CommandLine -c -O -D -I file:///C:/Users/mike/AppData/Local/Temp/tmpxkc92ws4 -Y
* Owlready2 * HermiT took 0.416165828704834 seconds
* Owlready * (NB: only changes on entities loaded in Python are shown, other changes are done but not listed)

We see that the ontology is consistent, which we can confirm with this additional command:

list(world.inconsistent_classes())
[]

Pellet Reasoner

The second of our reasoners, Pellet, operates under a similar set of arguments. We invoke Pellet through the modified reasoner command:.

sync_reasoner_pellet()
* Owlready2 * Running Pellet...
java -Xmx2000M -cp C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\antlr-3.2.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\antlr-runtime-3.2.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\aterm-java-1.6.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\commons-codec-1.6.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\httpclient-4.2.3.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\httpcore-4.2.2.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jcl-over-slf4j-1.6.4.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-arq-2.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-core-2.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-iri-0.9.5.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-tdb-0.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jgrapht-jdk1.5.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\log4j-1.2.16.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\owlapi-distribution-3.4.3-bin.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\pellet-2.3.1.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\slf4j-api-1.6.4.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\slf4j-log4j12-1.6.4.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\xercesImpl-2.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\xml-apis-1.4.01.jar pellet.Pellet realize --loader Jena --input-format N-Triples --ignore-imports C:\Users\mike\AppData\Local\Temp\tmp7_rotl4_
* Owlready2 * Pellet took 1.017836093902588 seconds
* Owlready * (NB: only changes on entities loaded in Python are shown, other changes are done but not listed)

Pellet, too, is configured to run in a debug mode. If you wish, you may turn it off with $ sync_reasoner(debug = 0).

Like HermiT we can also infer_property_values. But, different than HermiT, we may also infer_data_property_values = True using Pellet:

sync_reasoner_pellet(infer_property_values = True, infer_data_property_values = True)
* Owlready2 * Running Pellet...
java -Xmx2000M -cp C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\antlr-3.2.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\antlr-runtime-3.2.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\aterm-java-1.6.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\commons-codec-1.6.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\httpclient-4.2.3.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\httpcore-4.2.2.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jcl-over-slf4j-1.6.4.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-arq-2.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-core-2.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-iri-0.9.5.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jena-tdb-0.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\jgrapht-jdk1.5.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\log4j-1.2.16.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\owlapi-distribution-3.4.3-bin.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\pellet-2.3.1.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\slf4j-api-1.6.4.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\slf4j-log4j12-1.6.4.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\xercesImpl-2.10.0.jar;C:\1-PythonProjects\Python\lib\site-packages\owlready2\pellet\xml-apis-1.4.01.jar pellet.Pellet realize --loader Jena --input-format N-Triples --infer-prop-values --infer-data-prop-values --ignore-imports C:\Users\mike\AppData\Local\Temp\tmpcr2dw8yi
* Owlready2 * Pellet took 0.6863009929656982 seconds
* Owlready * (NB: only changes on entities loaded in Python are shown, other changes are done but not listed)
list(world.inconsistent_classes())
[]

SWRL

As long as we are introducing these capabilities, we should also mention that Owlready2 also supports the use of SWRL (the Semantic Web Rule Language) “if . . . then” type statements. To the best of my knowledge, Owlready2 supports all of the standard SWRL constructs. It is also possible to mix Python and OWL code together, but that, too, is a topic we will not be addressing further in this CWPK series.

Save and Exit

When we are finished with our tests, we can File → Save and Checkpoint, Rename our output file, or specify it at the command line:

kb.save(file = 'files/kbpedia_reference_concepts-pellet.owl', format = 'rdfxml')

Additional Documentation

Here are links to appropriate Owlready2 documentation:

NOTE: This article is part of the Cooking with Python and KBpedia series. See the CWPK listing for other articles in the series. KBpedia has its own Web site.
NOTE: This CWPK installment is available both as an online interactive file or as a direct download to use locally. Make sure and pick the correct installment number. For the online interactive option, pick the *.ipynb file. It may take a bit of time for the interactive option to load.
I am at best an amateur with Python. There are likely more efficient methods for coding these steps than what I provide. I encourage you to experiment — which is part of the fun of Python — and to notify me should you make improvements.

Posted by AI3's author, Mike Bergman Posted on August 31, 2020 at 11:01 am in CWPK, KBpedia, Semantic Web Tools | Comments (1)
The URI link reference to this post is: https://www.mkbergman.com/2360/cwpk-26-introduction-to-knowledge-graph-reasoners/
The URI to trackback this post is: https://www.mkbergman.com/2360/cwpk-26-introduction-to-knowledge-graph-reasoners/trackback/
Posted:August 28, 2020

Now, We Open Up the Power

In our recent installments we have been looking at how to search — ultimately, of course, related to how to extract — information from our knowledge graph, KBpedia, and the various large-scale knowledge bases to which it maps, such as Wikipedia, DBpedia, and Wikidata. We’ve seen that owlready2 offers us some native search capabilities, and that we can extend that by indexing additional attributes. What is powerful about knowledge graphs, however, is that all nodes and all edges are structural from the get-go, and we can easily add meaningful structure to our searches by how we represent the pieces (nodes) and by how we relate, or connect, them using the edges.

Today’s knowledge graphs are explicit in organizing information by structure. The exact scope of this structure varies across representations, and certainly one challenge in getting information to work together from multiple locations and provenances is the diversity of these representations. Those are the questions of semantics, and, fortunately, semantic technologies and parsers give us rich ways to retrieve and relate that structure. So, great, we now have structure galore! What are we going to do with it?

Well, this structured information exists, literally, everywhere. We have huge online structured datestores, trillions of semi-structured Web pages and records, and meaningful information and analysis across a rich pastiche of hierarchies and relationships. What is clear in any attempt to solve a meaningful problem is that we need much external information as well as much grounding in our internal circumstances. Problem solving can not be separated from obtaining and integrating meaningful information.

Thus, it is essential that we be able to query external information stores on an equivalent basis to our local ones. This equivalence requires both internal and external sources be structured and queriable on an equivalent basis, which is where the W3C-enabled standards and SPARQL come in.

The Role of SPARQL

I think one can argue that the purpose of semantic technologies like RDF and OWL is to enable a machine-readable format for human symbolic information. As a result, we now have a rich suite of standards and implementations using those standards.

The real purpose, and advantage, of SPARQL is to make explicit all of the structural aspects of a knowledge graph to inspection and query. Because of this intimate relationship, SPARQL is more often than not the most capable and precise language for extracting information from ontologies or knowledge graphs. SPARQL, pronounced “sparkle”, is a recursive acronym for SPARQL Protocol and RDF Query Language, and has many syntactical and structural parallels with the SQL database query language.

All explicit assignments of a semantic term in RDF or OWL or their semantic derivatives can be used as a query basis in SPARQL. Thus, SPARQL is the sine qua non option for obtaining information from an ontology or knowledge graph. SPARQL is the most flexible and responsive way to manipulate a semantically structured information store.

Let’s inspect the general components of a SPARQL query specification:

SPARQL Query Specification
Figure 1: SPARQL Query Specification

This figure is from Lee Feigenbaum’s SPARQL slides, included with other useful links under the Additional Documentation below.

Note that every SPARQL query gets directed to a specific endpoint, where access to the underlying RDF datastore takes place. These endpoints can be either local or accessed via the Web, with both examples shown below. In a standalone situation, the endpoint location is indicated by the FROM keyword. In our examples using RDFLib via Owlready2, these locations are set to a Python object.

Extended Startup

Let’s start again with the start-up script we used in the last installment, only now also opening rdflib and relating its namespace graph to the world namespace of KBpedia.

Which environment? The specific load routine you should choose below depends on whether you are using the online MyBinder service (the ‘raw’ version) or local files. The example below is based on using local files (though replace with your own local directory specification). If loading from MyBinder, replace with the lines that are commented (#) out.
main = 'C:/1-PythonProjects/kbpedia/sandbox/kbpedia_reference_concepts.owl'
# main = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kbpedia_reference_concepts.owl'
skos_file = 'http://www.w3.org/2004/02/skos/core' 
kko_file = 'C:/1-PythonProjects/kbpedia/sandbox/kko.owl'
# kko_file = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kko.owl'

from owlready2 import *
world = World()
kb = world.get_ontology(main).load()
rc = kb.get_namespace('http://kbpedia.org/kko/rc/')

skos = world.get_ontology(skos_file).load()
kb.imported_ontologies.append(skos)

kko = world.get_ontology(kko_file).load()
kb.imported_ontologies.append(kko)

import rdflib

graph = world.as_rdflib_graph()

We could have put the import statement for the RDFLib package at the top, but anywhere prior to formatting the query is fine.

We now may manipulate the knowledge graph as we would in a standard way using (in this case) the namespace world for owlready2 and access all of the additional functionality available via RDFLib using the (in this case) the graph namespace. This is a great example of the Python ecosystem at work.

Further, because of even greater integration, there are some native commands in Owlready2 that have been mapped to RDFLib making the syntax and conventions in working with both libraries easier.

Basic SPARQL Forms

In the last installment we presented two wrinkles for how to express your SPARQL queries to your local datastore. This form I noted looked closer to a standard SPARQL expression shown in Figure 1:

form_1 = list(graph.query_owlready("""
  PREFIX rc: <http://kbpedia.org/kko/rc/>
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?x ?label
  WHERE
  {
    ?x rdfs:subClassOf rc:Mammal.
    ?x skos:prefLabel  ?label. 
  }
"""))

print(form_1)
[[rc.AbominableSnowman, 'abominable snowman'], [rc.Afroinsectiphilia, 'Afroinsectiphilia'], [rc.Eutheria, 'placental mammal'], [rc.Marsupial, 'pouched mammal'], [rc.Australosphenida, 'Australosphenida'], [rc.Bigfoot, 'Sasquatch'], [rc.Monotreme, 'monotreme'], [rc.Vampire, 'vampire'], [rc.Werewolf, 'werewolf']]
* Owlready2 * Warning: ignoring cyclic subclass of/subproperty of, involving:
http://kbpedia.org/kko/rc/Person
http://kbpedia.org/kko/rc/HomoSapiens

The query above has a warning message we can ignore and lists all of the direct sub-classes to Mammal in KBpedia.

The last installment also offered a second form, which is the one I will be using hereafter. I am doing so because this form, and its further abstraction, is a more repeatable approach. In general, this advantage is because we can take this format and abstract it into a ‘wrapper’ that encapsulates the method of making the SPARQL call separate, abstracted from the actual SPARQL specification. We will increasingly touch on these topics, but for now this is the format we will take:

form_2 = """
  PREFIX rc: <http://kbpedia.org/kko/rc/>
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?x ?label
  WHERE
  {
    ?x rdfs:subClassOf rc:Mammal.
    ?x skos:prefLabel  ?label. 
  }
"""

results = list(graph.query_owlready(form_2))
print(results)
[[rc.AbominableSnowman, 'abominable snowman'], [rc.Afroinsectiphilia, 'Afroinsectiphilia'], [rc.Eutheria, 'placental mammal'], [rc.Marsupial, 'pouched mammal'], [rc.Australosphenida, 'Australosphenida'], [rc.Bigfoot, 'Sasquatch'], [rc.Monotreme, 'monotreme'], [rc.Vampire, 'vampire'], [rc.Werewolf, 'werewolf']]

These two examples cover how to access the local datastore.

External SPARQL Examples

We really like what we have seen with the SPARQL querying of the internal data store using RDFLib within Owlready2. But what of querying outside sources. (And, would it not be cool to be able to mix-and-match internal and external stuff?)

As we try to use RDFLib as is against external SPARQL endpoints we quickly see that we are not adequately identifying and talking with these sites. Well, we have been here before, but the nature of stuff with Python and packages and dependencies and such often requires another capability.

Some quick poking turns up that we are lacking a HTTP-aware ‘wrapper’ to external sites. We turn up a promising package in sparqlwrapper. We discover it is on conda-forge so we back out the system, and at the command line add the package:

$ conda install sparqlwrapper

We again get the feedback to the screen as the Anaconda configuration manager does its thing. When finally installed and the prompt returns, we again load up Jupyter Notebook and return to this notebook page.

We are now ready to try our first external example, this time to Wikidata, after we import SPARQLwrapper and set our endpoint target to Wikidata (https://query.wikidata.org/sparql):

from SPARQLWrapper import SPARQLWrapper, JSON
from rdflib import Graph

sparql = SPARQLWrapper("https://query.wikidata.org/sparql")

sparql.setQuery("""
  PREFIX schema: <http://schema.org/>
  SELECT ?item ?itemLabel ?wikilink ?itemDescription ?subClass ?subClassLabel WHERE {
  VALUES ?item { wd:Q25297630
  wd:Q537127
  wd:Q16831714
  wd:Q24398318
  wd:Q11755880
  wd:Q681337
}
  ?item wdt:P910 ?subClass.

  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
""")
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
print(results)
{'head': {'vars': ['item', 'itemLabel', 'wikilink', 'itemDescription', 'subClass', 'subClassLabel']}, 'results': {'bindings': [{'item': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q537127'}, 'subClass': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q8667674'}, 'itemLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'road bridge'}, 'itemDescription': {'xml:lang': 'en', 'type': 'literal', 'value': 'bridge that carries road traffic'}, 'subClassLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'Category:Road bridges'}}, {'item': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q11755880'}, 'subClass': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q8656043'}, 'itemLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'residential building'}, 'itemDescription': {'xml:lang': 'en', 'type': 'literal', 'value': 'building mainly used for residential purposes'}, 'subClassLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'Category:Residential buildings'}}, {'item': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q16831714'}, 'subClass': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q6259373'}, 'itemLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'government building'}, 'itemDescription': {'xml:lang': 'en', 'type': 'literal', 'value': 'building built for and by the government, such as a town hall'}, 'subClassLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'Category:Government buildings'}}, {'item': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q24398318'}, 'subClass': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q5655238'}, 'itemLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'religious building'}, 'itemDescription': {'xml:lang': 'en', 'type': 'literal', 'value': 'building intended for religious worship or other activities related to a religion; ceremonial structures that are related to or concerned with religion'}, 'subClassLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'Category:Religious buildings and structures'}}, {'item': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q25297630'}, 'subClass': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q7344076'}, 'itemLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'international bridge'}, 'itemDescription': {'xml:lang': 'en', 'type': 'literal', 'value': 'bridge built across a geopolitical boundary'}, 'subClassLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'Category:International bridges'}}]}}

Great! It works, and our first information retrieval from an external site!

Let me point out a couple of things about this format. First, the endpoint already has some built-in prefixes (wd: and wdt:) so we did not need to declare them in the query header. Second, there are some unique query capabilities of the Wikidata site noted by the SERVICE designation.

When first querying a new site it is perhaps best to stick to vanilla forms of SPARQL, but as one learns more it is possible to tailor queries more specifically. We also see that our setup will allow us to take advantage of what each endpoint gives us.

So, let’s take another example, this one using the DBpedia endpoint, to show how formats may also differ from endpoint to endpoint:

from SPARQLWrapper import SPARQLWrapper, RDFXML
from rdflib import Graph

sparql = SPARQLWrapper("http://dbpedia.org/sparql")

sparql.setQuery("""
    PREFIX dbo: <http://dbpedia.org/ontology/>
    PREFIX schema: <http://schema.org/>

    CONSTRUCT {
      ?lang a schema:Language ;
      schema:alternateName ?iso6391Code .
    }
    WHERE {
      ?lang a dbo:Language ;
      dbo:iso6391Code ?iso6391Code .
      FILTER (STRLEN(?iso6391Code)=2) # to filter out non-valid values
    }
""")

sparql.setReturnFormat(RDFXML)
results = sparql.query().convert()
print(results.serialize(format='xml'))

Notice again how the structure of our query code is pretty patterned. We also see in the two examples how we can specify different query results serializations (JSON and RDFXML in these examples) for our results sets.

Additional Documentation

The idea of a SPARQL tutorial is outside of the defined scope of this CWPK series. But, the power of SPARQL is substantial and it is well worth the time to learn more about this flexible language, that reminds one of SQL in many ways, but has its own charms and powers. Here are some great starting links about SPARQL:

NOTE: This article is part of the Cooking with Python and KBpedia series. See the CWPK listing for other articles in the series. KBpedia has its own Web site.
NOTE: This CWPK installment is available both as an online interactive file or as a direct download to use locally. Make sure and pick the correct installment number. For the online interactive option, pick the *.ipynb file. It may take a bit of time for the interactive option to load.
I am at best an amateur with Python. There are likely more efficient methods for coding these steps than what I provide. I encourage you to experiment — which is part of the fun of Python — and to notify me should you make improvements.

Posted by AI3's author, Mike Bergman Posted on August 28, 2020 at 10:36 am in CWPK, KBpedia, Semantic Web Tools | Comments (0)
The URI link reference to this post is: https://www.mkbergman.com/2358/cwpk-25-querying-kbpedia-with-sparql/
The URI to trackback this post is: https://www.mkbergman.com/2358/cwpk-25-querying-kbpedia-with-sparql/trackback/
Posted:August 27, 2020

It’s Time to Add a New Semantic Tool to the Toolbox

In CWPK #17 of this Cooking with Python and KBpedia series, we discussed what we would need in an API to OWL. Our work so far with owlready2 continues to be positive, leading us to believe it will prove out in the end to be the right API solution for our objectives. But in that same CWPK #17 review we also indicated intrigue with the RDFLib option. We know there are some soft spots with owlready2 in areas such as format support for which RDFLib is strong. It is also the case that owlready2 lacks a SPARQL query option, another area in which RDFLib is strong. In fact, the data exchange methods we use in KBpedia rely directly on simple variants of RDF, especially in the N3 notation.

In recognition of these synergies, just has it had in embracing SQLite as a lightweight native quad store, owlready2 has put in place many direct relations to RDFLib, including in the data store. What I had feared would be a difficult challenge of integrating Python, Anaconda, Jupyter Notebook, owlready2, and RDFLib, turned out in fact to be a very smooth process. We introduce the newest RDFLib piece in today’s installment.

RDFLib is a Python library for interacting with the Resource Description Framework (RDF) language. It has been actively maintained over 15 years and is presently in version 5.x. RDFLib is particularly strong in the areas of RDF format support, SPARQL querying of endpoints (including local stores), and CSV file functionality. Our hope in incorporating RDFLib is to provide the most robust RDF/OWL platform available in Python.

Installing RDFLib

Enter this at the command prompt:

$ conda install rdflib

You will see the standard feedback to the terminal that the package is being downloaded and then integrated with the other packages in the system. The simple install command is possible because we had already installed conda-forge as a channel within the Anaconda distribution system for Python as described in CWPK #9.

We are now ready to use RDFLib.

Basic Setup

OK, so we steer ourselves to the 24th installment in the CWPK directory and we fire up the system by invoking the command window from this directory. We enter $ jupyter notebook at the prompt and then proceed through the Jupyter file manager to this cwpk-24-intro-rdflib.ipynb file. We pick it, and then enter our standard set of opening commands to KBpedia:

Which environment? The specific load routine you should choose below depends on whether you are using the online MyBinder service (the ‘raw’ version) or local files. The example below is based on using local files (though replace with your own local directory specification). If loading from MyBinder, use this address for kbpedia_reference_concepts.owl
main = 'C:/1-PythonProjects/kbpedia/sandbox/kbpedia_reference_concepts.owl'
# main = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kbpedia_reference_concepts.owl'
skos_file = 'http://www.w3.org/2004/02/skos/core' 
kko_file = 'C:/1-PythonProjects/kbpedia/sandbox/kko.owl'
# kko_file = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kko.owl'

from owlready2 import *
world = World()
kb = world.get_ontology(main).load()
rc = kb.get_namespace('http://kbpedia.org/kko/rc/')

skos = world.get_ontology(skos_file).load()
kb.imported_ontologies.append(skos)

kko = world.get_ontology(kko_file).load()
kb.imported_ontologies.append(kko)

We could have done this first, but we need to import the RDFLib package into our active environment:

import rdflib

Depending on our use of RDFLib going forward, we could restrict this import to only certain modules in the package, but we load it all in this case.

Now, here is where the neat trick used by owlready2 in working with RDFLib comes into play. RDFLib also uses (in the standard case) SQLite as its backend. So, we point to the namespace graph (could be any name) that RDFLib expects, but we assign it to the namespace (in this case, world) already recognized by owlready2:

graph = world.as_rdflib_graph()

We now may manipulate the knowledge graph as we would in a standard way using (in this case) the namespace world for owlready2 and access all of the additional functionality available via RDFLib using the (in this case) the graph namespace. This is a great example of the Python ecosystem at work.

Further, because of even greater integration, there are some native commands in owlready2 that have been mapped to from RDFLib making the syntax and conventions in working with both libraries easier.

Initial SPARQL Examples

Of course, the reason we brought RDFLib into the picture at this point was to continue our exploration of querying the knowledge graph that began in our last installment, CWPK #23. We devote the next installment to a discussion of SPARQL queries in some depth, but let’s first test to see if our configuration is working properly.

In our first of two examples we present a fairly simple query in SPARQL format to our internal KBpedia reference concept store under the namespace graph.

r = list(graph.query_owlready("""
  PREFIX rc: <http://kbpedia.org/kko/rc/>
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?x ?label
  WHERE
  {
    ?x rdfs:subClassOf rc:Mammal.
    ?x skos:prefLabel  ?label. 
  }
"""))

print(r)
[[rc.AbominableSnowman, 'abominable snowman'], [rc.Afroinsectiphilia, 'Afroinsectiphilia'], [rc.Eutheria, 'placental mammal'], [rc.Marsupial, 'pouched mammal'], [rc.Australosphenida, 'Australosphenida'], [rc.Bigfoot, 'Sasquatch'], [rc.Monotreme, 'monotreme'], [rc.Vampire, 'vampire'], [rc.Werewolf, 'werewolf']]

The above format looks more akin to a standard SPARQL query format. While it is a bit different, the example below is a more Python-like expression. Note as well that the three-quote convention tells Python to expect a multi-line code block:

r = """
  PREFIX rc: <http://kbpedia.org/kko/rc/>
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
  SELECT DISTINCT ?x ?label
  WHERE
  {
    ?x rdfs:subClassOf rc:Mammal.
    ?x skos:prefLabel  ?label. 
  }
"""

results = list(graph.query_owlready(r))
print(results)
[[rc.AbominableSnowman, 'abominable snowman'], [rc.Afroinsectiphilia, 'Afroinsectiphilia'], [rc.Eutheria, 'placental mammal'], [rc.Marsupial, 'pouched mammal'], [rc.Australosphenida, 'Australosphenida'], [rc.Bigfoot, 'Sasquatch'], [rc.Monotreme, 'monotreme'], [rc.Vampire, 'vampire'], [rc.Werewolf, 'werewolf']]

Additional Documentation

In the next installment we will provide SPARQL documentation. Here, however, are a couple of useful links to learn mora about RDFLib and its capabilibies:

NOTE: This article is part of the Cooking with Python and KBpedia series. See the CWPK listing for other articles in the series. KBpedia has its own Web site.
NOTE: This CWPK installment is available both as an online interactive file or as a direct download to use locally. Make sure and pick the correct installment number. For the online interactive option, pick the *.ipynb file. It may take a bit of time for the interactive option to load.
I am at best an amateur with Python. There are likely more efficient methods for coding these steps than what I provide. I encourage you to experiment — which is part of the fun of Python — and to notify me should you make improvements.

Posted by AI3's author, Mike Bergman Posted on August 27, 2020 at 11:03 am in CWPK, KBpedia, Semantic Web Tools | Comments (0)
The URI link reference to this post is: https://www.mkbergman.com/2357/cwpk-24-introduction-to-rdflib/
The URI to trackback this post is: https://www.mkbergman.com/2357/cwpk-24-introduction-to-rdflib/trackback/
Posted:August 26, 2020

Using the Direct Approach with Owlready2

In this installment of the Cooking with Python and KBpedia series, we explore ways to directly search knowledge graph text from within the owlready2 API. We first introduced this topic in CWPK #19; we explain further some of the nuances here.

Recall that owlready2 uses its own local datastore, SQLite, for storing its knowledge graphs. Besides the search functionality added in Owlready2, we will also be taking advantage of the full-text search (FTS) functionality within SQLite.

Load Full Knowledge Graph

To get started, we again load our working knowledge graph. In this instance we will use the full KBpedia knowledge graph, kbpedia_reference_concepts.owl, because it has a richer set of contents.

Which environment? The specific load routine you should choose below depends on whether you are using the online MyBinder service (the ‘raw’ version) or local files. The example below is based on using local files (though replace with your own local directory specification). If loading from MyBinder, use this address for kbpedia_reference_concepts.owl
main = 'C:/1-PythonProjects/kbpedia/sandbox/kbpedia_reference_concepts.owl'
# main = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kbpedia_reference_concepts.owl'
skos_file = 'http://www.w3.org/2004/02/skos/core' 
kko_file = 'C:/1-PythonProjects/owlready2/kg/kko.owl'
# kko_file = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kko.owl'

from owlready2 import *
world = World()
kb = world.get_ontology(main).load()
rc = kb.get_namespace('http://kbpedia.org/kko/rc/')

skos = world.get_ontology(skos_file).load()
kb.imported_ontologies.append(skos)

kko = world.get_ontology(kko_file).load()
kb.imported_ontologies.append(kko)

To execute the load, pick shift+enter to execute the cell contents, or pick Run from the main menu.

Besides changing our absolute file input, note we have added another scoping assignment world to our load. world is a reserved keyword in Owlready2 that encompasses the SQLite storage space used by Owlready2. Note we assign all of our ontologies (knowledge graphs) to this namespace so that we may invoke some of the FTS functionality later in this installment.

Basic Search Functions

As the owlready2 documentation explains, it contains some pre-loaded search capabilities that can be performed with the .search() query method. This method can accept one or several keyword arguments:

  • iri – for searching entities by their full IRIs
  • type – for searching instances for a given class
  • subclass_of – for searching subclasses of a given class
  • is_a – for searching both instances and subclasses of a given class, or object, data or annotation property name.

Special arguments that may be added to these arguments are:

  • _use_str_as_loc_str – whether to treats plain Python strings as strings in any language (default is True)
  • _case_sensitive – whether to take lower/upper case into consideration (default is True).

Our search queries may accept quoted phrases and prefix or suffix wildcards (*). Let’s look at some examples combining these arguments and methods. Our first one is similar to what we presented in CWPK #19:

world.search(iri = "*luggage*")
[]

Notice our result here is an empty set, in other words, no matches. Yet we know there are IRIs in KBpedia that include the term ‘luggage’. We suspect the reason for not seeing a match is that the term might start with upper case in our IRIs. We will set the case sensitivity argument to false and try again:

world.search(iri = "*luggage*", _case_sensitive = False)

Great! We are now seeing the results we expected.

Note in the query above that we used the wildcard (*) to allow for either prefix or suffix matches. As you can see from the results above, most of the search references match the interior part of the IRI string.

The iri argument takes a search string as its assignment. The other three keyword assignments noted above take an object name, as this next example shows:

world.search(subclass_of=rc.Mammal)

We get a tremendous number of matches on this query, so much so that I cleared away the current cell output (via Cell → Current Outputs → Clear, when highlighting this cell). To winnow this results set further, we can combine search terms as the next example shows. We will add to our initial search a string search in the IRIs for which prior results might represent ‘Bats’:

world.search(subclass_of=rc.Mammal, iri = "*Bat*")
[rc.Bat-Mammal, rc.SacWingedBat, rc.BulldogBat, rc.FreeTailedBat, rc.HorseshoeBat, rc.SchreibersBat, rc.WesternSuckerFootedBat, rc.AfricanLongFingeredBat, rc.AfricanYellowBat, rc.AllensYellowBat, rc.AsianPartiColoredBat, rc.DaubentonsBat, rc.EasternRedBat, rc.GreatEveningBat, rc.GreaterTubeNosedBat, rc.GreyLongEaredBat, rc.HawaiianHoaryBat, rc.KobayashisBat, rc.LesserYellowBat, rc.LittleTubeNosedBat, rc.NewGuineaBigEaredBat, rc.NewZealandLongTailedBat, rc.NorthernLongEaredBat, rc.PallidBat, rc.SilverHairedBat, rc.SpottedBat, rc.AfricanSheathTailedBat, rc.AmazonianSacWingedBat, rc.BeccarisSheathTailedBat, rc.ChestnutSacWingedBat, rc.DarkSheathTailedBat, rc.EcuadorianSacWingedBat, rc.EgyptianTombBat, rc.FrostedSacWingedBat, rc.GraySacWingedBat, rc.GreaterSacWingedBat, rc.GreaterSheathTailedBat, rc.GreenhallsDogFacedBat, rc.HamiltonsTombBat, rc.HildegardesTombBat, rc.LargeEaredSheath-TailedBat, rc.LesserSacWingedBat, rc.LesserSheathTailedBat, rc.MauritianTombBat, rc.NorthernGhostBat, rc.PacificSheathTailedBat, rc.PelsPouchedBat, rc.PeterssSheathTailedBat, rc.ProboscisBat, rc.RaffraysSheathTailedBat, rc.SerisSheathtailBat, rc.SeychellesSheathTailedBat, rc.ShaggyBat, rc.ShortEaredBat, rc.SmallAsianSheathTailedBat, rc.TheobaldsTombBat, rc.ThomassSacWingedBat, rc.TroughtonsPouchedBat, rc.AntilleanFruitEatingBat, rc.BidentateYellowEaredBat, rc.BigEaredWoolyBat, rc.CommonVampireBat, rc.HairyLeggedVampireBat, rc.HonduranWhiteBat, rc.LesserLongNosedBat, rc.MexicanLongNosedBat, rc.SpectralBat, rc.VampireBat, rc.WhiteWingedVampireBat, rc.GreaterBulldogBat, rc.LesserBulldogBat, rc.BigCrestedMastiffBat, rc.BigFreeTailedBat, rc.BlackBonnetedBat, rc.BroadEaredBat, rc.EuropeanFreeTailedBat, rc.GallaghersFreeTailedBat, rc.IncanLittleMastiffBat, rc.LittleGoblinBat, rc.MexicanFreeTailedBat, rc.NatalFreeTailedBat, rc.PetersWrinkleLippedBat, rc.SumatranMastiffBat, rc.WroughtonsFreeTailedBat, rc.LesserFalseVampireBat, rc.YellowWingedBat, rc.BigNakedBackedBat, rc.ParnellsMustachedBat, rc.NewZealandGreaterShortTailedBat, rc.NewZealandLesserShortTailedBat, rc.BatesSlitFacedBat, rc.LargeSlitFacedBat, rc.DayakFruitBat, rc.LittleMarianaFruitBat, rc.LivingstonesFruitBat, rc.MarianaFruitBat, rc.PetersDiskWingedBat, rc.Bat-earedFox, rc.AboBat, rc.JapaneseHouseBat, rc.BigBrownBat, rc.NorthernBat, rc.GrayBat, rc.GreaterMouseEaredBat, rc.HodgsonsBat, rc.IkonnikovsBat, rc.IndianaBat, rc.LittleBrownBat, rc.NatterersBat, rc.GreaterNoctuleBat, rc.VirginiaBigEaredBat, rc.GreaterHorseshoeBat, rc.LamottesRoundleafBat, rc.LesserHorseshoeBat, rc.MalayanRoundleafBat, rc.VietnamLeafNosedBat, rc.PersianTridentBat, rc.BondaMastiffBat, rc.VelvetyFreeTailedBat, rc.WesternMastiffBat, rc.MexicanFunnelEaredBat, rc.AndersensFruitEatingBat, rc.JamaicanFruitBat]

Again, we get a large number of results. There are clearly many mammals and bats within the KBpedia reference graph!

Per the listing above, there are a number of these pre-configured search arguments directly available through Owlready2.

We can also instruct the FTS system in SQLite that we want to index still additional fields. Since we are interested in a term we know occurs in KBpedia’s annotations relating some reference concepts to the UN standard products and services codes (UNSPSC) we try that search directly:

world.search(entered = "*UNSPSC*")
[]

Hmm, this tells us there are no results. We must be missing an indexed field. So, let’s instruct the system to add indexing to the definition property where we suspect the reference may occur. We do so using the .append method to add a new field for our RC definitions (skos.definition) to the available FTS index structure:

world.full_text_search_properties.append(skos.definition)

Since this is just a simple assignment, when we Run the cell we get no results output.

However, that assignment now allows us to invoke the internal FTS (full-text search) argument:

world.search(definition = FTS("UNSPSC*"))

If you get an ‘operational error’ that means you did not Run the .append instruction above.

Like some of the other listings, this command results in a very large number of results, a couple of which are warnings we can ignore, so we again Clear the Cell. We can get a smaller listing with another keyword search, this time for the wildcarded ‘gear*’ search:

world.search(definition = FTS("gear*"))
[rc.undercarriage, rc.number-of-forward-gears, rc.vehicle-transmission, rc.AutomaticTransmission, rc.BearingBushingWheelGear, rc.BevelGear, rc.Bicycle-MultiGear, rc.BoeingAH-64Apache, rc.BugattiVeyron, rc.ChildrensWebSite, rc.CombatSportsEvent, rc.Commercialism, rc.CyclingClothing, rc.Device-FunctionallyDefective, rc.FirstNorthAmericansNovels, rc.Fishery, rc.FreeDiving, rc.Game-EquipmentSet, rc.Gear, rc.GearManufacturingMachine, rc.Gearing-Mechanical, rc.GearlessElectricDrive, rc.Goggles, rc.Harness-Animal, rc.Helmet, rc.IlyushinIl-30, rc.LandingGearAssembly, rc.MachineProtocol, rc.Mechanism-Technology, rc.Overdrive-Mechanics, rc.PinionGear, rc.ProtectiveEquipment-Human, rc.ProtectiveGear, rc.ScubaGear, rc.ScubaSnorkelingGear, rc.ShockAndAwe-MilitaryTactic, rc.Supercharger, rc.TeacherTrainingProgram, rc.Trek, rc.Wheel, rc.WildernessBackpacking, rc.WormGear]

Notice in this search that we are able to use the suffix wildcard () character. However, unlike the standard OWLready2 search, we are not able to use a wildcard () prefix search.

Since we have added a new indexed search table to our system, we may want to retain this capability. So, we decide to save the entire graph to the database, as the last example shows:

world.set_backend(filename = 'cwpk-23-text-searching-kbpedia.db', exclusive = False)

This now means our database has been saved persistently to disk.

If you run this multiple times you may get an operational error since you have already set the backend filename.

We can then .save() our work and exit the notebook.

world.save()

Additional Documentation

Here is additional information on the system’s text searching capabilities:

NOTE: This article is part of the Cooking with Python and KBpedia series. See the CWPK listing for other articles in the series. KBpedia has its own Web site.
NOTE: This CWPK installment is available both as an online interactive file or as a direct download to use locally. Make sure and pick the correct installment number. For the online interactive option, pick the *.ipynb file. It may take a bit of time for the interactive option to load.
I am at best an amateur with Python. There are likely more efficient methods for coding these steps than what I provide. I encourage you to experiment — which is part of the fun of Python — and to notify me should you make improvements.

Posted by AI3's author, Mike Bergman Posted on August 26, 2020 at 2:07 pm in CWPK, KBpedia, Semantic Web Tools | Comments (0)
The URI link reference to this post is: https://www.mkbergman.com/2356/cwpk-23-text-searching-kbpedia/
The URI to trackback this post is: https://www.mkbergman.com/2356/cwpk-23-text-searching-kbpedia/trackback/
Posted:August 25, 2020

Some More Basics About Ontology Components

We began looking at managing classes in our last installment of the Cooking with Python and KBpedia series. We continue on that quest in this current installment, expanding our basic commands for instances, data and object properties, annotations, property relations, and operators. We do not probe all available methods, but emphasize those most often encountered. See the Additional Documentation sources at the end of this installment to get further information.

This present installment, plus the prior one, will complete our initial introduction into the ontology management functions available in owlready2. From here we next introduce some of the more advanced features in this API.

To interact with this installment on Jupyter Notebook, we will be using the smaller KKO (Kbpedia Knowledge Ontology), kko.owl, because it is a bit easier to inspect and manipulate.

General Load Method

Note we are continuing to consolidate our initial load statements into a single routine, which we will often use to begin subsequent installments. We also continue to follow the best practice of using logical names to refer to absolute file addresses.

Which environment? The specific load routine you should choose below depends on whether you are using the online MyBinder service (the ‘raw’ version) or local files. The example below is based on using local files (though replace with your own local directory specification). If loading from MyBinder, use this address for kko.owl


main = 'C:/1-PythonProjects/kbpedia/sandbox/kko.owl'

# main = 'https://raw.githubusercontent.com/Cognonto/CWPK/master/sandbox/builds/ontologies/kko.owl'

skos_file = 'http://www.w3.org/2004/02/skos/core'
from owlready2 import * kko = get_ontology(main).load() skos = get_ontology(skos_file).load() kko.imported_ontologies.append(skos)

Having executed these methods with the shift+enter or Run command from the main menu, we can now do a quick inspection to see if everything has loaded properly. We again use a listing of subclasses of the Products class:

list(kko.Products.subclasses())
[kko.PrimarySectorProduct,
kko.SecondarySectorProduct,
kko.TertiarySectorService]

Instances

Instance assignments work similarly to classes, except we must relate the new instance to its parent class. In this example, we are adding a camera instance to our SecondarySectorProduct class, itself a subclass of Products. We also add a print statement to confirm our assignment worked OK:

camera = kko.SecondarySectorProduct("camera")
print(camera.name)
camera

Like for classes, there are multiple methods for creating a new instance. Here is another form:

computer = kko.SecondarySectorProduct("computer", 
    namespace = kko)

The .instances() class method can be used to iterate through all Instances of a class (including its subclasses). It returns a generator:

for i in kko.SecondarySectorProduct.instances(): print(i)
kko.camera
kko.computer

Go ahead and substitute ‘Products‘ for the class name above to verify it also works on subclasses.

As we learned in the last installment, we can also delete the instance and all of its internal references:

destroy_entity(computer)

Data and Object Properties

Recall that the basic statement (assertion) in RDF, the base language of our ontologies, is a ‘triple‘. The triple has the form of subject – property – object (or s-p-o; also, predicate or relation is a synonym for property). Data and object properties may be inferred over in a W3C ontology. Object properties require a defined and named object (instance or class) as its object. Data properties require a datatype as its object, including strings. Annotation properties (see below) also take strings or URIs as their object, but can not be inferred over.

One creates properties in a similar manner to classes. In this example, we are creating the has_color sub-property of the qualities property, itself a sub-property of an ObjectProperty:

with kko:
    class has_color(kko.qualities): pass

You will note in this example that we used the underscore character to separate our names to make the property name more readable. Actually, in KKO (and KBpedia) our preferred convention is to use CamelCase with classes initially capitalized, and properties initially lower case. We do not use the underscore. What is important to note here, however, is that these are only best-practice conventions. There is no absolute requirement for classes, individuals or properties to assume a particular naming form.

To see that our new property has been properly added, we can do our standard list method:

list(kko.properties())

We can delete properties and all internal references with the destroy_entity method. Further, we can make domain and range assignments and other specifications of properties, some addressed below, the others covered in the Additional Documentation section.

Annotations

As noted, annotations are one of the property types where the objects are names or strings or link addresses (IRIs), but over which no reasoning may occur. Annotations provide the labels, definitions, comments, and pointers to the actual objects in the system. We can assign values in a straightforward manner to annotations (in this case, altLabel):

computer.altLabel = ["Mike's computer","PC", "workstation"]

We can confirm the properties assigned to the computer instance with the get_properties method:

kko.computer.get_properties()
{core.altLabel}

We can also see the actual altLabels were properly assigned:

print(kko.computer.altLabel)
["Mike's computer", 'PC', 'workstation']

You will note that the first label in the example above is shown with double-quotes in order to properly account for the single quote in the label for possession.

Like all properties in the system we can get a listing of the properties and classes that have assigned values for that property with this form of .get_relations method:

list(skos.altLabel.get_relations())

Indirect Relations

There is a special method in Owlready2 called ‘INDIRECT_‘. Invoking this brings up results for:

  • transitive, symmetric and reflexive properties
  • property inheritance (i.e., subproperties)
  • classes of an individual
  • class inheritance (i.e., parent classes), and
  • equivalences (equivalent classes, sameAs individuals).

Here is an example:

print(computer.INDIRECT_altLabel)
["Mike's computer", 'workstation', 'PC']

Property Restrictions

Owlready2 enables us to also place restrictions on our classes through a special type of class constructed by the system.

  • some – Property.some(Range_Class)
  • only – Property.only(Range_Class)
  • min – Property.min(cardinality, Range_Class)
  • max – Property.max(cardinality, Range_Class)
  • exactly – Property.exactly(cardinality, Range_Class)
  • value – Property.value(Range_Individual / Literal value)
  • has_self – Property.has_self(Boolean value).

These are the same names and understandings as is used by the Protégé ontology IDE (see CWPK #5). See further Additional Documentation for details.

Operators

Owlready2 provides three logical operators between classes (including class constructs and restrictions):

  • ‘&’ – And operator (intersection). For example: Class1 & Class2. It can also be written: And([Class1, Class2])
  • ‘|’ – Or operator (union). For example: Class1 | Class2. It can also be written: Or([Class1, Class2])
  • Not() – Not operator (negation or complement). For example: Not(Class1).

Save and Exit

When we are finished with our tests, we can File → Save and Checkpoint, Rename our output file, or specify it at the command line (substituting your own local preferences):

kko.save(file = "C:/1-PythonProjects/kbpedia/sandbox/kko-test.owl", format = "rdfxml")

Additional Documentation

Here are links to other owlready2 documentation:

NOTE: This article is part of the Cooking with Python and KBpedia series. See the CWPK listing for other articles in the series. KBpedia has its own Web site.
NOTE: This CWPK installment is available both as an online interactive file or as a direct download to use locally. Make sure and pick the correct installment number. For the online interactive option, pick the *.ipynb file. It may take a bit of time for the interactive option to load.
I am at best an amateur with Python. There are likely more efficient methods for coding these steps than what I provide. I encourage you to experiment — which is part of the fun of Python — and to notify me should you make improvements.

Posted by AI3's author, Mike Bergman Posted on August 25, 2020 at 11:19 am in CWPK, KBpedia, Semantic Web Tools | Comments (0)
The URI link reference to this post is: https://www.mkbergman.com/2355/cwpk-22-basic-knowledge-graph-management-ii/
The URI to trackback this post is: https://www.mkbergman.com/2355/cwpk-22-basic-knowledge-graph-management-ii/trackback/