Leer líneas aleatorias de un enorme archivo CSV en Python


Tengo este archivo CSV bastante grande (15 Gb) y necesito leer aproximadamente 1 millón de líneas aleatorias de él. Por lo que puedo ver, e implementar, la utilidad CSV en Python solo permite iterar secuencialmente en el archivo.

Es muy consumo de memoria para leer el todo el archivo en la memoria para utilizar alguna elección aleatoria y es muy lento para ir a través de todo el archivo y descartar algunos valores y elegir otros, por lo tanto, está allí de todos modos para elegir alguna línea aleatoria del archivo CSV y solo lectura esa línea?

Lo intenté sin éxito:

   import csv

    with open('linear_e_LAN2A_F_0_435keV.csv') as file:
        reader = csv.reader(file)
        print reader[someRandomInteger]

Una muestra del archivo CSV:

331.093,329.735 
251.188,249.994 
374.468,373.782 
295.643,295.159 
83.9058,0 
380.709,116.221 
352.238,351.891 
183.809,182.615 
257.277,201.302
61.4598,40.7106
Author: the wolf, 2012-05-30

10 answers

import random

filesize = 1500                 #size of the really big file
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!

# extra to handle last/first line edge cases
if len(random_line) == 0:       # we have hit the end
    f.seek(0)
    random_line = f.readline()  # so we'll grab the first line instead

Como señaló @AndreBoos, este enfoque conducirá a una selección sesgada. Si conoce la longitud mínima y máxima de la línea, puede eliminar este sesgo haciendo lo siguiente:

Supongamos (en este caso) que tenemos min=3 y max=15

1) Encuentra la longitud (Lp) de la línea anterior.

Entonces si Lp = 3, la línea está más sesgada en contra. Por lo tanto, debemos tomarlo el 100% del tiempo Si Lp = 15, la línea está más sesgada hacia. Solo deberíamos tomarlo el 20% del tiempo, ya que es 5 * más probablemente seleccionado.

Logramos esto manteniendo aleatoriamente la línea X% del tiempo donde:

X = min / Lp

Si no mantenemos la línea, hacemos otra selección aleatoria hasta que nuestro lanzamiento de dados salga bien. :-)

 23
Author: Maria Zverina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-30 20:08:45

Tengo este archivo CSV bastante grande (15 Gb) y necesito leer aproximadamente 1 millón de líneas aleatorias de él

Suponiendo que no necesita exactamente1 millón de líneas y sepa entonces el número de líneas en su archivo CSV de antemano, puede usar reservoir sampling para recuperar su subconjunto aleatorio. Simplemente itere a través de sus datos y para cada línea determine las posibilidades de que la línea sea seleccionada. De esa manera, solo necesita una sola pasada de sus datos.

Esto funciona bien si debe extraer las muestras aleatorias con frecuencia, pero el conjunto de datos real cambia con poca frecuencia (ya que solo necesitará realizar un seguimiento del número de entradas cada vez que cambie el conjunto de datos).

chances_selected = desired_num_results / total_entries
for line in csv.reader(file):
   if random() < chances_selected:
        result.append(line)
 9
Author: Shawn Chin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-30 16:59:43

Puede usar una variación del método probabilístico para elegir una línea aleatoria en un archivo.

