Numpy Tutorial



Introduction

Visualisierung einer Matrix als Hinton-Diagram

NumPy ist ein Akronym für "Numeric Python" oder "Numerical Python". Bei diesem Modul handelt es sich um eine OpenSource Erweiterung für Python, die schnelle vorkompilierte Funktionen für mathematische und numerische Routinen bereitstellt. Außerdem bereichert NumPy die Programmiersprache Python um mächtige Datenstrukturen für das effiziente Rechnen mit großen Arrays und Matrizen. Die Implementierung zielt sogar auf extrem große ("big data") Matrizen und Arrays. Ferner bietet das Modul eine riesige Anzahl von hochwertigen mathematischen Funktionen um mit diese Matrizen und Arrays zu bearbeiten.

SciPy (Scientific Python) wird oft im gleichen Atemzug wie NumPy genannt. Scipy erweitert die Leistungsfähigkeit von NumPy um weitere nützliche Funktionen zur Minimierung, Regression, Fouriertransformation und vielen anderen.

Sowohl NumPy als auch SciPy sind üblicherweise bei einer Standarinstallation von Python nicht installiert. NumPy muss vor SciPy installiert werden. NumPy kann von folgender Webseite heruntergelanden werden:

http://www.numpy.org

(Kommentar: Das Diagramm im Bild auf der rechten Seite ist eine grafische Visualisierung einer Matrix mit 14 Reihen und 20 Spalten. The diagram of the image on the right side is the graphical visualisation of a matrix with 14 rows and 20 columns. Es handelt sich um ein sogenanntes Hinton-Diagramm. Die Größe eines Quadrates innerhalb dieses Diagrammes korrespondiert zu der Größe des entsprechenden Wertes in der darzustellenden Matrix. Die Farbe bestimmt dabei, ob es sich um einen positiven oder negativen Wert handelt. In unserem Beispiel: Die Farbe Rot bezeichnet die negativen Werte und die Farbe Grün bezeichnet die positiven Werte.)

NumPy basiert auf zwei früheren Python-Modulen, die mit Arrays zu tun hatten. Eines von diesen ist Numeric. Numeric ist wie Numpy ein Python-Modul leistungsstarke numerische Berechnungen, aber es ist heute überholt. Ein anderer Vorgänger von NumPy ist Numarray, bei dem es sich um eine vollständige Überarbeitung von Numeric handelt, aber auch dieses Modul ist heute veraltet. NumPy ist die Verschmelzung dieser beiden, d.h. es ist auf dem Code von Numeric und den Funktionalitäten von Numarray aufgebaut.



Die Python-Alternative zu MATLAB

Python in Kombination mit Numpy, Scipy und Matplotlib kann als vollwertiger Ersatz für MATLAB genutzt werden. Bei Python und seinen Modulen handelt es sich um freie Software ("free Software" oder "open source"), frei steht hier im Sinne von "Frei"heit und nicht von "Frei"bier, auch wenn Python kostenlos ist.

Obwohl für MATLAB eine riesige Anzahl von zusätzlichen Toolboxen verfügbar sind, hat Python in Verbindung mit oben erwähnten Modulen den Vorteil, dass es sich bei Python um die modernere und umfassendere Programmiersprache handelt.

Zusammenhang zwischen Numpy, Scipy, Matplotlib und Matlab



Vergleich zwischen Kern-Python und Numpy

Wenn wir von Kern-Python (engl. "Core Python") sprechen, dann meinen wir das reine Python ohne seine speziellen Module, also in unserem Fall NumPy.

Die Vorteile von Kern-Python:

Vorteile von NumPy gegenüber Python:


Ein einfaches Numpy-Beispiel

Bevor wir Numpy benutzen können, müssen wir es importieren. Es wird importiert wie jedes andere Modul auch:

import numpy

Die obige import-Anweisung wird man aber nur sehr selten zu sehen bekommen. Üblicherweise wird Numpy zu np umbenannt:

import numpy as np

Wir haben eine Liste mit Werten, zum Beispiel Temperaturen in Celsius:

cvalues = [25.3, 24.8, 26.9, 23.9]

Wir wandeln diese nun in ein eindimensionales Numpy-Array:

C = np.array(cvalues)
print(C)
Wir erhalten die folgende Ergebnisse:
[ 25.3  24.8  26.9  23.9]

