numpy – eine Bibliothek für numerische Berechnungen

Numpy ist eine Python-Bibliothek mit Datentypen und Funktionen, die für numerische Berechnungen optimiert sind. Meist lassen sich solche Aufgaben zwar auch mit den normalen Python-Funktionen berechnen, gerade bei großen Zahlenmengen ist Numpy allerdings wesentlich schneller.

Numpy ist nicht im Python3-Standard enthalten und muss daher separat installiert werden :

sudo aptitude install python3-numpy

# oder easy_install3 numpy nach Installation von python3-setuptools

Der zentrale Objekttyp in Numpy ist das ndarray (meist kurz “Array” genannt), das viele Gemeinsamkeiten mit der normalen Liste aufweist. Ein wesentlicher Unterschied besteht allerdings darin, dass alle im Array gespeicherten Elemente den gleichen Objekttyp haben müssen. Die Größe von Numpy-Arrays kann zudem nicht verändert werden, und es sind keine leeren Objekte erlaubt. Durch derartige Eigenschaften können Numpy-Arrays vom Python-Interpreter schneller durchlaufen werden. Darüber hinaus stellt Numpy etliche grundlegende Funktionen bereit, um mit den Inhalten solcher Arrays zu arbeiten und/oder Änderungen an solchen Arrays vorzunehmen.

Numpy-Arrays erstellen

Ein neues Numpy-Array kann folgendermaßen aus einer normalen Liste, deren Elemente alle den gleichen Datentyp haben müssen, erzeugt werden:

import numpy as np

nums = [1,2,3,4,5]

# Eindimensionales Array erstellen:
a = np.array(nums)

a
# Ergebnis: array([1, 2, 3, 4, 5])

Die beim Funktionsaufruf von array() übergebene Liste kann auch aus mehreren Teillisten bestehen, um beispielsweise zeilenweise die Werte einer Matrix als Numpy-Array zu speichern:

# Zweidimensionale Matrix erstellen
m1 = np.array([ [1,2,3], [4,5,6], [7,8,9] ])

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

Durch ein zweites Argument kann beim Aufruf der array()-Funktion der Datentyp der Elemente explizit festgelegt werden. Beispielsweise könnten im obigen Beispiel durch eine zusätzliche Angabe von dtype=float die in der Liste enthaltenen Integer-Werte automatisch in Gleitkomma-Zahlen umgewandelt werden.

Da auch Matrizen voller Nullen oder Einsen häufig vorkommen, können diese mittels der dafür vorgesehenen Funktionen zeros() bzw. ones() erzeugt werden. Dabei wird als erstes Argument ein Tupel als Argument angegeben, welches die Anzahl an Zeilen und Spalten der Matrix festlegt, sowie als zweites Argument wiederum optional der Datentyp der einzelnen Elemente:

# 2x3-Matrix aus Nullen erstellen:

# Zweidimensionale Matrix erstellen
m2 = np.zeros( (2,3), int)

m2
# Ergebnis:
# array([[0, 0, 0],
#    [0, 0, 0]])

Eindimensionale Arrays mittels arange() und linspace()

Mittels der Funktion arange() kann ein (eindimensionales) Numpy-Array auf Grundlage eines Zahlenbereichs erstellt werden:

# Numpy-Array aus Zahlenbereich mit angegebener Schrittweite erstellen:
# Syntax: np.arange(start, stop, step)

r = np.arange(0, 10, 0.1)

r
# Ergebnis:
# array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9,  1. ,
#         1.1,  1.2,  1.3,  1.4,  1.5,  1.6,  1.7,  1.8,  1.9,  2. ,  2.1,
#         2.2,  2.3,  2.4,  2.5,  2.6,  2.7,  2.8,  2.9,  3. ,  3.1,  3.2,
#         3.3,  3.4,  3.5,  3.6,  3.7,  3.8,  3.9,  4. ,  4.1,  4.2,  4.3,
#         4.4,  4.5,  4.6,  4.7,  4.8,  4.9,  5. ,  5.1,  5.2,  5.3,  5.4,
#         5.5,  5.6,  5.7,  5.8,  5.9,  6. ,  6.1,  6.2,  6.3,  6.4,  6.5,
#         6.6,  6.7,  6.8,  6.9,  7. ,  7.1,  7.2,  7.3,  7.4,  7.5,  7.6,
#         7.7,  7.8,  7.9,  8. ,  8.1,  8.2,  8.3,  8.4,  8.5,  8.6,  8.7,
#         8.8,  8.9,  9. ,  9.1,  9.2,  9.3,  9.4,  9.5,  9.6,  9.7,  9.8,
#         9.9])

Die Funktion arange() verhält sich also genauso wie die Funktion range(), liefert allerdings ein Numpy-Array mit den entsprechenden Werten als Ergebnis zurück. [1]

