Lektion 6

Wiederholung: Definition von Arrays

In [2]:
import numpy as np
In [2]:
#' Definiere Matrix
A = np.array([[1, 2], [3, 4]])
A
Out[2]:
array([[1, 2],
       [3, 4]])
In [3]:
#' Definiere Vektor
v = np.array([1, 2])
v
Out[3]:
array([1, 2])

Einfache Matrizen / Vektoren

In [4]:
#' Matrix mit Einsen
E = np.ones((3, 2))
E
Out[4]:
array([[1., 1.],
       [1., 1.],
       [1., 1.]])
In [5]:
Ei = np.ones_like(A)
Ei
Out[5]:
array([[1, 1],
       [1, 1]])
In [6]:
#' Nullmatrix
Z = np.zeros((3, 2))
Z
Out[6]:
array([[0., 0.],
       [0., 0.],
       [0., 0.]])
In [7]:
#' Einheitsmatrix Identitaet
I = np.eye(3)
I
Out[7]:
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

Einfache Operationen für Arrays

In [8]:
#' transponierte Matrix
A
A.T
A.transpose()
#' Spur 
A.trace()
Out[8]:
5
In [9]:
#' Eigenschaften
A.size
A.shape
A.ndim
Out[9]:
2
In [10]:
#' komplexwertige Matrix
C = np.array([[1.+2.j, 2.-1.j], [2.+1.j, 3.-5.j]]);
C
Out[10]:
array([[1.+2.j, 2.-1.j],
       [2.+1.j, 3.-5.j]])
In [11]:
#' transponiert-konjugierte / adjungierte Matrix
display(C.T.conj(),C)
array([[1.-2.j, 2.-1.j],
       [2.+1.j, 3.+5.j]])
array([[1.+2.j, 2.-1.j],
       [2.+1.j, 3.-5.j]])
In [12]:
C.real
Out[12]:
array([[1., 2.],
       [2., 3.]])
In [13]:
C.imag
Out[13]:
array([[ 2., -1.],
       [ 1., -5.]])

Achtung: +, -, , %, /, ** wirken eintragsweise Die Standardoperationen wirken eintragsweise auf die Einträge der Arrays. A A ist also KEINE Matrix-Matrix-Multiplikation!

In [14]:
#' eintragsweise +
A+A
#' eintragsweise *
A*A
#' eintragsweise /
C/A
#' eintragsweise **
A**A
Out[14]:
array([[  1,   4],
       [ 27, 256]])

Broadcasting / Expansion in der Singleton Dimenson +, -, * , %, / wirken eintragsweise wobei die fehlende Dimension expandiert wird.

In [15]:
v = np.array([-5, 7])
v
Out[15]:
array([-5,  7])
In [16]:
#' Das erste Element von v wird zu jeder zu jedem Element der ersten Spalte addiert
A+v
Out[16]:
array([[-4,  9],
       [-2, 11]])
In [17]:
#' Das erste Element von v wird ...
A*v
Out[17]:
array([[ -5,  14],
       [-15,  28]])
In [18]:
A.astype(float)**v
Out[18]:
array([[1.00000000e+00, 1.28000000e+02],
       [4.11522634e-03, 1.63840000e+04]])
In [19]:
#' zu jedem Eintrag wird 1 addiert
A + 1
#' jeder Eintrag wird durch 3 dividiert
A / 3
#' 3 wird durch jeden Eintrag dividiert
3 / A
Out[19]:
array([[3.  , 1.5 ],
       [1.  , 0.75]])

Matrix-Multiplikation

In [20]:
#' mit np.dot
A
Out[20]:
array([[1, 2],
       [3, 4]])
In [21]:
np.dot(A, A)
Out[21]:
array([[ 7, 10],
       [15, 22]])
In [22]:
#' @ Operator
#' verkürzte Notation: @
#' Statt des "dot"-Befehls kann man auch das @-Zeichen verwenden.
A@A
Out[22]:
array([[ 7, 10],
       [15, 22]])
