Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Einführung in das Arbeiten mit NumPy

In vielen mathematischen Anwendungen muss mit Vektoren und Matrizen gerechnet werden, beispielsweise bei der numerischen Berechnung von Integralen, Differentialgleichungen oder Problemen aus der Graphentheorie. Wir werden in diesem Kurs ausführlich mit linearen Modellen, zu denen beispielsweise die Regressionsmodelle gehören, beschäftigen und diese mit Hilfe von Matrizenrechnungen in Python umsetzen. Wesentlicher Bestandteil des Kurses ist die Zusammenstellung und Analyse von Portfolien. Sowohl die Berechnung von Portfoliowerten als auch -Renditen können bequem mittels Matrix-Vektor-Operationen der linearen Algebra effizient berechnet werden. Daher beschäftigen wir uns in diesem Kapitel mit der Python-Bibliothek NumPy, die die entsprechenden Datentypen und Rechenoperationen für Matrizen und Vektoren bereitstellt.

NumPy installieren

Zunächst muss die NumPy-Bibliothek installiert werden. Conda erledigt dies mit dem Konsolenbefehl:

conda install numpy

Nun muss die Bibliothek in unser Python-Skript eingebunden werden. Wir könnten dies wie in Abschnitt Variablen und Datentypen mit der Zeile from numpy import * machen, was aber nicht empfehlenswert ist, da die NumPy-Bibliothek die Funktionen sin, cos, sqrt, etc. bereit stellt, welche die aus der math-Bibliothek überschreiben würden. Daher nutzen wir folgende Variante:

import numpy as np

Der Zusatz as np gibt nur an, dass wir die Bibliothek in Zukunft unter dem kürzeren Namen np und nicht unter dem langen numpy ansprechen können.

Arbeiten mit Vektoren

Vektoren erzeugen

Ein Vektor bzw. NumPy-Array kann mit der Funktion np.array(...) von einem beliebigen iterierbaren Objekt (Liste, Tupel, ...), deren Elemente vom gleichen Typ sind, initialisiert werden:

a = np.array([1.,2.,3.])
b = np.array((6.,5.,4.))
print("a ist ein", type(a), "mit Wert", a)
print("b ist ein", type(b), "mit Wert", b)

Die Klasse ndarray repräsentiert ein mehrdimensionales Array. In unserem Fall ein Vektor, also ein Array der Dimension

a.ndim

mit Daten vom Typ

a.dtype

der Dimension

a.shape

2 weitere Funktionen um NumPy-Arrays zu erzeugen sind:

x = np.linspace(0,3,6) # Äquidistantes Punktgitter für [0,3] aus 6 Punkten
x
x = np.arange(0, 3, 0.5) # Äquidistantes Punktgitter für [0,3) mit Inkrement 0.5
x

Elementare Vektoroperationen

Ein ndarray ist, wie eine Liste oder ein Tupel, ein iterierbares Objekt. Wir können also einfach mit einer for-Schleife über alle Elemente gehen:

val = 0
for e in a:
    val += e
print("Die Summe der Einträge von", a, "ist", val)

Wir können einige elementare Rechenoperationen für mit unseren Vektoren durchführen und es kommt das erwartete Ergebnis raus:

a+b
b-a
a*b
a/b

Wir beobachten, dass die Grundrechenarten einfach elementweise durchgeführt werden. Bei Addition und Subtraktion ist es auch das, was wir aus der Linearen Algebra-Perspektive erwarten würden, aber bei der Multiplikation und Division ist dies nicht klar. Eventuell hätten wir die Berechnung des Skalar- oder Kreuzproduktes erwartet. Dazu später mehr.

Die Rechenoperation +, -, *, / werfen auch einen Fehler, wenn die Größen der beiden Vektoren nicht kompatibel sind:

c = np.array([8,7,6,5])
a+c

Untersuchen wir zunächst welche Attribute und Methoden von der Klasse ndarray bereitgestelt werden. Dazu geben wir a.<TAB> bzw. dir(a) ein und bekommen eine lange Liste angezeigt. Testen wir 3 dieser Methoden aus:

print("Der kleinste Eintrag von a ist", a.min(), "bei Index", a.argmin())
print("Das Skalarprodukt <a,b> ist", a.dot(b))
print("Die Summe aller Einträge von a ist", a.sum())

Weitere Rechenoperationen