Eine zweite, sehr ähnliche Möglichkeit zur Erstellung eines Numpy-Arrays bietet die Funktion linspace(): Bei dieser wird allerdings die Anzahl der Schritte zwischen dem Start- und dem Endwert angegeben; die Schrittweite wird dann automatisch berechnet.

# Numpy-Array aus Zahlenbereich mit angegebener Listen-Länge erstellen:
# Syntax: np.arange(start, stop, num)

l = np.linspace(0, 10, 100, endpoint=True)

l
# Ergebnis:
# array([  0.        ,   0.1010101 ,   0.2020202 ,   0.3030303 ,
#      0.4040404 ,   0.50505051,   0.60606061,   0.70707071,
#      0.80808081,   0.90909091,   1.01010101,   1.11111111,
#      1.21212121,   1.31313131,   1.41414141,   1.51515152,
#      1.61616162,   1.71717172,   1.81818182,   1.91919192,
#      2.02020202,   2.12121212,   2.22222222,   2.32323232,
#      2.42424242,   2.52525253,   2.62626263,   2.72727273,
#      2.82828283,   2.92929293,   3.03030303,   3.13131313,
#      3.23232323,   3.33333333,   3.43434343,   3.53535354,
#      3.63636364,   3.73737374,   3.83838384,   3.93939394,
#      4.04040404,   4.14141414,   4.24242424,   4.34343434,
#      4.44444444,   4.54545455,   4.64646465,   4.74747475,
#      4.84848485,   4.94949495,   5.05050505,   5.15151515,
#      5.25252525,   5.35353535,   5.45454545,   5.55555556,
#      5.65656566,   5.75757576,   5.85858586,   5.95959596,
#      6.06060606,   6.16161616,   6.26262626,   6.36363636,
#      6.46464646,   6.56565657,   6.66666667,   6.76767677,
#      6.86868687,   6.96969697,   7.07070707,   7.17171717,
#      7.27272727,   7.37373737,   7.47474747,   7.57575758,
#      7.67676768,   7.77777778,   7.87878788,   7.97979798,
#      8.08080808,   8.18181818,   8.28282828,   8.38383838,
#      8.48484848,   8.58585859,   8.68686869,   8.78787879,
#      8.88888889,   8.98989899,   9.09090909,   9.19191919,
#      9.29292929,   9.39393939,   9.49494949,   9.5959596 ,
#      9.6969697 ,   9.7979798 ,   9.8989899 ,  10.        ])

Setzt man im obigen Beispiel endpoint=False, so ist das mit linspace() erzeugte Array l mit dem Array r aus dem vorherigen Beispiel identisch.

Inhalte von Numpy-Arrays abrufen und verändern

Entspricht ein Numpy-Array einem eindimensionalen Vektor, so kann auf die einzelnen Elemente in gleicher Weise wie bei einer Liste zugegriffen werden:

nums = [1,2,3,4,5]

a = np.array(nums)

a[3]
# Ergebnis: 4

a[-1]
# Ergebnis: 5

Als positive Indizes sind Werte zwischen i >= 0 und i < len(array) möglich; sie liefern jeweils den Wert des i+1-ten Listenelements als Ergebnis zurück. Für negative Indizes sind Werte ab i <= -1 möglich; sie liefern jeweils den Wert des i-ten Listenelements – vom Ende der Liste her gerechnet – als Ergebnis zurück. Die Indizierung kann ebenso genutzt werden, um den Inhalt des Arrays an einer bestimmten Stelle zu verändern:

a[-1] = 10

a
# Ergebnis: array([1, 2, 3, 4, 10])

Um auf Zahlenbereiche innerhalb eines Numpy-Arrays zuzugreifen, können wiederum – wie bei der Indizierung von Listen und Tupeln – so genannte Slicings genutzt werden. Dabei wird innerhalb des Indexoperators [] der auszuwählende Bereich mittels der Syntax start:stop festgelegt, wobei für start und stop die Index-Werte der Bereichsgrenzen eingesetzt werden:

r = np.arange(10)

# Intervall selektieren:

r[3:8]
# Ergebnis: array([3, 4, 5, 6, 7])

# Jedes zweite Element im angegebenen Intervall auswählen:

r[3:8:2]
# Ergebnis: array([3, 5, 7])

Wie üblich wird bei Slicings die untere Grenze ins Intervall mit eingeschlossen, die obere nicht. Mit der Syntax start:stop:step kann bei Slicings zudem festgelegt werden, dass innerhalb des ausgewählten Zahlenbereichs nur jede durch die Zahl step bezeichnete Zahl ausgewählt wird. Wird für start oder step kein Wert angegeben, so wird der ganze Bereich ausgewählt:

# Ab dem fünften Element (von hinten beginnend) jedes Element auswählen:

r[5::-1]
# Ergebnis: array([5, 4, 3, 2, 1, 0])

