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.

Lineare Algebra mit NumPy

NumPy installieren

In vielen mathematischen Anwendungen muss mit Vektoren und Matrizen gerechnet werden, beispielsweise bei der numerischen Berechnung von Integralen, Differentialgleichungen oder Problemen aus der Graphentheorie.

In diesem Kapitel beschäftigen wir uns daher mit einer Python-Bibliothek, die entsprechende Datentypen für Vektoren und Matrizen sowie effiziente Rechenoperationen darauf bereitstellt: NumPy.

Zunächst muss die NumPy-Bibliothek installiert werden. Bei Verwendung von Conda geschieht dies mit dem Konsolenbefehl

conda install numpy

Anschließend muss die Bibliothek in unser Python-Skript eingebunden werden. Dies könnte – wie in Abschnitt Variablen und Datentypen – mit

from numpy import *

geschehen. Diese Variante ist jedoch nicht empfehlenswert, da NumPy ebenfalls Funktionen wie sin, cos, sqrt usw. bereitstellt. Diese würden dann gleichnamige Funktionen aus anderen Bibliotheken (z. B. math) überschreiben.

Stattdessen verwenden wir üblicherweise folgende Schreibweise:

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)
a ist ein <class 'numpy.ndarray'> mit Wert [1. 2. 3.]
b ist ein <class 'numpy.ndarray'> mit Wert [6. 5. 4.]

Die Klasse ndarray repräsentiert ein mehrdimensionales Array. In unserem Fall ein Vektor.

Die Anzahl der Dimensionen erhalten wir mit

a.ndim
1

Der Datentyp der gespeicherten Elemente ist

a.dtype
dtype('float64')

Die Form des Arrays (also die Größe jeder Dimension) erhalten wir mit

a.shape
(3,)

Zwei weitere nützliche Funktionen zur Erzeugung von NumPy-Arrays sind linspace und arange:

x = np.linspace(0,3,6) # Äquidistantes Punktgitter für [0,3] aus 6 Punkten
x
array([0. , 0.6, 1.2, 1.8, 2.4, 3. ])

Die Funktion np.linspace(start, stop, num) erzeugt num gleichmäßig verteilte Punkte im Intervall [start, stop]. Beide Randpunkte werden dabei standardmäßig eingeschlossen.

x = np.arange(0, 3, 0.5) # Äquidistantes Punktgitter für [0,3) mit Inkrement 0.5
x
array([0. , 0.5, 1. , 1.5, 2. , 2.5])

Die Funktion np.arange(start, stop, step) erzeugt eine Folge von Werten beginnend bei start mit Schrittweite step. Der Endwert stop wird dabei nicht mehr erreicht (analog zur Python-Funktion range).

Elementare Vektoroperationen

Ein ndarray ist – ähnlich wie eine Liste oder ein Tupel – ein iterierbares Objekt. Wir können daher mit einer for-Schleife über alle Elemente iterieren:

val = 0
for e in a:
    val += e
print("Die Summe der Einträge von", a, "ist", val)
Die Summe der Einträge von [1. 2. 3.] ist 6.0

Mit NumPy-Arrays lassen sich außerdem elementare Rechenoperationen direkt durchführen:

a+b
array([7., 7., 7.])
b-a
array([5., 3., 1.])
a*b
array([ 6., 10., 12.])
a/b
array([0.16666667, 0.4 , 0.75 ])

Wir beobachten, dass diese Operationen elementweise ausgeführt werden.

Bei Addition und Subtraktion entspricht dies genau der Definition der Vektoraddition aus der linearen Algebra. Bei Multiplikation und Division ist dies jedoch weniger offensichtlich – hier hätte man möglicherweise das Skalarprodukt oder das Kreuzprodukt erwartet. Diese Operationen werden wir später kennenlernen.

Die Rechenoperationen +, -, * und / funktionieren nur dann, wenn die beteiligten Arrays die gleiche Form (shape) besitzen. Andernfalls tritt ein Fehler auf:

c = np.array([8,7,6,5])
a+c
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[13], line 2
      1 c = np.array([8,7,6,5])
----> 2 a+c

ValueError: operands could not be broadcast together with shapes (3,) (4,) 

Untersuchen wir nun, welche Methoden die Klasse ndarray bereitstellt. Dazu geben wir a.<TAB> in einem Notebook ein oder rufen die Funktion dir(a) auf. In beiden Fällen erhalten wir eine lange Liste verfügbarer Methoden.

