From 616d69ffeb6bed9c5256a0f7ea3c7e29029e5565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Fernando=20S=C3=A1nchez?= Date: Tue, 13 Mar 2018 13:30:22 +0100 Subject: [PATCH] Add SPARQL notebooks --- lod/SPARQL.ipynb | 1825 ++++++++++++++++++++++++++++++++++++++++++++++ lod/helpers.py | 94 +++ logo.jpg | Bin 0 -> 65558 bytes 3 files changed, 1919 insertions(+) create mode 100644 lod/SPARQL.ipynb create mode 100644 lod/helpers.py create mode 100644 logo.jpg diff --git a/lod/SPARQL.ipynb b/lod/SPARQL.ipynb new file mode 100644 index 0000000..6e99077 --- /dev/null +++ b/lod/SPARQL.ipynb @@ -0,0 +1,1825 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "7276f055a8c504d3c80098c62ed41a4f", + "grade": false, + "grade_id": "cell-0bfe38f97f6ab2d2", + "locked": true, + "schema_version": 1, + "solution": false + } + }, + "source": [ + "
\n", + "
\n", + "

Course Notes for Learning Intelligent Systems

\n", + "

Department of Telematic Engineering Systems

\n", + "
Universidad Politécnica de Madrid
\n", + "
\n", + " \"UPM\"\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "a273399fb0e4a7752cea07a36562def1", + "grade": false, + "grade_id": "cell-0cd673883ee592d1", + "locked": true, + "schema_version": 1, + "solution": false + } + }, + "source": [ + "## Introduction to Linked Data\n", + "\n", + "This lecture provides a quick introduction to semantic queries in Python.\n", + "We will be using DBpedia, a semantic version of Wikipedia.\n", + "\n", + "The language we will use to query DBpedia is SPARQL, a semantic query language inspired by SQL.\n", + "For convenience, the examples in the notebook are executable, and they are accompanied by some code to test the results.\n", + "If the tests pass, you probably got the answer right.\n", + "\n", + "However, you can also use any other method to write and send your queries.\n", + "You may find online query editors particularly useful.\n", + "In addition to running queries from your browser, they provide useful features such as syntax highlighting and autocompletion.\n", + "Some examples are:\n", + "\n", + "* DBpedia's virtuoso query editor https://dbpedia.org/sparql\n", + "* A javascript based client hosted at GSI: http://yasgui.cluster.gsi.dit.upm.es/" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "255c4bd678939b4448860dc5e0afdae6", + "grade": false, + "grade_id": "cell-10264483046abcc4", + "locked": true, + "schema_version": 1, + "solution": false + } + }, + "source": [ + "## Objectives\n", + "\n", + "* Learning SPARQL and the Linked Data principles by defining queries to answer a set of problems of increasing difficulty\n", + "* Verifying the usefulness of the Linked Open Data initiative by querying data from different RDF graphs and endpoints\n", + "* Learning how to use integrated SPARQL editors and programming interfaces to SPARQL." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "f04dd27e103bacc5166763900527901e", + "grade": false, + "grade_id": "cell-4f8492996e74bf20", + "locked": true, + "schema_version": 1, + "solution": false + } + }, + "source": [ + "## Tools\n", + "\n", + "* This notebook\n", + "* SPARQL editors (optional)\n", + " * YASGUI-GSI http://yasgui.cluster.gsi.dit.upm.es\n", + " * DBpedia virtuoso http://dbpedia.org/sparql\n", + "\n", + "Using the YASGUI-GSI editor has several advantages over other options.\n", + "It features:\n", + "\n", + "* Selection of data source, either by specifying the URL or by selecting from a dropdown menu\n", + "* Interactive query editing\n", + " * A set of pre-defined queries\n", + " * Syntax errors\n", + " * Auto-complete\n", + "* Data visualization\n", + " * Total number of results\n", + " * Different formats (table, pivot table, raw response, etc.)\n", + " * Pagination of results\n", + " * Search and filter results" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "c5f8646518bd832a47d71f9d3218237a", + "grade": false, + "grade_id": "cell-eb13908482825e42", + "locked": true, + "schema_version": 1, + "solution": false + } + }, + "source": [ + "Run this line to enable the `%%sparql` magic command." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `%%sparql` magic command will allow us to use SPARQL inside normal jupyter cells.\n", + "\n", + "For instance, the following code:\n", + "\n", + "```\n", + "%%sparql\n", + "\n", + "MY QUERY\n", + "``` \n", + "\n", + "Is the same as `run_query('MY QUERY', endpoint='http://dbpedia.org/sparql')` plus some additional steps, such as saving the results in a nice table format so that they can be used later and storing the results in a variable (`LAST_QUERY`), which we will use in our tests.\n", + "\n", + "You do not need to worry about it, and **you can always use one of the suggested online editors if you wish**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises\n", + "\n", + "The following exercises cover the basics of SPARQL with simple use cases.\n", + "We will provide you some example code to get you started, the *question* you will have to answer using SPARQL, and the skeleton for the answer.\n", + "\n", + "After every query, you will find some python code to test the results of the query.\n", + "Make sure you've run the tests before moving to the next exercise.\n", + "If the test gives you an error, you've probably done something wrong.\n", + "You **do not need to understand or modify the test code**.\n", + "\n", + "\n", + "In case you're interested, the tests rely on the `LAST_QUERY` variable, which is updated by the `%%sparql` magic after every query.\n", + "This variable contains the full query used (`LAST_QUERY[\"query\"]`), the endpoint it was sent to (`LAST_QUERY[\"endpoint\"]`), and a dictionary with the response of the endpoint (`LAST_QUERY[\"results\"]`).\n", + "For convenience, the results are also given as tuples (`LAST_QUERY[\"tuples\"]`), and as a dictionary of of `{column:[values]}` (`LAST_QUERY[\"columns\"]`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### First Select\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start with a simple query. We will get a list of cities and towns in Madrid.\n", + "If we take a look at the DBpedia ontology or the page of any town we already know, we discover that the property that links towns to their community is [`isPartOf`](http://dbpedia.org/ontology/isPartOf), and [the Community of Madrid is also a resource in DBpedia](http://dbpedia.org/resource/Community_of_Madrid)\n", + "\n", + "Since there are potentially many cities to get, we will limit our results to the first 10 results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "SELECT ?localidad\n", + "WHERE {\n", + " ?localidad \n", + "}\n", + "LIMIT 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, that query is very verbose because we are using full URIs.\n", + "To simplify it, we will make use of SPARQL prefixes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX dbo: \n", + "PREFIX dbr: \n", + " \n", + "SELECT ?localidad\n", + "WHERE {\n", + " ?localidad dbo:isPartOf dbr:Community_of_Madrid.\n", + "}\n", + "LIMIT 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To make sure that the query returned something sensible, we can test it with some python code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert 'localidad' in LAST_QUERY['columns']\n", + "assert len(LAST_QUERY['tuples']) == 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that you have some experience under your belt, it is time to design your own query.\n", + "\n", + "Your first task it to get a list of Spanish Novelits, using the skeleton below and the previous query to guide you.\n", + "\n", + "Pages for Spanish novelists are grouped in the *Spanish novelists* DBpedia category. You can use that fact to get your list.\n", + "In other words, the difference from the previous query will be using `dct:subject` instead of `dbo:isPartOf`, and `dbc:Spanish_novelists` instead of `dbr:Community_of_Madrid`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "d73b49b84482f51dc199b0e22763e9cc", + "grade": false, + "grade_id": "cell-7a9509ff3c34127e", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "\n", + "SELECT ?escritor\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "LIMIT 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "a5aafd75ac7fa036fe5dafc4ed30c535", + "grade": true, + "grade_id": "cell-91240ded2cac7b6d", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert len(LAST_QUERY['columns']) == 1 # We only use one variable, ?escritor\n", + "assert len(LAST_QUERY['tuples']) == 10 # There should be 10 results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using more criteria\n", + "\n", + "We can get more than one property in the same query. Let us modify our query to get the population of the cities as well." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dbo: \n", + "PREFIX dbr: \n", + " \n", + "SELECT ?localidad ?pop ?when\n", + "\n", + "WHERE {\n", + " ?localidad dbo:populationTotal ?pop .\n", + " ?localidad dbo:isPartOf dbr:Community_of_Madrid.\n", + " ?localidad dbp:populationAsOf ?when .\n", + "}\n", + "\n", + "LIMIT 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert 'localidad' in LAST_QUERY['columns']\n", + "assert 'http://dbpedia.org/resource/Parla' in LAST_QUERY['columns']['localidad']\n", + "assert ('http://dbpedia.org/resource/San_Sebastián_de_los_Reyes', '75912', '2009') in LAST_QUERY['tuples']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Time to try it yourself.\n", + "\n", + "Get the list of Spanish novelists AND their name (using rdfs:label)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "7cbf5260bbc6121b4ec1ec0f62e814c1", + "grade": false, + "grade_id": "cell-83dcaae0d09657b5", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs:\n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "\n", + "SELECT ?escritor ?name\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "LIMIT 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "5c7bee95c0c08a8ede47fcaad597f51f", + "grade": true, + "grade_id": "cell-8afd28aada7a896c", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'escritor' in LAST_QUERY['columns']\n", + "assert 'http://dbpedia.org/resource/Eduardo_Mendoza_Garriga' in LAST_QUERY['columns']['escritor']\n", + "assert ('http://dbpedia.org/resource/Eduardo_Mendoza_Garriga', 'Eduardo Mendoza') in LAST_QUERY['tuples']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering and ordering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous example, we saw that we got what seemed to be duplicated answers.\n", + "\n", + "This happens because entities can have labels in different languages (e.g. English, Spanish).\n", + "To restrict the search to only those results we're interested in, we can use filtering.\n", + "\n", + "We can also decide the order in which our results are shown.\n", + "\n", + "For instance, this is how we could use filtering to get only large cities in our example, ordered by population:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dbo: \n", + "PREFIX dbr: \n", + " \n", + "SELECT ?localidad ?pop ?when\n", + "\n", + "WHERE {\n", + " ?localidad dbo:populationTotal ?pop .\n", + " ?localidad dbo:isPartOf dbr:Community_of_Madrid.\n", + " ?localidad dbp:populationAsOf ?when .\n", + " FILTER(?pop > 100000)\n", + "}\n", + "ORDER BY ?pop\n", + "LIMIT 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that ordering happens before limits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "c6080c3ed1dd3e9c3a224ac74e9dedc6", + "grade": true, + "grade_id": "cell-cb7b8283568cd349", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "# We still have the biggest city\n", + "assert ('http://dbpedia.org/resource/Madrid', '3141991', '2014') in LAST_QUERY['tuples']\n", + "# But the smaller ones are gone\n", + "assert 'http://dbpedia.org/resource/Tres_Cantos' not in LAST_QUERY['columns']['localidad']\n", + "assert 'http://dbpedia.org/resource/San_Sebastián_de_los_Reyes' not in LAST_QUERY['columns']['localidad']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, try filtering to get a list of novelists and their name in Spanish, ordered by name `(FILTER (LANG(?nombre) = \"es\") y ORDER BY`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "8b1697739ecd76d45b6597a28429f13d", + "grade": false, + "grade_id": "cell-ff3d611cb0304b01", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "\n", + "SELECT ?escritor, ?nombre\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 1000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "2300be1911eb9cfddc6e2a82dcb244c2", + "grade": true, + "grade_id": "cell-d70cc6ea394741bc", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert len(LAST_QUERY['tuples']) >= 50\n", + "assert 'Adelaida García Morales' in LAST_QUERY['columns']['nombre']\n", + "assert sum(1 for k in LAST_QUERY['columns']['escritor'] if k == 'http://dbpedia.org/resource/Adelaida_García_Morales') == 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From now on, we will focus on our Writers example.\n", + "\n", + "First, search for writers born in the XX century.\n", + "You can use a special filter, knowing that `\"2000\"^^xsd:date` is the first date of year 2000." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "1764314669c1e3ad131a0930fa33549c", + "grade": false, + "grade_id": "cell-ab7755944d46f9ca", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbo:\n", + "\n", + "SELECT ?escritor, ?nombre, year(?fechaNac) as ?nac\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 1000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "7a3c047b64ce4ffd02c87878f73f212a", + "grade": true, + "grade_id": "cell-cf3821f2d33fb0f6", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'Camilo José Cela' in LAST_QUERY['columns']['nombre']\n", + "assert 'Javier Marías' in LAST_QUERY['columns']['nombre']\n", + "assert all(int(x) > 1899 and int(x) < 2001 for x in LAST_QUERY['columns']['nac'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optional\n", + "\n", + "In our last example, we were missing all the novelists that are missing their birth information in DBpedia.\n", + "\n", + "We can specify optional values in a query using the `OPTIONAL` keyword.\n", + "When a set of clauses are inside an OPTIONAL group, the SPARQL endpoint will try to use them in the query\n", + "If there are no results for that part of the query, the variables it specifies will not be bound (i.e. they will be empty).\n", + "\n", + "Using that, let us retrieve all the novelists born between 1900 and 2000, and the date they died (if they are available)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "429b902d4da0f40aefebba0ab722645e", + "grade": false, + "grade_id": "cell-254a18dd973e82ed", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbo:\n", + "\n", + "SELECT ?escritor, ?nombre, ?fechaNac, ?fechaDef\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "ed9321d104cd1d6e7839e3bcac78a8f1", + "grade": true, + "grade_id": "cell-4d6a64dde67f0e11", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'Miguel de Cervantes' in LAST_QUERY['columns']['nombre']\n", + "assert '1547-1-1' in LAST_QUERY['columns']['fechaNac']\n", + "assert '' not in LAST_QUERY['columns']['fechaNac'] # All birthdates are defined\n", + "assert '' in LAST_QUERY['columns']['fechaDef'] # Some deathdates are not defined" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bound" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can check whether the optional value for a key was bound in a SPARQL query using `BOUND(?key)`.\n", + "\n", + "This is very useful for two purposes.\n", + "First, it allows us to look for patterns that **do not occur** in the graph, such as missing properties.\n", + "For instance, we could search for the authors with missing birth information so we can add it.\n", + "Secondly, we can use bound in filters to get conditional filters.\n", + "We will explore both uses in this exercise." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get the list of Spanish novelists that are still alive.\n", + "A person is alive if their death date is not defined and the were born less than 100 years ago" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "555154c87d8722bfeacd0e5cf5abc1a7", + "grade": false, + "grade_id": "cell-474b1a72dec6827c", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbo:\n", + "\n", + "SELECT ?escritor, ?nombre, year(?fechaNac) as ?nac\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 1000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "fd420f3d8b7eca269eaba715b3999893", + "grade": true, + "grade_id": "cell-46b62dd2856bc919", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'Fernando Arrabal' in LAST_QUERY['columns']['nombre']\n", + "assert 'Albert Espinosa' in LAST_QUERY['columns']['nombre']\n", + "for year in LAST_QUERY['columns']['nac']:\n", + " assert int(year) >= 1918" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, get the list of Spanish novelists that died before their fifties (i.e. younger than 50 years old), or that aren't 50 years old yet.\n", + "\n", + "Hint: you can use boolean logic in your filters (e.g. `&&` and `||`).\n", + "\n", + "Hint 2: Some dates are not formatted properly, which makes some queries fail when they shouldn't. You might need to convert between different types as a workaround. For instance, you could get the year from a date like this: `year(xsd:dateTime(str(?date)))`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "22505aa8eab7f771bf30ed12fe13f80c", + "grade": false, + "grade_id": "cell-ceefd3c8fbd39d79", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbo:\n", + "\n", + "SELECT ?escritor, ?nombre, year(?fechaNac) as ?nac, ?fechaDef\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "f11cf03b1c9ae7dbdaac314579b6c4bf", + "grade": true, + "grade_id": "cell-461cd6ccc6c2dc79", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'Javier Sierra' in LAST_QUERY['columns']['nombre']\n", + "assert 'http://dbpedia.org/resource/Sanmao_(author)' in LAST_QUERY['columns']['escritor']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Finding unique elements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In our last example, our results show some authors more than once.\n", + "This is because some properties are defined more than once.\n", + "For instance, birth date is giving using different formats.\n", + "Even if we exclude that property from our results by not adding it in our `SELECT`, we will get duplicated lines.\n", + "\n", + "To solve this, we can use the `DISTINCT` keyword." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Modify your last query to remove duplicated lines.\n", + "In other words, authors should only appear once." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "1380346cba93b5641132ba21f102e116", + "grade": false, + "grade_id": "cell-2a39adc71d26ae73", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbo:\n", + "\n", + "SELECT DISTINCT ?escritor, ?nombre, year(?fechaNac) as ?nac\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "c8e5bf05e9d050389b2f8e7f142fdab0", + "grade": true, + "grade_id": "cell-542e0e36347fd5d1", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'Javier Sierra' in LAST_QUERY['columns']['nombre']\n", + "assert 'http://dbpedia.org/resource/Albert_Espinosa' in LAST_QUERY['columns']['escritor']\n", + "\n", + "from collections import Counter\n", + "c = Counter(LAST_QUERY['columns']['nombre'])\n", + "for count in c.values():\n", + " assert count == 1\n", + " \n", + "c1 = Counter(LAST_QUERY['columns']['escritor'])\n", + "assert all(count==1 for count in c1.values())\n", + "# c = Counter(LAST_QUERY['columns']['nombre'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using other resources" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get the list of living Spanish novelists born in Madrid.\n", + "\n", + "Hint: use `dbr:Madrid` and `dbo:birthPlace`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "32e2c9b0ce32483960f5ca794da54fa8", + "grade": false, + "grade_id": "cell-d175e41da57c889b", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "SELECT DISTINCT ?escritor, ?nombre, ?lugarNac, year(?fechaNac) as ?nac\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "db2cdda5575af942f110d85e2dbe02b5", + "grade": true, + "grade_id": "cell-fadd095862db6bc8", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'José Ángel Mañas' in LAST_QUERY['columns']['nombre']\n", + "assert 'http://dbpedia.org/resource/Madrid' in LAST_QUERY['columns']['lugarNac']\n", + "MADRID_QUERY = LAST_QUERY['columns'].copy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Traversing the graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get the list of works of the authors in the previous query (i.e. authors born in Madrid), if they have any.\n", + "\n", + "Hint: use `dbo:author`, which is a **property of a literary work** that points to the author." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "abd3d09bdf5801d6d0b27d80326dfead", + "grade": false, + "grade_id": "cell-e4b99af9ef91ff6f", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "SELECT DISTINCT ?escritor, ?nombre, ?obra\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 10000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "d1305aa44456d51e3c52d78a9381f73a", + "grade": true, + "grade_id": "cell-68661b73c2140e4f", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'http://dbpedia.org/resource/A_Heart_So_White' in LAST_QUERY['columns']['obra']\n", + "assert 'http://dbpedia.org/resource/Tomorrow_in_the_Battle_Think_on_Me' in LAST_QUERY['columns']['obra']\n", + "assert '' in LAST_QUERY['columns']['obra'] # Some authors don't have works in dbpedia" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also get a list of the works in string format using GROUP_CONCAT.\n", + "For instance, `GROUP_CONCAT(?obra, \",\")`, to separate works with a comma.\n", + "\n", + "Try it yourself:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "f0ab8a246687b926fb919abbafaf3b53", + "grade": false, + "grade_id": "cell-e13fae23ccb78bb8", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "# YOUR CODE HERE\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 10000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Traversing the graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get a list of living Spanish novelists born in Madrid, their name in Spanish, a link to their foto and a website (if they have one).\n", + "\n", + "If the query is right, you should see a list of writers after running the test code.\n", + "\n", + "Hint: `foaf:depiction` and `foaf: homepage`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "4ffc5d79f79c2079e93843838e91e053", + "grade": false, + "grade_id": "cell-b1f71c67dd71dad4", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "SELECT ?escritor ?web ?foto\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "ORDER BY ?nombre\n", + "LIMIT 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "bc497e6eaebe05e31248e3479df43c0c", + "grade": true, + "grade_id": "cell-8b8ba7cca701c652", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "fotos = set(filter(lambda x: x != '', LAST_QUERY['columns']['foto']))\n", + "assert len(fotos) > 2\n", + "show_photos(fotos) #show the pictures of the writers!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Union" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can merge the results of several queries, just like using `JOIN` in SQL.\n", + "The keyword in SPARQL is `UNION`, because we are merging graphs.\n", + "\n", + "`UNION` is useful in many situations.\n", + "For instance, when there are equivalent properties, or when you want to use two search terms and FILTER would be too inefficient.\n", + "\n", + "The syntax is as follows:\n", + "\n", + "```sparql\n", + "SELECT ?title\n", + "WHERE {\n", + " { ?book dc10:title ?title }\n", + " UNION\n", + " { ?book dc11:title ?title }\n", + " \n", + " ... REST OF YOUR QUERY ...\n", + "\n", + "}\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using UNION, get a list of distinct spanish novelists AND poets.\n", + "\n", + "Hint: Category: Spanish_poets" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "5606810420d8cd259da74a3cc17fa824", + "grade": false, + "grade_id": "cell-21eb6323b6d0011d", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "SELECT DISTINCT ?escritor, ?nombre\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 10000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "eec248e71a855a5e713d31ae470f3fd4", + "grade": true, + "grade_id": "cell-004e021e877c6ace", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert 'Garcilaso de la Vega' in LAST_QUERY['columns']['nombre']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also get the count of results either by inspecting the result (we will not cover this) or by aggregating the results using the `COUNT` operation.\n", + "\n", + "The syntax is:\n", + " \n", + "```sparql\n", + "SELECT COUNT(?variable) as ?count_name\n", + "```\n", + "\n", + "Try it yourself with our previous example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "2452c6213ad156deb5adbcfaeef74b8b", + "grade": false, + "grade_id": "cell-e35414e191c5bf16", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "# YOUR CODE HERE\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "LIMIT 10000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "f8b76d57ce959522a3914a442835393a", + "grade": true, + "grade_id": "cell-7a7ef8255a5662e2", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert len(LAST_QUERY['columns']) == 1\n", + "column_name = list(LAST_QUERY['columns'].keys())[0]\n", + "assert int(LAST_QUERY['columns'][column_name][0]) > 200" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Regular expressions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "580570dba869801272f9948f1e901bfd", + "grade": false, + "grade_id": "cell-a57d3546a812f689", + "locked": false, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "%%sparql\n", + "\n", + "PREFIX rdfs: \n", + "PREFIX dct:\n", + "PREFIX dbc:\n", + "PREFIX dbr:\n", + "PREFIX dbo:\n", + "\n", + "# YOUR CODE HERE\n", + "\n", + "WHERE {\n", + "# YOUR CODE HERE\n", + "}\n", + "# YOUR CODE HERE\n", + "LIMIT 1000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "checksum": "71b5b187bb147c0e7444b29a4f413720", + "grade": true, + "grade_id": "cell-c149fe65008f39a9", + "locked": true, + "points": 0, + "schema_version": 1, + "solution": false + } + }, + "outputs": [], + "source": [ + "assert len(LAST_QUERY['columns']['nombre']) > 15\n", + "for i in LAST_QUERY['columns']['nombre']:\n", + " assert 'Juan' in i" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Additional exercises" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Find out if there are more dbpedia entries for writers (dbo:Writer) than for football players (dbo:SoccerPlayers)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get a list of European countries with a population higher than 20 million, in decreasing order of population, including their URI, name in English and population." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Find the country in the world that speaks the most languages. Show its name in Spanish, if available." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Querying custom data\n", + "\n", + "In the last part of this course, we will query the data annotated in the previous course on RDF.\n", + "\n", + "The goal is to try SPARQL with data annotated by users with limited knowledge of vocabularies and semantics, and to compare the experience with similar queries to a more structured dataset.\n", + "\n", + "Hence, there are two parts.\n", + "First, you will query a set of graphs annotated by students of this course.\n", + "Then, you will query a synthetic dataset that contains similar information." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In particular, you need to run five queries, each one will answer one of the following questions:\n", + "\n", + "* Number of hotels (or entities) with reviews\n", + "* Number of reviews\n", + "* The hotel with the lowest average score\n", + "* The hotel with the highest average score\n", + "* A list of hotels with their addresses and telephone numbers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Manually annotated" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Querying the manually annotated dataset is slightly different from querying DBpedia.\n", + "The main difference is that this dataset uses different graphs to separate the annotations from different students.\n", + "\n", + "First, let us get a list of graphs available:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql http://fuseki.cluster.gsi.dit.upm.es/ejerciciohoteles\n", + " \n", + "SELECT ?g WHERE {\n", + " GRAPH ?g {\n", + " ?s ?p ?o .\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you have this list, you can query specific graphs like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql http://fuseki.cluster.gsi.dit.upm.es/ejerciciohoteles\n", + " \n", + "SELECT *\n", + "WHERE {\n", + " GRAPH {\n", + " ?s ?p ?o .\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, design five queries to answer the questions in the description, and run each of them in at least five of these graphs.\n", + "\n", + "You can manually run the queries or use the code below, where you only need to specify your queries and the graphs you have identified.\n", + "\n", + "If you need additional prefixes, feel free to modify the TEMPLATE variable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "\n", + "QUERIES = {\n", + " 'highest score': '''\n", + " ?s ?p ?o\n", + "''',\n", + " 'lowest score': '''\n", + " ?s ?p ?o\n", + " ''',\n", + " 'number of hotels': '''\n", + " ?s ?p ?o\n", + " ''',\n", + " 'number of reviews': '''\n", + " ?s ?p ?o\n", + " ''',\n", + " 'telephones and addresses': '''\n", + " ?s ?p ?o\n", + " ''',\n", + " \n", + "}\n", + "\n", + "TEMPLATE = '''\n", + "SELECT * WHERE {{\n", + " GRAPH <{graph}>{{\n", + " {query}\n", + " }}\n", + " }}\n", + "'''\n", + "\n", + "GRAPHS = ['http://fuseki.cluster.gsi.dit.upm.es/36de86e6754934381d935f10618fe985',\n", + " ]\n", + "\n", + "for name, query in QUERIES.items():\n", + " for graph in GRAPHS:\n", + " print(name, '@', graph)\n", + " display(sparql('http://fuseki.cluster.gsi.dit.upm.es/ejerciciohoteles', TEMPLATE.format(graph=graph,\n", + " query=query)\n", + " ))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Synthetic dataset\n", + "\n", + "Now, run the same queries in the synthetic dataset.\n", + "\n", + "The query below should get you started:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%sparql http://fuseki.cluster.gsi.dit.upm.es/hotelessintetico \n", + "\n", + "SELECT *\n", + "WHERE {\n", + " ?s ?p ?o .\n", + "}\n", + "LIMIT 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Discussion\n", + "\n", + "Compare the results of the synthetic and the manual dataset, and answer these questions:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Both datasets should use the same schema. Are there any differences when it comes to using them?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "11e7e2b7d3dfb45f9534506761f896f9", + "grade": true, + "grade_id": "cell-9bd08e4f5842cb89", + "locked": false, + "points": 0, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "# YOUR CODE HERE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Are data correctly annotated in both datasets?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "f676f18c71297e8429448fa0f0833db1", + "grade": true, + "grade_id": "cell-9dc1c9033198bb18", + "locked": false, + "points": 0, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "# YOUR CODE HERE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Has any of the datasets been harder to query? Why?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "2a24b20a338d18f4879540f5e03f5889", + "grade": true, + "grade_id": "cell-0e63b8e9dcb24676", + "locked": false, + "points": 0, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "# YOUR CODE HERE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Has any of the datasets been harder to query? Why" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "2ec2cf74959db9112c189a4e7a0b3609", + "grade": true, + "grade_id": "cell-6c18003ced54be23", + "locked": false, + "points": 0, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "# YOUR CODE HERE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Are data correctly annotated in both datasets" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "deletable": false, + "nbgrader": { + "checksum": "4a062d17043e5459a48314b1177cb8f1", + "grade": true, + "grade_id": "cell-cdce24ef5f581981", + "locked": false, + "points": 0, + "schema_version": 1, + "solution": true + } + }, + "outputs": [], + "source": [ + "# YOUR CODE HERE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* [RDFLib documentation](https://rdflib.readthedocs.io/en/stable/).\n", + "* [Wikidata Query Service query examples](https://www.wikidata.org/wiki/Wikidata:SPARQL_query_service/queries/examples)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Licence\n", + "The notebook is freely licensed under under the [Creative Commons Attribution Share-Alike license](https://creativecommons.org/licenses/by/2.0/). \n", + "\n", + "© 2018 Universidad Politécnica de Madrid." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/lod/helpers.py b/lod/helpers.py new file mode 100644 index 0000000..b92f3d2 --- /dev/null +++ b/lod/helpers.py @@ -0,0 +1,94 @@ +from IPython.core.magic import (register_line_magic, register_cell_magic, + register_line_cell_magic) + +from IPython.display import HTML, display, Image +from urllib.request import Request, urlopen +from urllib.parse import quote_plus, urlencode +from urllib.error import HTTPError + +import json + + +def send_query(query, endpoint): + FORMATS = ",".join(["application/sparql-results+json", "text/javascript", "application/json"]) + + data = {'query': query} + # b = quote_plus(query) + + r = Request(endpoint, + data=urlencode(data).encode('utf-8'), + headers={'content-type': 'application/x-www-form-urlencoded', + 'accept': FORMATS}, + method='POST') + return json.loads(urlopen(r).read().decode('utf-8')); + + +def tabulate(tuples, header=None): + if not header: + header, tuples = tuples[0], tuples[1:] + header = '{}'.format(''.join('{}'.format(h) for h in header)) + rows = [] + for row in tuples: + inner = ''.join('{}'.format(c) for c in row) + rows.append('{}'.format(inner)) + body = ''.join(rows) + table = '{header}{body}
'.format(body=body, + header=header) + return table + + +LAST_QUERY = {} + + +def query(query, endpoint=None, print_table=False): + global LAST_QUERY + + endpoint = endpoint or "http://dbpedia.org/sparql" + results = send_query(query, endpoint) + tuples = to_table(results) + + + columns = {} + header, values = tuples[0], tuples[1:] + + for ix, h in enumerate(header): + columns[h] = [] + for row in values: + columns[h].append(row[ix]) + + LAST_QUERY.update({ + 'query': query, + 'endpoint': query, + 'results': results, + 'tuples': values, + 'columns': columns + }) + + if not print_table: + return tuples + return HTML(tabulate(tuples)) + + +def to_table(results): + table = [] + header = results['head']['vars'] + table.append(header) + for result in results["results"]["bindings"]: + table.append(tuple(result.get(h, {}).get('value', "") for h in header)) + return table + + +@register_cell_magic +def sparql(line, cell): + try: + return query(cell, endpoint=line, print_table=True) + except HTTPError as ex: + error_message = ex.read().decode('utf-8') + print('Error {}. Reason: {}'.format(ex.status, ex.reason)) + print(error_message) + + +def show_photos(values): + for value in values: + if 'http://' in value: + display(Image(url=value)) diff --git a/logo.jpg b/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ff723a773eff6a47d18442a197fd43c0cfce52a5 GIT binary patch literal 65558 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 d_rhhnq)%pN0NyzinUi0VzUVa(_TB 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