Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
💥 Les 22 & 23 novembre : Accès 100% GRATUIT
à la Bibliothèque Numérique ENI. Je m'inscris !

Accéder aux ontologies en Python

Introduction

Dans ce chapitre, nous verrons comment accéder au contenu d’une ontologie en Python avec Owlready. Nous utiliserons l’ontologie des bactéries que nous avons créée au chapitre Les ontologies OWL, ainsi que Gene Ontology, une ontologie très utilisée en bioinformatique.

Importer Owlready

Owlready (version 2) s’importe de la manière suivante en Python :

>>> from owlready2 import * 

Notez qu’il est préférable d’utiliser l’importation du contenu du module avec « from owlready2 import * » plutôt que l’importation du module avec « import owlready2 » (voir chapitre Le langage Python : adoptez un serpent ! - section Importer un module), car Owlready redéfinit certaines fonctions de Python, comme la fonction issubclass().

Charger une ontologie

Owlready permet de charger des ontologies OWL en Python et d’accéder aux entités OWL comme l’on accéderait à des objets « traditionnels » dans un module Python. Une ontologie peut être chargée de trois manières différentes :

  • À partir de son IRI (Internationalized Resource Identifier), c’est-à-dire d’une adresse Internet :

>>> onto = get_ontology("http://lesfleursdunormal.fr/static/ \ 
                                 _downloads/bacterie.owl").load() 

L’ontologie est alors téléchargée depuis Internet puis chargée.

  • À partir d’un fichier local contenant une copie de l’ontologie, par exemple sous Linux/Unix/Mac :

>>> onto = get_ontology("/home/jiba/owlready/bacterie.owl").load() 

ou sous Windows :

>>> onto = get_ontology("C:\\owlready\\bacterie.owl").load() 

Il est aussi possible de charger l’ontologie à partir d’une copie locale dans le répertoire courant :

>>> onto = get_ontology("bacterie.owl").load() 

L’ontologie est alors chargée à partir d’un fichier OWL déjà existant (vous obtiendrez évidemment une erreur si le fichier indiqué entre guillemets...

Ontologies importées

L’attribut imported_ontologies de l’ontologie contient la liste des autres ontologies qu’elle importe :

>>> onto.imported_ontologies  
[] 

Ici, notre ontologie des bactéries n’importe aucune autre ontologie (d’où la liste vide ci-dessus). Les ontologies importées sont automatiquement chargées par Owlready.

Parcourir le contenu de l’ontologie

L’objet ontologie possède de nombreuses méthodes pour parcourir les entités contenues dans l’ontologie, selon leur type. Le tableau suivant récapitule l’ensemble de ces méthodes :

Méthodes

Entités parcourues

individuals()

Tous les individus

classes()

Toutes les classes

properties()

Toutes les propriétés

object_properties()

Toutes les propriétés objet

data_properties()

Toutes les propriétés de données

annotation_properties()

Toutes les propriétés d’annotation

disjoints()

Toutes les disjonctions (y compris entre individus)

disjoint_classes()

Toutes les disjonctions entre classes

disjoint_properties()

Toutes les disjonctions entre propriétés

different_individuals()

Toutes les distinctions entre individus

rules()

Toutes les règles SWRL

variables()

Toutes les variables SWRL

general_axioms()

Tous les axiomes généraux

Ces méthodes retournent des générateurs (voir chapitre Le langage Python : adoptez un serpent ! - section Les générateurs) ; pour afficher le contenu, il faut utiliser la fonction list() de Python qui convertit le générateur en une liste :

>>> onto.classes()  
<generator object _GraphManager.classes at 0x7f5a000fae58>  
 
>>> list(onto.classes())  ...

Accéder aux entités

Lors du chargement, Owlready analyse le fichier de l’ontologie et le traduit automatiquement sous la forme d’un graphe RDF sous forme de triplets de la forme « sujet - verbe - objet » (nous reviendrons plus en détail sur RDF au chapitre Travailler avec les triplets RDF et les mondes). Ce graphe RDF est stocké dans une base de données au format SQLite3, qui réside par défaut en mémoire vive (mais la base de données peut également être stockée sur le disque, comme nous le verrons à la section Ontologie volumineuse et cache disque). Ensuite, les objets Python permettant d’accéder aux entités contenues dans l’ontologie sont créés dynamiquement à la demande. Ainsi, si une ontologie comprend 100 classes, mais qu’une seule est utilisée en Python, seule cette classe sera créée en Python et les 99 autres resteront au niveau du graphe RDF.

Le pseudo-dictionnaire IRIS permet d’accéder à une entité à partir de son IRI. Par exemple, pour accéder à l’individu bactérie_inconnue dont l’IRI complète est la suivante : http://lesfleursdunormal.fr/static/_downloads/bacterie.owl#bactérie_inconnue

nous indiquerons :

>>> IRIS["http://lesfleursdunormal.fr/static/_downloads/ \ 
bacterie.owl#bactérie_inconnue"] 

Cependant, cette notation est longue. Owlready permet d’accéder plus simplement aux entités présentes dans l’ontologie avec la notation pointée « . », comme si l’ontologie était un module Python contenant des classes et des objets. Par exemple, nous pouvons aussi accéder à l’individu bactérie_inconnue de la manière suivante :

>>> onto.bactérie_inconnue 

Lorsque la notation pointée est utilisée, Owlready prend l’IRI de base de l’ontologie (onto.base_iri, ici « http://lesfleursdunormal.fr/static/_downloads/bacterie.owl# ») et ajoute ce qui figure après le point (ici, bactérie_inconnue) pour obtenir l’IRI de l’entité demandée.

Certaines IRI peuvent contenir des caractères non acceptés par Python dans les noms d’attribut...

Rechercher des entités

La méthode search() de l’objet ontologie permet de rechercher des entités à partir de leur IRI et/ou de leurs relations. Lors de la recherche, les mots-clefs suivants sont utilisables et combinables entre eux :

  • iri pour rechercher par IRI

  • type pour rechercher des individus d’une classe donnée

  • subclass_of pour rechercher des classes descendantes d’une classe donnée

  • is_a pour rechercher à la fois les individus et les classes descendantes d’une classe donnée

  • n’importe quel nom de propriété, pour rechercher par relation

De plus, dans les chaînes de caractères, des « * » peuvent être utilisées comme joker. L’exemple suivant recherche l’ensemble des entités dont l’IRI contient « Coque » :

>>> onto.search(iri = "*Coque*")  
[bacterie.Coque] 

Par défaut, la recherche est sensible à la casse. Le paramètre _case_sensitive permet de changer ce comportement, par exemple :

>>> onto.search(iri = "*Coque*", _case_sensitive = False)  
[bacterie.Coque, bacterie.Staphylocoque, bacterie.Streptocoque] 

Cette fois-ci, nous trouvons davantage de résultats, car les classes Staphylocoque et Streptocoque contiennent bien coque, mais avec un « c » minuscule et non majuscule.

Le résultat...

Ontologie volumineuse et cache disque

Gene Ontology (GO) est une ontologie très utilisée en bioinformatique qui est assez volumineuse (près de 200 Mo). Le chargement de GO, à l’aide de la commande suivante, prend plusieurs dizaines de secondes, voire quelques minutes, en fonction de la puissance de l’ordinateur et du temps de téléchargement du fichier OWL (environ 170 Mo) :

>>> go = get_ontology("http://purl.obolibrary.org/obo/go.owl").load() 

Par défaut, Owlready stocke le quadstore contenant l’ontologie au format RDF en mémoire vive. À la fin de l’exécution du programme Python, le quadstore est perdu, et le fichier OWL devra être rechargé la prochaine fois. Afin d’éviter ces rechargements longs, il est possible de placer le quadstore sur le disque, à l’aide de la méthode default_world.set_backend().

Ensuite, default_world.save() permet de l’enregistrer. Par exemple :

>>> default_world.set_backend(filename = "quadstore.sqlite3")  
>>> go = get_ontology("http://purl.obolibrary.org/obo/go.owl").load() 
>>> default_world.save() 

Nous avons ici utilisé un chemin de fichier relatif pour le quadstore, nous aurions pu indiquer un chemin absolu (par exemple "/home/jiba/owlready/quadstore.sqlite3" sous Linux/Mac...

Espaces de nommage

Certaines ontologies définissent des entités dans un espace de nommage qui n’est pas le leur ; c’est le cas de GO. L’IRI de GO est http://purl.obolibrary.org/obo/go.owl, ses entités possèdent des IRI qui commencent par http://purl.obolibrary.org/obo/ (sans le « go.owl »). Par conséquent, il n’est pas possible d’utiliser l’objet ontologie go pour accéder aux entités avec la notation pointée :

>>> go.GO_0035065  
None 

En effet, la ligne ci-dessus correspond à l’IRI http://purl.obolibrary.org/obo/go.owl#GO_0035065 tandis que la vraie IRI du concept est http://purl.obolibrary.org/obo/GO_0035065 (donc sans le « go.owl »).

Pour accéder aux entités de GO, il est possible d’utiliser le pseudo-dictionnaire global IRIS (voir Accéder aux entités). Une autre possibilité plus facile est de créer un espace de nommage (namespace en anglais) pour l’IRI « http://purl.obolibrary.org/obo/ », de la manière suivante :

>>> obo = get_namespace("http://purl.obolibrary.org/obo/") 

L’espace de nommage obo peut ensuite être utilisé pour accéder aux entités avec la notation pointée :

>>> obo.GO_0035065  
obo.GO_0035065  
>>> obo.GO_0035065.label  
['regulation...

Modifier le rendu des entités

Par défaut, Owlready affiche le nom de l’entité, précédé d’un point et du dernier morceau de l’IRI (sans l’extension .owl). Cependant, lorsque les noms des entités sont des identifiants arbitraires, cet affichage n’est pas satisfaisant, comme dans l’exemple suivant :

>>> obo.GO_0035065  
obo.GO_0035065 

La fonction globale set_render_func() permet de redéfinir la manière dont Owlready effectue le rendu des entités (rendering en anglais). Dans l’exemple suivant, nous utilisons la propriété d’annotation label pour effectuer le rendu, ou, à défaut, le nom de l’entité (c’est-à-dire son identifiant).

>>> def mon_rendu(entity):  
...       return entity.label.first() or entity.name  
>>> set_render_func(mon_rendu)  
>>> obo.GO_0035065  
regulation of histone acetylation 

Dans GO, la quasi-totalité des entités sont des classes (et non des individus ; c’est une pratique assez courante dans les ontologies biomédicales). Comme vu précédemment (voir section Restrictions existentielles) il est possible d’accéder aux restrictions existentielles de ces classes avec la notation pointée (où RO_0002211 est le nom GO pour la propriété...

Répertoire local d’ontologies

Owlready peut également fonctionner avec un ou plusieurs répertoires contenant des copies locales d’ontologies. Celles-ci seront utilisées en priorité, au lieu de télécharger les ontologies sur Internet. Le ou les répertoires locaux doivent être renseignés dans la variable globale onto_path, de la manière suivante sous Unix/Linux/Mac :

>>> onto_path.append("/home/jiba/owlready")  
>>> onto = get_ontology("http://lesfleursdunormal.fr/static/ \ 
                        _downloads/bacterie.owl#").load() 

et sous Windows :

>>> onto_path.append("C:\\owlready")  
>>> onto = get_ontology("http://lesfleursdunormal.fr/static/ \ 
                        _downloads/bacterie.owl#").load() 

La variable globale onto_path contient une liste (vide par défaut) de répertoires locaux d’ontologies. Avant de télécharger une ontologie sur Internet, Owlready vérifie si une copie n’est pas disponible dans un des répertoires locaux. Dans l’exemple précédent, si un fichier bacterie.owl est présent dans le répertoire de cache (/home/jiba/owlready ou C:\\owlready), ce fichier est utilisé....

Recharger une ontologie dans le quadstore

Lorsque l’on utilise des fichiers locaux (répertoire local d’ontologies comme ci-dessus, ou bien chargement d’une ontologie à partir d’un fichier OWL local) et un quadstore stocké sur le disque, la question de la mise à jour des ontologies dans le quadstore se pose. En effet, lorsque le fichier OWL local est modifié, l’ontologie doit être mise à jour dans le quadstore. Cela peut se faire avec l’option reload de la méthode load() vue précédemment, mais aussi avec l’option reload_if_newer qui recharge l’ontologie uniquement si le fichier OWL est plus récent que la version stockée dans le quadstore :

>>> go = get_ontology("http://purl.obolibrary.org/obo/go.owl#") \ 
                         .load(reload_if_newer = True) 

Attention, le rechargement de l’ontologie à partir du fichier OWL écrase la version stockée dans le quadstore. Il faut donc éviter de modifier simultanément le fichier OWL de l’ontologie et sa version hébergée dans le quadstore !

Exemple : créer un site web dynamique à partir d’une ontologie

Dans cet exemple, nous allons générer un site web dynamique pour présenter les classes et les individus d’une ontologie. Pour cela, nous utiliserons Owlready ainsi que Flask, un module Python qui permet de réaliser facilement des sites web. Flask permet d’associer un chemin sur un site web à une fonction Python; lorsque ce chemin est demandé, la fonction est appelée et celle-ci doit retourner la page HTML correspondante. Le chemin est défini en ajoutant @app.route(’/chemin’) sur la ligne précédant la fonction (il s’agit d’un décorateur de fonction Python). Les chemins peuvent contenir des paramètres (indiqués entre chevrons <...>) qui seront passés comme argument à la fonction Python.

La fonction suivante montre un exemple simple de page web avec Flask :

from flask import Flask, url_for  
 
app = Flask(__name__)  
 
@app.route('/chemin/<parametre>')  
def page_web(parametre):  
   html = "<html><body>"  
   html += "La valeur du paramètre est : %s" % paramètre  
   html += "</body></html>"  
   return html 

Le programme complet de notre site web est le suivant :

# Fichier site_dynamique.py  
from owlready2 import *  
onto = get_ontology("bacterie.owl").load()  
 
from flask import Flask, url_for  
app = Flask(__name__)  
 
@app.route('/')  
def page_ontologie():...