textos - leer datos de un txt python
Python: cómo leer un gran archivo de texto en la memoria (6)
Creé un módulo para este caso de uso utilizando una fusión externa : https://bitbucket.org/richardpenman/csvsort
>>> from csvsort import csvsort
>>> csvsort(''links.csv'', columns=[1], has_header=False)
Estoy usando Python 2.6 en una Mac Mini con 1GB de RAM. Quiero leer en un gran archivo de texto
$ ls -l links.csv; file links.csv; tail links.csv
-rw-r--r-- 1 user user 469904280 30 Nov 22:42 links.csv
links.csv: ASCII text, with CRLF line terminators
4757187,59883
4757187,99822
4757187,66546
4757187,638452
4757187,4627959
4757187,312826
4757187,6143
4757187,6141
4757187,3081726
4757187,58197
Entonces cada línea en el archivo consiste en una tupla de dos valores enteros separados por comas. Quiero leer todo el archivo y ordenarlo según la segunda columna. Sé que podría hacer la clasificación sin leer todo el archivo en la memoria. Pero pensé que para un archivo de 500MB debería poder hacerlo en la memoria ya que tengo 1GB disponible.
Sin embargo, cuando trato de leer en el archivo, Python parece asignar mucha más memoria de la que necesita el archivo en el disco. Entonces, incluso con 1 GB de RAM, no puedo leer en el archivo de 500 MB en la memoria. El código de My Python para leer el archivo e imprimir información sobre el consumo de memoria es:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
infile=open("links.csv", "r")
edges=[]
count=0
#count the total number of lines in the file
for line in infile:
count=count+1
total=count
print "Total number of lines: ",total
infile.seek(0)
count=0
for line in infile:
edge=tuple(map(int,line.strip().split(",")))
edges.append(edge)
count=count+1
# for every million lines print memory consumption
if count%1000000==0:
print "Position: ", edge
print "Read ",float(count)/float(total)*100,"%."
mem=sys.getsizeof(edges)
for edge in edges:
mem=mem+sys.getsizeof(edge)
for node in edge:
mem=mem+sys.getsizeof(node)
print "Memory (Bytes): ", mem
La salida que obtuve fue:
Total number of lines: 30609720
Position: (9745, 2994)
Read 3.26693612356 %.
Memory (Bytes): 64348736
Position: (38857, 103574)
Read 6.53387224712 %.
Memory (Bytes): 128816320
Position: (83609, 63498)
Read 9.80080837067 %.
Memory (Bytes): 192553000
Position: (139692, 1078610)
Read 13.0677444942 %.
Memory (Bytes): 257873392
Position: (205067, 153705)
Read 16.3346806178 %.
Memory (Bytes): 320107588
Position: (283371, 253064)
Read 19.6016167413 %.
Memory (Bytes): 385448716
Position: (354601, 377328)
Read 22.8685528649 %.
Memory (Bytes): 448629828
Position: (441109, 3024112)
Read 26.1354889885 %.
Memory (Bytes): 512208580
Ya después de leer solo el 25% del archivo de 500MB, Python consume 500MB. Por lo tanto, parece que almacenar el contenido del archivo como una lista de tuplas de entradas no es muy eficiente desde el punto de vista de la memoria. ¿Hay una mejor manera de hacerlo, para poder leer en mi archivo de 500MB en mi 1GB de memoria?
Dado que estos son solo números, cargarlos en una matriz Nx2 eliminaría algunos gastos generales. Use NumPy para matrices multidimensionales. Alternativamente, puede usar dos arrays normales de python para representar cada columna.
Es posible que desee mirar mmap:
http://docs.python.org/library/mmap.html
Te permitirá tratar el archivo como una gran matriz / cadena y hará que el sistema operativo maneje la transferencia de datos dentro y fuera de la memoria para que quepa.
Para que pueda leer en el archivo csv, una línea a la vez, escriba los resultados en un archivo mmap''d (en un formato binario adecuado) y luego trabaje en el archivo mmap''d. Como el archivo mmap''d es solo temporal, por supuesto puede crear un archivo tmp para este fin.
Aquí hay un código que muestra usando mmap con un archivo temporal para leer en datos csv y almacenarlo como un par de enteros:
import sys
import mmap
import array
from tempfile import TemporaryFile
def write_int(buffer, i):
# convert i to 4 bytes and write into buffer
buffer.write(array.array(''i'', [i]).tostring())
def read_int(buffer, pos):
# get the 4 bytes at pos and convert to integer
offset = 4*pos
return array.array(''i'', buffer[offset:offset+4])[0]
def get_edge(edges, lineno):
pos = lineno*2
i, j = read_int(edges, pos), read_int(edges, pos+1)
return i, j
infile=open("links.csv", "r")
count=0
#count the total number of lines in the file
for line in infile:
count=count+1
total=count
print "Total number of lines: ",total
infile.seek(0)
# make mmap''d file that''s long enough to contain all data
# assuming two integers (4 bytes) per line
tmp = TemporaryFile()
file_len = 2*4*count
# increase tmp file size
tmp.seek(file_len-1)
tmp.write('' '')
tmp.seek(0)
edges = mmap.mmap(tmp.fileno(), file_len)
for line in infile:
i, j=tuple(map(int,line.strip().split(",")))
write_int(edges, i)
write_int(edges, j)
# now confirm we can read the ints back out ok
for i in xrange(count):
print get_edge(edges, i)
Aunque es un poco rudo En realidad, es probable que quieras terminar todo con una buena clase, para que se pueda acceder a tus bordes de una manera que los haga comportarse como una lista (con indización, len, etc.). Con suerte pensé que te daría un punto de partida.
Existe una receta para ordenar archivos de mayor tamaño que la RAM en esta página , aunque deberá adaptarla para su caso que involucre datos de formato CSV. También hay enlaces a recursos adicionales allí.
Editar: Es cierto que el archivo en el disco no es "más grande que la RAM", pero la representación en memoria puede ser mucho más grande que la RAM disponible . Por un lado, su propio programa no obtiene el total de 1 GB (sobrecarga del sistema operativo, etc.). Por otro, incluso si almacenó esto en la forma más compacta para Python puro (dos listas de enteros, asumiendo el equipo de 32 bits, etc.), estaría usando 934MB para esos 30M pares de enteros.
Usar numpy también puede hacer el trabajo, usando solo alrededor de 250MB. No es especialmente rápido cargar de esta manera, ya que tiene que contar las líneas y preasignar la matriz, pero puede ser el tipo más rápido dado que está en la memoria:
import time
import numpy as np
import csv
start = time.time()
def elapsed():
return time.time() - start
# count data rows, to preallocate array
f = open(''links.csv'', ''rb'')
def count(f):
while 1:
block = f.read(65536)
if not block:
break
yield block.count('','')
linecount = sum(count(f))
print ''/n%.3fs: file has %s rows'' % (elapsed(), linecount)
# pre-allocate array and load data into array
m = np.zeros(linecount, dtype=[(''a'', np.uint32), (''b'', np.uint32)])
f.seek(0)
f = csv.reader(open(''links.csv'', ''rb''))
for i, row in enumerate(f):
m[i] = int(row[0]), int(row[1])
print ''%.3fs: loaded'' % elapsed()
# sort in-place
m.sort(order=''b'')
print ''%.3fs: sorted'' % elapsed()
Salida en mi máquina con un archivo de muestra similar a lo que mostró:
6.139s: file has 33253213 lines
238.130s: read into memory
517.669s: sorted
El valor predeterminado en numpy es Quicksort . La rutina ndarray.sort () (que ordena en el lugar) también puede tomar el argumento keyword kind="mergesort"
o kind="heapsort"
pero parece que ninguno de estos es capaz de ordenar en una matriz de registros que, por cierto, utilicé como la única forma en que podría ver ordenar las columnas juntas en lugar de las predeterminadas que las ordenarían de forma independiente (arruinando totalmente sus datos).
La forma más económica de almacenar las líneas de entrada en la memoria es como elementos array.array (''i''), suponiendo que cada número encajará en un entero de 32 bits con signo. El costo de la memoria será de 8N bytes, donde N es el número de líneas.
A continuación se explica cómo hacer la ordenación y escribir el archivo de salida en orden ordenado:
from array import array
import csv
a = array(''i'')
b = array(''i'')
for anum, bnum in csv.reader(open(''input.csv'', ''rb'')):
a.append(int(anum))
b.append(int(bnum))
wtr = csv.writer(open(''output.csv'', ''wb''))
for i in sorted(xrange(len(a)), key=lambda x: b[x]):
wtr.writerow([a[i], b[i]])
Desafortunadamente sorted()
devuelve una lista, no un iterador, y esta lista será bastante grande: 4N bytes para punteros y 12N bytes para objetos int, es decir, 16N bytes para la salida sorted()
. Nota: esto se basa en CPython 2.X en una máquina de 32 bits; empeora para cada una de las máquinas de 3.xy 64 bits. Todo eso son 24N bytes. Tienes 31 millones de líneas, por lo que necesitas 31 * 24 = 744 MB ... parece que debería funcionar; tenga en cuenta que este cálculo no permite ninguna memoria asignada por el género, pero tiene un margen de seguridad razonable.
Aparte: ¿Cuál es el costo de un GB extra o 3 de memoria expresado en horas a su tasa de salario?
Todos los objetos python tienen una sobrecarga de memoria encima de los datos que en realidad están almacenando. De acuerdo con getsizeof en mi sistema Ubuntu de 32 bits, una tupla tiene una sobrecarga de 32 bytes y una int toma 12 bytes, por lo que cada fila en tu archivo toma 56 bytes + un puntero de 4 bytes en la lista - supongo que será mucho más para un sistema de 64 bits. Esto está en línea con las cifras que proporcionó y significa que sus 30 millones de filas tomarán 1,8 GB.
Sugiero que, en lugar de utilizar Python, utilice la utilidad de ordenamiento de Unix. No soy un jefe de Mac, pero supongo que las opciones de ordenación de OS X son las mismas que las de Linux, así que esto debería funcionar:
sort -n -t, -k2 links.csv
-n significa ordenar numéricamente
-t, significa usar una coma como el separador de campo
-k2 significa ordenar en el segundo campo
Esto ordenará el archivo y escribirá el resultado en stdout. Puede redirigirlo a otro archivo o canalizarlo a su programa python para hacer un procesamiento posterior.
editar: si no desea ordenar el archivo antes de ejecutar su secuencia de comandos python, puede usar el módulo de subproceso para crear una tubería para la utilidad de clasificación de shell, luego lea los resultados ordenados de la salida de la tubería.