4. Übung

Beispiel 4.1

Dateieingabe

Das Einlesen einer Datei setzt sowohl bei Text- wie bei Binärdateien immer voraus, dass wir wissen in welchem Format die Daten in der Datei enthalten sind. Für unsere Sinus-Wertetabelle wissen wir, dass es sich um zwei Werte pro Zeile handelt, von denen der erste Wert das Arguments x ist und der zweite Wert der Funktionswert sin(x) ist. Aber wie müssen wir nun vorgehen, um mit unserem bisherigen Wissen die Datei einlesen zu können und die Werte für x und sin(x) zur weiteren Verarbeitung wieder in einer Dateistruktur abzulegen? Wir wollen uns dies zunächst noch nicht in echtem Python-Code anschauen, sondern wollen das Ganze in einer Art umgangssprachlichem Pseudo-Code schreiben:

Öffne Datei

Für alle Zeilen der Datei:
  Lese Zeile ein
  Trenne die Zeile in ihre (String-)Bestandteile x und sin(x)
  Wandle die (Teil-)Strings für x und sin(x) in Zahlenwerte um
  Speichere die Zahlenwerte für x und sin(x) in einer Liste

Schließe Datei

Verarbeite die Daten

Auf den ersten Blick mag das etwas aufwendig aussehen, tatsächlich ist es aber genau die Umkehrung der Aktionen, die wir beim Speichern der Daten durchgeführt haben. Obwohl wir den o.a. Pseudo-Code in der gegebenen Form in Python umsetzen könnten, wollen wir das "Programm" noch ein klein wenig abändern:

Öffne Datei

Lese alle Zeilen der Datei in eine String-Liste ein

Für alle Einträge in der String-Liste:
  Trenne den Eintrag in seine (String-)Bestandteile x und sin(x)
  Wandle die (Teil-)Strings für x und sin(x) in Zahlenwerte um
  Speichere die Zahlenwerte für x und sin(x) in einer Liste

Schließe Datei

Verarbeite die Daten

Mit dieser Änderung können wir es nämlich noch einfacher in Python realisieren:

 1print('Einlesen einer Werte-Tabelle')
 2
 3file_name = input('Wie lautet der Dateiname? ')
 4
 5# Datei öffnen
 6fd = open(file_name,'r')
 7
 8# Jetzt lesen wir alle Zeilen der Datei auf einmal ein. Die Funktion
 9# 'readlines()' liefert uns eine Liste zurück, in der jeder Eintrag einer
10# Zeile der Datei entspricht.
11Lines = fd.readlines()
12
13# wir erzeugen 2 leere Liste für x und y ...
14x_list = []
15y_list = []
16
17# ... und arbeiten nun die Einträge der String-Liste
18# nacheinander ab
19for Line in Lines:
20  # 'split()' spaltet eine String in einzelne Wörter auf, als Trennzeichen
21  # werden alle sog. "Whitespace-Character" angenommen (' ' und ',' und '.' etc.)
22  line_elements = Line.split()
23  # jetzt wandeln wir die Teilstrings wieder in numerische Werte für x und y
24  x_list.append(float(line_elements[0]))
25  y_list.append(float(line_elements[1]))
26
27# und geben das Ganze zur Kontrolle auf der Konsole aus
28for i in range(0, len(x_list)):
29  print(x_list[i], y_list[i])

Eine interessante Variante für die for-Schleife in den Zeilen 28/29 ist der folgende Code (bei dem wir uns natürlich fragen, wie das wohl funktioniert):

for x,y in zip(x_list, y_list):
  print(x,y)

Beispiel 4.2

Grafische Ausgabe mit der Matplotlib

Nachdem das bisher alles recht gut geklappt hat, werden wir etwas mutiger und wollen uns eine größere Herausforderung vornehmen - die grafische Darstellung von Kurvenverläufen. Für grafische Ausgaben, bei denen es darum geht mathematische Inhalte darzustellen, gibt es in Python das Modul Matplotlib, das einen wirklich mächtigen Funktionsumfang bereitstellt. Das Modul gehört nicht zur Standard-Python-Distribution, die wir installiert haben, aber wir können es einfach nachinstallieren. Dazu gehen wir in eine Konsole, in der die virtuelle Umgebung aktiviert ist, in die wir das Paket installieren wollen.

Unter Windows geben wir folgende Kommandozeile ein:

> pip install matplotlib

Unter Linux und MacOS hingegen:

$ pip3 install matplotlib

Für den Einstieg nutzen wir natürlich nur einen Bruchteil der Funktionalität des Moduls und wollen uns auch gar nicht lange mit Erklärungen aufhalten, sondern gleich in die Vollen gehen:

 1# Import des Moduls
 2import matplotlib.pyplot as plt
 3
 4# wir legen 2 Listen mit Testwerten an
 5x = [0,1,2,3,4,5,6,7,8,9]
 6y = [1,1,-1,-1,1,1,-1,-1,1,1]
 7
 8# dann folgt das Anlegen des Plot-Objekts
 9# fig (figure): Objekt, das das Plotfenster repräsentiert
