Datentyp-Objekt: dtype


dtype

Numpy dtype

Das Datentypobjekt 'dtype' ist eine Instanz der numpy.dtype-Klasse. Es kann mit numpy.dtype konstruiert werden.

Bis jetzt haben wir in unseren Beispielen von Numpy-Arrays nur grundlegende numerische Typen wie 'int' und 'float' benutzt. Diese Numpy-Arrays enthielten nur homogene Datentypen. dtype-Objekte werden aus einer Kombination von grundlegenden Datentypen erzeugt. Mit Hilfe von dtype sind wir in der Lage "Strukturierte Arrays" zu erzeugen, auch bekannt als "record arrays". Strukturierte Arrays statten uns mit der Möglichkeit aus verschiedene Datentypen in verschiedenen Spalten zu haben. Es gibt somit Ähnlichkeit zu Excel- oder CSV-Dokumenten. Wir können also Daten wie in der folgenden Tabelle erzeugen:

Land Bevölkerungsdichte Fläche Einwohner
Niederlande 393 41526 16,928,800
Belgien 337 30510 11,007,020
Vereinigtes Königreich 256 243610 62,262,000
Deutschland 233 357021 81,799,600
Liechtenstein 205 160 32,842
Italien 192 301230 59,715,625
Schweiz 177 41290 7,301,994
Luxemburg 173 2586 512,000
Frankreich 111 547030 63,601,002
Österreich 97 83858 8,169,929
Griechenland 81 131940 11,606,813
Irland 65 70280 4,581,269
Schweden 20 449964 9,515,744
Finnland 16 338424 5,410,233
Norwegen 13 385252 5,033,675

Bevor wir jedoch mit komplexen Daten wie den obigen starten, wollen wir dtype an einem sehr einfachen Beispiel einführen. Wir definieren einen int16-Datentyp und nennen diesen Datentyp i16. (Wir müssen zugegeben, dass es sich dabei nicht gerade um einen schönen Namen handelt, aber wir benutzen ihn nur hier!)

Die Elemente der Liste 'lst' werden in i16-Typen gewandelt, um das zweidimensionale Array A zu erzeugen.

In [13]:
import numpy as np

i16 = np.dtype(np.int16)
print(i16)

lst = [ [3.4, 8.7, 9.9], 
        [1.1, -7.8, -0.7],
        [4.1, 12.3, 4.8] ]

A = np.array(lst, dtype=i16)

print(A)
int16
[[ 3  8  9]
 [ 1 -7  0]
 [ 4 12  4]]

Im vorigen Beispiel haben wir lediglich einen neuen Namen für einen Basisdatentyp eingeführt. Dies hat noch nichts zu tun mit Strukturierten Arrays, die wir am Anfang dieses Kapitels unseres Tutorials erwähnt hatten.

Nun gehen wir den ersten Schritt in Richtung Implementierung der Tabelle mit europäischen Ländern mit den Informtionen über Fläche, Bevölkerung und Bevölkerungsdichte.

Wir erzeugen ein strukturiertes Array mit einer Spalte 'Bevölkerungsdichte'. Den Datentyp definieren wir als np.dtype([('density', np.int)]). Diesen Datentyp weisen wir der Einfachheit halber der Variablen 'dt' zu. Diesen Datentyp benutzen wir dann in der Definition des darray, in dem wir die ersten drei Werte benutzen.

In [14]:
import numpy as np

dt = np.dtype([('density', np.int32)])

x = np.array([(393,), (337,), (256,)],
             dtype=dt)

print(x)

print("\nDie interne Darstellung:")
print(repr(x))
[(393,) (337,) (256,)]

Die interne Darstellung:
array([(393,), (337,), (256,)], 
      dtype=[('density', '<i4')])

Wir können auf die 'density'-Spalte zugreifen, indem wir als Schlüssel 'density' eingeben. Es ähnelt einem Dictionary-Zugriff in Python:

In [15]:
print(x['density'])
[393 337 256]

Man wird sich vielleicht wundern, dass wir 'np.int32' in unserer Definition benutzt haben, aber dass die interne Repräsentierung '<i4' zeigt.

In der dtype-Definition können wir den Typ direkt verwenden, also beispielsweise np.int32 oder wir können einen String beutzen, z.B. 'i4'. Also hätten wir unseren dtype auf wie folgt definieren können:

In [16]:
dt = np.dtype([('density', 'i4')])
x = np.array([(393,), (337,), (256,)],
             dtype=dt)
print(x)
[(393,) (337,) (256,)]

Das 'i' steht für Integer und die 4 bedeutet "4 Bytes". Was bedeutet aber der kleiner-als-Zeichen vor der 4? Wir hätten ebensogut '<i4' schreiben können. Wir können einem Typ ein '<'- oder ein '>'-Zeichen voranstellen. '<' bedeutet, dass bei der Speicherorganisation Little-Endian verwendet wird und '>' bedeutet entsprechend, dass Big-Endian verwendet wird. Ohne Präfix bedeutet, dass die natürliche Byte-Reihenfolge des Systems verwendet wird. Wir demonstrieren dies im Folgenden, indem wir eine Gleitkommazahl mit doppelter Genauigkeit (double precision floating-point) definieren:

In [17]:
dt = np.dtype('<d') 
print(dt.name, dt.byteorder, dt.itemsize)

dt = np.dtype('>d')  
print(dt.name, dt.byteorder, dt.itemsize)

dt = np.dtype('d') 
print(dt.name, dt.byteorder, dt.itemsize)
float64 = 8
float64 > 8
float64 = 8

Das Gleichheitszeichen '=' steht für die natürliche Byte-Reihenfolge ('native byte ordering'), definiert durch das Betriebssystem. In unserem Fall bedeutet dies Little-Endian, weil wir uns auf einem Linux-Rechner befinden.

Eine andere Sache in unserem Density-Array könnte verwirrend sein. Wir definierten das Array mit einer Liste, die 1-Tupels enthält. Vielleicht fragen Sie sich nun, ob es möglich ist Tupels und Listen austauschbar zu verwenden? Dies ist nicht möglich. Die Tupels werden verwendet um die Records zu definieren - in unserm Fall bestehen diese nur aus der Bevölkerungsdichte ('density') - und die Liste ist der 'Container' für die Records. Die Tupel definieren die atomaren Elemente der Struktur und die Listen die Dimensionen.

Nun werden wir die Ländernamen, die Flächen und die Populationen zu unserem Typ hinzufügen:

In [18]:
dt = np.dtype([('country', 'S20'), ('density', 'i4'), ('area', 'i4'), ('population', 'i4')])
x = np.array([('Netherlands', 393, 41526, 16928800),
('Belgium', 337, 30510, 11007020),
('United Kingdom', 256, 243610, 62262000),
('Germany', 233, 357021, 81799600),
('Liechtenstein', 205, 160, 32842),
('Italy', 192, 301230, 59715625),
('Switzerland', 177, 41290, 7301994),
('Luxembourg', 173, 2586, 512000),
('France', 111, 547030, 63601002),
('Austria', 97, 83858, 8169929),
('Greece', 81, 131940, 11606813),
('Ireland', 65, 70280, 4581269),
('Sweden', 20, 449964, 9515744),
('Finland', 16, 338424, 5410233),
('Norway', 13, 385252, 5033675)],
             dtype=dt)
print(x[:4])
[(b'Netherlands', 393, 41526, 16928800) (b'Belgium', 337, 30510, 11007020)
 (b'United Kingdom', 256, 243610, 62262000)
 (b'Germany', 233, 357021, 81799600)]

Wir können auf jedes Element individuell zugreifen:

In [19]:
print(x['density'])
print(x['country'])
print(x['area'][2:5])
[393 337 256 233 205 192 177 173 111  97  81  65  20  16  13]
[b'Netherlands' b'Belgium' b'United Kingdom' b'Germany' b'Liechtenstein'
 b'Italy' b'Switzerland' b'Luxembourg' b'France' b'Austria' b'Greece'
 b'Ireland' b'Sweden' b'Finland' b'Norway']
[243610 357021    160]


Übung:

A clock

Bevor wir weiter machen, wäre es sinnvoll den erlernten Stoff in einer Übung zu vertiefen.

Überlegen Sie sich eine Datentypdefinition für Zeitdatensätze mit Einträgen für Stunde (hours), Minuten (minutes) und Sekunden (seconds).

In [20]:
time_type = np.dtype( [('h', int), ('min', int), ('sec', int)])

