Introduction

The challenge is for someone who doesn' t know a heck of a lot about either SPARQL or Wikidata to use those tools as implemented in the Wolfram Language to generate a timeline of chief justices of the United States Supreme Court. The idea is to create a notebook that will be of mild interest to legal scholars but that may prove of greater interest to those seeking to probe Wikidata in a programmatic way for a variety of purposes.

Using a sample query and generating symbolic SPARQL

I really don’t know much about SPARQL or WikidataData. But I do know how to copy examples and modify them. So, Wikidata provides a LOT of examples on how to conduct queries. I’m going to start with one of those examples that seemed somewhat similar to what I wanted in that it has properties (a position like head of the French government) and subproperties (like when did that service start and end). Here’s the web address of the relevant example: (https://www.wikidata.org/wiki/Wikidata:SPARQL_query_service/queries/examples#French_heads_of_government_by_length_of_service) I copy and paste. (I suppose I could have used WebExecute to do this programmatically, but I’m not expert in that either).
In[]:=
frenchHeadOfState="SELECT DISTINCT ?item ?itemLabel ?positionLabel ?picture ?start ?end ?days WHERE{ ?item wdt:P31 wd:Q5 ; p:P39 ?position_statement . ?position_statement ps:P39 ?position ; pq:P580 ?start FILTER (?start >= \"1815-01-01T00:00:00Z\"^^xsd:dateTime) . ?position wdt:P279* wd:Q15135541 . OPTIONAL { ?position_statement pq:P582 ?x } OPTIONAL { ?item wdt:P18 ?picture } bind(if(bound(?x), ?x, NOW()) as ?end ) bind(floor(?end - ?start) as ?days) SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\" . }}ORDER BY DESC(?days) ?itemLabel";
I now use ImportString to convert the query to symbolic SPARQL.
In[]:=
frenchSPARQL=ImportString[frenchHeadOfState,"SPARQLQuery"]
Out[]=
SPARQLQuery[SPARQLSelect[{RDFTriple[SPARQLVariable[item],URL[{wdt,P31}],URL[{wd,Q5}]],RDFTriple[SPARQLVariable[item],URL[{p,P39}],SPARQLVariable[position_statement]],RDFTriple[SPARQLVariable[position_statement],URL[{ps,P39}],SPARQLVariable[position]],RDFTriple[SPARQLVariable[position_statement],URL[{pq,P580}],SPARQLVariable[start]],SPARQLPropertyPath[SPARQLVariable[position],{URL[{wdt,P279}]...},URL[{wd,Q15135541}]],SPARQLOptional[{RDFTriple[SPARQLVariable[position_statement],URL[{pq,P582}],SPARQLVariable[x]]}],SPARQLOptional[{RDFTriple[SPARQLVariable[item],URL[{wdt,P18}],SPARQLVariable[picture]]}],endSPARQLEvaluation[if][SPARQLEvaluation[bound][SPARQLVariable[x]],SPARQLVariable[x],SPARQLEvaluation[NOW][]],daysSPARQLEvaluation[floor][SPARQLVariable[end]-SPARQLVariable[start]],SPARQLService[URL[{wikibase,label}],{RDFTriple[URL[{bd,serviceParam}],URL[{wikibase,language}],[AUTO_LANGUAGE],en]}]}/;SPARQLVariable[start]≥RDFLiteral[1815-01-01T00:00:00Z,URL[{xsd,dateTime}]]]/*SPARQLOrderBy[{SPARQLVariable[days]Descending,SPARQLVariable[itemLabel]}]/*SPARQLProject[{item,itemLabel,positionLabel,picture,start,end,days}]/*SPARQLDistinct[]]
I can use a ResourceFunction to make this look a bit more attractive.
In[]:=
ResourceFunction["PrettyForm"][frenchSPARQL]
Out[]=
SPARQLQuery
​​ RightComposition​​
SPARQLSelect
​​ Condition​​ ​​
RDFTriple

SPARQLVariable
@item,​​ URL@{wdt,P31},​​ URL@{wd,Q5}​​ ,​​
RDFTriple

SPARQLVariable
@item,​​ URL@{p,P39},​​
SPARQLVariable
@position_statement​​ ,​​
RDFTriple

SPARQLVariable
@position_statement,​​ URL@{ps,P39},​​
SPARQLVariable
@position​​ ,​​
RDFTriple

SPARQLVariable
@position_statement,​​ URL@{pq,P580},​​
SPARQLVariable
@start​​ ,​​
SPARQLPropertyPath

SPARQLVariable
@position,​​ {RepeatedNull[URL[{wdt,P279}]]},​​ URL@{wd,Q15135541}​​ ,​​
SPARQLOptional
@​​
RDFTriple
​​
SPARQLVariable
@position_statement,​​ URL@{pq,P582},​​
SPARQLVariable
@x​​ ​​ ,​​
SPARQLOptional
@​​
RDFTriple
​​
SPARQLVariable
@item,​​ URL@{wdt,P18},​​
SPARQLVariable
@picture​​ ​​ ,​​ end
SPARQLEvaluation
[if]​​
SPARQLEvaluation
[bound][
SPARQLVariable
@x],​​
SPARQLVariable
@x,​​
SPARQLEvaluation
[NOW][]​​ ,​​ days
SPARQLEvaluation
[floor][
SPARQLVariable
[end]+-
SPARQLVariable
[start]],​​
SPARQLService
URL@{wikibase,label},​​ ​​
RDFTriple
​​ URL@{bd,serviceParam},​​ URL@{wikibase,language},​​ [AUTO_LANGUAGE],en​​ ​​ ​​ ​​ ,​​ GreaterEqual
SPARQLVariable
@start,​​
RDFLiteral
[1815-01-01T00:00:00Z,URL@{xsd,dateTime}]​​ ​​ ​​ ,​​ RightComposition​​
SPARQLOrderBy
​​ ​​
SPARQLVariable
[days]Descending,​​
SPARQLVariable
@itemLabel​​ ​​ ,​​ RightComposition​​
SPARQLProject
@{item,itemLabel,positionLabel,picture,start,end,days},​​
SPARQLDistinct
[]​​ ​​ ​​ ​​

Modifying Symbolic SPARQL

What I now realize is that the SPARQLPropertyPath component of the expression is what is choosing French head of state. If I modify that just a bit and modify the filter just a bit, I should be able to get what I need. I just need to change the occupation searched for ...
In[]:=
makeItTheChiefJustice=SPARQLPropertyPath[a_,b_,URL[{"wd","Q15135541"}]]SPARQLPropertyPath[a,b,URL[{"wd","Q11147"}]];
... and eliminate the filter.
In[]:=
changeFilterDate=HoldPattern[Condition][a_,b_]a;
In[]:=
chiefSPARQL=frenchSPARQL//.{makeItTheChiefJustice,changeFilterDate}
Out[]=
SPARQLQuery[SPARQLSelect[{RDFTriple[SPARQLVariable[item],URL[{wdt,P31}],URL[{wd,Q5}]],RDFTriple[SPARQLVariable[item],URL[{p,P39}],SPARQLVariable[position_statement]],RDFTriple[SPARQLVariable[position_statement],URL[{ps,P39}],SPARQLVariable[position]],RDFTriple[SPARQLVariable[position_statement],URL[{pq,P580}],SPARQLVariable[start]],SPARQLPropertyPath[SPARQLVariable[position],{URL[{wdt,P279}]...},URL[{wd,Q11147}]],SPARQLOptional[{RDFTriple[SPARQLVariable[position_statement],URL[{pq,P582}],SPARQLVariable[x]]}],SPARQLOptional[{RDFTriple[SPARQLVariable[item],URL[{wdt,P18}],SPARQLVariable[picture]]}],endSPARQLEvaluation[if][SPARQLEvaluation[bound][SPARQLVariable[x]],SPARQLVariable[x],SPARQLEvaluation[NOW][]],daysSPARQLEvaluation[floor][SPARQLVariable[end]-SPARQLVariable[start]],SPARQLService[URL[{wikibase,label}],{RDFTriple[URL[{bd,serviceParam}],URL[{wikibase,language}],[AUTO_LANGUAGE],en]}]}]/*SPARQLOrderBy[{SPARQLVariable[days]Descending,SPARQLVariable[itemLabel]}]/*SPARQLProject[{item,itemLabel,positionLabel,picture,start,end,days}]/*SPARQLDistinct[]]

Query Execution

Let' s execute this query.

Using the Wolfram Language To Product The Plot

Now we’re in the Wolfram world, which I know well. It will help visualization if I convert this into a Wolfram Language Dataset.
I can make a timeline with relatively simple code. The only thing that takes any time is downloading the photos. Here’s a sample of how I can get a picture of a justice out of the Dataset. The idea is actually execute the link contained in the dataset.

Python Code

We can also run something like this from within Python from within the Wolfram Frontend. I don’t know much Python. But that doesn’t matter. I can run the code within the Wikidata Query Service and ask it to then generate the code to do so in Python. I can then tweak the code just a little bit (getting rid of print statements, for example). Moreover, to keep the output short, I’m going to restrict ourselves to justices that started their chieftancy after 1950. A warning: one has to pip install the Python SPARQLWrapper module first.
〉
In[]:=
from SPARQLWrapper import SPARQLWrapper, JSON
​
endpoint_url = "https://query.wikidata.org/sparql"
​
query = """#Chief Justices by length of service
SELECT DISTINCT ?item ?itemLabel ?positionLabel ?picture ?start ?end ?days WHERE
{
?item wdt:P31 wd:Q5 ;
p:P39 ?position_statement .
?position_statement ps:P39 ?position ;
pq:P580 ?start FILTER (?start >= "1950-01-01T00:00:00Z"^^xsd:dateTime) .
?position wdt:P279* wd:Q11147 .
OPTIONAL { ?position_statement pq:P582 ?x }
OPTIONAL { ?item wdt:P18 ?picture }
bind(if(bound(?x), ?x, NOW()) as ?end )
bind(floor(?end - ?start) as ?days)
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" . }
}
ORDER BY DESC(?days) ?itemLabel"""
​
​
def get_results(endpoint_url, query):
sparql = SPARQLWrapper(endpoint_url)
sparql.setQuery(query)
sparql.setReturnFormat(JSON)
return sparql.query().convert()
​
​
get_results(endpoint_url, query)["results"]["bindings"]
I can get the output into a Dataset.
And once again I can turn this into a timeline plot.

Conclusion

Using examples taken from WikidataData, one can grab a lot of data in a relatively simple way. One can then use the Wolfram Language to conduct whatever data analysis one wishes.