10# ax (axes):  Objekt, das das Koordinatensystem repräsentiert
11(fig, ax) = plt.subplots()
12
13# jetzt plotten wir unsere Testwerte in der Form "y über x"
14ax.plot(x, y)
15
16# und wenn alles fertig ist, dann erst "veröffentlichen" wir das Plotfenster
17plt.show()

Wenn wir das Programm laufen lassen, sollte das Ergebnis in etwa so aussehen:

../_images/exer_04_matplotlib_01.PNG

Wie wir sehen, "zaubert" die Matplotlib aus den wenigen Zeilen Code etwas durchaus ansehnliches: Es gibt einen Diagrammrahmen, die Achsen besitzen Skalen, das Diagramm wird auch passend zu den Daten skaliert und das Plotfenster kann beispielsweise vergrößert oder auch gespeichert werden und mehr. Und das Ganze mit nur drei Zeilen Python:

Zeile 11:

Wir lassen uns von der Matplotlib durch die Funktion plt.subplots() ein Objekt zum Plotten erzeugen und erhalten zwei Rückgabewerte von einer Funktion:

  1. ein Objekt zur Verwaltung des eigentlichen Plot-Fensters

  2. ein Objekt zum tatsächlichen Zeichnen in das Achsenkreuz des Plot-Objekts

Zeile 14:

Hier plotten wir unsere Testwerte, indem wir die Funktion ax.plot() des axes-Objekts mit den darzustellenden Werten aufrufen.

Zeile 17:

Alle bisherigen Aktionen fanden für die Nutzer unsichtbar im Verborgenen statt. Mit plt.show() wird der Plot zur Anzeige gebracht ... und muss geschlossen werden, damit das Programm weiterläuft und sich beendet.

Merke

Funktionen können in Python eine (theoretisch) unbegrenzte Zahl an Rückgabewerten liefern:

 1def funktion(arg1, arg2):
 2  # tue ein paar Dinge ...
 3  ...
 4  # ... und liefere 3 Werte zurück
 5  return ret_1, ret_2, ret_3
 6
 7# Aufruf der Funktion:
 8# so...
 9(a, b, c) = funktion(1,2)
10# ...oder so...
11a, b, c = funktion(1,2)
12# ...oder so
13d = funktion(1,2)
14# d: (ret_1, ret_2, ret_3)

Auch wenn der Aufruf und die Übernahme der Rückgabewerte in allen drei Fällen (Zeilen 7, 9, 11) etwas anders aussieht, steckt dahinter tatsächlich immer der gleiche Mechanismus. Bei der Rückgabe von mehreren Werten, werden diese als sog. Tupel zurückgegeben. Ein Tupel in Python kann ähnlich wie eine Liste eine Reihe von Werten unterschiedlichen Datentyps enthalten. Die Werte eines Tupels werden durch ( ) eingeschlossen (Listen durch [ ]). Der wesentliche Unterschied zwischen beiden Datenstrukturen ist, dass wir bei einem Tupel die Werte nachträglich nicht mehr verändern können, bei Listen hingegen schon.

Wir können den Plot noch in vielfältiger Weise anpassen oder um weitere Darstellungselemente ergänzen, z.B.:

 1# Import des Moduls
 2import matplotlib.pyplot as plt
 3
 4# wir legen 2 Listen mit Testwerten an
 5x = [0,1,2,3,4,5,6,7,8,9]
 6y = [1,1,-1,-1,1,1,-1,-1,1,1]
 7
 8# dann folgt das Anlegen des Plot-Objekts
 9# fig (figure): Objekt, das das Plotfenster repräsentiert
10# ax (axes):  Objekt, das das Koordinatensystem repräsentiert
11(fig, ax) = plt.subplots()
12
13# jetzt plotten wir unsere Testwerte in der Form "y über x"
14ax.plot(x, y)
15
16# wir können selber die Grenzen der Achsen festlegen ...
17ax.set_xlim(-1, +10)
18ax.set_ylim(-2, +2)
19# ... und die Achsen beschriften ...
20ax.set_xlabel('x')
21ax.set_ylabel('y')
22# ... und ein Gitter hinterlegen ...
23ax.grid()
24# ... und am Ende das Ganze mit einer Überschrift garnieren
25ax.set_title('Unser erster Plot mit Python')
26
27# und wenn alles fertig ist, dann erst "veröffentlichen" wir das Plotfenster
28plt.show()

Das sieht immer noch alles sehr einfach aus, interessant wird es aber, wenn wir die Standardeinstellungen für den Plot verändern wollen, z.B. eine andere Linienfarbe oder eine gestrichelte Linie haben möchten. Werfen wir einmal einen Blick auf die Definition der Funktion ax.plot() (bitte dem Link zur Originaldokumentation folgen). Beim Blick darauf kann man schon mal die Orientierung verlieren, was man wie alles machen können soll. Hier nur einmal die "Grundform" des Funktionsaufrufs:

Axes.plot(*args, scalex=True, scaley=True, data=None, **kwargs)
*args:

Mit den "arguments" sind die Daten gemeint, die geplotted werden sollen. '*' deshab, weil wir die Daten in unterschiedlicher Form übergeben können, ebenso wie einen oder mehrere Datensätze.

scalex=True, scaley=True, data=None:

Dies sind Argumente mit Defaultwerten. Übergeben wir diese Argumente nicht explizit, dann werden die Defaultwerte verwendet (zu dieser Form der Datenübergabe gleich mehr).

**kwargs:

Mit den "keyword-arguments" ist eine Aufzählung variabler Länge von Modifikationsparametern gemeint. Auch diese Parameter haben Standardwerte hinterlegt, die wir ändern können, z.B. ändern wir durch das 'kwarg' color='r' die Farbe der zu plottenden Kurve in 'red'.

Die Mächtigkeit des Funktionsaufruf ist tatsächlich so, dass wir als Anfänger uns am besten immer an Beispielen orientieren, um von da aus weiterzukommen. Daneben ist es wichtig, bei Unklarheiten einfach ein kleines Testprogramm zu schreiben und die Parameter auszuprobieren. Das wollen wir gleich einmal tun und ändern dazu die Zeile 14 im o.a. Programm wie folgt ab:

ax.plot(x, y, color='r', linestyle=':', marker='o')

Und weil diese drei 'Keyword'-Parameter so oft gesetzt werden, können wir die drei sogar ohne Angabe der Argumentnamen einfach in einem String zusammen übergeben:

ax.plot(x, y, 'r:o')

Um noch etwas mehr zu erproben, wollen wir einen zweiten Kurvenverlauf mit etwas anderem Darstellungsstil nach dem ersten plot()-Aufruf hinzufügen (Zeile 15 folgende):

# einen 2.Kurvenverlauf kann man ganz problemlos "hinzu"-plotten
z = [0,1,0,-1,0,1,0,-1,0,1]     # x bleibt in diesem Fall gleich
ax.plot(x, z, color='b', linestyle='--', marker='^')

Für MATLAB-Erfahrene

Die (Grund-)Funktionalität der Matplotlib war ursprünglich eng an MATLAB und die dortigen Plotbefehle angelehnt. So gibt es neben den Befehlen, die wir oben verwendet haben, noch andere Funktionsaufrufe, die den MATLAB-Befehlen noch ähnlicher sind. Die Verwandtschaft sieht man z.B. sehr gut bei den "keyword arguments" wie color='r'. Wir haben hier stattdessen die "modernen" objektorientierten Plotbefehle verwendet, die in sich konsistenter und nachvollziehbarer sind als die "Original"-Plotbefehle.

Aufgabe 4.1

In dieser Aufgabe wollen wir die bisher gewonnenen Erkenntnisse zum Einlesen von Dateien und zum Plotten zusammenbringen und unsere "Sinus-Aufgabe" fortsetzen.

To Do

Zu erstellen ist ein Programm, welches auf der Basis der vorherigen Beispiele die Wertetabelle für eine Funktion (x,y) aus einer Datei einliest und die Funktionswerte grafisch darstellt. Dabei gelten im Detail folgende Vorgaben:

  1. Das Einlesen der Datei soll mit einer Funktion realisiert werden, die folgende Signatur aufweist (so nennt man die Aufrufdefinition auch):

    x, y = read_fct_values(filename)
    
  2. Die grafische Ausgabe der Funktionswert soll ebenfalls mit einer Funktion realisiert werden:

    plot_fct_values(x,y)
    
  3. Die grafische Darstellung soll folgende Merkmale haben:

    • Die Kurve soll in rot mit gepunkteter Linie dargestellt werden.

    • Die Achsen sollen beschriftet werden.

    • Es soll ein Grid hinterlegt werden.

    • Der Plot soll einen passenden Titel (Dateiname) erhalten.

  4. Das "Hauptprogramm" (der nicht eingerückte Programmabschnitt) soll:

    • eine Meldung über den Zweck des Programms ausgeben

    • den Dateinamen einlesen

    • die Datei einlesen durch Aufruf von read_fct_values()

    • die Funktionswerte ausgeben durch Aufruf von plot_fct_values()

Lösungshinweise:

  1. Programme mit mehreren Funktionen werden immer schrittweise Funktion-für-Funktion entwickelt und getestet, um potentielle Fehler leicht einkreisen zu können.

  2. Die o.a. Beispiele enthalten alle wesentlichen Programmanweisungen bereits.

  3. Gute Programme sind mit sinnvollen Kommentaren versehen.