【问题标题】:Python group by array a, and summarize array b - PerformancePython 按数组 a 分组,并汇总数组 b - 性能
【发布时间】:2011-11-24 04:49:16
【问题描述】:

给定两个相同长度的无序数组 a 和 b:

a = [7,3,5,7,5,7]
b = [0.2,0.1,0.3,0.1,0.1,0.2]

我想按 a 中的元素分组:

aResult = [7,3,5]

对 b 中的元素求和(用于总结概率密度函数的示例):

bResult = [0.2 + 0.1 + 0.2, 0.1, 0.3 + 0.1] = [0.5, 0.1, 0.4]

或者,在 python 中随机 a 和 b:

import numpy as np
a = np.random.randint(1,10,10000)
b = np.array([1./len(a)]*len(a))

我有两种方法,它们肯定远离较低的性能边界。 方法1(至少不错且简短):时间:0.769315958023

def approach_2(a,b):
    bResult = [sum(b[i == a]) for i in np.unique(a)]
    aResult = np.unique(a)

方法 2(numpy.groupby,非常慢)时间:4.65299129486

def approach_2(a,b): 
    tmp = [(a[i],b[i]) for i in range(len(a))]
    tmp2 = np.array(tmp, dtype = [('a', float),('b', float)])
    tmp2 = np.sort(tmp2, order='a') 

    bResult = []
    aResult = []
    for key, group in groupby(tmp2, lambda x: x[0]):
        aResult.append(key)
        bResult.append(sum([i[1] for i in group]))

更新:Pablo 的 Approach3。时间:1.0265750885

def approach_Pablo(a,b):    

    pdf = defaultdict(int); 
    for x,y in zip(a,b):
        pdf[x] += y  

更新:方法 4,由 Unutbu 提供。时间:0.184849023819 [WINNER SO FAR, but a as integer only]

def unique_Unutbu(a,b):

    x=np.bincount(a,weights=b)
    aResult = np.unique(a)
    bResult = x[aResult]

也许有人找到了比我更聪明的解决方案:)

【问题讨论】:

  • 我的意思是您不能假设列表 a 已排序。

标签: python performance sorting numpy group-by


【解决方案1】:

这里的方法类似于@unutbu's one:

import numpy as np

def f(a, b):
    result_a, inv_ndx = np.unique(a, return_inverse=True)
    result_b = np.bincount(inv_ndx, weights=b)
    return result_a, result_b

它允许a 数组使用非整数类型。它允许a 数组中的大值。它以排序顺序返回a 元素。如果需要,可以使用np.unique() 函数的return_index 参数轻松恢复原始顺序。

随着a 中唯一元素数量的增加,它的性能会变差。它比@unutbu 对您问题数据的版本慢 4 倍。

我用另外三种方法制作了performance comparison。领导者是:对于整数数组——Cython 中的 hash-based implementation;对于 double 数组(对于输入大小 10000)——sort-based impl. 也在 Cython 中。

【讨论】:

    【解决方案2】:

    如果 a 由 ints a 的值可以适合 dtype int32),那么您可以使用带有权重的 np.bincount

    import numpy as np
    a = [7,3,5,7,5,7]
    b = [0.2,0.1,0.3,0.1,0.1,0.2]
    
    x=np.bincount(a,weights=b)
    print(x)
    # [ 0.   0.   0.   0.1  0.   0.4  0.   0.5]
    
    print(x[[7,3,5]])
    # [ 0.5  0.1  0.4]
    

    np.unique(a) 返回[3 5 7],因此结果以不同的顺序出现:

    print(x[np.unique(a)])
    # [ 0.1  0.4  0.5]
    

    使用np.bincount 的一个潜在问题是它返回一个数组,其长度等于a 中的最大值。如果a 甚至包含一个值接近2**31-1 的元素,那么bincount 将不得不分配一个大小为8*(2**31-1) 字节(或16GiB)的数组。

    所以np.bincount 可能是数组a 的最快解决方案,它的长度很大,但值不大。对于具有小长度(以及大小值)的数组a,使用collections.defaultdict 可能会更快。

    编辑:请参阅J.F. Sebastian's solution 了解解决仅限整数值限制和大值问题的方法。

    【讨论】:

    【解决方案3】:

    这个方法怎么样:

    from collections import defaultdict
    pdf = defaultdict(int)
    a = [7,3,5,7,5,7]
    b = [0.2,0.1,0.3,0.1,0.1,0.2]
    for x,y in zip(a,b):
      pdf[x] += y
    

    您只需遍历每个元素一次并使用字典进行快速查找。如果你真的想要两个单独的数组作为最后的结果,你可以要求它们:

    aResult = pdf.keys()
    bResult = pdf.values()
    

    【讨论】:

    • 你可以使用defaultdict(int),它更干净。
    • 谢谢!我不知道。更新答案:)
    • 我喜欢这种方法,它很漂亮。不幸的是,它似乎比“方法 1”慢,尤其是对于长数组...
    • @Helga:我已经使用unordered_map 在 Cython 中重写了 Pablo 的实现。它快 10-30 倍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-14
    • 2021-03-25
    • 1970-01-01
    • 2021-11-13
    相关资源
    最近更新 更多