Как читать большой текстовый файл, избегая построчного чтения :: Python

У меня есть большой файл данных (N, 4), который я отображаю построчно. Мои файлы имеют размер 10 ГБ, упрощенная реализация приведена ниже. Хотя следующие работы, это занимает огромное количество времени.

Я хотел бы реализовать эту логику так, чтобы текстовый файл читался напрямую, и я мог получить доступ к элементам. После этого мне нужно отсортировать весь (сопоставленный) файл на основе элементов столбца-2.

Примеры, которые я вижу в Интернете, предполагают меньший фрагмент данных (d) и используют f[:] = d[:], но я не могу этого сделать, так как d в моем случае огромен и съедает мою оперативную память.

PS: я знаю, как загрузить файл с помощью np.loadtxt и отсортировать их с помощью argsort, но эта логика не работает (ошибка памяти) для размера файла в ГБ. Буду признателен за любое направление.

nrows, ncols = 20000000, 4  # nrows is really larger than this no. this is just for illustration
f = np.memmap('memmapped.dat', dtype=np.float32,
              mode='w+', shape=(nrows, ncols))

filename = "my_file.txt"

with open(filename) as file:

    for i, line in enumerate(file):
        floats = [float(x) for x in line.split(',')]
        f[i, :] = floats
del f

person nuki    schedule 22.07.2020    source источник
comment
Если вы можете разделить файлы, возможно, вы сможете использовать dask.   -  person James    schedule 22.07.2020
comment
@user13815479 user13815479 nrows, ncols в вашем примере представляет 320 МБ данных, которые должны легко поместиться в памяти. Насколько он велик на самом деле?   -  person Han-Kwang Nienhuys    schedule 22.07.2020
comment
@James: Спасибо, я новичок. Не могли бы вы рассказать подробнее? Если вы можете поделиться MWE, я был бы очень признателен.   -  person nuki    schedule 22.07.2020
comment
@Han-KwangNienhuys: Это просто простой пример, демонстрирующий мою логику. Мой текстовый файл действительно большой (10 ГБ), и N соответственно большой.   -  person nuki    schedule 22.07.2020
comment
loadtxtgenfromtxt) читают CSV-файлы построчно, накапливая значения в списке списков (или массивов), который в конце преобразуется в массив. pandas имеет режим на основе c для своего pd.read_csv, который быстрее, но результатом является кадр данных.   -  person hpaulj    schedule 23.07.2020


Ответы (2)


РЕДАКТИРОВАТЬ: Вместо того, чтобы самостоятельно создавать фрагменты, лучше использовать функцию фрагментирования панд, которая намного, намного быстрее, чем load_txt numpy.

import numpy as np
import pandas as pd

## create csv file for testing
np.random.seed(1)
nrows, ncols = 100000, 4
data = np.random.uniform(size=(nrows, ncols))
np.savetxt('bigdata.csv', data, delimiter=',')

## read it back
chunk_rows = 12345
# Replace np.empty by np.memmap array for large datasets.
odata = np.empty((nrows, ncols), dtype=np.float32)
oindex = 0
chunks = pd.read_csv('bigdata.csv', chunksize=chunk_rows, 
                     names=['a', 'b', 'c', 'd'])
for chunk in chunks:
    m, _ = chunk.shape
    odata[oindex:oindex+m, :] = chunk
    oindex += m

# check that it worked correctly.
assert np.allclose(data, odata, atol=1e-7)

Функция pd.read_csv в режиме фрагментирования возвращает специальный объект, который можно использовать в цикле, например for chunk in chunks:; на каждой итерации он будет считывать фрагмент файла и возвращать его содержимое в виде pandas DataFrame, который в этом случае можно рассматривать как массив numpy. Параметр names необходим для предотвращения обработки первой строки CSV-файла как имен столбцов.

Старый ответ ниже

Функция numpy.loadtxt работает с именем файла или чем-то, что будет возвращать строки в цикле в такой конструкции, как:

for line in f: 
   do_something()

Ему даже не нужно притворяться файлом; список строк подойдет!

Мы можем читать фрагменты файла, которые достаточно малы, чтобы поместиться в памяти, и предоставлять пакеты строк для np.loadtxt.

