====== Datenverarbeitung mit Python ======
===== Slides & Lernziele =====
**Slides:** {{ :gf_informatik:daten_sca:gfif_datenverarbeitung_python.pdf |}}
++++Lernziele|
Prüfungsrelevant ist alles, was in den Lektionen und Übungen behandelt wurde. Die Lernziele unten dienen als Gradmesser und sind nicht unbedingt komplett.
* Was ist **Datenverarbeitung**? Mache Beispiele.
* **Vor- und Nachteile** von Datenauswertung mit Spreadsheets vs. Textfiles und Python
* Strings in Zahlen (int und float) umwandeln und umgekehrt.
* Unterschiede von ints und floats kennen und wissen, wann man welche verwenden soll.
* Erkläre was **Textfiles** und **Binärfiles** sind, vergleiche diese und mache Beispiele.
* Textdateien in Python **einlesen** und **schreiben** können.
* Daten mit Python **verarbeiten** können, z.B.: alle Werte durchgehen, mit diesen Rechnen, in neuer Form speichern.
* **Textdateiformate CSV & JSON**:
* Beide Einlesen und Schreiben können.
* Bedeutung von CSV und JSON kennen.
* Miteinander vergleichen: Vor- und Nachteile?
* **Dictionaries:**
* Wissen, sie sind und wie sie aufgebaut sind.
* Mit ihnen arbeiten können (Values auslesen, neue Einträge, löschen , alle Keys oder Werte durchgehen ...)
* Dictionaries mit Listen vergleichen können.
* Verbindung Dictionaries <-> JSON erklären.
* JSON-File als Dictionary einlesen, Dictionary als JSON-File speichern
* Situationen erkennen, in denen man **Try-Except** anwenden kann ...
* ... und anwenden
++++
===== - Einlesen & Schreiben von Dateien =====
==== Theorie ====
=== Datei einlesen ===
Das Code-Beispiel unten zeigt, wie man eine **Textdatei einliest** und die Anzahl Zeilen zählt, die es hat:
import io
# Oeffne File
with io.open('dateiname.txt', 'r', encoding='utf-8') as infile:
line_count = 0 # Counter, um Anz. Zeilen zu zaehlen
for line in infile: # gehe der Reihe nach alle Zeilen des Files durch
line_count += 1
print(line_count) # gebe am Schluss Anz. Zeilen aus
Bemerkungen:
* Wenn Dateien gelesen oder geschrieben werden, müssen sie beim Betriebssystem reserviert werden. Mit `with` wird sichergestellt, dass die Datei nicht offen bleibt sondern nach dem Lesevorgang wieder geschlossen wird.
* Die Datei (hier 'dateiname.txt') muss sich im gleichen Ordner befinden wie das Python-File.
* `'r'` bedeutet *read*, dass das angegebene File nur gelesen und nicht verändert wird.
=== Datei schreiben ===
**Schreiben** (also in Datei *speichern*) funktioniert ganz ähnlich wie lesen, aber als zweites Argument muss 'w' angegeben werden:
import io
with io.open('dateiname.txt', 'w', encoding='utf-8') as outfile:
for i in range(10):
line = "Zeile " + str(i) + "\n"
outfile.write(line)
Bemerkungen:
* `str()` verwandelt die Zahl in einen Text.
* `'\n'` ist der ASCII-Code für einen Zeilenumbruch. Lässt man diese am Ende der Zeile weg, werden keine Zeilenumbrüche gemacht (mehr dazu unten).
* 'w' steht für *write*: es wird in das File geschrieben.
* Neben `io.open(...)` gibt auch die Funktion `open(...)`, die praktisch identisch angewendet wird. Der wichtige Unterschied ist, dass man bei der ersteren die Codierung (typischerweise UTF-8) angeben kann. Dadurch werden z.B. Umlaute wie ä,ö,ü richtig dargestellt.
=== Zeilenumbrüche ===
Liest man ein Textfile ein, so **endet jede Linie mit einem Zeilenumbruch.** Achtung: Je nach System können dafür [[https://www.sttmedia.de/zeilenumbruch|unterschiedliche ASCII/Unicode-Steuerzeichen verwendet werden]]. Der Code unten wandelt die Symbole im String `line` in die zugehörigen Positionen in der Unicode-Zeichentabelle um:
# String
line = "Hello Wörld\n"
print(' '.join(str(ord(c)) for c in list(line)))
# Output: 72 101 108 108 111 32 87 246 114 108 100 10
Wahrscheinlich wird die letzte Zahl eine $10$ sein, welche für das ASCII/Unicode-Steuerzeichen "Line Feed (LF)" steht.
Liest man nun ein Textfile ein und printed jede Zeile, so wird es zwischen zwei eigentlichen Zeilen immer noch eine leere haben, da zwei Zeilenumbrüche eingefügt werden: Einen vom letzten Symbol jeder Linie und einen, den der `print()`-Befehl einfügt. Um dies zu vermeiden, gibt es zwei Möglichkeiten:
1. Zeilenumbrüche am Ende der Zeile entfernen: `line = line.replace('\n','')`
1. print ohne zusätzlichen Zeilenumbruch: `print(line,end='')`
==== Auftrag A ====
=== Aufgabe A1: File Einlesen ===
1. Erstelle mit einem beliebigen Editor ein Textfile mit Endung .txt und schreibe einige Zeilen Text hinein.
1. Speichere es im gleichen Ordner, in dem du das zugehörige Python-File speichern wirst.
1. Schreibe einen Python-Code, der dieses File einliest und folgendes macht:
1. Anzahl Zeilen Text bestimmen und ausgeben.
1. Jede Zeile in der Konsole ausgibt. Stelle sicher, dass *keine überflüssigen Zeilenumbrüche* angezeigt werden (siehe Theorie oben).
=== Aufgabe A2: File schreiben ===
1. Schreibe einen Python-Code, der eine Liste mit mehreren Strings beinhaltet: `s = ['Hallo Welt','Wie geht es dir?',...]`
1. Die Strings in dieser Liste sollen dann in ein neues Textfile geschrieben werden. Jeder Eintrag der Liste steht in einer eigenen Zeile.
=== Aufgabe A3: Buch einlesen ===
1. Lade die Bibel herunter:
1. Lade die [[https://www.sermon-online.com/de/contents/6068|Text Version (4.93 MB)]] herunter.
1. Oder {{ :gf_informatik:daten_sca:bibel_martin_luther_1912.txt.zip |direkt hier vom Wiki}}. Achtung: Muss zuerst *entzippt* werden!
1. Lese sie in Python ein.
1. Wie viele Zeilen enthält das Buch?
1. Gebe die ersten 20 Zeilen aus. Stelle wieder sicher, dass keine überflüssigen Zeilenumbrüche angezeigt werden.
1. Erstelle eine Version des Buchs, welche nur aus Grossbuchstaben besteht (siehe Tipps unten).
++++Tipps|
Nur Grossbuchstaben:
* Lese das Buch ein.
* Wandle jede Zeile in Grossbuchstaben um (ein Befehl) und speichere diesen String in einer Liste.
* Schreibe dann diese Liste (jeder Eintrag eine Zeile) in ein neues File.
* That's it!
++++
=== Zusatzaufgaben ===
* Mache eine **Häufigkeitsanalyse** der Buchstaben in der Bibel. Unterscheide nicht zwischen Gross-/Kleinbuchstaben, eliminiere Umlaute und andere Spezialbuchstaben (z.B. a zu ae, ß zu ss). Welcher Buchstabe kommt am seltenesten vor? Tipp: Verwende ein Dictionary.\\ \\
* In der Theorie oben gibt es eine sehr kompakte Zeile Code, mit dem man von einem String die zugehörigen **Unicode-Zeichen** ermitteln kann. Schreibe selbst (ohne diese Zeile anzuschauen) einen mehrzeiligen Code, der dies macht. Vergleiche dann deinen Code mit dieser Zeile und versuche diese nachzuvollziehen.
===== - Notentabelle =====
==== Theorie I ====
=== CSV-Files ===
Daten werden oft in **CSV-Files** gespeichert, wobei CSV für **Comma Separated Values** steht. Ein CSV-File ist ein einfaches Textfile, in dem die Werte durch ein Sonderzeichen, genannt **Delimiter**, getrennt werden. Wie der Name suggeriert, verwendet man dafür oft ein Komma. Dies ist problematisch, wenn man das Komma bereits verwendet, um Nachkommastellen von Zahlen abzutrennen. Gerne verwendet man deshalb ein Semikolon.
=== String-Manipulation ===
Folgende Befehle sind sehr hilfreich, um die Strings, die man aus Dateien einliest, so zu manipulieren, dass man gut mit ihnen arbeiten kann:
* `x = li[3]`: Bestimmtes Element aus Liste auslesen
* `teil_li = li[3:8]`: Teilliste auslesen
* `int(s)`: String `s` der Zahl beinhaltet in ganze Zahl (integer) **umwandeln**
* `float(s)`: String `s` der Zahl beinhaltet in Zahl mit Nachkommastellen (float) **umwandeln**
* `str(z)`: Zahl `z` in String umwandeln
* `s = s.replace('a','A')`: Zeichen in String `s` durch anderes **ersetzen**
* `l = s.split(',')`: String `s` immer dort wo ein `,` (oder welches Zeichen auch immer man angibt) auftritt **aufgeteilt** und in **Liste** `l` gespeichert
* `s = s.strip()` **entfernt** links und rechts im String alle Leerschläge. Funktioniert auch für andere Zeichen, z.B. `s = s.strip('_')`. Mit `lstrip` resp. `rstrip` nur von links, resp. rechts, Zeichen entfernen.
=== CSV-Files lesen & schreiben ===
Mithilfe des **csv-Moduls** können CSV-Dateien einfach **eingelesen** werden:
import csv
import io
with io.open('dateiname.csv', 'r', encoding='utf-8', newline='') as csv_file:
# Create a CSV reader object, change delimiter if elements are separated by different symbol than comma ','
reader = csv.reader(csv_file,delimiter=',')
# Read each row in the CSV file, creates list of all elements (separated by delimiter) in line
for row in reader:
print(row)
Beachte den Parameter `newline=''`. Dieser ist optional, vermeidet aber Probleme, die auftreten können, wenn CSV-Dateien zwischen verschiedenen Systemen (z.B. Windows und Mac) ausgetauscht werden, da diese beiden Plattformen Zeilenumbrüche anders handeln.
In ein CSV-File **schreiben** geht dann wie folgt. Erstelle zuerst eine Liste, die die Daten beinhaltet: Diese besteht wiederum aus Listen, die die Inhalte jeder Zeile beinhalten sollen:
data = [['Hans',42],['Monika',37]]
Diese Liste kann nun ganz einfach in ein CSV-File geschrieben werden
import csv
import io
with io.open('dateiname.csv', 'w', , encoding='utf-8', newline='') as csv_file:
# Create a CSV writer object
writer = csv.writer(csv_file,delimiter=',')
# Write the data to the CSV file
writer.writerows(data)
Das Beispiel oben erzeugt dann ein CSV-File mit Inhalt
Hans,42
Monika,37
=== Zusatzinformationen ===
* Neben der Codierung "UTF-8" gibt es auch **"UTF-8 with BOM"**. UTF-8 with BOM (Byte Order Mark) ist eine Variante der UTF-8-Kodierung, die einen speziellen Marker am Anfang der kodierten Daten enthält. Der Byte Order Mark ist eine Byte-Folge (0xEF, 0xBB, 0xBF), die die Byte-Reihenfolge und das verwendete Kodierungsschema angibt.
==== Auftrag B ====
Schreibe ein Programm, welches eine Notentabelle im CSV-Format einliest. Für jedes Fach wird (falls möglich) der Durchschnitt berechnet und auf halbe Noten gerundet. Daraufhin werden berechnet/ermittelt:
* Anzahl UGs
* Anzahl Pluspunkte
* Anzahl Minuspunkte doppelt
* Promotion ('promoviert' oder 'nicht promoviert')
Sämtliche Resultate, inkl. gerundeter Noten der einzelnen Fächer, werden in ein neues CSV-File geschrieben.
++++Beispiel|
Für die Notentabelle
Deutsch,4.5,4,5.5,5.5
Französisch,5,5.5,5,4.5
Englisch,3,5,1,
Mathematik,3,2,3,
Biologie,5,4.5,4.5,
Chemie,,,,
Physik,6,6,,
Geschichte,4.5,4,4,
Geographie,5,4,5,
Wirtschaft und Recht,3.5,2.5,3,
Bildnerisches Gestalten,5,6,5,
Musik,6,,,
Informatik,6,6,,
Schwerpunktfach,4.5,,,
Ergänzungsfach,6,,,
Erhält man das Output-File:
Deutsch,5.0
Französisch,5.0
Englisch,3.0
Mathematik,2.5
Biologie,4.5
Chemie,
Physik,6.0
Geschichte,4.0
Geographie,4.5
Wirtschaft und Recht,3.0
Bildnerisches Gestalten,5.5
Musik,6.0
Informatik,6.0
Schwerpunktfach,4.5
Ergänzungsfach,6.0
Anz UG,3
Pluspunkte,13.0
Minuspunkte (doppelt),7.0
Promotion,promoviert
++++
Das Programm wird am Schluss einige Zeilen Code haben, weshalb es wichtig ist, dass du die Sache strukturiert angehst. Daher teilen wir den Auftrag in mehrere Teile ein.
=== Teil I ===
**Ziel:** Für jedes Fach durchschnitt berechnen und ausgeben (printen).
Befolge die Schritte und Tipps unten:
1. Erstelle ein CSV-File mit einer Notentabelle. Achte darauf, dass unterschiedliche Fächer unterschiedlich viele Noten haben und es Fächer ohne Noten gibt. Verwende z.B. die Notentabelle aus dem Beispiel oben.
1. Lese das CSV-File mit der Notentabelle ein. Verwende dazu das csv-Module (siehe Theorie oben).
1. Gehe mit einer Schleife jede Zeile durch.
1. Für jede Zeile erhält man eine Liste à la `['Mathematik', '3', '2', '3', '']`
1. Extrahiere das Fach und die Noten (siehe Tipp A)
1. Berechne für das Fach den Notendurchschnitt (siehe Tipp B)
1. Runde den Durchschnitt auf halbe Noten (siehe Tipp C)
1. Gebe Fach und Durchschnitt in der Konsole aus
++++Tipps|
* Tipp A:
* Extrahiere das Fach ...
* ... und entferne es aus der Liste (`pop()`)
* Nun solltest du eine Liste nur mit den Noten und leeren Einträgen `''` haben.
* Tipp B:
* Eliminiere oder ignoriere alle leeren Einträge `''`.
* Berechne Durchschnitt nur, wenn überhaupt Noten vorliegen.
* Für die Berechnung kann man (muss man aber nicht) eine Funktion `calc_average(grades)` schreiben, die die Note für eine Liste mit Noten `grades` berechnet.
* Zahlen-Strings (z.B. '3.5') in Zahl umwandeln mit `float()` (Zahl mit NKS), damit man mit diesen rechnen kann.
* Durchschnitt: Noten aufsummiert, dividiert durch Anzahl Noten
* Tipp C:
* Anfänger: verwende die Funktion `round_to_multiple(...)` unten
* Fortgeschrittene: Versuche zuerst selbst, eine Funktion zu schreiben, die dir auf halbe Noten rundet.
def round_to_multiple(x, mul):
return round(x / mul) * mul
++++
=== Teil II ===
**Ziel:** Durchschnitte in neuem File speichern
Speichere die in Teil I berechneten Notendurchschnitte inkl. Fächernamen in einem neuen File `notentabelle_resultate.csv`.
Speichere dazu die Fächernamen und Durchschnitte in einer Daten-Liste: Jeder Eintrag dieser Liste soll dann einen Eintrag im neuen CSV-File sein, z.B.: `data = [['Deutsch',5.0],['Französisch',5.0]]`. Mit `csv.writer` kann diese Liste mit einem einzigen Befehl ins File geschrieben werden (siehe Tipps oben).
=== Teil III ===
**Ziel:** Restliche Dinge berechnen und in File speichern.
1. Bestimme die Anzahl UGs, speichere in Variable
1. Bestimme die Pluspunkte, speichere in Variable (siehe Tipps unten)
1. Bestimme die Minuspunkte (doppelt), speichere in Variable
1. Bestimme Promotion, speichere in Variable
1. Füge passende Einträge zu data-Liste hinzu
1. Sollte jetzt automatisch in CSV-File geschrieben werden (mit Code von Teil II)
1. Das erwartete Output-CSV sollte etwa wie unten angegeben aussehen
++++Tipps|
* Plus/Minuspunkte: Gegeben durch Differenz von 4. Note 5.5 gibt +1.5 PP weil 5.5-4 = 1.5
++++
++++Erwartetes Output-CSV|
Deutsch,5.0
Französisch,5.0
Englisch,3.0
Mathematik,2.5
Biologie,4.5
Chemie,
Physik,6.0
Geschichte,4.0
Geographie,4.5
Wirtschaft und Recht,3.0
Bildnerisches Gestalten,5.5
Musik,6.0
Informatik,6.0
Schwerpunktfach,4.5
Ergänzungsfach,6.0
Anz UG,3
Pluspunkte,13.0
Minuspunkte (doppelt),7.0
Promotion,promoviert
++++
==== Theorie II ====
=== Try-except ===
Wir haben gesehen, dass man mit `int(...)` und `float(...)` Zahlenstrings (z.B. `'5'` oder `'3.14159'`) in Zahlen umwandeln kann. Dies geht aber nur, wenn man diese Funktionen auf passende Strings anwendet, ansonsten gibt es einen Fehler und das Programm bricht ab. Beispiele:
* `int('6')`: funktioniert
* `float('4.5')`: funktioniert
* `int('4.5')`: Fehler! Weil $4.5$ keine ganze Zahl
* `int('')` oder `float('ksr')` Fehler! Weil String leer resp. Text beinhaltet.
Ist man nicht sicher, ob man einen Fehler erhalten wird, so kann man **try-except** anwenden, mit dem man *Fehler abfangen* kann:
st = '4.5' # define string that may or may not contain a number
try: # try ...
nr = float(st) # ... to convert string into float
print(nr) # if works, print number
except: # if float(st) raises error (exception), code in except is executed
print('String does not contain number')
==== Auftrag B (weiter) ====
=== Teil IV ===
Studiere die Theorie oben zum Thema **Try-Except** und mache dich mit dieser vertraut:
1. Tippe das entsprechende Code-Snippet ab (*nicht* copy-paste).
1. Teste es für verschiedene Strings, wie z.B. `st = '3.14159'`, `st = 'ksr'`, `s = 4cm`.
1. Verwende es, um eine Notenliste wie `['5.5','6','5','','','']` umzuwandeln in `[5.5,6,5]`. Mit try-except ist es *nicht* mehr nötig, zu überprüfen, ob man `float()` überhaupt auf ein Element anwenden kann. Man macht es einfach und schaut dann, ob ein Fehler auftritt oder nicht.
=== Teil V ===
1. Mache eine Kopie deines Codes zur Notentabelle und passe diesen wie unten beschrieben an.
1. Verwende try-except für die ...
1. Aufbereitung der Notenlisten (`['5.5','6','5','','','']` zu `[5.5,6,5]`)
1. Berechnung des Durchschnitts (ergibt Fehler, Division durch 0, wenn keine Noten beinhaltet)
===== - Gemeinden =====
In diesem Auftrag geht es darum, einen grösseren Datensatz, der Informationen zu allen Gemeinden der Schweiz beinhaltet, einzulesen und auszuwerten.
==== Theorie ====
CSV-Datensätze haben typischerweise einen **Header**, der angibt, welche Daten sich in welchen Spalten befinden. Zum Beispiel sehen die ersten Zeilen des Datensatzes dieser Übung wie folgt aus:
['Gemeinde', 'Kanton', 'Einwohner', 'Fläche']
['Aadorf', 'TG', '9216', '19.93']
['Aarau', 'AG', '21726', '12.36']
['Aarberg', 'BE', '4626', '7.94']
['Aarburg', 'AG', '8577', '4.40']
Die erste Zeile ist der Header und muss für die Datenverarbeitung ignoriert werden:
with open(...) ...:
reader = csv.reader(csv_file)
header = next(reader)
...
Der Header wird so in der Variablen `header` gespeichert. Nachher kann man wie bisher mit einer for-Schleife den Datensatz durchgehen.
==== Aufgabe C ====
* Lade die Datei {{ :gf_informatik:daten_sca:gemeinden.csv.zip |}} herunter und entzippe sie.
* Lese die Datei in Python ein.
* Schreibe einen Code, der die Fragen unten beantwortet und die Antworten in passend formatierten Strings in eine **Textdatei** `gemeinden_resultate.txt` **schreibt** (alle Antworten zusammen in einem File).
**Fragen:**
1. Wie viele Gemeinden gibt es in der Schweiz?
1. Wie viele Einwohner hat die Schweiz?
1. Welche Gemeinde hat am meisten Einwohner?
1. Welche Gemeinde hat die grösste Fläche?
1. Wie viele Städte (mehr als 10000 EW) gibt es in der Schweiz?
**Zusatzaufgaben:**
1. Auf welchem Platz im Ranking 'Gemeinden mit den meisten Einwohnern' steht Romanshorn?
1. Es gibt mehrere Ortschaften, die gleich viele Einwohner haben. Welches sind die beiden Orte mit den meiden Einwohnern, die identisch sind?
++++Antworten ohne Code |
* Anz. Gemeinden: 2145
* Einwohner Schweiz: 8670125
* Am meisten Einwohner hat Zürich mit 421878 Einwohnern.
* Grösste Fläche hat Scuol mit 438.76 km^2 Fläche.
* Anzahl Städte (> 10000 EW): 164
**Zusatzaufgaben:**
* Platz von Romanshorn im Ranking "meiste EW": 136
* Gemeinden mit grösster identischer Anz. EW: Amriswil und Horw
++++
===== - Dictionaries & JSON =====
==== Theorie ====
=== Dictionary ===
Ein **Dictionary** (kurz **Dict**) ist eine **Datenstruktur** in Python, die es ermöglicht, Daten als **Schlüssel-Wert-Paare** (**key:value**) zu speichern. Jeder Schlüssel in einem Dictionary muss *eindeutig* sein, und er wird mit einem Wert verknüpft. Dictionaries sind äusserst vielseitig und nützlich, um verschiedene Arten von Daten zu organisieren. Ein Dictionary findet für einen Key den zugehörigen Value sehr schnell, da Dictionaries mit **Hash-Tables** arbeiten (mehr dazu später).
Mit einem Dictionary kannst du zum Beispiel speichern, wie viele Früchte du noch hast:
fruits = {'Apfel': 5, 'Banane': 3, 'Orange': 2}
Jedem Key wird also ein Wert zugeordnet. Zum Beispiel hat der Key "Banane" den Wert $3$.
Über den Key kann man nun auf die Werte zugreifen:
print("Ich habe noch " + str(fruits['Banane']) + " Bananen.")
Isst man eine Banane, kann man den zugehörigen Wert anpassen:
fruits['Banane'] = 2
print(fruits) # -> {'Banane': 2, 'Orange': 2, 'Apfel': 5}
Wenn ein Schlüssel nicht vorhanden ist, führt der Zugriff zu einem Fehler. Um dies zu vermeiden, können Sie die Methode get() verwenden, die einen Standardwert zurückgibt, wenn der Schlüssel nicht gefunden wird:
print(fruits.get('Birne', 0)) # Gibt den Wert 0 aus, da 'Birne' nicht im Dictionary enthalten ist
Mit dem Keyword `in` kann man überprüfen, ob ein Key im Dictionary vorhanden is:
if 'Banane' in fruits:
print('Ich habe noch Bananen zuhause.')
else:
print('Ich muss Bananen kaufen.')
Neue Einträge können direkt über den neuen Key generiert werden:
fruits['Kiwi'] = 7
print(fruits) # -> {'Banane': 2, 'Orange': 2, 'Kiwi': 7, 'Apfel': 5}
Mit `fruits.keys()` und `fruits.values()` kann man alle **Key-Value-Paare** systematisch durchzugehen:
# Option 1
for key in fruits: # oder for key in fruits.keys()
print(key, fruits[key])
# Option 2
for key,value in fruits.items():
print(key,value)
Die Befehle `.keys()` und `.values()` erstellen "dict_key" Objekte (zumindest in neueren Python Versionen). Möchte man aber eine **Liste mit allen Keys oder Values** eines Dicts haben, muss man diese mit der `list()`-Funktion noch in solche umwandeln:
key_list = list(fruits.keys())
value_list = list(fruits.values())
Interessiert man sich nur für die Werte, kann man auch einfach diese durchgehen:
for value in fruits.values():
print(value)
Mit dem Keyword `del` kann man Einträge aus dem Dictionary **löschen**:
del fruits['Banane']
print(fruits) # -> {'Orange': 2, 'Kiwi': 7, 'Apfel': 5}
=== JSON ===
**JSON** steht für **J**ava**S**cript **O**bject **N**otation und ist ein Datenformat (`my_file.json`), welches sich zur Speicherung von Daten eignet. Es ist eine Alternative zu CSV und besonders für etwas komplexere, z.B. verschachtelte, Datensätze geeignet.
JSON repräsentiert Daten in Form von **Schlüssel:Wert-Paaren** (**Key:Value**) und unterstützt verschiedene Datentypen wie Strings, Zahlen, Booleans (True/False), Listen und Dictionaries. Es ähnelt daher der **Struktur von Python-Dictionaries**.
Hier zwei **Beispiele** für JSON-Daten:
* **Einfacher Datensatz** (Key: Gemeinde, Value: PLZ):
{
"Bern": 3000,
"Lausanne": 1000,
"Zurich": 8000,
"Basel": 4000,
"Genf": 1200
}
* **Komplexerer Datensatz** (Key: Gemeinde, Value: Dict mit Infos zur Gemeinde):
{
"Genthod": {
"area": 2.81,
"inhabitants": 2893,
"canton": "GE"
},
"Gams": {
"area": 22.28,
"inhabitants": 3587,
"canton": "SG"
},
"Rorbas": {
"area": 4.5,
"inhabitants": 2885,
"canton": "ZH"
},
...
}
JSON-Files können in Python ganz einfach in Dictinaries **eingelesen** werden:
import json
# LESEN von JSON-File
with open("my_file", "r") as json_file:
data = json.load(json_file) # lade File in Dictionary data
Hat man in Python ein Dictionary, so kann man dieses ganz einfach in ein JSON-File **schreiben**:
import json
data = {} # wichtig: muss DICTIONARY sein!
# SCHREIBEN von JSON-File
with open('my_file.json', 'w', encoding='utf-8') as json_file:
json.dump(data, json_file, indent=4, ensure_ascii=False) # indent: Anz. Leerschlaege zum Einruecken
==== Auftrag D ====
=== Aufgabe D1 ===
**Studiere die Theorie** oben zu Dictionaries und JSON und löse dann die Aufgabe unten:
Die fünf bevölkerungsstärksten Städte mit ihrer jeweiligen Postleitzahl sind Zurich (8000), Genf (1200), Basel (4000), Lausanne (1000) und Bern (3000). Starte mit dem Dictionary:
big_5 = {'Zurich': 9999, 'Weinfelden': 8570, 'Basel': 4000, 'P3Rn': 3000}
1. **Korrigiere** das Dictionary in möglichst wenigen Schritten.
1. **Speichere** das fertige Dictionary in einem JSON-File.
=== Aufgabe D2: Voci-Trainer ===
Ziel ist, einen einfachen Voci-Trainer zu implementieren.
== Version 1 ==
1. Erstelle zuerst ein Dictionary `word_pairs`. Der Key ist immer das Deutsche Wort (z.B. 'Katze') und der Value die englische Übersetzung ('cat'). Füge deinem Dictionary mindestens zehn solche Key-Value-Paare hinzu.
1. Gehe nun einmal das Dictionary durch (*Tipp:* In der Theorie oben wird erklärt, wie man ein Dict durchgehen kann.). Fordere die Benutzerin auf, das deutsche Wort auf Englisch zu übersetzen.
1. Die Eingabe wird dann mit der korrekten Antwort verglichen ...
1. ... und es wird eine entsprechende Ausgabe gemacht, z.B. "Antwort korrekt", "Antwort leider falsch".
1. Speichere den Score. Speichere dazu in zwei Variablen die Anzahl korrekten und falschen Antworten. Gebe den Score nach jeder Ausgabe aus.
== Version 2 ==
1. Mache eine Kopie der letzten Version.
1. Das Spiel soll nun unendlich lange gespielt werden können. In jedem Durchgang soll ein **zufälliges Wort** ausgewählt werden. Siehe Tipp unten.
1. Alles andere soll gleich bleiben wie bei der letzten Version.
++++Tipp|
**Unendlich** lange spielen: `while True` Endlosschleife!
**Zufallswort:** Mit `my_dict.keys()` erstellst du eine *Liste* mit allen Keys des Dictionaries. Mit `random.choice(my_list)` wählst du aus einer Liste ein zufälliges Element aus. Vergesse nicht, ganz oben das random-Modul zu importieren.
++++
== Version 3 (optional, Pflicht für TALITs) ==
Mache eine Kopie der letzten Version und erweitere den Code wie folgt:
1. Für jedes Wort soll unabhängig der **Fortschritt (Progress) gespeichert** werden (siehe Tipps unten).
1. Übersetzt man ein Wort korrekt/falsch, so soll dessen Progress um eins erhöht/verringert werden. Negativer Progress soll verhindert werden.
1. Hat ein Wort einen maximalen Progress (z.B. $3$) erreicht, wird es aus dem Dict entfernt und nicht mehr trainiert.
1. Hat man alle Wörter vollständig gelernt, wird einem gratuliert und das Programm ist zu Ende.
1. *Optional:*
1. Nach jeder Frage soll man abbrechen können. Aktueller Stand wird dann in JSON-File geschrieben. Lösche keine Wörter aus dem File.
1. Soll am Anfang des Spiels den Progress zurücksetzen können, damit man alle Wörter wieder lernen kann.
1. Implementiere eigene Features.
++++Tipp|
Die Values eines Dictionaries können selbst wieder Dictionaries sein. Jedes deutsche Wort (key, z.B. 'Katze') soll als Value ein Dict haben, welches zwei Key-Value-Paare beinhaltet: `'en':'cat'` und `'progress':0`
++++
=== Aufgabe D3: Gemeinden ===
== Teil I ==
Lese den Gemeinden-Datensatz aus dem CSV-File von weiter oben ein und speichere die Daten in einem **Dictionary** mit der folgenden Form:
{
"Genthod": {
"area": 2.81,
"inhabitants": 2893,
"canton": "GE"
},
"Gams": {
"area": 22.28,
"inhabitants": 3587,
"canton": "SG"
},
"Rorbas": {
"area": 4.5,
"inhabitants": 2885,
"canton": "ZH"
},
...
}
Speichere dann das Dict in einem JSON-File `gemeinden.json`.
== Teil II ==
Öffne nun in einem *neuen Python-File* das JSON-File `gemeinden.json` aus dem ersten Teil der Aufgabe und lese dessen Daten in ein Dictionary ein. Beantworte mit dessen Hilfe die folgenden Fragen:
1. Wie viele Einwohner hat Aarwangen?
1. Welche Fläche hat Estavayer?
1. Welche Gemeinde hat $314$ Einwohner?
1. Wie viele Gemeinden liegen im Kanton Tessin?
++++Lösungen kurz|
1. $4638$
1. $44.89$ km$^2$
1. Courchavon (obwohl Pitown passender wäre)
1. $106$
++++
===== Lösungen =====
++++Lösungen A|
=== Aufgabe A1 ===
import io
with io.open('dateiname.txt', 'r', encoding='utf-8') as infile:
line_count = 0
for line in infile:
line_count += 1
print(line_count,end='')
=== Aufgabe A2 ===
poem = ['Hallo Welt','Wie geht es dir?','Nicht so gut','Danke der Nachfrage']
with io.open('my_great_poem.txt', 'w', encoding='utf-8') as outfile:
for line in poem:
outfile.write(line + '\n')
=== Aufgabe A3 ===
Lösung 1
import io
bible_caps = []
with io.open('bibel_martin_luther_1912.txt', 'r', encoding='utf-8') as infile:
line_count = 0
for line in infile:
if line_count < 20: # ersten ... Zeilen ausgeben
print(line,end='')
line_count += 1
# in caps umwandeln und in Liste speichern
bible_caps.append(upper(line))
print(line_count)
with io.open('bibel_caps.txt', 'w', encoding='utf-8') as outfile:
for line in bible_caps:
outfile.write(line)
Lösung 2
import io
with io.open('bibel_martin_luther_1912.txt', 'r', encoding='utf-8') as infile:
with io.open('bibel_caps2.txt', 'w', encoding='utf-8') as outfile:
line_count = 0
for line in infile:
if line_count < 20:
print(line,end='')
line_count += 1
outfile.write(line)
print(line_count)
=== Zusatzaufgaben ===
== Häufigkeitsanalyse Buch ==
import io
char_count = {}
with io.open('bibel_martin_luther_1912.txt', 'r', encoding='utf-8') as infile:
line_count = 0
for line in infile:
line = line.replace('Ä','ae').replace('Ö','oe').replace('Ü','ue').replace('ä','ae').replace('ö','oe').replace('ü','ue').replace('ß','SS')
line = upper(line)
for c in line:
if c in string.ascii_uppercase:
if c in char_count:
char_count[c] += 1
else:
char_count[c] = 1
line_count += 1
print(line_count)
nr_letters = sum(char_count.values())
with io.open('bibel_char_count.txt', 'w', encoding='utf-8') as outfile:
for key,value in char_count.items():
line = key + ": " + str(value) + " (" + str(round(value/nr_letters*100,3)) + "%)"
outfile.write(line + "\n")
== Unicode-Zeichen String ==
s = ""
for c in line:
s += str(ord(c)) + " "
s = s[:-1] # entfernt letztes Zeichen (ueberfluessiger Leerschlag)
print(s)
++++
++++Lösungen B|
=== Teil I ===
import csv
import io
def round_to_multiple(x, mul):
return round(x / mul) * mul
def calc_average(grades):
# create new list without empty entries and with floats instead of strings
grades_new = []
for g in grades:
if g != '':
grades_new.append(float(g))
# calc average
if len(grades_new) == 0: # no grades
return ''
else:
average = sum(grades_new)/len(grades_new) # calc average
average = round_to_multiple(average,0.5) # round
return average
with io.open('notentabelle.csv', 'r', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile,delimiter=',')
for row in reader:
subject = row[0] # first element of list
grades = row[1:] # sublist without first element
average = calc_average(grades)
print(subject + "," + str(average))
=== Teil II ===
import csv
import io
def round_to_multiple(x, mul):
return round(x / mul) * mul
def calc_average(grades):
# create new list without empty entries and with floats instead of strings
grades_new = []
for g in grades:
if g != '':
grades_new.append(float(g))
# calc average
if len(grades_new) == 0: # no grades
return ''
else:
average = sum(grades_new)/len(grades_new) # calc average
average = round_to_multiple(average,0.5) # round
return average
data = [] # when reading data, save here to write afterwards
with io.open('notentabelle.csv', 'r', encoding='utf-8', newline='') as csv_file:
reader = csv.reader(csv_file,delimiter=',')
for row in reader:
subject = row[0] # first element of list
grades = row[1:] # sublist without first element
average = calc_average(grades)
print(subject + "," + str(average))
# Teil II: save data into data-list
data.append([subject,average])
# Teil II
with io.open('notentabelle_resultate.csv', 'w', encoding='utf-8', newline='') as csv_file:
writer = csv.writer(csv_file,delimiter=',') # Create a CSV writer object
writer.writerows(data) # Write the data to the CSV file
=== Teil III ===
import csv
import io
def round_to_multiple(x, mul):
return round(x / mul) * mul
def calc_average(grades):
# create new list without empty entries and with floats instead of strings
grades_new = []
for g in grades:
if g != '':
grades_new.append(float(g))
# calc average
if len(grades_new) == 0: # no grades
return ''
else:
average = sum(grades_new)/len(grades_new) # calc average
average = round_to_multiple(average,0.5) # round
return average
data = [] # when reading data, save here to write afterwards
nr_ug = 0
points_positive = 0
points_negative = 0
promotion = "promoviert"
with io.open('notentabelle.csv', 'r', encoding='utf-8', newline='') as csv_file:
reader = csv.reader(csv_file,delimiter=',')
for row in reader:
subject = row[0] # first element of list
grades = row[1:] # sublist without first element
average = calc_average(grades)
print(subject + "," + str(average))
# Teil II: save data into data-list
data.append([subject,average])
# Teil III
if average != '':
if average < 4:
nr_ug += 1
points_negative += -2*(average - 4)
else:
points_positive += average - 4
# Teil III: determine promotion conditions
if nr_ug > 3 or points_positive - points_negative < 0:
promotion = "nicht promoviert"
# add additional info to data
data.append(['Anz UG',nr_ug])
data.append(['Pluspunkte',points_positive])
data.append(['Minuspunkte (doppelt)',points_negative])
data.append(['Promotion',promotion])
# Teil II
with io.open('notentabelle_resultate.csv', 'w', encoding='utf-8', newline='') as csv_file:
writer = csv.writer(csv_file,delimiter=',') # Create a CSV writer object
writer.writerows(data) # Write the data to the CSV file
=== Teil IV ===
grades = ['5.5','6','5','','','']
grades_new = []
for grade in grades:
try:
grade = float(grade)
grades_new.append(grade)
except:
pass
print(grades_new)
=== Teil V ===
import csv
import io
def round_to_multiple(x, mul):
return round(x / mul) * mul
def calc_average(grades):
# create new list without empty entries and with floats instead of strings
grades_new = []
for g in grades:
try:
grades_new.append(float(g))
except:
pass
# calc average
try:
average = sum(grades_new)/len(grades_new) # calc average
average = round_to_multiple(average,0.5) # round
return average
except:
return ''
data = [] # when reading data, save here to write afterwards
nr_ug = 0
points_positive = 0
points_negative = 0
promotion = "promoviert"
with io.open('notentabelle.csv', 'r', encoding='utf-8', newline='') as csv_file:
reader = csv.reader(csv_file,delimiter=',')
for row in reader:
subject = row[0] # first element of list
grades = row[1:] # sublist without first element
average = calc_average(grades)
print(subject + "," + str(average))
# Teil II: save data into data-list
data.append([subject,average])
# Teil III
if average != '':
if average < 4:
nr_ug += 1
points_negative += -2*(average - 4)
else:
points_positive += average - 4
# Teil III: determine promotion conditions
if nr_ug > 3 or points_positive - points_negative < 0:
promotion = "nicht promoviert"
# add additional info to data
data.append(['Anz UG',nr_ug])
data.append(['Pluspunkte',points_positive])
data.append(['Minuspunkte (doppelt)',points_negative])
data.append(['Promotion',promotion])
# Teil II
with io.open('notentabelle_resultate.csv', 'w', encoding='utf-8', newline='') as csv_file:
writer = csv.writer(csv_file,delimiter=',') # Create a CSV writer object
writer.writerows(data) # Write the data to the CSV file
++++
++++Lösungen C|
=== Teil I ===
grades = ['5.5','6','5','','','']
grades_new = []
for grade in grades:
try:
grade = float(grade)
grades_new.append(grade)
except:
pass
print(grades_new)
=== Teil II ===
import csv
import io
nr_of_towns = 0
inhabitants_total = 0
inhabitants_max_nr = 0
inhabitants_max_town = None # or = ''
area_max_nr = 0
area_max_town = None
towns_over_10000 = 0
inhabitants_romanshorn = 0
ranking_inhabitants_romanshorn = 1
# only necessary for add. exercises
data = []
inhabitants_list = []
with io.open('gemeinden.csv','r',encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
header = next(reader)
print(header)
for row in reader:
town = row[0]
canton = row[1]
inhabitants = int(row[2])
area = float(row[3])
nr_of_towns += 1
inhabitants_total += inhabitants
if area > area_max_nr:
area_max_nr = area
area_max_town = town
if inhabitants > inhabitants_max_nr:
inhabitants_max_nr = inhabitants
inhabitants_max_town = town
if inhabitants > 10000:
towns_over_10000 += 1
# ADDITIONAL EXERCISES
data.append(row)
if town == 'Romanshorn':
inhabitants_romanshorn = inhabitants
inhabitants_list.append(inhabitants)
# ADDITIONAL EXERCISES
inhabitants_list.sort()
inhabitants_max_double = None
for i in range(len(inhabitants_list)-1):
if inhabitants_list[i] == inhabitants_list[i+1]:
inhabitants_max_double_nr = inhabitants_list[i]
inhabitants_max_double_towns = []
for row in data:
town = row[0]
canton = row[1]
inhabitants = int(row[2])
area = float(row[3])
if inhabitants > inhabitants_romanshorn:
ranking_inhabitants_romanshorn += 1
if inhabitants == inhabitants_max_double_nr:
inhabitants_max_double_towns.append(town)
# OUTPUT
with io.open('gemeinden_resultate.txt','w',encoding='utf-8') as txt_file:
txt_file.write('Reguläre Aufgaben:\n')
txt_file.write(' * Anz. Gemeinden: ' + str(nr_of_towns) + '\n')
txt_file.write(' * Einwohner Schweiz: ' + str(inhabitants_total) + '\n')
txt_file.write(' * Am meisten Einwohner hat ' + str(inhabitants_max_town) + ' mit ' + str(inhabitants_max_nr) + ' Einwohnern.\n')
txt_file.write(' * Grösste Fläche hat ' + str(area_max_town) + ' mit ' + str(area_max_nr) + ' km^2 Fläche.\n')
txt_file.write(' * Anzahl Städte (> 10000 EW): ' + str(towns_over_10000) + '\n')
txt_file.write('Zusatzaufgaben:\n')
txt_file.write(' * Platz von Romanshorn im Ranking "meiste EW": ' + str(ranking_inhabitants_romanshorn) + '\n')
txt_file.write(' * Gemeinden mit grösster identischer Anz. EW: ' + inhabitants_max_double_towns[0] + ' und ' + inhabitants_max_double_towns[1] + '\n')
++++
++++Lösungen D|
==== Aufgaben D1 ====
big_5 = {'Zurich': 9999, 'Weinfelden': 8570, 'Basel': 4000, 'Lausanne': 1000, 'P3Rn': 3000}
# Wert anpassen
big_5['Zurich'] = 8000
# Eintraege loeschen
del big_5['Weinfelden']
del big_5['P3Rn'] # ACHTUNG! key ist unveraenderlich! Kann deshalb nicht geaendert werden, muss loeschen und neuen Eintrag machen
# Neue Eintraege
big_5['Bern'] = 3000
big_5['Genf'] = 1200
print(big_5)
=== Aufgabe D2 Version 1 ===
word_pairs = {
'Apfel': 'apple',
'Haus': 'house',
'Auto': 'car',
'Buch': 'book',
'Katze': 'cat',
'Tisch': 'table',
'Stuhl': 'chair',
'Hund': 'dog',
'Schule': 'school',
'Bleistift': 'pencil'
}
correct = 0
wrong = 0
for german in word_pairs: # or: for german in word_pairs.keys():
english = word_pairs[german]
answer = input('Uebersetze auf Englisch: ' + german)
if answer == english:
correct += 1
print('Antwort korrekt. Score: ' + str(correct) + '/' + str(correct + wrong))
else:
wrong += 1
print('Antwort falsch. Score: ' + str(correct) + '/' + str(correct + wrong))
=== Aufgabe D2 Version 2 ===
import random
word_pairs = {
'Apfel': 'apple',
'Haus': 'house',
'Auto': 'car',
'Buch': 'book',
'Katze': 'cat',
'Tisch': 'table',
'Stuhl': 'chair',
'Hund': 'dog',
'Schule': 'school',
'Bleistift': 'pencil'
}
correct = 0
wrong = 0
while True:
german = random.choice(word_pairs.keys())
english = word_pairs[german]
answer = input('Uebersetze auf Englisch: ' + german)
if answer == english:
correct += 1
print('Antwort korrekt. Score: ' + str(correct) + '/' + str(correct + wrong))
else:
wrong += 1
print('Antwort falsch. Score: ' + str(correct) + '/' + str(correct + wrong))
=== Aufgabe D2 Version 3 ===
import random
# CONSTANTS ('variables' that don't change)
PROGRESS_MAX = 3
# dict from previous exercise
word_pairs_0 = {
'Apfel': 'apple',
'Haus': 'house',
'Auto': 'car',
'Buch': 'book',
'Katze': 'cat',
'Tisch': 'table',
'Stuhl': 'chair',
'Hund': 'dog',
'Schule': 'school',
'Bleistift': 'pencil'
}
# extended dict with progress
word_pairs = {}
for german in word_pairs_0.keys():
word_pairs[german] = {'en': word_pairs_0[german], 'progress': 0}
print(word_pairs)
# main loop
while len(word_pairs) > 0:
german = random.choice(word_pairs.keys())
english = word_pairs[german]['en']
progress = word_pairs[german]['progress']
answer = input('Uebersetze auf Englisch: ' + german)
if answer == english:
word_pairs[german]['progress'] += 1
print('Antwort korrekt. Progress: ' + str(word_pairs[german]['progress']) + '/' + str(PROGRESS_MAX))
if word_pairs[german]['progress'] == PROGRESS_MAX:
del word_pairs[german]
else:
word_pairs[german]['progress'] -= 1
if word_pairs[german]['progress'] < 0:
word_pairs[german]['progress'] = 0
print('Antwort falsch. Score: ' + str(word_pairs[german]['progress']) + '/' + str(PROGRESS_MAX))
print(len(word_pairs))
print("GRATULATION! Du hast alle Wörter gut gelernt!")
=== Aufgabe D3 ===
== Teil I ==
import csv
import io
import json
# CSV LESEN und DICT ERSTELLEN
data = {} # dict fuer alle Daten
with io.open('gemeinden.csv','r',encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
header = next(reader)
for row in reader:
town = row[0]
canton = row[1]
inhabitants = int(row[2])
area = float(row[3])
data[town] = {
"canton":canton,
"inhabitants":inhabitants,
"area":area
}
# IN JSON SCHREIBEN
with open('gemeinden.json', 'w') as json_file:
json.dump(data, json_file, indent=4)
== Teil II ==
# 1.
print(data['Aarwangen']['inhabitants'])
# 2.
print(data['Estavayer']['area'])
# 3. Variante 1
for key,value in data.items():
if value['inhabitants'] == 314:
print(key)
# 3. Variante 2
for town in data.keys():
if data[town]['inhabitants'] == 314:
print(town)
# 4.
count = 0
for key,value in data.items():
if value['canton'] == 'TI':
count += 1
print(count)
++++