In [23]:
#' Matrix-Vektor Produkt funktioniert genau so
v = np.array([1, 2])
v
Out[23]:
array([1, 2])
In [24]:
np.dot(A, v)
Out[24]:
array([ 5, 11])
In [25]:
#' Auch hier kann @ verwendet werden
A@v
Out[25]:
array([ 5, 11])
In [26]:
#' Skalarprodukt zweier Vektoren
np.dot(v, v)
Out[26]:
5
In [27]:
#' Beachte: gleiche Resultate
np.dot(v, v.T)
Out[27]:
5
In [28]:
v@v
Out[28]:
5
In [29]:
np.dot(v.T, v)
Out[29]:
5

Wieso ist das so? 'v' ist KEIN Zeilen/Spaltenvektor (hat nur eine Dimension), also bewirkt das Transponieren nichts.

In [30]:
# Achtung
xxl = np.array([i**2. for i in range(18952)])
xxl@xxl
Out[30]:
4.889314162674018e+20
In [31]:
#' Kreuzprodukt / Vektorprodukt mit np.cross
x = np.array([1, 0, 0])
y = np.array([0, 1, 0])
np.cross(x, y)
Out[31]:
array([0, 0, 1])
In [32]:
#' Dyadisches Produkt / tensorielles Produkt
#' (Multiplikation eines Spaltenvektor mit einem Zeilenvektor liefert Matrix)
np.outer(v, v)
Out[32]:
array([[1, 2],
       [2, 4]])

Mehr Informationen zu Operationen auf Arrays finden Sie unter

https://www.python-kurs.eu/numpy_numerische_operationen_auf_arrays.php

Übersichtlicher: Definiere Vektoren als Zeilen- bzw. Spaltenvektor

In [33]:
#' @ funktionert dann immer so wie man es aus der linearen Algebra erwartet,
#' d.h. Zeilenvektor @ Spaltenvektor liefert skalare Größe,
#' Spaltenvektor @ Zeilenvektor liefert Matrix
v = np.array([1, 0]).reshape(2, 1)  # Spaltenvektor
u = np.array([0, 1]).reshape(1, 2)  # Zeilenvektor
A = np.array([[1, 2], [3, 4]])
In [34]:
#' Zahl
u@v
Out[34]:
array([[0]])
In [35]:
#' Matrix
v@u
Out[35]:
array([[0, 1],
       [0, 0]])
In [36]:
#' Matrix-Vektor-Produkt
A@v
Out[36]:
array([[1],
       [3]])
In [37]:
#' Das klappt auch von links
u@A
Out[37]:
array([[3, 4]])
In [38]:
#' Achtung, hier stimmen die Dimensionen nicht mehr!
#A@u

Weitere Arrayoperationen

In [39]:
#' np.amax(A) oder A.max() liefert das maximale Matrixelement:
np.max(A), A.max()
Out[39]:
(4, 4)
In [40]:
#'Maximum entlang der ersten Achse
A.max(1)
Out[40]:
array([2, 4])
In [41]:
#' Maximum der 1. Zeile
A
np.max(A[0, :])
Out[41]:
2
In [42]:
# "flattens" A in ein 1-dim. Array
A.flatten()
Out[42]:
array([1, 2, 3, 4])

np.argmin(B) / np.argmax(B) liefert Index bezüglich "flattened array" des kleinsten / größten Matrixelements Falls das größte / kleinste Element mehrfach vorkommt, dann erhält man denersten Index

In [4]:
B = np.array([[2, 1], [1, 2]])
B
B.max(), B.argmax()
Out[4]:
(2, 0)
In [44]:
#' liefert das (erste) maximale Element
B.flatten()[np.argmax(B)]
Out[44]:
2
In [45]:
#' Unterschied zu np.maximum
display(A,B,np.maximum(A,B))
array([[1, 2],
       [3, 4]])
array([[2, 1],
       [1, 2]])
array([[2, 2],
       [3, 4]])