def get_file_lines(fname, seek, maxlen):
    """Read lines from a section of a file.
    
    Parameters:
        
    - fname: filename
    - seek: start position in the file
    - maxlen: maximum length (bytes) to read
    
    Return:
        
    - lines: list of lines (only entire lines).
    - seek_end: seek position at end of this chunk.
    
    Reference: https://stackoverflow.com/a/63043614/6228891
    Copying: any of CC-BY-SA, CC-BY, GPL, BSD, LPGL
    Author: Han-Kwang Nienhuys
    """
    f = open(fname, 'rb') # binary for Windows \r\n line endings
    f.seek(seek)
    buf = f.read(maxlen)
    n = len(buf)
    if n == 0:
        return [], seek
    
    # find a newline near the end
    for i in range(min(10000, n)):
        if buf[-i] == 0x0a:
            # newline
            buflen = n - i + 1
            lines = buf[:buflen].decode('utf-8').split('\n')
            seek_end = seek + buflen
            return lines, seek_end
    else:
        raise ValueError('Could not find end of line')

import numpy as np

## create csv file for testing
np.random.seed(1)
nrows, ncols = 10000, 4

data = np.random.uniform(size=(nrows, ncols))
np.savetxt('bigdata.csv', data, delimiter=',')

# read it back        
fpos = 0
chunksize = 456 # Small value for testing; make this big (megabytes).

# we will store the data here. Replace by memmap array if necessary.
odata = np.empty((nrows, ncols), dtype=np.float32)
oindex = 0

while True:
    lines, fpos = get_file_lines('bigdata.csv', fpos, chunksize)
    if not lines:
        # end of file
        break
    rdata = np.loadtxt(lines, delimiter=',')
    m, _ = rdata.shape
    odata[oindex:oindex+m, :] = rdata
    oindex += m
    
assert np.allclose(data, odata, atol=1e-7)

Отказ от ответственности: я тестировал это в Linux. Я ожидаю, что это будет работать в Windows, но может случиться так, что обработка символов '\r' вызывает проблемы.

person Han-Kwang Nienhuys    schedule 22.07.2020
comment
Потрясающий! Кажется, это работает с тестовым файлом размером 1 ГБ, который у меня есть. Буду признателен, если вы ответите на мои конкретные вопросы: [1] odata = np.empty((nrows, ncols), dtype=np.float32) ‹= любое ограничение на nrows/ncols. Я боюсь, что это даст мне ошибку для действительно больших (10-40 ГБ) файлов? [2] Что делает цикл for? Будучи новичком, с трудом разбираюсь. [3] Итак, все данные сохраняются в odata, и мы по-прежнему потребляем оперативную память, верно? Потому что при сортировке odata = odata[np.argsort(odata[:, 1])] я получаю ошибку памяти для большого файла. Любые предложения для выполнения сортировки? - person nuki; 23.07.2020
comment
Относительно [1] и [2]: я обновил ответ. Что касается [3] о том, как сортировать большой массив memmap numpy: вам нужно опубликовать это как новый вопрос, если существующие ответы на эту тему вам не подходят. - person Han-Kwang Nienhuys; 23.07.2020
comment
Спасибо, с вашей помощью я могу читать и хранить данные в HDF5, но я не могу выполнить сортировку по каждому фрагменту и создать окончательный (Nx4) массив, отсортированный по столбцу-2. Какие-либо предложения? Я только что спросил сообщество SO. - person nuki; 28.07.2020

Я понимаю, что это не может быть ответом, но рассматривали ли вы возможность использования двоичных файлов? Когда файлы очень большие, сохранение в ascii очень неэффективно. Если можете, используйте вместо этого np.save и np.load.

person scarpma    schedule 22.07.2020
comment
Можно поподробнее, учитывая что я новичок? У меня в основном есть большой текстовый файл с 4 столбцами. Вы говорите преобразовать это в двоичный файл, а затем использовать np.load? Использует ли это оперативную память? Был бы признателен, если бы вы могли поделиться MWE, пожалуйста? - person nuki; 23.07.2020
comment
Ваш ответ неполный и должен был быть опубликован как комментарий. - person mac13k; 23.07.2020