In diesem kleinen Projekt werden die Typen der Pokemon mit ihren Stärken, Schwächen und Immunitäten verbunden. Der Sinn dahiner ist es beispielsweise ein Pokemon zu nennen und dessen Informationen zu erhalten. Aber auch die Eingabe des eigenen Pokemon-Teams soll erfolgen, um zu prüfen, gegen welche Typen das Team im Kampf nicht gewappnet ist. Daraufhin hat man die Möglichkeit sein Team zu prüfen oder gar zu ändern.
Folgende Daten wurden dafür verwendet:
- RDF Daten: Pokemon & Attribute
https://triplydb.com/academy/pokemon
- JSON Datei (sonstiges Format): Pokemontypen
https://github.com/filipekiss/pokemon-type-chart/blob/master/types.json
Zuerst wird, wie im Unterricht gelernt, der SparqlWrapper importiert.
from SPARQLWrapper import SPARQLWrapper, JSON, CSV
Die ersten Versuche mit DBpedia waren eher erfolglos. Es gab zwar ein paar Pokemon, leider aber unter verschiedenen Bezeichnungen und es gab keine Typen der Pokemon, weshalb ich mich auf die Suche nach einer neuen RDF Quelle gemacht habe. Fündig wurde ich hier: https://triplydb.com/academy/pokemon
Diese Datenbank enthaltet gefühlt alle Pokemon, deren Typen und viele weitere Attribute, wie die male/female ratio, rareness, cry als link zum audio file, etc.
sparql = SPARQLWrapper("https://triplydb.com/academy/pokemon/sparql/pokemon")
Nun erfolgt die SparQL query. Hier werden die RDF Daten aus der Datenbank gezogen. Die Prefixe pokemon und type sind auch sehr praktisch, habe ich aber für die Abfrage nicht benötigt. Mit vocab lässt sich dagegen alle gewünschten Pokemon abfragen.
Die NationalNumber, der Pokemon Name auf Deutsch, der Typ, aber auch die Seltenheit des Pokemons werden hier abgefragt.
sparql.setQuery("""
PREFIX pokemon: <https://triplydb.com/academy/pokemon/id/pokemon/>
PREFIX type: <https://triplydb.com/academy/pokemon/id/type/>
PREFIX vocab: <https://triplydb.com/academy/pokemon/vocab/>
select distinct ?nationalNumber (LCASE(?name) As ?name) (Strafter(Str(?type), "type/") As ?type) ?rareness
where {
?pokemon vocab:nationalNumber ?nationalNumber .
?pokemon vocab:name ?name .
filter(lang(?name) = 'de-de') .
?pokemon vocab:rareness ?rareness .
?pokemon vocab:type ?type .
}
LIMIT 300
""")
sparql.setReturnFormat(JSON)
poke = sparql.query().convert()
for res in poke["results"]["bindings"]:
print(res["nationalNumber"]["value"], res["name"]["value"], res["type"]["value"], res["rareness"]["value"])
Wie man sieht hat alles ganz gut funktioniert. Der einzige Mangel ist, dass ein Pokemon mit zwei verschiedenen Typen auf zwei Zeilen aufgeteilt wird. Dies ist jedoch nicht weiter schlimm, da dies als Und-Logig zu verstehen ist, also ein Pokemon hat sowohl die Stärken/Schwächen des ersten Typen, aber auch des Zweiten.
In Github (https://github.com/filipekiss/pokemon-type-chart/blob/master/types.json) konnte ich eine ideale Tabelle finden, die alle existierenden Typen, sowie deren Immunitäten, Stärken und Schwächen beinhaltet. Diese wird natürlich automatisch gedownloadet. Das bedeutet aber auch, dass wenn dieser Github-Beitrag gelöscht werden sollte, auch dieses Notebook nicht mehr funktioniert.
import requests
r = requests.get('https://raw.githubusercontent.com/filipekiss/pokemon-type-chart/master/types.json')
f = open('pokemontypes.json', 'wb')
f.write(r.content)
f.close()
Nun wird eine neue Datenbank namens "poke" erstellt und die Verbindung hergestellt.
%load_ext sql
%sql sqlite:///poke.db
import sqlite3
import pandas as pd
conn = sqlite3.connect('poke.db')
Der Table "poketypes", welcher die Typen der Pokemon enthält, wird gedroppt, falls vorhanden.
%%sql
drop table if exists poketypes;
Hier wird ein Pandas Dataframe mit den erstellt, bearbeitet und befüllt. Zuerst wird eine Spalte passend umbenannt, die Spalten-Typen werden zu "str", um eine weitere Bearbeitung zu ermöglichen und nicht benötigte Zeichen werden gelöscht.
df = pd.read_json('pokemontypes.json')
df=df.rename(columns = {'name':'types'})
df['types']= df['types'].astype('str')
df['immunes']= df['immunes'].astype('str').str.strip('[]')
df['weaknesses']= df['weaknesses'].astype('str').str.strip('[]')
df['strengths']= df['strengths'].astype('str').str.strip('[]')
df.replace(r"'", value="", inplace=True, regex=True)
#df.sort_values(['types']) - kann auch in SQL erfolgen
df.to_sql('poketypes', con=conn, index = False)
Nun wird geprüft, ob der Code von oben funktioniert hat. Siehe da - eine passende Tabelle wird ausgegeben, geordnet nach "types".
%%sql
select * from poketypes
order by types;
Hier wird nun die Pokemon Liste als CSV geschrieben.
f = open("pokemontypes.json", "w")
f.write("nationalNumber;name;type;rareness\n")
for res in poke["results"]["bindings"]:
f.write(res["nationalNumber"]["value"] +";"+ res["name"]["value"] +";"+ res["type"]["value"] +";"+ res["rareness"]["value"] +"\n")
f.close()
Der Table poke, welcher die Liste der Pokemon enthält, wird gedroppt, falls schon vorhanden.
%%sql
drop table if exists poke;
Die vorhin erstellte csv Datei wird nun wieder eingelesen. Um später die Tabels miteinander verbinden zu können, werden die Spalten-Typen name und type zum Format "str" konvertiert, aber auch die ersten Buchstaben als Großbuchstaben dargestellt. Die hätte auch in SQL erfolgen können, was sich aber als ein Mehraufwand herausgestellt hat.
tdf = pd.read_csv('pokemontypes.json', delimiter=";")
tdf['name']= tdf['name'].astype('str').str.capitalize()
tdf['type']= tdf['type'].astype('str').str.capitalize()
tdf.to_sql('poke', conn, index = False)
Nun wird überprüft, ob der Tabel nun befüllt ist. Das hat ebenfalls geklappt!
%%sql
select * from poke limit 10;
Nun wird die Key-Spalte type verwendet, um die beiden Tabellen zu joinen/mergen. Nun erhalten wir einen Output, der uns zeigt, welche Immunitäten, Stärken und Schwächen ein Pokemon hat.
Somit können wir auch herausfinden, welches Pokemon gegen welchen Typ eine bessere Chance im Kampf hat. (Dies kommt natürlich auch auf deren verwendeten Attaken an, aber der Pokemon Typ sagt darüber natürlich viel aus.)
%%sql
select nationalNumber, name, type, immunes, weaknesses, strengths
from poke join poketypes on poke.type = poketypes.types
order by name
limit 200;
Nun da die Arbeit erledigt ist folgen nun noch kleine SQL Abfragen, um die Verbindung der Tabellen einen Sinn zu geben.
Eine view namens pokejoin wird erstellt. Sie hat enthält das Ergebnis der gejointen Tabellen.
%%sql
drop view if exists pokejoin;
create view pokejoin as select nationalNumber, name, type, immunes, weaknesses, strengths
from poke join poketypes on poke.type = poketypes.types ;
Hier kann ein Pokemon, vielleicht sogar das eigene Lieblingspokemon, abgefragt werden, um zu sehen, gegen welche Typen man es eher kämfen lassen sollte bzw. einen Kampf versucht zu vermeiden. Falls kein Pokemon gefunden wurde, kann es daran liegen, dass das Limit in der SparQL Abfrage zu niedrig gesetzt wurde. Die Prozentzeichen beim Namen verhindern, dass ein vielleicht eingeschlichenes Leerzeichen die Abfrage ehlschlagen lässt.
%%sql
select * from pokejoin
where name like '%Libelldra%'
limit 2
Wenn man gegen ein Pokemon wie z.B. Libelldra, ein Boden Drachen Pokemon, kämpft, will man wissen welche Pokemon dagegen geeignet sind. Das erledigt diese Abfrage.
Wenn im Code steht: "and" sind die gegebenen Pokemon sehr wahrscheinlich sehr effektiv gegen die eingegebenen Typen. "or" sind diese Pokemon wahrscheinlich nur effektiv.
%%sql
select * from pokejoin
where strengths like '%Dragon%' and strengths like '%Ground%'
limit 20
Zuerst wird eine view namens myteam erstellt, die, wie erwartet, (m)ein Pokemon-Team beinhaltet. Dazu werden die Namen der Pokemon aus der Spalte name aus pokejoin abgefragt. Das Prozentzeichen müsste nicht verwendet werden, wenn mit einer trim() Funktion alle Leerzeichen herausgelöscht worden wären.
%%sql
drop view if exists myteam;
create view myteam as select * from pokejoin
where name like '%Arkani%'
or name like '%Absol%'
or name like '%Stollos%'
or name like '%Lampi%'
or name like '%Lohgock%'
or name like '%Pudox%' ;
select * from myteam;
Mit dieser etwas aufwändigeren Abfrage konnte ich eine view erstellen mit allen Stärken meines Teams, also alle Typen, gegen die mein Team gut kämpfen kann. Da SQLite nicht alle Funktionen, wie String_Split(), ermöglicht, wurde die Abfrage etwas länger.
%%sql
drop view if exists myteam_strengths;
create view myteam_strengths as
WITH split(word, str) AS (
SELECT '', (select group_concat(strengths) from myteam) ||','
UNION ALL SELECT
substr(str, 0, instr(str, ',')),
substr(str, instr(str, ',')+1)
FROM split WHERE str!=''
) SELECT distinct cast(trim(word) as text) as types FROM split WHERE word!=''
order by word;
select types from myteam_strengths;
Nun wird noch eine view namens result erstellt, die die allgemeine Typenliste und die Liste der Stärken meines Teams vergleicht. Typen, die nicht in beiden Listen vorkommen, werden im Output sichtbar.
%%sql
drop view if exists result;
create view result as
select types from poketypes except select types from myteam_strengths;
select * from result;
Siehe da - vier Pokemon Typen fehlen! Demnach wäre es gescheit eines oder mehrere Pokemon auszutauschen, um gegen alle Typen erfolgreich zu sein.
Diese Abfrage wurde verwendet, um den Datei-Typen der Tables herauszufinden, woraufhin eine cast funktion (siehe oben) angewendet wurde.
#%%sql
#PRAGMA table_info(myteam_strengths);
Diese Abfrage funktioniert, jedoch gibt sie einen String mit den Typen aus, womit ich nicht weiterarbeiten konnte. Würde die Funktion String_Split() in SQLite funktionieren, wäre der Code sehr einfach und nützlich gewesen.
#%%sql
#
#drop view if exists myteam_strengths;
#
#create view myteam_strengths as
#select group_concat(strengths) as strengths from myteam;
#
#select * from myteam_strengths;