Aller au contenu

RDF et SPARQL

ddigraph lit et écrit du RDF. Vous pouvez convertir un fichier DDI en Turtle, JSON-LD, N-Triples ou RDF/XML, le vérifier avec des formes SHACL, puis le relire.

Installation

Le support RDF est un extra optionnel :

pip install "ddigraph[rdf]"

Ajoutez la validation SHACL avec :

pip install "ddigraph[shacl]"

Exporter un fichier

Aucune base de données n'est nécessaire. La commande export lit du DDI et écrit un fichier :

ddigraph export survey.xml --format turtle -o survey.ttl

Elle fonctionne pour les trois variantes DDI : Codebook, Lifecycle et CDI. Les autres formats sont ntriples, jsonld, rdfxml, json et csv. Les formats json et csv ne demandent aucun extra.

Depuis Python :

import os

import ddigraph

result = ddigraph.export(os.environ["FIXTURE"], "survey.ttl", format="turtle")
print(result.nodes, "nodes,", result.triples, "triples")

Le vocabulaire

Chaque graphe utilise un seul espace de noms, qui possède sa propre version :

https://pbisson44.github.io/ddigraph/ns/1.0/

La version appartient au vocabulaire, pas au paquet. Elle change uniquement quand un terme change de sens : une requête écrite aujourd'hui continue donc de fonctionner.

Cet IRI se résout. Ouvrez-le et vous obtenez la référence du vocabulaire, avec vocabulary.ttl à côté : chaque classe et chaque prédicat, générés à partir du schéma qui pilote l'exportateur.

Les termes publiés d'abord

Quand l'Alliance DDI ou le monde du web sémantique propose déjà un terme, ddigraph l'utilise. C'est ce qui permet de relier vos données à celles des autres.

Concept DDI Classe RDF
Study, StudyUnit disco:Study
Variable disco:Variable
Question, QuestionItem disco:Question
Universe disco:Universe
DataFile disco:DataFile
CodeList, CodeScheme skos:ConceptScheme
Category, Concept skos:Concept
CategoryGroup xkos:ClassificationLevel
Organization foaf:Organization

DISCO est le vocabulaire RDF de l'Alliance DDI, construit à partir de DDI Codebook et DDI Lifecycle. XKOS étend SKOS pour les classifications statistiques.

DDI compte environ 250 types de nœuds et DISCO définit 16 classes. Tout ce qui n'a pas d'équivalent publié reçoit un terme dans l'espace de noms ddigraph.

Chaque nœud porte deux types

Un nœud reçoit la classe publiée et une classe ddigraph :

<urn:ddi:ie.cso:q-4711:1.0.0>
    a disco:Question , ddigraph:QuestionItem ;
    skos:prefLabel "Main activity status"@en-IE .

La classe publiée est celle que lisent les autres outils. La classe ddigraph indique le type DDI réel. Question et QuestionItem deviennent tous deux disco:Question : sans le second type, on ne pourrait plus les distinguer.

Les prédicats

Les noms de relations sont en lowerCamelCase. Une relation HAS_CONSTRUCT devient ddigraph:hasConstruct. Quand un prédicat publié existe, il est utilisé à la place :

Relation du graphe Prédicat RDF
USES_CONCEPT disco:concept
ASKS_QUESTION disco:question
USES_CODELIST disco:responseDomain
IN_DATASET dcterms:isPartOf
HAS_CATEGORY skos:inScheme

Les IRI des sujets

Les URN DDI sont réutilisés tels quels quand l'enregistrement en possède un :

urn:ddi:ie.cso:q-4711:1.0.0

Un URN est déjà unique à l'échelle mondiale : créer un nouvel IRI n'apporterait rien. Les enregistrements sans URN reçoivent un identifiant urn:ddigraph:. Utilisez --base-uri pour votre propre espace de noms au moment de publier :

ddigraph export survey.xml --format turtle -o out.ttl \
  --base-uri https://example.org/id/

Les listes de codes sont du SKOS

