Чтение огромного файла .csv

107

В настоящее время я пытаюсь читать данные из файлов .csv в Python 2.7 с количеством до 1 миллиона строк и 200 столбцов (файлы варьируются от 100 МБ до 1,6 ГБ). Я могу сделать это (очень медленно) для файлов с менее чем 300 000 строк, но как только я перейду выше, я получаю ошибки памяти. Мой код выглядит так:

def getdata(filename, criteria):
    data=[]
    for criterion in criteria:
        data.append(getstuff(filename, criteron))
    return data

def getstuff(filename, criterion):
    import csv
    data=[]
    with open(filename, "rb") as csvfile:
        datareader=csv.reader(csvfile)
        for row in datareader: 
            if row[3]=="column header":
                data.append(row)
            elif len(data)<2 and row[3]!=criterion:
                pass
            elif row[3]==criterion:
                data.append(row)
            else:
                return data

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

Мои вопросы:

  1. Как мне заставить это работать с большими файлами?

  2. Есть ли способ сделать это быстрее?

Мой компьютер имеет 8 ГБ ОЗУ, работает под управлением 64-битной Windows 7, а процессор - 3,40 ГГц (не уверен, какая информация вам нужна).

Чарльз Диллон
источник
1
Я знаю, что есть несколько похожих кажущихся вопросов, но ни один из них не был достаточно конкретным для моей проблемы, чтобы сильно помочь. Извините, если я пропустил один.
Чарльз Диллон
2
Вы должны хранить прочитанные данные в базе данных (например, Sqlite) вместо того, чтобы хранить их в памяти. Затем вы можете запустить дальнейшую обработку, такую ​​как фильтрация в базе данных
Майкл Бутшер

Ответы:

159

Вы читаете все строки в список, а затем обрабатываете этот список. Не делай этого .

Обрабатывайте ряды по мере их создания. Если вам нужно сначала отфильтровать данные, используйте функцию генератора:

import csv

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        count = 0
        for row in datareader:
            if row[3] == criterion:
                yield row
                count += 1
            elif count:
                # done when having read a consecutive series of rows 
                return

Я также упростил ваш тест фильтра; логика та же, но более лаконичная.

Поскольку вы сопоставляете только одну последовательность строк, соответствующую критерию, вы также можете использовать:

import csv
from itertools import dropwhile, takewhile

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        # first row, plus any subsequent rows that match, then stop
        # reading altogether
        # Python 2: use `for row in takewhile(...): yield row` instead
        # instead of `yield from takewhile(...)`.
        yield from takewhile(
            lambda r: r[3] == criterion,
            dropwhile(lambda r: r[3] != criterion, datareader))
        return

Теперь вы можете перейти getstuff()напрямую. Сделайте то же самое в getdata():

def getdata(filename, criteria):
    for criterion in criteria:
        for row in getstuff(filename, criterion):
            yield row

Теперь зацикливайтесь прямо getdata()в вашем коде:

for row in getdata(somefilename, sequence_of_criteria):
    # process row

Теперь вы храните в памяти только одну строку вместо тысячи строк на критерий.

yieldделает функцию функцией- генератором , что означает, что она не будет работать, пока вы не начнете перебирать ее.

Мартейн Питерс
источник
получаете ли вы такую ​​же эффективность памяти при использовании этого метода с csv.DictReader? Поскольку мои тесты с файлом .csv размером 2,5 ГБ показывают, что попытка перебора строки за строкой, подобная этой, при использовании этого, вместо того, csv.readerчтобы вызывать процесс Python, увеличивающийся до полного использования памяти 2,5 ГБ.
user5359531
@ user5359531, что означает, что вы где-то храните ссылки на объекты словаря. Сам по себе DictReader не сохраняет ссылки, поэтому проблема в другом.
Мартейн Питерс
40

Хотя ответ Мартиджина, наверное, лучший. Вот более интуитивно понятный способ обработки больших файлов CSV для начинающих. Это позволяет обрабатывать группы строк или фрагментов одновременно.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)
mmann1123
источник
9
Почему использование панд делает его более интуитивным?
wwii
25
Для новичков вроде меня всегда лучше 4 строки кода.
mmann1123
3
Обычный код Python такой же короткий и позволяет обрабатывать каждую строку. Функция генератора предназначена только для фильтрации материала; как бы вы сделали такую ​​же фильтрацию в Pandas?
Мартейн Питерс
1
Это круто! Решил мою проблему загрузки и обработки больших файлов CSV с помощью pandas. Спасибо!
Эльза Ли
1
Он работает очень хорошо, даже когда содержимое некоторых строк занимает несколько строк!
Dielson Sales
19

