Grundkonzepte der wissenschaftlichen Programmierung mit Python – NumPy PDF
Document Details
Uploaded by ImportantPluto253
Fachhochschule Wiener Neustadt
Michael Rauter
Tags
Summary
These lecture notes cover fundamental concepts of scientific programming using Python and the NumPy library, focusing on introduction and practical aspects.
Full Transcript
EDV in der Radiologietechnologie ILV SS Michael Rauter Master Program MedTech – functional imaging, conventional and ion radiotherapy https://www.python.org/community/logos/ Grundkonzepte der wissenschaftlichen Programmierung mit Python – NumPy Überblick Thema dieses Foliensatze...
EDV in der Radiologietechnologie ILV SS Michael Rauter Master Program MedTech – functional imaging, conventional and ion radiotherapy https://www.python.org/community/logos/ Grundkonzepte der wissenschaftlichen Programmierung mit Python – NumPy Überblick Thema dieses Foliensatzes https://numpy.org/doc/stable/_static/numpylogo.svg Einführung in NumPy die Grundlagen des package https://www.w3schools.com/python/numpy/default.asp https://numpy.org/doc/stable/ 3 Programmieren mit Einführung einige Fakten über NumPy open-source Python Bibliothek/Paket, das häufig für wissenschaftliche Berechnungen eingesetzt wird universeller Standard für das Arbeiten mit numerischen Daten in Python viele andere packages verwenden NumPy als Grundlage (z.B.: scipy, matplotlib, scikit-learn, opencv-python) und bauen darauf auf (verwenden Funktionalitäten aus NumPy) Das Kern-Feature sind multidimensionale Arrays und Matrix-Datenstrukturen implementiert im speziellen Datentyp ndarray, welcher es erlaubt homogene Daten (Daten, wo alle Elemente vom selben numerischen Datentyp sind) in n-dimensionalen Arrays zu speichern (1D-Array = Vector, 2D-Array = Matrix, nD-Array) stellt Methoden und Funktionalitäten für das Arbeiten mit/auf diesen Daten in vektorisierter Form zur Verfügung → dies ist viel schneller als gewöhnliche Berechnungen mit Python durchzuführen Es erlaubt es, mathematische Funktionen auf alle Werte (Elemente) eines Arrays anzuwenden. 4 Programmieren mit Verwenden des NumPy packages in Python Einbinden von NumPy in Python jedes Python script, das Funktionalität von NumPy verwenden will, muss das NumPy package importieren NumPy wird mit dem import Kommando importiert, welches wir schon kennengelernt haben das Importieren von NumPy wird üblicherweise mit dem Umbenennen des packages in ein Alias np realisiert (dadurch werden NumPy kürzer zu schreiben, z.B: np.array statt numpy.array) Kurzanleitung: # import numpy and rename it to np: import numpy as np 5 Programmieren mit ndarrays Erzeugen von ndarrays Erzeugen eines 3D-arrays (Tensor 3. Stufe, Volumen): Erzeugen eines 1D-arrays (Vektor): import numpy as np # create a 2 by 2 by 2 3d-array import numpy as np x = np.array([ x = np.array([0,8,15]) [[1, 2], [3, 4]], print(x) [[5, 6], [7, 8]] ]) print(x) Erzeugen eines 2D-arrays (Matrix): import numpy as np Erzeugen eines nD-arrays (Tensor n-ter Stufe): x = np.array([[1, 2, 3, 4], [5, 6, 7, 8], analog zu vorhin mit 4 Ebenen von [] … [9, 10, 11, 12]]) print(x) equivalent zu: import numpy as np x = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) print(x) 6 Programmieren mit ndarrays (Fortsetzung) Unterschied: Typen von ndarrays und dtype (Datentyp) eines ndarray Der Typ ist – wie erwartbar – ndarray (numpy.ndarray): import numpy as np x = np.array([0,8,15]) print(type(x)) Mit dem Klassen-Attribut dtype liefert den Datentyp (data type) der im ndarray gespeicherten Elemente import numpy as np x = np.array([0,8,15]) print(x.dtype) ndarray mit spezifischen dtype erzeugen (funktioniert auch für die Methoden zur ndarray-Erzeugung): import numpy as np x = np.array([0,8,15], dtype=np.float64) print(x.dtype) 7 Programmieren mit ndarrays - Datentypen NumPy Datentypen Hier eine unvollständige Liste einiger wichtiger numerischer Datentypen, welche NumPy unterstützt: Datentyp Wertebereich Speicherbedarf eines Wertes Code-Beispiel zum Erzeugen float64 [2.2251 × 10−308 , 1.7977 × 10+308] 8 Bytes (64 Bit) x = np.float64(-7.5) float32 [1.1755 × 10−38 , 3.4028 × 10+38] 4 Bytes (32 Bit) x = np.float32(-7.5) uint8 [0, 255] 1 Byte (8 Bit) x = np.uint8(42) int8 [−128, 127] 1 Byte (8 Bit) x = np.int8(-20) uint16 [0, 65535] 2 Byte (16 Bit) x = np.uint16(9000) int16 [−32768, 32767] 2 Byte (16 Bit) x = np.int16(-400) uint32 [0, 4294967295] 4 Byte (32 Bit) x = np.uint32(100000) int32 [−2147483648, 2147483647] 4 Byte (32 Bit) x = np.int32(-300000) uint64 [0, 18446744073709551615] 8 Byte (64 Bit) x = np.uint64(42e16) int64 [np.iinfo(np.int64).min, np.iinfo(np.int64).max] 8 Byte (64 Bit) x = np.int64(-42e16) 8 bool_ True or False 1 Byte (8 Bit) x = np.bool_(True) Programmieren mit ndarrays - Datentypen NumPy Datentypen konvertieren Mit der Klassenmethode astype kann man ein Array in ein Array anderen Datentyps konvertieren: import numpy as np # create variable of datatype uint8 # and convert it to int32 datatype x = np.uint8(42) y = x.astype(np.int32) print("value: ", y, "; datatype: ", y.dtype) Dabei ist zu beachten, dass ungewollte Effekte passieren können, wenn die Werte des Eingangs-Arrays nicht im Datentyp des Ausgangsarrays abgebildet werden können (z.B: Überlauf bei Integer-Datentypen oder Genauigkeitsverluste bei Konvertierungen in float-Datentypen mit weniger Bit zur Zahlendarstellung) 9 Programmieren mit ndarrays (Fortsetzung) Dimensionen (dimensions) eines ndarrays die Dimensionalität eines Arrays ist die Anzahl der Achsen/Dimensionen (auch Arraytiefe (array depth)) anders gesagt: jede Dimension im Array ist ein Level der Arraytiefe Ist die Arraytiefe > 1 sprechen wir von nested arrays (2D, 3D, nD-Arrays sind nested arrays, 1D ist keines) Die Anzahl der Dimensionen eines Arrays erhält man mithilfe des import numpy as np x = np.array([0,8,15]) Attributes ndim: print(x.ndim) Ein skalarer Wert ist ein 0-D Array, Elemente eines Arrays sind Skalare, import numpy as np x = np.array(42) jeder Wert in einem Array ist ein 0-D Array (Anmerkung: [] fehlt dann) print(x) print(x.ndim) 10 Programmieren mit ndarrays (Fortsetzung) tuple der Längen der entsprechenden Array-Dimensionen Gestalt (shape) eines ndarrays das shape Attribut liefert den shape eines Arrays - im folgenden Beispiel hat der 1D-vector shape (3,) import numpy as np x = np.array([0,8,15]) print(x.shape) shape eines 2D-Vektors – in diesem Bsp. hat x den shape (3,4) – 3 Zeilen (1. Achse) and 4 Spalten (2. Achse): import numpy as np x = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) print(x.shape) import numpy as np # create a 2 by 2 by 2 3d-array x = np.array([ shape eines 3D-Vektorr – in diesem Bsp. hat x shape (2,2,2): [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]) print(x.shape) 11 Programmieren mit ndarrays (Fortsetzung) Erzeugen von ndarrays mit den speziellen Erzeugungs-Funktionen Erzeuge Array, in dem alle Elemente Wert 0 haben: # create vector containing zeros # create 2D array of shape (4,5) containing zeros x = np.zeros(5) x = np.zeros((4,5)) print(x) print(x) Erzeuge Array mit Elementen von Wert 1: # create 2D array of shape (4,5) containing ones x = np.ones((4,5)) print(x) Erzeuge Array, in dem alle Elemente einen vorgegebenen Wert haben: # create 2D array of shape (4,5) with target value # alternative to create array with target value x = np.full((4,5), 42) x = np.ones((4,5)) * 42 print(x) print(x) Erzeuge Array mit zufälligen Werten (siehe https://numpy.org/doc/stable/reference/random/index.html für mehr Details): # create 2D array of shape (4,5) with random values between 0 and 100 (sampled uniformly) x = np.random.uniform(0, 100, (4,5)) 12 print(x) Programmieren mit ndarrays (Fortsetzung) Erzeugen von ndarrays mit den speziellen Erzeugungs-Funktionen (Fortsetzung) Erzeugen eines Arrays ausgehend von einer range: x = np.array(range(0,8,1)) # step must be an integer number print(x) Erzeugen eines Arrays mit der ndarray-Methode arange: x = np.arange(0,8,2.5) # step can be a floating point number print(x) Erzeugen eines Arrays mit der linspace Methode (einheitlicher Abstand zwischen den Elementen): # create vector with linear spacing between elements (here: 20, 20.5, 21, … 28.5, 29) # first parameter is first element, second parameter is last element, third parameter is number of # elements between first and last element (including those), elements get equally spaced x = np.linspace(20,29,19) print(x) 13 Programmieren mit ndarrays (Fortsetzung) Erzeugen von ndarrays mit den speziellen Erzeugungs-Funktionen (Fortsetzung) x Erzeugen eines 2D-Arrays mit mgrid Methode (mit Array-Index Syntax): # create 2D arrays x resp. y with mgrid x,y = np.mgrid[-2:2:0.5, -2:2:0.5] print(x) print(y) Erzeugen eines 2D-Arrays mit meshgrid Methode (mittels np.range): y # create 2D arrays x resp. y with meshgrid x,y = np.meshgrid(np.arange(-2,2,0.5), np.arange(-2,2,0.5)) print(x) print(y) beide Methoden können nD-Arrays höherer Dimension erzeugen 14 Programmieren mit Lese- und Schreibzugriffe auf Elemente eines ndarrays Verwendung von Indizes und Slicing für ndarrays Indexieren von Elementen eines ndarrays wird sowohl für Lese- als auch and Schreibvorgänge verwendet: x = np.arange(0,8,0.5) print(x) x = -22 print(x) Die weiteren Indizier-Mechanismen/slicing-Techniken funktionieren ebenfalls x = np.arange(0,8,1) # create 1d array with values 0,1,…,7 print(x) x[2:4] = -22 # set values at index 2 and 3 to value -22 print(x[1:-1]) # print values from index 1 up to the second last index Indizieren von Elementen in mehr-dimensionalen Arrays (hier ein Beispiel mit 2D-Array): x = np.array([[1, 2, 3], [4, 5, 6]]) # create 2 by 3 array x = -22 # set value in second row and second column to -22 using double bracket notation x[1,2] = 42 # set value in second row and third column to 42 using alternative 2D indexing print(x) 15 Programmieren mit Elementweise Operationen auf ndarrays Arithmetische Operations mit ndarrays 2 ndarrays können addiert, subtrahiert, multipliziert, dividiert, etc. werden wenn ihr shape übereinstimmt: x = np.arange(0,8,0.5) x,_ = np.mgrid[0:5, 0:6] y = np.arange(23,39,1) m = np.full((5,6),2) sum = x + y diff_squared = (x - m) * (x - m) print(x, "\n\n", y, "\n\n", sum) print(x, "\n\n", m, "\n\n", diff_squared) Wenn sich ihr shape unterscheidet, tritt einer von zwei Fällen ein: ❖ Entweder ist es für NumPy möglich das kleinere Array (auf den shape) zu erweitern (broadcasting) ❖ Oder es ist kein broadcasting möglich → Fehlermeldung Spezialfall: broadcasting eines skalaren Wertes (dies ist immer möglich) x = np.arange(1,4,1) result = x * 2 print(x, "\n\n", result) 16 Quelle: https://numpy.org/doc/stable/user/basics.broadcasting.html#basics-broadcasting Programmieren mit Elementweise Operationen auf ndarrays Regeln für broadcasting Finden immer dann Anwendung, wenn sich der shape von 2 Operanden-ndarrays unterscheidet Abhängig von Einschränkungen, “broadcasting” des kleineren Arrays auf shape des größeren Arrays NumPy vergleicht die shapes der Arrays dimensionsweise ❖ startend mit der führenden (rechteste, innerste) Dimension nach links gehend, bis zur linksten (äußersten) ❖ zwei Dimensionen sind kompatibel, wenn die entsprechenden Längen der betrachteten Dimension: ❖ gleich groß sind, oder ❖ eine von ihnen 1 ist ❖ Dimensionen mit Länge 1 werden durch Kopieren des Wertes zur Anpassung ans andere Array gestreckt siehe https://numpy.org/doc/stable/user/basics.broadcasting.html#basics-broadcasting für mehr Details und Beispiele 17 Programmieren mit Elementweise Operationen auf ndarrays (Fortsetzung) Anwendung mathematischer Funktionen auf ndarrays NumPy stellt viele mathematische Funktionen zur Verfügung, die auf ndarrays angewendet werden können Die mathematische Funktion wird dann elementweise auf die einzelnen Elemente des ndarray angewendet # compute square root of x x = np.arange(0,10,0.5) result = np.sqrt(x) print(x, "\n\n", result) Hier ist eine unvollstängige Liste einiger mathematischer Funktionen, die in NumPy verfügbar sind: pow, sqrt, exp, log, log2, log10, sin, cos, tan, arctan, arctan2, arcsin, arccos, … 18 Programmieren mit Aggregations-Operationen auf ndarrays Aggregations-Operationen auf ndarrays Aggregations-Operationen ändern die Anzahl der Elemente des Arrays durch Aggregation, wenn sie angewendet werden (kombinieren oder gruppieren Elemente, shape des ndarray ändert sich dabei!) sie können entlang einer Dimension aggregieren oder über alle Dimensionen hinweg (letzteres ergibt Skalar) man kann meist angeben, entlang welcher Achse die Operation angewendet werden soll Beispiele: min, max, sum, mean, … # get minimum value along certain axis A = np.random.uniform(0,100,(4,5)) minimum_along_xaxis = np.min(A, 0) # option1: numpy static function minimum_along_yaxis = A.min(1) # option2: class member function print(A, "\n\n", minimum_along_xaxis, "\n\n", minimum_along_yaxis) # get minimum value of all elements of a ndarray A = np.random.uniform(0,100,(4,5)) minValue = np.min(A) # option1: numpy static function minValue2 = A.min() # option2: class member function print(A, "\n\n", minValue, "\n\n", minValue2) 19 Programmieren mit Filtern von ndarrays Achtung: nicht zu verwechseln mit gleichnamigem Begriff aus Mathematik und Bildverarbeitung Erzeugen von booleschen Index-Listen/Masken und Verwendung dieser, um ndarrays zu filtern Extrahieren von Elementen aus einem Array (Erzeugen eines neuen Arrays daraus) nennt man filtern (filtering) Boolesche Index-Listen/Masken erlauben das Identifizieren von Array-Elementen, die eine Bedingung erfüllen: # get index positions as index list/mask of elements that are greater than 5 A = np.random.uniform(0,10,20) M = A > 5 # create (Boolean) mask indicating elements which meet a condition print(A, "\n\n", M) mit solch einer Index-Liste/Maske kann man die Elemente auswählen, welche die Bedingung erfüllen: # get index positions as index list/mask of elements that are greater than 5 A = np.random.uniform(0,10,20) M = A > 5 # create (Boolean) mask indicating elements which meet a condition C = A[M] # get array only containing those elements of the original array fulfilling the condition print(A, "\n\n", M, "\n\n", C) Das Setzen von den Elementen innerhalb eines ndarrays, die die Bedingung erfüllen, wird analog realisiert: A = np.random.uniform(0,10,20); C = A.copy() # real copy of A, not just a shallow copy (instead C = A) C[A > 5] = 0 # here the index list creation and filtering is done in one line of code 20 print(A, "\n\n", C) Programmieren mit Form eines ndarrays abändern (reshaping) reshaping ndarrays manchmal ist es notwendig/praktischer die Anordnung der Elemente über die Arrayform zu ändern (reshape) Reshaping eines mehrdimensionalen Arrays in ein 1D-Array bezeichnet man auch als abflachen (flattening), NumPy stellt die flatten() bzw. ravel() Funktion zur Verfügung (Unterschied: Kopie vs. View) A = np.random.randint(0,10,(2,3)) print(A, A.shape) A = A.flatten() print(A, A.shape) mit der reshape() Funktion kann ein ndarray in die gewünschte Form gebracht werden (Elemente werden dann entsprechend verteilt) A = np.random.uniform(0,10,6) source: https://numpy.org/doc/stable/user/absolute_beginners.html#transposing-and-reshaping-a-matrix print(A, A.shape) A = A.reshape((2,3)) print(A, A.shape) 21 Programmieren mit Zusammenfügen von ndarrays Zusammenfügen von ndarrays (joining) Joinen: Inhalt von zwei oder mehreren Arrays in ein Array zusammenfügen (entlang einer gewählten Achse) Joinen mittels concatenate() Funktion: man muss die Sequenz der zusammenzufügenden Arrays als Tupel sowie die Achse übergeben, wenn keine Achse spezifiziert wird, wird als die erste Achse verwendet arr1 = np.array([1, 2, 3]) arr2 = np.array([4, 5, 6]) arr = np.concatenate((arr1, arr2)) print(arr, "\n\n", arr.shape) Spezifizieren der Achse für concatenate() arr1 = np.array([[1, 2], [3, 4]]) arr1 = np.array([[1, 2], [3, 4]]) arr2 = np.array([[5, 6], [7, 8]]) arr2 = np.array([[5, 6], [7, 8]]) arr = np.concatenate((arr1, arr2), axis = 0) arr = np.concatenate((arr1, arr2), axis = 1) print(arr, "\n\n", arr.shape) print(arr, "\n\n", arr.shape) 22 Programmieren mit Stapeln von ndarrays Stapeln von ndarrays (stacking) Stapeln ist ähnlich wie Zusammenfügen, der Unterschied ist: Stapeln geschieht entlang einer neuen Achse: arr1 = np.array([1, 2, 3]) arr1 = np.array([1, 2, 3]) arr2 = np.array([4, 5, 6]) arr2 = np.array([4, 5, 6]) arr = np.stack((arr1, arr2), axis = 0) arr = np.stack((arr1, arr2), axis = 1) print(arr, "\n\n", arr.shape) print(arr, "\n\n", arr.shape) vstack, hstack und dstack erzeugen einen vertikalen, horizontalen oder in die Tiefen gehenden Stapel column_stack kann verwendet werden, um 1D-Arrays als Spalten in einem 2D-Array zu stapeln arr1 = np.array([1, 2, 3]) arr1 = np.array([1, 2, 3]) arr1 = np.array([1, 2, 3]) arr2 = np.array([4, 5, 6]) arr2 = np.array([4, 5, 6]) arr2 = np.array([4, 5, 6]) arr = np.vstack((arr1, arr2)) arr = np.hstack((arr1, arr2)) arr = np.dstack((arr1, arr2)) print(arr, "\n\n", arr.shape) print(arr, "\n\n", arr.shape) print(arr), "\n\n", arr.shape 23 Programmieren mit Matrizenrechnung mit zweidimensionalen ndarrays Matrizenrechnung mit zweidimensionalen ndarrays 2D-ndarrays können als Matrizen interpretiert werden, NumPy erlaubt es Matrix-Berechnungen durchzuführen A = np.array([[3,7,5],[4,2,0]]) Ein einfaches Bsp. ist die Berechnung der transponierten Matrix T = A.T # same as T = np.transpose(A) print(A, "\n\n", T) Ein anderes Bsp. wäre Durchführen der Matrix-Multiplikation T = np.array([[0,1],[1,0]]) v1 = np.array([-7,2]) (z.B.: für Koordinaten-Transformationen in der Computergrafik) v2 = T @ v1 # same as v2 = np.matmul(T,v1) print(v1, "\n\n", v2) Fortgeschrittene Beispiele wäre Funktionalität aus dem A = np.array([[3,7],[4,2]]) I = np.linalg.inv(A) Unter-Namensraum numpy.linalg, z.B.: print(A, "\n\n", I) ❖ inv Funktion (um die Inverse Matrix zu berechnen) ❖ eig Funktion (Eigenwert- und Eigenvektor-Berechnung einer Matrix) 24 ❖ svd Funktion (Singulärwertzerlegung, z.B.: Lösen von linearen Gleichungssystemen) Programmieren mit Suchen in ndarrays Suchen in ndarrays durch Verwendung der where Funktion erhält man ein ndarray von Indizes, welche ein Filter-Kriterium erfüllen a = np.array([1, 2, 3, 4, 5, 4, 4]) idx = np.where(a == 4) print(idx, "\n\n", a[idx]) die where Funktion auf multidimensionale Arrays angewendet, liefert ein Tupel von ndarrays von Indizes (1 Array pro Dimension) a = np.array([[[1, 2, 3, 2], [4, 5, 4, 5]], [[0, 9, 8, 9], [2, 3, 1, 4]], [[7, 0, 6, 3], [4, 4, 3, 1]]]) idx = np.where(a == 4) print(a, "\n", a.shape, "\n\n", idx, "\n\n", a[idx]) index dim0 vom 3. übereinstimmenden Element index dim1 vom 3. übereinstimmenden Element 25 index dim2 vom 3. übereinstimmenden Element Programmieren mit Ändern der Elementreihenfolge in ndarrays Sortieren in ndarrays original = np.array([7, 9, 2, 1, 6, 0, 3, 4]) die sort Funktion sortiert ein ndarray sorted = np.sort(original) print(original, "\n\n", sorted) Beim Sortieren eines multidimensionalen ndarrays, werden alle Sub-Arrays entlang der innersten Dimension (letzte Achse) sortiert original = np.array([[[1, 2, 3, 2], [4, 5, 4, 5]], [[0, 9, 8, 9], [2, 3, 1, 4]], [[7, 0, 6, 3], [4, 4, 3, 1]]]) sorted = np.sort(original) print(original, "\n\n", sorted) Umkehren der Elementreihenfolge (flipping) von ndarrays die flip() Funktion kehrt die Reihenfolge original = np.array([[7, 9, 2, 1], [6, 0, 3, 4]]) der Elemente eines ndarray um (Achse wählbar) flipped = np.flip(original, axis=0) print(original, "\n\n", flipped) 26 Programmieren mit Eindeutige Elemente eines ndarrays ermitteln Eindeutige (unique) Elemente eines ndarrays die Funktion unique liefert eindeutige Elemente eines ndarrays (ohne Duplikate) a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20]) u = np.unique(a) print(a, "\n\n", u) zusätzliches Argument return_index=True gibt Liste mit Indizes auf jeweilige erste Vorkommen zurück. a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20]) u, idx = np.unique(a, return_index=True) print(a, "\n\n", idx, "\n\n", u) mit Argument return_counts=True erhält man auch Anzahl der Vorkommen der unterschiedlichen Werte a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20]) u, idx, c = np.unique(a, return_index=True, return_counts=True) print(a, "\n\nuniques values: ", u, "\n\nindices: ", idx, "\n\ncounts: ", c) 27