Les listes de codes et les catégories sont la partie de DDI la plus utile en dehors de DDI. Elles sortent en SKOS correct :

<urn:ddi:test.org:cl1:1.0>
    a skos:ConceptScheme , ddigraph:CodeList ;
    skos:prefLabel "Age Groups" .

<urn:ddi:test.org:cat1:1.0>
    a skos:Concept , ddigraph:Category ;
    skos:inScheme <urn:ddi:test.org:cl1:1.0> ;
    skos:prefLabel "Under 18" .

Notez le sens du lien. Le fichier DDI imbrique les catégories dans la liste de codes, mais SKOS place le lien sur le membre, via skos:inScheme. skos:member appartient à skos:Collection, pas à skos:ConceptScheme.

Les références externes deviennent skos:exactMatch. C'est le lien qui relie une liste de codes à EuroVoc, DBpedia ou tout autre vocabulaire publié.

Interroger avec SPARQL

Une fois le fichier chargé dans rdflib, vous pouvez l'interroger :

import os

import rdflib

import ddigraph

ddigraph.export(os.environ["FIXTURE"], "survey.ttl", format="turtle")

graph = rdflib.Graph().parse("survey.ttl", format="turtle")
rows = graph.query("""
    PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

    SELECT ?category ?label ?scheme
    WHERE {
        ?category a skos:Concept ;
                  skos:prefLabel ?label ;
                  skos:inScheme ?scheme .
    }
""")

for category, label, scheme in rows:
    print(label, "in", scheme)

Le même fichier se charge dans n'importe quel triplestore : Jena, GraphDB, Virtuoso, Stardog, Blazegraph.

Valider avec SHACL

ddigraph shapes écrit les formes SHACL du vocabulaire. Elles viennent du schéma qui construit aussi les contraintes Neo4j : elles ne peuvent donc pas diverger.

ddigraph shapes -o shapes.ttl --flavor lifecycle

Utilisez --flavor pour valider des données réelles. Un fichier a une seule variante, et 21 noms de types DDI apparaissent dans plusieurs variantes avec des clés différentes.

import os

import pyshacl
import rdflib

import ddigraph
from ddigraph.rdf.shacl import shapes_graph

ddigraph.export(os.environ["FIXTURE"], "survey.ttl", format="turtle")

data = rdflib.Graph().parse("survey.ttl", format="turtle")
conforms, _report_graph, report = pyshacl.validate(
    data, shacl_graph=shapes_graph(flavor="lifecycle")
)

print("conforms:", conforms)
assert conforms, report

Relire du RDF

Le RDF est aussi un format d'entrée. read_graph analyse Turtle, JSON-LD, N-Triples et RDF/XML vers la forme produite par les analyseurs DDI :

import os

import ddigraph
from ddigraph.rdf.reader import read_graph

ddigraph.export(os.environ["FIXTURE"], "survey.ttl", format="turtle")

nodes = [node for chunk in read_graph("survey.ttl") for node in chunk.nodes]
print(len(nodes), "nodes read back")
print(sorted({node.label for node in nodes}))

L'aller-retour ne perd rien. Exportez un fichier, relisez-le, exportez-le à nouveau : vous obtenez les mêmes triplets.

Vous pouvez aussi charger du RDF directement dans Neo4j :

ddigraph export survey.xml --format turtle -o out.ttl
ddigraph load out.ttl

Le lecteur ignore les sujets sans type ddigraph. Pointez-le vers du RDF étranger et vous n'obtenez rien, plutôt que n'importe quoi.

Construire votre propre graphe

iter_graph donne directement les nœuds et les relations, pour toutes les variantes DDI. Utilisez-le pour alimenter un stockage que ddigraph ne gère pas :

import os

from ddigraph import iter_graph

for chunk in iter_graph(os.environ["CDI_FIXTURE"]):
    for node in chunk.nodes:
        print(node.label, node.identity)
    for edge in chunk.relationships:
        print(edge.start.label, "-", edge.type, "->", edge.end.label)