Datentyp-Objekt: dtype


dtype

Veranschaulichung von dtype-Daten mit Europa-Daten-Bild 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. Dies ermöglicht es uns somit Daten wie in der folgenden Tabelle mit dtype zu 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 Pixel-Datentyp, der einem numpy.uint8-Datentyp entspricht.

Die Elemente der Liste 'lst' werden in Pixel-Typen gewandelt, um das zweidimensionale Array A zu erzeugen. Wir können sehen, dass dabei auch Float-Werte automatisch in Pixel-Datentypen, also numpy.uint8, gewandelt werden.

import numpy as np
Pixel = np.dtype(np.uint8)
print(Pixel)
lst = [ [115, 230.9, 229.2, 234], 
        [117, 229, 232.1, 235],
        [116, 140, 141, 142] ]
A = np.array(lst, dtype=Pixel)
print(A)
uint8
[[115 230 229 234]
 [117 229 232 235]
 [116 140 141 142]]

Im vorigen Beispiel haben wir lediglich einen neuen Namen für einen Basisdatentyp eingeführt. Damit kann man beispielsweise die Lesbarkeit und Verständlichkeit des Programmes erhöhen. Dies hat noch nichts zu tun mit "Strukturierten Arrays", die wir am Anfang dieses Kapitels unseres Tutorials erwähnt hatten.

Strukturierte Arrays

ndarrays sind homogene Datenobjekte, d.h. alle Elemente eines Arrays haben den gleichen Datentyp. Der Datentype "dtype" hingegen erlaubt es uns spaltenweise Typen zu deklarieren.

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 Variablen 'Density' zu. Wir haben die Variable groß geschrieben, damit man sieht, dass es einen Unterschied gibt zwischen dem 'density' in der Typdefinition selbst. Den Datentyp 'Density' benutzen wir dann in der Definition des Numpy-Arrays, in dem wir die ersten drei Werte benutzen.

import numpy as np
Density = np.dtype([('density', np.int32)])
x = np.array([(393,), (337,), (256,)],
             dtype=Density)
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:

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 einer dtype-Definition können wir den Typ direkt verwenden, also beispielsweise np.int32 oder wir können einen String beutzen, z.B. 'i4'.

In unserem Beispiel hätten wir unseren dtype auch wie folgt definieren können:

Density = np.dtype([('density',  'i4')])
x = np.array([(393,), (337,), (256,)],
             dtype=Density)
print(x)
[(393,) (337,) (256,)]

Das 'i' steht für Integer und die 4 bedeutet "4 Bytes". Was bedeutet aber das 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 wird die natürliche Byte-Reihenfolge des Systems verwendet. Wir demonstrieren dies im Folgenden, indem wir eine Gleitkommazahl mit doppelter Genauigkeit (double precision floating-point) in verschiedenen Byte-Reihenfolgen definieren:

# little-endian ordering
dt = np.dtype('<d')
print(dt.name, dt.byteorder, dt.itemsize)
# big-endian ordering
dt = np.dtype('>d')  
print(dt.name, dt.byteorder, dt.itemsize)
# native byte ordering
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:

dt = np.dtype([('country', 'S20'), 
               ('density', 'i4'), 
               ('area', 'i4'), 
               ('population', 'i4')])
population_table = 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])
[(393,) (337,) (256,)]

Wir können auf jedes Element individuell zugreifen:

