有没有一种方法可以更快地运行此Python代码段?

时间:2019-09-17 18:26:44

标签: python loops dictionary series

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的熊猫系列的列表,而indices_ordered是长度为20000的字符串的列表。在我的Mac(2.3 GHz Intel Core i5和16)中需要23分钟GB 2133 MHz LPDDR3)来运行此代码。我对Python很陌生,但是我觉得更聪明的编码(也许更少的循环)会有所帮助。


编辑:

这里是一个示例,说明如何创建数据(resindices_ordered)以使其能够在代码段上方运行(该代码段稍作更改以访问唯一的字段,而不是按字段名称,因为我可以找不到如何使用字段名称构造内联系列)

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)]

4 个答案:

答案 0 :(得分:3)

编辑:既然可以使用测试数据,显然以下更改对运行时没有影响。所描述的技术仅在内部循环非常有效时(大约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。我不知道熊猫中物品的查找效率如何,但最好是在外部而不是在内部循环中进行。
  • score属性查找移动到外部循环。
  • 摆脱defaultdict并预先创建列表并使用普通字典。
  • 完全避免dict存储并直接在列表上工作,并按顺序预先创建它们。仅在最后创建字典。
  • 缓存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)}

答案 1 :(得分:3)

这里的问题是,您需要为每个单个值遍历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 :(得分:2)

真的应该使用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作为数组或dict进行访问。

它应该比处理defaultdict,列表和Series快得多。

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 :(得分:2)

pd.Series对象的输入列表中使用 pandas 魔术(带有2行代码):

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)的列表中,最后将分组结果转换为字典{{1} }(pandas.Series.groupby