Не используйте DictReader()
.DictReader()
выполняет большую работу по преобразованию строки в словарь с настраиваемой обработкой пропущенных и лишних столбцов, которые вам здесь действительно не нужны .Просто используйте обычный читатель и получите доступ к 3-му столбцу каждой строки.
Вы можете еще больше ускорить это, используя для начала объект Counter()
(он автоматически обработает для вас случай 0
),Вы можете получить очень небольшое увеличение скорости, открыв файл с newline=''
;модуль CSV рекомендует делать это в любом случае, так как он хочет убедиться, что он знает об окончаниях строк и возможных встроенных символах новой строки в столбцах.
Если вы используете объект map()
и operator.itemgetter()
, вы можете в дальнейшем избежатьиздержки цикла оценки и передача идентификаторов непосредственно в счетчик:
import csv
import os.path
from collections import Counter
from operator import itemgetter
filename = '/PATH/file'
with open(os.path(data_path, filename), newline='') as f:
reader = csv.reader(f)
id_counts = Counter(map(itemgetter(2), reader))
Тем не менее, 200 миллионов строк - это много работы.Я сгенерировал 1 миллион строк полуреалистичных данных, используя Faker , скопировал эти строки 200 раз в новый файл, и моя модель Macbook Pro с твердотельным накопителем 2017 года обработала полученные 12 ГБ данных всего за 6 минут с помощью tqdm
включено и 5 минут 14 секунд без.tqdm
утверждает, что добавляет к каждой итерации всего 60 наносекунд (12 секунд на 200 миллионов строк), но в моих тестах это, кажется, легко в 3 или 4 раза превышает это число.
Панды чтение данные будут примерно с той же скоростью, поскольку Pandas '1025 * построен поверх csv.reader()
, и вышеописанное так же быстро, как Python может читать файл с 200 миллионами строк.Тем не менее, он затем создаст информационный фрейм для этих 200 миллионов строк, и для его обработки потребуется значительный объем памяти.Вам нужно будет обработать ваши данные в блоках и объединить результаты, чтобы это было вообще возможно.
Давайте проведем несколько тестов скорости, сравнивая вашу версию (одну с другой, а другую безtqdm
Speed Bump), Панды и вышеупомянутый подход.Мы будем использовать тестовый набор из 10 тысяч строк с около 100 уникальными идентификаторами, чтобы сравнивать вещи без использования ввода-вывода.Это проверяет только возможности подсчета каждого подхода.Итак, настройка тестовых данных и тестов;name=name
назначения ключевых слов помогают избежать глобальных поисков имен для повторных тестов:
>>> import csv, pandas
>>> from timeit import Timer
>>> from collections import Counter
>>> from contextlib import redirect_stderr
>>> from io import StringIO
>>> from operator import itemgetter
>>> from random import randrange
>>> from tqdm import tqdm
>>> row = lambda: f",,{randrange(100)},,\r\n" # 5 columns, only care about middle column
>>> test_data = ''.join([row() for _ in range(10 ** 4)]) # CSV of 10.000 rows
>>> field_names = ['A', 'B', 'ID', 'C', 'D']
>>> filename = '/PATH/file'
>>> tests = []
>>> def as_test(f):
... tests.append((f.__name__, f))
...
>>> @as_test
... def in_question(f, csv=csv, tqdm=tqdm, field_names=field_names):
... ID_dict = {}
... reader = csv.DictReader(f, field_names, delimiter=',')
... for row in tqdm(reader):
... label = row['ID']
... if label not in ID_dict.keys():
... ID_dict[label] = 0
... ID_dict[label] += 1
...
>>> @as_test
... def in_question_no_tqdm(f, csv=csv, tqdm=tqdm, field_names=field_names):
... ID_dict = {}
... reader = csv.DictReader(f, field_names, delimiter=',')
... for row in reader:
... label = row['ID']
... if label not in ID_dict.keys():
... ID_dict[label] = 0
... ID_dict[label] += 1
...
>>> @as_test
... def pandas_groupby_count(f, pandas=pandas, field_names=field_names):
... df = pandas.read_csv(f, names=field_names)
... grouped_counts = df.groupby('ID').count()
...
>>> @as_test
... def pandas_value_counts(f, pandas=pandas, field_names=field_names):
... df = pandas.read_csv(f, names=field_names)
... counts = df['ID'].value_counts()
...
>>> @as_test
... def counter_over_map(f, csv=csv, Counter=Counter, ig2=itemgetter(2)):
... reader = csv.reader(f)
... id_counts = Counter(map(ig2, reader))
...
и выполнения временных тестов:
>>> for testname, testfunc in tests:
... timer = Timer(lambda s=StringIO, t=test_data: testfunc(s(t)))
... with redirect_stderr(StringIO()): # silence tqdm
... count, totaltime = timer.autorange()
... print(f"{testname:>25}: {totaltime / count * 1000:6.3f} microseconds ({count:>2d} runs)")
...
in_question: 33.303 microseconds (10 runs)
in_question_no_tqdm: 30.467 microseconds (10 runs)
pandas_groupby_count: 5.298 microseconds (50 runs)
pandas_value_counts: 5.975 microseconds (50 runs)
counter_over_map: 4.047 microseconds (50 runs)
Комбинация DictReader()
и Python *Цикл 1042 * - это то, что делает вашу версию медленной в 6-7 раз.Служебная нагрузка tqdm
упала до 0,3 наносекунды с подавленной stderr
;удаление менеджера контекста with redirect_stderr()
сделало вывод более подробным и увеличило время до 50 микросекунд, так что около 2 наносекунд на итерацию:
>>> timer = Timer(lambda s=StringIO, t=test_data: tests[0][1](s(t)))
>>> count, totaltime = timer.autorange()
10000it [00:00, 263935.46it/s]
10000it [00:00, 240672.96it/s]
10000it [00:00, 215298.98it/s]
10000it [00:00, 226025.18it/s]
10000it [00:00, 201787.96it/s]
10000it [00:00, 202984.24it/s]
10000it [00:00, 192296.06it/s]
10000it [00:00, 195963.46it/s]
>>> print(f"{totaltime / count * 1000:6.3f} microseconds ({count:>2d} runs)")
50.193 microseconds ( 5 runs)
Панды, однако, держатся здесь хорошо!Но без разбивки гигабайтов памяти, необходимых для считывания всех 200 миллионов строк данных в память (с фактическим набором данных, а не с пустыми столбцами, как я создал здесь), будет намного медленнее и, возможно, не то, что ваша машина может нести на самом деле.Использование Counter()
здесь не требует гигабайт памяти.
Если вам нужно больше обрабатывать набор данных CSV, то использование SQLite будет хорошей идеей.Я бы тогда даже не использовал Python;просто используйте инструмент командной строки SQLite для прямого импорта данных CSV :
$ csvanalysis.db
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> CREATE TABLE csvdata (A, B, ID, C, D);
sqlite> CREATE INDEX csvdata_id on csvdata (ID);
sqlite> .import /path/to/file.csv csvdata
sqlite> SELECT ID, COUNT(*) FROM csvdata GROUP BY ID;
и т. д.