【问题标题】:Is there a way to run this Python snippet faster?有没有办法更快地运行这个 Python 片段?
【发布时间】:2020-01-18 15:51:44
【问题描述】:
from collections import defaultdict
dct = defaultdict(list)
for n in range(len(res)):
    for i in indices_ordered:
        dct[i].append(res[n][i])

请注意,res 是长度为 5000 的 pandas Series 列表,indices_ordered 是长度为 20000 的字符串列表。在我的 Mac(2.3 GHz Intel Core i5 和 16 GB 2133 MHz LPDDR3 ) 运行此代码。我对 Python 很陌生,但我觉得更聪明的编码(可能更少循环)会有很大帮助。


编辑:

这是一个示例,说明如何创建数据(resindices_ordered)以便能够在 sn-p 上运行(由于我找不到,它稍微更改为访问唯一字段而不是按字段名称如何使用字段名称构建内联系列)

import random, string, pandas
index_sz = 20000
res_sz = 5000
indices_ordered = [''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) for i in range(index_sz)]
res = [pandas.Series([random.randint(0,10) for i in range(index_sz)], index = random.sample(indices_ordered, index_sz)) for i in range(res_sz)]

【问题讨论】:

  • 您可以为“res”添加一个示例值吗?可视化您正在尝试做的事情变得更容易。
  • 我们看不到 indices_ordered 是什么,或者任何预期输入和输出的示例
  • 使用列表理解:[res[n].score[i] for i in indices_ordered for n in range(len(res))]。没有改进,但只是作为一种选择
  • indices_ordered 的目的是什么?
  • 如果你使用 pandas,你应该留在 pandas 中。 pandas 数据框既可以被视为字典也可以被视为列表,因此无需创建所有这些普通的 Python 对象。请发布一个带有简短系列和一些索引的示例以及所需的输出应该是什么。

标签: python loops dictionary series


【解决方案1】:

这里的问题是您对每个值都迭代 indices_ordered。只需删除indices_ordered。以数量级方式剥离它以测试时间:

import random
import string

import numpy as np
import pandas as pd

from collections import defaultdict


index_sz = 200
res_sz = 50
indices_ordered = [''.join(random.choice(string.ascii_uppercase + string.digits)
                   for _ in range(10)) for i in range(index_sz)]

res = [pd.Series([random.randint(0,10) for i in range(index_sz)],
                  index = random.sample(indices_ordered, index_sz))
       for i in range(res_sz)]


def your_way(res, indices_ordered):
    dct = defaultdict(list)
    for n in range(len(res)):
        for i in indices_ordered:
            dct[i].append(res[n][i])


def my_way(res):
    dct = defaultdict(list)
    for item in res:
        for string_item, value in item.iteritems():
            dct[string_item].append(value)

给予:

%timeit your_way(res, indices_ordered)
160 ms ± 5.45 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit my_way(res)
6.79 ms ± 47.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这降低了整个方法的时间复杂度,因为您不必每次都通过indicies_ordered 并分配值,因此随着数据大小的增长,差异将变得更加明显。

只是增加一个数量级:

index_sz = 2000
res_sz = 500

给予:

%timeit your_way(res, indices_ordered)
17.8 s ± 999 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit my_way(res)
543 ms ± 9.07 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

    【解决方案2】:

    编辑:现在测试数据可用,很明显下面的更改对运行时没有影响。所描述的技术仅在内部循环非常有效(大约 5-10 次 dict 查找)时才有效,通过删除一些上述查找使其更加高效。这里r[i] 项查找使其他任何东西都相形见绌数量级,因此优化根本无关紧要。


    您的外循环需要 5000 次迭代,而您的内循环需要 20000 次迭代。这意味着您在 23 分钟内执行 1 亿次迭代,即每次迭代需要 13.8 μs。这并不快,即使在 Python 中也是如此。

    我会尝试通过从内部循环中剥离任何不必要的工作来缩短运行时间。具体来说:

    • for n in range(len(res)) 后跟res[n] 转换为for r in res。我不知道在 pandas 中查找项目的效率如何,但最好在外部循环中进行而不是在内部循环中进行。
    • score 属性查找移至外部循环。
    • 摆脱defaultdict 并预先创建列表并使用普通字典。
    • 完全避免使用字典存储并直接处理列表,按顺序预先创建它们。只在最后创建一个字典。
    • 缓存append列表方法的查找,提前准备内循环需要的(append, i)对。

    以下是实现上述建议的代码:

    # pre-create the lists
    lsts = [[] for _ in range(len(indices_ordered))]
    # prepare the pairs (appendfn, i)
    fast_append = [(l.append, i)
                   for (l, i) in zip(lsts, indices_ordered)]
    
    for r in res:
        # pre-fetch res[n].score
        r_score = r.score
        for append, i in fast_append:
            append(r_score[i])
    
    # finally, create the dict out of the lists
    dct = {i: lst for (i, lst) in zip(indices_ordered, lsts)}
    

    【讨论】:

    • 这些建议是有道理的,但 OP 应该继续使用 pandas 数据帧和系列,而不是从数组创建列表列表。
    • @EricDuminil 我也这么认为。如果有人提出如何在 Pandas 中执行此操作的正确建议,我将删除我的答案。
    • 您不必删除任何内容。我相信您的提示将至少稍微提高 OP 的性能。不过,我想有一些 pandas 方法可以在几秒钟内完成这项工作。
    • @user4815162342 感谢您的回答,是的,我不想更改我使用的数据结构,但我编辑了问题以包含一些示例数据。希望有帮助!
    • @EricDuminil 我可以留下答案,但遗憾的是它对运行时没有任何影响。这是因为它描述的技术仅在内部循环非常有效时才有效,使其更加高效。在这里,r[i] 项目查找使属性查找的成本和res[n]数量级的索引相形见绌。
    【解决方案3】:

    真的应该使用DataFrame

    这是一种直接创建数据的方法:

    import pandas as pd
    import numpy as np
    import random
    import string
    index_sz = 3
    res_sz = 10
    
    indices_ordered = [''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(3)) for i in range(index_sz)]
    
    df = pd.DataFrame(np.random.randint(10, size=(res_sz, index_sz)), columns=indices_ordered)
    

    无需对任何内容进行排序或索引。 DataFrame 基本上可以作为数组或字典访问。

    它应该比同时使用默认字典、列表和系列要快得多。

    df 现在看起来像:

    >>> df
       7XQ  VTV  38Y
    0    6    9    5
    1    5    5    4
    2    6    0    7
    3    0    0    8
    4    7    8    9
    5    8    6    4
    6    2    4    9
    7    3    2    2
    8    7    6    0
    9    8    0    1
    
    >>> df['7XQ']
    0    6
    1    5
    2    6
    3    0
    4    7
    5    8
    6    2
    7    3
    8    7
    9    8
    Name: 7XQ, dtype: int64
    
    >>> df['7XQ'][:5]
    0    6
    1    5
    2    6
    3    0
    4    7
    Name: 7XQ, dtype: int64
    

    使用原始大小,此脚本输出5000 rows × 20000 columns DataFrame 在我的笔记本电脑上不到 3 秒。

    【讨论】:

    • 谢谢,但问题是,我真的无法控制“系列列表”,因为它来自其他人编写的外部函数,并且它的设计方式与 5000系列是并行创建的,并且在该过程中以不同的顺序为每个系列分配相同的索引。
    • @user5054 有很多方法可以创建 df。传递 dicts 的迭代器也很好。例如,第三个答案应该适合。
    • 我实际上并没有遵循您对澄清的回答
    • @roganjosh:AFAICT,我的代码生成的信息与 OP 的简化代码相同,但生成速度更快,需要的内存更少。它可能不适合 OP 的实际输入,但如果没有看到一个工作示例,我就无法判断。建议仍然有效:OP 的目标当然可以使用 DataFrame 来实现,并且应该使用 DataFrame 来完成。
    • OP给出了一个工作示例,将其剥离几个数量级
    【解决方案4】:

    pd.Series 对象的输入列表中使用 pandas 魔法(两行代码):

    all_data = pd.concat([*res])
    d = all_data.groupby(all_data.index).apply(list).to_dict() 
    

    暗示动作:

    • pd.concat([*res]) - 将所有系列连接成一个保留每个系列对象的索引 (pandas.concat)
    • all_data.groupby(all_data.index).apply(list).to_dict() - 在all_data.index 上确定一组相同的索引标签值,然后将每个组值放入带有.apply(list) 的列表中,并最终将分组结果转换为字典.to_dict() (pandas.Series.groupby)

    【讨论】:

    • pd.concat 是否处理res 中每个项目中索引的不同顺序? (请参阅我在 Eric Duminil 的回答中的评论)
    • @user5054,看我的解释
    猜你喜欢
    • 2021-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-19
    • 1970-01-01
    • 2019-09-12
    • 2020-07-29
    • 1970-01-01
    相关资源
    最近更新 更多