Nehmen wir nun an, dass wir die Werte in Grad Fahrenheit benötigen. Dies kann sehr einfach mit einem NumPy-Array bewerkstelligt werden. Die Lösung unseres Problems besteht in einfacher Skalarmultiplikation:

print(C * 9 / 5 + 32)
Führt man obigen Code aus, erhält man folgendes Ergebnis:
[ 77.54  76.64  80.42  75.02]

Verglichen zu diesem Vorgehen stellt sich die Python-Lösung mit Listen als sehr umständlich dar:

fvalues = [ x*9/5 + 32 for x in cvalues] 
print(fvalues)
Führt man obigen Code aus, erhält man folgendes Ergebnis:
[77.54, 76.64, 80.42, 75.02]



Erzeugung gleichmäßig verteilter Werte

Numpy bietet Funktionen, um Intervalle mit Werten zu erzeugen, deren Abständig gleichmäßig verteilt sind. 'arange' benutzt einen gegebenen Abstandwert um innerhalb von gegebenen Intervallgrenzen entsprechende Werte zu generieren, während 'linspace' eine bestimmte Anzahl von Werten innerhalb gegebener Intervallgrenzen berechnet. Den Abstand berechnet 'linspace' automatisch.


arange

Die Syntax von arange:

arange([start,] stop[, step,], dtype=None)

arange liefert gleichmäig verteilte Werte innerhalb eines gegebenen Intervalles zurück. Die Werte werden innerhalb des halb-offenen Intervalles '[start, stop)' generiert. Wird diese Funktion mit Integerwerten benutzt, ist sie beinahe äquivalent zu der built-in Python-Funktion 'range'. 'arange' liefert jedoch ein ndarray zurück, während range einen Listen-Iterator zurückliefert. Falls der 'start'-Parameter nicht übergeben wird, wird 'start' auf 0 gesetzt. Das Ende des Intervalls wird durch den Parameter 'stop' bestimmt. Üblicherweise wird das Intervall diesen Wert nicht beinhalten, außer in den Fällen, in denen 'step' keine Ganzzahl ist und floating-point-Effekte die Länge des Ausgabearrays beeinflussen. Der Abstand zwischen zwei benachbarten Werten des Ausgabearrays kann mittels des optionalen Parameters 'step' gesetzt werden. Der Default-Wert für 'step' ist 1.

Falls ein Wert für 'step' angegeben wird, kann der 'start'-Parameter nicht mehr optional sein, d.h. er muss dann auch angegeben werden.

Der Type des Ausgabearrays kann mit dem Parameter 'dtype' bestimmt werden. Wird er nicht angegeben, wird der Typ automatisch aus den übergebenen Eingabewerten ermittelt.

