Lesen und Schreiben von Daten-Dateien

Scrabble with the Text Numpy, read, write, array

Es gibt in Numpy viele Wege um Daten aus Dateien zu lesen und ebenso zu schreiben. In diesem Kapitel werden die verschiedenen Möglichkeiten diskutieren und die dazugehörigen Funktionen betrachten.


Text-Dateien speichern mit savetxt

Die ersten zwei Funktionen, die wir uns anschauen möchten, sind savetxt und loadtxt.

Im folgenden einfachen Beispiel definieren wir ein Array x und speichern es als Text-Datei mit savetxt:

import numpy as np
x = np.array([[1, 2, 3], 
              [4, 5, 6],
              [7, 8, 9]], np.int32)
np.savetxt("test.txt", x)

Die Datei "test.txt" ist eine Text-Datei und der Inhalt sieht wie folgt aus:

bernd@andromeda:~/Dropbox/notebooks/numpy$ more test.txt
1.000000000000000000e+00 2.000000000000000000e+00 3.000000000000000000e+00
4.000000000000000000e+00 5.000000000000000000e+00 6.000000000000000000e+00
7.000000000000000000e+00 8.000000000000000000e+00 9.000000000000000000e+00

Achtung: Die gezeigte Ausgabe wurde in einem Linux-Terminal erzeugt!

Es ist ebenso möglich das Array in einem speziellen Format auszugeben, z.B. mit drei Nachkommastellen oder als Integers mit vorangestellten Leerzeichen, wenn die Anzahl der Stellen kleiner als 4 ist. Dafür übergeben wie einen Format-String als dritten Parameter "fmt". Im vorigen Beispiel haben wir gesehen, dass der Default-Delimiter ein Leerzeichen ist. Wir können das Verhalten anpassen, indem wir einen String dem Parameter "delimiter" mitgeben. In den meisten Fällem wird dies ein einzelnes Zeichen sein. Jedoch kann ebenso eine ganze Zeichen-Sequenz übergeben werden, z.B. ein Smiley " :-)":

np.savetxt("test2.txt", x, fmt="%2.3f", delimiter=",")
np.savetxt("test3.txt", x, fmt="%04d", delimiter=" :-) ")

Die neu erstellte Datei sieht wie folgt aus:

bernd@andromeda:~/Dropbox/notebooks/numpy$ more test2.txt 
1.000,2.000,3.000
4.000,5.000,6.000
7.000,8.000,9.000
bernd@andromeda:~/Dropbox/notebooks/numpy$ more test3.txt 
0001 :-) 0002 :-) 0003
0004 :-) 0005 :-) 0006
0007 :-) 0008 :-) 0009

Die komplette Syntax von "savetxt" sieht folgendermaßen aus:

savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# ')
Parameter Bedeutung
X Array-Ähnliche Daten, die in einer Text-Datei gespeichert werden sollen.
fmt String oder Sequenz aus Strings, optional
Ein einzelner Format-String (%10.5f), eine Sequenz aus String-Formaten oder ein Multi-Format-String, z.B. 'Iteration %d -- %10.5f', wobei hier der "delimiter" ignoriert wird. Für komplexe "X", sind folgende Optionen für "fmt" erlaubt:
a) Ein einzelnes Spezifikationssysmbol, "fmt='%.4e'", liefert eine Zahlen-Formatierung, wie "' (%s+%sj)' % (fmt, fmt)".
b) Ein vollständiger Spezifikations-String, der alle reellen und vorstellbaren Fälle umfasst. z.B. "' %.4e %+.4j %.4e %+.4j %.4e %+.4j'", für 3 Spalten.
c) Eine Liste mit Spezifikationen, eine pro Spalte - in diesem Fall müssen die reellen und vorstellbaren Teile getrennte Spezfikatoren haben, d.h. "['%.3e + %.3ej', '(%.15e%+.15ej)']" für 2 Spalten.
delimiter Ein String, der für die Separierung der Spalten genutzt wird.
newline Ein String (z.B. "\n", "\r\n" or ",\n"), der eine Zeile abschliesst statt der Standard-Zeilen-Endung.
header Ein String, der an den Beginn der Datei geschrieben wird.
footer Ein String, der an das Ende der Datei geschrieben wird.
comments Ein String, der for "header" und "footer" gestellt wird, um diese als Kommentar zu markieren. Als Default wird hier das Hasht-Tag "#" benutzt.


Text-Dateien laden mit loadtxt

Jetzt werden wir die Datei "test.txt" einlesen, die wir im vorigen Unter-Kapitel erstellt haben:

y = np.loadtxt("test.txt")
print(y)
[[ 1.  2.  3.]
 [ 4.  5.  6.]
 [ 7.  8.  9.]]
y = np.loadtxt("test2.txt", delimiter=",")
print(y)
[[ 1.  2.  3.]
 [ 4.  5.  6.]
 [ 7.  8.  9.]]

Auch nichts neues, wenn wir den Text einlesen, in dem wir als Separator ein Smiley verwendet haben:

y = np.loadtxt("test3.txt", delimiter=" :-) ")
print(y)
[[ 1.  2.  3.]
 [ 4.  5.  6.]
 [ 7.  8.  9.]]

Es ebenso möglich die Spalten über den Index auszuwählen:

y = np.loadtxt("test3.txt", delimiter=" :-) ", usecols=(0,2))
print(y)
[[ 1.  3.]
 [ 4.  6.]
 [ 7.  9.]]

In unserem nächsten Beispiel lesen wir die Datei "times_and_temperatures.txt" aus dem Kapitel Generatoren des Python-Tutorials. Jede Zeile enthält eine Zeitangabe im Format "hh::mm::ss" und eine zufällige Temperatir zwischen 10.0 und 25.0 C°. Wir müssen den Zeit-String in einen Float-Wert konvertieren. Die Zeit wird in Minuten und Sekunden angegeben. Wir definieren zuerst eine Funktion, die "hh::mm::ss" in Minuten wandelt:

def time2float_minutes(time):
    if type(time) == bytes:
        time = time.decode()
    t = time.split(":")
    minutes = float(t[0])*60 + float(t[1]) + float(t[2]) * 0.05 / 3
    return minutes
for t in ["06:00:10", "06:27:45", "12:59:59"]:
    print(time2float_minutes(t))
360.1666666666667
387.75
779.9833333333333

Sie werden festgestellt haben, dass wir den Typ der Zeit gegen Binär geprüft haben. Der Grund liegt in der Benutzung unserer Funktion "time2float_minutes" in loadtxt im nächsten Beispiel. Die Schlüsselwort-Parameter converters beinhaltet ein Dictionary, welches eine Funktion für jede Spalte hält (der Schlüssel der Spalte entspricht dem Schlüssel des Dictionaries) um die String-Daten der Spalte in Float-Werte zu konvertieren. Die String-Daten sind ein Byte-String. Deshalb müssen wir diesen in unserer Funkion in einen Unicode-String transferieren:

y = np.loadtxt("times_and_temperatures.txt", 
               converters={ 0: time2float_minutes})
print(y)
[[  360.     20.1]
 [  361.5    16.1]
 [  363.     16.9]
 ..., 
 [ 1375.5    22.5]
 [ 1377.     11.1]
 [ 1378.5    15.2]]
# delimiter = ";" , # d.h. benutze ";" als als Delimiter statt Leerzeichen


tofile

tofile ist eine Funktion um den Inhalt eines Arrays sowohl im Binär-Format, als auch im Text-Format, in eine Datei zu schreiben.

A.tofile(fid, sep="", format="%s")

Die Daten aus dem ndarray A sind nun in "C"-Reihenfolge geschrieben, ohne Rücksicht der Reihenfolge aus A.

Die Datei, die mit dieser Methode geschrieben wurde, kann mit der Funktion fromfile() wieder geladen werden.

Parameter Bedeutung
fid Kann entweder ein offenes Datei-Objekt sein, oder ein String mit einem Dateinamen.
sep Der String "sep" definiert den Separator, der für die Text-Ausgabe zwischen den Elementen verwendet wird. Wenn der String leer gelassen wird (''), wird eine Binär-Datei geschrieben, genau wie file.write(a.tostring()).
format Format-String für die Text-Ausgabe. Jeder Eintrag im Array wird so formatiert, indem dieser in den nächsten Python-Typ konvertiert wird und dann "format" angewendet wird.

Anmerkung:

Informationen zur Byte-Reihenfolge und Präzision gehen verloren. Deshalb ist keine gute Idee die Funktion zu benutzen um Daten zu archivieren oder zwischen Maschinen mit verschiedener Byte-Reihenfolge zu transportieren. Einige dieser Probleme können überwunden werden bei der Ausgabe der Daten als Text-Datei es auf die Kosten der Geschwindigkeit und Dateigröße geht.

dt = np.dtype([('time', [('min', int), ('sec', int)]),
               ('temp', float)])
x = np.zeros((1,), dtype=dt)
x['time']['min'] = 10
x['temp'] = 98.25
print(x)
fh = open("test6.txt", "bw")
x.tofile(fh)
[((10, 0), 98.25)]