En lugar de solo mantener un solo número que se elige, puede mantener un búfer de tamaño C. Para cada número de línea, n, en el archivo con N líneas, desea elegir esa línea con probabilidad C/n (en lugar del original 1/n. Si el número está seleccionado, a continuación, elija una ubicación aleatoria del búfer de longitud C para desalojar.

Así es como obras:

import random

C = 2
fpath = 'somelines.txt'
buffer = []

f = open(fpath, 'r')
for line_num, line in enumerate(f):
    n = line_num + 1.0
    r = random.random()
    if n <= C:
        buffer.append(line.strip())
    elif r < C/n:
        loc = random.randint(0, C-1)
        buffer[loc] = line.strip()

Esto requiere un simple pasar a través del archivo (por lo que es tiempo lineal) y devuelve exactamente C líneas del archivo. Cada línea tendrá probabilidad C/N de ser seleccionada.

Para verificar que lo anterior funciona, creé un archivo con 5 líneas que contienen a,b,c,d,e. Ejecuté el código 10.000 veces con C=2. Esto debería producir alrededor de una distribución uniforme de las 5 elegir 2 (so 10) opciones posibles. Los resultados:

a,b: 1046
b,c: 1018
b,e: 1014
a,c: 1003
c,d: 1002
d,e: 1000
c,e: 993
a,e: 992
a,d: 985
b,d: 947
 7
Author: jterrace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:27

Si desea capturar líneas aleatorias muchas veces (por ejemplo, mini-lotes para aprendizaje automático), y no le importa escanear a través del archivo enorme una vez (sin cargarlo en la memoria), entonces puede crear una lista de líneas y usar seek para capturar rápidamente las líneas (basado en la respuesta de Maria Zverina).

# Overhead:
# Read the line locations into memory once.  (If the lines are long,
# this should take substantially less memory than the file itself.)
fname = 'big_file'
s = [0]
linelocs = [s.append(s[0]+len(n)) or s.pop(0) for n in open(fname)]
f = open(fname) # Reopen the file.

# Each subsequent iteration uses only the code below:
# Grab a 1,000,000 line sample
# I sorted these because I assume the seeks are faster that way.
chosen = sorted(random.sample(linelocs, 1000000))
sampleLines = []
for offset in chosen:
  f.seek(offset)
  sampleLines.append(f.readline())
# Now we can randomize if need be.
random.shuffle(sampleLines)
 3
Author: Marctar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-03 23:36:26

Si las líneas son verdaderas .formato CSV y NO campo fijo, entonces no, no hay. Puede rastrear a través del archivo una vez, indexando las compensaciones de bytes para cada línea, luego, cuando sea necesario, solo use el conjunto de índices, pero no hay forma de predecir a priori la ubicación exacta del carácter \n que termina la línea para archivos csv arbitrarios.

 2
Author: parselmouth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-30 16:05:44

Otra solución es posible si conoce el número total de líneas: genere 1 millón de números aleatorios (random.sample(xrange(n), 1000000)) hasta el número total de líneas como un conjunto, luego use:

for i, line in enumerate(csvfile):
    if i in lines_to_grab:
        yield line

Esto le dará exactamente 1 millón de líneas de una manera imparcial, pero necesita tener el número de líneas de antemano.

 2
Author: Thomas K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-22 11:23:58

Si puede colocar estos datos en una base de datos sqlite3, seleccionar un número de filas aleatorias es trivial. No necesitará leer previamente ni rellenar líneas en el archivo. Dado que los archivos de datos sqlite son binarios, su archivo de datos será de 1/3 a 1/2 más pequeño que el texto CSV.

Puede usar un script como THIS para importar el archivo CSV o, mejor aún, simplemente escriba sus datos en una tabla de base de datos en primer lugar. SQLITE3 es parte de la distribución Python.

Entonces use estos instrucciones para obtener 1,000,000 filas aleatorias:

mydb='csv.db'
con=sqlite3.connect(mydb)

with con:
    cur=con.cursor()
    cur.execute("SELECT * FROM csv ORDER BY RANDOM() LIMIT 1000000;")

    for row in cur.fetchall():
        # now you have random rows...
 1
Author: dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-30 22:24:27

Puede reescribir el archivo con registros de longitud fija, y luego realizar un acceso aleatorio en el archivo intermedio más tarde:

ifile = file.open("inputfile.csv")
ofile = file.open("intermediatefile.csv",'w')
for line in ifile:
    ofile.write(line.rstrip('\n').ljust(15)+'\n')

Entonces, puedes hacer:

import random
ifile = file.open("intermediatefile.csv")
lines = []
samples = random.sample(range(nlines))
for sample in samples:
    ifile.seek(sample)
    lines.append(ifile.readline())

Requiere más espacio en disco, y el primer programa puede tardar algún tiempo en ejecutarse, pero permite un acceso aleatorio ilimitado posterior a los registros con el segundo.

 0
Author: Andrew Buss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-30 16:45:52
# pass 1, count the number of rows in the file
rowcount = sum(1 for line in file)
# pass 2, select random lines
file.seek(0)
remaining = 1000000
for row in csv.reader(file):
    if random.randrange(rowcount) < remaining:
        print row
        remaining -= 1
    rowcount -= 1
 0
Author: Mark Ransom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-30 17:38:30

En este método, generamos un conjunto de números aleatorios cuyo número de elementos es igual al número de líneas a leer, con su rango siendo el número de filas presentes en los datos. Luego se clasifica de más pequeño a más grande y se almacena.

Luego el archivo csv se lee línea por línea y se coloca un line_counter para denotar el número de fila. Este line_counter se comprueba con el primer elemento de la lista de números aleatorios ordenados y si son iguales, entonces esa línea específica se escribe en el nuevo csv archivo y el primer elemento se elimina de la lista y el segundo elemento anterior toma el lugar del primero y el ciclo continúa.

import random
k=random.sample(xrange(No_of_rows_in_data),No_of_lines_to_be_read)
Num=sorted(k)    
line_counter = 0

with open(input_file,'rb') as file_handle:
    reader = csv.reader(file_handle)
    with open(output_file,'wb') as outfile:
            a=csv.writer(outfile)
            for line in reader:
                line_counter += 1
                if line_counter == Num[0]:
                a.writerow(line)
                Num.remove(Num[0])
                if len(Num)==0:
                break    
 0
Author: Kothandaraman R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-14 12:46:53