import numpy as np
a = np.arange(1, 10)
print(a)
# compare to range:
x = range(1,10)
print(x)    # x is an iterator
print(list(x))
# some more arange examples:
x = np.arange(10.4)
print(x)
x = np.arange(0.5, 10.4, 0.8)
print(x)
x = np.arange(0.5, 10.4, 0.8, int)
print(x)
Führt man obigen Code aus, erhält man Folgendes:
[1 2 3 4 5 6 7 8 9]
range(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[  0.   1.   2.   3.   4.   5.   6.   7.   8.   9.  10.]
[  0.5   1.3   2.1   2.9   3.7   4.5   5.3   6.1   6.9   7.7   8.5   9.3
  10.1]
[ 0  1  2  3  4  5  6  7  8  9 10 11 12]



linspace

Die Syntax von linspace:

linspace(start, stop, num=50, endpoint=True, retstep=False)

linspace liefert ein ndarray zurück, welches aus 'num' gleichmäßig verteilten Werten aus dem geschlossenen Interval ['start', 'stop'] oder dem halb-offenen Intervall ['start', 'stop') bestehen. Ob ein geschlossenes oder ein halb-offenes Intervall zurückgeliefert wird, hängt vom Wert des Parameters 'endpoint' ab. 'stop' ist der letzte Wert des Intervalls, falls 'endpoint' nicht auf False gesetzt ist. Die Schrittweite ist unterschiedlich, je nachdem ob 'endpoint' True oder False ist.

import numpy as np
# 50 zwischen 1 und 10:
print(np.linspace(1, 10))
# 7 Werte zwischen 1 und 10:
print(np.linspace(1, 10, 7))
# jetzt ohne Endpunkt:
print(np.linspace(1, 10, 7, endpoint=False))
Der obige Code liefert folgendes Ergebnis:
[  1.           1.18367347   1.36734694   1.55102041   1.73469388
   1.91836735   2.10204082   2.28571429   2.46938776   2.65306122
   2.83673469   3.02040816   3.20408163   3.3877551    3.57142857
   3.75510204   3.93877551   4.12244898   4.30612245   4.48979592
   4.67346939   4.85714286   5.04081633   5.2244898    5.40816327
   5.59183673   5.7755102    5.95918367   6.14285714   6.32653061
   6.51020408   6.69387755   6.87755102   7.06122449   7.24489796
   7.42857143   7.6122449    7.79591837   7.97959184   8.16326531
   8.34693878   8.53061224   8.71428571   8.89795918   9.08163265
   9.26530612   9.44897959   9.63265306   9.81632653  10.        ]
[  1.    2.5   4.    5.5   7.    8.5  10. ]
[ 1.          2.28571429  3.57142857  4.85714286  6.14285714  7.42857143
  8.71428571]

Bis jetzt haben wir einen interessanten Parameter nicht besprochen. Falls der Parameter 'retstep' gesetzt ist, wird die Funktion auch den Wert des Abstandes zwischen zwei benachbarten Werten des Ausgabearrays zurückliefern. Die Funktion liefert also ein Tupel ('samples', 'step') zurück:

import numpy as np
samples, spacing = np.linspace(1, 10, retstep=True)
print(spacing)
samples, spacing = np.linspace(1, 10, 20, endpoint=True, retstep=True)
print(spacing)
samples, spacing = np.linspace(1, 10, 20, endpoint=False, retstep=True)
print(spacing)
Der obige Code führt zu folgendem Ergebnis:
0.1836734693877551
0.47368421052631576
0.45

Zeitvergleich zwischen Python-Listen und Numpy-Arrays

Einer der Hauptvorteile von NumPy ist sein Zeitvorteil gegenüber Standardpython. Schauen wir uns die folgenden Funktionen an:

import time
size_of_vec = 1000
def pure_python_version():
    t1 = time.time()
    X = range(size_of_vec)
    Y = range(size_of_vec)
    Z = []
    for i in range(len(X)):
        Z.append(X[i] + Y[i])
    return time.time() - t1
def numpy_version():
    t1 = time.time()
    X = np.arange(size_of_vec)
    Y = np.arange(size_of_vec)
    Z = X + Y
    return time.time() - t1

Wir rufen diese Funktionen auf und können den Zeitvorteil sehen:

t1 = pure_python_version()
t2 = numpy_version()
print(t1, t2)
print("Numpy is in this example " + str(t1/t2) + " faster!")
Führt man obigen Code aus, erhält man folgendes Ergebnis:
0.0004532337188720703 3.0517578125e-05
Numpy is in this example 14.8515625 faster!

Die Zeitmessung gestaltet sich einfacher und vor allen Dingen besser, wenn wir dazu das Modul timeit verwenden. In dem folgenden Skript werden wir die Timer-Klasse nutzen.

Dem Konstruktor eines Timer-Objektes können zwei Anweisungen übergeben werden: eine die gemessen werden soll und eine, die als Setup fungiert. Beide Anweisungen sind auf 'pass' per Default gesetzt. Ansonsten kann noch eine Timer-Funktion übergeben werden.

import numpy as np
from timeit import Timer
size_of_vec = 1000
def pure_python_version():
    X = range(size_of_vec)
    Y = range(size_of_vec)
    Z = []
    for i in range(len(X)):
        Z.append(X[i] + Y[i])
def numpy_version():
    X = np.arange(size_of_vec)
    Y = np.arange(size_of_vec)
    Z = X + Y
#timer_obj = Timer("x = x + 1", "x = 0")
timer_obj1 = Timer("pure_python_version()", "from __main__ import pure_python_version")
timer_obj2 = Timer("numpy_version()", "from __main__ import numpy_version")
print(timer_obj1.timeit(10))
print(timer_obj2.timeit(10))
Der obige Python-Code führt zu folgender Ausgabe:
0.004527652999968268
8.753100701142102e-05



Arrays Erzeugen

Nulldimensionale Arrays in Numpy

In NumPy kann man mehrdimensionale Arrays erzeugen. Skalare sind 0-dimensional. Im folgenden Beispiel erzeugen wir den Skalar 42. Wenden wir die ndim-Methode auf unseren Skalar an, erhalten wir die Dimension des Arrays. Wir können außerdem sehen, dass der Typ ein "numpy.ndarray-Typ ist.

x = np.array(42)
print(type(x))
print(np.ndim(x))
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
<class 'numpy.ndarray'>
0

Eindimensionales Array

Wir haben bereits in unserem anfänglichen Beispiel ein eindimensionales Array - besser als Vektoren bekannt - gesehen. Was wir bis jetzt noch nicht erwähnt haben, aber was Sie sich bereits gedacht haben, ist die Tatsache, dass die NumPy-Arrays Container sind, die nur einen Typ enthalten können, also beispielsweise nur Integers. Den homogenen Datentyp eines Arrays können wir mit dem Attribut "dtype" bestimmen, wie wir im folgenden Beispiel lernen können:

F = np.array([1, 1, 2, 3, 5, 8, 13, 21])
V = np.array([3.4, 6.9, 99.8, 12.8])
print(F.dtype)
print(V.dtype)
print(np.ndim(F))
print(np.ndim(V))
Führt man obigen Code aus, erhält man folgendes Ergebnis:
int64
float64
1
1



Zwei- und Mehrdimensionale Arrays

Natürlich sind die Arrays in NumPy nicht auf eine Dimension beschränkt. Sie können eine beliebige Dimension haben. Wir erzeugen sie, indem wir verschachtelte Listen (oder Tupel) an die array-Methode von NumPy übergeben:

A = np.array([ [3.4, 8.7, 9.9], 
               [1.1, -7.8, -0.7],
               [4.1, 12.3, 4.8]])
print(A)
print(A.ndim)
Der obige Python-Code liefert folgendes Ergebnis:
[[  3.4   8.7   9.9]
 [  1.1  -7.8  -0.7]
 [  4.1  12.3   4.8]]
2
B = np.array([ [[111, 112], [121, 122]],
               [[211, 212], [221, 222]],
               [[311, 312], [321, 322]] ])
print(B)
print(B.ndim)
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
[[[111 112]
  [121 122]]
 [[211 212]
  [221 222]]
 [[311 312]
  [321 322]]]
3



Shape / Größe eines Arrays

Die Funktion "shape" liefert die Größe ("shape") eines Arrays zurück. Die Shape wird durch ein Integer-Tupel definiert. Diese Zahlen bezeichnen die Längen der entsprechenden Array-Dimensionen. In anderen Worten: Die Shape eines Arrays ist ein Tupel mit der Anzahl der Elemente pro Achse (Dimension). In unserem Beispiel ist die Shape gleich (6, 3). Das bedeutet, das wir sechs Zeilen und drei Spalten haben.

x = np.array([ [67, 63, 87],
               [77, 69, 59],
               [85, 87, 99],
               [79, 72, 71],
               [63, 89, 93],
               [68, 92, 78]])
print(np.shape(x))
Der obige Code führt zu folgendem Ergebnis:
(6, 3)

Es gibt auch ein äquivalentes Array-Attribut:

print(x.shape)
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
(6, 3)
Numerierung der Achsen

Die Shape eines Arrays sagt uns auch etwas über die Reihenfolge in der die Indizes ausgeführt werden, d.h. zuerst die Reihen, dann die Spalten und dann gegebenenfalls die anderen Dimensionen.

"shape" kann auch dazu genutzt werden die "Shape" eines Arrays zu ändern.

x.shape = (3, 6)
print(x)
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
[[67 63 87 77 69 59]
 [85 87 99 79 72 71]
 [63 89 93 68 92 78]]
x.shape = (2, 9)
print(x)
Führt man obigen Code aus, erhält man folgendes Ergebnis:
[[67 63 87 77 69 59 85 87 99]
 [79 72 71 63 89 93 68 92 78]]

Viele haben sicherlich bereits vermutet, dass die neue Shape der Anzahl der Elemente des Arrays entsprechen muss, d.h. die totale Größe des neuen Arrays muss die gleiche als die alte sein. Eine Ausnahme wird erhoben (raised), wenn dies nicht der Fall ist:

x.shape = (4, 4)
Der obige Python-Code liefert Folgendes:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-24-5c4497921b8c> in <module>()
----> 1 x.shape = (4, 4)
ValueError: total size of new array must be unchanged

Wir wollen uns nun einige weitere Beispiel anschauen.

Die Shape eines Skalars ist ein leeres Tupel:

x = np.array(11)
print(np.shape(x))
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
()
B = np.array([ [[111, 112], [121, 122]],
               [[211, 212], [221, 222]],
               [[311, 312], [321, 322]] ])
print(B.shape)
Führt man obigen Code aus, erhält man Folgendes:
(3, 2, 2)



Indizierung und Teilbereichsoperator

Der Zugriff oder die Zuweisung an die Elemente eines Arrays funktioniert ähnlich wie bei den sequentiellen Datentypen von Python, d.h. den Listen und Tupeln. Außerdem haben wir verschiedene Optionen zu Indizieren. Dies macht das Indizieren in Numpy sehr mächtig und ähnlich zum Standardpython.

Einzelne Element zu indizieren funktioniert so, wie es die meisten wahrscheinlich erwarten:

F = np.array([1, 1, 2, 3, 5, 8, 13, 21])
# print the first element of F, i.e. the element with the index 0
print(F[0])
# print the last element of F
print(F[-1])
B = np.array([ [[111, 112], [121, 122]],
               [[211, 212], [221, 222]],
               [[311, 312], [321, 322]] ])
print(B[0][1][0])
Führt man obigen Code aus, erhält man Folgendes:
1
21
121

Ehrdimensionale Arrays indizieren:

A = np.array([ [3.4, 8.7, 9.9], 
               [1.1, -7.8, -0.7],
               [4.1, 12.3, 4.8]])
print(A[1][0])
Wir erhalten die folgende Ausgabe:
1.1

Wir haben auf das Element in der zweiten Zeile, d.h. die Zeile mit dem Index 1, und der ersten Spalte (Index 0) zugegriffen. Wir haben auf dieses Element in der gleichen Art zugegriffen, wie wir mit einem Element in einer verschachtelten Python-List verfahren hätten.

Es gibt aber auch eine Alternative: Wir benutzen nur ein Klammernpaar und alle Indizes werden mit Kommas separiert:

print(A[1, 0])
Führt man obigen Code aus, erhält man folgende Ausgabe:
1.1

Man muss sich aber der Tatsache bewusst sein, dass die zweite Art effizienter ist. Im ersten Fall erzeugen wir ein als Zwischenschritt ein Array A[1], in dem wir dann auf das Element mit dem Index 0 zugreifen. Dies entspricht in etwa dem Folgenden:

tmp = A[1]
print(tmp)
print(tmp[0])
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
[ 1.1 -7.8 -0.7]
1.1

Wir nehmen an, dass Sie bereits vertraut sind mit den Teilbereichsoperatoren (slicing) von den Listen und Tupeln.

Das englische Verb "to slice" bedeutet in Deutsch "schneiden" oder auch "in Scheiben schneiden". Letztere Bedeutung entspricht auch der Arbeitsweise des Teilbereichsoperators in Python und Numpy. Man schneidet sich gewissermaßen eine "Scheibe" aus einem sequentiellen Datentyp oder einem Array heraus.

Die Syntax in Numpy ist analog zu der von Standardpython im Falle von eindimensionalen Arrays. Allerdings können wir "Slicing" auch auf mehrdimensionale Arrays anwenden.

Die allgemeine Syntax für den eindimensionalen Fall lautet wie folgt:

A[start:stop:step]

Wir demonstrieren die Arbeitsweise des Teilbereichsoperators an einigen Beispielen. Wir beginnen mit dem einfachsten Fall, also dem eindimensionalen Array:

S = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(S[2:5])
print(S[:4])
print(S[6:])
print(S[:])
Der obige Code führt zu folgendem Ergebnis:
[2 3 4]
[0 1 2 3]
[6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]

Die Anwendung des Teilbereichsoperators auf mehrdimensionale Arrays illustrieren wir in den folgenden Beispielen. Die Bereiche für jede Dimension werden durch Kommas getrennt:

A = np.array([
[11,12,13,14,15],
[21,22,23,24,25],
[31,32,33,34,35],
[41,42,43,44,45],
[51,52,53,54,55]])
print(A[:3,2:])
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
[[13 14 15]
 [23 24 25]
 [33 34 35]]
Abbildung zum ersten Beispiel eines Teilbereichsoperators in Numpy
print(A[3:,:])
Führt man obigen Code aus, erhält man Folgendes:
[[41 42 43 44 45]
 [51 52 53 54 55]]
Abbildung zum zweiten Beispiel eines Teilbereichsoperators in Numpy
print(A[:,4:])
Der obige Code führt zu folgendem Ergebnis:
[[15]
 [25]
 [35]
 [45]
 [55]]
Abbildung zum dritten Beispiel eines Teilbereichsoperators in Numpy

Die folgenden beiden Beispiele benutzten auch noch den dritten Parameter "step". Die reshape-Funktion benutzen wir um ein eindimensionales Array in ein zweidimensionales zu wandeln. Wir werden reshape im folgenden Unterkapitel erklären:

X = np.arange(28).reshape(4,7)
print(X)
Wir erhalten die folgende Ergebnisse:
[[ 0  1  2  3  4  5  6]
 [ 7  8  9 10 11 12 13]
 [14 15 16 17 18 19 20]
 [21 22 23 24 25 26 27]]
print(X[::2, ::3])
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
[[ 0  3  6]
 [14 17 20]]
Abbildung zum vierten Beispiel eines Teilbereichsoperators in Numpy
print(X[::, ::3])
Der obige Python-Code liefert Folgendes:
[[ 0  3  6]
 [ 7 10 13]
 [14 17 20]
 [21 24 27]]
Abbildung zum fünften Beispiel eines Teilbereichsoperators in Numpy

Warnzeichen

Achtung: Während der Teilbereichsoperator bei Listen und Tuples neue Objekte erzeugt, generiert er bei Numpy nur eine Sicht (englisch: "view") auf das Originalarray. Dadurch erhalten wir einen andere Möglichkeit das Array anzusprechen, oder besser einen Teil des Arrays. Daraus folgt, dass wenn wir eine Sicht verändern, wir auch das Originalarray verändern.

A = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
S = A[2:6]
S[0] = 22
S[1] = 23
print(A)
Führt man obigen Code aus, erhält man folgende Ausgabe:
[ 0  1 22 23  4  5  6  7  8  9]

Wenn wir das analog bei Listen tun, sehen wir, dass wir eine Kopie erhalten. Genaugenommen müssten wir sagen, eine flache Kopie. Den Unterschied zwischen flacher und tiefer Kopie haben wir in unserem Kapitel "Flaches und tiefes Kopieren" ausführlich erklärt.

lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
lst2 = lst[2:6]
lst2[0] = 22
lst2[1] = 23
print(lst)
Der obige Code liefert folgendes Ergebnis:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Will man prüfen, ob zwei Arrays auf den gleichen Speicherbereich zugreifen, so kann man die Funktion "np.may_share_memory" benutzen:

np.may_share_memory(A, B)

Um zu entscheiden, ob zwei Arrays A und B sich Speicher teilen, werden die Speichergrenzen von A und B berechnet. Die Funktion liefert True zurück, falls sie überlappen und False ansonsten. Die Funktion kann allerdings falsch-positive Ergebnisse liefern, d.h. falsch sie True zurückliefert könnten die Arrays gleich sein.

np.may_share_memory(A, S)
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
True

Der folgende Code zeigt einen Fall, in dem die Benutzung von may_share_memory ziemlich sinnvoll ist:

A = np.arange(12)
B = A.reshape(3, 4)
A[0] = 42
print(B)
Führt man obigen Code aus, erhält man Folgendes:
[[42  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

Wir können erkennen, dass A und B in gewisser Weise sich den Speicher teilen müssen. Das array-Attribut "data" ist ein Objekt-Zeiger auf den Anfang der Daten eines Arrays. Schauen wir uns die data-Attribute an, zeigt sich etwas erstaunliches:

print(A.data)
print(B.data)
print(A.data == B.data)
Wir können die folgenden Ergebnisse erwarten, wenn wir den obigen Python-Code ausführen:
<memory at 0x7f15fc078b38>
<memory at 0x7f15fc0969e8>
False

Wie sieht es aber mit der Gleichheit der Arrays aus?

print(A == B)
Der obige Python-Code liefert folgendes Ergebnis:
False

Dies ergibt Sinn, weil es sich um verschiedene Arrays handelt bezüglich Ihrer Struktur.

print(A)
print(B)
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
[42  1  2  3  4  5  6  7  8  9 10 11]
[[42  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

Wir haben aber gesehen, dass wenn wir ein Element eines Arrays ändern auch automatisch das andere Array verändert wird. Diese Tatsache wird durch may_share_memory reflektiert:

np.may_share_memory(A, B)
Führt man obigen Code aus, erhält man folgende Ausgabe:
True

Das obige Beispiel ist ein falsch-positiv-Beispiel für may_share_memory in dem Sinn, dass jemand denken könnte, dass die Arrays dir selben wären, was aber nicht der Fall ist.



Arrays mit Nullen und Einsen

Arrays können auf zwei Arten mit Nullen und Einsen initialisiert werden. Die Methode ones(t) hat als Parameter ein Tupel t mit der Shape des Arrays und erzeugt entsprechend ein Array mit Einsen. Defaultmäßig wird es mit float-Einsen gefüllt. Wenn man integer-Einsen benötigt, kann man den optionalen Parameter dtype auf "int"setzen:

import numpy as np
E = np.ones((2,3))
print(E)
F = np.ones((3,4),dtype=int)
print(F)
Der obige Python-Code liefert Folgendes:
[[ 1.  1.  1.]
 [ 1.  1.  1.]]
[[1 1 1 1]
 [1 1 1 1]
 [1 1 1 1]]

Was wir über die Methode ones(9 gesagt haben gilt analog für zeros(), wie wir im folgenden Beispiel erkennen können:

Z = np.zeros((2,4))
print(Z)
Der obige Code führt zu folgendem Ergebnis:
[[ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]]

Es gibt noch eine andere interessante Möglichkeit ein Array mit Einsen oder Nullen zu erzeugen, wenn es die gleiche Shape wie ein anderes existierendes Array 'a' haben soll. Für diesen Zweck stellt Numpy die Methoden ones_like() und zeros_like() zur Verfügung:

x = np.array([2,5,18,14,4])
E = np.ones_like(x)
print(E)
Z = np.zeros_like(x)
print(Z)
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
[1 1 1 1 1]
[0 0 0 0 0]



Arrays kopieren


numpy.copy()

copy(obj, order='K')

Return an array copy of the given object 'obj'.

Parameter Bedeutung
obj array-ähnliche Eingabedaten.
order Die möglichen Werte sind {'C', 'F', 'A', 'K'}. Dieser Parameter kontrolliert das Speicher-Layout der Kopie. 'C' bedeutet C-Reihenfolge, 'F' Fortran-Reihenfolge, 'A' verhält sich wie 'F' falls das Objekt 'obj' in Fortran-Reihenfolge ist, ansonsten verhält sich 'A' wie 'C'. 'K' bedeutet, dass das Layout von 'obj' so nahe wie möglich realisiert werden soll.
import numpy as np
x = np.array([[42,22,12],[44,53,66]], order='F')
y = x.copy()
x[0,0] = 1001
print(x)
print(y)
Der obige Code führt zu folgendem Ergebnis:
[[1001   22   12]
 [  44   53   66]]
[[42 22 12]
 [44 53 66]]
print(x.flags['C_CONTIGUOUS'])
print(y.flags['C_CONTIGUOUS'])
Der obige Code führt zu folgendem Ergebnis:
False
True



ndarray.copy()

Es gibt auch noch eine ndarray-Methode 'copy', die direkt auf ein Array angewendet werden kann. Sie ist ähnlich wie die obige Funktion aber die Defaultwerte sind anders.

a.copy(order='C')

Liefert eine Kopie des Arrays 'a' zurück.

Parameter Bedeutung
order Gleich wie numpy.copy, aber 'C' ist der Default-Wert.
import numpy as np
x = np.array([[42,22,12],[44,53,66]], order='F')
y = x.copy()
x[0,0] = 1001
print(x)
print(y)
print(x.flags['C_CONTIGUOUS'])
print(y.flags['C_CONTIGUOUS'])
Der obige Code liefert folgendes Ergebnis:
[[1001   22   12]
 [  44   53   66]]
[[42 22 12]
 [44 53 66]]
False
True



Identitäts-Array

Ein Identitäts-Array ist ein quadratisches Array. Es gibt zwei Möglichkeiten ein solches Array zu erzeugen:


Die identity-Funktion

Wir können Identitäts-Arrays mit der Funktion identity generieren:

identity(n, dtype=None)

Die Parameter:

Parameter Bedeutung
n Eine Integer-Zahl, welche die Anzahl der Reihen und Spalten der Ausgabe definiert, d.h. 'n' x 'n'
dtype Ein optionales Argument, welches den Datentyp des Ergebnisses definiert. Der Default ist 'float'

Die Ausgabe von identity ist ein 'n' x 'n'-Array, in dem die Elemente auf der hauptdiagonalen auf 1 gesetzt sind und alle anderen Elemente auf 0.

import numpy as np
np.identity(4)
Wir erhalten die folgende Ergebnisse:
array([[ 1.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  1.]])
np.identity(4, dtype=int) # equivalent to np.identity(3, int)
Der obige Python-Code liefert Folgendes:
array([[1, 0, 0, 0],
       [0, 1, 0, 0],
       [0, 0, 1, 0],
       [0, 0, 0, 1]])



Die eye-Funktion

Die Funktion eye bietet eine andere Möglichkeit Identitätsarrays zu erzeugen. Die Ausgabe von eye ist zweidimensionales Array, in dem die Elemente auf der hauptdiagonalen auf 1 gesetzt sind und alle anderen Elemente auf 0.

eye(N, M=None, k=0, dtype=float)

Parameter Bedeutung
N Eine Integer-Zahl, welche die Reihen des Ausgebearrays bestimmt.
M Eine Integer-Zahl, welche die Spalten des Ausgebearrays bestimmt. Falls dieser Parameter nicht gesetzt ist oder None ist, wird er per Default auf 'N' gesetzt.
k mit 'k' wird die Position der Diagonalen gesetzt. Der Default ist 0. 0 und bezeichnet die Hauptdiagonale. Ein positiver Wert bezeichnet eine obere Diagonale und ein negativer Wert eine untere Diagonale.
dtype Ein optionales Argument, welches den Datentyp des Ergebnisses definiert. Der Default ist 'float'.

eye liefert ein ndarray der Shape (N,M) zurück. Alle Elemente dieses Arrays sind 0 außer denen auf der 'k'-ten Diagonale, die auf 1 gesetzt sind.

import numpy as np
np.eye(5, 8, k=1, dtype=int)
Führt man obigen Code aus, erhält man folgendes Ergebnis:
array([[0, 1, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0, 0],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 1, 0, 0]])

Die Arbeitsweise des Parameters 'd' von 'eye' illustrieren wir in folgendem Diagramm:

Principle of operation eye function and parameter d



Übungen:

  1. Erzeugen Sie ein beliebiges eindimensionales Array, welches Sie "v" nennen.

  2. Erzeugen Sie nun ein neues Array, das aus den ungeraden Indizes des vorher erzeugten Arrays "v" besteht.

  3. Erzeugen Sie aus 'v' eine Array in umgekehrter Reihenfolge.

  4. Wie sieht die Ausgabe des folgenden Codes aus?
       a = np.array([1, 2, 3, 4, 5])
       b = a[1:4]
       print(a[1])
       
  5. Erzeugen Sie ein zweidimensionales Array mit dem Namen "m".

  6. Erzeugen Sie nun ein neues Array aus 'm', in dem die Elemente von jeder Reihe in umgekehrter Reihenfolge sind.

  7. Ein weiteres, in dem die Reihen in vertauschter Reihenfolge sind.

  8. Erzeugen Sie ein Array aus m, in dem sowohl die Reihen als auch die Spalten in umgekehrter Reihenfolge sind.

  9. Schneiden Sie die erste und die letzte Reihe und Spalte ab.



Lösungen zu den Übungen:

  1.    import numpy as np
       a = np.array([3,8,12,18,7,11,30])
       
  2. odd_elements = a[1::2]
  3. reverse_order = a[::-1]

  4. Die Ausgabe lautet 200, weil 'slices' Views und keine Kopien sind.

  5. m = np.array([ [11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]])

  6. m[::,::-1]

  7. m[::-1]

  8. m[::-1,::-1]

  9. m[1:-1,1:-1]