13. Nov 2017
Lesedauer 10 Min.
Arrays mit Schleife
Mathematik mit Python, Teil 1
Die leistungsfähigen Arrays der Mathe-Bibliothek numpy im praktischen Einsatz.

Die Lösungen wissenschaftlich oder technisch orientierter Probleme findet man oft schon vorgefertigt in Python-Bibliotheken. In dieser kleinen Artikelserie werden insbesondere die Python-Bibliotheken numpy, scipy und sympy vorgestellt. Dieser erste Teil der Serie zeigt die Bordmittel von Python, stellt die Arrayfunktionen von numpy vor und zeigt, wie Sie das Modul matplotlib zur Anzeige von Daten benutzen. Sollten Sie jetzt die Nase rümpfen, weil Sie glauben, Python sei eine langsame Skriptsprache, dann sollten Sie bedenken, dass die genannten Bibliotheken hocheffizient in C/C++ implementiert wurden und vorkompiliert in Python geladen werden. In Sachen Performance können sie sich deshalb durchaus mit Eigenimplementierungen messen.Die allererste Entscheidung, die Python-Entwickler treffen müssen, ist, welche Version sie nutzen wollen: Python 2.7 oder 3.x. Ich benutze wenn möglich Python 3, da dort einige wichtige Neuerungen eingeführt wurden, die das Programmieren erleichtern [1][2]. Da eine der Änderungen den hier abgedruckten Code betrifft, sei sie kurz vorgestellt: In Python 2 lassen sich Ausgaben mit einem print-Befehl mit oder ohne Klammer erstellen, in Python 3 muss man dagegen die Klammer-Schreibweise benutzen:
# Python <span class="hljs-number">2</span>
<span class="hljs-built_in">print</span> <span class="hljs-string">'Hello, World!'</span>
# Python <span class="hljs-number">3</span>
<span class="hljs-built_in">print</span> (<span class="hljs-string">'Hello, World!'</span>)
Python-Programmierumgebungen (IDEs) gibt es sowohl für Linux und Mac als auch für Windows. Ist eine IDE installiert, finden Sie mithilfe der folgenden Zeilen heraus, welche Python-Version Sie gerade benutzen:
<span class="hljs-keyword">from</span> platform <span class="hljs-keyword">import</span> python_version
<span class="hljs-built_in">print</span> (<span class="hljs-string">"Python: "</span>, python_version())
Auch wenn diese ersten Codezeilen den Eindruck vermitteln mögen, so ist diese Artikelserie dennoch kein Einführungskurs in Python, sondern sie dreht sich um das Thema Mathematik mit Python. Suchen Sie eine Einführung, so werden Sie unter [3] und [4] fündig.
Einfache Rechenaufgaben
Selbstverständlich kann man mit Python auch Rechenoperationen durchführen, ohne eine spezielle Bibliothek dafür zu benutzen. Dazu zählen die vier Grundrechenarten sowie das Setzen von Klammern, um die Ausführungsreihenfolge zu kontrollieren. Für leistungsfähigere mathematische Funktionen müssen Sie jedoch das Modul math importieren. Listing 1 zeigt einige Beispiele.Listing 1: Einfaches Rechnen
import math <br/><br/>a = ((<span class="hljs-number">3</span> + <span class="hljs-number">5</span>) / <span class="hljs-number">2</span> + <span class="hljs-number">3</span>) * (<span class="hljs-number">2</span> + <span class="hljs-number">1</span>)**<span class="hljs-number">2</span> <br/>print (<span class="hljs-string">"Klammern:"</span>, a) <br/>b = math.sqrt(a) <br/>print (<span class="hljs-string">"Wurzel:"</span>,b) <br/>c = math.fsum([ <span class="hljs-number">1.5</span>, <span class="hljs-number">2.3</span>, <span class="hljs-number">1.9</span>, <span class="hljs-number">-3.0</span>, <span class="hljs-number">2.8</span>]) <br/>print (<span class="hljs-string">"Summe exakt:"</span>, c) <br/>c1 = sum([ <span class="hljs-number">1.5</span>, <span class="hljs-number">2.3</span>, <span class="hljs-number">1.9</span>, <span class="hljs-number">-3.0</span>, <span class="hljs-number">2.8</span>]) <br/>print (<span class="hljs-string">"Summe:"</span>, c1) <br/>d = math.exp(-c) <br/>print (<span class="hljs-string">"Exp.:"</span>, d)
Die Funktion fsum im Modul math garantiert übrigens Arithmetikgenauigkeit nach IEEE-754. Der Unterschied zur Standardfunktion sum ist zur Laufzeit erkennbar. Das Modul math stellt folgende Funktionsgruppen zur Verfügung:
- Zahlentheoretische Funktionen
- Darstellungsfunktionen
- Logarithmische Funktionen
- Trigonometrische Funktionen
- Hyperbolische Funktionen
- Konvertierungen
- Spezielle Funktionen
- Konstanten
Listing 2: Rechnen mit komplexen Zahlen
import cmath <br/><br/><span class="hljs-selector-tag">a</span> = <span class="hljs-number">3.5</span> + <span class="hljs-number">4</span>j <br/>print (<span class="hljs-string">"Complex:"</span>, a) <br/>print (<span class="hljs-string">"Real: "</span>, <span class="hljs-selector-tag">a</span>.real) <br/>print (<span class="hljs-string">"Imag.:"</span>, <span class="hljs-selector-tag">a</span>.imag) <br/><br/><span class="hljs-selector-tag">b</span> = -<span class="hljs-number">1.2</span> - <span class="hljs-number">1</span>j <br/>c = <span class="hljs-selector-tag">a</span> + <span class="hljs-selector-tag">b</span> <br/><br/>print (<span class="hljs-string">"Addition:"</span>, c) <br/>d = cmath.sqrt(a) <br/>print (<span class="hljs-string">"Komplexe Wurzel:"</span>, d) <br/><br/>e = cmath.tan(a) <br/>print (<span class="hljs-string">"Komplexer Tangens:"</span>, e)
Noch bevor es um weitere technische und wissenschaftliche Möglichkeiten von Python geht, soll betrachtet werden, wie sich Daten mit Python visualisieren lassen. Dabei hilft das Modul matplotlib.pyplot, das Methoden zum Zeichnen von 2D- und 3D-Grafiken enthält.Der Code in Listing 3 berechnet die darzustellenden Daten sehr konservativ in einer Schleife. Dazu werden zwei leere Arrays angelegt und in einer for-Schleife mit Daten gefüllt. Das Ergebnis soll in einem XY-Diagramm ausgegeben werden. Im Beispiel kommen dafür drei Module zum Einsatz: matplotlib für die grafische Darstellung, math für die Sinus-Berechnung und numpy zum Erzeugen der leeren Arrays.
Listing 3: Grafik ausgeben
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt <br/><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np <br/><span class="hljs-keyword">import</span> math <br/><br/>t = np.empty(<span class="hljs-number">201</span>) <br/>s = np.empty(<span class="hljs-number">201</span>) <br/># Schleife von <span class="hljs-number">0</span> bis einschließlich <span class="hljs-number">200</span> <br/>for i <span class="hljs-keyword">in</span> range(<span class="hljs-number">201</span>): <br/> t[i] = (i - <span class="hljs-number">1</span>) * <span class="hljs-number">0.01</span> <br/> s[i] = <span class="hljs-number">1.0</span> + math.sin(<span class="hljs-number">2</span> * math.pi * t[i]) <br/><br/># Grafik erstellen und ausgeben <br/>plt.plot(t, s) <br/>plt.xlabel(<span class="hljs-string">'Zeit t[s]'</span>) <br/>plt.ylabel(<span class="hljs-string">'Spannung U[mV]'</span>) <br/>plt.title(<span class="hljs-string">'Test'</span>) <br/>plt.grid(<span class="hljs-literal">True</span>) <br/>plt.savefig(<span class="hljs-string">"test.png"</span>) # Speichern <br/>plt.show() # Darstellen
Der plot-Funktion werden die beiden Daten-Arrays übergeben. Danach bietet die Funktion mehrere Optionen, um die Grafikausgabe zu erweitern oder zu formatieren. Hier wird ein Text für die beiden Achsen und den Titel definiert und mit grid die Darstellung eines Rasters bestimmt. Der Aufruf von savefig(„test.png“) speichert die Grafik als PNG-Datei. Die show-Funktion zeigt die Grafik in einem separaten Fenster beziehungsweise in der Python-Konsole an, siehe Bild 1.
Wer das Modul numpy bereits kennt, wird sich über das obige Beispiel gewundert haben, denn numpy kann viel mehr, als leere Arrays bereitstellen. Listing 4 zeigt einige der Tricks, die numpy in Sachen Arrays beherrscht. Es hat sich übrigens als Quasi-Standard etabliert, das numpy-Modul unter dem Alias np zu importieren.
Listing 4: Vereinfachte Arrays
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt <br/><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np <br/><br/># Berechnung über Arrays <br/>t = np.arange(<span class="hljs-number">0.0</span>, <span class="hljs-number">2.0</span>, <span class="hljs-number">0.01</span>) <br/>s = <span class="hljs-number">1</span> + np.sin(<span class="hljs-number">2</span> * np.pi * t) <br/><br/># Grafik erstellen und ausgeben <br/>plt.plot(t,s) <br/>plt.xlabel(<span class="hljs-string">'Zeit t[s]'</span>) <br/>plt.ylabel(<span class="hljs-string">'Spannung U[mV]'</span>) <br/>plt.title(<span class="hljs-string">'Test'</span>) <br/>plt.grid(<span class="hljs-literal">True</span>) <br/>plt.savefig(<span class="hljs-string">"test.png"</span>) # Speichern <br/>plt.show() # Darstellen
Auf die Import-Befehle folgen die interessantesten Zeilen des Listings: Das Erzeugen der Arrays sowie die explizit programmierte Schleife wurden durch Aufrufe im numpy-Modul ersetzt. Zunächst wird mit der arange-Funktion ein Array t erzeugt, welches die Werte von 0.0 bis 2.0 mit einer Schrittweite von 0.01 enthält. Im zweiten Schritt wird das Array s erzeugt und für jeden Wert im Array t ein Wert im Array s berechnet, ganz ohne Schleife. Allerdings ist zu beachten, dass nun nicht mehr die Funktion math.sin benutzt werden darf, sondern die Sinus-Funktion np.sin zum Einsatz kommt. Die Ausgabe der Grafik gleicht hier dem vorherigen Beispiel.Die Varianten in Listing 3 und 4 benötigen sehr unterschiedliche Rechenzeiten. Bei der zweiten Variante wird nicht intern parallelisiert, sondern es werden die Vektoreigenschaften der Prozessoren – sofern vorhanden – ausgenutzt. Diese Verfahren sind unter den Namen SSE (Streaming SIMD Extensions) oder AVX (Advanced Vector Extensions) bekannt.
Tabelle 1: Rechenzeiten
|
Tabelle 1 zeigt die Rechenzeiten der beiden Python-Varianten und eines entsprechenden C#- beziehungsweise C++-Programms. Die Anzahl der Schleifendurchläufe wurde bei der Messung auf 2.000.000 erhöht. In allen Beispielen läuft jeweils nur ein Thread.Das Python-Modul matplotlib ist sehr leistungsfähig und enthält zugleich Funktionen für andere Darstellungsarten der Daten. Dies soll ein weiteres einfaches Beispiel zeigen. Dafür wird eine Funktion f(x, y) definiert, die einen Wertebereich aufspannt. Diese Daten sollen in einem sogenannten Höhenliniendiagramm dargestellt werden. Listing 5 zeigt den Quellcode dazu.
Listing 5: Höhenlinien
import matplotlib.pyplot as plt <br/>import numpy as <span class="hljs-built_in">np</span> <br/><br/># Funktion <br/>def f(x, y): <br/> <span class="hljs-built_in">return</span> (<span class="hljs-number">0.9</span> - x/<span class="hljs-number">2</span> + <br/> x**<span class="hljs-number">5</span> + y**<span class="hljs-number">3</span>) * <br/> <span class="hljs-built_in">np</span>.<span class="hljs-built_in">exp</span>(-x**<span class="hljs-number">2</span> - y**<span class="hljs-number">2</span>) <br/> <br/># Anzahl der Punkte <span class="hljs-keyword">in</span> X <span class="hljs-literal">und</span> Y <br/>n = <span class="hljs-number">256</span> <br/>xx = <span class="hljs-built_in">np</span>.linspace(-<span class="hljs-number">3</span>, <span class="hljs-number">3</span>, n) <br/>yy = <span class="hljs-built_in">np</span>.linspace(-<span class="hljs-number">3</span>, <span class="hljs-number">3</span>, n) <br/>X, Y = <span class="hljs-built_in">np</span>.meshgrid(xx, yy) <br/><br/># Grafikausgabe <br/>plt.<span class="hljs-built_in">axes</span>([<span class="hljs-number">0.05</span>, <span class="hljs-number">0.05</span>, <span class="hljs-number">0.9</span>, <span class="hljs-number">0.9</span>]) <br/><br/>plt.contourf(X, Y, f(X, Y), <span class="hljs-number">8</span>, alpha = <span class="hljs-number">0.75</span>,<br/> cmap = plt.cm.hot) <br/>C=plt.<span class="hljs-built_in">contour</span>(X, Y, f(X, Y), <span class="hljs-number">8</span>, colors = 'black',<br/> <span class="hljs-built_in">linewidth</span> = <span class="hljs-number">0.5</span>) <br/>plt.clabel(C, inline = <span class="hljs-number">1</span>, fontsize = <span class="hljs-number">10</span>) <br/><br/>plt.<span class="hljs-built_in">grid</span>() <br/>plt.xticks() <br/>plt.yticks() <br/><br/>plt.savefig(<span class="hljs-string">"test.png"</span>) # Speichern <br/>plt.<span class="hljs-built_in">show</span>() # Darstellung Fenster
Zunächst wird die Python-Funktion f(x, y) definiert, welche die Höhenwerte in Abhängigkeit von den Koordinaten x und netz der Berechnungspunkte erstellt. Mit der axes-Funktion kann man festlegen, welcher Bereich des Fensters mit der eigentlichen Grafik auszufüllen ist. Im Beispiel sollen links und unten jeweils fünf Prozent freier Platz bleiben. Die übrigen 90 Prozent der Fensterbreite und -höhe darf die Grafik einnehmen. In drei Schritten wird nun der Höhenlinien-Plot vorbereitet und gezeichnet: Die contourf-Funktion stellt zunächst die einzelnen Höhenflächen farblich dar, danach werden die schwarzen Trennlinien für die Höhenbereiche eingezeichnet und im dritten Schritt die Beschriftungen an den Höhenlinien angebracht. Es folgen das Raster (grid) und die Achsenbeschriftungen (xticks und yticks), bevor die Grafik in einem Fenster ausgegeben beziehungsweise gespeichert wird (Bild 2).
Noch einmal Python-Arrays
In Listing 4 wurden die Arrays aus dem Modul numpy bereits benutzt. Diese Arrays sind jedoch ein wesentlicher Teil von Python, der nun genauer behandelt werden soll. Grundsätzlich wird durch diese Arrays folgende generelle Vereinfachung eingeführt:# <span class="hljs-type">Aus</span> der <span class="hljs-type">Schleife</span>:
<span class="hljs-built_in">c</span> = [] # <span class="hljs-type">Leeres</span> <span class="hljs-type">Array</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(n):
<span class="hljs-built_in">c</span>.append(a[i] * b[i])
# wird die einfache <span class="hljs-type">Zeile</span>:
<span class="hljs-built_in">c</span> = a * b
Man spart also die Schleife und die Indizierung der Variablen. Außerdem lässt sich der Code für Vektoroperationen optimieren, die wesentlich schneller als normale Schleifendurchläufe sind, wie die Laufzeiten in Tabelle 1 belegen.Die Arrays aus dem Modul numpy werden über die Klasse ndarray implementiert. Darum hört man oft auch den Begriff ndarray-Objekt. Die ndarray-Objekte lassen sich in einer vorgegebenen Größe erstellen, sie können einen Rang (Dimensionalität) und einen Typ haben und in unterschiedlicher Weise initialisiert werden. Eine interessante Vorgehensweise beim Initialisieren eines Arrays ist der Aufruf einer Funktion, die einen Wert zurückgibt:
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">f</span><span class="hljs-params">(i, j)</span>:</span>
<span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * i * j
a = np.fromfunction(f, (<span class="hljs-number">4</span>, <span class="hljs-number">3</span>))
<span class="hljs-keyword">print</span> (a)
Mit diesen Zeilen erstellen Sie ein Array a, welches aus vier Zeilen und drei Spalten besteht. Die Funktion f(i, j) wird mit den Werten für i und j aufgerufen und liefert das Ergebnis zurück, mit welchem das jeweilige Array-Element initialisiert wird. Die Ausgabe sieht also so aus:
[[ <span class="hljs-number">0.</span> <span class="hljs-number">0.</span> <span class="hljs-number">0.</span>]
[ <span class="hljs-number">0.</span> <span class="hljs-number">2.</span> <span class="hljs-number">4.</span>]
[ <span class="hljs-number">0.</span> <span class="hljs-number">4.</span> <span class="hljs-number">8.</span>]
[ <span class="hljs-number">0.</span> <span class="hljs-number">6.</span> <span class="hljs-number">12.</span>]]
Auf diese Weise lassen sich Arrays sehr schnell und einfach mit beliebigen Daten initialisieren. Im Speicher werden ndarray-Objekte in der Reihenfolge „Zeile zuerst“ abgelegt. Das heißt, in einem zweidimensionalen Array werden die Daten Zeile für Zeile hintereinander abgelegt:
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
b = np.array( ((1,2), (3,4)) )
print (b)
print (b.flatten())
Der erste print-Befehl zeigt das normale Array in zwei Dimensionen, mit dem zweiten Befehl werden die Daten sequentiell ausgegeben, so wie sie im Speicher stehen:
[[<span class="hljs-number">1</span> <span class="hljs-number">2</span>] # Erstes print
[<span class="hljs-number">3</span> <span class="hljs-number">4</span>]]
[<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span> <span class="hljs-number">4</span>] # Zweites print
Selbstverständlich lassen sich diese Arrays auch in der Größe oder in ihrer Anordnung (Dimensionalität) ändern. Hier müssen Sie jedoch beachten, dass die Reihenfolge der Daten im Speicher nicht verändert wird. Neu hinzukommende Array-Elemente werden mit Null initialisiert.Einige Array-Operationen, die in der mathematischen Praxis häufig auftreten, sollen hier noch an einem weiteren Beispiel vorgestellt werden: das Transponieren einer Matrix sowie das Zusammenfassen und Aufsplitten von Arrays, vergleiche Listing 6).
Listing 6: Array-Operationen
import numpy as np <br/><br/># Matrix transponieren <br/>a = np.linspace(<span class="hljs-number">1</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>).reshape(<span class="hljs-number">3</span>,<span class="hljs-number">2</span>) <br/>print (<span class="hljs-string">"Ursprung:<span class="hljs-subst">\n</span>"</span>, a) <br/>b = a.transpose() <br/>print (<span class="hljs-string">"Transponiert:<span class="hljs-subst">\n</span>"</span>, b) <br/><br/># Arrays zusammenfassen <br/>c1 = np.array([<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>]) <br/>c2 = np.array([<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>]) <br/>c = np.vstack((c1, c2)) <br/><br/>print (<span class="hljs-string">"Vertikal zusammen:<span class="hljs-subst">\n</span>"</span>, c) <br/>d = np.hstack((c1, c2)) <br/>print (<span class="hljs-string">"Horizontal zusammen:<span class="hljs-subst">\n</span>"</span>, d) <br/><br/># Arrays splitten <br/>e = np.hsplit(d, <span class="hljs-number">4</span>) <br/>print (<span class="hljs-string">"Aufgesplittet:<span class="hljs-subst">\n</span>"</span>, e)
Wenn dieses Programm ausgeführt wird, erscheinen folgende Ausgaben auf dem Bildschirm:
Ursprung:
<span class="hljs-comment">[<span class="hljs-comment">[ 1. 2.]</span> </span>
<span class="hljs-comment"> <span class="hljs-comment">[ 3. 4.]</span> </span>
<span class="hljs-comment"> <span class="hljs-comment">[ 5. 6.]</span>]</span>
Transponiert:
<span class="hljs-comment">[<span class="hljs-comment">[ 1. 3. 5.]</span> </span>
<span class="hljs-comment"> <span class="hljs-comment">[ 2. 4. 6.]</span>]</span>
Vertikal zusammen:
<span class="hljs-comment">[<span class="hljs-comment">[0 0 0 0]</span> </span>
<span class="hljs-comment"> <span class="hljs-comment">[1 1 1 1]</span>]</span>
Horizontal zusammen:
<span class="hljs-comment">[0 0 0 0 1 1 1 1]</span>
Aufgesplittet:
<span class="hljs-comment">[array(<span class="hljs-comment">[0, 0]</span>), array(<span class="hljs-comment">[0, 0]</span>), array(<span class="hljs-comment">[1, 1]</span>), </span>
<span class="hljs-comment"> array(<span class="hljs-comment">[1, 1]</span>)]</span>
Im ersten Teil des Beispiels wird ein zweidimensionales Array mit drei Zeilen und zwei Spalten erstellt. Hierzu findet die Funktion linspace Verwendung. Die drei Parameter in diesem Aufruf sind Startwert, Endwert und Anzahl der Werte. Es wird jedoch ein lineares, eindimensionales Array erstellt, welches im zweiten Schritt zu einem Array mit drei Zeilen und zwei Spalten umgeordnet (reshape) wird. Dieses Array lässt sich mit der transpose-Funktion „stürzen“, das heißt, aus den Zeilen werden Spalten und aus den Spalten werden Zeilen. Danach werden zwei eindimensionale Arrays c1 und c2 erstellt und zunächst vertikal mit der vstack-Funktion und dann horizontal mit der hstack-Funktion zusammengefasst. Das Aufspalten eines großen Arrays in mehrere kleinere Arrays klappt mit hsplit (horizontal) oder vsplit (vertikal). Im Beispiel wird das Array d, welches acht Zahlen enthält, in vier Arrays mit jeweils zwei Zahlen aufgeteilt.
Listing 7: Array-Indizierungen
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np <br/><br/># Einfache Indizierung <br/>a = np.linspace(<span class="hljs-number">1</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>) <br/>print (a) <br/># Ab a[<span class="hljs-number">1</span>] bis a[<span class="hljs-number">4</span>] jedes zweite Element <br/>print (a[<span class="hljs-number">1</span>:<span class="hljs-number">4</span>:<span class="hljs-number">2</span>]) <br/># Ab a[<span class="hljs-number">2</span>] bis zum vorletzten Element <br/>print (a[<span class="hljs-number">2</span>:<span class="hljs-number">-1</span>]) <br/># Ab a[<span class="hljs-number">3</span>] jedes zweite Element rückwärts <br/>print (a[<span class="hljs-number">3</span>::<span class="hljs-number">-2</span>]) <br/>print () <br/><br/># Etwas komplexere Indizierung <br/>b = np.linspace(<span class="hljs-number">1</span>,<span class="hljs-number">12</span>,<span class="hljs-number">12</span>).reshape(<span class="hljs-number">4</span>,<span class="hljs-number">3</span>) <br/>print (b) <br/># Die komplette dritte Zeile <br/>print (b[<span class="hljs-number">2</span>,:]) <br/># Die komplette zweite Spalte <br/>print (b[:,<span class="hljs-number">1</span>]) <br/># Von der zweiten Zeile bis zur vorletzten Zeile <br/># und von der zweiten Spalte bis zum Ende <br/>print (b[<span class="hljs-number">1</span>:<span class="hljs-number">-1</span>,<span class="hljs-number">1</span>:]) <br/># Vom Anfang jede zweite Zeile <br/>print (b[::<span class="hljs-number">2</span>,:]) <br/># Zwei Zeilen und Spalten unten links <br/>print (b[<span class="hljs-number">2</span>:,:<span class="hljs-number">2</span>]) <br/># Jedes zweite Element <span class="hljs-keyword">in</span> jeder zweiten Zeile <br/>print (b[<span class="hljs-number">1</span>::<span class="hljs-number">2</span>,::<span class="hljs-number">2</span>])
Als Nächstes wollen wir die Indizierung von numpy-Arrays etwas genauer unter die Lupe nehmen, da es dort sehr viele interessante Möglichkeiten gibt. Das Beispiel aus Listing 7 erzeugt die folgenden Ausgaben:
[ <span class="hljs-number">1.</span> <span class="hljs-number">2.</span> <span class="hljs-number">3.</span> <span class="hljs-number">4.</span> <span class="hljs-number">5.</span> <span class="hljs-number">6.</span>]
[ <span class="hljs-number">2.</span> <span class="hljs-number">4.</span>]
[ <span class="hljs-number">3.</span> <span class="hljs-number">4.</span> <span class="hljs-number">5.</span>]
[ <span class="hljs-number">4.</span> <span class="hljs-number">2.</span>]
[[ <span class="hljs-number">1.</span> <span class="hljs-number">2.</span> <span class="hljs-number">3.</span>]
[ <span class="hljs-number">4.</span> <span class="hljs-number">5.</span> <span class="hljs-number">6.</span>]
[ <span class="hljs-number">7.</span> <span class="hljs-number">8.</span> <span class="hljs-number">9.</span>]
[ <span class="hljs-number">10.</span> <span class="hljs-number">11.</span> <span class="hljs-number">12.</span>]]
[ <span class="hljs-number">7.</span> <span class="hljs-number">8.</span> <span class="hljs-number">9.</span>]
[ <span class="hljs-number">2.</span> <span class="hljs-number">5.</span> <span class="hljs-number">8.</span> <span class="hljs-number">11.</span>]
[[ <span class="hljs-number">5.</span> <span class="hljs-number">6.</span>]
[ <span class="hljs-number">8.</span> <span class="hljs-number">9.</span>]]
[[ <span class="hljs-number">1.</span> <span class="hljs-number">2.</span> <span class="hljs-number">3.</span>]
[ <span class="hljs-number">7.</span> <span class="hljs-number">8.</span> <span class="hljs-number">9.</span>]]
[[ <span class="hljs-number">7.</span> <span class="hljs-number">8.</span>]
[ <span class="hljs-number">10.</span> <span class="hljs-number">11.</span>]]
[[ <span class="hljs-number">4.</span> <span class="hljs-number">6.</span>]
[ <span class="hljs-number">10.</span> <span class="hljs-number">12.</span>]]
Im ersten Teil des Beispiels wird ein eindimensionales Array benutzt, welches sechs Elemente enthält. Die Indizierung des Arrays funktioniert so:
<span class="hljs-keyword">array</span>[<span class="hljs-keyword">start</span>,ende,schrittweite]
Teile der Indizierung können weggelassen werden. Wenn man zum Beispiel die Angabe für das Ende weglässt, so wird das gesamte Array bis zum letzten Element benutzt. Allerdings sind alle Doppelpunkte richtig zu setzen. In Listing 7 ist in Kommentarzeilen angegeben, welche Datenelemente ausgegeben werden.Noch interessanter sind die Ausgaben im zweiten Teil des Beispiels, da hier ein zweidimensionales Array benutzt wird. Trotzdem bleiben die Regeln dieselben wie eben beschrieben. Sie können ganze Zeilen oder Spalten aus einem Array mit einem einzigen Befehl herauslösen. Bei einer solchen Operation entsteht wieder ein neues Array, mit dem Sie weiterarbeiten können. Stellvertretend soll hier noch das letzte Beispiel aus Listing 7 erklärt werden:
print (b[<span class="hljs-number">1</span>::<span class="hljs-number">2</span>,::<span class="hljs-number">2</span>])
Da alle Indizes mit 0 beginnen, ergibt sich folgende Operation: Ab der zweiten Zeile soll jede zweite Zeile, ab der ersten Spalte jede zweite Spalte benutzt werden. Die Endwerte wurden nicht angegeben, da jeweils bis zum Ende der Spalten und Zeilen gegangen werden soll. Außerdem ist es nicht nötig, einen Startwert für die Spalten anzugeben, da mit der ersten Spalte begonnen werden soll.Es sei noch erwähnt, dass Sie die zuletzt gezeigten Möglichkeiten nicht nur bei der Ausgabe von Teil-Arrays nutzen können, vielmehr gelten die gleichen Syntax-Regeln auch beim Setzen oder Ändern von Array-Elementen:
v = np.array([<span class="hljs-number">1,1,1,1</span>,<span class="hljs-number">1,1,1,1</span>])
v[<span class="hljs-number">3::2</span>] = <span class="hljs-number">5</span>
print (v)
In diesem Beispiel wird ab Array-Element v[3] in jedem zweiten Element die Zahl 1 durch eine 5 ersetzt. Als Ausgabe erhalten Sie also:
[<span class="hljs-number">1</span> <span class="hljs-number">1</span> <span class="hljs-number">1</span> <span class="hljs-number">5</span> <span class="hljs-number">1</span> <span class="hljs-number">5</span> <span class="hljs-number">1</span> <span class="hljs-number">5</span>]
Diese Beispiele zeigen, wie leistungsfähig die numpy-Arrays sind. Durch geschickten Einsatz dieser Arrays lassen sich viele Zeilen Code einsparen. Trotzdem ist diese Art und Weise, Datenfelder zu erzeugen, zunächst etwas gewöhnungsbedürftig. Eine ebenfalls sehr spannende Array-Syntax zeigt noch folgendes Beispiel:
import numpy as np
x = np.array([<span class="hljs-number">-5</span>, <span class="hljs-number">3</span>, <span class="hljs-number">-4</span>, <span class="hljs-number">7</span>, <span class="hljs-number">0</span>, <span class="hljs-number">-5</span>, <span class="hljs-number">-3</span>, <span class="hljs-number">5</span>])
x[x < <span class="hljs-number">-3</span>] = <span class="hljs-number">0</span>
print (x)
Nach Ausführen dieses kleinen Beispiels enthält das Array x die folgenden Zahlenwerte:
[ <span class="hljs-number">0</span> <span class="hljs-number">3</span> <span class="hljs-number">0</span> <span class="hljs-number">7</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">-3</span> <span class="hljs-number">5</span>]
Alle Werte im Array x, die kleiner als -3 sind, wurden durch den Wert 0 ersetzt, wieder ganz ohne explizite for-Schleife.
Von Min zu Max
Die Arrays im Modul numpy können auch mathematische Operationen ausführen. An dieser Stelle sollen die Berechnung von Maximal- und Minimalwerten und die Sortierung von Arrays gezeigt werden:
import numpy as np
# Maximum und Minimum (einfach)
a = np.array([<span class="hljs-number">1.</span>, <span class="hljs-number">4.5</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">5.6</span>, <span class="hljs-number">8.9</span>, <span class="hljs-number">3.7</span>, <span class="hljs-number">9.</span>, <span class="hljs-number">-2.4</span>])
print (<span class="hljs-string">"Max.:"</span>, a.max())
print (<span class="hljs-string">"Min.:"</span>, a.min())
Als Ergebnis liefert dieser Code 9.0 als Maximum und -2.4 als Minimum. Es gibt aber auch noch weitere interessante Aspekte: Zum einen können Sie mehrdimensionale Arrays benutzen und dann beispielsweise die Maxima und Minima der Spalten oder Zeilen eines zweidimensionalen Arrays ermitteln. Als Ergebnis liefert numpy in diesem Fall ein Array der entsprechenden Größe. Mit dem axis-Parameter können Sie dabei angeben, ob über Spalten oder Zeilen gerechnet werden soll.
import numpy as np
# Maximum und Minimum (Array)
b = np.array([[<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>], [<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>], [<span class="hljs-number">7</span>,<span class="hljs-number">8</span>,<span class="hljs-number">9</span>]])
mi1 = b.min(axis=<span class="hljs-number">0</span>) # [<span class="hljs-number">1</span> <span class="hljs-number">2</span> <span class="hljs-number">3</span>]
mi2 = b.min(axis=<span class="hljs-number">1</span>) # [<span class="hljs-number">1</span> <span class="hljs-number">4</span> <span class="hljs-number">7</span>]
ma1 = b.max(axis=<span class="hljs-number">0</span>) # [<span class="hljs-number">7</span> <span class="hljs-number">8</span> <span class="hljs-number">9</span>]
ma2 = b.max(axis=<span class="hljs-number">1</span>) # [<span class="hljs-number">3</span> <span class="hljs-number">6</span> <span class="hljs-number">9</span>]
Der Aufruf der argmin- oder argmax-Funktion liefert den Integer-Index des Array-Elements mit dem jeweils kleinsten oder größten Wert.Die Sortierung von Arrays funktioniert übrigens in gleicher Weise und kann bei mehrdimensionalen Arrays ebenfalls mit dem axis-Parameter gesteuert werden.
Listing 8: Ein vollständiges Beispiel
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np <br/><span class="hljs-keyword">import</span> matplotlib.pylab <span class="hljs-keyword">as</span> plt <br/><br/># Anzahl der Werte <br/>N = <span class="hljs-number">100</span> <br/><br/># Funktion für die Berechnung <br/>def f(i, n): <br/> x = i / N <br/> lam = <span class="hljs-number">2</span> / (n+<span class="hljs-number">1</span>) <br/> return x * (<span class="hljs-number">1</span>-x) * np.sin(<span class="hljs-number">2</span> * np.pi * x / lam) <br/><br/># Daten berechnen <br/>a = np.fromfunction(f, (N+<span class="hljs-number">1</span>, <span class="hljs-number">5</span>)) <br/># Wo sind Maxima und Minima <br/>min_i = a.argmin(axis=<span class="hljs-number">0</span>) <br/>max_i = a.argmax(axis=<span class="hljs-number">0</span>) <br/><br/># Plotten der Funktionslinien <br/>plt.plot(a, color=<span class="hljs-string">'k'</span>) <br/># Plotter der Minima und Maxima <br/>plt.plot(min_i, a[min_i, np.arange(<span class="hljs-number">5</span>)], <span class="hljs-string">'v'</span>, <br/> color=<span class="hljs-string">'r'</span>, markersize=<span class="hljs-number">10</span>) <br/>plt.plot(max_i, a[max_i, np.arange(<span class="hljs-number">5</span>)], <span class="hljs-string">'^'</span>,<br/> color=<span class="hljs-string">'b'</span>, markersize=<span class="hljs-number">10</span>) <br/><br/># Grafik im Fenster zeigen und speichern <br/>plt.savefig(<span class="hljs-string">"test.png"</span>) <br/>plt.show()
Zum Schluss noch ein Beispiel, das Arrays, grafische Ausgabe und Berechnungsfunktionen im Zusammenspiel effizient ausnutzt. Es sollen mehrere Sinus-Funktionen mit unterschiedlicher Frequenz berechnet und gezeichnet werden. Zudem gilt es, die Maximal- und Minimalwerte der einzelnen Funktionen zu kennzeichnen. Listing 8 zeigt den Python-Code für diese Aufgabe. Der Code ist nahezu selbsterklärend. Anzumerken ist, dass color=‘k‘ die Zeichenfarbe Schwarz bedeutet. Interessant ist hier, wie einfach sich das Zeichnen der Minima- und Maxima-Punkte gestaltet. Die Arrays min_i und max_i enthalten jeweils die Array-Elementnummern der kleinsten beziehungsweise größten Funktionswerte. Diese Indizes werden zusammen mit den Daten im Array a benutzt, um die Dreiecke an den richtigen Positionen zu zeichnen. Bild 3 zeigt die fertige Grafik.
Fazit
In diesem ersten Teil der Artikelserie wurde gezeigt, wie stark Sie Berechnungen mithilfe des Moduls numpy vereinfachen können. Die meisten Schleifen fallen weg, der Code wird besser lesbar und deutlich kürzer. Und weniger Code heißt letztendlich auch weniger Fehler!Der kommende zweite Teil des Artikels erklimmt etwas höhere Sphären der Mathematik. Vorgestellt werden Statistik-Funktionen sowie Polynome, und die lineare Algebra wird auch nicht zu kurz kommen.Fussnoten
- Neues in Python 3, http://www.dotnetpro.de/SL1712Rechnen1
- Auswahlhilfe Python 2 oder 3, http://www.dotnetpro.de/SL1712Rechnen2
- Tutorial zu Python 3.3, http://www.dotnetpro.de/SL1712Rechnen3
- Python, Einfach & schnell programmieren, http://www.dotnetpro.de/SL1712Rechnen4