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:

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)

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