2012-10-09 2 views
1

j'ai des fichiers texte CSV dans le format:Convertir un texte CSV fichier binaire et obtenir une ligne au hasard à partir en Python sans le lire à la mémoire

1.3, 0, 1.0 
20.0, 3.2, 0 
30.5, 5.0, 5.2 

Les fichiers sont sur 3.5GB taille et I Je ne peux pas lire l'un d'entre eux dans la mémoire de Pandas dans un laps de temps utile.

Mais je n'ai pas besoin de lire tout le fichier, car ce que je veux faire, c'est choisir des lignes aléatoires du fichier et lire les valeurs, et je sais qu'il est théoriquement possible de le faire si le fichier est formaté de manière à ce que tous les champs aient la même taille - par exemple, float16 dans un fichier binaire.

Maintenant, je pense que je peux le convertir, en utilisant la méthode NumPy spécifiée dans la réponse à la question: How to output list of floats to a binary file in Python

Mais, comment dois-je aller à ramasser une ligne au hasard à partir après la conversion est effectuée ?

Dans un fichier texte normal, je pouvais faire:

import random 
offset = random.randrange(filesize) 
f = open('really_big_file') 
f.seek(offset)     #go to random position 
f.readline()     # discard - bound to be partial line 
random_line = f.readline()  # bingo! 

Mais je ne peux pas trouver un moyen pour que cela fonctionne dans un fichier binaire fabriqué à partir NumPy.

+0

@TimPietzcker - N'est-ce pas ce que fait le fragment de code? Bien sûr, avec cette approche vous éliminez la possibilité de choisir la première ligne ... – mgilson

+2

Non, parce que les lignes dans le texte original CSV, ont une longueur différente, et en tant que tel, j'obtiendrais un biais qui favoriserait les plus grandes lignes pour obtenir ramassé à la place des plus petits. (c'est-à-dire, dans les données d'exemple, la troisième ligne aurait une probabilité de 30% plus élevée d'être choisie que la première.) – jbssm

+0

@jbssm - Hmm ... Point intéressant. – mgilson

Répondre

2

J'utilise struct pour convertir en binaire:

import struct 
with open('input.txt') as fin, open('output.txt','wb') as fout: 
    for line in fin: 
     #You could also use `csv` if you're not lazy like me ... 
     out_line = struct.pack('3f',*(float(x) for x in line.split(','))) 
     fout.write(out_line) 

Cela écrit tout comme flotte standard 4 octets sur la plupart des systèmes.

Maintenant, pour lire les données à nouveau:

with open('output.txt','rb') as fin: 
    line_size = 12 #each line is 12 bytes long (3 floats, 4 bytes each) 
    offset = random.randrange(filesize//line_size) #pick n'th line randomly 
    f.seek(offset*line_size) #seek to position of n'th line 
    three_floats_bytes = f.read(line_size) 
    three_floats = struct.unpack('3f',three_floats_bytes) 

Si vous êtes préoccupé par l'espace disque et que vous voulez compresser les données vers le bas à l'aide np.float16 (2 flotteurs d'octets), vous pouvez le faire aussi en utilisant la base squelette ci-dessus, il suffit de remplacer np.fromstring pour struct.unpack et ndarray.tostring à la place de struct.pack (avec le ndarray de type de données approprié bien sûr - et line_size tomberait à 6 ...).

+0

Merci, vous m'avez beaucoup aidé. En combinant votre exemple avec celui que j'ai mentionné dans la question sur NumPy, j'ai été capable de faire ce que je voulais en NumPy. Je pense que je vais l'ajouter ci-dessous. – jbssm

+0

Juste pour dire que ce code ne fonctionne pas. J'obtiens l'erreur: lineOut = struct.pack ('3f', * (float (x) pour x dans line.split (','))) struct.erreur: pack nécessite exactement 3 arguments – jbssm

+0

@jbssm - Il semble alors que vous avez un enregistrement contenant plus (ou moins) de 3 éléments. – mgilson

0

Vous auriez à jouer avec des décalages en fonction de la taille de stockage, mais:

import csv 
import struct 
import random 

count = 0 
with open('input.csv') as fin, open('input.dat', 'wb') as fout: 
    csvin = csv.reader(fin) 
    for row in csvin: 
     for col in map(float, row): 
      fout.write(struct.pack('f', col)) 
      count += 1 


with open('input.dat', 'rb') as fin: 
    i = random.randrange(count) 
    fin.seek(i * 4) 
    print struct.unpack('f', fin.read(4)) 
+0

Ceci cherche à un flottement aléatoire, pas une ligne aléatoire de flottants. En d'autres termes, vous perdez votre information "record" ici. – mgilson

+0

@mgilson en effet - Je viens de voir votre réponse - mais c'est à peu près la même chose, il suffit d'ajuster le 'line_size' comme vous l'avez appelé –

0

Ainsi, en utilisant l'exemple fourni par les réponses de helpfull, je trouve un moyen de le faire avec NumPy si quelqu'un est intéressé:

# this converts the file from text CSV to bin 
with zipfile.ZipFile("input.zip", 'r') as inputZipFile: 
    inputCSVFile = inputZipFile.open(inputZipFile.namelist()[0], 'r') # it's 1 file only zip 

    with open("output.bin", 'wb') as outFile: 
     outCSVFile = csv.writer(outFile, dialect='excel') 
     for line in inputCSVFile: 
      lineParsed = ast.literal_eval(line) 
      lineOut = numpy.array(lineParsed,'float16') 
      lineOut.tofile(outFile) 
     outFile.close() 

    inputCSVFile.close() 
    inputZipFile.close() 

# this reads random lines from the binary file 
with open("output.bin", 'wb') as file: 
    file.seek(0) 

    lineSize = 20 # float16 has 2 bytes and there are 10 values: 
    fileSize = os.path.getsize("output.bin") 

    offset = random.randrange(fileSize//lineSize) 
    file.seek(offset * lineSize) 
    random_line = file.read(lineSize) 
    randomArr = numpy.fromstring(random_line, dtype='float16') 
Questions connexes