我试图对一个巨大的文件执行非常简单的计算,比如计算某些列的标签数量或其他列的平均值和标准偏差。该文件太大而无法放入内存中,我目前正在每行处理它:
unique = {key: [] for key in categorical_keys}
means = {key: 0.0 for key in numerical_keys}
sds = {key: 0.0 for key in numerical_keys}
with open('input/train.csv', 'r') as read_file:
reader = csv.DictReader(read_file, delimiter=',', quotechar='|')
for i, row in enumerate(reader):
for key, value in row.iteritems():
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
elif key in numerical_keys:
if value:
means[key] = (means[key]*i + float(value))/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (float(value)-means[key])**2)/i
现在这似乎太慢了,我想知道以适合内存的块处理它是否会更快。会更快吗?如果是,为什么?
谢谢你的帮助。
最佳答案
循环优化
如果您需要获得一些速度:
这是我的优化代码草稿(未测试):
from collections import defaultdict
unique = defaultdict(set)
means = {key: 0.0 for key in numerical_keys}
sds = {key: 0.0 for key in numerical_keys}
with open('input/train.csv', 'r') as read_file:
reader = csv.DictReader(read_file, delimiter=',', quotechar='|')
for i, row in enumerate(reader):
for key in categorical_keys:
unique[key].add(row[key])
for key in numerical_keys:
try:
# shall throw ValueError if None or empty string
value=float(row[key])
mean_val = (means[key]*i + value)/(i+1)
means[key] = mean_val
# following fails for i < = 1 with ZeroDivisionError
sds[key] = (sds[key]*(i-1) + (value-mead_val)**2)/i
except (ValueError, ZeroDivisionError):
pass
收集独特的值(value)
您将 dict 与唯一值列表一起使用:
unique = {key: [] for key in categorical_keys}
并将其作为列表项添加到它的唯一值(在循环内发生):
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
如果您直接将值添加到 set 中,您可以安全地进行一些测试(如果该值存在于列表中) -
集合会照顾,那里只收集唯一值。
使用
defaultdict
将确保,如果您使用任何键,您已经有一些空集,尚未使用。
不测试每个循环中记录键的类型,提前了解它们
您的代码反复循环记录键并测试它们的类型,然后执行某些操作:
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
elif key in numerical_keys:
if value:
means[key] = (means[key]*i + float(value))/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (float(value)-means[key])**2)/i
如果您的
categorical_keys
,您可以安全地进行这些测试。和 numerical_keys
正确设置为现有值。然后你可以直接循环遍历已知的键名:
for key in categorical_keys:
unique[key].add(row[value])
for key in numerical_keys:
try:
# shall throw ValueError if None or empty string
value=float(row[value])
means[key] = (means[key]*i + value)/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except ValueError:
pass
重用一次计算值
您的代码反复计算该值:
float(value)
做一次并重复使用。
还有
mean[key]
值一次计算并设置为means[key]
, 和两行后再次使用该值。更好地将值存储在局部变量中
并使用它两次。任何查找(如
means[key]
)都需要付出一些代价。捕获异常通常比值测试更快
您的代码测试值不为空:
elif key in numerical_keys:
if value:
# something here
您可以将其替换为直接与值一起使用的代码。如果值错误,它将
失败和异常
ValueError
将被捕获并忽略。如果您设置了大多数值,这将加快速度向上。
try:
value=float(value)
means[key] = (means[key]*i + value)/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except ValueError:
pass
你能阻止
if i > 1:
测试?这个条件在大多数情况下是正确的,但你在每个循环中测试它。如果你找到一种方法(我没有)
阻止此测试,您也可以更快地进行。
正如您所建议的,我们可以通过捕获
ZeroDivisionError
来解决它对于 i <= 1: try:
# shall throw ValueError if None or empty string
value=float(value)
means[key] = (means[key]*i + value)/(i+1)
# for i <= 1 shall raise ZeroDivisionError
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except (ValueError, ZeroDivisionError):
pass
分块处理数据
关于分块处理:
分块可以提高速度的地方
读取更大的文件
这听起来很明显,但图书馆已经解决了它。预计会有轻微改善或没有改善。
分块获取 CSV 记录
我不知道
csv.reader
的方法或 csv.DictReader
允许得到直接记录块。你必须自己做。这是可能的,我
建议使用
itertools.groupby
.不要指望它自己加速(它会减慢一点),但它是以后其他基于块的加速的先决条件。
将一组值添加到集合中
代码(当前)将值逐个添加到集合中。如果你用大块做
值(越多越好),它会更快,因为每个 python 调用都有
一些小的开销。
计算平均值和 sds 值
您可以利用
statistics
包,这很可能有优化的代码(但它似乎是在纯 python 中)。
无论如何,当您要分块处理数据时,很简单
statistics.mean
不会为您工作,否则您将不得不汇总结果以某种方式结合在一起(如果可能的话)。
如果您自己计算值,通过仔细编码,您可以获得一些
加速,主要基于这一事实,您可以直接在一个块中获得值,并且
不必在每个循环中逐个取消引用值。
大块结论
对我来说分块优化似乎太复杂了,很难
预测,如果它带来任何值(value)。
关于python速度处理每行VS block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37039641/