pythonrdfrdflib

How to get all items of a blank node in rdflib


I'm new to RDflib, I want to implement a feature like this: To say, Person hasProperty Weight(kg) , Height(m), and BMI (Body Mass Index)=Weight/Height^2, so if Bob's Weight=70, Height=1.75, How to deduce Bob's BMI from this model? I use the following RDF file to store the above information:

@prefix : <http://ex.org/BMI#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
:Person rdf:type owl:Class;
         :hasProperty :Height, :Weight, :BMI.
:OriginalProperty rdf:type owl:Class .
:DerivedProperty rdf:type owl:Class .
:Weight rdf:type owl:Class ;
        rdfs:subClassOf :OriginalProperty .
:Height rdf:type owl:Class ;
        rdfs:subClassOf :OriginalProperty .
:BMI rdf:type owl:Class ;
     rdfs:subClassOf :DerivedProperty ;
     :equalTo [:divide (:Weight [:power  :Height])] .
:MathOperator rdf:type owl:Class .
:equalTo rdf:type owl:Class ;
         rdfs:subClassOf :MathOperator .
:divide rdf:type owl:Class ;
           rdfs:subClassOf :MathOperator .
:power rdf:type owl:Class ;
       rdfs:subClassOf :MathOperator .
:Bob rdf:type owl:NamedIndividual , :Person ;
     :hasProperty :BMIOfBob ,
                  :HeightOfBob ,
                  :WeightOfBob .
:HeightOfBob rdf:type owl:NamedIndividual , :Height ;
            :hasValue 1.75 .
:WeightOfBob rdf:type owl:NamedIndividual , :Weight ;
            :hasValue 70 .
:BMIOfBob rdf:type owl:NamedIndividual ,:BMI.

my python code is as follows:

from rdflib import Graph
from rdflib.namespace import Namespace
g = Graph()
x = Graph()
g.parse("BMI.ttl")
n = Namespace("http://ex.org/BMI#")
x = g.triples((None,n.equalTo,None))
for s, p, o in x:
    print(s,p,o)

Only the blank node itself is returned, excluding other data of the BNode.

http://ex.org/BMI#BMI http://ex.org/BMI#equalTo n2dd6baee104f49189928ce08eb003834b1

How can I get additional information of the BNode? Or is there a better way to implement the functionality of this model with RDF?
Thank you for any help and advice.

Joylix


Solution

    1. Always put your RDF data through a converter to validate it and tidy it up. Try http://rdftools.surroundaustralia.com/convert. Using that you get:
    @prefix : <http://ex.org/BMI#> .
    @prefix owl: <http://www.w3.org/2002/07/owl#> .
    @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
    @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    
    :Bob a :Person,
            owl:NamedIndividual ;
        :hasProperty :BMIOfBob,
            :HeightOfBob,
            :WeightOfBob .
    
    :divide a owl:Class ;
        rdfs:subClassOf :MathOperator .
    
    :equalTo a owl:Class ;
        rdfs:subClassOf :MathOperator .
    
    :power a owl:Class ;
        rdfs:subClassOf :MathOperator .
    
    :BMIOfBob a :BMI,
            owl:NamedIndividual .
    
    :DerivedProperty a owl:Class .
    
    :HeightOfBob a :Height,
            owl:NamedIndividual ;
        :hasValue 1.75 .
    
    :Person a owl:Class ;
        :hasProperty :BMI,
            :Height,
            :Weight .
    
    :WeightOfBob a :Weight,
            owl:NamedIndividual ;
        :hasValue 70 .
    
    :BMI a owl:Class ;
        :equalTo [ :divide ( :Weight [ :power :Height ] ) ] ;
        rdfs:subClassOf :DerivedProperty .
    
    :OriginalProperty a owl:Class .
    
    :Height a owl:Class ;
        rdfs:subClassOf :OriginalProperty .
    
    :MathOperator a owl:Class .
    
    :Weight a owl:Class ;
        rdfs:subClassOf :OriginalProperty .
    
    1. Remodel your numeric values in a standard way - using established ontologies. The way you've done things like:
    :WeightOfBob 
        a :Weight, owl:NamedIndividual ;
        :hasValue 70 .
    

    is pretty good - you've indicated a quantity kind (type of thing measured) - weight - and a value - 70 but you should also include units. Try QUDT modelling, which would look like this:

    @prefix qk: <http://qudt.org/vocab/quantitykind/> .
    @prefix qudt: <http://qudt.org/schema/qudt/> .
    @prefix unit: <http://qudt.org/vocab/unit/> .
    
    :WeightOfBob 
        a qudt:Quantity ;
        qudt:hasQuantityKind qk:Weight ;
        qudt:numericValue 70 ;
        qudt:unit unit:KiloGM ;   # kilogram
    

    There are lots of defined units of measure, see http://www.qudt.org/doc/DOC_VOCAB-UNITS.html

    Now height of Bob:

    :WeightOfBob 
        a qudt:Quantity ;
        qudt:hasQuantityKind qk:Height ;
        qudt:numericValue 1.75 ;
        qudt:unit unit:M ;   # metre
    

    So now, using the common ontology [schema.org](https://schema.org]: for 'Person':

    @prefix : <http://ex.org/BMI#> .
    @prefix sdo: <https://schema.org/> .
    
    :Bob 
      a sdo:Person ;
      :hasProperty [
        a qudt:Quantity ;
        qudt:hasQuantityKind qk:Weight ;
        qudt:numericValue 70 ;
        qudt:unit unit:KiloGM ;   # kilogram
      ] ,
      [
        a qudt:Quantity ;
        qudt:hasQuantityKind qk:Height ;
        qudt:numericValue 1.75 ;
        qudt:unit unit:M ;   # metre
      ] ;
    .
    

    Now, with that more common data pattern, let's answer your question.

    How can I get additional information of the BNode?

    What you've got to do is loop through the graph to get the Blank Node you want and then traverse the graph from there with that Blank Node as a subject in another loop.

    Rather than code like g.triples((...)), use the simpler g.subjects(...), g.subject_objects(...) etc.

    There is also a g.value(..) function you can use to get single values for any subject, predicate or object by specifying the other two triple values you know.

    So, to get the weight & heigh values using the new data modelled above:

    # get the Blank Nodes for Bob for weight & height
    height_bn = g.value(predicate=qudt.hasQuantityKind, object=qk.Height)
    weight_bn = g.value(predicate=qudt.hasQuantityKind, object=qk.Weight)
    
    # get the numeric values from those Blank Nodes
    height_value = g.value(subject=height_bn, predicate=qudt.numericValue)
    weight_value = g.value(subject=weight_bn, predicate=qudt.numericValue)
    
    # if you needed to know units, you could do that here...
    
    

    Then, using normal Python, calculate BMI:

    import math
    bmi_value = float(weight_value) / math.pow(float(height_value), 2)
    

    Sure, your BMI algorithm here is expressed in Python, not RDF, but you could also express it in SPARQL or you could generate the algorithm from RDF structures, wich may be your ultimate goal, but this simple method answers at least some of your questions.

    You can write that bmi_value back into your RDF data too as another Quantity with appropriate quantityKind, unit etc. values