我有一个csv文件,其中包含2亿行。
加载此文件的最佳方法是使用csv读取器逐行(因为我有很多这些文件,因此以后并行化代码不需要加载大量数据集和RAM过载)。
我正在尝试计算某列中出现值的次数,并在字典中记录它们的值和频率。例如,计算一列中唯一ID的数量以及这些ID出现的次数。
以下是我如何执行此操作的示例:
import csv
from tqdm import tqdm
field_names = ['A','B','ID','C','D']
filename = '/PATH/file'
ID_dict = {}
with open(data_path+filename) as f:
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
所以我在这里感兴趣的是标记为“ ID”的列,但想象其中有大约2亿个条目。
遍历所有这些行并填充字典很慢(在我的计算机上大约需要10个小时)。
或者,将值附加到新数组,然后使用Counter查找每个唯一元素的出现次数也需要很长时间。 (请参阅How do I count unique values inside a list)
有没有一种更快的方法可以使我丢失?也许有一种更快的熊猫方式? 预先感谢
答案 0 :(得分:2)
尝试将cvs文件转换为sql数据库,例如在一次性预处理步骤中,每个文件都代表一个表。
在单列中搜索将减少为sql查询。 内存优化由datebase引擎负责。
由于您使用的是python,因此建议您使用sqlite3(导入sqlite3)。
答案 1 :(得分:1)
请勿使用DictReader()
。 DictReader()
做了很多工作,将行转换为字典,并且可以配置缺少和多余的列,而您在这里确实不需要。只需使用常规阅读器并访问每一行的第三列即可。
您可以通过使用Counter()
对象开始进一步加快速度(它会自动为您处理0
情况)。通过使用newline=''
打开文件,可能会获得很小的速度提升; CSV模块建议您还是这样做,因为它要确保它知道行尾与列中可能嵌入的换行符。
如果您使用map()
对象和operator.itemgetter()
,则可以进一步避免评估循环开销,并将ID直接传递给计数器:
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))
仍有2亿行需要处理。我使用Faker生成了100万行的半现实数据,并将这些行复制了200次到一个新文件中,而我的2017年型号Macbook Pro带有SSD,使用{{1 }},没有5分钟14秒。 tqdm
声称每次迭代仅增加60纳秒(在2亿行中为12秒),但是在我的测试中,它看起来很容易是该数字的3或4倍。
Pandas 读取数据的速度大约相同,因为Pandas的tqdm
建立在read_csv()
之上,而上述速度与Python一样快可以读取具有2亿行的文件。但是,它将为这2亿行建立一个数据帧,这将需要大量的内存来处理。您必须process your data in chunks并汇总结果才能完全可行。
让我们进行一些速度测试,比较您的版本(一个带有csv.reader()
减速带的版本),Pandas和上述方法。我们将使用一个包含约100个唯一ID的1万行的测试集来均匀地比较事物,而无需使用I / O。这将测试每种方法的公正计数功能。因此,设置测试数据和测试; tqdm
关键字分配有助于避免针对重复测试的全局名称查找:
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)
和Python DictReader()
循环的结合真正使您的版本慢6到7倍。在for
被抑制的情况下,tqdm
的开销已降至0.3纳秒;放下stderr
上下文管理器会使输出更加冗长,并将时间增加到50微秒,因此每次迭代大约需要2纳秒:
with redirect_stderr()
但是,熊猫在这里保持良好状态!但是,如果不对将全部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)
不需要千兆字节的内存。
如果您需要对CSV数据集进行更多处理,那么使用SQLite也是一个好主意。那时我什至不使用Python。只需使用SQLite command line tool to import the CSV data directly:
Counter()
等
答案 2 :(得分:0)
怎么样:
1)df.groupby('ID')。count()
或
2)df ['ID']。value_counts()
另请参阅: When is it appropriate to use df.value_counts() vs df.groupby('...').count()?
然后,您可以使用数据并从两个已经对条目进行计数的列表中创建字典。