<jupyter_start><jupyter_text>SPARQL TutorialFrom the very fine Jena SPARQL Tutorial: https://jena.apache.org/tutorialsThe objective of this SPARQL tutorial is to give a fast course in SPARQL. The tutorial covers the major features of the query language through examples but does not aim to be complete.If you are looking for a short introduction to SPARQL and Jena try Search RDF data with SPARQL. If you are looking to execute SPARQL queries in code and already known SPARQL then you likely want to read the ARQ Documentation instead.SPARQL is a query language and a protocol for accessing RDF designed by the W3C RDF Data Access Working Group. As a query language, SPARQL is “data-oriented” in that it only queries the information held in the models; there is no inference in the query language itself.  Of course, the Jena model may be ‘smart’ in that it provides the impression that certain triples exist by creating them on-demand, including OWL reasoning.  SPARQL does not do anything other than take the description of what the application wants, in the form of a query, and returns that information, in the form of a set of bindings or an RDF graph.  First, some DataFrame helpIt's going to be useful to both view and manipulate these data using DataFrames. So, first a helpful function to convert a SPARQL query to a DataFrame.<jupyter_code>#%load_ext nb_black

import rdflib
import io
import pandas as pd
import json
from pprint import pprint
from box import Box


def to_dataframe(sparql_query_result):
    """
    Convert the result of a SPARQL query into a Pandas DataFrame
    
    A bit naive, in that I'm not handling types at all.
    """

    qres_dict = json.loads(sparql_query_result.serialize(format="json"))
    b = Box(qres_dict)

    vars = b.head.vars

    rows = []
    for v in b.results.bindings:
        row = {}
        for var in vars:
            try:
                chunk = {var: v[var].value}
                row.update(chunk)
            except:
                chunk = {var: None}
        rows.append(row)

    df = pd.DataFrame(rows)
    return df<jupyter_output><empty_output><jupyter_text>Data FormatsFirst, we need to be clear about what data is being queried. SPARQL queries RDF graphs. An RDF graph is a set of triples (Jena calls RDF graphs “models” and triples “statements” because that is what they were called at the time the Jena API was first designed).It is important to realize that it is the triples that matter, not the serialization. The serialization is just a way to write the triples down. RDF/XML is the W3C recommendation but it can be difficult to see the triples in the serialized form because there are multiple ways to encode the same graph.  In this tutorial, we use a more “triple-like” serialization, called Turtle (see also N3 language described in the W3C semantic web primer).<jupyter_code>turtle_data = """
@prefix vCard:   <http://www.w3.org/2001/vcard-rdf/3.0#> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

<http://somewhere/MattJones/>  vCard:FN   "<NAME>" .
<http://somewhere/MattJones/>  vCard:N    _:b0 .
_:b0  vCard:Family "Jones" .
_:b0  vCard:Given  "Matthew" .


<http://somewhere/RebeccaSmith/> vCard:FN    "<NAME>" .
<http://somewhere/RebeccaSmith/> vCard:N     _:b1 .
_:b1 vCard:Family "Smith" .
_:b1 vCard:Given  "Rebecca" .

<http://somewhere/JohnSmith/>    vCard:FN    "<NAME>" .
<http://somewhere/JohnSmith/>    vCard:N     _:b2 .
_:b2 vCard:Family "Smith" .
_:b2 vCard:Given  "John"  .

<http://somewhere/SarahJones/>   vCard:FN    "<NAME>" .
<http://somewhere/SarahJones/>   vCard:N     _:b3 .
_:b3 vCard:Family  "Jones" .
_:b3 vCard:Given   "Sarah" .
"""

