Numpy
Inhalt
Numpy¶
numpy
ist vielleicht die wichtigste Python-Bibliothek welche nicht Teil der Python-Standardbibliothek. Viele weitere Python-Bibliothek basieren entweder auf numpy
oder bieten eine numpy
-Schnittstelle. Beispiele sind scipy
, matplotlib
, pandas
, sklearn
, pytorch
oder python-control
.
Geschichte von Numpy
Die Geschichte von numpy
ist eng mit dem Namen Travis Oliphant verbunden. Wer die Geschichte von numpy
lernen will, sollte die Folge-224 vom Lex Friedman Podcast hören.
Installation¶
Per Konvention wird numpy
mit dem Alias Namen np
versehen. Daran sollten wir uns halten.
numpy
wird ständig weiterentwickelt. Mit __version__
kann die Version ausgelesen werden.
import numpy as np
np.__version__
'1.21.2'
Numpy Arrays - Vektoren, Matrizen, 3D Tensoren¶
Der Hauptvorteil den numpy
bring, sind die n-dimensionalen Arrays. In diesem Buch beschränken wir uns jedoch auf Vektoren, Matrizen und 3D Tensoren. Sind diese Objekte gut verstanden, fällt eine Verallgemeinerung auf n-dimensionale Objekte meist nicht schwer.
Skalar¶
Als Skalar wird eine mathematische Größe verstanden, die mit einem Zahlenwert charakterisiert wird. Skalare Größen besitzen keine Achsen bzw. Dimensionen.
Die Kreiszahl \(\pi\) ist ein solche Skalare Größe. Diese ist als Konstante in numpy
enthalten.
np.pi
3.141592653589793
# (np.pi).shape # error
Warnung
(np.pi).shape
führt auf einen Fehler, weil skalare Größen keine Dimensionen besitzen. Das unterscheidet skalare Größen fundamental von Arrays.
Es sei aber sofort darauf hingewiesen, dass auch Arrays skalare Werte abbilden können. [np.pi]
ist ein Vektor, [[np.pi]]
ist eine Matrix, und [[[np.pi]]]
ein 3D Tensor.
Vektoren¶
Wir wollen hier Vektoren in mathematische Vektoren und technische/physikalische Vektoren unterscheiden.
Als mathematische Vektoren
wollen wir Objekte bezeichnen welche nur eine Achse bzw. Dimension besitzen. Diese Art von Vektoren wird in der Mathematik verwendet.
v = np.array([1,2,3]) # (mathematical) vector
print(v)
print(v.shape) # a vector has only 1-dimension
[1 2 3]
(3,)
Als technische Vektoren wollen wir Objekte bezeichnen welche zwei Achsen bzw. Dimension besitzen, wobei eine der beiden Dimensionen immer nur \(1\) sein darf.
Als Zeilenvektoren
werden \(1 \times n\) Matrizen bezeichnet.
Als Spaltenvektoren
werden \(n \times 1\) Matrizen bezeichnet.
Viele technische Bücher benutzen technische Vektoren. Manche Bücher erlauben nur das Definieren von Spaltenvektoren \(v\) und Zeilenvektoren dürfen nur als Transponierte Spaltenvektoren \(v^T\) auftreten.
v = np.array([[1,2,3]]) # row vector
print(v)
print(v.shape) # a row vector has 2-dimensions
[[1 2 3]]
(1, 3)
v =np.array([[1],[2],[3]]) # column vector
print(v)
print(v.shape) # a column vector has 2-dimensions
[[1]
[2]
[3]]
(3, 1)
Matrizen¶
Objekte mit 2 Achsen bzw. Dimensionen werden als Matrizen bezeichnet.
Unter einer Matrix vom Typ \(m \times n\) (man spricht auch von einer \(m \times n\) Matrix) versteht man
X_matrix = np.array(
[[1, 2],
[2, 3]])
print(X_matrix)
print(X_matrix.shape)
[[1 2]
[2 3]]
(2, 2)
3D Tensoren¶
Arrays und Tensoren können als Verallgemeinerungen von Matrizen verstanden werden. Arrays können beliebige Werte beinhalten und Tensoren normalerweise nur Zahlenwerte.
Unter einem Array/Tensor vom Typ \(l \times m \times n\) versteht man
Mehrdimensionale Arrays lassen sich nur eingeschränkt in einer 2D Form darstellen.
X_tensor = np.array(
[[[11, 12],
[13, 14]],
[[21, 22],
[23, 24]],
[[31, 32],
[33, 34]]])
print(X_tensor)
print(X_tensor.shape)
[[[11 12]
[13 14]]
[[21 22]
[23 24]]
[[31 32]
[33 34]]]
(3, 2, 2)
Die neue Dimension wird einer Matrix vorangestellt.
print(X_tensor[0,0,0])
print(X_tensor[1,0,0])
print(X_tensor[2,0,0])
11
21
31
Konstanten¶
numpy
beinhaltet einige vordefinierte Konstanten. Eine vollständige Liste kann unter https://numpy.org/doc/stable/reference/constants.html gefunden werden.
Wichtig für viele Felder ist die Kreiszahl \(\pi\).
np.pi
3.141592653589793
Auch die Eulerkonstante \(e\) ist vorhanden.
np.e
2.718281828459045
Unendlichkeit.
np.inf
inf
Wert ist keine Nummer (nan = not a number).
np.nan
nan
Negative Null.
np.NZERO
-0.0
Positive Null.
np.PZERO
0.0
Arrays¶
numpy
bringt moderne N-Arrays und Matrizen in die Programmiersprache Python. Zwar besitzt Python mit list
N-Dimensionale Listen, aber diesen sind für mathematischen Operation nicht gut geeignet.
l = [[1., 2.], [3., 4.]]
l
[[1.0, 2.0], [3.0, 4.0]]
type(l)
list
A = np.array(l)
A
array([[1., 2.],
[3., 4.]])
type(A)
numpy.ndarray
Numpy Arrays - Typen¶
Numpy Arrays können aus verschiedenen Typen bestehen welche unter https://numpy.org/devdocs/user/basics.types.html aufgelistet sind.
Wir wollen hier nur ein paar wichtige Datentypen kennen lernen.
v = np.zeros(3)
print(v)
print(type(v))
print(type(v[0]))
[0. 0. 0.]
<class 'numpy.ndarray'>
<class 'numpy.float64'>
v = np.zeros(3,dtype=np.float32)
print(v)
print(type(v))
print(type(v[0]))
[0. 0. 0.]
<class 'numpy.ndarray'>
<class 'numpy.float32'>
v = np.zeros(3,dtype=int)
print(v)
print(type(v))
print(type(v[0]))
[0 0 0]
<class 'numpy.ndarray'>
<class 'numpy.int64'>
v = np.zeros(3,dtype=complex)
print(v)
print(type(v))
print(type(v[0]))
[0.+0.j 0.+0.j 0.+0.j]
<class 'numpy.ndarray'>
<class 'numpy.complex128'>
v = np.zeros(3,dtype=bool)
print(v)
print(type(v))
print(type(v[0]))
[False False False]
<class 'numpy.ndarray'>
<class 'numpy.bool_'>
Arrays - Anlegen¶
Vektoren - Anlegen¶
np.zeros(2)
array([0., 0.])
np.ones(2)
array([1., 1.])
np.array([0., 1.])
array([0., 1.])
np.array((0., 1.))
array([0., 1.])
np.arange(2)
array([0, 1])
np.empty(2)
array([4.63848754e-310, 0.00000000e+000])
np.linspace(0, 5, num=3)
array([0. , 2.5, 5. ])
Wenn wir gezielt einen Zeilenvektor der Form \(\mathbb{R}^{1 \times n}\) anlegen wollen können wir das mit dem Hinzufügen von []
um den Vektor. Es handelt sich hier aber eigentlich schon um eine Matrix.
np.array([[1,2,3]])
array([[1, 2, 3]])
Oder wir verwenden den Befehl np.expand_dims
.
np.expand_dims(np.array([1,2,3]), axis=0)
array([[1, 2, 3]])
Wenn wir gezielt einen Spaltenvektor der Form \(\mathbb{R}^{n\times1}\) anlegen wollen können wir das mit dem Hinzufügen von []
um jeden Vektorwert. Es handelt sich hier aber eigentlich schon um eine Matrix.
np.array([[1],[2],[3]])
array([[1],
[2],
[3]])
Oder wir verwenden den Befehl np.expand_dims
.
np.expand_dims(np.array([1,2,3]), axis=1)
array([[1],
[2],
[3]])
Matrizen - Anlegen¶
np.empty((3,3))
array([[4.63848756e-310, 0.00000000e+000, 8.48798317e-313],
[2.56761491e-312, 2.14321575e-312, 2.50395503e-312],
[8.70018274e-313, 4.63848623e-310, 3.95252517e-322]])
np.zeros((3,2))
array([[0., 0.],
[0., 0.],
[0., 0.]])
np.ones((3,2))
array([[1., 1.],
[1., 1.],
[1., 1.]])
np.eye(3)
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
np.eye(3, k=1)
array([[0., 1., 0.],
[0., 0., 1.],
[0., 0., 0.]])
np.eye(3, k=-1)
array([[0., 0., 0.],
[1., 0., 0.],
[0., 1., 0.]])
np.diag([1,2,3])
array([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
np.diag([1,2,3], k=1)
array([[0, 1, 0, 0],
[0, 0, 2, 0],
[0, 0, 0, 3],
[0, 0, 0, 0]])
Array - Dimension¶
x = np.array([0.,.1])
x.shape
(2,)
x = np.array([[0., .1]])
x.shape
(1, 2)
x = np.array([[0.],[.1]])
x.shape
(2, 1)
x = np.array((1, 2, 3, 4))
x.shape
(4,)
x.shape = (2,2)
x
array([[1, 2],
[3, 4]])
Array - Indexierung¶
Der Zugriff auf Element eines Arrays wird als Indexierung bezeichnet.
z = np.linspace(1, 2, 5)
z
array([1. , 1.25, 1.5 , 1.75, 2. ])
len(z)
5
Numpy Arrays beginnen mit dem Index \(0\) und enden mit Index \(N-1\).
z[0]
1.0
z[4]
2.0
Wollen wir den Datentyp behalten können wir :
benutzen.
z[:1]
array([1.])
z[4:]
array([2.])
Wir können auch in die umgekehrte Richtung gehen. Dazu benutzen wir einen negativen Index. Der Index beginnt hier mit \(-1\) und endet mit \(-N\).
z[-1]
2.0
z[-5]
1.0
Wollen wir den Datentyp behalten können wir :
benutzen.
z[-1:]
array([2.])
z[:-4]
array([1.])
Wir können auch mehrere Elemente selektieren, indem wir \([start]:[end]\) benutzen. Dabei werden nur die Elemente bis \(end-1\) selektiert.
z[0:3] # select elements with the index 0,1,2
array([1. , 1.25, 1.5 ])
z[1:5] # select elements with the index 1,2,3,4
array([1.25, 1.5 , 1.75, 2. ])
Starten wir mit dem Index \(0\) oder Enden wir mit dem Index \(end\) können wir diese auch weglassen.
z[:3] # select elements with the index 0,1,2
array([1. , 1.25, 1.5 ])
z[1:] # select elements with the index 1,2,3,4
array([1.25, 1.5 , 1.75, 2. ])
Auch ein gezieltes zugreifen auf mehrere Elemente ist möglich.
idx = [0,2,4] # idx as list
z[idx]
array([1. , 1.5, 2. ])
idx = np.array([0,2,4]) # idx as numpy array
z[idx]
array([1. , 1.5, 2. ])
Die Indexierung mit einem gleichgroßen Booleanarray als Maske kann sehr hilfreich sein.
idx_bool = np.array([1, 0, 0, 0, 1], dtype=bool)
print(idx_bool)
z[idx_bool]
[ True False False False True]
array([1., 2.])
zz = np.zeros(3)
print(zz)
zz[:] = 21
print(zz)
[0. 0. 0.]
[21. 21. 21.]
Array - Operationen¶
x = np.array((5, 4, 3, 2, 1))
x
array([5, 4, 3, 2, 1])
x.sort()
x
array([1, 2, 3, 4, 5])
x.sum()
15
x.mean()
3.0
x.max()
5
x.argmax()
4
x.cumsum()
array([ 1, 3, 6, 10, 15])
x.cumprod()
array([ 1, 2, 6, 24, 120])
x.var()
2.0
x.std()
1.4142135623730951
Elementweise Operationen¶
In NumPy sind im Gegensatz von z.B “Matlab” alle Operationen elementweise Operationen.
Die Operatoren +
,−
,∗
,/
und ∗∗
wirken also alle elementweise auf das Array.
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
x+10
array([11, 12, 13, 14])
x-10
array([-9, -8, -7, -6])
x*10
array([10, 20, 30, 40])
x/10
array([0.1, 0.2, 0.3, 0.4])
x**10
array([ 1, 1024, 59049, 1048576])
x+y
array([ 6, 8, 10, 12])
x-y
array([-4, -4, -4, -4])
x*y
array([ 5, 12, 21, 32])
x/y
array([0.2 , 0.33333333, 0.42857143, 0.5 ])
x**y
array([ 1, 64, 2187, 65536])
Array Muliplikationen¶
Für Array Multiplikationen stehen verschieden Befehle zur Verfügung. Diese Multiplikation kann np.dot
durchgeführt werden. Seit Python 3.5 steht auch noch der @
Operator zur Verfügung.
Warnung
Wichtige zu verstehen sind die Dimensionen der Objekte und wie sie das Ergebnis beeinflussen.
Vektor Multiplikationen¶
Wir starten mit den mathematischen Vektoren \(x \in \mathbb{R}^{n}\) und \(y \in \mathbb{R}^{n}\).
x = np.array([1, 2])
y = np.array([10, 20])
print(x.shape)
print(y.shape)
(2,)
(2,)
Das Skalarprodukt oder inneres Produkt kann mit np.dot
oder @
berechnet werden.
Das Resultat reduziert sich auf einen Skalar.
np.dot(x,y)
50
x.dot(y)
50
x@y
50
Warnung
Fast immer kann der @
Operator np.dot
ersetzen. Jedoch erlaubt np.dot
eine Multiplikation mit einem Skalar-Datentyp, der @
Operator jedoch nicht. Dieser Unterschied kann zu Fehlern führen, wenn er nicht bedacht wird.
np.dot(x,1) # x @ 1 liefert dagegen einen Fehler
array([1, 2])
np.dot(1,x) # 1 @ x liefert dagegen einen Fehler
array([1, 2])
Wir starten mit technischen Spaltenvektoren (Matrizen) \(x \in \mathbb{R}^{n \times 1}\) und \(y \in \mathbb{R}^{n \times 1}\).
x2 = np.array([[1, 2]]).T
y2 = np.array([[10, 20]]).T
print(x2.shape)
print(y2.shape)
(2, 1)
(2, 1)
Das Skalarprodukt oder inneres Produkt kann mit np.dot
oder @
berechnet werden. Es handelt sich hier aber eigentlich um ein Matrizenprodukt und die Dimensionen müssen nun übereinstimmen. Im reellen Fall gilt
Das Resultat ist zwar auch hier notwendigerweise ein Skalar, aber die Dimension des Ergebnisses ist mit (1, 1) geben.
np.dot(x2.T,y2)
array([[50]])
x2.T.dot(y2)
array([[50]])
x2.T@y2
array([[50]])
(x2.T@y2).shape
(1, 1)
Matrix Multiplikationen¶
Kopieren von Arrays¶
Tiefes Kopieren spielt auch bei numpy
-Arrays eine wichtige Rolle.
Referenzen¶
Wir wollen einen Vektor anlegen.
x = np.array([1, 2, 3])
x
array([1, 2, 3])
Durch eine Zuweisung entsteht eine neue Referenz.
y=x
y
array([1, 2, 3])
Durch eine Zuweisung entsteht eine neue Referenz, jedoch kein neuer Speicher, was sich durch id
schnell überprüfen lässt.
print(id(x))
print(id(y))
140590591083600
140590591083600
Wird nun ein Element des neuen Vektors verändert, verändert sich auch der ursprüngliche Vektor.
y[0] = 0
print(x)
print(y)
[0 2 3]
[0 2 3]
Tiefes Kopieren¶
Eine echte Kopie eines Arrays erhalten wir durch die Verwendung der Methode np.copy()
.
x = np.array([1, 2, 3])
x
array([1, 2, 3])
y = np.copy(x)
x
array([1, 2, 3])
Durch copy
ist ein neuer Speicherplatz geschaffen worden.
print(id(x))
print(id(y))
140590591028560
140590591085904
Wird nun ein Element des neuen Vektors verändert, verändert sich der ursprüngliche Vektor nicht.
y[0] = 0
print(x)
print(y)
[1 2 3]
[0 2 3]
Weitere Funktionalität¶
Vektorisierte Funktionen¶
x = np.array([1, 2, 3])
n = len(x)
y = np.empty(n)
for i in range(n):
y[i] = np.sin(x[i])
y
array([0.84147098, 0.90929743, 0.14112001])
np.sin(x)
array([0.84147098, 0.90929743, 0.14112001])
np.cos(x)
array([ 0.54030231, -0.41614684, -0.9899925 ])
np.sqrt(x)
array([1. , 1.41421356, 1.73205081])
Ebenen (Vektorisieren)¶
Vektorisieren¶
Die Methode flatten()
kann für viele Algorithmen sehr sinnvoll sein. Am Einfachsten zu verstehen ist es am Beispiel der Matrix
welche durch die Methode flatten
in einen Vektor
umgeformt werden kann.
a = np.array([[1, 2],
[3, 4]])
print(a)
print(a.shape)
[[1 2]
[3 4]]
(2, 2)
Besonders wichtig ist hier, dass die Matrix eine Dimension verliert. Also aus \(\mathbb{R}^{2\times2}\) wird also wirklich ein mathematischer Vektor \(\mathbb{R}^{4}\).
f = a.flatten()
print(f)
print(f.shape)
[1 2 3 4]
(4,)
Wird ein Zeilenvektor der Form \(\mathbb{R}^{1\times4}\) benötigt, kann das mit der Methode expand_dims
erreicht werden.
f = np.expand_dims(a.flatten(), axis=0)
print(f)
print(f.shape)
[[1 2 3 4]]
(1, 4)
Wird ein Spaltenvektor der Form \(\mathbb{R}^{1\times4}\) benötigt, kann das mit der Methode expand_dims
erreicht werden.
f = np.expand_dims(a.flatten(), axis=1)
print(f)
print(f.shape)
[[1]
[2]
[3]
[4]]
(4, 1)
Tipp
Zeilen und Spaltenvektoren sind eigentlich zwei dimensionale Matrizen, wo ein der beiden Achsen die Dimension 1 besitzt. Mathematische Vektoren besitzen nur eine Dimension.
Wir wollen nun ein Beispiel besprechen, wo die Methode flatten
angewendet werden kann.
Gegeben sei ein quadratische Lyapunov Funktion
Diese kann nun mit \(V=vec(xx^T)vec(P)\) in die lineare Parameterform \(V=\phi(x)^Tp\) umgeschrieben werden. Für vec können wir flatten
nutzen.
P = np.array([[2,1],[1,2]])
P
array([[2, 1],
[1, 2]])
x = np.array([[5,10]]).T
V1 = x.T@P@x
V1
array([[350]])
p = np.expand_dims(P.flatten(), axis=1) # row vector
phi = np.expand_dims((x@x.T).flatten(), axis=0) # column vector
V2 = phi@p
V2
array([[350]])
Stapeln¶
Das Stapeln von Vektoren, Matrizen und Tensoren ist für viele Algorithmen sehr hilfreich.
Dazu stehen einige Numpy-Methoden wie stack, vstack, hstack, concatente, append
zur Verfügung.
Vektoren¶
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(a.shape)
print(b.shape)
(3,)
(3,)
Als erstes wollen wir aus den 2 Vektoren a
und b
einen einzelnen Vektor machen.
np.append(a,b)
array([1, 2, 3, 4, 5, 6])
np.concatenate((a, b), axis=0)
array([1, 2, 3, 4, 5, 6])
np.hstack((a, b))
array([1, 2, 3, 4, 5, 6])
Nun wollen wir aus den 2 Vektoren a
und b
eine Matrix erstellen.
Die Vektoren werden als Zeilenvektoren aufgefasst.
np.stack((a, b))
array([[1, 2, 3],
[4, 5, 6]])
np.vstack((a, b))
array([[1, 2, 3],
[4, 5, 6]])
np.row_stack((a,b))
array([[1, 2, 3],
[4, 5, 6]])
Die Vektoren werden als Spaltenvektoren aufgefasst.
np.stack((a, b), axis=1)
array([[1, 4],
[2, 5],
[3, 6]])
np.column_stack((a, b))
array([[1, 4],
[2, 5],
[3, 6]])
Matrizen¶
a = np.array([[1, 2],
[3, 4]])
b = np.array([[5, 6],
[7, 8]])
np.vstack((a, b))
array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
np.concatenate((a, b), axis=0)
array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
np.hstack((a, b))
array([[1, 2, 5, 6],
[3, 4, 7, 8]])
np.concatenate((a, b), axis=1)
array([[1, 2, 5, 6],
[3, 4, 7, 8]])
Aufteilen¶
Arrays können mit den Methoden split
und vsplit
einfach aufgeteilt werden.
a = np.array([[1, 3, 5, 7, 9, 11],
[2, 4, 6, 8, 10, 12]])
a.shape
(2, 6)
Mit hsplit
kann dieses Array in n Arrays aufgespaltet werden.
np.hsplit(a, 2)
[array([[1, 3, 5],
[2, 4, 6]]),
array([[ 7, 9, 11],
[ 8, 10, 12]])]
a1, a2 = np.hsplit(a, 2)
print(a1.shape)
print(a2.shape)
(2, 3)
(2, 3)
Mit vsplit
kann dieses Array in n Arrays aufgespaltet werden.
np.vsplit(a, 2)
[array([[ 1, 3, 5, 7, 9, 11]]), array([[ 2, 4, 6, 8, 10, 12]])]
a1, a2 = np.vsplit(a, 2)
print(a1.shape)
print(a2.shape)
(1, 6)
(1, 6)
Fazit¶
Wir haben hier wichtige Methoden der numpy
Bibliothek kennen gelernt. numpy
ist mit Sicherheit jene Python-Bibliothek welche am meisten studiert werden sollte. Kaum ein Feld kommt ohne diese Bibliothek aus.