Utilisateur:Vieux têtard/Visite guidée de Wikidata

SPARQL : pour y voir plus clair modifier

Si l’on se plonge dans les différentes pages d’aide sur l’utilisation de SPARQL [1], on ne trouve pas grand chose qui nous parle de Wikisource. Les exemples de requêtes parlent de la famille de Bach ou de chats. J’aime bien les chats, et surtout les chats sans maître [2], mais ceux-ci ne risquent pas de se retrouver dans Wikidata.

L’avantage des requêtes que Vigneron nous présente, chaque mois depuis décembre 2017, est qu’elles concernent directement les contributeurs de Wikisource.

Pour ceux que que la programmation n’effraye pas, essayons de comprendre comment il sparkle, comment sont construites ses requêtes pour savoir construire les nôtres.

  1. le portail de l’aide au service de requêtes, la syntaxe SPARQL, Introduction à l’assistant du service de requête, Manuel de l’utilisateur du service de requêtes
  2. ou "adespotes" selon M. Panagiotis Grigoriou.

Requête de février 2018 modifier

C’est une requête répartissant les textes de la Wikisource francophone par année de publication sous forme de diagramme en barres.

Les données importantes à récupérer sont donc les pages de la Wikisource francophone, déclarées dans Wikidata et pourvues de la propriété « date de publication » (P577).

Nous ne retiendrons que l’année de cette date de publication, et le nombre de pages trouvées pour chaque année.

Traduit en Wikidata, cela revient à :

  • trouvez-moi toutes les entités pourvues d’une propriété « date de publication » et d’un lien (Sitelink) vers la Wikisource francophone,
  • faites un sous-total par année de la date de publication du nombre d’entités trouvées,
  • et rendez-moi le résultat sous forme de diagramme en barres.

Et en SPARQL, ça donne :

 #defaultView : BarChart
 SELECT (str(?year) AS ?year) (COUNT(?livre) AS ?count)
 WHERE {
  ?livre wdt:P577 ?date.
  BIND (year(?date) as ?year)
  ?page schema:about ?livre.
  ?page schema:isPartOf <https://fr.wikisource.org/>.
  FILTER(?year >= 1500)
 }
 GROUP BY ?year 
 ORDER BY ?year
  1. Ligne 1 : on commence par la fin, en précisant sous quelle forme on veut afficher le résultat, ici un diagramme en barres, par défaut l'affichage est un tableau.
  2. Ligne 2 : le SELECT : c’est là qu’on donne un nom aux variables à afficher. Les noms sont libres et précédés d’un "?". On n’a encore rien dit sur ce qu’on veut y trouver ;
    • on met l’année sous forme de chaine de caractères (fonction string()), le module "diagramme en barres" ne veut que des étiquettes sous cette forme.
    • on ne veut pas la liste des livres mais leur décompte (COUNT() AS ) (voir ligne 10).
  3. Ligne 3 : on ouvre le bloc WHERE des conditions qui vont permettre de remplir ces variables.
  4. Ligne 4 : chaque item wikidata devra posséder une déclaration "date de publication" (P577) dont la valeur sera affectée à une variable intermédiaire "?date".
  5. Ligne 5 : On ne retient que l’année de la date (fonction year()) et on l’affecte (BIND) à la variable "?year"
  6. Ligne 6 : on déclare une variable "?page" rattachée à "?livre" mais dont la valeur ne sera pas donnée par une propriété (schema:about).
  7. Ligne 7 : cette variable ?page récupère le "sitelink" vers la Wikisource francophone (schema:isPartOf).
  8. Ligne 8 : on ne retiendra que les années de publication à partir de "1500" (FILTER).
  9. Ligne 9 : on ferme le bloc des conditions sur les variables.
  10. Ligne 10 : pour le comptage demandé dans le "SELECT", on donnera un résultat pour chaque année (GROUP BY).
  11. Ligne 11 : les résultats seront triés par année (ORDER BY).

Requête de janvier 2018 modifier