In [46]:
#' broadcasting wird angewendet
v = np.array([5,1])
v
Out[46]:
array([5, 1])
In [47]:
np.maximum(A,v)
Out[47]:
array([[5, 2],
       [5, 4]])

Weitere Möglichkeiten zur Definition von Arrays

In [48]:
A = np.arange(12)
#' A ist ein 1D array
A.shape
Out[48]:
(12,)

A.reshape(i,j) transformiert A in ein array mit i Zeilen und j Spalten

In [49]:
#' B hat zwei Dimensionen
B = A.reshape(3, 4)
B.shape, B
Out[49]:
((3, 4), array([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]]))
In [50]:
#' Die Anzahl der Elemente ändert sich natürlich nicht
A.size-B.size
Out[50]:
0
In [51]:
#' Wichtig: A und B zeigen noch immer auf die selben Einträge im Speicher!
A[3] = -42
B[0,0] = -567
In [52]:
C = A.reshape(3, 4).copy()
C[0] = 123
In [53]:
display(A, B, C)
array([-567,    1,    2,  -42,    4,    5,    6,    7,    8,    9,   10,
         11])
array([[-567,    1,    2,  -42],
       [   4,    5,    6,    7],
       [   8,    9,   10,   11]])
array([[123, 123, 123, 123],
       [  4,   5,   6,   7],
       [  8,   9,  10,  11]])

np.diag, np.triu, np.tril

In [54]:
A = np.arange(16).reshape(4, 4)
A
Out[54]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
In [55]:
#'Diagonale von A
np.diag(A)
Out[55]:
array([ 0,  5, 10, 15])
In [56]:
#' Untere Dreiecksmatrix
np.tril(A)
Out[56]:
array([[ 0,  0,  0,  0],
       [ 4,  5,  0,  0],
       [ 8,  9, 10,  0],
       [12, 13, 14, 15]])
In [57]:
#' Obere Dreiecksmatrix
np.triu(A)
Out[57]:
array([[ 0,  1,  2,  3],
       [ 0,  5,  6,  7],
       [ 0,  0, 10, 11],
       [ 0,  0,  0, 15]])
In [58]:
#' np.diag kann auch Diagonalmatrizen erzeugen
B = np.diag(v.flatten())
B
Out[58]:
array([[5, 0],
       [0, 1]])
In [59]:
#' np.diag für Nebendiagonalen
NU = np.diag(A, k=1)  # obere Nebendiagonale
NU
Out[59]:
array([ 1,  6, 11])
In [60]:
NL = np.diag(A, k=-1) # untere Nebendiagonale
NL
Out[60]:
array([ 4,  9, 14])
In [61]:
#' Benutze np.diag zur Definition von Matrizen
x = [1, 2, 3]
A = np.diag(x, k=-1) + np.diag(x, k=1)
A
Out[61]:
array([[0, 1, 0, 0],
       [1, 0, 2, 0],
       [0, 2, 0, 3],
       [0, 0, 3, 0]])

Index Arrays

Wir können Integer-Arrays verwenden um andere Arrays zu definieren

In [62]:
#' 1. Beispiel zur Verwendung von Index-Arrays
# Array mit den ersten 8 Zweierpotenzen
a = 2**np.arange(8);
a
Out[62]:
array([  1,   2,   4,   8,  16,  32,  64, 128])
In [63]:
# Array mit geraden Zahlen von 0 bis 7
i = 2*np.arange(4); # Index-Array
i
Out[63]:
array([0, 2, 4, 6])
In [64]:
# Jedes zweite Element von a in b speichern
b = a[i]
b
Out[64]:
array([ 1,  4, 16, 64])
In [65]:
#' Beachte: Diesen Vektor können wir auch folgendermaßen erzeuge:
a[::2]
#' Die Konstruktion unter Verwendung von Index-Arrays ist aber im Allgemeinen flexibler
Out[65]:
array([ 1,  4, 16, 64])
In [66]:
#' 2. Beispiel zur Verwendung von Index-Arrays
# Erzeuge einen Vektor, der die Gegendiagonale einer Matrix enthält
A = np.arange(16).reshape(4, 4)
A
Out[66]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
In [67]:
i = np.arange(4)
j = i[::-1]
In [68]:
b = A[i, j]
b
Out[68]:
array([ 3,  6,  9, 12])

