Читання величезного .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

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

Мої запитання:

  1. Як мені вдається змусити це працювати з більшими файлами?

  2. Чи є якийсь спосіб я зробити це швидше?

На моєму комп’ютері є 8 Гб оперативної пам’яті, працює 64-бітова Windows 7, а процесор - 3,40 ГГц (не точно, яка інформація вам потрібна).


1
Я знаю, що є кілька подібних, здавалося б, питань, але жодне з них не здавалося достатньо конкретним моїй проблемі, щоб допомогти багато. Вибачте, якщо є такий, який я пропустив.
Чарльз Діллон

2
Ви повинні зберігати прочитані дані в базі даних (наприклад, Sqlite), а не зберігати їх у пам'яті. Потім можна запустити подальшу обробку, як фільтрування на db
Майкл Бутчер

Відповіді:


158

Ви читаєте всі рядки в списку, потім обробляєте цей список. Не робіть цього .

Обробляйте рядки під час їх виготовлення. Якщо вам потрібно спочатку відфільтрувати дані, використовуйте функцію генератора:

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? Оскільки мої тести на 2,5 csv.readerГб.
користувач5359531

@ user5359531, який би вказував на те, що ви десь зберігаєте посилання на об'єкти словника. DictReader сам по собі не зберігає посилання, тому проблема полягає в іншому.
Martijn Pieters

39

Хоча відповідь Мартіхіна найкраща. Ось більш інтуїтивний спосіб обробляти великі файли CSV для початківців. Це дозволяє одночасно обробляти групи рядків або шматки.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

9
Чому використання панд робить її більш інтуїтивно зрозумілою?
wwii

25
4 рядки коду завжди краще для новачків, як я.
mmann1123

3
Звичайний код Python такий же короткий і дозволяє обробляти по рядку. Функція генератора існує лише для фільтрування матеріалів; як би ти пішов робити таку саму фільтрацію в Pandas?
Martijn Pieters

1
Це круто! Вирішили мою проблему із завантаженням та обробкою великих CSV-файлів за допомогою панд. Дякую!
Ельза Лі

1
Це дуже добре працює, навіть якщо вміст деяких рядків перебуває в декількох рядках!
продажів

19

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

Я розмістив коди та набори даних, які використовував у цьому тестуванні, у своєму блозі, де обговорювали MATLAB vs Python для аналізу вібрації .


3
Основна проблема ОП була не швидкістю, а вичерпанням пам'яті. Використання іншої функції для обробки самого файлу не усуває недоліків читання його у списку, а не використання потокового процесора.
пидсигнер

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

Для того, хто приземлиться до цього питання. Використання панд з ' 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)

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функція генератора.


Отже, це працює так само ефективно, як і рішення, яке використовує оператор урожайності. : вибачте, це не так. Виклик функції зворотного виклику додає більше накладних витрат, тим більше, що вам потрібно обробляти стан явно та окремо.
Martijn Pieters

@MartijnPieters Дякую Оновлено відповідь.
Рішабх Аграхарі

0

Якщо ви використовуєте панда і багато оперативної пам'яті (досить , щоб прочитати весь файл в пам'ять) спробуйте використовувати pd.read_csvз low_memory=False, наприклад:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.