C’est une requête répartissant les auteurs de la Wikisource francophone par nombre d'éditions toujours sur la Wikisource francophone, le résultat est présenté sous deux formes : 1° un tableau (forme par défaut) 2° un diagramme en bulles, beaucoup plus explicite.

Les données importantes à récupérer sont donc les pages pourvues de la propriété « auteur » (P50) et les pages Auteur correspondantes pourvu qu’elles soient dans la Wikisource francophone et déclarées dans Wikidata

Nous ne retiendrons que le nombre de pages trouvées pour chaque auteur et les auteurs seront représentés par leurs noms et non par leurs identifiants Wikidata (Qid).

Traduit en Wikidata, cela revient à :

  • trouvez-moi toutes les entités pourvues d’une propriété « auteur » et d’un lien (Sitelink) vers la Wikisource francophone,
  • de ces entités "pages", ne retenez que celles dont l'entité "auteur" a un lien vers la Wikisource francophone,
  • faites des sous-totaux par auteur du nombre d’entités trouvées,
  • et rendez-moi le résultat sous forme :
    1. de tableau par auteur, trié par nombres de pages décroissants,
    2. de diagramme en bulles où chaque auteur est représenté par une bulle de diamètre proportionnel à son nombre de pages.

Et en SPARQL, ça donne :

 #Authors on french wikisource and theirs books
 SELECT ?authorLabel ?pageAuthor (COUNT(?page) AS ?count) 
 WHERE {
  ?livre wdt:P50 ?author .
  ?pageAuthor schema:about ?author.
  ?pageAuthor schema:isPartOf <https://fr.wikisource.org/> .
  ?page schema:about ?livre.
  ?page schema:isPartOf <https://fr.wikisource.org/> .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "fr". }
 }
 GROUP BY ?authorLabel ?pageAuthor
 ORDER BY DESC(?count)

Pour le résultat en tableau.

  1. Ligne 1 : un commentaire décrivant la requête (tout ce qui ne fait pas partie de la syntaxe SPARQL commence par "#").
  2. Ligne 2 : le SELECT : on veut le nom de l’auteur (suffixe "Label" à la variable ?author), sa page Auteur (il peut y avoir plusieurs auteurs de même nom, par exemple Gabriel Marcel), le nombre de textes (COUNT() AS) rattachés à cette page (voir ligne 11).
  3. Ligne 3 : on ouvre le bloc WHERE des conditions qui vont permettre de remplir ces variables.
  4. Ligne 4 : chaque item wikidata "?livre" devra posséder une déclaration "auteur" (P50) dont la valeur sera affectée à la variable "?author".
  5. Ligne 5 : on attache une variable ?pageAuthor à l’entité "?author" (schema:about).
  6. Ligne 6 : cette variable "?pageAuthor" récupère le "sitelink" de la page Auteur de la Wikisource francophone (schema:isPartOf).
  7. Ligne 7 : on déclare une variable "?page" rattachée à "?livre" (schema:about).
  8. Ligne 8 : cette variable ?page récupère le "sitelink" de la page texte de la Wikisource francophone (schema:isPartOf).
  9. Ligne 9 : par cette instruction barbare on demande que les libellés soient en français. Ici cela donnera le nom en français de l’auteur ("?authorLabel")
  10. Ligne 10 : on ferme le bloc des conditions sur les variables.
  11. Ligne 11 : pour le comptage demandé dans le "SELECT", on donnera un résultat pour chaque auteur (GROUP BY), sachant que plusieurs auteurs peuvent avoir le même nom.
  12. Ligne 12 : les résultats seront triés par nombre de textes décroissant (ORDER BY DESC).

Lors de la dernière exécution de cette requête, j’ai obtenu 2 111 lignes. Pour se faire une idée, il vaut mieux une présentation plus synthétique, d’où le diagramme en bulles.

Pour l’obtenir, il suffit de mettre avant la première ligne la ligne suivante :

 #defaultView:BubbleChart