Slicings können bei Zuweisungen von neuen Werten auch auf der linken Seite des =-Zeichens stehen. Auf diese Weise kann bisweilen auf eine for-Schleife verzichtet und der Code somit lesbarer gemacht werden.

Um in mehrdimensionalen Numpy-Arrays Werte zu selektieren, wird folgende Syntax verwendet:

m = np.array([ [1,2,3], [4,5,6] ])

m
# Ergebnis:
# array([[1, 2, 3],
#    [4, 5, 6]])


# Element in der zweiten Zeile in der dritten Spalte auswählen:

m[1][2]
# Ergebnis: 6

Bei Numpy-Arrays können die “Verschachtelungstiefen” wie bei Listen durch eine mehrfache Anwendung des Index-Operators [] aufgelöst werden; ebenso ist für das obige Beispiel allerdings auch die Syntax m3[1,2] erlaubt und auch üblich. Bei der Auswahl eines Elements aus einer Matrix können also innerhalb des Index-Operators die Zeile und Spalte durch ein Komma getrennt ausgewählt werden; Slicings sind hierbei ebenfalls möglich.

Funktionen für Numpy-Arrays

Viele Funktionen wie die Betragsfunktion abs(), die Wurzelfunktion sqrt() oder trigonometrische Funktionen wie sin(), cos() und tan(), die im math-Modul definiert sind, existieren in ähnlicher Weise auch im Numpy-Modul – mit dem Unterschied, dass sie auf Numpy-Arrays angewendet werden können. Dabei wird die jeweilige mathematische Funktion auf jedes einzelne Element des Arrays angewendet, und als Ergebnis ebenfalls ein Array mit den entsprechenden Funktionswerten zurück gegeben. [2]

Ebenso können die gewöhnlichen Operationen +, -, * und / angewendet werden, um beispielsweise zu allen Elemente eines Numpy-Arrays eine bestimmte Zahl zu addieren/subtrahieren oder um alle Elemente mit einer bestimmten Zahl zu multiplizieren. Die Numpy-Funktionen erzeugen dabei stets neue Numpy-Arrays, lassen die originalen Arrays also stets unverändert.

r = np.arange(10)

r
# Ergebnis: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8, 9])

r+1
# Ergebnis: array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])

r**2
# Ergebnis: array([ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81])

np.sqrt(r**4)
# Ergebnis: array([ 0,  1,  4,  9, 16, 25, 36, 49, 64, 81])

np.sin(r)
# Ergebnis: array([ 0.        ,  0.84147098,  0.90929743,  0.14112001, -0.7568025 ,
#  -0.95892427, -0.2794155 ,  0.6569866 ,  0.98935825,  0.41211849])

Zusätzlich gibt es in Numpy Funktionen, die speziell für Zahlenreihen und Matrizen vorgesehen sind. Beispielsweise kann mit der Numpy-Funktionen argmin() und argmax() der Index des kleinsten und größten Elements in einem Array gefunden werden. Wendet man diese Funktionen auf ein Matrix-Array an, so erhält man diejenige Index-Nummer des kleinsten beziehungsweise größten Elements, die sich bei einem eindimensionalen Array mit den gleichen Werten ergeben würde. Ist man hingegen spalten- oder zeilenweise an den jeweiligen Minima beziehungsweise Maxima interessiert, so kann beim Aufruf dieser beiden Funktionen als zweites Argument axis=0 für eine spaltenweise Auswertung oder axis=1 für eine zeilenweie Auswertung angegeben werden:

a = np.array( [3,1,2,6,5,4] )
m = np.array([ [3,1,2], [6,5,4] ])

np.argmin(a)
# Ergebnis: 1

np.argmin(m)
# Ergebnis: 1

np.argmin(m, axis=0)
# Ergebnis: array([0, 0, 0])

np.argmin(m, axis=1)
# Ergebnis: array([1, 2])

Für Matrix-Arrays existieren zusätzlich die Numpy-Funktionen dot(), inner() und outer(), mit deren Hilfe Multiplikationen von Matrizen beziehungsweise Vektoren durchgeführt werden können.

... to be continued ...

Links


Anmerkungen:

[1]

Auch bei der arange()-Funktion ist die untere Grenze im Zahlenbereich enthalten, die obere jedoch nicht.

Das optionale dritte Argument gibt, ebenso wie bei range(), die Schrittweite zwischen den beiden Zahlengrenzen an. Ist der Zahlenwert der unteren Bereichsgrenze größer als derjenige der oberen Bereichsgrenze, so muss ein negativer Wert als Schrittweite angegeben werden, andererseits bleibt das resultierende Array leer.

[2]Die gleichnamigen Funktionen aus dem math-Modul können also auf einzelne Elemente eines Numpy-Arrays, nicht jedoch auf das ganze Array an sich angewendet werden. Letzteres könnte man zwar beispielsweies mittels einer for-Schleife erreichen, doch die Ausführung des Codes bei Verwendung der Numpy-Varianten ist erheblich schneller.