Я провожу изрядный анализ вибрации и смотрю на большие наборы данных (десятки и сотни миллионов точек). Мое тестирование показало, что функция pandas.read_csv () в 20 раз быстрее, чем numpy.genfromtxt (). А функция genfromtxt () в 3 раза быстрее, чем numpy.loadtxt (). Похоже, вам нужны панды для больших наборов данных.

Я разместил код и наборы данных, которые я использовал в этом тестировании, в блоге, в котором обсуждались MATLAB и Python для анализа вибрации .

Стив
источник
3
Основная проблема OP заключалась не в скорости, а в нехватке памяти. Использование другой функции для обработки самого файла не устраняет недостатков чтения его в список вместо использования потокового процессора.
pydsigner
6

то, что сработало для меня, было и остается сверхбыстрым

import pandas as pd
import dask.dataframe as dd
import time
t=time.clock()
df_train = dd.read_csv('../data/train.csv', usecols=[col1, col2])
df_train=df_train.compute()
print("load train: " , time.clock()-t)

Другое рабочее решение:

import pandas as pd 
from tqdm import tqdm

PATH = '../data/train.csv'
chunksize = 500000 
traintypes = {
'col1':'category',
'col2':'str'}

cols = list(traintypes.keys())

df_list = [] # list to hold the batch dataframe

for df_chunk in tqdm(pd.read_csv(PATH, usecols=cols, dtype=traintypes, chunksize=chunksize)):
    # Can process each chunk of dataframe here
    # clean_data(), feature_engineer(),fit()

    # Alternatively, append the chunk to list and merge all
    df_list.append(df_chunk) 

# Merge all dataframes into one dataframe
X = pd.concat(df_list)

# Delete the dataframe list to release memory
del df_list
del df_chunk
Юрий Кошелек
источник
Разве df_train=df_train.compute()строка в вашем первом решении не загружает весь набор данных в память ... чего он пытается не делать?
Сэм Диллард,
3

Для тех, кто задает этот вопрос. Использование pandas с chunksize и usecols помогло мне прочитать огромный zip-файл быстрее, чем другие предложенные варианты.

import pandas as pd

sample_cols_to_keep =['col_1', 'col_2', 'col_3', 'col_4','col_5']

# First setup dataframe iterator, ‘usecols’ parameter filters the columns, and 'chunksize' sets the number of rows per chunk in the csv. (you can change these parameters as you wish)
df_iter = pd.read_csv('../data/huge_csv_file.csv.gz', compression='gzip', chunksize=20000, usecols=sample_cols_to_keep) 

# this list will store the filtered dataframes for later concatenation 
df_lst = [] 

# Iterate over the file based on the criteria and append to the list
for df_ in df_iter: 
        tmp_df = (df_.rename(columns={col: col.lower() for col in df_.columns}) # filter eg. rows where 'col_1' value grater than one
                                  .pipe(lambda x:  x[x.col_1 > 0] ))
        df_lst += [tmp_df.copy()] 

# And finally combine filtered df_lst into the final lareger output say 'df_final' dataframe 
df_final = pd.concat(df_lst)
Ewalel
источник
1

вот еще одно решение для Python3:

import csv
with open(filename, "r") as csvfile:
    datareader = csv.reader(csvfile)
    count = 0
    for row in datareader:
        if row[3] in ("column header", criterion):
            doSomething(row)
            count += 1
        elif count > 2:
            break

вот datareaderфункция генератора.

Ришаб Аграхари
источник
Таким образом, это работает так же эффективно, как и решение, использующее оператор yield. : извините, это не так. Вызов функции обратного вызова добавляет дополнительные накладные расходы, особенно потому, что вам нужно обрабатывать состояние явно и отдельно.
Мартейн Питерс
@MartijnPieters Спасибо. Обновил ответ.
Ришаб Аграхари
0

Если вы используете панда и много оперативной памяти (достаточно , чтобы прочитать весь файл в память) попробуйте использовать pd.read_csvс low_memory=False, например:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Майк Т
источник