Testen wir einige dieser Methoden:

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())
Der kleinste Eintrag von a ist 1.0 bei Index 0
Das Skalarprodukt <a,b> ist 28.0
Die Summe aller Einträge von a ist 6.0

Weitere Rechenoperationen

Neben den elementaren Methoden, welche die Klasse ndarray direkt bereitstellt, gibt es zahlreiche weitere Rechenoperationen, die als freie Funktionen in der Bibliothek numpy implementiert sind.

Geben wir np.<TAB> ein, erhalten wir eine Liste dieser Funktionen. Dabei fällt auf, dass hier ebenfalls Funktionen wie sqrt, exp, sin oder cos definiert sind. Diese existieren zwar auch in der Bibliothek math, lassen sich dort jedoch nicht auf NumPy-Arrays anwenden:

import math
math.exp(a)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[15], line 2
      1 import math
----> 2 math.exp(a)

TypeError: only 0-dimensional arrays can be converted to Python scalars

Dies führt zu einem Fehler, da math.exp nur für skalare Werte definiert ist.

Die entsprechende Funktion aus der numpy-Bibliothek hingegen wird elementweise auf das Array angewendet:

np.exp(a)
array([ 2.71828183, 7.3890561 , 20.08553692])

Dabei wird die Exponentialfunktion auf jede Komponente des Vektors angewendet.

Auch andere Funktionen wie log, sin, cos, tan, abs usw. werden elementweise auf NumPy-Arrays angewendet.

Ebenso funktionieren Vergleichsoperationen elementweise:

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

Das Ergebnis ist ein Array von boolschen Werten, welches für jedes Element von x angibt, ob die Bedingung erfüllt ist.

Neben diesen elementweisen Rechenoperationen finden wir im Modul numpy auch vektorspezifische Operationen, wie beispielsweise das Skalarprodukt oder das Kreuzprodukt.

np.dot(a,b)
np.float64(28.0)
np.cross(a,b)
array([-7., 14., -7.])

Bei einer genaueren Betrachtung der Funktionen im Modul numpy stellt man fest, dass viele Operationen aus der linearen Algebra im Submodul numpy.linalg organisiert sind. Dazu gehören beispielsweise Funktionen zur Berechnung von Normen, Determinanten oder Matrixinversen.

Geben wir

np.linalg.<TAB>

ein, erhalten wir eine Liste der verfügbaren Funktionen.

Mit der Funktion np.linalg.norm lassen sich verschiedene Vektornormen berechnen:

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

Zugriff auf Vektoreinträge

Schauen wir uns zuletzt noch an, wie auf einzelne oder mehrere Elemente eines NumPy-Arrays zugegriffen werden kann. Dies geschieht – wie auch bei Listen – mit dem []-Operator:

a = np.linspace(0,1,11)
a
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
print("Vierter Eintrag:") # Die Zählung des Index' beginnt bei 0
a[3]
Vierter Eintrag:
np.float64(0.30000000000000004)

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

a[3:6] # Indizes zwischen 3 und 5
array([0.3, 0.4, 0.5])

Dabei gilt allgemein:

  • i ist der Startindex (inklusive)

  • j ist der Endindex (exklusive)

Weitere Beispiele:

a[:6] # Indizes bis 5
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5])
a[6:] # Indizes ab 6
array([0.6, 0.7, 0.8, 0.9, 1. ])

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
array([0. , 1. , 1. , 1. , 0.4, 0.5, 0. , 0. , 0.8, 0.9, 1. ])

Dabei haben wir nebenbei zwei weitere Funktionen kennengelernt, mit denen sich Arrays mit konstanten Einträgen erzeugen lassen:

  • np.ones(shape) erzeugt ein Array der angegebenen Form, dessen Einträge alle 1 sind.

  • np.zeros(shape) erzeugt ein Array der angegebenen Form, dessen Einträge alle 0 sind.

Arbeiten mit Matrizen

Erzeugen von Matrizen

Auch für Matrizen verwenden wir den Datentyp numpy.ndarray. Zur Initialisierung übergeben wir der Funktion np.array eine Liste von Listen, wobei jede innere Liste einer Zeile der Matrix entspricht. NumPy erkennt automatisch, dass ein zweidimensionales Array, also eine Matrix, erzeugt werden soll:

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