Neben diesen elementaren Funktionen, welche die Klasse ndarray direkt bereitstellt, finden wir noch weitere Rechenoperation, welche als freie Funktionen in der Bibliothek numpy implementiert sind. Wir tippen np.<TAB> ein und erhalten eine Liste all dieser Funktionen. Uns fällt auf, dass hier nochmals die Funktionen sqrt, exp, sin, cos definiert sind. Diese sind zwar schon in der math-Bibliothek vorhanden, lassen sich aber nicht auf Objekte vom Typ ndarray anwenden:

import math
math.exp(a)

Die Exponentialfunktion aus der numpy-Bibliothek hingegen wendet die Exponentialfunktion komponentenweise auf den Vektor an:

np.exp(a)

Auch die Funktionen log, sin, cos, tan, abs, ..., werden elementweise auf den Vektor angewendet. Genau so werden auch Vergleichsoperationen elementweise angewendet:

x = np.array([0,1,2,3,4])
y = (x <=2)
y

Neben diesen elementaren Rechenoperationen finden wir im Modul numpy aber auch vektorspezifische Operationen, wie beispielsweise das Skalar- und Kreuzprodukt:

np.dot(a,b)
np.cross(a,b)

Bei einer genaueren Betrachtung der Funktionen aus np stellt man fest, dass Funktionen für beispielsweise Vektornormen fehlen. Diese finden wir im Submodul numpy.linalg. Wir tippen np.linalg.<TAB> ein und erhalten wieder eine Liste aller angebotenen Funktionen.

Wir können beispielsweise wie folgt die üblichen Normen berechnen:

print("Euklidische Norm :", np.linalg.norm(a))
print("Maximumnorm      :", np.linalg.norm(a, np.inf))
print("1-Norm           :", np.linalg.norm(a, 1))

Zugriff auf Vektoreinträge

Schauen wir uns zuletzt noch an, wie auf einzelne oder mehrere Elemente des Vektors in einem bestimmten Bereich zugegriffen werden kann. Dies geschieht mit dem []-Operator:

a = np.linspace(0,1,11)
a
print("Vierter Eintrag:") # Die Zählung des Index' beginnt bei 0
a[3]

Wir können auch einen Teil des Arrays extrahieren, indem wir einen Indexbereich i:j angeben:

a[3:6] # Indizes zwischen 3 und 5
a[:6] # Indizes bis 5
a[6:] # Indizes ab 6

Wir können auch Teilvektoren überschreiben, natürlich unter Beachtung der Dimension:

a[1:4] = np.ones((3,))  # Schreibe Einsen in die Einträge 1 bis 3
a[6:8] = np.zeros((2,)) # Schreibe Nullen in die Einträge 6 und 7
a

Nebenbei haben wir hier auch 2 Methoden kennengelernt um Arrays mit Einträgen 0 oder 1 zu initialisieren.

Arbeiten mit Matrizen

Erzeugen von Matrizen

Auch für Matrizen nutzen wir den Datentyp numpy.ndarray. Zur Initialisierung übergeben wir der np.array-Funktion eine Liste bestehend aus Listen bestehend aus Zahlen. NumPy erkennt automatisch, dass wir offensichtlich ein zweidimensionales Array, also eine Matrix, erstellen wollen:

A = np.array([[1.,4.,2.],[2.,0.,3.],[1.,1.,4.]])
A

Um eine Einheitsmatrix zu erzeugen nutzen wir

B = np.eye(3)
B

Wir können auch eine leere Matrix erzeugen und die Einträge anschließend direkt eintragen:

C = np.zeros((3,3)) # Das Argument ist ein Tupel und gibt die Größer der Matrix an
C[0,0] = 1
C[1,1] = 2
C[1,2] = 5
C[2,1] = 4
C[2,2] = 1
C

Zu einem gegebenen Vektor kann man auch die zugehörige Diagonalmatrix erzeugen mit

D = np.diag(np.array([1.,2.,3.]))
D

Rechnen mit Matrizen

Wie schon bei den Vektoren beobachtet werden die Grundrechenarten sowie Funktionen exp, sin, cos, log, etx. aus dem Paket numpy elementweise angewendet:

C = A+B
C
D = A*B
D
E = np.exp(A)
E

Weitere Rechenoperationen werden von der Klasse numpy.ndarray bereitgestellt, wie das Transponieren einer Matrix:

C.transpose()

Die Matrixmultiplikation wird als freie Funktion vom Modul numpy bereitgestellt:

np.matmul(A,B)

oder alternativ mit dem @-Operator

A@B

Auch in der Bibliothek np.linalg finden wir weitere Funktionen aus der linearen Algebra, beispielsweise eine Funktion zur Berechnung der Determinante

