Kapitel 4. Numerisches Rechnen mit NumPy
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Computer sind nutzlos. Sie können nur Antworten geben.
Pablo Picasso
Obwohl der Python-Interpreter selbst bereits eine Vielzahl von Datenstrukturen mitbringt, ergänzen NumPy
und andere Bibliotheken diese auf wertvolle Weise. Dieses Kapitel konzentriert sich auf NumPy
, das ein multidimensionales Array-Objekt zur Verfügung stellt, um homogene oder heterogene Datenarrays zu speichern und die Vektorisierung von Code zu unterstützen.
Das Kapitel behandelt die folgenden Datenstrukturen:
Objekttyp | Bedeutung | Verwendet für |
---|---|---|
|
n-dimensionales Array-Objekt |
Große Arrays mit numerischen Daten |
|
2-dimensionales Array-Objekt |
Tabellarische Daten, die in Spalten organisiert sind |
Dieses Kapitel ist wie folgt gegliedert:
- "Arrays of Data"
-
In diesem Abschnitt geht es um den Umgang mit Arrays von Daten mit reinem Python-Code.
- "Reguläre NumPy-Arrays"
-
Dies ist der Kernabschnitt über die reguläre Klasse
NumPy
ndarray
, das Arbeitspferd in fast allen datenintensiven Python-Anwendungen, die numerische Daten beinhalten. - "Strukturierte NumPy-Arrays"
-
In diesem kurzen Abschnitt werden strukturierte (oder Datensatz-)
ndarray
Objekte für die Bearbeitung von Tabellendaten mit Spalten vorgestellt. - "Vektorisierung von Code"
-
In diesem Abschnitt werden die Vorteile der Vektorisierung von Code und die Bedeutung der Speicheranordnung in bestimmten Szenarien diskutiert.
Arrays von Daten
Das vorherige Kapitel hat gezeigt, dass Python einige recht nützliche und flexible allgemeine Datenstrukturen bietet. Insbesondere list
Objekte können als echte Arbeitstiere mit vielen praktischen Eigenschaften und Anwendungsbereichen angesehen werden. Die Verwendung einer solch flexiblen (veränderbaren) Datenstruktur hat ihren Preis, und zwar in Form eines relativ hohen Speicherverbrauchs, einer geringeren Leistung oder beidem. Wissenschaftliche und finanzielle Anwendungen benötigen jedoch in der Regel leistungsstarke Operationen mit speziellen Datenstrukturen. Eine der wichtigsten Datenstrukturen in diesem Zusammenhang ist das Array. Arrays strukturieren im Allgemeinen andere (grundlegende) Objekte desselben Datentyps in Zeilen und Spalten.
Nehmen wir einmal an, dass nur Zahlen relevant sind, obwohl sich das Konzept auch auf andere Arten von Daten übertragen lässt. Im einfachsten Fall stellt ein eindimensionales Array dann mathematisch gesehen einen Vektor von reellen Zahlen dar, die intern durch float
Objekte repräsentiert werden. Es besteht dann nur aus einer einzigen Zeile oder Spalte von Elementen. Im häufigeren Fall stellt ein Array eine i × j-Matrix aus Elementen dar. Dieses Konzept lässt sich auf i × j × k Würfel von Elementen in drei Dimensionen sowie auf allgemeine n-dimensionale Arrays der Form .
Mathematische Disziplinen wie die lineare Algebra und die Vektorraumtheorie zeigen, dass solche mathematischen Strukturen in einer Reihe von wissenschaftlichen Disziplinen und Bereichen von großer Bedeutung sind. Daher kann es sich als nützlich erweisen, eine spezialisierte Klasse von Datenstrukturen zur Verfügung zu haben, die explizit für den bequemen und effizienten Umgang mit Arrays entwickelt wurde. Hier kommt die Python-Bibliothek NumPy
ins Spiel, mit ihrer leistungsstarken Klasse ndarray
. Bevor diese Klasse im nächsten Abschnitt vorgestellt wird, zeigt dieser Abschnitt zwei Alternativen für den Umgang mit Arrays.
Arrays mit Python-Listen
Arrays können mit den eingebauten Datenstrukturen konstruiert werden, die im vorherigen Kapitel vorgestellt wurden. list
Objekte eignen sich besonders gut für diese Aufgabe. Ein einfaches list
kann bereits als eindimensionales Array betrachtet werden:
In
[
1
]
:
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
Da list
Objekte beliebige andere Objekte enthalten können, können sie auch andere list
Objekte enthalten. Auf diese Weise lassen sich zwei- und höherdimensionale Arrays leicht durch verschachtelte list
Objekte aufbauen:
In
[
2
]
:
m
=
[
v
,
v
,
v
]
m
Out
[
2
]
:
[
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
]
Man kann auch einfach Zeilen über eine einfache Indizierung oder einzelne Elemente über eine doppelte Indizierung auswählen (ganze Spalten sind jedoch nicht so einfach auszuwählen):
In
[
3
]:
m
[
1
]
Out
[
3
]:
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
In
[
4
]:
m
[
1
][
0
]
Out
[
4
]:
0.5
Die Verschachtelung kann für noch allgemeinere Strukturen weiter vorangetrieben werden:
In
[
5
]
:
v1
=
[
0.5
,
1.5
]
v2
=
[
1
,
2
]
m
=
[
v1
,
v2
]
c
=
[
m
,
m
]
c
Out
[
5
]
:
[
[
[
0.5
,
1.5
]
,
[
1
,
2
]
]
,
[
[
0.5
,
1.5
]
,
[
1
,
2
]
]
]
In
[
6
]
:
c
[
1
]
[
1
]
[
0
]
Out
[
6
]
:
1
Beachte, dass das Kombinieren von Objekten auf die soeben vorgestellte Weise im Allgemeinen mit Referenzzeigern auf die ursprünglichen Objekte funktioniert. Was bedeutet das in der Praxis? Sieh dir die folgenden Operationen an:
In
[
7
]:
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
m
=
[
v
,
v
,
v
]
m
Out
[
7
]:
[[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
],
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
],
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]]
Ändere nun den Wert des ersten Elements des v
Objekts und schau, was mit dem m
Objekt passiert:
In
[
8
]:
v
[
0
]
=
'Python'
m
Out
[
8
]:
[[
'Python'
,
0.75
,
1.0
,
1.5
,
2.0
],
[
'Python'
,
0.75
,
1.0
,
1.5
,
2.0
],
[
'Python'
,
0.75
,
1.0
,
1.5
,
2.0
]]
Dies kann vermieden werden, indem du die Funktion deepcopy()
des Moduls copy
verwendest:
In
[
9
]
:
from
copy
import
deepcopy
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
m
=
3
*
[
deepcopy
(
v
)
,
]
m
Out
[
9
]
:
[
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
]
In
[
10
]
:
v
[
0
]
=
'
Python
'
m
Out
[
10
]
:
[
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
]
Die Python-Array-Klasse
Mit gibt es ein eigenes array
Modul in Python. In der Dokumentation steht:
Dieses Modul definiert einen Objekttyp, der ein Array von Basiswerten kompakt darstellen kann: Zeichen, Ganzzahlen, Fließkommazahlen. Arrays sind Sequenztypen und verhalten sich ähnlich wie Listen, mit dem Unterschied, dass der Typ der in ihnen gespeicherten Objekte eingeschränkt ist. Der Typ wird bei der Objekterstellung durch einen Typcode festgelegt, der aus einem einzigen Zeichen besteht.
Betrachte den folgenden Code, der ein array
Objekt aus einem list
Objekt instanziiert:
In
[
11
]
:
v
=
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
In
[
12
]
:
import
array
In
[
13
]
:
a
=
array
.
array
(
'
f
'
,
v
)
a
Out
[
13
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
)
In
[
14
]
:
a
.
append
(
0.5
)
a
Out
[
14
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
]
)
In
[
15
]
:
a
.
extend
(
[
5.0
,
6.75
]
)
a
Out
[
15
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
]
)
In
[
16
]
:
2
*
a
Out
[
16
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
,
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
]
)
Die Instanziierung des
array
Objekts mitfloat
als Typcode.Die wichtigsten Methoden funktionieren ähnlich wie die des
list
Objekts.Obwohl die "Skalarmultiplikation" im Prinzip funktioniert, ist das Ergebnis nicht das mathematisch erwartete; vielmehr werden die Elemente wiederholt.
Der Versuch, ein Objekt mit einem anderen Datentyp als dem angegebenen anzuhängen, löst eine TypeError
aus:
In
[
17
]
:
a
.
append
(
'
string
'
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TypeErrorTraceback
(
most
recent
call
last
)
<
ipython
-
input
-
17
-
14
cd6281866b
>
in
<
module
>
(
)
-
-
-
-
>
1
a
.
append
(
'
string
'
)
TypeError
:
must
be
real
number
,
not
str
In
[
18
]
:
a
.
tolist
(
)
Out
[
18
]
:
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
,
0.5
,
5.0
,
6.75
]
Nur
float
Objekte können angehängt werden; andere Datentypen/Typcodes führen zu Fehlern.Das
array
Objekt kann jedoch leicht in einlist
Objekt zurückverwandelt werden, wenn eine solche Flexibilität erforderlich ist.
Ein Vorteil der Klasse array
ist, dass sie über eine integrierte Speicherung und Abruffunktion verfügt:
In
[
19
]
:
f
=
open
(
'
array.apy
'
,
'
wb
'
)
a
.
tofile
(
f
)
f
.
close
(
)
In
[
20
]
:
with
open
(
'
array.apy
'
,
'
wb
'
)
as
f
:
a
.
tofile
(
f
)
In
[
21
]
:
!
ls
-
n
arr
*
-
rw
-
r
-
-
r
-
-
@
1
503
20
32
Nov
7
11
:
46
array
.
apy
Öffnet eine Datei auf der Festplatte zum Schreiben von Binärdaten.
Schreibt die Daten von
array
in die Datei.Schließt die Datei.
Alternative: verwendet einen
with
Kontext für denselben Vorgang.Zeigt die Datei so an, wie sie auf die Festplatte geschrieben wurde.
Wie zuvor ist der Datentyp des array
Objekts beim Lesen der Daten von der Festplatte von Bedeutung:
In
[
22
]
:
b
=
array
.
array
(
'
f
'
)
In
[
23
]
:
with
open
(
'
array.apy
'
,
'
rb
'
)
as
f
:
b
.
fromfile
(
f
,
5
)
In
[
24
]
:
b
Out
[
24
]
:
array
(
'
f
'
,
[
0.5
,
0.75
,
1.0
,
1.5
,
2.0
]
)
In
[
25
]
:
b
=
array
.
array
(
'
d
'
)
In
[
26
]
:
with
open
(
'
array.apy
'
,
'
rb
'
)
as
f
:
b
.
fromfile
(
f
,
2
)
In
[
27
]
:
b
Out
[
27
]
:
array
(
'
d
'
,
[
0.0004882813645963324
,
0.12500002956949174
]
)
Reguläre NumPy-Arrays
Das Zusammensetzen von Array-Strukturen mit list
Objekten funktioniert einigermaßen. Aber es ist nicht wirklich praktisch, und die Klasse list
wurde nicht mit diesem speziellen Ziel im Hinterkopf entwickelt. Sie hat vielmehr einen viel breiteren und allgemeineren Anwendungsbereich. Die Klasse array
ist etwas spezieller und bietet einige nützliche Funktionen für die Arbeit mit Arrays von Daten. Eine wirklich spezialisierte Klasse könnte jedoch sehr nützlich sein, um mit arrayartigen Strukturen zu arbeiten.
Die Grundlagen
numpy.ndarray
ist eine solche Klasse, die mit dem Ziel entwickelt wurde, n-dimensionale Arrays bequem und effizient - d.h. hochperformant - zu verarbeiten. Die grundlegende Handhabung von Instanzen dieser Klasse lässt sich wiederum am besten anhand von Beispielen veranschaulichen:
In
[
28
]
:
import
numpy
as
np
In
[
29
]
:
a
=
np
.
array
(
[
0
,
0.5
,
1.0
,
1.5
,
2.0
]
)
a
Out
[
29
]
:
array
(
[
0.
,
0.5
,
1.
,
1.5
,
2.
]
)
In
[
30
]
:
type
(
a
)
Out
[
30
]
:
numpy
.
ndarray
In
[
31
]
:
a
=
np
.
array
(
[
'
a
'
,
'
b
'
,
'
c
'
]
)
a
Out
[
31
]
:
array
(
[
'
a
'
,
'
b
'
,
'
c
'
]
,
dtype
=
'
<U1
'
)
In
[
32
]
:
a
=
np
.
arange
(
2
,
20
,
2
)
a
Out
[
32
]
:
array
(
[
2
,
4
,
6
,
8
,
10
,
12
,
14
,
16
,
18
]
)
In
[
33
]
:
a
=
np
.
arange
(
8
,
dtype
=
np
.
float
)
a
Out
[
33
]
:
array
(
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
)
In
[
34
]
:
a
[
5
:
]
Out
[
34
]
:
array
(
[
5.
,
6.
,
7.
]
)
In
[
35
]
:
a
[
:
2
]
Out
[
35
]
:
array
(
[
0.
,
1.
]
)
Importiert das Paket
numpy
.Erzeugt ein
ndarray
Objekt aus einemlist
Objekt mitfloat
s.Erzeugt ein
ndarray
Objekt aus einemlist
Objekt mitstr
s.np.arange()
funktioniert ähnlich wierange()
...... nimmt aber als zusätzliche Eingabe den Parameter
dtype
.Bei eindimensionalen
ndarray
Objekten funktioniert die Indizierung wie gewohnt.
Ein wichtiges Merkmal der Klasse ndarray
ist die Vielzahl der eingebauten Methoden. Zum Beispiel:
In
[
36
]
:
a
.
sum
(
)
Out
[
36
]
:
28.0
In
[
37
]
:
a
.
std
(
)
Out
[
37
]
:
2.29128784747792
In
[
38
]
:
a
.
cumsum
(
)
Out
[
38
]
:
array
(
[
0.
,
1.
,
3.
,
6.
,
10.
,
15.
,
21.
,
28.
]
)
Die Summe aller Elemente.
Die Standardabweichung der Elemente.
Die kumulative Summe aller Elemente (beginnend bei Indexposition 0).
Ein weiteres wichtiges Merkmal von sind die (vektorisierten) mathematischen Operationen, die auf ndarray
Objekten:
In
[
39
]
:
l
=
[
0.
,
0.5
,
1.5
,
3.
,
5.
]
2
*
l
Out
[
39
]
:
[
0.0
,
0.5
,
1.5
,
3.0
,
5.0
,
0.0
,
0.5
,
1.5
,
3.0
,
5.0
]
In
[
40
]
:
a
Out
[
40
]
:
array
(
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
)
In
[
41
]
:
2
*
a
Out
[
41
]
:
array
(
[
0.
,
2.
,
4.
,
6.
,
8.
,
10.
,
12.
,
14.
]
)
In
[
42
]
:
a
*
*
2
Out
[
42
]
:
array
(
[
0.
,
1.
,
4.
,
9.
,
16.
,
25.
,
36.
,
49.
]
)
In
[
43
]
:
2
*
*
a
Out
[
43
]
:
array
(
[
1.
,
2.
,
4.
,
8.
,
16.
,
32.
,
64.
,
128.
]
)
In
[
44
]
:
a
*
*
a
Out
[
44
]
:
array
(
[
1.00000e+00
,
1.00000e+00
,
4.00000e+00
,
2.70000e+01
,
2.56000e+02
,
3.12500e+03
,
4.66560e+04
,
8.23543e+05
]
)
Die Skalarmultiplikation mit
list
Objekten führt zu einer Wiederholung der Elemente.Im Gegensatz dazu wird bei der Arbeit mit
ndarray
Objekten eine richtige skalare Multiplikation durchgeführt.Damit werden die quadratischen Werte elementweise berechnet.
Damit werden die Elemente der
ndarray
als Kräfte interpretiert.Damit wird die Leistung jedes Elements zu sich selbst berechnet.
Universelle Funktionen sind ein weiteres wichtiges Merkmal des NumPy
Pakets. Sie sind "universell" in dem Sinne, dass sie im Allgemeinen sowohl auf ndarray
Objekte als auch auf grundlegende Python-Datentypen wirken. Wenn du universelle Funktionen z. B. auf ein Python-Objekt float
anwendest, musst du dich jedoch der geringeren Leistung im Vergleich zu den gleichen Funktionen im Modul math
bewusst sein:
In
[
45
]
:
np
.
exp
(
a
)
Out
[
45
]
:
array
(
[
1.00000000e+00
,
2.71828183e+00
,
7.38905610e+00
,
2.00855369e+01
,
5.45981500e+01
,
1.48413159e+02
,
4.03428793e+02
,
1.09663316e+03
]
)
In
[
46
]
:
np
.
sqrt
(
a
)
Out
[
46
]
:
array
(
[
0.
,
1.
,
1.41421356
,
1.73205081
,
2.
,
2.23606798
,
2.44948974
,
2.64575131
]
)
In
[
47
]
:
np
.
sqrt
(
2.5
)
Out
[
47
]
:
1.5811388300841898
In
[
48
]
:
import
math
In
[
49
]
:
math
.
sqrt
(
2.5
)
Out
[
49
]
:
1.5811388300841898
In
[
50
]
:
math
.
sqrt
(
a
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TypeErrorTraceback
(
most
recent
call
last
)
<
ipython
-
input
-
50
-
b39de4150838
>
in
<
module
>
(
)
-
-
-
-
>
1
math
.
sqrt
(
a
)
TypeError
:
only
size
-
1
arrays
can
be
converted
to
Python
scalars
In
[
51
]
:
%
timeit
np
.
sqrt
(
2.5
)
722
ns
±
13.7
ns
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
1000000
loops
each
)
In
[
52
]
:
%
timeit
math
.
sqrt
(
2.5
)
91.8
ns
±
4.13
ns
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10000000
loops
each
)
Berechnet die Exponentialwerte elementweise.
Berechnet die Quadratwurzel für jedes Element.
Berechnet die Quadratwurzel für ein Python
float
Objekt.Die gleiche Berechnung, diesmal mit dem Modul
math
.Die Funktion
math.sqrt()
kann nicht direkt auf das Objektndarray
angewendet werden.Die Anwendung der universellen Funktion
np.sqrt()
auf ein Pythonfloat
Objekt ...... ist viel langsamer als der gleiche Vorgang mit der Funktion
math.sqrt()
.
Mehrere Dimensionen
Der Übergang zu mehr als einer Dimension ist nahtlos, und alle bisher vorgestellten Merkmale lassen sich auf die allgemeineren Fälle übertragen. Insbesondere wird das Indexierungssystem über alle Dimensionen hinweg einheitlich gestaltet:
In
[
53
]
:
b
=
np
.
array
(
[
a
,
a
*
2
]
)
b
Out
[
53
]
:
array
(
[
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
,
[
0.
,
2.
,
4.
,
6.
,
8.
,
10.
,
12.
,
14.
]
]
)
In
[
54
]
:
b
[
0
]
Out
[
54
]
:
array
(
[
0.
,
1.
,
2.
,
3.
,
4.
,
5.
,
6.
,
7.
]
)
In
[
55
]
:
b
[
0
,
2
]
Out
[
55
]
:
2.0
In
[
56
]
:
b
[
:
,
1
]
Out
[
56
]
:
array
(
[
1.
,
2.
]
)
In
[
57
]
:
b
.
sum
(
)
Out
[
57
]
:
84.0
In
[
58
]
:
b
.
sum
(
axis
=
0
)
Out
[
58
]
:
array
(
[
0.
,
3.
,
6.
,
9.
,
12.
,
15.
,
18.
,
21.
]
)
In
[
59
]
:
b
.
sum
(
axis
=
1
)
Out
[
59
]
:
array
(
[
28.
,
56.
]
)
Konstruiert ein zweidimensionales
ndarray
Objekt aus dem eindimensionalen.Wählt die erste Zeile aus.
Wählt das dritte Element in der ersten Zeile aus; die Indizes werden innerhalb der Klammern durch ein Komma getrennt.
Wählt die zweite Spalte aus.
Berechnet die Summe aller Werte.
Berechnet die Summe entlang der ersten Achse, d. h. spaltenweise.
Berechnet die Summe entlang der zweiten Achse, d. h. zeilenweise.
Es gibt mehrere Möglichkeiten, ndarray
Objekte zu initialisieren (instanziieren). Eine davon ist, wie zuvor vorgestellt, über np.array
. Dabei wird jedoch davon ausgegangen, dass alle Elemente des Arrays bereits vorhanden sind. Im Gegensatz dazu kann es sinnvoll sein, die ndarray
Objekte zuerst zu instanziieren, um sie später mit den Ergebnissen zu füllen, die bei der Ausführung des Codes entstehen. Zu diesem Zweck kann man die folgenden Funktionen verwenden:
In
[
60
]
:
c
=
np
.
zeros
(
(
2
,
3
)
,
dtype
=
'
i
'
,
order
=
'
C
'
)
c
Out
[
60
]
:
array
(
[
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
]
,
dtype
=
int32
)
In
[
61
]
:
c
=
np
.
ones
(
(
2
,
3
,
4
)
,
dtype
=
'
i
'
,
order
=
'
C
'
)
c
Out
[
61
]
:
array
(
[
[
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
]
,
[
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
,
[
1
,
1
,
1
,
1
]
]
]
,
dtype
=
int32
)
In
[
62
]
:
d
=
np
.
zeros_like
(
c
,
dtype
=
'
f16
'
,
order
=
'
C
'
)
d
Out
[
62
]
:
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.
]
]
]
,
dtype
=
float128
)
In
[
63
]
:
d
=
np
.
ones_like
(
c
,
dtype
=
'
f16
'
,
order
=
'
C
'
)
d
Out
[
63
]
:
array
(
[
[
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
]
,
[
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
,
[
1.
,
1.
,
1.
,
1.
]
]
]
,
dtype
=
float128
)
In
[
64
]
:
e
=
np
.
empty
(
(
2
,
3
,
2
)
)
e
Out
[
64
]
:
array
(
[
[
[
0.00000000e+000
,
0.00000000e+000
]
,
[
0.00000000e+000
,
0.00000000e+000
]
,
[
0.00000000e+000
,
0.00000000e+000
]
]
,
[
[
0.00000000e+000
,
0.00000000e+000
]
,
[
0.00000000e+000
,
7.49874326e+247
]
,
[
1.28822975e-231
,
4.33190018e-311
]
]
]
)
In
[
65
]
:
f
=
np
.
empty_like
(
c
)
f
Out
[
65
]
:
array
(
[
[
[
0
,
0
,
0
,
0
]
,
[
0
,
0
,
0
,
0
]
,
[
0
,
0
,
0
,
0
]
]
,
[
[
0
,
0
,
0
,
0
]
,
[
0
,
0
,
740455269
,
1936028450
]
,
[
0
,
268435456
,
1835316017
,
2041
]
]
]
,
dtype
=
int32
)
In
[
66
]
:
np
.
eye
(
5
)
Out
[
66
]
:
array
(
[
[
1.
,
0.
,
0.
,
0.
,
0.
]
,
[
0.
,
1.
,
0.
,
0.
,
0.
]
,
[
0.
,
0.
,
1.
,
0.
,
0.
]
,
[
0.
,
0.
,
0.
,
1.
,
0.
]
,
[
0.
,
0.
,
0.
,
0.
,
1.
]
]
)
In
[
67
]
:
g
=
np
.
linspace
(
5
,
15
,
12
)
g
Out
[
67
]
:
array
(
[
5.
,
5.90909091
,
6.81818182
,
7.72727273
,
8.63636364
,
9.54545455
,
10.45454545
,
11.36363636
,
12.27272727
,
13.18181818
,
14.09090909
,
15.
]
)
Erzeugt ein
ndarray
Objekt, das mit Nullen vorausgefüllt ist.Erzeugt ein
ndarray
Objekt, das mit Einsen vorausgefüllt ist.Das Gleiche, aber man braucht ein anderes
ndarray
Objekt, um auf die Form zu schließen.Erzeugt ein
ndarray
Objekt, das mit nichts vorausgefüllt ist (die Zahlen hängen von den im Speicher vorhandenen Bits ab).Erzeugt eine quadratische Matrix als
ndarray
Objekt, wobei die Diagonale mit Einsen gefüllt ist.Erzeugt ein eindimensionales
ndarray
Objekt mit gleichmäßigen Abständen zwischen den Zahlen; die verwendeten Parameter sindstart
,end
undnum
(Anzahl der Elemente).
Für alle diese Funktionen kannst du die folgenden Parameter angeben:
shape
-
Entweder ein
int
, eine Folge vonint
Objekten oder ein Verweis auf ein anderesndarray
dtype
(optional)-
A
dtype
-das sindNumPy
-spezifische Datentypen fürndarray
Objekte order
(optional)-
Die Reihenfolge, in der die Elemente im Speicher abgelegt werden:
C
für C-ähnliche (d.h. zeilenweise) oderF
für Fortran-ähnliche (d.h. spaltenweise)
Hier wird deutlich, wie NumPy
die Konstruktion von Arrays mit der Klasse ndarray
im Vergleich zum list
-basierten Ansatz spezialisiert:
-
Das Objekt
ndarray
hat eingebaute Abmessungen (Achsen). -
Das Objekt
ndarray
ist unveränderlich; seine Länge (Größe) ist fest. -
Sie erlaubt nur einen einzigen Datentyp (
np.dtype
) für das gesamte Array.
Die Klasse array
hat dagegen nur die Eigenschaft, einen einzigen Datentyp zuzulassen (Typcode, dtype
).
Die Rolle des Parameters order
wird später in diesem Kapitel besprochen. Tabelle 4-1 gibt einen Überblick über ausgewählte np.dtype
Objekte (d. h. die grundlegenden Datentypen, die NumPy
erlaubt).
dtype | Beschreibung | Beispiel |
---|---|---|
|
Boolesche |
|
|
Ganzzahl mit Vorzeichen |
|
|
Ganzzahl ohne Vorzeichen |
|
|
Fließkomma |
|
|
Komplexes Gleitkomma |
|
|
|
|
|
|
|
|
Objekt |
|
|
Unicode |
|
|
Rohdaten (ungültig) |
|
Umformung und Größenanpassung
Obwohl ndarray
Objekte standardmäßig unveränderlich sind, gibt es mehrere Möglichkeiten, ein solches Objekt umzugestalten und seine Größe zu ändern. Während die Umformung im Allgemeinen nur eine andere Sicht auf dieselben Daten ermöglicht, wird durch die Größenänderung im Allgemeinen ein neues (temporäres) Objekt erstellt. Zunächst einige Beispiele für die Umformung:
In
[
74
]
:
g
=
np
.
arange
(
15
)
In
[
75
]
:
g
Out
[
75
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
76
]
:
g
.
shape
Out
[
76
]
:
(
15
,
)
In
[
77
]
:
np
.
shape
(
g
)
Out
[
77
]
:
(
15
,
)
In
[
78
]
:
g
.
reshape
(
(
3
,
5
)
)
Out
[
78
]
:
array
(
[
[
0
,
1
,
2
,
3
,
4
]
,
[
5
,
6
,
7
,
8
,
9
]
,
[
10
,
11
,
12
,
13
,
14
]
]
)
In
[
79
]
:
h
=
g
.
reshape
(
(
5
,
3
)
)
h
Out
[
79
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
80
]
:
h
.
T
Out
[
80
]
:
array
(
[
[
0
,
3
,
6
,
9
,
12
]
,
[
1
,
4
,
7
,
10
,
13
]
,
[
2
,
5
,
8
,
11
,
14
]
]
)
In
[
81
]
:
h
.
transpose
(
)
Out
[
81
]
:
array
(
[
[
0
,
3
,
6
,
9
,
12
]
,
[
1
,
4
,
7
,
10
,
13
]
,
[
2
,
5
,
8
,
11
,
14
]
]
)
Die Form des ursprünglichen
ndarray
Objekts.Umformung in zwei Dimensionen (Speicheransicht).
Erstellen eines neuen Objekts.
Die Transponierung des neuen
ndarray
Objekts.
Während eines Umformungsvorgangs bleibt die Gesamtzahl der Elemente im ndarray
Objekt unverändert. Bei einer Größenänderung ändert sich diese Zahl - sie wird entweder verringert ("down-sizing") oder erhöht ("up-sizing"). Hier einige Beispiele für Größenänderungen:
In
[
82
]
:
g
Out
[
82
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
83
]
:
np
.
resize
(
g
,
(
3
,
1
)
)
Out
[
83
]
:
array
(
[
[
0
]
,
[
1
]
,
[
2
]
]
)
In
[
84
]
:
np
.
resize
(
g
,
(
1
,
5
)
)
Out
[
84
]
:
array
(
[
[
0
,
1
,
2
,
3
,
4
]
]
)
In
[
85
]
:
np
.
resize
(
g
,
(
2
,
5
)
)
Out
[
85
]
:
array
(
[
[
0
,
1
,
2
,
3
,
4
]
,
[
5
,
6
,
7
,
8
,
9
]
]
)
In
[
86
]
:
n
=
np
.
resize
(
g
,
(
5
,
4
)
)
n
Out
[
86
]
:
array
(
[
[
0
,
1
,
2
,
3
]
,
[
4
,
5
,
6
,
7
]
,
[
8
,
9
,
10
,
11
]
,
[
12
,
13
,
14
,
0
]
,
[
1
,
2
,
3
,
4
]
]
)
Stapeln ist eine spezielle Operation, die die horizontale oder vertikale Kombination von zwei ndarray
Objekten ermöglicht. Allerdings muss die Größe der "verbindenden" Dimension gleich sein:
In
[
87
]
:
h
Out
[
87
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
88
]
:
np
.
hstack
(
(
h
,
2
*
h
)
)
Out
[
88
]
:
array
(
[
[
0
,
1
,
2
,
0
,
2
,
4
]
,
[
3
,
4
,
5
,
6
,
8
,
10
]
,
[
6
,
7
,
8
,
12
,
14
,
16
]
,
[
9
,
10
,
11
,
18
,
20
,
22
]
,
[
12
,
13
,
14
,
24
,
26
,
28
]
]
)
In
[
89
]
:
np
.
vstack
(
(
h
,
0.5
*
h
)
)
Out
[
89
]
:
array
(
[
[
0.
,
1.
,
2.
]
,
[
3.
,
4.
,
5.
]
,
[
6.
,
7.
,
8.
]
,
[
9.
,
10.
,
11.
]
,
[
12.
,
13.
,
14.
]
,
[
0.
,
0.5
,
1.
]
,
[
1.5
,
2.
,
2.5
]
,
[
3.
,
3.5
,
4.
]
,
[
4.5
,
5.
,
5.5
]
,
[
6.
,
6.5
,
7.
]
]
)
Eine weitere spezielle Operation ist die Verflachung eines mehrdimensionalen ndarray
Objekts in ein eindimensionales Objekt. Man kann wählen, ob die Verflachung zeilenweise (C
Reihenfolge) oder spaltenweise (F
Reihenfolge) erfolgt:
In
[
90
]
:
h
Out
[
90
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
91
]
:
h
.
flatten
(
)
Out
[
91
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
92
]
:
h
.
flatten
(
order
=
'
C
'
)
Out
[
92
]
:
array
(
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
93
]
:
h
.
flatten
(
order
=
'
F
'
)
Out
[
93
]
:
array
(
[
0
,
3
,
6
,
9
,
12
,
1
,
4
,
7
,
10
,
13
,
2
,
5
,
8
,
11
,
14
]
)
In
[
94
]
:
for
i
in
h
.
flat
:
(
i
,
end
=
'
,
'
)
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
,
In
[
95
]
:
for
i
in
h
.
ravel
(
order
=
'
C
'
)
:
(
i
,
end
=
'
,
'
)
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
,
In
[
96
]
:
for
i
in
h
.
ravel
(
order
=
'
F
'
)
:
(
i
,
end
=
'
,
'
)
0
,
3
,
6
,
9
,
12
,
1
,
4
,
7
,
10
,
13
,
2
,
5
,
8
,
11
,
14
,
Boolesche Arrays
Der Vergleich und logische Operationen funktionieren auf ndarray
Objekten genauso elementweise wie auf Standard-Python-Datentypen. Die Auswertung von Bedingungen ergibt standardmäßig ein boolesches ndarray
Objekt (dtype
ist bool
):
In
[
97
]
:
h
Out
[
97
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
98
]
:
h
>
8
Out
[
98
]
:
array
(
[
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
,
[
True
,
True
,
True
]
,
[
True
,
True
,
True
]
]
)
In
[
99
]
:
h
<
=
7
Out
[
99
]
:
array
(
[
[
True
,
True
,
True
]
,
[
True
,
True
,
True
]
,
[
True
,
True
,
False
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
]
)
In
[
100
]
:
h
==
5
Out
[
100
]
:
array
(
[
[
False
,
False
,
False
]
,
[
False
,
False
,
True
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
,
[
False
,
False
,
False
]
]
)
In
[
101
]
:
(
h
==
5
)
.
astype
(
int
)
Out
[
101
]
:
array
(
[
[
0
,
0
,
0
]
,
[
0
,
0
,
1
]
,
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
]
)
In
[
102
]
:
(
h
>
4
)
&
(
h
<
=
12
)
Out
[
102
]
:
array
(
[
[
False
,
False
,
False
]
,
[
False
,
False
,
True
]
,
[
True
,
True
,
True
]
,
[
True
,
True
,
True
]
,
[
True
,
False
,
False
]
]
)
Ist der Wert größer als ...?
Ist der Wert kleiner oder gleich als ...?
Ist der Wert gleich ...?
Stelle
True
undFalse
als ganzzahlige Werte 0 und 1 dar.Ist der Wert größer als ... und kleiner als oder gleich ...?
Solche booleschen Arrays können zur Indizierung und Datenauswahl verwendet werden. Beachte, dass die folgenden Operationen die Daten glätten:
In
[
103
]
:
h
[
h
>
8
]
Out
[
103
]
:
array
(
[
9
,
10
,
11
,
12
,
13
,
14
]
)
In
[
104
]
:
h
[
(
h
>
4
)
&
(
h
<
=
12
)
]
Out
[
104
]
:
array
(
[
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
]
)
In
[
105
]
:
h
[
(
h
<
4
)
|
(
h
>
=
12
)
]
Out
[
105
]
:
array
(
[
0
,
1
,
2
,
3
,
12
,
13
,
14
]
)
Gib mir alle Werte größer als ...
Gib mir alle Werte größer als ... und kleiner oder gleich ...
Gib mir alle Werte größer als ... oder kleiner oder gleich ...
Ein mächtiges Werkzeug in dieser Hinsicht ist die Funktion np.where()
, die die Definition von Aktionen/Operationen ermöglicht, je nachdem, ob eine Bedingung True
oder False
ist. Das Ergebnis der Anwendung von np.where()
ist ein neues ndarray
Objekt, das die gleiche Form wie das ursprüngliche hat:
In
[
106
]
:
np
.
where
(
h
>
7
,
1
,
0
)
Out
[
106
]
:
array
(
[
[
0
,
0
,
0
]
,
[
0
,
0
,
0
]
,
[
0
,
0
,
1
]
,
[
1
,
1
,
1
]
,
[
1
,
1
,
1
]
]
)
In
[
107
]
:
np
.
where
(
h
%
2
==
0
,
'
even
'
,
'
odd
'
)
Out
[
107
]
:
array
(
[
[
'
even
'
,
'
odd
'
,
'
even
'
]
,
[
'
odd
'
,
'
even
'
,
'
odd
'
]
,
[
'
even
'
,
'
odd
'
,
'
even
'
]
,
[
'
odd
'
,
'
even
'
,
'
odd
'
]
,
[
'
even
'
,
'
odd
'
,
'
even
'
]
]
,
dtype
=
'
<U4
'
)
In
[
108
]
:
np
.
where
(
h
<
=
7
,
h
*
2
,
h
/
2
)
Out
[
108
]
:
array
(
[
[
0.
,
2.
,
4.
]
,
[
6.
,
8.
,
10.
]
,
[
12.
,
14.
,
4.
]
,
[
4.5
,
5.
,
5.5
]
,
[
6.
,
6.5
,
7.
]
]
)
Setze im neuen Objekt
1
, wennTrue
und0
, wenn nicht.Setze im neuen Objekt
even
, wennTrue
undodd
, wenn nicht.Setze im neuen Objekt das zweifache Element
h
, wennTrue
, und ansonsten die Hälfte des Elementsh
.
In späteren Kapiteln findest du weitere Beispiele für diese wichtigen Operationen an ndarray
Objekten.
Geschwindigkeitsvergleich
Wir werden in Kürze zu strukturierten Arrays mit NumPy
übergehen, aber lass uns für einen Moment bei normalen Arrays bleiben und sehen, was die Spezialisierung in Bezug auf die Leistung bringt.
Ein einfaches Beispiel ist die Erstellung einer Matrix/eines Arrays der Form 5.000 × 5.000 Elemente, die mit pseudozufälligen, normalverteilten Zahlen gefüllt sind. Anschließend soll die Summe aller Elemente berechnet werden. Zunächst der reine Python-Ansatz, bei dem die list
comprehensions verwendet werden:
In
[
109
]
:
import
random
I
=
5000
In
[
110
]
:
%
time
mat
=
[
[
random
.
gauss
(
0
,
1
)
for
j
in
range
(
I
)
]
\
for
i
in
range
(
I
)
]
CPU
times
:
user
17.1
s
,
sys
:
361
ms
,
total
:
17.4
s
Wall
time
:
17.4
s
In
[
111
]
:
mat
[
0
]
[
:
5
]
Out
[
111
]
:
[
-
0.40594967782329183
,
-
1.357757478015285
,
0.05129566894355976
,
-
0.8958429976582192
,
0.6234174778878331
]
In
[
112
]
:
%
time
sum
(
[
sum
(
l
)
for
l
in
mat
]
)
CPU
times
:
user
142
ms
,
sys
:
1.69
ms
,
total
:
144
ms
Wall
time
:
143
ms
Out
[
112
]
:
-
3561.944965714259
In
[
113
]
:
import
sys
sum
(
[
sys
.
getsizeof
(
l
)
for
l
in
mat
]
)
Out
[
113
]
:
215200000
Die Erstellung der Matrix über eine verschachtelte
list
comprehension.Einige wählten Zufallszahlen aus den gezogenen Zahlen aus.
Die Summen der einzelnen
list
Objekte werden zuerst während eines Listenverständnisses berechnet; dann wird die Summe der Summen genommen.Dabei wird der Speicherverbrauch aller
list
Objekte addiert.
Wenden wir uns nun NumPy
zu und sehen wir uns an, wie das gleiche Problem dort gelöst wird. Der Einfachheit halber bietet das Unterpaket NumPy
random
eine Vielzahl von Funktionen, um ein ndarray
Objekt zu instanziieren und es gleichzeitig mit Pseudo-Zufallszahlen zu füllen:
In
[
114
]
:
%
time
mat
=
np
.
random
.
standard_normal
(
(
I
,
I
)
)
CPU
times
:
user
1.01
s
,
sys
:
200
ms
,
total
:
1.21
s
Wall
time
:
1.21
s
In
[
115
]
:
%
time
mat
.
sum
(
)
CPU
times
:
user
29.7
ms
,
sys
:
1.15
ms
,
total
:
30.8
ms
Wall
time
:
29.4
ms
Out
[
115
]
:
-
186.12767026606448
In
[
116
]
:
mat
.
nbytes
Out
[
116
]
:
200000000
In
[
117
]
:
sys
.
getsizeof
(
mat
)
Out
[
117
]
:
200000112
Erzeugt das Objekt
ndarray
mit standardmäßig normalverteilten Zufallszahlen; es ist etwa um den Faktor 14 schneller.Berechnet die Summe aller Werte im Objekt
ndarray
; es ist um den Faktor 4,5 schneller.Der
NumPy
Ansatz spart auch etwas Speicherplatz, da der Speicher-Overhead desndarray
Objekts im Vergleich zur Größe der Daten selbst winzig ist.
NumPy-Arrays verwenden
Die Verwendung von NumPy
für Array-basierte Operationen und Algorithmen führt in der Regel zu kompaktem, leicht lesbarem Code und deutlichen Leistungssteigerungen gegenüber reinem Python-Code.
Strukturierte NumPy-Arrays
Die Spezialisierung der Klasse ndarray
bringt natürlich eine Reihe von wertvollen Vorteilen mit sich. Eine zu enge Spezialisierung könnte sich jedoch für die meisten array-basierten Algorithmen und Anwendungen als zu große Last erweisen. Deshalb bietet NumPy
strukturierte ndarray
und Record recarray
Objekte, die es dir ermöglichen, für jede Spalte ein anderes dtype
zu haben. Was bedeutet "pro Spalte"? Betrachte die folgende Initialisierung eines strukturierten ndarray
Objekts:
In
[
118
]
:
dt
=
np
.
dtype
(
[
(
'
Name
'
,
'
S10
'
)
,
(
'
Age
'
,
'
i4
'
)
,
(
'
Height
'
,
'
f
'
)
,
(
'
Children/Pets
'
,
'
i4
'
,
2
)
]
)
In
[
119
]
:
dt
Out
[
119
]
:
dtype
(
[
(
'
Name
'
,
'
S10
'
)
,
(
'
Age
'
,
'
<i4
'
)
,
(
'
Height
'
,
'
<f4
'
)
,
(
'
Children/Pets
'
,
'
<i4
'
,
(
2
,
)
)
]
)
In
[
120
]
:
dt
=
np
.
dtype
(
{
'
names
'
:
[
'
Name
'
,
'
Age
'
,
'
Height
'
,
'
Children/Pets
'
]
,
'
formats
'
:
'
O int float int,int
'
.
split
(
)
}
)
In
[
121
]
:
dt
Out
[
121
]
:
dtype
(
[
(
'
Name
'
,
'
O
'
)
,
(
'
Age
'
,
'
<i8
'
)
,
(
'
Height
'
,
'
<f8
'
)
,
(
'
Children/Pets
'
,
[
(
'
f0
'
,
'
<i8
'
)
,
(
'
f1
'
,
'
<i8
'
)
]
)
]
)
In
[
122
]
:
s
=
np
.
array
(
[
(
'
Smith
'
,
45
,
1.83
,
(
0
,
1
)
)
,
(
'
Jones
'
,
53
,
1.72
,
(
2
,
2
)
)
]
,
dtype
=
dt
)
In
[
123
]
:
s
Out
[
123
]
:
array
(
[
(
'
Smith
'
,
45
,
1.83
,
(
0
,
1
)
)
,
(
'
Jones
'
,
53
,
1.72
,
(
2
,
2
)
)
]
,
dtype
=
[
(
'
Name
'
,
'
O
'
)
,
(
'
Age
'
,
'
<i8
'
)
,
(
'
Height
'
,
'
<f8
'
)
,
(
'
Children/Pets
'
,
[
(
'
f0
'
,
'
<i8
'
)
,
(
'
f1
'
,
'
<i8
'
)
]
)
]
)
In
[
124
]
:
type
(
s
)
Out
[
124
]
:
numpy
.
ndarray
Der Komplex
dtype
besteht aus.Eine alternative Syntax, um das gleiche Ergebnis zu erzielen.
Die strukturierte
ndarray
wird mit zwei Datensätzen instanziiert.Der Objekttyp ist immer noch
ndarray
.
In gewisser Weise kommt diese Konstruktion dem Vorgang der Initialisierung von Tabellen in einer SQL-Datenbank recht nahe: Man hat Spaltennamen und Spaltendatentypen, vielleicht mit einigen zusätzlichen Informationen (z. B. die maximale Anzahl von Zeichen pro str
Objekt). Auf die einzelnen Spalten kann nun einfach über ihre Namen und auf die Zeilen über ihre Indexwerte zugegriffen werden:
In
[
125
]
:
s
[
'
Name
'
]
Out
[
125
]
:
array
(
[
'
Smith
'
,
'
Jones
'
]
,
dtype
=
object
)
In
[
126
]
:
s
[
'
Height
'
]
.
mean
(
)
Out
[
126
]
:
1.775
In
[
127
]
:
s
[
0
]
Out
[
127
]
:
(
'
Smith
'
,
45
,
1.83
,
(
0
,
1
)
)
In
[
128
]
:
s
[
1
]
[
'
Age
'
]
Out
[
128
]
:
53
Auswählen einer Spalte nach Name.
Aufrufen einer Methode für eine ausgewählte Spalte.
Auswählen eines Datensatzes.
Auswählen eines Feldes in einem Datensatz.
Zusammenfassend lässt sich sagen, dass strukturierte Arrays eine Verallgemeinerung des regulären ndarray
Objekttyps sind, da der Datentyp nur pro Spalte gleich sein muss, wie bei Tabellen in SQL-Datenbanken. Ein Vorteil von strukturierten Arrays ist, dass ein einzelnes Element einer Spalte ein anderes multidimensionales Objekt sein kann und nicht den grundlegenden NumPy
Datentypen entsprechen muss.
Strukturierte Arrays
NumPy
bietet zusätzlich zu regulären Arrays strukturierte (und Record-)Arrays, die die Beschreibung und Handhabung von tabellenähnlichen Datenstrukturen mit einer Vielzahl verschiedener Datentypen pro (benannter) Spalte ermöglichen. Sie bringen SQL-tabellenähnliche Datenstrukturen nach Python, mit den meisten Vorteilen der regulären ndarray
Objekte (Syntax, Methoden, Leistung).
Vektorisierung des Codes
Vektorisierung ist eine Strategie, um kompakteren Code zu erhalten, der möglicherweise schneller ausgeführt wird. Die Grundidee besteht darin, eine Operation auf ein komplexes Objekt durchzuführen oder eine Funktion auf ein komplexes Objekt "auf einmal" anzuwenden und nicht in einer Schleife über die einzelnen Elemente des Objekts. In Python bieten funktionale Programmierwerkzeuge wie map()
und filter()
einige grundlegende Möglichkeiten zur Vektorisierung. NumPy
hat die Vektorisierung jedoch tief in seinem Kern eingebaut.
Grundlegende Vektorisierung
Wie im vorherigen Abschnitt gezeigt wurde, können einfache mathematische Operationen - wie die Berechnung der Summe aller Elemente - direkt auf ndarray
Objekten implementiert werden (über Methoden oder universelle Funktionen). Auch allgemeinere vektorisierte Operationen sind möglich. Zum Beispiel kann man zwei NumPy
Arrays wie folgt elementweise addieren:
In
[
129
]
:
np
.
random
.
seed
(
100
)
r
=
np
.
arange
(
12
)
.
reshape
(
(
4
,
3
)
)
s
=
np
.
arange
(
12
)
.
reshape
(
(
4
,
3
)
)
*
0.5
In
[
130
]
:
r
Out
[
130
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
]
)
In
[
131
]
:
s
Out
[
131
]
:
array
(
[
[
0.
,
0.5
,
1.
]
,
[
1.5
,
2.
,
2.5
]
,
[
3.
,
3.5
,
4.
]
,
[
4.5
,
5.
,
5.5
]
]
)
In
[
132
]
:
r
+
s
Out
[
132
]
:
array
(
[
[
0.
,
1.5
,
3.
]
,
[
4.5
,
6.
,
7.5
]
,
[
9.
,
10.5
,
12.
]
,
[
13.5
,
15.
,
16.5
]
]
)
Das erste
ndarray
Objekt mit Zufallszahlen.Das zweite
ndarray
Objekt mit Zufallszahlen.Elementweise Addition als vektorisierte Operation (keine Schleifenbildung).
NumPy
unterstützt auch das so genannte Broadcasting. Damit kannst du Objekte mit unterschiedlichen Formen in einem einzigen Vorgang kombinieren. Die vorherigen Beispiele haben davon bereits Gebrauch gemacht. Betrachte die folgenden Beispiele:
In
[
133
]
:
r
+
3
Out
[
133
]
:
array
(
[
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
,
[
12
,
13
,
14
]
]
)
In
[
134
]
:
2
*
r
Out
[
134
]
:
array
(
[
[
0
,
2
,
4
]
,
[
6
,
8
,
10
]
,
[
12
,
14
,
16
]
,
[
18
,
20
,
22
]
]
)
In
[
135
]
:
2
*
r
+
3
Out
[
135
]
:
array
(
[
[
3
,
5
,
7
]
,
[
9
,
11
,
13
]
,
[
15
,
17
,
19
]
,
[
21
,
23
,
25
]
]
)
Bei der Skalaraddition wird der Skalar übertragen und zu jedem Element hinzugefügt.
Bei der Skalarmultiplikation wird der Skalar auch an jedes Element gesendet und mit ihm multipliziert.
Diese lineare Transformation kombiniert beide Operationen.
Diese Vorgänge funktionieren bis zu einem gewissen Punkt auch mit anders geformten ndarray
Objekten:
In
[
136
]
:
r
Out
[
136
]
:
array
(
[
[
0
,
1
,
2
]
,
[
3
,
4
,
5
]
,
[
6
,
7
,
8
]
,
[
9
,
10
,
11
]
]
)
In
[
137
]
:
r
.
shape
Out
[
137
]
:
(
4
,
3
)
In
[
138
]
:
s
=
np
.
arange
(
0
,
12
,
4
)
s
Out
[
138
]
:
array
(
[
0
,
4
,
8
]
)
In
[
139
]
:
r
+
s
Out
[
139
]
:
array
(
[
[
0
,
5
,
10
]
,
[
3
,
8
,
13
]
,
[
6
,
11
,
16
]
,
[
9
,
14
,
19
]
]
)
In
[
140
]
:
s
=
np
.
arange
(
0
,
12
,
3
)
s
Out
[
140
]
:
array
(
[
0
,
3
,
6
,
9
]
)
In
[
141
]
:
r
+
s
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ValueErrorTraceback
(
most
recent
call
last
)
<
ipython
-
input
-
141
-
1890
b26ec965
>
in
<
module
>
(
)
-
-
-
-
>
1
r
+
s
ValueError
:
operands
could
not
be
broadcast
together
with
shapes
(
4
,
3
)
(
4
,
)
In
[
142
]
:
r
.
transpose
(
)
+
s
Out
[
142
]
:
array
(
[
[
0
,
6
,
12
,
18
]
,
[
1
,
7
,
13
,
19
]
,
[
2
,
8
,
14
,
20
]
]
)
In
[
143
]
:
sr
=
s
.
reshape
(
-
1
,
1
)
sr
Out
[
143
]
:
array
(
[
[
0
]
,
[
3
]
,
[
6
]
,
[
9
]
]
)
In
[
144
]
:
sr
.
shape
Out
[
144
]
:
(
4
,
1
)
In
[
145
]
:
r
+
s
.
reshape
(
-
1
,
1
)
Out
[
145
]
:
array
(
[
[
0
,
1
,
2
]
,
[
6
,
7
,
8
]
,
[
12
,
13
,
14
]
,
[
18
,
19
,
20
]
]
)
Ein neues eindimensionales
ndarray
Objekt der Länge 3.Die Objekte
r
(Matrix) unds
(Vektor) können ganz einfach hinzugefügt werden.Ein weiteres eindimensionales
ndarray
Objekt der Länge 4.Die Länge des neuen
s
(Vektor)-Objekts unterscheidet sich nun von der Länge der zweiten Dimension desr
Objekts.Das erneute Transponieren des
r
Objekts ermöglicht die vektorisierte Addition.Alternativ kann die Form von
s
in(4, 1)
geändert werden, damit die Addition funktioniert (die Ergebnisse sind dann allerdings anders).
Oft funktionieren benutzerdefinierte Python-Funktionen auch mit ndarray
Objekten. Wenn die Implementierung es zulässt, können Arrays genauso mit Funktionen verwendet werden wie int
oder float
Objekte. Betrachte die folgende Funktion:
In
[
146
]
:
def
f
(
x
)
:
return
3
*
x
+
5
In
[
147
]
:
f
(
0.5
)
Out
[
147
]
:
6.5
In
[
148
]
:
f
(
r
)
Out
[
148
]
:
array
(
[
[
5
,
8
,
11
]
,
[
14
,
17
,
20
]
,
[
23
,
26
,
29
]
,
[
32
,
35
,
38
]
]
)
Eine einfache Python-Funktion, die eine lineare Transformation des Parameters
x
durchführt.Die Funktion
f()
wird auf ein Pythonfloat
Objekt angewendet.Dieselbe Funktion wird auf ein
ndarray
Objekt angewendet, was zu einer vektorisierten und elementweisen Auswertung der Funktion führt.
NumPy
wendet einfach die Funktion f
auf das Objekt an, und zwar elementweise. In diesem Sinne vermeidet man mit dieser Art von Operation keine Schleifen; man vermeidet sie nur auf der Python-Ebene und delegiert die Schleifenbildung an NumPy
. Auf der Ebene NumPy
wird die Schleifenbildung über das ndarray
Objekt von optimiertem Code übernommen, der größtenteils in C geschrieben wurde und daher im Allgemeinen schneller ist als reines Python. Das erklärt den "secret
", der hinter den Leistungsvorteilen der Verwendung von NumPy
für array-basierte Anwendungsfälle steckt.
Speicher-Layout
Wenn ndarray
Objekte durch die Verwendung von np.zeros()
initialisiert werden, wie in "Multiple Dimensions", wird ein optionales Argument für die Speicheranordnung angegeben. Dieses Argument legt, grob gesagt, fest, welche Elemente eines Arrays nebeneinander (zusammenhängend) im Speicher abgelegt werden. Bei kleinen Arrays hat dies kaum messbare Auswirkungen auf die Leistung von Array-Operationen. Bei großen Arrays und je nach (Finanz-)Algorithmus, der darauf angewendet werden soll, sieht die Sache jedoch anders aus. Hier kommt das Speicherlayout ins Spiel (siehe z. B. Eli Benderskys Artikel "Memory Layout of Multi-Dimensional Arrays").
Um die potenzielle Bedeutung des Speicherlayouts von Arrays in der Wissenschaft und im Finanzwesen zu veranschaulichen, betrachte die folgende Konstruktion von mehrdimensionalen ndarray
Objekten:
In
[
149
]
:
x
=
np
.
random
.
standard_normal
(
(
1000000
,
5
)
)
In
[
150
]
:
y
=
2
*
x
+
3
In
[
151
]
:
C
=
np
.
array
(
(
x
,
y
)
,
order
=
'
C
'
)
In
[
152
]
:
F
=
np
.
array
(
(
x
,
y
)
,
order
=
'
F
'
)
In
[
153
]
:
x
=
0.0
;
y
=
0.0
In
[
154
]
:
C
[
:
2
]
.
round
(
2
)
Out
[
154
]
:
array
(
[
[
[
-
1.75
,
0.34
,
1.15
,
-
0.25
,
0.98
]
,
[
0.51
,
0.22
,
-
1.07
,
-
0.19
,
0.26
]
,
[
-
0.46
,
0.44
,
-
0.58
,
0.82
,
0.67
]
,
.
.
.
,
[
-
0.05
,
0.14
,
0.17
,
0.33
,
1.39
]
,
[
1.02
,
0.3
,
-
1.23
,
-
0.68
,
-
0.87
]
,
[
0.83
,
-
0.73
,
1.03
,
0.34
,
-
0.46
]
]
,
[
[
-
0.5
,
3.69
,
5.31
,
2.5
,
4.96
]
,
[
4.03
,
3.44
,
0.86
,
2.62
,
3.51
]
,
[
2.08
,
3.87
,
1.83
,
4.63
,
4.35
]
,
.
.
.
,
[
2.9
,
3.28
,
3.33
,
3.67
,
5.78
]
,
[
5.04
,
3.6
,
0.54
,
1.65
,
1.26
]
,
[
4.67
,
1.54
,
5.06
,
3.69
,
2.07
]
]
]
)
Ein
ndarray
Objekt mit großer Asymmetrie in den beiden Dimensionen.Eine lineare Transformation der ursprünglichen Objektdaten.
Dadurch wird ein zweidimensionales
ndarray
Objekt mit der ReihenfolgeC
(zeilen-major) erstellt.Dadurch wird ein zweidimensionales
ndarray
Objekt mit der ReihenfolgeF
(column-major) erstellt.Der Speicher wird freigegeben (abhängig von der Speicherbereinigung).
Einige Zahlen aus dem
C
Objekt.
Schauen wir uns einige grundlegende Beispiele und Anwendungsfälle für beide Arten von ndarray
Objekten an und betrachten die Geschwindigkeit, mit der sie angesichts der unterschiedlichen Speicheraufteilung ausgeführt werden:
In
[
155
]
:
%
timeit
C
.
sum
(
)
4.36
ms
±
89.3
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
100
loops
each
)
In
[
156
]
:
%
timeit
F
.
sum
(
)
4.21
ms
±
71.4
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
100
loops
each
)
In
[
157
]
:
%
timeit
C
.
sum
(
axis
=
0
)
17.9
ms
±
776
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
100
loops
each
)
In
[
158
]
:
%
timeit
C
.
sum
(
axis
=
1
)
35.1
ms
±
999
µ
s
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10
loops
each
)
In
[
159
]
:
%
timeit
F
.
sum
(
axis
=
0
)
83.8
ms
±
2.63
ms
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10
loops
each
)
In
[
160
]
:
%
timeit
F
.
sum
(
axis
=
1
)
67.9
ms
±
5.16
ms
per
loop
(
mean
±
std
.
dev
.
of
7
runs
,
10
loops
each
)
In
[
161
]
:
F
=
0.0
;
C
=
0.0
Berechnet die Summe aller Elemente.
Berechnet die Summen pro Zeile ("many").
Berechnet die Summen pro Spalte ("few").
Wir können die Leistungsergebnisse wie folgt zusammenfassen:
-
Wenn du die Summe aller Elemente berechnest, spielt die Speicheranordnung keine Rolle.
-
Das Aufsummieren über die
C
-geordnetenndarray
Objekte ist sowohl über Zeilen als auch über Spalten schneller (ein absoluter Geschwindigkeitsvorteil). -
Mit dem Objekt
C
-ordered (row-major)ndarray
ist das Aufsummieren über Zeilen relativ schneller als das Aufsummieren über Spalten. -
Mit dem
F
-ordered (column-major)ndarray
Objekt ist das Aufsummieren über Spalten relativ schneller als das Aufsummieren über Zeilen.
Fazit
NumPy
ist das Paket der Wahl für numerische Berechnungen in Python. Die Klasse ndarray
wurde speziell für den bequemen und effizienten Umgang mit (großen) numerischen Daten entwickelt. Leistungsstarke Methoden und die universellen Funktionen von NumPy
ermöglichen einen vektorisierten Code, der langsame Schleifen auf Python-Ebene weitgehend vermeidet. Viele der in diesem Kapitel vorgestellten Ansätze lassen sich auch auf pandas
und die dazugehörige Klasse DataFrame
übertragen (siehe Kapitel 5).
Weitere Ressourcen
Viele hilfreiche Ressourcen findest du auf der Website NumPy
:
Gute Einführungen in NumPy
in Buchform sind:
-
McKinney, Wes (2017). Python für die Datenanalyse. Sebastopol, CA: O'Reilly.
-
VanderPlas, Jake (2016). Python Data Science Handbook. Sebastopol, CA: O'Reilly.
Get Python für Finanzen, 2. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.