Numerical Python – Einführung in wissenschaftliches Rechnen mit NumPy

NumPy steht für Numerical Python und ist eines der bekanntesten Pakete für alle Python-Programmierer mit wissenschaftlichen Hintergrund. Von persönlichen Kontakten erfuhr ich, dass NumPy heute in der Astrophysik fast genauso verwendet wird wie auch von sogenannten Quants im Investment-Banking. Das NumPy-Paket ist sicherlich ein Grundstein des Erfolges für Python in der Wissenschaft und für den häufigen Einsatz für die Implementierung von Algorihtmen des maschinellen Lernens in Python.

Die zentrale Datenstruktur in NumPy ist das mehrdimensionale Array. Dieses n-dimensionale Array (ndarray) ist eine sehr mächtige Datenstruktur und verwende ich beispielsweise in meinem Artikel über den k-Nächste-Nachbarn-Algorithmus. Die Besonderheit des NumPy-Arrays ist, dass es ein mehrdimensionaler Container für homogene Daten ist. Ein Datentyp gilt also für das gesamte Array, nicht nur für bestimmte Zeilen oder Spalten!

import numpy as np

Wer ein NumPy-Array erstellen möchte, kann dies beispielsweise einfach über eine Liste (Standard-Python) tun, die in ein NumPy-Array umgewandelt werden kann:

dataList = [[2, 9],[3, 4, 7, 10, 2],[0, 9, 3, 2, 3]] # dies ist eine Liste (Standard-Python)

dataList
Out: [[2, 9], [3, 4, 7, 10, 2], [0, 9, 3, 2, 3]]

array = np.array(dataList) # die Liste ist ein beliebter Ausgangspunkt für ein NumPy.Array

array
Out: array([[2, 9], [3, 4, 7, 10, 2], [0, 9, 3, 2, 3]], dtype=object)

array.shape  # Gibt die Dimensionsgrößen des Arrays aus
Out: (3,)    # 3 Zeilen, unterschiedlich viele Spalten

Eine zweite, einfachere Liste mit einheitlicher Spaltenzahl pro Zeile verdeutlicht das Prinzip noch mehr:

dataList2 = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]

array2 = np.array(dataList2)

array2
Out: 
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])

array2.shape
Out[15]: (4, 3) # Vier Zeilen, einheitliche drei Spalten

Während die Listen, die Python von Haus aus mitbringt, schwer zu indizieren sind (bei Mehrdimensionalität) und auch nicht wie eine Matrix dargestellt werden (und auch keine Matrix-Operationen anwendbar sind), haben wir all diese Features mit dem ndarray von NumPy.

Schnelle Erzeugung von Arrays

Schnell mal ein Array erzeugen? Kein Problem mit np.zeros() oder np.ones().

a1 = np.ones((5,5))

a2 = np.ones((5,5))

a3 = np.ones((5,5))

a1 + a2 + a3
Out: 
array([[ 3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.]])

(a1 + a2 + a3).sum()
Out: 75.0

Indizierung

ndarrays lassen sich wie gewohnt über einen Index (in eckigen Klammern) gezielt ansprechen:

array
Out[28]:
array([[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10]])

array[0]
Out[29]: array([1, 2, 3, 4, 5])

array[1,3]
Out[30]: 9

array[1][3]
Out[31]: 9

Bei mehrdimensionalen Arrays wird es schnell unübersichtlich, allerdings ist dies in NumPy recht intelligent gelöst. Elemente können natürlich rekursiv angesprochen werden, wie wir es auch bei den Listen im Standard-Python gewohnt sind:

array = np.array([[1,2,3],[4,5,6],[7,8,9]])

array
Out: 
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

array[0]
Out: array([1, 2, 3])

array[0][0]
Out: 1

Darüber hinaus können NumPy-Arrays auch über ein Slicing (Ausschnitte über eine Range) indiziert angesprochen werden: array[Zeilen, Spalten].

array[1:2, 2]
Out: array([6]) # zeige nur die mittlere Zeile, davon nur die letzte Spalte 
array[1,2]
Out: 6          # gleiches Spiel wie oben (nur schöner)

array[:, 2]
Out: array([3, 6, 9])   # Alle Zeilen, aber nur aus der letzten Spalte