Pour SPARQL, c’est un commentaire, mais le service de requête Wikidata (WDQS) qui lance les requêtes SPARQL le reconnait comme demande de mise en forme des résultats.

On peut aussi, à partir d’un résultat, changer sa représentation par le bouton d’une liste déroulante, en bas, à droite, qui offre différentes représentations possibles.

Requête de décembre 2017 modifier

C’est une requête montrant le lieu de naissance des personnes ayant une page dans la Wikisource francophone sur la carte du monde.

Il faut donc :

  • que ces personnes aient une page dans la Wikisource francophone,
  • que cette page soit déclarée dans Wikidata,
  • que cette déclaration comporte une propriété "Lieu de naissance" (P19),
  • que ce lieu de naissance ait des "coordonnées géographiques" (P65) déclarées dans Wikidata.

En SPARQL, ça donne :

#defaultView:Map
SELECT ?person ?personLabel ?placeLabel ?coord ?page 
WHERE {
  ?person wdt:P19 ?place .
  ?place wdt:P625 ?coord .
  ?page schema:about ?person .
  ?page schema:isPartOf <https://fr.wikisource.org/> .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
  1. Ligne 1 : On demande l’affichage sur une carte.
  2. Ligne 2 : Le SELECT précise les résultats suivants :
    • L’identifiant de la personne (Qid) dans la variable ?person
    • Son nom dans la variable ?personLabel
    • Le nom du lieu de naissance dans la variable ?placeLabel
    • Les coordonnées du lieu de naissance dans la variable ?coord
    • Le lien vers la page wikisource de la personne dans la variable ?page
  3. Ligne 3 : on ouvre le bloc WHERE des conditions qui vont permettre de remplir ces variables.
  4. Ligne 4 : on sélectionne les entités ?person qui ont une propriété "lieu de naissance" (P19) dont l’identifiant est stocké dans ?place
  5. Ligne 5 : on sélectionne celles de ces ?place qui ont des "coordonnées géographiques" (P625) qui seront stockées dans ?coord
  6. Ligne 6 : on déclare une variable "?page" rattachée à "?person" (schema:about).
  7. Ligne 7 : cette variable reçoit le "sitelink" de la page de la wikisource francophone consacrée à la personne, sinon la personne est ignorée.
  8. Ligne 8 : par cette instruction barbare on demande que les libellés soient dans la langue de celui qui émet la requête, donc le français pour moi, sinon, par défaut, les libellés seront en anglais. Si le libellé n’existe ni dans la langue de la personne, ni en anglais, c’est le Qid qui est affiché. Ceci vaudra pour le nom de la personne (?personLabel) et le nom du lieu de naissance (?placeLabel).
  9. Ligne 9 : on ferme le bloc des conditions sur les variables.

Wikidata est une base de connaissances en graphe modifier

Dans les exemples précédents, on aura remarqué qu’une requête SPARQL est formée des parties suivantes :

  • Une clause SELECT qui énonce les variables à produire,
  • Une clause WHERE qui précise comment les remplir,
  • éventuellement une clause GROUP BY qui indique le regroupement à faire pour les résultats de fonctions COUNT, MAX, 'MIN, AVG, etc., figurant dans le SELECT
  • éventuellement aussi une clause ORDER BY pour trier les résultats.

Il en existe aussi la clause LIMIT si on veut limiter les résultats à un nombre donné.

Enfin la clause SELECT peut être pourvue de l’attribut DISTINCT qui, comme en SQL, permet d’éliminer les doublons dans les résultats.

Pour ceux qui connaissent SQL et les bases de données relationnelles, on pourrait se croire en terrain connu, cependant il y a de grosses différences entre la logique de consultation des base de données relationnelles et celle des bases en graphe comme Wikidata.

La structure d’une base relationnelle reflète le sens attribué aux données, mais elle est fixe, celle d’une base en graphe est dynamique : il n’y a plus de tables mais des entités (nœuds du graphe) et des propriétés reliant entre les entités (chemins du graphe) et ce graphe est dynamiquement modifiable, tant en forme qu’en contenu.

Les contraintes se situent au niveau des propriétés qui n’admettent que certains types de valeurs ou d’entités cibles et ne peuvent appartenir qu’à certains types d’entités. On ne parle plus de structure de données mais d’ontologie.

Au lieu de consulter des tables prédéfinies, on va parcourir ce graphe dynamique, c’est le rôle de la clause "WHERE", la clause "JOIN" de SQL n’a plus lieu d’être puisqu’elle matérialisait des relations qui sont maintenant exprimées directement par des chemins dans le graphe.

Pour ceux qui sont intéressés, voir :

Introduction to the Principles of Linked Open Data

pour les principes de cette sorte de base et surtout

Using SPARQL to access Linked Open Data

pour la technique d’interrogation.

Sans vouloir explorer toute la syntaxe de SPARQL, il reste à étudier quelques points importants, mais quand je n’aurai pas trouvé d’exemples concernant wikisource, j’utiliserai une forme simplifié ou quelques exemples du tutoriel :

Les instructions de la clause WHERE se composent principalement de triplets (ou "tuples") de la forme :

?item	prop	valeur.

ce qui signifie : chercher les items "?item" ayant la propriété "prop" de valeur "valeur" ou :

?item	prop	?valeur.

ce qui signifie : chercher les items "?item" ayant la propriété "prop" et affecter la valeur de la propriété à la variable "?valeur".

La mise en facteurs des tuples modifier

Elle permet de ne pas répéter la même partie du tuple plusieurs fois inutilement.

Un item peut avoir plusieurs propriétés qui nous intéressent, dans ce cas, au lieu de répéter l’item :

?item	prop1	?valeurp1.
?item	prop2	?valeurp2.

On peut écrire :

?item	prop1	?valeurp1;
	prop2	?valeurp2.

Le ";" au lieu du "." final indique à SPARQL qu'il s’agit du même item.

De même, pour un item donné, une propriété peut avoir plusieurs valeurs que l’on recherche, dans ce cas, au lieu de répéter la propriété :

?item	prop	?valeur1;
	prop	?valeur2.

On peut écrire :

?item	prop	?valeur1,
		?valeur2.

La "," au lieu du ";" final indique à SPARQL qu'il s’agit du même item et de la même propriété.

Bien sûr, pour le même item on peut combiner les deux formes mais attention : plus on met de conditions sur un item, moins on en trouve car toutes ces conditions sont reliées entre elle par un "ET" logique c.a.d. qu’il faut les satisfaire toutes à la fois pour le même item.

On peut cependant demander des condition optionnelles, c.a.d. dire présentes ou non, comme le montre le tutoriel SPARQL sur les ouvrages de Sir Arthur Conan Doyle :

SELECT ?livre ?titre ?illustrateurLabel ?editeurLabel ?datepublication
WHERE
{
  ?livre wdt:P50 wd:Q35610.
  OPTIONAL { ?livre wdt:P1476 ?titre. }
  OPTIONAL { ?livre wdt:P110 ?illustrateur. }
  OPTIONAL { ?livre wdt:P123 ?editeur. }
  OPTIONAL { ?livre wdt:P577 ?datepublication. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}

qui signifie : donnez-moi les livres de Conan Doyle, avec le titre, l’éditeur, la date de publication s’ils sont précisés, le nom de l’illustrateur s’il y en a un.

Les conditions optionnelles, pour rester indépendantes les unes des autres, ne doivent pas être regroupées à l’intérieur d’une seule clause "OPTIONAL" où elles seraient reliées par un "ET", c.a.d. toutes présentes ou absentes en même temps.

Les variables anonymes modifier

Comment ne pas déclarer une variable temporaire.

Ceci est un exemple tiré du tutoriel, il s’agit de trouver les petits-enfants de Bach.

Dans la forme habituelle on écrirait :

SELECT ?petitenfant ?petitenfantLabel
WHERE
{
  wd:Q1339 wdt:P40 ?enfant.
  ?enfant wdt:P40 ?petitenfant.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}

Mais, en fait, on n’a besoin de la variable "?enfant" que comme intermédiaire pour trouver les petits-enfants, on ne l’affichera pas. On peut alors remplacer cette variable par l'expression suivante :

SELECT ?petitenfant ?petitenfantLabel
WHERE
{
  wd:Q1339 wdt:P40 [ wdt:P40 ?petitenfant ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}

Les chemins de propriétés modifier

Parcourir un emboitement de classes de profondeur non connue ou un arbre généalogique.

Prenons pour exemple la requête présentée par Cantons-de-l'Est sur le scriptorium en décembre 2017 : il s’agissait de montrer sur une carte les sites du patrimoine culturel du Québec, avec les photos qu’il en avait prises.

Chacun des sites est catalogué dans Wikidata avec la propriété "statut patrimonial" (P1435) dont la valeur (par exemple "immeuble patrimonial classé" (Q137889518)) est une sous-classe (un sous-ensemble) d’une classe plus vaste, etc., pour aboutir à la classe "patrimoine culturel du Québec" (Q3370013) (elle-même d’ailleurs sous-classe de "lieu patrimonial du Canada" (Q14469659)). On a là des classes emboitées comme des poupées russes, en nombre variable, dépendant entre autre du statut patrimonial.

#defaultView:Map
SELECT DISTINCT ?item ?itemLabel ?coord ?image
WHERE {
	?item	wdt:P1435/wdt:P279* wd:Q3370013;
			wdt:P625 ?coord;
	OPTIONAL { ?item wdt:P18 ?image . }
	FILTER NOT EXISTS { ?item wdt:P576 ?end }
	SERVICE wikibase:label { bd:serviceParam wikibase:language "fr" . } . 
}

À la ligne 4, on observe une nouvelle façon d’écrire la partie centrale (le prédicat) d’un tuple, c’est un chemin de propriétés : /wdt:P279*. Le tuple peut se lire comme :

?item		wdt:P1435	?statut .
?statut		wdt:P279	?classea .

si ?classa n’est pas Q3370013 alors on remonte à la classe englobante :

?classea	wdt:P279	?classeb .

etc., jusqu’à trouver une sous-classe de Q3370013

?classex	wdt:P279	wd:Q3370013 .

C’est à dire : "?item" doit avoir un "statut patrimonial" (P1435), ce statut est sous-classe d’une classe plus vaste, etc., jusqu’à la sous-classe "patrimoine culturel du Québec" (Q3370013).

On parcourt le graphe grâce à la propriété "sous-classe de" (P279) en n’énonçant pas les variables intermédiaires et sans connaître à priori le nombre de nœuds du graphe que l’on va rencontrer.

On peut cependant préciser, par une syntaxe semblable à celle des expressions rationnelles, combien des nœuds du graphe on souhaite parcourir :

  • avec une "*", on veut de zéro à n nœuds,
  • avec un "+", on veut de un à n nœuds,
  • avec un "?", on veut de zéro à un nœud,

De plus on peut spécifier un choix entre plusieurs propriétés pour parcourir le graphe :

Dans cet exemple issu du tutoriel, on cherche les descendants de Bach en remontant par la propriété "mère" (P25) ou "père" (P22) jusqu’à rencontrer "Bach" (Q1339). À chaque nœud, grâce au "|", on a le choix entre les deux propriétés entre parenthèses. Remarquons le "+" qui indique ici qu’on s’arrête aux enfants de Bach : il faut parcourir le chemin vers le nœud "père" ou "mère" au moins une fois. Ceci s’exprime par la syntaxe (wdt:P22|wdt:P25)+.

SELECT ?descendant ?descendantLabel
WHERE
{
  ?descendant (wdt:P22|wdt:P25)+ wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "(AUTO_LANGUAGE]". }
}

Avec ces quelques rudiments, il doit être possible de construire des requêtes simples sur Wikidata. Pour aller plus loin dans la syntaxe SPARQL, voyez le tutoriel.