g = rdflib.Graph()
g.parse(format="turtle", data=turtle_data)<jupyter_output><empty_output><jupyter_text>A First SPARQL Query<jupyter_code>sparql_query = """
SELECT ?x
WHERE { ?x  <http://www.w3.org/2001/vcard-rdf/3.0#FN>  "<NAME>" }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>This works by matching the triple pattern in the WHERE clause against the triples in the RDF graph. The predicate and object of the triple are fixed values so the pattern is going to match only triples with those values. The subject is a variable, and there are no other restrictions on the variable. The pattern matches any triples with these predicate and object values, and it matches with solutions for x.The item enclosed in  is a URI (actually, it’s an IRI) and the item enclosed in "” is a plain literal. Just like Turtle, N3 or N-triples, typed literals are written with ^^ and language tags can be added with @.?x is a variable called x. The ? does not form part of the name which is why it does not appear in the table output.There is one match. The query returns the match in the x query variable. The output shown was obtained by using one of ARQ’s command line applications.  Basic patternsThis section covers basic patterns and solutions, the main building blocks of SPARQL queries.  SolutionsQuery solutions are a set of pairs of a variable name with a value. A SELECT query directly exposes the solutions (after order/limit/offset are applied) as the result set - other query forms use the solutions to make a graph. The solution is the way the pattern matched - which values the variables must take for a pattern to match.The first query example had a single solution. Change the pattern to this second query:<jupyter_code>sparql_query = """
SELECT ?x ?fname
WHERE {?x  <http://www.w3.org/2001/vcard-rdf/3.0#FN>  ?fname}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>This has 4 solutions, one for each VCARD name property triples in the data source.So far, with triple patterns and basic patterns, every variable will be defined in every solution. The solutions to a query can be thought of a table, but in the general case, it is a table where not every row will have a value for every column. All the solutions to a given SPARQL query don’t have to have values for all the variables in every solution as we shall see later.  Basic PatternsA basic pattern is a set of triple patterns. It matches when the triple patterns all match with the same value used each time the variable with the same name is used.<jupyter_code>sparql_query = """
SELECT ?givenName
WHERE
  { ?y  <http://www.w3.org/2001/vcard-rdf/3.0#Family>  "Smith" .
    ?y  <http://www.w3.org/2001/vcard-rdf/3.0#Given>  ?givenName .
  }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>This query involves two triple patterns, each triple ends in a ‘.’ (but the dot after the last one can be omitted like it was in the one triple pattern example). The variable y has to be the same for each triple pattern match.  QNamesThere is shorthand mechanism for writing long URIs using prefixes. The query above is more clearly written as the query:<jupyter_code>sparql_query = """
PREFIX vcard:      <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?givenName
WHERE
 { ?y vcard:Family "Smith" .
   ?y vcard:Given  ?givenName .
 }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>This is a prefixing mechanism - the two parts of the URIs, from the prefix declaration and from the part after the “:” in the qname, are concatenated together. This is strictly not what an XML qname is but uses the RDF rule for turning a qname into a URI by concatenating the parts.  Blank NodesNote that the y will be rendered as a full BNode in rdflib. It's tough to read that they are truly different, but they are.<jupyter_code>sparql_query = """
PREFIX vcard:      <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?y ?givenName
WHERE
 { ?y vcard:Family "Smith" .
   ?y vcard:Given  ?givenName .
 }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>FiltersGraph matching allows patterns in the graph to be found. This section describes how the values in a solution can be restricted. There are many comparisons available - we just cover two cases here.  String MatchingSPARQL provides an operation to test strings, based on regular expressions.  This includes the ability to ask SQL “LIKE” style tests, although the syntax of the regular expression is different from SQL.The syntax is:`FILTER regex(?x, "pattern" [, "flags"])`The flags argument is optional.  The flag “i” means a case-insensitive pattern match is done.The example query finds given names with an “r” or “R” in them.<jupyter_code>sparql_query = """
PREFIX vcard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?g
WHERE
{ ?y vcard:Given ?g .
  FILTER regex(?g, "r", "i") }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>The regular expression language is the same as the XQuery regular expression language which is codified version of that found in Perl.  Testing ValuesThere are times when the application wants to filter on the value of a variable.  In the data, we have added an extra field for age.  Age is not defined by the vCard schema so we have created a new property for the purpose of this tutorial.  RDF allows such mixing of different definitions of information because URIs are unique. Note also that the info:age property value is typed.<jupyter_code>xml_data = """
<rdf:RDF
  xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
  xmlns:vCard='http://www.w3.org/2001/vcard-rdf/3.0#'
  xmlns:info='http://somewhere/peopleInfo#'
   >

  <rdf:Description rdf:about="http://somewhere/JohnSmith">
    <vCard:FN><NAME></vCard:FN>
    <info:age rdf:datatype='http://www.w3.org/2001/XMLSchema#integer'>25</info:age>
    <vCard:N rdf:parseType="Resource">
    <vCard:Family>Smith</vCard:Family>
    <vCard:Given>John</vCard:Given>
    </vCard:N>
  </rdf:Description>

  <rdf:Description rdf:about="http://somewhere/RebeccaSmith">
    <vCard:FN><NAME></vCard:FN>
    <info:age rdf:datatype='http://www.w3.org/2001/XMLSchema#integer'>23</info:age>
    <vCard:N rdf:parseType="Resource">
    <vCard:Family>Smith</vCard:Family>
    <vCard:Given>Rebecca</vCard:Given>
    </vCard:N>
  </rdf:Description>

  <rdf:Description rdf:about="http://somewhere/SarahJones">
    <vCard:FN><NAME></vCard:FN>
    <vCard:N rdf:parseType="Resource">
    <vCard:Family>Jones</vCard:Family>
    <vCard:Given>Sarah</vCard:Given>
    </vCard:N>
  </rdf:Description>

  <rdf:Description rdf:about="http://somewhere/MattJones">
    <vCard:FN><NAME></vCard:FN>
    <vCard:N
    vCard:Family="Jones"
    vCard:Given="Matthew"/>
  </rdf:Description>

</rdf:RDF>
"""

g = rdflib.Graph()
g.parse(format="xml", data=xml_data)<jupyter_output><empty_output><jupyter_text>So, a query to find the names of people who are older than 24 is:<jupyter_code>sparql_query = """
PREFIX info: <http://somewhere/peopleInfo#>

SELECT ?resource
WHERE
  {
    ?resource info:age ?age .
    FILTER (?age >= 24)
  }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>The arithmetic expression must be in parentheses (round brackets). Just one match, resulting in the resource URI for <NAME>. Turning this round to ask for those less than 24 also yields one match for <NAME>.  Nothing about the Jones’s.The database contains no age information about the Jones: there are no info:age properties on these vCards so the variable age did not get a value and so was not tested by the filter.  Optional informationRDF is semi-structured data so SPARQL has a the ability to query for data but not to fail query when that data does not exist. The query is using an optional part to extend the information found in a query solution but to return the non-optional information anyway.  OPTIONALsThis query gets the name of a person and also their age if that piece of information is available.<jupyter_code>sparql_query = """
PREFIX info:    <http://somewhere/peopleInfo#>
PREFIX vcard:   <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name ?age
WHERE
{
    ?person vcard:FN  ?name .
    OPTIONAL { ?person info:age ?age }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>Two of the four people in the data have age properties so two of the query solutions have that information.  However, because the triple pattern for the age is optional, there is a pattern solution for the people who don’t have age information.If the optional clause had not been there, no age information would have been retrieved. If the triple pattern had been included but not optional then we would have the query with only two solutions because the `info:age` property must now be present in a solution:<jupyter_code>sparql_query = """
PREFIX info:   <http://somewhere/peopleInfo#>
PREFIX vcard:  <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name ?age
WHERE
{
    ?person vcard:FN  ?name .
    ?person info:age ?age .
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>OPTIONALs with FILTERsOPTIONAL is a binary operator that combines two graph patterns. The optional pattern is any group pattern and may involve any SPARQL pattern types.  If the group matches, the solution is extended, if not, the original solution is given. So, if we filter for ages greater than 24 in the optional part, we will still get 4 solutions (from the vcard:FN pattern) but only get ages if they pass the test.<jupyter_code>sparql_query = """
PREFIX info:        <http://somewhere/peopleInfo#>
PREFIX vcard:      <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name ?age
WHERE
{
    ?person vcard:FN  ?name .
    OPTIONAL { ?person info:age ?age . FILTER ( ?age > 24 ) }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>No age included for “<NAME>” because it is less than 24.If the filter condition is moved out of the optional part, then it can influence the number of solutions but it may be necessary to make the filter more complicated to allow for variable age being unbound.If a solution has an age variable, then it must be greater than 24. It can also be unbound.  There are now three solutions:<jupyter_code>sparql_query = """
PREFIX info:        <http://somewhere/peopleInfo#>
PREFIX vcard:      <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name ?age
WHERE
{
    ?person vcard:FN  ?name .
    OPTIONAL { ?person info:age ?age . }
    FILTER ( !bound(?age) || ?age > 24 )
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>Evaluating an expression which has an unbound variables where a bound one was expected causes an evaluation exception and the whole expression fails.  OPTIONALs and Order Dependent QueriesOne thing to be careful of is using the same variable in two or more optional clauses (and not in some basic pattern as well):<jupyter_code>sparql_query = """
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name
WHERE
{
  ?x a foaf:Person .
  OPTIONAL { ?x foaf:name ?name }
  OPTIONAL { ?x vCard:FN  ?name }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>If the first optional binds `?name` and `?x` to some values, the second OPTIONAL is an attempt to match the ground triples (`?x` and `?name` have values). If the first optional did not match the optional part, then the second one is an attempt to match its triple with two variables.  AlternativesAnother way of dealing with the semi-structured data is to query for one of a number of possibilities. This section covers UNION patterns, where one of a number of possibilities is tried.  UNION - two ways to the same dataBoth the vCard vocabulary and the FOAF vocabulary have properties for people’s names.  In vCard, it is vCard:FN, the “formatted name”, and in FOAF, it is foaf:name. In this section, we will look at a small set of data where the names of people can be given by either the FOAF or the vCard vocabulary.Suppose we have an RDF graph that contains name information using both the vCard and FOAF vocabularies.<jupyter_code>rdf_data = """
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix vcard: <http://www.w3.org/2001/vcard-rdf/3.0#> .

_:a foaf:name   "<NAME>" .

_:b foaf:name   "<NAME>" .

_:c vcard:FN    "<NAME>" .

_:d vcard:FN    "<NAME>" .
"""

g = rdflib.Graph()
g.parse(format="turtle", data=rdf_data)<jupyter_output><empty_output><jupyter_text>A query to access the name information, when it can be in either form, could return the results:<jupyter_code>sparql_query = """
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name
WHERE
{
   { [] foaf:name ?name } UNION { [] vCard:FN ?name }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>It didn’t matter which form of expression was used for the name, the `?name` variable is set. This can be achieved using a FILTER as this query shows:<jupyter_code>sparql_query = """
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name
WHERE
{
  [] ?p ?name
  FILTER ( ?p = foaf:name || ?p = vCard:FN )
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>testing whether the property is one URI or another. The solutions may not come out in the same order.  The first form is more likely to be faster, depending on the data and the storage used, because the second form may have to get all the triples from the graph to match the triple pattern with unbound variables (or blank nodes) in each slot, then test each ?p to see if it matches one of the values. It will depend on the sophistication of the query optimizer as to whether it spots that it can perform the query more efficiently and is able to pass the constraint down as well as to the storage layer.  UNION - remembering where the data was found.The example above used the same variable in each branch. If different variables are used, the application can discover which sub-pattern caused the match:<jupyter_code>sparql_query = """
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name1 ?name2
WHERE
{
   { [] foaf:name ?name1 } UNION { [] vCard:FN ?name2 }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>This second query has retained information of where the name of the person came from by assigning the name to different variables.  OPTIONAL and UNIONn practice, OPTIONAL is more common than UNION but they both have their uses. OPTIONAL are useful for augmenting the solutions found, UNION is useful for concatenating the solutions from two possibilities. They don’t necessary return the information in the same way:<jupyter_code>sparql_query = """
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX vCard: <http://www.w3.org/2001/vcard-rdf/3.0#>

SELECT ?name1 ?name2
WHERE
{
  ?x a foaf:Person
  OPTIONAL { ?x  foaf:name  ?name1 }
  OPTIONAL { ?x  vCard:FN   ?name2 }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>but beware of using ?name in each OPTIONAL because that is an order-dependent query.  Named Graphs_Work-in-progress! rfdlib calls these Conjunctive Graphs, but I haven't quite cracked the pathing or the data reads yet._This section covers RDF Datasets - an RDF Dataset is the unit that is queried by a SPARQL query. It consists of a default graph, and a number of named graphs.  Querying datasetsThe graph matching operation (basic patterns, OPTIONALs, and UNIONs) work on one RDF graph.  This starts out being the default graph of the dataset but it can be changed by the GRAPH keyword.```GRAPH uri { ... pattern ... }GRAPH var { ... pattern ... }```If a URI is given, the pattern will be matched against the graph in the dataset with that name - if there isn’t one, the GRAPH clause fails to match at all.If a variable is given, all the named graphs (not the default graph) are tried.  The variable may be used elsewhere so that if, during execution, its value is already known for a solution, only the specific named graph is tried.  Example DataAn RDF dataset can take a variety of forms.  Two common setups are to have the default graph being the union (the RDF merge) of all the named graphs or to have the default graph be an inventory of the named graphs (where they came from, when they were read etc).  There are no limitations - one graph can be included twice under different names, or some graphs may share triples with others.In the examples below we will use the following dataset that might occur for an RDF aggregator of book details:  Named Graph 1<jupyter_code>named1_data = """
@prefix dc: <http://purl.org/dc/elements/1.1/> .

[] dc:title "<NAME> and the Philospher's Stone" .
[] dc:title "H<NAME>ter and the Chamber of Secrets" .
"""

g1 = rdflib.Graph()
g1.parse(format="turtle", data=named1_data)<jupyter_output><empty_output><jupyter_text>Named Graph 2<jupyter_code>named2_data = """
@prefix dc: <http://purl.org/dc/elements/1.1/> .

[] dc:title "<NAME> and the Sorcerer's Stone" .
[] dc:title "H<NAME>ter and the Chamber of Secrets" .
"""

g2 = rdflib.Graph()
g2.parse(format="turtle", data=named2_data)<jupyter_output><empty_output><jupyter_text>Default graph<jupyter_code>default_data = """
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<resources/ds-ng-1.ttl> dc:date "2005-07-14T03:18:56+0100"^^xsd:dateTime .
<resources/ds-ng-2.ttl> dc:date "2005-09-22T05:53:05+0100"^^xsd:dateTime .
"""

g = rdflib.ConjunctiveGraph()
g.parse(format="turtle", data=default_data)<jupyter_output><empty_output><jupyter_text>Accessing the DatasetThe first example just accesses the default graph.This is the default graph only - nothing from the named graphs because they aren’t queried unless explicitly indicated via GRAPH.<jupyter_code>sparql_query = """
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX : <.>

SELECT *
{ ?s ?p ?o }
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>We can query for all triples by querying the default graph and the named graphs giving:<jupyter_code>sparql_query = """
PREFIX  xsd:    <http://www.w3.org/2001/XMLSchema#>
PREFIX  dc:     <http://purl.org/dc/elements/1.1/>
PREFIX  :       <.>

SELECT *
{
    { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>Querying a specific graphIf the application knows the name graph, it can directly ask a query such as finding all the titles in a given graph<jupyter_code>sparql_query = """
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX : <.>

SELECT ?title
{
  GRAPH :ds-ng-2.ttl
    { ?b dc:title ?title }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>Querying to find data from graphs that match a patternThe name of the graphs to be queried can be determined with the query itself. The same process for variables applies whether they are part of a graph pattern or the GRAPH form. The query below sets a condition on the variable used to select named graphs, based on information in the default graph.The results of executing this query on the example dataset are the titles in one of the graphs, the one with the date later than 1 August 2005.<jupyter_code>sparql_query = """
PREFIX  xsd:    <http://www.w3.org/2001/XMLSchema#>
PREFIX  dc:     <http://purl.org/dc/elements/1.1/>
PREFIX  :       <.>

SELECT ?date ?title
{
  ?g dc:date ?date . FILTER (?date > "2005-08-01T00:00:00Z"^^xsd:dateTime )
  GRAPH ?g
      { ?b dc:title ?title }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output><jupyter_text>Describing RDF Datasets - FROM and FROM NAMEDA query execution can be given the dataset when the execution object is built or it can be described in the query itself. When the details are on the command line, a temporary dataset is created but an application can create datasets and then use them in many queries.When described in the query, FROM  is used to identify the contents to be in the default graph. There can be more than one FROM clause and the default graph is result of reading each file into the default graph. It is the RDF merge of the individual graphs.Don’t be confused by the fact the default graph is described by one or more URLs in FROM clauses. This is where the data is read from, not the name of the graph. As several FROM clauses can be given, the data can be read in from several places but none of them become the graph name.FROM NAMED  is used to identify a named graph. The graph is given the name url and the data is read from that location. Multiple FROM NAMED clauses cause multiple graphs to be added to the dataset.Note that graphs are loaded with the Jena FileManager which includes the ability to provide alternative locations for files. For example, the query may have FROM NAMED , and the data actually be read from file:local.rdf. The name of the graph will be http://example/data\ as in the query.For example, the query to find all the triples in both default graph and named graphs could be written as (q-ds-5.rq):<jupyter_code>sparql_query = """
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dc:  <http://purl.org/dc/elements/1.1/>
PREFIX :    <.>

SELECT *
FROM       <resources/ds-dft.ttl>
FROM NAMED <resources/ds-ng-1.ttl>
FROM NAMED <resources/ds-ng-2.ttl>
{
   { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } }
}
"""

qres = g.query(sparql_query)
df = to_dataframe(qres)
df<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09572050383267208, 'fraction_numerical': 0.012788413389314742, 'mean_word_length': 4.243789133508382, 'pattern_counts': {'":': 0, '<': 235, '<?xml version=': 0, '>': 239, 'https://': 1, 'lorem ipsum': 0, 'www.': 28, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '28', 'dir': 'jupyter-structured-clean-dedup', 'id': '399389', 'license': 'Apache-2.0', 'n_tokens_mistral': 8558, 'n_tokens_neox': 8193, 'n_words': 3244, 'path': 'rdflib and Pandas SPARQL Tutorial.ipynb', 'repo_name': 'staylorx/sparql_tutorial', 'size': '26051'}