print("A ist vom Typ:", type(A))
print("A hat die Form:", A.shape)
print(A)
A ist vom Typ: <class 'numpy.ndarray'>
A hat die Form: (3, 3)
[[1. 4. 2.]
 [2. 0. 3.]
 [1. 1. 4.]]
  • Die Form der Matrix (A.shape) gibt die Anzahl der Zeilen und Spalten an, also (3,3) für dieses Beispiel.

  • Intern ist A weiterhin ein ndarray, nur mit zwei Dimensionen (A.ndim == 2).

  • Genau wie bei Vektoren können wir auf einzelne Elemente oder Teilbereiche zugreifen, z.B. A[0,1] für Zeile 0, Spalte 1.

Es gibt mehrere Möglichkeiten, Matrizen in NumPy zu erzeugen:

  1. Einheitsmatrix
    Eine quadratische Einheitsmatrix kann direkt über np.eye(n) erzeugt werden:

B = np.eye(3)
B
array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])
  1. Leere Matrix und manuelles Befüllen
    Mit np.zeros((m,n)) kann eine Matrix der Größe m×n erzeugt werden, deren Einträge zunächst 0 sind. Anschließend können einzelne Einträge direkt zugewiesen werden:

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
array([[1., 0., 0.], [0., 2., 5.], [0., 4., 1.]])
  1. Diagonalmatrix aus einem Vektor
    Aus einem gegebenen Vektor v lässt sich eine Diagonalmatrix erzeugen, bei der die Elemente von v auf der Hauptdiagonalen stehen:

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

Rechnen mit Matrizen

Bei Matrizen funktionieren die elementaren Rechenoperationen ähnlich wie bei Vektoren: Addition, Subtraktion, Multiplikation und Division sowie viele Funktionen aus dem Paket numpy (exp, sin, cos, log etc.) werden elementweise auf die Matrizen angewendet.

C = A+B
C
array([[2., 4., 2.], [2., 1., 3.], [1., 1., 5.]])
D = A*B
D
array([[1., 0., 0.], [0., 0., 0.], [0., 0., 4.]])
E = np.exp(A)
E
array([[ 2.71828183, 54.59815003, 7.3890561 ], [ 7.3890561 , 1. , 20.08553692], [ 2.71828183, 2.71828183, 54.59815003]])

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

C.transpose()
array([[2., 2., 1.], [4., 1., 1.], [2., 3., 5.]])

Für lineare Algebra-Operationen nutzt man die Funktionen aus numpy oder numpy.linalg:

Matrixmultiplikation:

np.matmul(A,B) # Freie Funktion
A@B            # Alternativ mit dem @-Operator
array([[1., 4., 2.], [2., 0., 3.], [1., 1., 4.]])

Determinante:

np.linalg.det(C)
np.float64(-22.000000000000004)

Inversen:

np.linalg.inv(C)
array([[-0.09090909, 0.81818182, -0.45454545], [ 0.31818182, -0.36363636, 0.09090909], [-0.04545455, -0.09090909, 0.27272727]])

Eigenwerte- und Vektoren:

E,V = np.linalg.eig(C)
print("Eigenwerte    :\n", E)
print("Eigenvektoren :\n", V)
Eigenwerte    :
 [ 6.97413644 -1.33574651  2.36161007]
Eigenvektoren :
 [[ 0.63932126  0.77319109 -0.8514755 ]
 [ 0.50515119 -0.63379132 -0.29406667]
 [ 0.57973321 -0.02200211  0.43418229]]

Hinweis: 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))
Fehler:  3.66205343881779e-15
Fehler:  1.5852031831917945e-15
Fehler:  2.434909185329438e-15

Lösen linearer Gleichungssysteme:

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

# Probe:
np.matmul(C,x)-b
x = [3. 2. 5.]
array([ 0.00000000e+00, 0.00000000e+00, -3.55271368e-15])

Zugriff auf Matrixeinträge

Der Zugriff auf einzelne Einträge oder Teilmatrizen funktioniert analog zu eindimensionalen ndarray-Arrays:

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

Matrizen stapeln

Mit den Funktionen numpy.vstack und numpy.hstack lassen sich Matrizen vertikal bzw. horizontal zusammenfügen:

np.vstack([A,B,C]) # Stapelt A, B, C vertikal
array([[1., 4., 2.], [2., 0., 3.], [1., 1., 4.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.], [2., 4., 2.], [2., 1., 3.], [1., 1., 5.]])
np.hstack([A,B,C]) # Stapelt A, B, C horizontal
array([[1., 4., 2., 1., 0., 0., 2., 4., 2.], [2., 0., 3., 0., 1., 0., 2., 1., 3.], [1., 1., 4., 0., 0., 1., 1., 1., 5.]])

