【问题标题】:Improve execution time of very slow python code改善非常慢的 python 代码的执行时间
【发布时间】:2021-09-26 10:40:28
【问题描述】:

这是我的第一篇文章,所以请多多包涵。我需要一些帮助来优化下面的一个班轮。

pd_df.loc[flag, 'COL_{}'.format(col_number)] = pd_df.loc[flag,'COL{}'.format(col_number)].apply(lambda x: x + str(userid) + "@")

pd_df : Panda 数据框包含 2M 行

flag= numpy 一维布尔数组在 pd_df 中一次过滤/更新多行

COL_{}'.format(col_number)= 根据主 FOR 循环的随机列号,如 COL_1、COL_5 到 COL_15(数据类型字符串,长度为 5000 个字符)

一般来说,这段代码是做什么的,首先根据列号过滤要更新的标志和列,并在这些多行和单列中附加用户ID列表,并以@作为分隔符。例如@userid1@userid2@userid2 等等。

这行代码占用了我总时间的 75%,原因是 pandas 数据帧 loc 函数缓慢且行数很大,即 2M。

谁能帮我把这块转换成更优化的方式,比如字典/numpy数据类型。

下面是上面代码正在创建的输出。基于 Country 和 Category 用户 ID 相关,其用户 ID 附加到该列号。假设 Col_1 最多可以包含 userid3,column2 最多可以包含 userid7,依此类推,直到 col15。

提前致谢。

问候, 丽娃

【问题讨论】:

  • 如果您展示更多代码,可能会有其他优化帮助更多。
  • 其他部分已经优化,只是一个循环,通过用户ID列表检查它将在哪些行和列中附加分隔符
  • 您可以编辑您的问题并将示例输入放在那里以便我们复制吗?
  • 样本输入?我不明白。你想要一些 excel 格式的样本输入数据吗?我共享的代码是大代码的一小部分,并且该代码的输入全部在内存中,而不是来自本地文件。加载初始文件后,我们会根据逻辑进行大量计算。所以输入数据可能没有用。我只是想知道一些更好的方法来替换这个占用我所有执行时间的线性代码。
  • 对不起,如果我一开始不够清楚。

标签: python python-3.x pandas numpy dictionary


【解决方案1】:

同意apply() 可能会很慢。您希望尽可能利用矢量化操作。尝试使用连接运算符 (+)。这工作更快吗

pd_df.loc[flag, 'COL_{}'.format(col_number)] = pd_df.loc[flag,'COL{}'.format(col_number)] + (str(userid) + "@")

此外,不确定它是否会有所帮助,但其中一些字符串应该预先计算(可能 Python 已经在缓存它们,但以防万一):

col_name = 'COL_{}'.format(col_number)
suffix = str(userid) + "@"
pd_df.loc[flag, col_name] = pd_df.loc[flag, col_name] + suffix

【讨论】:

  • 谢谢。通过删除 apply 运算符并使用 f 字符串,时间已从 75% 减少到 15%。对于这条线。如果有一些我可以使用的数据类型(例如 numpy 数组或字典而不是熊猫框架),我期待更多的改进。
  • 很高兴到目前为止您已经成功地进行了这些改进。如果切换到 f 个字符串会有所不同,那么这意味着字符串连接被计算了多次。试试我上面的预计算,也许会有所帮助。
  • 我早先做了这个以及其他更改。
【解决方案2】:

几点:

  1. f-strings 总是比str.format 快,尽可能使用它们:

    In [3]: fmt = "{foo}"
    In [4]: %timeit fmt.format(foo=5)
    299 ns ± 21.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    In [5]: foo = 5
    In [6]: %timeit f"{foo}"
    79.2 ns ± 2.31 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
    
  2. 看来userid是独立于数据框的,我不知道你为什么用apply,就用broadcasting吧:

    In [8]: userid = "abcdef"
    In [9]: pd.Series('abc def ghi jkl'.split()) + f'@{userid}'
    Out[9]:
    0    abc@abcdef
    1    def@abcdef
    2    ghi@abcdef
    3    jkl@abcdef
    dtype: object
    

所以最终的方法可能是这样的:

for num in range(5):
    flag = ... # calculate flag
    df[flag, f"col_{num}"] = df[flag, f"col_{num}"] + f"@{userid}"

【讨论】:

  • 谢谢。它有助于将这行代码的时间从 75% 减少到 15%。如果我可以使用其他一些数据类型(例如 numpy 数组或字典而不是 panda 框架),我期待更多的改进。
  • pandas 数据框和 numpy 数组之间没有太大区别。熊猫数据框是下面的 numpy 数组。字典很慢,但我不确定为什么这在这里相关,因为您正在使用数组/表格数据。如果您不分享您的代码,就很难提出更多建议,尤其是在性能方面。
【解决方案3】:

apply 是按项目运行函数的较慢方式之一。

pd_df.loc[flag, f’COL_{col_number}’] = pd_df.loc[flag, f’COL_{col_number}’].map(lambda x: f’{x}{userid}@‘)

【讨论】:

  • 谢谢。用地图替换了应用,现在它更慢了。我想如果我们可以将此熊猫数据框转换为新对象(dp/dict),那么可能会有一些改进。根本原因是这个 loc 函数和在大型数据集上使用分隔符 (@) 连接值并且一次连接多行。
猜你喜欢
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 2018-01-18
  • 2019-09-06
  • 1970-01-01
  • 2021-10-22
  • 1970-01-01
相关资源
最近更新 更多