array[:, :]             # Alle Zeilen und davon alle Spalten
Out: 
array([[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]])

array[0, :]
Out: array([1, 2, 3])   # Nur die erste Zeile, über alle Spalten

array[:, 0]
Out: array([1, 4, 7])   # Alle Zeilen, aber nur aus der ersten Spalte

Fancy Indexing

Fancy Indexing (zu Deutsch etwa “ausgefallenes Indexing”) bietet zwei Funktionen zum Auswählen von Zeilen oder Zellen im mehrdimensionalen Array, die besonders praktisch sind, wenn man es mit sehr großen multidimensionalen Arrays zutun hat.

array = np.zeros((10,5))

array
Out: 
array([[ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])

for i in range(10):
 array[i] = i*3
 

array
Out: 
array([[ 0., 0., 0., 0., 0.],
 [ 3., 3., 3., 3., 3.],
 [ 6., 6., 6., 6., 6.],
 [ 9., 9., 9., 9., 9.],
 [ 12., 12., 12., 12., 12.],
 [ 15., 15., 15., 15., 15.],
 [ 18., 18., 18., 18., 18.],
 [ 21., 21., 21., 21., 21.],
 [ 24., 24., 24., 24., 24.],
 [ 27., 27., 27., 27., 27.]])
array[[1]]     # Selektiere die zweite Zeile
Out: array([[ 3., 3., 3., 3., 3.]]) 

array[[1,2,3]] # Selektiere die zweite, dritte und vierte Zeile
Out: 
array([[ 3., 3., 3., 3., 3.],
       [ 6., 6., 6., 6., 6.],
       [ 9., 9., 9., 9., 9.]])


array[[2,8,-1,-5]]  # Selektiere die dritte und neunte Zeile, sowie die letzte und die fünf-letzte Zeile
Out: 
array([[ 6., 6., 6., 6., 6.],
       [ 24., 24., 24., 24., 24.],
       [ 27., 27., 27., 27., 27.],
       [ 15., 15., 15., 15., 15.]])

Wenn wir mehrere Vektoren als Index liefern, werden diese als Koordinatensystem aufgefasst und so können wir auch über x und y zugreifen:

for i in range(10):
    for j in range(5):
        array[i,j] = str(i)+str(j)
        

array
Out[78]: 
array([[  0.,   1.,   2.,   3.,   4.],
       [ 10.,  11.,  12.,  13.,  14.],
       [ 20.,  21.,  22.,  23.,  24.],
       [ 30.,  31.,  32.,  33.,  34.],
       [ 40.,  41.,  42.,  43.,  44.],
       [ 50.,  51.,  52.,  53.,  54.],
       [ 60.,  61.,  62.,  63.,  64.],
       [ 70.,  71.,  72.,  73.,  74.],
       [ 80.,  81.,  82.,  83.,  84.],
       [ 90.,  91.,  92.,  93.,  94.]])


array[[5],[3]]    # Selektriere den einen Wert in der Zelle y = 5, x = 3
Out: array([ 53.])

array[[2,3,4],[0,1,4]] # Selektiere drei Zellen (y,x) = (2,0),(3,1),(4,4)
Out: array([ 20.,  31.,  44.])

Hier als Beispiel (da gerade noch übersichtlich darstellbar) nur mit zwei Dimensionen (x, y). Es funktioniert aber auch mit drei oder noch mehr Dimensionen.

Achtung: NumPy-Arrays sind standardmäßig Referenzen!

Jeder Programmierer kennt den Unterschied zwischen der Referenz einer Variable und einer Kopie. Während im Standard-Python Listen standardmäßig kopiert werden, werden NumPy-Arrays standardmäßig referenziert. Wenn man dies nicht im Bewusstsein hat, provoziert schnell mal ungeahnte Fehler.

array = np.array([1,2,3,4,5,6,7,8,9,10,11,12])

array
Out: array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])

ausschnitt = array[4:9]

ausschnitt
Out: array([5, 6, 7, 8, 9])

ausschnitt[2:4] = 66666 


ausschnitt
Out: array([ 5, 6, 66666, 66666, 9])

array
Out: 
array([ 1, 2, 3, 4, 5, 6, 66666, 66666, 9, 10, 11, 12])

ausschnitt
Out: array([ 5, 6, 66666, 66666, 9])

