在Python中即时计算csv列的出现次数

时间:2018-11-29 12:17:34

标签: python csv dictionary unique

我有一个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

有没有一种更快的方法可以使我丢失?也许有一种更快的熊猫方式? 预先感谢

3 个答案:

答案 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()?

然后,您可以使用数据并从两个已经对条目进行计数的列表中创建字典。