np.linalg.det(C)

der Inversen

np.linalg.inv(C)

und der Eigenwerte- und Vektoren

E,V = np.linalg.eig(C)
print("Eigenwerte    :\n", E)
print("Eigenvektoren :\n", V)

Die Eigenvektoren stehen spaltenweise in V. Testen wir die Rechnung indem wir Cvλv=0C\,v - \lambda\,v=0 überprüfen:

for i in range(3):
    res = np.matmul(C, V[:,i]) - E[i]*V[:,i]  # C*v-lambda*v    
    print("Fehler: ", np.linalg.norm(res))

Auch für die Lösung linearer Gleichungssysteme gibt es eine Funktion aus dem Modul numpy.linalg:

b = np.array([24.,23.,30.])
x = np.linalg.solve(C, b)
x

Eine einfache Probe ergibt

np.matmul(C,x)-b

Zugriff auf Matrixeinträge

Auch der Zugriff auf einzelne Einträge bzw. auf Teilmatrizen funktioniert analog zu eindimensionalen NumPy-Arrays:

print("Eintrag C_11           :", C[0,0])
print("Zweite Spalte          :", C[:,1])
print("Dritte Zeile           :", C[2,:])
print("Letzte (=dritte) Zeile :", C[-1,:])

Matrizen stapeln

Mit den Befehlen numpy.vstack und numpy.hstack lassen sich Matrizen vertikal bzw. horizontal “zusammenkleben”:

np.vstack([A,B,C]) # Stapelt A, B, C vertikal
np.hstack([A,B,C]) # Stapelt A, B, C vertikal

Um einen Vektor an eine Matrix zu kleben müssen wir bei numpy.hstack den Vektor zunächst in eine n×1n\times 1-Matrix umwandeln wir folgendes Beispiel zeigt:

np.vstack([A,b])
np.hstack([A,b.reshape((3,1))])

Mutable oder Immutable?

Zuletzt wollen wir noch überprüfen, wie sich NumPy-Arrays als Funktionsparameter verhalten. Sind diese mutable oder immutable? Ein einfacher Test gibt:

def modify_matrix(A):
    A[1,1] = 1.
    
A = np.zeros((3,3))
modify_matrix(A)
A

Objekte vom Typ numpy.ndarray sind offensichtlich mutable. Man muss an dieser Stelle auch bei einer Zuweisung von Matrizen aufpassen:

B = A
B[1,1] = 2.
A

Wir haben hier den Namen B an das gleiche Objekt wie A gebunden. Nachdem wir B verändert haben, hat sich diese Änderung offensichtlich auch auf die Matrix A ausgewirkt. Es wird also keine Kopie der Matrix angezeigt.

Möchte man tatsächlich eine Kopie einer Matrix erstellen, so nutzt man

B = np.copy(A)
B[1,1] = 3.
A

Wir haben hier B verändert, die Änderung wirkt sich aber nicht auf die Matrix A aus.

Finanzmathematik mit NumPy

NumPy, genauer gesagt das Modul numpy-financial, stellt Standardfunktionen für Finanzmathematik bereit, die man z. B. für Investitionsrechnungen, Kredite oder Cashflow-Analysen braucht.

Wir installieren numpy-financial mit folgendem Befehl in unserer Konsole bzw. Terminal.

conda install -c conda-forge numpy-financial 

In Python importieren wir das Modul dann wie folgt:

import numpy_financial as npf

Finanzfunktionen in NumPy

FunktionBeschreibung
fv(rate, nper, pmt, pv[, when])Endwert (Future Value) berechnen
ipmt(rate, per, nper, pv[, fv, when])Zinsanteil einer Zahlung berechnen
irr(values)Interne Verzinsung (IRR) berechnen
mirr(values, finance_rate, reinvest_rate)Modifizierte interne Verzinsung (MIRR) berechnen
nper(rate, pmt, pv[, fv, when])Anzahl der Perioden berechnen
npv(rate, values)Kapitalwert (Net Present Value, NPV) berechnen
pmt(rate, nper, pv[, fv, when])Zahlungsrate (Annuität = Zins + Tilgung) berechnen
ppmt(rate, per, nper, pv[, fv, when])Tilgungsanteil einer Zahlung berechnen
pv(rate, nper, pmt[, fv, when])Barwert (Present Value) berechnen
rate(nper, pmt, pv, fv[, when, guess, tol, ...])Zinssatz pro Periode berechnen