print(population_table['density'])
print(population_table['country'])
print(population_table['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]



Ein- und Ausgabe von strukturierten Arrays

In den meisten Applikationen ist es notwendig, die Daten aus einem Programm in einer Datei zu speichern. Wir werden nun unser zuvor erzeugtes Array in einer Datei mit dem Kommando "savetxt" speichern. Eine detaillierte Einführung in diese Thematik findet man in unserem Kapitel Lesen und Schreiben von Daten-Dateien

np.savetxt("population_table.csv",
           population_table,
           fmt="%s;%d;%d;%d", 
           delimiter=";")

Sehr wahrscheinlich wird man zu einem späteren Zeitpunkt die Daten der eben gespeicherten Datei wieder einlesen wollen. Dies können wir mit dem Kommando genfromtxt bewerkstelligen.

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

Statt genfromtxt kann man auch loadtxt verwenden:

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



Unicode-Strings in Arrays

Einige werden sicherlich bemerkt haben, dass die Strings in unserem vorigen Beispielarray ein kleines "b" als Präfix hatten. Dies bedeutet, dass wir mit unserer dtype-Definition "('country', 'S20')" geschrieben hatten und dadurch unsere Läündernamen als Binärstrings definiert hatten.

Um Unicode-Strings zu erhalten, müssen wir die Definition in "('country', np.unicode, 20)" umändern. Wir ändern die Definition für population_table wie folgt:

dt = np.dtype([('country', np.unicode, 25), 
               ('density', 'i4'), 
               ('area', 'i4'), 
               ('population', 'i4')])
population_table = 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(population_table[:4])
[('Netherlands', 393,  41526, 16928800) ('Belgium', 337,  30510, 11007020)
 ('United Kingdom', 256, 243610, 62262000)
 ('Germany', 233, 357021, 81799600)]

Umbenennen von Spaltennamen

Nun wollen wir die Spaltennamn in deutsche Bezeichnungen umbenennen. Auf die Spaltennamen kann man mit der Property names von dtype zugreifen:

population_table.dtype.names
Der obige Python-Code führt zu folgender Ausgabe:
('country', 'density', 'area', 'population')

Das Umbennen gestaltet sich denkbar einfach. Man weißt dieser Property einfach ein neues Tupel mit den neuen Namen zu:

population_table.dtype.names = ('Land', 
                                'Bevölkerungsdichte', 
                                'Fläche', 
                                'Bevölkerung')
population_table['Land']
Wir erhalten die folgende Ausgabe:
array(['Netherlands', 'Belgium', 'United Kingdom', 'Germany',
       'Liechtenstein', 'Italy', 'Switzerland', 'Luxembourg', 'France',
       'Austria', 'Greece', 'Ireland', 'Sweden', 'Finland', 'Norway'],
      dtype='<U25')

Spaltenwerte austauschen

Nun wollen wir auch die Ländernamen von Englisch nach Deutsch übersetzen. Dazu erzeugen wir eine Liste lands. Wir können diese mit np.array in ein Array wandeln und dann die bisherige Spalte population_table['Land'] komplett austauschen:

lands = ['Niederlande', 'Belgien', 'Vereinigtes Königreich', 
         'Deutschland', 'Liechtenstein', 'Italien', 'Schweiz', 
         'Luxemburg', 'Frankreich', 'Österreich', 'Griechenland', 
         'Irland', 'Schweden', 'Finnland', 'Norwegen']
population_table['Land'] = np.array(lands, dtype='<U25')
population_table
Der obige Code liefert folgendes Ergebnis:
array([('Niederlande', 393,  41526, 16928800),
       ('Belgien', 337,  30510, 11007020),
       ('Vereinigtes Königreich', 256, 243610, 62262000),
       ('Deutschland', 233, 357021, 81799600),
       ('Liechtenstein', 205,    160,    32842),
       ('Italien', 192, 301230, 59715625),
       ('Schweiz', 177,  41290,  7301994),
       ('Luxemburg', 173,   2586,   512000),
       ('Frankreich', 111, 547030, 63601002),
       ('Österreich',  97,  83858,  8169929),
       ('Griechenland',  81, 131940, 11606813),
       ('Irland',  65,  70280,  4581269),
       ('Schweden',  20, 449964,  9515744),
       ('Finnland',  16, 338424,  5410233),
       ('Norwegen',  13, 385252,  5033675)],
      dtype=[('Land', '<U25'), ('Bevölkerungsdichte', '<i4'), ('Fläche', '<i4'), ('Bevölkerung', '<i4')])


Komplexeres Beispiel

In den bisherigen Beispielen haben wir unsere Arrays direkt erzeugt. Normalerweise müssen wir uns jedoch 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":

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:

time_type = np.dtype([('city', 'U30'), ('day', 'U3'), ('time', [('h', int), ('min', int)])])
times = np.array( cities_and_times , dtype=time_type)
lst = []
for row in times:
    t = row[2]
    t = f"{t[0]:02d}:{t[1]:02d}"
    lst.append((row[0], row[1], t))
time_type = np.dtype([('city', 'U30'), ('day', 'U3'), ('time', 'U5')])
times2 = np.array( lst , dtype=time_type)
print(times2)
[('Amsterdam', 'Sun', '08:52') ('Anchorage', 'Sat', '23:52')
 ('Ankara', 'Sun', '10:52') ('Athens', 'Sun', '09:52')
 ('Atlanta', 'Sun', '02:52') ('Auckland', 'Sun', '20:52')
 ('Barcelona', 'Sun', '08:52') ('Beirut', 'Sun', '09:52')
 ('Berlin', 'Sun', '08:52') ('Boston', 'Sun', '02:52')
 ('Brasilia', 'Sun', '05:52') ('Brussels', 'Sun', '08:52')
 ('Bucharest', 'Sun', '09:52') ('Budapest', 'Sun', '08:52')
 ('Cairo', 'Sun', '09:52') ('Calgary', 'Sun', '01:52')
 ('Cape Town', 'Sun', '09:52') ('Casablanca', 'Sun', '07:52')
 ('Chicago', 'Sun', '01:52') ('Columbus', 'Sun', '02:52')
 ('Copenhagen', 'Sun', '08:52') ('Dallas', 'Sun', '01:52')
 ('Denver', 'Sun', '01:52') ('Detroit', 'Sun', '02:52')
 ('Dubai', 'Sun', '11:52') ('Dublin', 'Sun', '07:52')
 ('Edmonton', 'Sun', '01:52') ('Frankfurt', 'Sun', '08:52')
 ('Halifax', 'Sun', '03:52') ('Helsinki', 'Sun', '09:52')
 ('Houston', 'Sun', '01:52') ('Indianapolis', 'Sun', '02:52')
 ('Istanbul', 'Sun', '10:52') ('Jerusalem', 'Sun', '09:52')
 ('Johannesburg', 'Sun', '09:52') ('Kathmandu', 'Sun', '13:37')
 ('Kuwait City', 'Sun', '10:52') ('Las Vegas', 'Sun', '00:52')
 ('Lisbon', 'Sun', '07:52') ('London', 'Sun', '07:52')
 ('Los Angeles', 'Sun', '00:52') ('Madrid', 'Sun', '08:52')
 ('Melbourne', 'Sun', '18:52') ('Miami', 'Sun', '02:52')
 ('Minneapolis', 'Sun', '01:52') ('Montreal', 'Sun', '02:52')
 ('Moscow', 'Sun', '10:52') ('New Orleans', 'Sun', '01:52')
 ('New York', 'Sun', '02:52') ('Oslo', 'Sun', '08:52')
 ('Ottawa', 'Sun', '02:52') ('Paris', 'Sun', '08:52')
 ('Philadelphia', 'Sun', '02:52') ('Phoenix', 'Sun', '00:52')
 ('Prague', 'Sun', '08:52') ('Reykjavik', 'Sun', '07:52')
 ('Riyadh', 'Sun', '10:52') ('Rome', 'Sun', '08:52')
 ('Salt Lake City', 'Sun', '01:52') ('San Francisco', 'Sun', '00:52')
 ('San Salvador', 'Sun', '01:52') ('Santiago', 'Sun', '04:52')
 ('Seattle', 'Sun', '00:52') ('Shanghai', 'Sun', '15:52')
 ('Singapore', 'Sun', '15:52') ('Stockholm', 'Sun', '08:52')
 ('Sydney', 'Sun', '18:52') ('São Paulo', 'Sun', '05:52')
 ('Tokyo', 'Sun', '16:52') ('Toronto', 'Sun', '02:52')
 ('Vancouver', 'Sun', '00:52') ('Vienna', 'Sun', '08:52')
 ('Warsaw', 'Sun', '08:52') ('Washington DC', 'Sun', '02:52')
 ('Winnipeg', 'Sun', '01:52') ('Zurich', 'Sun', '08:52')]

Nun wollen wir diese Daten in einer csv-Datei speichern. Leider können wir die Funktionnp.savetxt nicht nutzen, da diese Funktion nicht mit Unicode-Strings zurechtkommt. Wir benutzen deshalb die normale write-Methode eines File-Streams:

with open("cities_and_times.csv", "w") as fh:
    for city_data in times2:
        fh.write(",".join(city_data) + "\n")