Möchte man einen Vektor an eine Matrix “kleben”, muss der Vektor ggf. zunächst in eine n×1n\times 1-Matrix umgeformt werden:

np.vstack([A,b])
array([[ 1., 4., 2.], [ 2., 0., 3.], [ 1., 1., 4.], [24., 23., 30.]])
np.hstack([A,b.reshape((3,1))])
array([[ 1., 4., 2., 24.], [ 2., 0., 3., 23.], [ 1., 1., 4., 30.]])

Hinweis: reshape((n,1)) formt den Vektor in eine Spaltenmatrix um, damit hstack korrekt funktioniert.

Mutable oder Immutable?

NumPy-Arrays verhalten sich als mutable Objekte. Das bedeutet, dass Änderungen innerhalb von Funktionen oder über neue Variablen das ursprüngliche Array verändern können:

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

Das Array A wurde durch die Funktion verändert, da ndarray mutable ist.

Achten wir auf eine einfache Zuweisung:

B = A
B[1,1] = 2.
A
array([[0., 0., 0.], [0., 2., 0.], [0., 0., 0.]])

Hier zeigt der Name B auf dasselbe Objekt wie A. Änderungen über B wirken sich also direkt auf A aus.

Möchte man eine echte Kopie erstellen, die unabhängig vom Original ist, verwendet man:

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

Änderung an B beeinflusst A nicht, da np.copy ein neues Array erzeugt.

NumPy-Glossar – Vektoren & Matrizen

Erzeugung von Arrays

FunktionBeschreibungBeispiel
np.array(seq)Wandelt iterierbares Objekt in Array umnp.array([1,2,3])
np.zeros(shape)Array gefüllt mit Nullennp.zeros((3,3))
np.ones(shape)Array gefüllt mit Einsennp.ones(5)
np.eye(n)Einheitsmatrix (n×n)np.eye(3)
np.diag(v)Diagonalmatrix aus Vektor vnp.diag([1,2,3])
np.linspace(start, stop, num)Gleichmäßig verteilte Wertenp.linspace(0,1,5)
np.arange(start, stop, step)Werte in festem Schrittnp.arange(0,3,0.5)

Basis-Attribute von Arrays (ndarray)

AttributBedeutung
.ndimAnzahl Dimensionen
.shapeForm / Größe des Arrays
.sizeAnzahl Elemente
.dtypeDatentyp der Elemente

Elementweise Operationen

Operator / FunktionBeschreibung
+,-,*,/Elementweise Addition, Subtraktion, Multiplikation, Division
np.exp(a)Elementweise Exponentialfunktion
np.log(a)Elementweise natürliche Logarithmus
np.sin(a), np.cos(a), np.tan(a)Elementweise trigonometrische Funktionen
Vergleichsoperatoren (<, <=, >, >=, ==, !=)Elementweise

Methoden von ndarray

MethodeBeschreibung
.sum()Summe aller Elemente
.min()Kleinster Wert
.max()Größter Wert
.argmin()Index des kleinsten Werts
.argmax()Index des größten Werts
.dot(b)Skalarprodukt (nur Vektoren)
.transpose()Transponierte Matrix

Freie Funktionen in numpy

FunktionBeschreibung
np.dot(a,b)Skalarprodukt / Matrix-Vektor-Produkt
np.cross(a,b)Kreuzprodukt (3D-Vektoren)
np.matmul(a,b)Matrixmultiplikation
np.vstack([A,B])Vertikales Stapeln von Matrizen
np.hstack([A,B])Horizontales Stapeln von Matrizen

Funktionen in numpy.linalg (lineare Algebra)

FunktionBeschreibung
np.linalg.norm(a, ord=2)Vektornorm (Standard: 2-Norm)
np.linalg.inv(A)Inverse einer Matrix
np.linalg.det(A)Determinante
np.linalg.eig(A)Eigenwerte und Eigenvektoren
np.linalg.solve(A,b)Löst lineares Gleichungssystem Ax=b

Zugriff auf Elemente

SyntaxBedeutung
a[i]i-ter Eintrag eines Vektors
a[i:j]Einträge i bis j-1
a[:j]Einträge 0 bis j-1
a[i:]Einträge i bis Ende
A[i,j]Eintrag (i,j) einer Matrix
A[i,:]Zeile i
A[:,j]Spalte j
A[i:j, k:l]Teilmatrix

Mutable Verhalten

  • NumPy-Arrays sind mutable. Änderungen innerhalb von Funktionen oder über Zuweisungen wirken sich auf das Original-Array aus.

  • Für echte Kopien: B = np.copy(A)