Library zur effizienten Datenverarbeitung
Daten sind in mehrdimensionalen Arrays von Zahlen gespeichert, die resourcenschonend umgesetzt sind:
Daten können z.B. Bilder, Tondateien, Messwerte und vieles anderes repräsentieren
oft verkürzt als:
import numpy as np
Erstellen eines 1-dimensionalen Arrays:
a1d = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Erstellen eines 2-dimensionalen Arrays:
a2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Ausgabe:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Erstellen eines 3-dimensionalen Arrays:
a3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7,8]]])
Ausgabe:
array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
Arrays sind im Hintergrund in C implementiert, die numerischen Einträge (z.B. Integer) sind keine Python-Objekte und damit resourcenschonender.
Python-Liste (referenziert Integer-Objekte):
list_a = [1, 2, 3, 4]
NumPy Array (Daten sind im Array enthalten ohne auf externe Objekte zu verweisen):
array_a = np.array(list_a)
Schnelle elementweise Operation (in C implementiert):
array_a * array_a
Ãœbung:
Vergleiche die Ausführungszeit einer Operation in reinem Python und in NumPy mittels time.perf_counter()
z.B. berechne die Quadratwurzeln aller Zahlen von 0 bis 1 000 000
Wir können folgendes abfragen:
a3d.shape
: (2, 2, 2)a3d.ndim
: 3a3d.size
: 8aus dem Zen of Python:
There should be one-- and preferably only one --obvious way to do it.
diese Philosophie wird bei NumPy oft nicht angewendet
Beispiel: Transponieren eines Arrays
a2d.T
a2d.transpose()
np.transpose(a2d)
viele Operationen sind auf zwei Arten verfügbar:
numpy
-Paketarray
-Klassewir werden meist Funktionen verwenden
verfügbar als Funktionen und Methoden:
np.max(a2d)
a2d.max()
np.round(a2d)
a2d.round()
nur als Funktionen verfügbar:
np.sin(a2d)
np.exp(a2d)
np.expand_dims(a2d, 2)
Ein Array der Größe 2x6, gefüllt mit Nullen:
np.zeros((2, 6))
# or
np.full((2, 6), 0.0)
Zahlenfolgen erstellen:
np.linspace(0, 1.0, 11)
# [0.0, 0.1, ... 1.0]
np.arange(0, 3.14, 0.1)
# [0.0, 0.1, ... 3.1]
Ein 2x2 Array mit Zufallswerten:
# create a random number generator
rng = np.random.default_rng(seed=1)
# floats between 0 and 1:
rng.random((2, 2))
# integers between 1 and 6:
rng.integers(1, 7, (2, 2))
älteres Interface: np.random.random()
und np.random.randint()
a1d[0] # 0
a2d[0, 1] # 2
a2d[0, :] # [1, 2, 3]
a2d[:, 0] # [1, 4, 7]
bei 2D-Arrays: [Zeilenindex, Spaltenindex]
im Allgemeinen:
a2d[0, :] # [1, 2, 3]
Kurzform:
a2d[0] # [1, 2, 3]
a1d[:3] # [0, 1, 2]
a1d[3:6] # [3, 4, 5]
a1d[6:] # [6, 7, 8, 9]
a1d[0:8:2] # [0, 2, 4, 6]
a1d[3:0:-1] # [3, 2, 1]
a1d[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
a2d[1:, :] # [[5, 6, 7], [8, 9, 10]]
gleiches funktioniert mit Python-Listen
Operatoren werden elementweise angewendet:
a = np.array([0, 1, 2, 3])
b = np.array([2, 2, 2, 2])
-a
# np.array([0, -1, -2, -3])
a + b
# np.array([2, 3, 4, 5])
a * b
# np.array([0, 2, 4, 6])
Elementweises Vergleichen von Arrays:
a < b
# np.array([True, True, False, False])
a == b
# np.array([False, False, True, False])
Achtung: a == b
kann nicht sinnvoll in if-Abfragen verwendet werden - verwende np.array_equal(a, b)
.
Auch mit einzelnen Zahlen möglich (broadcasting):
print(a + 1)
# np.array([1, 2, 3, 4])
Einige Konstanten sind direkt in NumPy verfügbar:
print(a + np.pi)
print(a + np.e)
print(np.nan)
NumPy bietet spezielle Funktionen, die elementweise angewendet werden:
print(np.sin(a))
# [0.0, 0.84147098, 0.9... ]
print(np.sqrt(a))
# [0.0, 1.0, 1.414... ]
abs
sin
cos
sqrt
exp
log
log10
round
Aggregationen berechnen beispielsweise Werte zu jeder Zeile / jeder Spalte oder zu einem ganzen Array
Gesamtsumme:
np.sum(a2d)
Summe entlang Achse 0 ("richtung unten")
np.sum(a2d, axis=0)
Summe entlang Achse 1 ("richtung rechts")
np.sum(a2d, axis=1)
sum
min
max
std
percentile
(siehe nächste Slides)
Gegeben sind ein Array von Preisen und ein Array von gekauften Mengen. Bestimme den Gesamtpreis:
prices = np.array([3.99, 4.99, 3.99, 12.99])
# buying the first item 3 times and the last item 2 times
quantities = np.array([3, 0, 0, 2])
# solution: 37.95
Gegeben sind die Massen und Geschwindigkeiten einiger Körper; bestimme die kinetische Energie aller einzelnen Körper und die gesamte kinetische Energie aller Körper zusammen
masses = np.array([1.2, 2.2, 1.5, 2.0])
velocities = np.array([12.0, 14.0, 14.0, 7.5])
Formel: E = m*v^2 / 2
Gegeben sind die Koordinaten von Eckpunkten eines Dreiecks. Bestimme den Schwerpunkt (arithmetisches Mittel der Eckpunkte).
a = np.array([5, 1])
b = np.array([6, 8])
c = np.array([1, 3])
# solution: [4, 4]
Erstelle eine Wertetabelle für Sinus- und Kosinusfunktion im Intervall von 0 bis 2*pi.
Resultat:
# x, sin(x), cos(x)
np.array([[0.0, 0.01, 0.02, ...],
[0.0, 0.0099998, 0.0199999, ...],
[1.0, 0.99995, 0.99980, ...]])
Überprüfe anhand der Daten, ob näherungsweise gilt: sin(x)^2 + cos(x)^2 = 1
Simuliere 1 Million Mal würfeln mit je 10 Würfeln
a = np.array([4.1, 2.7, -1, 3.8, -1])
a_valid = a > 0
# array([True, True, False, True, False])
a_filtered = a[a_valid]
# array([4.1, 2.7, 3.8])
a_invalid = a < 0
a_with_nans = np.copy(a)
a_with_nans[a_invalid] = np.nan
# array([4.1, 2.7, nan, 3.8, nan])
a = np.array([4.1, 2.7, -1, 3.8, -1])
a_filtered = a[a >= 0]
a_with_nans = np.copy(a)
a_with_nans[a_with_nans < 0] = np.nan
ein int8 besteht aus 8 bits und kann 2^8 (256) verschiedene Zahlen darstellen
Anzahl an darstellbaren Zahlen:
Ein unsigned integer (uint) kann nur nicht-negative Zahlen repräsentieren
z.B. bei uint8: 0 bis 255
Standard für die Repräsentation reeller Zahlen in Computern: IEEE 754
wichtige Gleitkommatypen:
Rundungsfehler: manche Zahlen können nicht als Gleitkommazahlen dargestellt werden, sie sind immer Annäherungen
Beispiele im Dezimalsystem: 1/3, 1/7, π
Beispiele im Binärsystem (floats): 1/10, 1/5, 1/3, π
Beispiel: π + π ergibt 6.2
, wenn wir Dezimalzahlen mit 2 Stellen verwenden (besseres Ergebnis wäre 6.3
)
Beispiel: 0.1 + 0.2
ergibt ~ 0.30000000000000004
, wenn wir 64-bit floats verwenden
manche Operationen führen zu einem Verlust von Genauigkeit - z.B. Subtrahieren von nahe beieinanderliegenden Zahlen
Beispiel:
a = 0.001234567 (7 relevante Dezimalstellen)
b = 0.001234321 (7 relevante Dezimalstellen)
c = a - b
c = 0.000000246 (3 relevante Dezimalstellen)
Besondere Werte in IEEE 754:
inf
und -inf
(unendliche Werte)nan
(not-a-number: undefinierter / unbekannter Wert)Speicherformat:
(-) 2^e * s
pi als float32:
0 10000000 10010010000111111011011
2*pi als float32:
0 10000001 10010010000111111011011
pi/2 als float32:
0 01111111 10010010000111111011011
Die Zahlen 0.20000000, 0.20000001, ... 0.20000005 als nächstgelegene float32 ausgedrückt:
0 01111100 10011001100110011001101
0 01111100 10011001100110011001101
0 01111100 10011001100110011001110
0 01111100 10011001100110011001111
0 01111100 10011001100110011001111
0 01111100 10011001100110011010000
Avogadro-Konstante (6.02214076 * 10^23):
0 11001101 11111110000110001000001
Planck-Länge (1.61625518 * 10^-35):
0 00001011 01010111101011110110100
größte float32-Zahl:
0 11111110 11111111111111111111111
~ 2^127.9999 ~ 3.403e38
kleinste positive float32 Zahl mit voller Präzision:
0 00000001 00000000000000000000000
= 2^-126 ~ 1.175e-36
größere Zahlen ergeben inf
kleinere Zahlen verlieren Genauigkeit oder ergeben 0.0
inf: 0 11111111 00000000000000000000000
nan: 0 11111111 00000000000000000000001
Jedes NumPy Array kann nur Daten eines Typs enthalten (z.B. nur 64-bit floats oder nur bytes)
Jedes Array hat einen vorgegebenen Datentyp für alle Einträge
a = np.array([1])
a.dtype # int32
b = np.array([1.0])
b.dtype # float64
c = np.array(['abc'])
c.dtype # <U3
d = np.array([b'abc'])
d.dtype # |S3
Typen können explizit angegeben werden:
a = np.array([1, 2, 3, 4], dtype='int64')
b = np.array([1, 2, 3, 4], dtype='uint8')
Typen werden wenn möglich automatisch umgewandelt:
c = a + b
c.dtype # int64
wichtige Typen:
Genauigkeit für float Typen:
Floats haben ebenfalls einen Minimal- und Maximalwert
float16: genau für etwa 3 Dezimalstellen
np.array([2.71828, 0.271828], dtype="float16")
# array([2.719 , 0.2717])
float16: overflow
np.array([65450, 65500, 65550], dtype="float16")
# array([65440, 65500, inf])
float16: underflow
np.array(
[3.141e-5, 3.141e-6, 3.141e-7, 3.141e-8, 3.141e-9],
dtype="float16"
)
# array([3.14e-05, 3.16e-06, 2.98e-07, 5.96e-08, 0.00e+00])
Mehrere Operationen in NumPy erstellen views der Daten - mehrere arrays können im Hintergund auf die gleichen Daten verweisen (für bessere Effizienz)
Beispiel: Erstellen einer Kopie einer Liste, Erstellen eines Views eines Arrays
list = [1, 2, 3]
list_copy = list[:]
list_copy[0] = 10 # does NOT change list
array = np.array([1, 2, 3])
array_view = array[:]
array_view[0] = 10 # DOES change array
Arrays können via np.copy()
kopiert werden
np.reshape(a3d, (8, )) # 1d array
np.reshape(a3d, (2, 4)) # 2d array
Automatische Größe entlang einer Achse:
np.ravel(a3d) # 1d array
np.reshape(a3d, (-1, )) # 1d array
np.reshape(a3d, (2, -1)) # 2d array
diese Operationen erstellen Views
Umkehren der Achsenreihenfolge:
np.transpose(a2d)
a2d.T
entlang einer bestehenden Achse aneinanderfügen (standardmäßig Achse 0):
np.concatenate([a1d, a1d])
np.concatenate([a2d, a2d])
np.concatenate([a2d, a2d], axis=1)
entlang einer neuen Achse aneinanderfügen:
np.stack([a1d, a1d])
np.transpose(m)
np.linalg.inv(m)
np.eye(2) # unit matrix
mittels des binären Operators @
Beispiel: Rotation verschiedener Punkte um 45° bzw 90° (gegen den Uhrzeigersinn):
points = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
m = np.array([[np.sqrt(0.5), np.sqrt(0.5)],
[-np.sqrt(0.5), np.sqrt(0.5)]])
print(points @ m)
print(points @ m @ m)
Beispiel:
bekannt: Preise verschiedener Produkte, derent Bestände in verschiedenen Lagern
prices = np.array([3.99, 12.99, 5.90, 15])
quantities = np.array([[0, 80, 80, 100],
[100, 0, 0, 0],
[50, 0, 0, 50]])
Gesucht: Warenwert pro Lager