Der Grund für diesen Umgang ist, dass NumPy für große Datenmengen konzipiert wurde, für die Kopien als Default besser vermieden werden. Wer also ein Array oder einen Ausschnitt aus diesem bewusst kopieren möchte, darf.copy() nicht vergessen.

array = np.array([1,2,3,4,5,6,7,8,9,10,11,12])

ausschnitt = array[4:11].copy()

ausschnitt[:] = 989898

ausschnitt
Out: array([989898, 989898, 989898, 989898, 989898, 989898, 989898])

array
Out: array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])

Datentypen festlegen und Casten

Numpy erkennt selbstständig, welcher Datentyp für den Arrayinhalt der vermutlich richtige ist. Wer sichergehen will, kann den Datentypen jedoch auch direkt bei der Erstellung des Arrays festlegen.

array1 = np.array([[1,2,3],[4, 5, 6],[7,8,9]], dtype=np.int16)

array1
Out: 
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]], dtype=int16)

array2 = np.array(array1, dtype=np.float64)

array2
Out: 
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.],
       [ 7.,  8.,  9.]])

Wer nicht gleich das ganze Array kopieren möchte, um den Datentypen zu wechseln, kann selbstverständlich auch Casting betreiben.

array3 = array2 * 4.5

array3
Out: 
array([[  4.5,   9. ,  13.5],
       [ 18. ,  22.5,  27. ],
       [ 31.5,  36. ,  40.5]])

array3.astype(np.int16)
Out: 
array([[ 4,  9, 13],
       [18, 22, 27],
       [31, 36, 40]], dtype=int16)
array2 = array.astype('float64')

array2 ** array # Jeder Wert an seiner jeweiligen Stelle von array2 (Basis) hoch dem Wert an der gleichen Stelle von array (Exponent) 

Out:

array([[ 1.00000000e+00, 4.00000000e+00, 2.70000000e+01, 2.56000000e+02, 3.12500000e+03],
[ 4.66560000e+04, 8.23543000e+05, 1.67772160e+07, 3.87420489e+08, 1.00000000e+10]])

Matrizenberechnungen mit NumPy

Vermutlich ist die Vektorberechnung der häufigste Grund, die NumPy-Bibliothek zu importieren. Früher hatte ich solche Operationen mit einer oder mehreren For-Schleifen durchführen müssen, mit NumPy können Arrays einfach untereinander “verrechnet” werden.

array = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])

array
Out[95]: 
array([[ 1, 2, 3],
       [ 4, 5, 6],
       [ 7, 8, 9],
       [10, 11, 12]])

array2 * 2 # Verdopplung aller Werte im Array
Out:
array([[ 2, 4, 6],
       [ 8, 10, 12],
       [14, 16, 18],
       [20, 22, 24]])

array2 ** 2 # entspricht array2 * array2
Out: 
array([[ 1, 4, 9],
       [ 16, 25, 36],
       [ 49, 64, 81],
       [100, 121, 144]])

Aber Achtung! Vor solchen Berechnungen, sollte das ndarray vorher den passenden Datentypen zugewiesen bekommen, sonst drohen ungeahnte Fehler:

array

Out[169]:

array = np.array([[ 1,  2,  3,  4,  5],[ 6,  7,  8,  9, 10]])

array ** array

Out:

array([[ 1, 4, 27, 256, 3125],

[46656, 823543, 16777216, 387420489, -2147483648]], dtype=int32) #FEHLER! 10 ** 10 sprengen den Raum von Int32!

Mit dem Zuweisen des Float64-Datentypen wird die Kalkulation wieder korrekt:

array2 = array.astype('float64')

array2 ** array

Out:

array([[ 1.00000000e+00, 4.00000000e+00, 2.70000000e+01, 2.56000000e+02, 3.12500000e+03],
[ 4.66560000e+04, 8.23543000e+05, 1.67772160e+07, 3.87420489e+08, 1.00000000e+10]])

 

Benjamin Aunkofer

Benjamin Aunkofer ist Lead Data Scientist bei DATANOMIQ und Hochschul-Dozent für Data Science und Data Strategy. Darüber hinaus arbeitet er als Interim Head of Business Intelligence und gibt Seminare/Workshops zu den Themen BI, Data Science und Machine Learning für Unternehmen.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

1987 Views