Verwende boolsche Ausdrücke bei der Definition von Matrizen

In [69]:
#' Bsp: Setze alle negativen Matrixeinträge auf Null
A 
B = A - A.T
B
Out[69]:
array([[ 0, -3, -6, -9],
       [ 3,  0, -3, -6],
       [ 6,  3,  0, -3],
       [ 9,  6,  3,  0]])
In [70]:
B<0
Out[70]:
array([[False,  True,  True,  True],
       [False, False,  True,  True],
       [False, False, False,  True],
       [False, False, False, False]])
In [71]:
B[B < 0] = 0
B
Out[71]:
array([[0, 0, 0, 0],
       [3, 0, 0, 0],
       [6, 3, 0, 0],
       [9, 6, 3, 0]])
In [72]:
#' elementweises logisches "oder" und "und"
B = A - A.T
B
Out[72]:
array([[ 0, -3, -6, -9],
       [ 3,  0, -3, -6],
       [ 6,  3,  0, -3],
       [ 9,  6,  3,  0]])
In [73]:
#' Welche Elemente von B sind kleiner als -5 ODER größer als 6?
IA = np.logical_or(B < -5, B > 6)
IA
Out[73]:
array([[False, False,  True,  True],
       [False, False, False,  True],
       [False, False, False, False],
       [ True, False, False, False]])
In [74]:
#' Welche Elemente von B sind kleiner als 2 UND größer als 0?
np.logical_and(B < 7, B > 0)
Out[74]:
array([[False, False, False, False],
       [ True, False, False, False],
       [ True,  True, False, False],
       [False,  True,  True, False]])
In [75]:
B[IA] = 100
B
Out[75]:
array([[  0,  -3, 100, 100],
       [  3,   0,  -3, 100],
       [  6,   3,   0,  -3],
       [100,   6,   3,   0]])

Kronecker Produkt

Erzeugt ein zusammengesetztes Array aus Blöcken des 2. Arrays unter Verwendung der durch das 1. Array beschriebenen Skalierung

In [76]:
#' einmal so rum
np.kron([1, 10, 100], [5, 6, 7])
Out[76]:
array([  5,   6,   7,  50,  60,  70, 500, 600, 700])
In [77]:
#' anders rum kommt was anderes raus
np.kron([5, 6, 7], [1, 10, 100])
Out[77]:
array([  5,  50, 500,   6,  60, 600,   7,  70, 700])
In [78]:
np.kron(np.eye(2), np.ones((2, 2)))
Out[78]:
array([[1., 1., 0., 0.],
       [1., 1., 0., 0.],
       [0., 0., 1., 1.],
       [0., 0., 1., 1.]])
In [79]:
np.kron(np.ones((2, 2)),np.eye(2))
Out[79]:
array([[1., 0., 1., 0.],
       [0., 1., 0., 1.],
       [1., 0., 1., 0.],
       [0., 1., 0., 1.]])

vstack und hstack

Füge eine Zeile oder Spalte b zu einer 3x3 Matrix A hinzu

In [80]:
#' Zeile anhängen mit np.vstack:
A = np.ones((3, 3))
b = np.array((2, 2, 2))
np.vstack([A, b])
Out[80]:
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.],
       [2., 2., 2.]])
In [81]:
#' Spalte anhängen mit np.hstack
A = np.ones((3, 3))
b = np.array((2, 2, 2)).reshape(3, 1)  # erzeugt Spaltenvektor
np.hstack([A, b])
Out[81]:
array([[1., 1., 1., 2.],
       [1., 1., 1., 2.],
       [1., 1., 1., 2.]])

Alternativ kann man auch die (mächtigere) Funktion "np.concatenate" verwenden (siehe Hilfe)