times = np.array([(11, 38, 5), 
                  (14, 56, 0),
                  (3, 9, 1)], dtype=time_type)
print(times)
print(times[0])
# reset the first time record:
times[0] = (11, 42, 17)
print(times[0])
[(11, 38, 5) (14, 56, 0) (3, 9, 1)]
(11, 38, 5)
(11, 42, 17)


Ein komplizierteres Beispiel

Wir erhöhen die Komplexität unseres vorigen Beispiels, indem wir noch Temperaturwerte zu den Records hinzufügen.

In [21]:
time_type = np.dtype( np.dtype([('time', [('h', int), ('min', int), ('sec', int)]),
                                ('temperature', float)] ))

times = np.array( [((11, 42, 17), 20.8), ((13, 19, 3), 23.2) ], dtype=time_type)
print(times)
print(times['time'])
print(times['time']['h'])
print(times['temperature'])
[((11, 42, 17), 20.8) ((13, 19, 3), 23.2)]
[(11, 42, 17) (13, 19, 3)]
[11 13]
[ 20.8  23.2]


Übung

Dieses Beispiel ist ein wenig näher am wirklichen Leben. Normalerweise müssen wir uns erst die Daten für unsere strukturierten Arrays aus Datenbanken oder Dateien beschaffen. Wir werden nun die Liste benutzen, die wir im Kapitel über Dateimanagement erzeugt und gespeichert hatten. Die Liste hatten wir mit Hilfe von pickle.dumpy in der Datei cities_and_times.pkl gespeichert.

Die erste Aufgabe besteht also darin diese Datei wieder zu ent"pickeln":

In [22]:
import pickle
fh = open("cities_and_times.pkl", "br")
cities_and_times = pickle.load(fh)
print(cities_and_times[:30])
[('Amsterdam', 'Sun', (8, 52)), ('Anchorage', 'Sat', (23, 52)), ('Ankara', 'Sun', (10, 52)), ('Athens', 'Sun', (9, 52)), ('Atlanta', 'Sun', (2, 52)), ('Auckland', 'Sun', (20, 52)), ('Barcelona', 'Sun', (8, 52)), ('Beirut', 'Sun', (9, 52)), ('Berlin', 'Sun', (8, 52)), ('Boston', 'Sun', (2, 52)), ('Brasilia', 'Sun', (5, 52)), ('Brussels', 'Sun', (8, 52)), ('Bucharest', 'Sun', (9, 52)), ('Budapest', 'Sun', (8, 52)), ('Cairo', 'Sun', (9, 52)), ('Calgary', 'Sun', (1, 52)), ('Cape Town', 'Sun', (9, 52)), ('Casablanca', 'Sun', (7, 52)), ('Chicago', 'Sun', (1, 52)), ('Columbus', 'Sun', (2, 52)), ('Copenhagen', 'Sun', (8, 52)), ('Dallas', 'Sun', (1, 52)), ('Denver', 'Sun', (1, 52)), ('Detroit', 'Sun', (2, 52)), ('Dubai', 'Sun', (11, 52)), ('Dublin', 'Sun', (7, 52)), ('Edmonton', 'Sun', (1, 52)), ('Frankfurt', 'Sun', (8, 52)), ('Halifax', 'Sun', (3, 52)), ('Helsinki', 'Sun', (9, 52))]

Nun wandeln wir unsere Daten in ein strukturiertes Array:

In [1]:
import numpy as np
time_type = np.dtype([('city', 'U30'), ('day', 'U3'), ('time', [('h', int), ('min', int)])])
cities_and_times = [(['Amsterdam'], 'Sun', (8, 52)), (['Anchorage'], 'Sat', (23, 52))]
times = np.array( cities_and_times , dtype=time_type)
print(times['time'])
print(times['city'])
x = times[27]
x[0]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-c645249425b9> in <module>()
      2 time_type = np.dtype([('city', 'U30'), ('day', 'U3'), ('time', [('h', int), ('min', int)])])
      3 cities_and_times = [(['Amsterdam'], 'Sun', (8, 52)), (['Anchorage'], 'Sat', (23, 52))]
----> 4 times = np.array( cities_and_times , dtype=time_type)
      5 print(times['time'])
      6 print(times['city'])

ValueError: setting an array element with a sequence
In [ ]: