python速度处理每行VS块

Rom*_*ain 5 python performance chunks

我试图在一个巨大的文件上执行非常简单的计算,例如计算某些列的标签数量或其他列的平均值和标准偏差.该文件太大,无法容纳在内存中,我目前正在每行处理它:

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
Run Code Online (Sandbox Code Playgroud)

现在这似乎太慢了,我想知道是否可以更快地处理它可以适合内存的块.会更快吗?如果是,为什么?

谢谢你的帮助.

Jan*_*sky 4

循环优化

如果您需要获得一些速度:

  • 当然,你确实需要加速(否则你会在毫无价值的任务上花费太多时间)
  • 从循环开始
    • 检查是否可以防止某些循环
    • 优化/删除循环内的指令
      • 每条指令都很重要
      • 每个引用都有计数

这是我的优化代码草稿(未经测试):

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
Run Code Online (Sandbox Code Playgroud)

收集独特的价值

您可以将 dict 与唯一值列表一起使用:

unique = {key: [] for key in categorical_keys}
Run Code Online (Sandbox Code Playgroud)

并向其添加唯一值作为列表项(发生在循环内):

if key in categorical_keys:
    if row[key] not in unique[key]:
        unique[key].extend([value])
Run Code Online (Sandbox Code Playgroud)

如果您直接将值添加到集合中,您可以安全地进行一些测试(如果列表中存在该值) - 该集合会照顾,只在那里收集唯一的值。

使用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
Run Code Online (Sandbox Code Playgroud)

如果您的categorical_keysnumerical_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
Run Code Online (Sandbox Code Playgroud)

重复使用一次计算值

您的代码重复计算该值:

float(value)
Run Code Online (Sandbox Code Playgroud)

做一次并重复使用。

mean[key]值也被计算并设置到 中means[key],两行后再次使用该值。最好将值存储在局部变量中并使用它两次。任何查找(例如means[key])都会花费一些费用。

捕获异常通常比值测试更快

您的代码测试值是否不为空:

        elif key in numerical_keys:
            if value:
                # something here
Run Code Online (Sandbox Code Playgroud)

您可以将其替换为直接与该值一起使用的代码。如果该值错误,它将失败并且异常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
Run Code Online (Sandbox Code Playgroud)

你能阻止if i > 1:测试吗?

此条件在大多数情况下都是正确的,但您需要在每个循环中测试它。如果你找到一种方法(我没有)来阻止这个测试,你也会更快地得到它。

正如您所建议的,我们可以通过捕获ZeroDivisionErrori <= 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
Run Code Online (Sandbox Code Playgroud)

分块处理数据

关于块处理:

  • 这肯定会增加一些复杂性
  • 如果小心的话,它可能会加快程序的速度
  • 它可能会减慢速度或提供错误的结果

分块可以提高速度的地方

读取较大块的文件

这听起来很明显,但图书馆已经解决了这个问题。预计会有轻微改善或没有改善。

以块的形式获取 CSV 记录

我不知道有一种方法csv.readercsv.DictReader允许直接获取记录块。你必须自己做。这是可能的,我建议使用itertools.groupby.

不要指望它本身会加速(它会减慢速度),但这是以后其他基于块的加速的先决条件。

将值块添加到集合中

该代码(当前)将值逐一添加到集合中。如果你使用大量的值(越多越好),它会更快,因为每个 python 调用都有一些小的开销。

计算平均值和 sds 值

你可以利用statisticspackage,它可能有优化的代码(但它似乎是纯 python 的)。

无论如何,当您要分块处理数据时,简单的方法 statistics.mean对您不起作用,或者您必须以某种方式将结果聚合在一起(如果可能的话)。

如果您自己计算该值,通过仔细编码,您可以获得一些加速,主要基于这样的事实,您可以直接在一个块中获取值,而不必在每个循环中逐个值地取消引用。

分块结论

对我来说,分块优化似乎太复杂了,而且很难预测它是否能带来任何价值。