3 Star 4 Star
Contents
3 Star 4 Star#
Dieses Notebook: Wir explorieren technisch, wie man eine “Dreistern”-CSV-Datei in eine “Vierstern”-RDF-Datei übersetzen kann: Zuerst exemplarisch eine Zeile manuell, dann die komplette Datei automatisch.
Begriff Linked Open Data 5 Star:
historisch relevantes Dokument von Tim Berners-Lee: https://www.w3.org/DesignIssues/LinkedData.html
5 star im Linked Data Glossary: https://dvcs.w3.org/hg/gld/raw-file/default/glossary/index.html#x5-star-linked-open-data
Dieses Notebook: Wir schauen uns exemplarisch eine (3 star-) CSV-Tabelle an, und überführen sie in eine 4-star Repräsentation in RFD(S)
Suche auf https://www.govdata.de/ nach Abfallarten Münster (Juni 2023: 12 Treffer), führt u.a. auf Nach Abfallarten differenzierte erzeugte Abfallmengen Münster. Abfallmengen in Tonnen bezogen auf die Jahre ab 2013, erzeugt in der Stadt Münster.
Datei: https://opendata.stadt-muenster.de/sites/default/files/Erzeugte-Abfallmengen-MS_2.csv (Moodle: Erzeugte Abfallmengen Sitzung 2023-06-07.csv)
aus csv einlesen#
https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html
import pandas as pd
import numpy as np
Abfall_df = pd.read_csv("../govdata/Erzeugte-Abfallmengen-MS_2.csv",
thousands = ".",
decimal = ",",
nrows = 3, # für unsere Demo hier nur die ersten 3 Zeilen
sep = ";")
Abfall_df.head(3)
Jahr | AVV-Abfallart-Nr | AVV-Abfallart Bezeichnung | Menge in Tonnen | Unnamed: 4 | |
---|---|---|---|---|---|
0 | 2019 | 150106 | gemischte Verpackungen | 9847.7 | NaN |
1 | 2019 | 150107 | Verpackungen aus Glas | 7435.1 | NaN |
2 | 2019 | 160103 | Altreifen | 166.1 | NaN |
Abfall_df.reset_index(inplace=True)
Abfall_df.head(3)
index | Jahr | AVV-Abfallart-Nr | AVV-Abfallart Bezeichnung | Menge in Tonnen | Unnamed: 4 | |
---|---|---|---|---|---|---|
0 | 0 | 2019 | 150106 | gemischte Verpackungen | 9847.7 | NaN |
1 | 1 | 2019 | 150107 | Verpackungen aus Glas | 7435.1 | NaN |
2 | 2 | 2019 | 160103 | Altreifen | 166.1 | NaN |
# https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.assign.html
manuell rekonstruieren#
Wir wollen Erfahrung sammeln mit ttl und rdflib. Dazu codieren wir die Tabellenzeile 4 (im Pandas Dataframe der Dtaensatz mit Index 2) manuell als ttl, um es in rdflib einzulesen.
import rdflib
import owlrl
Graph a1
manuell in ttl bauen#
abfall_ttl = """
@prefix ex: <http://jbusse/ex#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
# Sammlung
_:4 ex:AVV-Abfallart-Nr ex:160103 ;
ex:Menge_in_Tonnen "166.1" .
# "AVV-Abfallart"
ex:160103
ex:hat_Bezeichnung "Altreifen" .
"""
# später ggf. hinzufügen:
# ex:AVV-Abfallart-Nr rdfs:range ex:AVV-Abfallart .
a1 = rdflib.Graph().parse(data= abfall_ttl)
a1
ist ein rdf-Graph. Anzahl der in ihm enthaltenen Tripel:
len(a1)
3
a1
läst sich wie eine Liste durchlaufen.
for x in a1:
print(len(x), x)
3 (rdflib.term.BNode('n7cb9a1f5dda84eeb9b4c61cc542e2235b1'), rdflib.term.URIRef('http://jbusse/ex#Menge_in_Tonnen'), rdflib.term.Literal('166.1'))
3 (rdflib.term.URIRef('http://jbusse/ex#160103'), rdflib.term.URIRef('http://jbusse/ex#hat_Bezeichnung'), rdflib.term.Literal('Altreifen'))
3 (rdflib.term.BNode('n7cb9a1f5dda84eeb9b4c61cc542e2235b1'), rdflib.term.URIRef('http://jbusse/ex#AVV-Abfallart-Nr'), rdflib.term.URIRef('http://jbusse/ex#160103'))
oder auch so:
for s, p, o in a1:
print(s, p, o)
n7cb9a1f5dda84eeb9b4c61cc542e2235b1 http://jbusse/ex#Menge_in_Tonnen 166.1
http://jbusse/ex#160103 http://jbusse/ex#hat_Bezeichnung Altreifen
n7cb9a1f5dda84eeb9b4c61cc542e2235b1 http://jbusse/ex#AVV-Abfallart-Nr http://jbusse/ex#160103
Graph mit SPARQL anfragen#
Ein librdf-Objekt hat eine SPARQL-Schnittstelle. aq
sei unsere SPARL-Query:
q1 = """
PREFIX ex: <http://jbusse/ex#>
SELECT ?x ?y
WHERE { ?x ex:AVV-Abfallart-Nr ?y .}
"""
q1_result = a1.query(q1)
q1_result
<rdflib.plugins.sparql.processor.SPARQLResult at 0x7f181b02cd90>
Weil in unserer SPARQL-Query 2 Variablen erfragt werden, erhalten wir eine Liste aus 2-Tupeln:
for r1, r2 in q1_result:
print(r1, r2)
n7cb9a1f5dda84eeb9b4c61cc542e2235b1 http://jbusse/ex#160103
Graph a2
: CSV-Datei automatisch in RDF umwandeln#
Oben haben wir die CSV-Datei mit Pandas eingelesen - das geht einfach, komfortabel und schnell. Würden wir Pandas gut kennen, würden wir mit vektorisierten Funktionen weiterabeiten.
Im folgenden gehen wir aber “zu Fuß” vor, arbeiten auf den Datenstrukturen, die wir aus der Python 1-Veranstaltung gut kennen: Verschachtelte Listen und Dicts.
Lesen:
Abfall_dict_dict = Abfall_df.to_dict("index")
Abfall_dict_dict
{0: {'index': 0,
'Jahr': 2019,
'AVV-Abfallart-Nr': 150106,
'AVV-Abfallart Bezeichnung': 'gemischte Verpackungen',
'Menge in Tonnen': 9847.7,
'Unnamed: 4': nan},
1: {'index': 1,
'Jahr': 2019,
'AVV-Abfallart-Nr': 150107,
'AVV-Abfallart Bezeichnung': 'Verpackungen aus Glas',
'Menge in Tonnen': 7435.1,
'Unnamed: 4': nan},
2: {'index': 2,
'Jahr': 2019,
'AVV-Abfallart-Nr': 160103,
'AVV-Abfallart Bezeichnung': 'Altreifen',
'Menge in Tonnen': 166.1,
'Unnamed: 4': nan}}
# konventionelle Technik: Repräsentiere Ausgabe als eine Liste von Strings,
# die am Schluss mit join zusammengefügt werden
result_string_list = [ "@prefix ex: <http://jbusse/ex#> ."]
# Zeilen
for id, row in Abfall_dict_dict.items():
result_string_list.append(f"\n# {id}")
# Spalten
for Spalte, Wert in row.items():
# Resoures
if Spalte in [ "Jahr", "AVV-Abfallart-Nr" ]:
result_string_list.append(f"ex:Sammlung_{id} ex:{Spalte} ex:{Wert} .")
# Zahl als Literal
elif Spalte == "Menge in Tonnen":
result_string_list.append(f"ex:Sammlung_{id} ex:Menge_in_Tonnen {Wert} .")
# String als Literal
elif Spalte == 'AVV-Abfallart Bezeichnung':
result_string_list.append(f"ex:{row['AVV-Abfallart-Nr']} ex:hat_Bezeichnung '{Wert}' .")
result_string_list
['@prefix ex: <http://jbusse/ex#> .',
'\n# 0',
'ex:Sammlung_0 ex:Jahr ex:2019 .',
'ex:Sammlung_0 ex:AVV-Abfallart-Nr ex:150106 .',
"ex:150106 ex:hat_Bezeichnung 'gemischte Verpackungen' .",
'ex:Sammlung_0 ex:Menge_in_Tonnen 9847.7 .',
'\n# 1',
'ex:Sammlung_1 ex:Jahr ex:2019 .',
'ex:Sammlung_1 ex:AVV-Abfallart-Nr ex:150107 .',
"ex:150107 ex:hat_Bezeichnung 'Verpackungen aus Glas' .",
'ex:Sammlung_1 ex:Menge_in_Tonnen 7435.1 .',
'\n# 2',
'ex:Sammlung_2 ex:Jahr ex:2019 .',
'ex:Sammlung_2 ex:AVV-Abfallart-Nr ex:160103 .',
"ex:160103 ex:hat_Bezeichnung 'Altreifen' .",
'ex:Sammlung_2 ex:Menge_in_Tonnen 166.1 .']
result_string = "\n".join(result_string_list)
print(result_string)
@prefix ex: <http://jbusse/ex#> .
# 0
ex:Sammlung_0 ex:Jahr ex:2019 .
ex:Sammlung_0 ex:AVV-Abfallart-Nr ex:150106 .
ex:150106 ex:hat_Bezeichnung 'gemischte Verpackungen' .
ex:Sammlung_0 ex:Menge_in_Tonnen 9847.7 .
# 1
ex:Sammlung_1 ex:Jahr ex:2019 .
ex:Sammlung_1 ex:AVV-Abfallart-Nr ex:150107 .
ex:150107 ex:hat_Bezeichnung 'Verpackungen aus Glas' .
ex:Sammlung_1 ex:Menge_in_Tonnen 7435.1 .
# 2
ex:Sammlung_2 ex:Jahr ex:2019 .
ex:Sammlung_2 ex:AVV-Abfallart-Nr ex:160103 .
ex:160103 ex:hat_Bezeichnung 'Altreifen' .
ex:Sammlung_2 ex:Menge_in_Tonnen 166.1 .
a2 = rdflib.Graph().parse(data= result_string)
q2 = """
PREFIX ex: <http://jbusse/ex#>
SELECT ?s ?p ?o
WHERE { ?s ?p ?o .}
"""
for s, p, o in a2.query(q2):
print(s.n3(), p.n3(), o.n3())
<http://jbusse/ex#160103> <http://jbusse/ex#hat_Bezeichnung> "Altreifen"
<http://jbusse/ex#150107> <http://jbusse/ex#hat_Bezeichnung> "Verpackungen aus Glas"
<http://jbusse/ex#Sammlung_0> <http://jbusse/ex#Jahr> <http://jbusse/ex#2019>
<http://jbusse/ex#Sammlung_2> <http://jbusse/ex#Jahr> <http://jbusse/ex#2019>
<http://jbusse/ex#Sammlung_0> <http://jbusse/ex#Menge_in_Tonnen> "9847.7"^^<http://www.w3.org/2001/XMLSchema#decimal>
<http://jbusse/ex#Sammlung_1> <http://jbusse/ex#Jahr> <http://jbusse/ex#2019>
<http://jbusse/ex#Sammlung_1> <http://jbusse/ex#Menge_in_Tonnen> "7435.1"^^<http://www.w3.org/2001/XMLSchema#decimal>
<http://jbusse/ex#Sammlung_0> <http://jbusse/ex#AVV-Abfallart-Nr> <http://jbusse/ex#150106>
<http://jbusse/ex#150106> <http://jbusse/ex#hat_Bezeichnung> "gemischte Verpackungen"
<http://jbusse/ex#Sammlung_1> <http://jbusse/ex#AVV-Abfallart-Nr> <http://jbusse/ex#150107>
<http://jbusse/ex#Sammlung_2> <http://jbusse/ex#AVV-Abfallart-Nr> <http://jbusse/ex#160103>
<http://jbusse/ex#Sammlung_2> <http://jbusse/ex#Menge_in_Tonnen> "166.1"^^<http://www.w3.org/2001/XMLSchema#decimal>
Alle Arten von zweifach verschachtelten Listen von Listen, Dicts, etc. lassen sich leicht in ein Pandas DataFrame umwandeln:
a2_df = pd.DataFrame(data=a2.query(q2), columns=["S", "P", "O"])
a2_df
S | P | O | |
---|---|---|---|
0 | http://jbusse/ex#160103 | http://jbusse/ex#hat_Bezeichnung | Altreifen |
1 | http://jbusse/ex#150107 | http://jbusse/ex#hat_Bezeichnung | Verpackungen aus Glas |
2 | http://jbusse/ex#Sammlung_0 | http://jbusse/ex#Jahr | http://jbusse/ex#2019 |
3 | http://jbusse/ex#Sammlung_2 | http://jbusse/ex#Jahr | http://jbusse/ex#2019 |
4 | http://jbusse/ex#Sammlung_0 | http://jbusse/ex#Menge_in_Tonnen | 9847.7 |
5 | http://jbusse/ex#Sammlung_1 | http://jbusse/ex#Jahr | http://jbusse/ex#2019 |
6 | http://jbusse/ex#Sammlung_1 | http://jbusse/ex#Menge_in_Tonnen | 7435.1 |
7 | http://jbusse/ex#Sammlung_0 | http://jbusse/ex#AVV-Abfallart-Nr | http://jbusse/ex#150106 |
8 | http://jbusse/ex#150106 | http://jbusse/ex#hat_Bezeichnung | gemischte Verpackungen |
9 | http://jbusse/ex#Sammlung_1 | http://jbusse/ex#AVV-Abfallart-Nr | http://jbusse/ex#150107 |
10 | http://jbusse/ex#Sammlung_2 | http://jbusse/ex#AVV-Abfallart-Nr | http://jbusse/ex#160103 |
11 | http://jbusse/ex#Sammlung_2 | http://jbusse/ex#Menge_in_Tonnen | 166.1 |
Über dem automatisch übersetzten Datenatz jetzt nochmal die Query q1
:
for s, o in a2.query(q1):
print(s, o)
http://jbusse/ex#Sammlung_0 http://jbusse/ex#150106
http://jbusse/ex#Sammlung_1 http://jbusse/ex#150107
http://jbusse/ex#Sammlung_2 http://jbusse/ex#160103