fromfile

fromfile liest Daten, die mit tofile geschrieben wurden. Es ist möglich Binär-Daten zu lesen, wenn der Typ der Daten bekannt ist. Es ist ebenfalls möglich einfach formatierte Txt-Dateien zu parsen. Die Daten aus der Datei werden als Array zurückgegeben.

Die allgemeine Syntax sieht wie folgt aus:

numpy.fromfile(file, dtype=float, count=-1, sep='')

Parameter Bedeutung
file 'file' kann entweder ein offenes Datei-Objekt sein oder ein String mit einem Dateinamen der gelesen werden soll.
dtype definiert den Daten-Typ des Arrays, der aus der Daten-Datei konstruiert wird. Bei Binär-Dateien dient es zur Festlegung der Größe und Bety-Reihenfolge der Elemente der Datei.
count definiert die Anzahl der Elemente, die gelesen werden sollen. -1 bedeutet, das alle gelesen werden.
sep Der String "sep" gibt den Separator an der verwendet wird, wenn die Datei eine Text-Datei ist. Wenn der String leer ist (''), so wird die Datei wie eine Binär-Datei behandelt. Ein Leerzeichen (' ') in einem Separator steht für 0 oder mehrere Leerzeichen. Ein Separator der nur Leerzeichen enthält muss mindestens einem Leerzeichen entsprechen.
fh = open("test4.txt", "rb")
np.fromfile(fh, dtype=dt)
Führt man obigen Code aus, erhält man folgende Ausgabe:
array([((4294967296, 12884901890), 1.0609978957e-313),
       ((30064771078, 38654705672), 2.33419537056e-313),
       ((55834574860, 64424509454), 3.60739284543e-313),
       ((81604378642, 90194313236), 4.8805903203e-313),
       ((107374182424, 115964117018), 6.1537877952e-313),
       ((133143986206, 141733920800), 7.42698527006e-313),
       ((158913789988, 167503724582), 8.70018274493e-313),
       ((184683593770, 193273528364), 9.9733802198e-313)], 
      dtype=[('time', [('min', '<i8'), ('sec', '<i8')]), ('temp', '<f8')])
import numpy as np
import os
# platform dependent: difference between Linux and Windows
# data = np.arange(50, dtype=np.int)
data = np.arange(50, dtype=np.int32)
data.tofile("test4.txt")
fh = open("test4.txt", "rb")
# 4 * 32 = 128
fh.seek(128, os.SEEK_SET)
x = np.fromfile(fh, dtype=np.int32)
print(x)
[32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]

Vorsicht:

Es können Probleme auftreten wenn tofile und fromfile für Daten-Speicherung (Storage) benutzt werden, denn Binär-Dateien sind nicht Plattform-Unabhängig. Über tofile werden keine Informationen zur Byte-Reihenfolge oder Daten-Typen abgespeichert. Daten können im .npy-Format Plattform-Unabhängig gespeichert werden, wenn stattdessen save und load verwendet werden.



Best Practice um Daten zu laden und zu speichern

Der empfohlene Weg um Daten mit Numpy in Python zu speichern und zu laden besteht aus load und save. Im folgenden Beispiel benutzen wir eine temporäre Datei:

import numpy as np
print(x)
from tempfile import TemporaryFile
outfile = TemporaryFile()
x = np.arange(10)
np.save(outfile, x)
outfile.seek(0) # Only needed here to simulate closing & reopening file
np.load(outfile)
[32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]
Der obige Python-Code führt zu folgender Ausgabe:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])



Und noch ein anderer Weg: genfromtxt

Es gibt noch einen weiteren Weg um tabellarische Daten aus einer Datei zu lesen um Arrays zu konstruieren. Wie der Name schon verrät, sollte die Datei eine Text-Datei sein. Die Datei kann ebenfalls eine Archiv-Datei sein. genfromtxt kann die Archiv-Formate gzip und bzip2 verarbeiten. Der Typ des Archivs wird durch die Datei-Erweiterung angegebenm d.h. '.gz' für gzip und '.bz2' für bzip2.

genfromtxt ist langsamer als loadtxt, jedoch kann es mit fehlenden Daten umgehen. Es verarbeitet die Datei in zwei Phasen. Zuerst werden die Zeilen in Strings konvertiert. Anschliessend werden die Strings in den geforderten Daten-Typ konvertiert. Auf der anderen Seite arbeitet loadtxt mit nur einem Schritt, wodurch es schneller ist.