Python组由数组a组成,并汇总数组b - Performance

时间:2011-09-24 10:18:25

标签: python performance sorting numpy group-by

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

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

我想按照:

中的元素进行分组
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]))

更新:方法3,由巴勃罗。时间: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,但仅限整数]

def unique_Unutbu(a,b):

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

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

3 个答案:

答案 0 :(得分:6)

这种方法类似于@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个元素。如果需要,可以使用return_index函数的np.unique()参数轻松恢复原始订单。

随着a中独特元素的数量增加,性能会恶化。它比你问题的数据慢了4倍于@ unutbu的版本。

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

答案 1 :(得分:5)

如果a由整数组成< 2 ** 31-1(即如果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了解有关整数值限制和大值问题的方法。

答案 2 :(得分:2)

这种方法怎么样:

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