Dieses Werk ist lizenziert unter einer
Creative Commons Namensnennung 4.0 International Lizenz.
Unter Programmieren versteht man ganz allgemein die Tätigkeit, einem Computer Anweisungen zu geben. Das Eintippen einer Rechenanweisung in einen Taschenrechner lässt sich also schon als Programmieren betrachten. Man erklärt dem Computer die Berechnung, die man gerne durchführen möchte, und der Computer erledigt die Rechenarbeit. Einfache Taschenrechner kennen jedoch nur wenige Befehle, meist nur Grundrechenarten. Aufwendigere kennen zum Beispiel Winkelfunktionen oder die Möglichkeit, Zahlen zu speichern und wieder abzurufen. Will man noch komplexere Berechnungen durchführen, ist man auf „echte“ Computer angewiesen.
Im Unterschied zum Taschenrechner mit seinen Tasten für jede Grundrechenart, muss dem Computer nun allerdings der Umstand, dass zum Beispiel die Wurzel einer Zahl berechnet werden soll, mithilfe einer Programmiersprache mitgeteilt werden. Dies könnte zum Beispiel der Befehl sqrt(x) sein, den es wohl – abhängig von der so genannten Syntax einer Sprache – in der einen oder anderen Form in jeder Programmiersprache gibt. Das hat den Vorteil, dass die Kenntnis einer Programmiersprache in der Regel dafür sorgt, dass auch andere Programmiersprachen leicht erlernt werden. Darüber hinaus steigern Programmierkenntnisse die allgemeine Problemlösungskompetenz, die Abstraktionsfähigkeit und die präzise Ausdrucksweise im wissenschaftlichen Kontext.
Die Vorlesung – und damit dieses Skriptum – beziehen sich auf die Programmiersprache Python. Dies hat mehrere Gründe: Python ( https://www.python.org ) ist eine universelle, vielseitig einsetzbare Programmiersprache, die
Anaconda (https://www.anaconda.com/) ist eine sogenannte Python-Distribution, die eine Vielzahl von Werkzeugen vereint, die für das Programmieren mit Python notwendig sind. Anaconda ist frei (d.h. kostenlos), sehr leicht zu installieren, und verfügbar für Windows, Mac-OS und Linux. Anaconda enthält mit über 150 mitgelieferten Modulen (packages) im Prinzip alles (und viel mehr), was zum Programmieren im Bereich der USW-Systemwissenschaften nötig ist.
Vor allem enthält Anaconda bereits auch eine sehr komfortable Ein- und Ausgabe-Umgebung (d.h. eine Entwicklungsumgebung) namens Jupyter Notebook , die einfach im gewohnten Browser läuft, wie er für das tägliche Surfen im Internet verwendet wird (Google-Chrome, Firefox, Internetexplorer ...). Das heißt, nach Installation von Anaconda sind im Prinzip keine zusätzlichen oder ungewohnten Programme mehr notwendig, um sofort mit Python zu arbeiten zu beginnen. Im Folgenden wird nun der Installationsvorgang für Anaconda mit Python 3.7. auf einem Windows-Computer beschrieben.
Eine weitere Windows-Installationsbeschreibung findet sich hier: https://docs.continuum.io/anaconda/install/windows
Für die Mac-OS-Installation siehe hier: https://docs.continuum.io/anaconda/install/mac-os
Für Linux siehe hier: https://docs.continuum.io/anaconda/install/linux
Nach erfolgreicher Installation sollten sie in Ihrem Windows Startmenü einen Menüpunkt Anaconda sehen, der seinerseits einen Menü - Unterpunkt Jupyter Notebook enthält.
Das Jupyter Notebook ist eine sehr komfortable Ein-und Ausgabe-Umgebung für die Programmiersprache Python , d.h. eine Entwicklungsumgebung, die im bereits auf ihrem Computer installierten Browser läuft. Alle in diesem Skriptum vorkommenden Beispiele wurden mit dem Jupyter Notebook geschrieben und werden auch in diesem angezeigt.
Klicken Sie , um das Jupyter Notebook zu starten, im Windows-Startmenü unter Anaconda auf den Menüpunkt Jupyter Notebook.
Es öffnet sich ein Fenster, das zuerst schwarz ist, und sich dann mit weißem Text füllt. Dieses Fenster darf nicht geschlossen werden, solange Sie mit dem Jupyter Notebook arbeiten möchten. Es handelt sich hierbei um eine Anzeige für den so genannten Kernel , also den Prozess, in dem die eigentlichen Berechnungen durchgeführt werden. Zusätzlich öffnet sich Ihr Browser, bzw. ein neuer Tab in Ihrem Browser.
Um ein neues Jupyter Notbook zu öffnen, klicken Sie im rechten Bereich dieses Browser-Tabs auf das Dropdown-Menü „New“ und wählen Sie „Python 3“ aus.
Jupyter-Notebooks bestehen aus Zellen. Es gibt drei Typen von Zellen:
Um eine In-Zelle auszuführen klickt man einfach in die gewünschte Zelle und drückt die Shift- und die Enter-Taste gleichzeitig. Es wird automatisch eine Out-Zelle produziert, in der das Ergebnis der In-Zelle steht.
Auch ohne jegliche Programmierkenntnisse kann man so schon Python benutzen, wenn auch nur als Taschenrechner:
7 * 7 - 7
((12 + 144 + 20 + 3 * 4**0.5) / 7) + 5 * 11
# Exponenten werden in Python als x**y angegeben. 4**0.5 bedeutet also 4 hoch 0.5.
# Der Text hinter einer Raute (#) wird vom Computer ignoriert und ist als Information für den Menschen gedacht.
Python kann aber natürlich viel mehr als ein Taschenrechner. In eine Zelle kann man nicht nur einzelne Rechnungen schreiben, sondern ganze Programme oder vollständige Simulationen. Versuchen wir eine ganz simple Simulation zu erstellen, um die Grundbefehle von Python zu lernen. Nehmen wir an, wir wollen die Populationsentwicklung in einem Froschteich simulieren. In allererster Näherung gehen wir davon aus, das sich jedes Jahr 3 neue Frösche im Teich ansiedeln.
Um Zahlen (wie z.B. die Anzahl der Frösche in einem Teich) zu speichern muss eine so genannte Variable angelegt werden. Das ist sozusagen der Name unter dem die Zahl gespeichert ist. Die Zuweisung von Variablenname zur Zahl passiert in Python mit dem = Zeichen. Wichtig ist, dass der Name links vom = Zeichen steht, die Zahl die dort gespeichert werden soll, rechts. Wir starten unsere Teichsimulation also indem wir die Anzahl der Frösche auf 0 setzen.
froschanzahl = 0
Um eine Variable zu ändern kann man einfach den aktuellen Wert mit dem neuen Wert überschreiben. Auch das geschieht mit dem = Zeichen.
# Jahr 1:
froschanzahl = 3
Um Variablen wieder anzuzeigen benutzt man den Befehl print
. Dieser Befehl schreibt den aktuellen Wert der abgefragten Variable in die Ausgabezeile. Schreiben wir also print(froschanzahl)
, sollte das das Ergebnis 3 bringen.
print(froschanzahl)
Das ist sehr hilfreich für unsere weiter Simulation, denn so können wir die aktuelle Zahl der Frösche benutzen, um die neue zu berechnen. Laut unserer Annahme ist die Zahl der Frösche in jedem Jahr um 3 höher als zuvor, mathematisch ausgedrückt also froschanzahl + 3
. Somit sieht unsere Froschsimulation so aus:
froschanzahl = 0
#Jahr 1
froschanzahl = froschanzahl + 3
#Jahr 2
froschanzahl = froschanzahl + 3
#Jahr 3
froschanzahl = froschanzahl + 3
#Jahr 4
froschanzahl = froschanzahl + 3
#Jahr 5
froschanzahl = froschanzahl + 3
#Um ein Ergebnis auszugeben benutzen wir den Befehl "print"
print(froschanzahl)
In dieser Version ist unsere Froschsimulation nicht nur sehr unspektakulär, sondern auch recht umständlich. Sie ist jedoch ein guter Ausgangpunkt für eine besser Simulation. Als ersten Schritt wäre es schön, wenn wir die Anzahl der Jahre, die simuliert werden sollen, einstellen könnten und nicht immer exakt 5 Jahre simulieren müssen. Dazu benötigen wir ein wichtiges Konzept: die For-Schleife.
For-Schleifen werden benutzt um einen Befehl mehrmals hintereinander ausführen zu lassen, in unserem Fall das Hinzuzählen von Fröschen. So würde unser Programm mit einer For-Schleife aussehen:
froschanzahl = 0
for it in range(5):
froschanzahl = froschanzahl + 3
print(froschanzahl)
Das ist deutlich kompakter, dadurch aber auch ein wenig komplizierter. Die erste Zeile ist exakt gleich, wir setzen froschanzahl
auf 0. In der nächsten Zeile leitet der Befehl for
die For-Schleife ein. Lesen könnte man diese Zeile als: Mache folgenden Befehl für jede ganze Zahl it
, die kleiner ist als 5, also ingesamt 5 mal (für 0,1,2,3,4). Nach dem Doppelpunkt kommt in der nächsten Zeile der Befehl, der ausgeführt werden soll. Zum Schluss lassen wir uns wieder das Ergebnis ausgeben.
Woher weiß Python jetzt aber, das wir den letzten Befehl (das print
) nicht auch 5 mal ausgeführt haben wollen? Das passiert mittels Einrückungen (tab-Taste). Befehle, die direkt untereinander stehen gehören für Python zusammen. Das macht Pythoncode automatisch gut lesbar und man benötigt keine Klammern.
Würden wir die Ausgabe wirklich gerne nach jedem Jahr, und nicht erst am Schluss haben, können wir den print-Befehl mit der Tabulatortaste einrücken. So gehört er zur For-Schleife:
froschanzahl = 0
for it in range(5):
froschanzahl = froschanzahl + 3
print(froschanzahl)
Ob Befehle innerhalb oder außerhalb einer For-Schleife stehen macht einen großen Unterschied und ist eine häufige Fehlerquelle. Würde man beispielsweise auch die Zeile froschanzahl = 0
in die For-Schleife einbauen, würde die Zahl der Frösche bei jedem Durchlauf der Schleife auf 0 gesetzt werden:
for it in range(5):
froschanzahl = 0
froschanzahl = froschanzahl + 3
print(froschanzahl)
Dennoch bietet das Verwenden einer For-Schleife einen riesigen Vorteil: Wir müssen nur mehr eine einzige Zahl ändern um die Zahl der simulierten Jahre zu verändern. Wollen wir beispielweise 10 Jahre simulieren:
froschanzahl = 0
for it in range(10):
froschanzahl = froschanzahl + 3
print(froschanzahl)
Zu einem schönen Stil beim Programmieren gehört es, solche Zahlen, die man öfter ändern möchte an den Anfang des Programms zu stellen. Wir definieren also eine neue Variable, damit wir die Zahl nicht mitten im Code ändern müssen, sondern schön übersichtlich am Anfang. Mit einem Kommentar wissen auch zukünftige Benutzer, was diese Variable genau macht.
simulationszeit = 10
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird
froschanzahl = 0
for it in range(simulationszeit):
froschanzahl = froschanzahl + 3
print(froschanzahl)
Mit diesem Programm können wir nun auch sehr lange Zeitbereiche simulieren:
simulationszeit = 1000
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird
froschanzahl = 0
for it in range(simulationszeit):
froschanzahl = froschanzahl + 3
print(froschanzahl)
In dieser Simulation ist das Endergebnis weniger spannend als die zeitliche Entwicklung. Es wäre also interessant die Froschanzahl zu jedem Zeitpunkt auszugeben. Am einfachsten verschieben wir den print Befehl in die For-Schleife:
simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird
froschanzahl = 0
for it in range(simulationszeit):
froschanzahl = froschanzahl + 3
print(froschanzahl), #ein Beistrich hinter einem Print-Befehl schreibt die Ergebnisse, statt untereinander, in eine Zeile
Auf diese Weise sieht man wie sich die Froschpopulation entwickelt, da wir jeden Wert einzeln ausgegeben haben. Der Nachteil an dieser Variante: Wirklich gespeichert haben wir immer nur ein Zahl. Am Ende der Simulation sehen wir zwar viele Zahlen, gespeichert (und somit zum Weiterarbeiten verfügbar) ist aber nur die letzte. Eine schönere Lösung ist das Verwenden von einer Liste.
Listen bestehen aus mehreren aufeinanderfolgenden Werten. Um eine Liste zu erstellen, fängt man am besten mit einer leeren Liste an und hängt dann immer wieder neue Elemente an. Auch Listen bekommen, so wie Variablen, einen eindeutigen Namen. Leere Listen erstellt man mit name=[]
und neu Einträge fügt man mit dem append-Befehl hinzu. Für unser Beispiel:
simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird
froschanzahl = 0
froschanzahl_liste = [] #leere liste wird erstellt
froschanzahl_liste.append(froschanzahl) #erster Eintrag (0 Frösche) wird angehängt
for it in range(simulationszeit):
froschanzahl = froschanzahl + 3
#am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die liste angehängt
froschanzahl_liste.append(froschanzahl)
print(froschanzahl_liste)
Nun kann man schon grob erkennen was passiert, viel vorstellen kann man sich aber noch nicht. Schön wäre eine grafische Darstellung. Und das ist in Python zum Glück sehr einfach.
Der Befehl zum Erstellen von Plots lautet "plot". Dieser Befehl ist jedoch nicht standardmäßg in jedem Pythonprogramm vorhanden, sondern muss in der Regel erst aus einem sogenannten Paket importiert werden. Pakete sind sozusagen Sammlungen von Befehlen.
Der plot Befehl selbst funktioniert dann ganz einfach: In Klammer schreibt man einfach die Liste von Zahlen, die man darstellen möchte.
import matplotlib.pyplot as plt #wir importieren das Modul matplotlib.pyplot und geben ihm die Abkürzung plt
# die nächste Zeile bewirkt, dass die Grafiken direkt in der Zelle angezeigt werden
%matplotlib inline
simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die der Froschteich simuliert wird
froschanzahl = 0
froschanzahl_liste = [] #leere Liste wird erstellt
froschanzahl_liste.append(froschanzahl) #erster Eintrag (0 Frösche) wird angehängt
for it in range(simulationszeit):
froschanzahl = froschanzahl + 3
#am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die Liste angehängt
froschanzahl_liste.append(froschanzahl)
plt.plot(froschanzahl_liste)
Eine Grafik ist schon viel anschaulicher als eine Liste von Zahlen. Natürlich kann man diese Grafik noch deutlich verbessern. Bei Fröschen würde es sich beispielsweise anbieten, die Linie grün darzustellen. Solche zusätzlichen Optionen kann man im Plot-Befehl einfach nach einem Beistrich anfügen:
plt.plot(froschanzahl_liste, color = 'green')
Man beachte, dass man die Farbe selbst unter Anführungsstrichen schreiben muss, da das Programm sonst nach einer Variable suchen würde, die green heißt.
Eine weitere mögliche Verbesserung wäre eine Beschriftung der Achsen:
plt.plot(froschanzahl_liste, color = 'green')
plt.xlabel('Zeit (Jahre)')
plt.ylabel('Frösche')
Durch Variablen können wir Werte einem Namen zuordnen. Mit
varname = 42
weisen wir der Variable varname
den Wert 42 zu und überschreiben den aktuell dort gespeichert Wert, wenn dort schon etwas gespeichert ist.
Mit
varname = varname + 1
erhöhen wir den Wert, der unter varname
gespeichert ist um 1.
Den aktuellen Wert der Variable varname
kann mit
print(varname)
angezeigt werden.
Mit For-Schleifen ist es möglich gleiche oder ähnliche Befehl oftmals hintereinander auszuführen. Um einen Befehl also beispielsweise 100 mal abarbeiten zu lassen, verwenden wir:
for it in range(100):
befehl
Man beachte die Einrückung des Befehls, die zeigt, das sich der Befehl innerhalb der Schleife befindet.
In Listen können mehrere Werte unter nur einem Namen abgespeichert werden. Leere Listen erstellt man mit
listenname = []
Einen neuen Eintrag (z.B. neueselement
) zur Liste hinzufügen kann man dann mit
listenname.append(neueselement)
Um innerhalb eines Jupyter-Notebooks den Plotbefehl benutzen zu können, verwenden wir die Zeilen
import matplotlib.pyplot as plt
%matplotlib inline
am Anfang des Programms. Danach können wir mit dem Befehl
plt.plot(listenname)
die Liste mit dem Namen listenname
grafisch darstellen. Zusatzoptionen wie Farbe können nach einem Beistrich übergeben werden:
plt.plot(listenname, color='green')
In dieser Einheit möchten wir uns genauer mit verschiedenen Möglichkeiten der Populationsentwicklung auseinandersetzen. Mit dem Beispiel des Froschteiches haben wir bereits eine sehr einfache Form der Populationsentwicklung kennengelernt, so genanntes:
Wir ziehen nun als weiteres Beispiel die Vermehrung von Kaninchen heran.
import matplotlib.pyplot as plt #wir importieren das paket matplotlib.pyplot und geben ihm die abkürzung plt
#die nächste Zeile bewirkt, dass die Grafiken direkt in der Zelle angezeigt werden
%matplotlib inline
simulationszeit = 30
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden
kaninchenanzahl = 0
kaninchenanzahl_liste = [] #leere liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (0 Kaninchen) wird angehängt
for it in range(simulationszeit):
kaninchenanzahl = kaninchenanzahl + 1
#am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
kaninchenanzahl_liste.append(kaninchenanzahl)
plt.plot(kaninchenanzahl_liste)
Diese Form von Wachstum war für einen Froschteich zwar eine gute Näherung, wenn wir annehmen, dass die Frösche dort einfach zuwandern. Unsere Kaninchenpopulation vermehrt sich aber durch Fortpflanzung. Das heißt, je mehr Kaninchen es gibt, um so schneller kommen neue hinzu.
Dies führt zu sogenanntem exponentiellen Wachstum.
Von exponentellem Wachstum spricht man, wenn das Wachstum einer Größe proportional zu der Größe selbst ist. In Python ausgedrückt also:
kaninchen = kaninchen + x * kaninchen
wobei x ein Wachstumsparameter ist, den wir beliebig wählen können. Möchten wir zum Beispiel, dass sich die Anzahl der Kanichen in jedem Zeitschritt verdoppelt, können wir als Wachstumsparameter 1 wählen:
kaninchen = kaninchen + 1 * kaninchen
was man auch als kaninchen = 2 * kaninchen
schreiben könnte. Möchten wir jeden Zeitschritt ein Wachstum von 10% wählen wir x als 0.1:
kaninchen = kaninchen + 0.1 * kaninchen
Machen wir dazu eine Simulation:
import matplotlib.pyplot as plt
%matplotlib inline
simulationszeit = 10
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden
kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere Liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
for it in range(simulationszeit):
kaninchenanzahl = kaninchenanzahl + kaninchenanzahl * 0.1 #neue kaninchenanzahl wird berechnet
#am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
kaninchenanzahl_liste.append(kaninchenanzahl)
plt.plot(kaninchenanzahl_liste)
Exponentielles Wachstum ist eine recht gute Näherung an das echte Verhalten einer Population. Ein Problem entsteht aber, wenn wir sehr lange Zeiträume betrachten. Setzen wir die Simulationszeit einmal auf 150 und sehen was passiert:
import matplotlib.pyplot as plt
%matplotlib inline
simulationszeit = 150
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden
kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere Liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
for it in range(simulationszeit):
kaninchenanzahl = kaninchenanzahl + kaninchenanzahl * 0.1 #neue kaninchenanzahl wird berechnet
#am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
kaninchenanzahl_liste.append(kaninchenanzahl)
plt.plot(kaninchenanzahl_liste)
Das Wachstum wird immer schneller und die Kaninchenpopulation explodiert. Das ist ein unrealistisches Verhalten, denn jedes Ökosystem hat eine gewisse Kapazitätsgrenze, also eine maximale Anzahl an Individuen, die im System leben können (aufgrund von Nahrung- oder Unterschlupfangebot). Auch das sollten wir unser Programm einbauen. Nehmen wir an es kann in unserem System nie mehr als 1000 Kaninchen geben. Wenn diese Zahl überschritten ist, sollen keine neuen Kaninchen dazukommen. Um das ins Programm einzubauen, brauchen wir eine neue Struktur, die so genannte If-Abfrage.
If-Abfragen
sind "Wenn-Dann-Fragen", mit denen wir Befehle an Bedingungen knüpfen können. Sie haben stets folgenden Aufbau:
if BEDINGUNG:
BEFEHL
BEFEHL
meint hierbei irgendeine Anweisung an Python, also zum Beispiel das Ändern einer Variable, einen Printbefehl oder etwas Ähnliches. Eine If-Abfrage
kann auch mehrere Befehle enthalten.
Eine BEDINGUNG
kann alles sein, was entweder wahr
oder falsch
sein kann. Oftmal benutzt man zur Definition einer Bedingung eine Gleichung. Man überprüft also etwa, ob es wahr
ist, dass eine Variable x
gleich groß ist wie eine Variable y
. Noch häufiger werden Ungleichungen benutzt. Man überprüft also, ob es wahr
ist, dass eine Variable x
größer (oder kleiner) als eine Variable y
ist.
Im folgenden Beispiel betrachten wir eine If-Abfrage
in einer For-Schleife. Die For-Schleife erhöht die Zahl der Kaninchen fortlaufend um 1, die If-Abfrage
führt einen Printbefehl aus, allerdings nur dann, wenn die Zahl der Hasen größer ist als 100.
kaninchen = 0 # Variablen-Definition, d.h. Zuweisung des Wertes 0 and die Variable hasen
for it in range(105):
kaninchen = kaninchen + 1
if kaninchen == 100: # Vergleich des Wertes der Variable hasen mit dem Wert 100
print("Es gibt jetzt EXAKT 100 Kaninchen!")
Dieses Beispiel demonstriert den wichtigen Unterschied zwischen dem einfachen = Zeichen und dem doppelten = Zeichen noch einmal: Das einfache = wird benutzt um einer Variable (kaninchen
) einen Wert zu zuweisen. Das doppelte = Zeichen wird dagegen für einen Vergleich verwendet.
Es können auch mehrere Befehle unter eine If-Abfrage
gestellt werden. Ähnlich wie bei der For-Schleife wird hier durch Einrücken signalisiert, ob ein Befehl Teil der If-Abfrage
ist, oder nicht:
kaninchen = 95
for it in range(10):
kaninchen = kaninchen + 1
if kaninchen > 100:
print("Es gibt jetzt mehr als 100 Kaninchen!")
print("Die exakte Zahl der Kaninchen ist ")
print(kaninchen)
Wenn wir die beiden zuletzt stehenden Befehle nicht einrücken, dann sind sie nicht mehr Teil der If-Abfrage
und werden in jedem Fall ausgeführt, egal wie viele Kaninchen es gibt:
kaninchen = 95
for it in range(10):
kaninchen = kaninchen + 1
if kaninchen > 100:
print("Es gibt jetzt mehr als 100 Kaninchen!")
print("Die exakte Zahl der Kaninchen ist ")
print(kaninchen)
Wenn wir die beiden Befehle noch einmal nach außen verschieben, stehen sie auch außerhalb der For-Schleife und werden somit nur einmal ausgeführt, und zwar nachdem die For-Schleife abgeschlossen ist:
kaninchen = 95
for it in range(10):
kaninchen = kaninchen + 1
if kaninchen > 100:
print("Es gibt jetzt mehr als 100 Kaninchen!")
print("Die exakte Zahl der Kaninchen ist ")
print(kaninchen)
Nun, da wird If-Abfragen verstehen können wir die Beschränkung auf 1000 Individuen in unser Programm einbauen: Nur wenn es noch weniger als 1000 Kaninchen gibt soll die Population weiter anwachsen:
import matplotlib.pyplot as plt
%matplotlib inline
simulationszeit = 100
#simulationszeit: Die Zeit in Jahren, die die Kaninchen simuliert werden
kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere Liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
for it in range(simulationszeit):
if kaninchenanzahl < 1000: #NUR WENN es weniger als 1000 Kaninchen gibt
kaninchenanzahl = kaninchenanzahl + kaninchenanzahl * 0.1 #neue kaninchenanzahl wird berechnet
#am ende jedes schleifendurchgangs wird die aktuelle zahl an die liste angehängt
kaninchenanzahl_liste.append(kaninchenanzahl)
plt.plot(kaninchenanzahl_liste)
Neben dem exponentiellen Wachstum gibt es aber noch andere Methoden um das Wachstum einer Population zu beschreiben. Mann kann auch von der Idee ausgehen, dass die momentane Population einerseits von der Population aus dem letzten Zeitschritt, andererseits aber auch von der Population aus dem vorletzten Zeitschritt abhängt.
Dies führt zur sogenannten
In diesem Model errechnet sich die Anzahl der Kaninchen im aktuellen Jahr aus der Anzahl der Kaninchen aus dem Vorjahr plus der Anzahl der Kaninchen aus dem vorletzen Jahr. Um so etwas zu programmieren, müssen wir aber zuerst mehr über Listen in Python lernen:
Bislang wissen wir, wie man leere Listen erstellt und Elemente zu Listen hinzufügt. Man kann aber natürlich auch spezifische Listeneinträge abfragen. Möchte man beispielsweise das siebente Element der Liste kaninchenanzahl_liste
wissen, kann man es mit kaninchenanzahl_liste[6]
abrufen.
Warum nicht 7?
In Programmiersprachen ist es üblich, das erste Element einer Liste stets mit der Nummer 0 zu speichern, in diesem Fall also als kaninchenanzahl_liste[0]
. Das ist anfangs zwar verwirrend, für spätere Anwendungen jedoch eine recht sinnvolle Konvention.
kaninchenanzahl_liste[0]
For-Schleifen haben wir schon kennen gelernt, eine wichtige Eigenschaft haben wir aber bislang ignoriert. Die Zahl it, die sozusagen mitzählt wie oft ein Befehl schon ausgeführt worden ist, darf auch innerhalb eines Befehls benutzt werden:
for it in range(5):
print(it)
Wenn wir diese beiden Kenntnisse nun kombinieren, haben wir eine Möglichkeit gefunden, wie wir innerhalb einer Schleife auf vorherige Listeneinträge zugreifen können. Das ist genau das, was wir für unsere Fibonacci-Folge benötigen.
Eine weitere wichtige Eigenschaft von for-Schleifen ist, dass it nicht unbedingt bei 0 anfangen muss. Möchten wir beispielsweise erst ab Jahr 2 simulieren, so können wir die Schleife auch erst bei Jahr zwei beginnen lassen:
for it in range(2,5):
print(it)
import matplotlib.pyplot as plt
%matplotlib inline
simulationszeit = 10
# Simulationszeit: Die Zeit in Jahren, die die Kaninchenfortpflanzung simuliert wird
kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
kaninchenanzahl = 1
kaninchenanzahl_liste.append(kaninchenanzahl)#zweiter Eintrag (1 Kaninchen) wird angehängt
#wir benötigen hier zwei Kaninchen, damit sie sich auch vermehren können
for it in range(2, simulationszeit):
letztesjahr = kaninchenanzahl_liste[it - 1] # Kaninchenanzahl vom letzten jahr wird berechnet
vorletztesjahr = kaninchenanzahl_liste[it - 2] # Kaninchenanzahl vom vorletzten jahr wird berechnet
kaninchenanzahl = letztesjahr + vorletztesjahr # neue Kaninchenanzahl wird berechnet
# am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die Liste angehängt
kaninchenanzahl_liste.append(kaninchenanzahl)
plt.plot(kaninchenanzahl_liste)
Natürlich können wir auch hier eine Beschränkung des Wachstums durch eine If-Abfrage einbauen, ganz gleich wie beim exponentiellen Wachstum:
import matplotlib.pyplot as plt
%matplotlib inline
simulationszeit = 20
# Simulationszeit: Die Zeit in Jahren, die die Kaninchenfortpflanzung simuliert wird
kaninchenanzahl = 1
kaninchenanzahl_liste = [] #leere liste wird erstellt
kaninchenanzahl_liste.append(kaninchenanzahl) #erster Eintrag (1 Kaninchen) wird angehängt
kaninchenanzahl = 1
kaninchenanzahl_liste.append(kaninchenanzahl)#zweiter Eintrag (1 Kaninchen) wird angehängt
#wir benötigen hier zwei Kaninchen, damit sie sich auch vermehren können
for it in range(2, simulationszeit):
if kaninchenanzahl < 1000: #NUR WENN es weniger als 1000 Kaninchen gibt
letztesjahr = kaninchenanzahl_liste[it - 1] # Kaninchenanzahl vom letzten jahr wird berechnet
vorletztesjahr = kaninchenanzahl_liste[it - 2] # Kaninchenanzahl vom vorletzten jahr wird berechnet
kaninchenanzahl = letztesjahr + vorletztesjahr # neue Kaninchenanzahl wird berechnet
# am Ende jedes Schleifendurchgangs wird die aktuelle Zahl an die Liste angehängt
kaninchenanzahl_liste.append(kaninchenanzahl)
plt.plot(kaninchenanzahl_liste)
Was aber, wenn wir nun sozusagen in den interessanten Teil des Wachstums (zum Beispiel den Knick) hineinzoomen möchten. Dazu wird es notwendig sein, nicht nur einzelne Elemente der Liste auszuwählen, sondern ganze Stücke. Das funktioniert so:
Wir können aber auch mehrere Elemente auf einmal auswählen. Dazu schreiben wir in eckige Klammern das erste Element das wir haben möchten, dann einen Doppelpunkt, und dann das erste Element das wir nicht mehr haben wollen. Das Element mit dem Index 10 und das mit dem Index 11 bekommen wir also mit
print(kaninchenanzahl_liste[14:19])
Wird vor dem Doppelpunkt keine Zahl geschrieben, wird beim Element mit dem Index Null begonnen. Wird nach dem Doppelpunkt keine Zahl geschrieben, wird bis zum letzen Element alles ausgewählt.
print(kaninchenanzahl_liste[:3])
print(kaninchenanzahl_liste[17:])
Python akzeptiert auch negative Zahlen als Index. Der Index -1 bezeichnet dabei das letzte Element einer Liste, der Index -2 das vorletzte und so weiter. Das ist besonders praktisch, wenn man z.b. die letzten 5 Einträge der Liste auswählen möchte:
print(kaninchenanzahl_liste[-5:])
Das Auswählen von Listenteilen funktioniert natürlich nicht nur in Kombination mit dem print-Befehl, sondern auch mit allen anderen Befehlen, zum Beispiel plot. Wir können also nun auf den relevanten Teil unserer Grafik zoomen:
# Listen werden geplottet
plt.plot(kaninchenanzahl_liste[14:19])
Bei linearem Wachstum kommt zu einer Population in jedem Zeitschritt eine konstante Anzahl hinzu:
kaninchen = kaninchen + x
wobei x angibt wie viele Individuen hinzukommen, also wie schnell die Population wächst.
Bei exponentiellem Wachstum kommt zur Population kein konstanter Wert hinzu, sondern ein Wert, der direkt von der aktuellen Population abhängt. Die genaue Abhängigkeit von Wachstum und momentaner Population gibt der so genannte Wachstumsparameter an:
kaninchen = kaninchen + kaninchen * wachstumsparameter
If-Abfragen
können benutzt werden, um Anweisungen und Befehle nur unter gewissen Bedingungen ausführen zu lassen. Sie haben den Aufbau
if BEDINGUNG:
BEFEHL
Die Bedingungen sind meist Ungleichungen (hasen >= 100
) oder Gleichungen (hasen == 100
), die entweder wahr oder falsch sein können. Wichtig ist, bei einem Vergleich das doppelte =-Zeichen zu verwenden.
Die Laufvariable (im Beispiel it
) kann innerhalb der Schleife benutzt werden. So liefert die Schleife
for it in range(3):
print(it)
das Ergebnis
0
1
2
Um die einzelnen Elemente einer Liste zu verwenden oder auszugeben, benutzt man eckige Klammern:
listenname[2]
liefert zum Beispiel das dritte(!) Element. Achtung: Das erste Element in der Liste hat stets den Index 0, das zweite den Index 1, und so weiter.
Wenn wir mehrere Elemente einer Liste auswählen möchten, können wir
listenname[a:b]
verwenden, wobei a
das erste Element ist, das wir haben möchten und b
das erste, das nicht mehr Teil der Auswahl sein soll. Wenn wir a
frei lassen, wird beim Element mit Index 0 begonnen, wenn wir b frei lassen wird bis zum letzten Element ausgewählt. Der Index -1 meint immer das letze Element einer Liste, -2 das vorletzte und so weiter.
In diesem Kapitel werden wir unser Wissen über For-Schleifen und If-Abfragen weiter ausbauen, um die Schadstoffstatistik einer Firma auszuwerten. Die Schadstoffwerte sind in einer Liste gespeichert. In einem ersten Schritt sollen wir einfach eine Warnung ausgeben, wenn der Wert den Grenzwert von 100 Einheiten überschreitet. Dazu reicht eine If-Abfrage.
Da wir ein Programm haben wollen, das für Listen von jeder Länge funktioniert, können wir bei der Länge der For-Schleife nicht fix 10 schreiben, sondern lassen uns die Länge der Liste mit len
ausrechnen.
werte = [89,96,125,88,110,112,99,84,50,130]
for it in range(len(werte)): #Schleife hat die Länge der Liste
if werte[it] > 100:
print("Zu hohe Schadstoffwerte am Tag",it,"!")
Wenn unser Programm aber nur Alarm schlagen soll, wenn der Wert an zwei Tagen hintereinander überschritten wird, dann brauchen wir eine weitere If-Abfrage. Außerdem müssen wir den Tag 0 dann ausnehmen, da es ja keinen Tag -1 gibt.
werte = [89,96,125,88,110,112,99,84,50,130]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if werte[it] > 100:
if werte[it-1] > 100:
print("Zu hohe Schadstoffwerte am Tag" ,it,"!")
Man kann sich vorstellen, dass so eine Struktur relativ schnell unübersichtlich wird, vor allem wenn man viele Bedingungen hat. Deswegen kann man solche If-Abfragen auf verkürzt schreiben: Man kann die einzelnen Bedingungen mit einem logischen und (and) und einem logischen oder (or) verknüpfen:
werte = [89,96,125,88,110,112,99,84,50,130]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if werte[it] > 100 and werte[it-1] > 100:
print("Zu hohe Schadstoffwerte am Tag",it,"!")
Damit lassen sich auch kompliziertere Verknüpfungen erstellen, die man über runde Klammern verbinden kann. Wir könnten also zum Beispiel verlangen, dass zusätzlich eine Warnung geschrieben wird, wenn der Wert größer als 120 ist. Ist der Wert am Tag davor aber kleiner als 50, gibt es keinen Alarm. Das würde so aussehen:
werte = [89,96,125,88,110,112,99,84,50,130]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag",it,"!")
Als nächstes wollen wir die Tage, die einen Alarm ausgelöst haben, grafisch darstellen. Wir legen dazu eine Liste an, in die wir jedes mal die Zahl 1 schreiben, wenn der Alarm ausgelöst wurde, und 0 wenn er nicht ausgelöst wurde. Der erste Teil ist einfach, wir können jedes mal direkt nach dem Printbefehl eine 1 in die Liste schreiben. Aber wie identifizieren wir die Tage ohne Alarm? Wir könnten noch einmal eine ähnliche If-Abfrage bauen und einfach "umdrehen". Aber reicht es, einfach die Größer- und Kleiner-Zeichen umzudrehen? Was wenn wir genau den Grenzwert treffen? Und wie funktioniert das mit dem or? In jedem Fall wäre so eine Lösung sehr kompliziert und fehleranfällig. Deswegen gibt es eine besser Lösung: Jede if-Abfrage kann auch mit einem else ausgestattet werden, also einem Befehl, der gemacht werden soll, wenn die If-Abfrage nicht erfüllt ist. Vom Einrücken her ist dieses else auf gleicher Höhe wie das if, zu dem es gehört:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,125,88,110,112,99,84,50,130]
protokoll=[]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag",it,"!")
protokoll.append(1)
else:
protokoll.append(0)
plt.plot(protokoll)
Hierbei sind wir aber von einer perfekten Datenlage ausgegangen. Echte Daten sind selten so vollständig. Man kann davon ausgehen, dass an einigen Tagen keine Messung durchgeführt wurde. Solche Tage würden dann einfach als 0 in unserer Liste auftauchen, und nicht einfach nur selbst keinen Alarm auslösen, sondern auch Auswirkungen auf den Tag danach haben:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,125,88,110,0,112,99,84,50,0,130]
protokoll=[]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag" ,it, "!")
protokoll.append(1)
else:
protokoll.append(0)
plt.plot(protokoll)
Was könnten wir in so einem Fall machen? Eine Möglichkeit ist zu interpolieren, also zu versuchen die fehlenden Daten anhand der existierenden Daten abzuschätzen. Das Einfachste wäre anzunehmen, dass die fehlenden Schadstoffwerte immer genau der Mittelwert aus dem Wert vom Vortag und dem Wert vom Tag danach ist. Bereinigen wir unsere Liste auf diese Art und plotten wir die entstehenden Listen:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,125,88,110,0,112,99,84,50,0,130]
werte_neu=[]
for it in range(len(werte)):
if werte[it] == 0:
mittelwert = (werte[it-1] + werte[it+1]) / 2
werte_neu.append(mittelwert)
else:
werte_neu.append(werte[it])
plt.plot(werte)
plt.plot(werte_neu)
Es gibt natürlich weiter Möglichkeiten zu interpolieren. Man könnte zum Beispiel Ableitungen betrachten, oder mehr Werte miteinbeziehen. Fürs erste bleiben wir aber bei dieser Methode und benutzen sie für unsere Auswertung:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,125,88,110,0,112,99,84,50,0,130]
werte_neu=[]
for it in range(len(werte)):
if werte[it] == 0:
mittelwert = (werte[it-1] + werte[it+1]) / 2
werte_neu.append(mittelwert)
else:
werte_neu.append(werte[it])
werte = werte_neu
protokoll=[]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag",it,"!")
protokoll.append(1)
else:
protokoll.append(0)
plt.plot(protokoll)
Mit Hilfe dieser Interpolation können wir nun also auch mit Datensätzen arbeiten, die mit 0en aufgefüllt wurden. Echte Datensätz sind aber meist noch schwieriger zu handhaben. Zum Beispiel können wir auf Daten treffen, die nicht einmal eine Zahl sind, sondern einen ganz anderen Datentyp haben, zum Beispiel Zeichenketten. Das wäre noch schlimmer für unser Programm, denn es führt nicht zu falschen Ergebnissen, sondern zu einer Fehlermeldung und somit einem Absturz, wie wir hier sehen:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,"keine Messung",125,88,110,0,112,"44",99,84,50,0,130,[99,88],125,"🐇",120]
werte_neu=[]
for it in range(len(werte)):
if werte[it] == 0:
mittelwert = (werte[it-1] + werte[it+1]) / 2
werte_neu.append(mittelwert)
else:
werte_neu.append(werte[it])
werte = werte_neu
protokoll=[]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag",it, "!")
protokoll.append(1)
else:
protokoll.append(0)
plt.plot(protokoll)
Das Problem hier ist, dass man eine Zeichenkette nicht mit einer Zahl vergleichen kann und es zu einer Fehlermeldung kommt. Wir müssten die Daten also vor dem Bereinigen noch einmal "vorbereinigen". Leider können wir damit nicht ausschließen, dass es noch zu weiteren Fehlermeldungen kommt. Dennoch hätten wir gerne ein Programm, das nicht abstürzt, auch wenn es einmal zu einem Fehler kommt. Hier gibts es einen guten Ausweg: Wir versuchen unsere Rechenoperation durchzuführen, aber wenn irgendwo ein Fehler auftritt, müssen wir den Wert aus der Datenbank mit dem interpolierten Wert ersetzen.
Dafür gibt es in Python try und except. Zuerst wird versucht den Teil des Codes, der unter try steht auszuführen. Wenn das aber zu einer Fehlermeldung führt, wird das gemacht, was unter except steht. Für unseren Fall würde das so aussehen:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,"keine Messung",125,88,110,0,112,"44",99,84,50,0,130,[99,88],125,"🐇",120]
protokoll=[]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
try: #Dieser Codeblock wird versucht
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag" ,it, "!")
protokoll.append(1)
else:
protokoll.append(0)
except: #Wenn der obere Block einen Fehler verursacht, wird dieser Block ausgeführt
werte[it] = (werte[it-1] + werte[it + 1]) / 2
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag" ,it, "!")
protokoll.append(1)
else:
protokoll.append(0)
plt.plot(protokoll)
Auf diese Art ersparen wir uns das Vorbereinigen der Liste. Aber Achtung, die 0-Werte lösen hier keinen Fehler aus und werden somit auch nicht interpoliert. Wir könnten zwar die 0-Werte vorher gesondert ausfiltern, es geht aber auch in der gleichen try-except Struktur, die wir schon haben. Dazu ist es jedoch nötig, eine eigene Fehlermeldung zu definieren. Das funktioniert in Python mit raise Exception(). Dieser Befehl bricht das Programm ab und gibt eine Fehlermeldung aus. Innerhalb von unserem try-except führt es aber nur dazu, dass wir in den except-Block springen.
Bauen wir also unseren eigenen "Diese Zahl darf nicht 0 sein"-Fehler ein:
import matplotlib.pyplot as plt
%matplotlib inline
werte = [89,96,"keine Messung",125,88,110,0,112,"44",99,84,50,0,130,[99,88],125,"🐇",120]
protokoll=[]
for it in range(1,len(werte)): #Schleife hat die Länge der Liste
try: #Dieser Codeblock wird versucht
if werte[it] == 0:
raise Exception("Diese Zahl darf nicht 0 sein!")
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag" ,it, "!")
protokoll.append(1)
else:
protokoll.append(0)
except: #Wenn der obere Block einen Fehler verursacht, wird dieser Block ausgeführt
werte[it] = (werte[it-1] + werte[it + 1]) / 2
if (werte[it] > 100 and werte[it-1] > 100) or (werte[it] > 120 and werte[it-1] > 50):
print("Zu hohe Schadstoffwerte am Tag" ,it, "!")
protokoll.append(1)
else:
protokoll.append(0)
plt.plot(protokoll)
Achtung: Das Arbeiten mit try und except führt zwar zu einfachen und eleganten Lösungen, ist aber relativ gefährlich: Auch wenn man Programmierfehler macht, werden sie durch except einfach abgefangen und man sieht nicht mehr gut was passiert ist. Deswegen sollte man besonders aufpassen, wenn man mit try und except arbeitet. Dazu ist es wichtig, dass wir uns genauer ansehen, wie das mit Fehlern in Python eigentlich funktioniert:
testwerte = [99, 120, 0, 101, "k.A.", 140]
for it in range(len(testwerte)):
if testwerte[it] > 100:
print("Alarm!")
Hier wird ein Fehler ausgelöst, wenn wir zum Eintrag "k.A." kommen. Dabei handelt es sich um eine Zeichenkette, die wir also nicht mit der Zahl 100 vergleichen können. Es handelt sich also um den falschen Datentyp, deswegen heißt der Fehler "TypeError". Auch hier können wir unseren eigenen Fehlertyp einbauen:
testwerte = [99, 120, 0, 101, "k.A.", 140]
for it in range(len(testwerte)):
if testwerte[it] == 0:
raise Exception("Dieser Wert darf nicht 0 sein!")
if testwerte[it] > 100:
print("Alarm!")
Nun bekommen wir unsere individualisierte Fehlermeldung. Bauen wir nun try-except ein. Der Einfachheit halber werden wir als Alternative einfach nur Text ausgeben, der sagt, dass ein Eintrag übersprungen wurde.
testwerte = [99, 120, 0, 101, "k.A.", 140]
for it in range(len(testwerte)):
try:
if testwerte[it] == 0:
raise Exception("Dieser Wert darf nicht 0 sein!")
if testwerte[it] > 100:
print("Alarm!")
except:
print("Ein Eintrag wurde übersprungen!")
Dieses Programm ist jetzt aber recht gefährlich, denn wir sehen die Fehlermeldungen nicht mehr. Sollten wir also einen Fehler beim Programmieren machen, wird dieser genauso rausgefiltert:
testwerte = [99, 120, 0, 101, "k.A.", 140]
for it in range(len(testwerte)):
try:
if testwerte[it] == 0:
raise Exception("Dieser Wert darf nicht 0 sein!")
if testwerte > 100: #ACHTUNG: Hier ist jetzt ein Fehler eingebaut: Wir haben das [it] entfernt.
print("Alarm!")
except:
print("Ein Eintrag wurde übersprungen!")
Um so etwas zu verhindern, oder zumindest die Chance, dass es unbemerkt passiert, zu verringern, empfiehlt es sich die Fehlermeldungen, die entstehen, zumindest auszugeben. Das Programm wird nicht unterbrochen, man sieht aber trotzdem was passiert ist. Obwohl wir die Fehlermeldung also überspringen, haben wir eien Teil der Vorteile eine Fehlermeldung.
Dazu müssen wir unsere try-except Struktur ein wenig ausbauen. Wir möchten die genaue Fehlermeldung ausgeben. Dazu benutzt man except Exception as errormsg
und kann daraufhin den genauen Fehlertext, der in der Variable errormsg
gespeichert wurde, verwenden.
testwerte = [99, 120, 0, 101, "k.A.", 140]
for it in range(len(testwerte)):
try:
if testwerte[it] == 0:
raise Exception("Dieser Wert darf nicht 0 sein!")
if testwerte[it] > 100:
print("Alarm!")
except Exception as errormsg:
print("Ein Eintrag wurde übersprungen! Der Fehler war:")
print(errormsg)
Um das eigentliche Protokoll vom Fehlerprotokoll zu trennen, können wir eine eigene Liste dafür anfangen, die wir nur bei Bedarf anschauen können:
testwerte = [99, 120, 0, 101, "k.A.", 140]
fehlerlog=[]
for it in range(len(testwerte)):
print("Tag",it)
try:
if testwerte[it] == 0:
raise Exception("Dieser Wert darf nicht 0 sein!")
if testwerte[it] > 100:
print("Alarm!")
else:
print("Kein Alarm!")
except Exception as errormsg:
print("Fehler!")
fehlerlog.append(it)
fehlerlog.append("Ein Eintrag wurde übersprungen! Der Fehler war:")
fehlerlog.append(errormsg)
else: #auch das except kann ein else haben, das ausgeführt wird, wenn das except nicht ausgelöst wurde
fehlerlog.append(it)
fehlerlog.append("Fehlerfrei!")
print("Fehlerprotokoll:")
print(fehlerlog)
Auf diese Art können wir relativ gut mit Fehlern umgehen. Try-except bietet jedoch noch viel mehr Möglichkeiten: Wir könnten die Art der Fehler unterscheiden und je nach Fehlertyp andere Lösungsmöglichkeiten finden. Generell muss man beim Benutzen von try und except aber vorsichtig sein: Meist ist eine Fehlermeldung ein Problem, dass man beheben sollte, indem man am Programm so lange etwas ändert, bis eben keine Fehlermeldungen mehr entstehen. Dennoch gibt es Situationen in denen (potentielle) Fehlermeldungen unvermeidbar sind, zum Beispiel wenn Daten von außen eingelesen werden und man noch nicht wissen kann, was das für Daten sein werden. Hier ist dann sinnvoll, mit try und except vorzubeugen.
Die If-Else-Abfrage ist eine Erweiterung der If-Abfrage. Sie erweitert diese Struktur um zusätzliche Befehle, die ausgeführt werden sollen, wenn die Bedingung nicht wahr ist. Sie hat den Aufbau:
if BEDINGUNG:
BEFEHL WENN WAHR
else:
BEFEHL WENN FALSCH
Mit and und or kann man mehrere Bedingungen miteinander verknüpfen, entweder so, dass alle Bedingungen erfüllt sein müssen, oder so, dass nur eine erfüllt werden muss.
Mittels try
und except
ist es möglich ein Programm trotz Fehlermeldung weiterlaufen zu lassen. Tritt im Try-Block ein Fehler auf, springt das Programm in den Except-block. Damit wir aber totzdem noch merken, dass es zu einem Fehler kommt, ist es sinnvoll die Fehlermeldung zumindest mitzuprotokollieren. Dazu verwendet man except Exception as errormsg:
und hat dann Zugriff auf die Fehlermeldung unter dem Variablennamen errormsg
.
Wir haben in einem vorherigen Kapitel dieses Skriptums bereits die Entwicklung einer Tierpopulationen betrachtet. Dies war aber noch stark vereinfach, denn meist hat das Wachstum eine Abhängigkeit von anderen Größen, zum Beispiel der Population einer anderen Tierart. Solche Abhängigkeiten und Beinflussungen unterschiedlicher Dynamiken sind gewissermaßen der Normalfall in den Systemen, die für die Systemwissenschaften interessant sind. Der Systembegriff geht ja eben davon aus, dass interagierende, also sich wechselseitig beeinflussende Dynamiken in ihrem Zusammenwirken etwas generieren, das ohne dieses Zusammenwirken nicht, oder zumindest nicht so, beobachtet werden kann.
In Bezug auf diese sich wechselseitig beeinflussenden Dynamiken spricht man von gekoppelten Dynamiken. Und die Mathematik kennt zu ihrer Analyse die Methode der gekoppelten Differentialgleichungen. Das systemwissenschaftliche Standardbeispiel für solche gekoppelten Differentialgleichungssysteme sind Räuber-Beute-Systeme, für die es in der Regel allerdings keinen analytischen (rein mathematischen) Lösungsweg gibt. Im Folgenden wollen wir deshalb ein historisches Beispiel eines solchen Räuber-Beute-Systems mit Hilfe von Python simulieren.
Beginnen wir ähnlich, wie wir in einem früheren Kapitel Hasen simuliert haben. Innerhalb einer For-Schleife, wächste die Population von Hasen exponentiell. ZUsätzlich bauen wir auch noch eine zweite Tierart, die Luchse, ein.
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.01 # Wachstumsrate, wie schnell vermehren sich die Hasen
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Luchse
for it in range(365): # Schleife über 365 Tage
# Populuationsgleichungen:
# population = population + wachstum
# population = population + wachstumrate * population
hasen = hasen + hasen_wachstum * hasen
luchse = luchse + luchs_wachstum * luchse
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden geplottet (lw gibt die Stärke der Linien an)
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Nun müssen wir auch noch den Term in die Gleichung einbauen, der beschreibt, dass Tiere sterben. Das möchten wir elegant ausdrücken.
Wir wollen folgende Informationen in die Gleichung einbauen:
Wenn es viele Hasen gibt, können sich die Luchse schneller vermehren.
Wenn es viele Luchse gibt, sterben mehr Hasen.
Wie bauen wir das nun mathematisch ein? Ohne Beeinflussung der Tierarten untereinander wäre das
$$\Delta\ Hasen = a * Hasen - {b} * Hasen $$ $$\Delta\ Luchse = {c} * Luchse - d * Luchse $$
Nun möchten wir diese zusätzliche Abhängigkeit einbauen:
$$\Delta\ Hasen = a * Hasen - \color{darkred}{ \tilde b * Hasen * Luchse}$$ $$\Delta\ Luchse = \color{darkred}{ \tilde c * Luchse * Hasen} - d * Luchse $$
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01
for it in range(10 * 365): # Schleife über 10 * 365 Tage
# Populuationsgleichungen:
# population = population + wachstum - sterben
hasen = hasen + hasen_wachstum * hasen - hasen_sterben * hasen * luchse
luchse = luchse + luchs_wachstum * luchse * hasen - luchs_sterben * luchse
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden geplottet (lw gibt die Stärke der Linien an)
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Einen kleinen Fehler machen wir hierbei aber noch:
Wenn wir die neue Hasenpopulation ausrechnen, verwenden wir korrekterweise die Hasen und Luchspopulation aus dem letzten Zeitschritt. Dann überschreiben wir die Hasenzahl. Wenn wir dann also in der nächsten Zeile die Luchse berechnen, verwenden wir schon die neue Zahl der Hasen. Hier sollten wir eigentlich die Zahl vom Vortag benutzen.
Also ersetzen wir hasen
durch hasen_liste[it-1]
und luchse
durch luchs_liste[it-1]
.
Zusätzlich müssen wir mit unserer Schleife nicht mehr bei 0, sondern bei 1 beginnen.
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01
for it in range(1,10 * 365): # Schleife über 10 * 365 Tage
# Populuationsgleichungen:
# population = population + wachstum - sterben
hasen = hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]
luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden geplottet (lw gibt die Stärke der Linien an)
plt.plot(hasen_liste, color = "gray", lw = 2)
plt.plot(luchs_liste, color = "brown", lw = 2)
Diese Berechnung ist nun tagesgenau. Wenn wir genauere Ergebnisse haben möchten, könnten wir unsere Zeitschritte verkürzen, also auf Stunden, Minuten oder Sekunden. Wir könnten auch unendlich kleine Zeitschritte machen. Die Differenzengleichung würde dann zu einer Differentialgleichung werden.
Dieses System aus gekoppelten Differentialgleichungen ist allgemein bekannt. Es heißt Lotka-Volterra-System:
$$\frac{dH}{dt}=\alpha*H-\beta*H*L$$
$$\frac{dL}{dt}=\gamma*L*H-\phi*L$$
Fürs erste sind wir aber mit der Rechengenauigkeit unserer Differenzengleichungen zufrieden. Für ein Lotka-Volterra-System gibt es 3 Regeln. Wir können im Folgenden überprüfen, ob diese Regeln auch für unser System aus Differenzengleichungen gelten.
Lotka-Volterra-Regel 1
Die Räuber- und die Beute-Populationen oszillieren periodisch und zueinander zeitlich versetzt. Die Räuber-Population läuft der Beute-Population zeitlich etwas hinterher.
Um das zu überprüfen, müssen wir unsere grafische Darstellung ein wenig verbessern. Da es sehr viel weniger Luchse als Hasen gibt, ist es schwer die Werte zu vergleichen. Besser wäre es, wenn wir nicht die absolute Population, sondern die relative Population, also die aktuelle Population dividiert durch die Startpopulation darstellen.
Was wir also machen möchten, ist jeden Eintrag der Populationslisten durch 1000 bzw. durch 100 zu dividieren. Mit Listen funktioniert das leider nicht so einfach, wohl aber mit numpy-arrays. Wir konvertieren unsere Listen also zu numpy-arrays, die wir dann ohne Probleme dividieren können:
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01
for it in range(1,10 * 365): # Schleife über 10 * 365 Tage
# Populuationsgleichungen:
# population = population + wachstum - sterben
hasen = hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]
luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden zu Arrays konvertiert und normiert, d.h durch die Startpopulation dividiert.
hasen_array = np.array(hasen_liste)
hasen_array = hasen_array / 1000
luchs_array = np.array(luchs_liste)
luchs_array = luchs_array / 100
# Listen werden geplottet (lw gibt die Stärke der Linien an)
plt.plot(hasen_array, color = "gray", lw = 2)
plt.plot(luchs_array, color = "brown", lw = 2)
Hier sehen wir eindeutig: Es gibt Oszillationen und das Maximum der Jägerpopulation is immer etwas nach dem Maximum der Beutepopulation. Die erste Lotka-Volterra-Regel wird also korrekt in unserem Modell wiedergegeben.
Lotka-Volterra-Regel 2
Die durchschnittlichen Größen der beiden Populationen bleiben über längere Zeiträume konstant, auch wenn die Maxima und Minima sehr unterschiedlich sind.
Um das zu überprüfen müssen wir längere Zeiträume betrachten. Innerhalb der ersten zwar Oszillationen sieht es zwar so aus, als würde die durchschnittliche Population konstant bleiben, wir sollten usn aber sicherheitshalber längere Zeiträume ansehen. Erhöhen wir den Simulationszeitraum auf 100 Jahre.
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01
for it in range(1,100 * 365): # Schleife über 10 * 365 Tage
# Populuationsgleichungen:
# population = population + wachstum - sterben
hasen = hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]
luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden zu Arrays konvertiert und normiert, d.h durch die Startpopulation dividiert.
hasen_array = np.array(hasen_liste)
hasen_array = hasen_array / 1000
luchs_array = np.array(luchs_liste)
luchs_array = luchs_array / 100
# Listen werden geplottet (lw gibt die Stärke der Linien an)
plt.plot(hasen_array, color = "gray", lw = 2)
plt.plot(luchs_array, color = "brown", lw = 2)
Hier sehen wir sehr deutlich, dass das Maximum immer größer wird. Die zweite Lotka-Volterra-Regel wird in unserem Modell also verletzt. Woran liegt das? Der erste Verdacht ist unsere Vereinfachung, dass wir anstelle einer Differentialgleichung nur eine tagesgenaue Differentialgleichung benutzen. Um festzustellen ob wirklich das für dieses Verhalten verantwortlich ist, müssen wir eine bessere Methode finden, um längere Zeitentwicklungen grafisch darzustellen. Wir werden im Folgenden die sogenannte Phasenraumdarstellung benutzen.
In unseren üblichen Grafiken tragen wir immer die Zeit auf der einen Achse und die Populationen auf der anderen Achse auf. Das ist aber nicht die einzige Möglichkeit, die wir haben. Wir könnten auch die eine Achse für die Jäger und die andere Achse für die Beutetiere verwenden. Dann stellen wir nicht mehr die Zeitentwicklung darn, sondern viel mehr die Population der Jäger und Beute, die uns während der ganzen Zeitentwicklung untergekommen sind.
Sollten wir immer konstante Maxima und Miminma haben, sollte die Phasenraumdarstellung eine geschlossene Form haben, ähnlich einem Kreis. Wenn wir immer größere Werte bekommen, sehen wir eine Spirale. Machen wir nun also zur Probe einen Phasenraumplot:
plt.plot(hasen_array,luchs_array)
plt.title("Phasenraumdarstellung")
plt.xlabel("Beute")
plt.ylabel("Räuber")
Wir sehen hier also eindeutig eine Spirale, die Maxima werden also immer größer. Um festzustellen ob wirklich unsere Rechengenauigkeit das Problem verursacht, können wir diese einfach erhöhen. Verwenden wir statt ganze Tage als Zeitschritte nun das Hundertstel eines Tages. Wir müssen also 100 mal mehr Zeitschritte machen, dafür die einzelnen Änderungen pro Zeitschritt durch 100 dividieren.
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01
for it in range(1,100 * 365 * 100): # Schleife über 10 * 365 Tage
# Populuationsgleichungen:
# population = population + wachstum - sterben
hasen = hasen_liste[it-1] + \
1/100 * (hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1])
luchse = luchs_liste[it-1] + \
1/100 * (luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1])
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden zu Arrays konvertiert und normiert, d.h durch die Startpopulation dividiert.
hasen_array = np.array(hasen_liste)
hasen_array = hasen_array / 1000
luchs_array = np.array(luchs_liste)
luchs_array = luchs_array / 100
plt.plot(hasen_array,luchs_array)
plt.title("Phasenraumdarstellung")
plt.xlabel("Beute")
plt.ylabel("Räuber")
Nun ist die Phasenraumdarstellung wirklich eine geschlossene Figur. Die Rechengenauigkeit macht also wirklich einen Unterschied. Mit erhöhter Rechengenauigkeit ist nun also auch die zweite Lotka-Volterra-Regel erfüllt.
Lotka-Volterra-Regel 3
Werden Räuber- und Beute-Population gleichzeitig um den gleichen Prozentanteil dezimiert, so steigt der Mittelwert der Beutepopulation kurzfristig an, und der Mittelwert der Räuberpopulation sinkt kurzfristig ab.
Auch diesen Effekt können wir ganz einfach ausprobieren. Eine Periode (also die Zeit zwischen zwei Punkten, in denen sowohl Räuber als auch Beutepopulation normiert den Wert 1 haben) dauert in unserem Modell 972 Zeiteinheiten. Wir können also den Mittelwert der ersten 972 Zeiteinheiten ausrechnen, dann die Populationen dezimiren, und denn die Mittelwerte der zweiten 972 Zeitschritte ausrechnen. Mittelwerte von arrays berechnet man am besten mit dem numpy-Befehl np.mean
. Damit kann man auch den Mittelwert von bestimmten Teilen eines Arrays ausrechnen lassen. Dazu schreibt man np.mean(liste[anfang:ende])
also zum Beispiel np.mean(hasen_array[0:972])
# Importieren von matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
# Startbedingungen für Hasen
hasen = 1000 # wie viele Hasen sind am Anfang vorhanden
hasen_liste = [] # leere Liste
hasen_liste.append(hasen) #speichert den ersten Eintrag in der Liste
hasen_wachstum = 0.005 # Wachstumsrate, wie schnell vermehren sich die Hasen
hasen_sterben = 0.005 / 100
# Startbedingungen für Luchse
luchse = 100 # wie viele Luchse sind am Anfang vorhanden
luchs_liste = [] # leere Liste
luchs_liste.append(luchse) #speichert den ersten Eintrag in der Liste
luchs_wachstum = 0.005 / 1000
luchs_sterben = 0.01
for it in range(1,8 * 365): # Schleife über 8 * 365 Tage
# Populuationsgleichungen:
# population = population + wachstum - sterben
hasen = hasen_liste[it-1] + hasen_wachstum * hasen_liste[it-1] - hasen_sterben * hasen_liste[it-1] * luchs_liste[it-1]
luchse = luchs_liste[it-1] + luchs_wachstum * luchs_liste[it-1] * hasen_liste[it-1] - luchs_sterben * luchs_liste[it-1]
#Dezimieren der Population:
if it == 972:
hasen = hasen * 0.2
luchse = luchse * 0.2
# je aktuelle Population wird an die Listen angefügt
hasen_liste.append(hasen)
luchs_liste.append(luchse)
# Listen werden zu Arrays konvertiert und normiert, d.h durch die Startpopulation dividiert.
hasen_array = np.array(hasen_liste)
hasen_array = hasen_array / 1000
luchs_array = np.array(luchs_liste)
luchs_array = luchs_array / 100
# Listen werden geplottet (lw gibt die Stärke der Linien an)
plt.plot(hasen_array, color = "gray", lw = 2)
plt.plot(luchs_array, color = "brown", lw = 2)
Hmittel1=np.mean(hasen_array[0:972])
Hmittel2=np.mean(hasen_array[972:2*972])
Lmittel1=np.mean(luchs_array[0:972])
Lmittel2=np.mean(luchs_array[972:2*972])
print("Hasenmittelwert ändert sich von")
print(Hmittel1)
print("auf")
print(Hmittel2)
print("")
print("Luchsmittelwert ändert sich von")
print(Lmittel1)
print("auf")
print(Lmittel2)
Wir sehen, auch die dritte Lotka-Volterra-Regel wird von unserem Modell erfüllt.
Räuber-Beute-Systeme von Tierpopulationen werden mathematisch mit gekoppelten Differentialgleichungssystemen dargestellt und, weil zumeist keine analytische Lösung möglich ist, am Computer als Differenzengleichungssystem simuliert.
In dieser Darstellungsweise werden nicht mehrere Variablen gegen die Zeit aufgetragen, sondern eine Variable gegen eine andere. Bei Räuber-Beute-Systemen wird beispielsweise die Beute-Population auf der x-Achse und die Räuber-Population auf der y-Achse dargestellt. Man kann den Plot dann etwa so lesen: Zu jedem Wert der Beutepopulation auf der x-Achse, finden wir auf der y-Achse wie die Räuberpopulation unter diesen Bedingungen war. Die Zeit selbst sehen wir auf diesem Plot aber nicht.
Arrays lassen sich im Gegensatz zu Listen mit Zahlen multiplizieren und durch Zahlen dividieren. Dabei wird jedes Element im Array einzeln behandelt, ähnlich wie es bei einem Vektor der Fall wäre.
Mittelwerte von Arrays oder Teilen von Arrays kann man mit np.mean(arrayname[anfang:ende])
berechnen.
Wie wir schon in den bisherigen Kapitel gesehen haben, interessieren sich die Systemwissenschaften vor allem für dynamische Systeme, d.h. für Systeme, deren Variablen oder Zustände sich verändern. Um Veränderungen, Bewegungen oder Entwicklungen zu erfassen, stellt die Mathematik eine elaborierte Methodik bereit, die Differential- und Integralrechnung. Beginnend mit diesem Kapitel beschäftigen wir uns mit dieser für die Systemwissenschaften so zentralen Methode, dies allerdings nicht primär aus der Perspektive der Mathematik, sondern eher aus der der Möglichkeiten, die der Computer bietet.
Als Beispiele betrachten wir in diesem Kapitel den Prozess der Stromerzeugung mithilfe eines Solarpanels und den Betrieb eines Elektrofahrzeugs. Wir nehmen - um auch dies in Python kennenzulernen - an, dass dafür das Einlesen und Bearbeiten von Daten aus externen Dateien notwendig ist, d.h. aus Dateien, die wir am Computer gespeichert haben, aber noch nicht im Rahmen des Jupyter-Notebooks berücksichtigt haben (die entsprechenden Dateien findet man hier (Rechtsklick + Ziel speichern unter): solargrob.txt
, solarfein.txt
und batterie.txt
).
Zuerst interessiert uns hier, wieviel Energie ein Solarpanel liefert. In der Datei solargrob.txt
wurde mitprotokolliert, wie viel Leistung das Panel zu welcher Stunde der Tageszeit liefert. Wir benötigen also eine Methode, wie wir diese Datei, bzw. den Inhalt dieser Datei in ein Jupyter-Notebook laden, um ihn mit Python bearbeiten zu können. Für das so genannte Einlesen von Daten gibt es in Python vorgefertigte Pakete. Eines davon trägt den Namen csv
(für comma separated values), und ermöglicht es, Inhalte aus Dateien aufzubereiten.
In einem ersten Schritt laden wir dieses Paket in unser Notebook, zusammen mit dem bereits bekannten Paket matplotlib
für wissenschaftliches Zeichnen.
import csv
import matplotlib.pyplot as plt
%matplotlib inline
Im nächsten Schritt greifen wir auf die besagte Textdatei zu. (Die Datei solargrob.txt
muss dazu im gleichen Ordner wie das Jupyter-Notebook gespeichert sein).
In der Datei sind 24 Einträge, einer für jede volle Stunde eines Messtages. Jeder Eintrag gibt die Leistung des Solarpanels zu dieser Stunde in Watt an. Um eine Datei in Python zu öffnen und ihr einen Namen zu geben (z.B. inputfile
) kann man den Befehl with open(filename.txt) as inputfile
verwenden. Alle Befehle, die diesen Namen inputfile
verwenden sollen, müssen dazu eingerückt werden, ähnlich wie bei einer For-Schleife.
Wir öffnen die Datei und versuchen, ihren Inhalt mit dem print
-Befehl auszugeben:
import csv
import matplotlib.pyplot as plt
%matplotlib inline
with open('solargrob.txt') as inputfile: #solargrob.txt wird geöffnet und heißt im programm jetzt inputfile
print(inputfile) #inputfile soll geprintet werden
Wir sehen: der print
-Befehl liefert nicht das gewünschte Ergebnis. Wir sehen keine 24 Zahlen, sondern nur die Speicher-Adresse, unter der diese Datei Computer-intern geführt wird. Um den Inhalt der Textdatei zu extrahieren, ist vielmehr ein weiterer Schritt notwendig. Wir müssen über die gesamte Datei, Zeile für Zeile, iterieren und jede Zeile für sich ausgeben. Dazu benutzen wir wieder eine For-Schleife, kombiniert mit dem neuen Befehl csv.reader
, der Inhalte aus Dateien liest.
import csv
import matplotlib.pyplot as plt
%matplotlib inline
with open('solargrob.txt') as inputfile: # solargrob.txt wird geöffnet und heißt im programm jetzt inputfile
for row in csv.reader(inputfile): # für jede Zeile innerhalb von inputfile
print(row) # wird die Zeile ausgegeben
Das Ergebnis ist schon eher das, was wir erwarten. Wir können Zahlen erkennen. Die ersten Zahlen sind allerdings sehr klein (e-08 am Ende einer Zahl bedeutet, dass die vorne stehende Zahl mit 10^-8 multipliziert wird, also mit 0.00000001). Das Maximum der Zahlen liegt beim 13ten Eintrag. Es handelt sich um die Leistungsdaten eines Solarpanels.
Dennoch ist diese Ausgabe noch nicht perfekt, denn die Zahlen stehen innerhalb einiger Sonderzeichen, die uns beim Bearbeiten, also zum Beispiel beim Zeichnen eines Leistungsverlaufs, stören. Der Umstand, dass jeder Eintrag in eckigen Klammern steht, sagt uns, dass es sich bei den Einträgen um Listen handelt mit jeweils nur einem Eintrag. Hätten wir eine Datei mit mehreren Einträgen pro Zeile, wäre das vielleicht eine vernünftige Notation. In unserem Fall hätten wir aber lieber einzelne Einträge, und keine Listen der Länge 1. Wir spezifizieren also, dass wir von der ganze Zeile, immer nur den ersten (und einzigen) Eintrag, also den Eintrag mit dem Index 0 brauchen:
import csv
import matplotlib.pyplot as plt
%matplotlib inline
with open('solargrob.txt') as inputfile: # solargrob.txt wird geöffnet und heißt im programm jetzt inputfile
for row in csv.reader(inputfile): # für jede Zeile innerhalb von inputfile
print(row[0]) # wird der erste Eintrag jeder Zeile ausgegeben
Das sieht nun schon besser aus. Als nächstes sollten wir noch den Datentyp überprüfen. Beim Einlesen von Daten geht Python nämlich oftmals davon aus, dass es sich dabei um Text (einfach gesagt: um Buchstaben, so genannte strings
) handelt.
Merke: Der Datentyp gibt an, in welcher Weise ein Datum - etwa eine Zahl oder ein Buchstabe - am Computer gespeichert wird. Gewöhnlich können Programmiersprachen den Datentyp nicht selbständig erkennen. Eine '1' kann zum Beispiel als Buchstabe (string
) oder als ganze Zahl (integer
) oder auch als relle Zahl (float
) abgespeichert sein. Was für uns nahezu identisch aussieht und leicht in seiner Bedeutung verstanden wird, sind für den Computer drei völlig verschiedene Objekte.
Deswegen sollten wir den Typ der Variablen, die ausgegeben werden soll, mit dem Befehl type
abfragen. Da uns hier, um den Typ festzustellen, auch schon wenige Einträge reichen, begrenzen wir unsere Ausgabe mithilfe eines Zählers c
und einer if-Abfrage
auf fünf.
import csv
import matplotlib.pyplot as plt
%matplotlib inline
c = 0
with open('solargrob.txt') as inputfile:
for row in csv.reader(inputfile):
if c < 5:
print(row[0])
print(type(row[0])) # type ermittelt den Typ einer Variable
c = c + 1
Wie vermutet, sind die Daten als Text (d.h. als <type 'str'>
) abgespeichert, nicht aber als Zahlen, mit denen man rechnen kann. Wir müssen diesen Text also zuerst in Fließkomma- (sprich reelle) Zahlen verwandeln, damit wir damit arbeiten können. Dies geschieht in Python mit dem Befehl float
(Wir demonstrieren dies neuerlich nur für die ersten fünf Einträge und verwandeln die restlichen Einträge dann erst im nächsten Schritt unten in reelle Zahlen):
import csv
import matplotlib.pyplot as plt
%matplotlib inline
c = 0
with open('solargrob.txt') as inputfile:
for row in csv.reader(inputfile):
if c < 5:
print(float(row[0])) # float konvertiert den Text in eine Zahl
print(type(float(row[0])))
c = c + 1
Als letzten Schritt unserer Datenvorbehandlung möchten wir die Zahlen nicht einfach nur auf den Bildschirm schreiben, sondern in einer Liste speichern, damit wir damit auch wirklich arbeiten können. Wir erstellen also eine leere Liste und füllen sie sodann mit den umgewandelten Einträgen. Erst dann lesen wir die gesamte Liste mithilfe des print
-Befehls aus:
import csv
import matplotlib.pyplot as plt
%matplotlib inline
solargrob = [] # eine leere Liste wird erstellt
with open('solargrob.txt') as inputfile:
for row in csv.reader(inputfile):
solargrob.append(float(row[0])) # der erste Eintrag jeder Zeile wird als Zahl an die Liste angehängt
print(solargrob) # die gesamte Liste wird gedruckt
Nun können wir mit diesen Messdaten arbeiten. Wir können zum Beispiel einen Plot erstellen, der uns zeigt zu welcher Tageszeit das Solarpanel wie viel Leistung erbringt. Dazu können wir den Plotbefehl plt.fill
benutzen, der im Unterschied zu plt.plot
die Fläche unter einer Kurve miteinfärbt.
import csv
import matplotlib.pyplot as plt
%matplotlib inline
solargrob = []
with open('solargrob.txt') as inputfile:
for row in csv.reader(inputfile):
solargrob.append(float(row[0]))
plt.fill(range(24), solargrob, color = "orange") # fill funktioniert ähnlich wie plot, nur wird die Fläche gefüllt
plt.ylabel("Leistung / Watt")
plt.xlabel("Zeit / Stunden")
plt.xlim(0, 23)
Wir können nun den zeitlichen Verlauf der Leistung deutlich erkennen. Zur Spitzenzeit um 12h liefert das Panel 1000 Watt (W), also genau 1 Kilowatt (kW). Was aber ist seine Gesamtleistung über 24 Stunden hinweg?
Wenn das Panel 24 Stunden lang stets 1000 Watt liefern würde, wäre die Berechnung der Gesamtleistung einfach. Die wäre dann einfach
$$1 kW * 24 h = 24 kWh$$
Wie wir im Plot sehen können, ist die Leistung aber zu jeder Tageszeit anders. Wir können also nicht 24 mal den gleichen Wert aufsummieren, um zur Gesamtleistung zu kommen. Wir müssen vielmehr für jede Stunde den Wert berücksichtigen, der in unserer Liste gespeichert ist. Wir haben in dieser Liste 24 Einträge und kennen somit die Leistung zu jeder Stunde der Tageszeit. Offensichtlich sollten wir also einfach die Einträge unserer Liste aufsummieren können, um die Tagesgesamtleistung zu bestimmen. Dieses Aufsummieren bedeutet praktisch, dass wir damit die gelb gezeichnete Fläche unter der Kurve näherungsweise bestimmen. (Nur näherungsweise deswegen, weil wir ja genaugenommen nur die Leistungsdaten zu jeder vollen Stunde berücksichtigen und nicht zB um 12.30h. Siehe dazu gleich unten).
Zum Berechnen der Fläche unter einer Kurve verwendet die Mathematik die Integralrechnung. Unser "Aufsummieren einer Liste" entspricht diesem Zweck. Man spricht diesbezüglich von der Methode der numerischen Integration.
Python stellt für diesen Vorgang des Aufsummierens den einfachen Befehl sum
zur Verfügung. Im Folgenden benutzen wir diesen Befehl, um festzustellen, wieviel Leistung das Solarpanel über den Tag hinweg liefert:
import csv
import matplotlib.pyplot as plt
%matplotlib inline
solargrob = []
with open('solargrob.txt') as inputfile:
for row in csv.reader(inputfile):
solargrob.append(float(row[0]))
gesamtleistung = sum(solargrob) #sum addiert alle Elemente der Liste = numerische Integration
print(gesamtleistung)
In Summe produziert unser Solarpanel also etwa 3741 Wattstunden an einem Tag. Dieses Resultat ist freilich noch sehr ungenau. Warum? Wie gesagt, gehen wir bisher in dieser Betrachtung davon aus, dass es für jede Stunde des Tages einen fixen Leistungswert gibt. Und wir nehmen implizit an, dass dieser Wert innerhalb dieser Stunde konstant bleibt. Das ist natürlich in der Realität nicht der Fall.
Wir können den Effekt unserer Vereinfachung mit einem so genannten Bar-Plot
darstellen. Damit können wir die Daten in Form von Rechtecken darstellen, also genau so, wie auch unser Aufsummieren funktioniert. Man sieht sofort, dass das nur eine Näherungslösung sein kann:
plt.bar(range(24), solargrob, width = 1.0, color = "orange")
plt.xlim(0, 23)
plt.ylabel("Leistung / Watt")
plt.xlabel("Zeit / Stunden")
Um die Genauigkeit unserer Ergebnisse zu erhöhen, haben wir prinzipiell zwei Möglichkeiten. Entweder wir finden bessere Integrationsmethoden oder wir erhöhen einfach die Zeitauflösung unserer Messdaten.
Hier machen wir Zweiteres. Glücklicherweise können wir auf genauere Daten zurückgreifen. Zusätzlich zu der Datei, die wir schon eingelesen haben, liegt eine Datei vor, die minuten-genaue Daten enthält, die also 24 * 60 = 1440
Einträge hat. Im Folgenden lesen wir auch diese Datei in unseren Python-Code ein und erstellen einen Plot.
(Die Umrechnung von Stunden in Minuten muss hier nicht vorgezogen werden. Der Befehl range(1440)
kann auch als range(24 * 60)
geschrieben werden.)
import csv
import matplotlib.pyplot as plt
%matplotlib inline
solarfein = []
with open('solarfein.txt') as inputfile:
for row in csv.reader(inputfile):
solarfein.append(float(row[0]))
plt.fill(range(24 * 60),solarfein, color = "orange")
plt.ylabel("Leistung / Watt")
plt.xlabel("Zeit / Minuten")
plt.xlim(0, 24 * 60)
In dieser Auflösung sehen wir eine Reihe von Details, zum Beispiel, die Bewölkung, die offensichtlich den Vormittag kurz getrübt hat.
Es liegt nun nahe, auch mit diesen Daten eine numerische Integration durchzuführen, um die Gesamtleistung zu berechnen. Aber Vorsicht! Mit den gröberen Daten hatten wir 24 Zahlen aufsummiert, die größte davon war 1000. Nun wollen wir 1440 Zahlen aufsummieren, von denen wiederum die größte 1000 ist. Das kann kein ähnliches Ergebnis liefern. Offensichtlich haben wir etwas übersehen?
Für die gröberen Daten war unsere Argumentation wie folgt: Wenn das Solarpanel eine Stunde lang 1000 Watt liefert, produziert es eine Kilowattstunde. Deswegen war es zulässig, die Werte in der Liste einfach aufzuaddieren.
Nun liegen uns aber minutengenaue Daten vor. Die Aussage "Wenn ein Solarpanel eine Minute lang 1000 Watt liefert, produziert es eine Kilowattstunde." wäre falsch. Richtig ist: "Wenn ein Solarpanel eine Minute lang 1000 Watt liefert, produziert es eine Kilowattminute, also ein Sechzigstel einer Kilowattstunde."
Die Werte, die in unserer Liste stehen haben also die falschen Einheit. Gegeben sind Kilowattminuten, wir hätten aber gerne Kilowattstunden, damit wir das Ergebnis der numerischen Integration (des Aufsummierens) mit unserer ursprünglichen Berechnung vergleichen können. Wir müssen diesen Umrechnungsfaktor (1/60) also miteinbeziehen und können erst danach summieren. Dazu erstellen wir eine For-Schleife, die uns jeden Wert der ursprünglichen Liste umrechnet und in einer neuen Liste speichert. Wie lange muss diese Schleife nun aber laufen? Das kommt auf die Länge von solarfein
an. Diese Länge bekommen wir mit len(solarfein)
:
import csv
import matplotlib.pyplot as plt
%matplotlib inline
solarfein = []
with open('solarfein.txt') as inputfile:
for row in csv.reader(inputfile):
solarfein.append(float(row[0]))
solarfeinumgerechnet = []
for it in range(len(solarfein)):
solarfeinumgerechnet.append(solarfein[it]/60) # jeder Wert wird durch 60 dividiert und dann an die neue Liste gehängt
gesamtleistungfein = sum(solarfeinumgerechnet)
print(gesamtleistungfein)
Wir sehen also, da