在numpy中是否有类似于MATLAB accumarray的功能?

18

我正在寻找在numpy中类似MATLAB的accumarray函数的快速解决方案。该函数可以将属于同一索引的数组元素累加起来。以下是一个例子:

a = np.arange(1,11)
# array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
accmap = np.array([0,1,0,0,0,1,1,2,2,1])

结果应该是

array([13, 25, 17])

我已经做了什么: 我尝试了这里accum函数,虽然可行但速度较慢。

accmap = np.repeat(np.arange(1000), 20)
a = np.random.randn(accmap.size)
%timeit accum(accmap, a, np.sum)
# 1 loops, best of 3: 293 ms per loop

我尝试使用这里的解决方案,它应该更快,但它不正确地工作:

accum_np(accmap, a)
# array([  1.,   2.,  12.,  13.,  17.,  10.])

有没有内置的numpy函数可以像这样进行累加?或者有其他建议吗?


我的博客文章已经过时了。请尝试使用Github版本,它有一个完整的测试套件。 - Michael
@Michael和我创建了一个名为numpy-groupies的包,其中包括一个名为aggregate的类似于accumarray的函数。有关详细信息,请参见下面的答案。 - dan-man
7个回答

22

使用带有可选参数weightsnp.bincount。在您的示例中,您需要执行以下操作:

np.bincount(accmap, weights=a)

9

虽然来晚了,但...

正如@Jamie所说,对于求和的情况,np.bincount快速且简单。然而,在更一般的情况下,对于其他ufuncs,比如maximum,可以使用np.ufunc.at方法。

我编写了一个类似于Matlab界面的代码片段[请参见下面的链接],它封装了这个方法。它还利用了重复索引规则,提供了'last''first'函数,并且不像Matlab,'mean'被明智地优化了(在Matlab中使用@mean调用accumarray非内置函数会变得特别慢,因为它会为每个组运行一次函数)。

请注意,我没有特别测试过这个代码片段,但希望将来更新它并添加额外的功能和Bug修复。

2015年5月/6月更新: 我重构了我的实现 - 现在作为ml31415/numpy-groupies的一部分可用,并且可以在PyPi上获取(pip install numpy-groupies)。基准测试如下(请参阅github存储库获取最新值)...

function  pure-py  np-grouploop   np-ufuncat np-optimised    pandas        ratio
     std  1737.8ms       171.8ms     no-impl       7.0ms    no-impl   247.1: 24.4:  -  : 1.0 :  -  
     all  1280.8ms        62.2ms      41.8ms       6.6ms    550.7ms   193.5: 9.4 : 6.3 : 1.0 : 83.2
     min  1358.7ms        59.6ms      42.6ms      42.7ms     24.5ms    55.4: 2.4 : 1.7 : 1.7 : 1.0 
     max  1538.3ms        55.9ms      38.8ms      37.5ms     18.8ms    81.9: 3.0 : 2.1 : 2.0 : 1.0 
     sum  1532.8ms        62.6ms      40.6ms       1.9ms     20.4ms   808.5: 33.0: 21.4: 1.0 : 10.7
     var  1756.8ms       146.2ms     no-impl       6.3ms    no-impl   279.1: 23.2:  -  : 1.0 :  -  
    prod  1448.8ms        55.2ms      39.9ms      38.7ms     20.2ms    71.7: 2.7 : 2.0 : 1.9 : 1.0 
     any  1399.5ms        69.1ms      41.1ms       5.7ms    558.8ms   246.2: 12.2: 7.2 : 1.0 : 98.3
    mean  1321.3ms        88.3ms     no-impl       4.0ms     20.9ms   327.6: 21.9:  -  : 1.0 : 5.2 
Python 2.7.9, Numpy 1.9.2, Win7 Core i7.

这里我们使用从[0, 1000)中均匀选择的100,000个索引。具体来说,大约25%的值为0(用于布尔运算),其余部分在[-50,25)上均匀分布。时间显示为10次重复运行。
  • purepy - 仅使用纯python,部分依赖itertools.groupby
  • np-grouploop - 使用numpy根据idx对值进行排序,然后使用split创建单独的数组,然后循环遍历这些数组,对每个数组运行相关的numpy函数。
  • np-ufuncat - 使用numpyufunc.at方法,速度比应该慢 - 如我在numpy的github存储库中创建的问题中所讨论的那样。
  • np-optimisied - 使用自定义的numpy索引/其他技巧击败了上述两种实现(除了依赖ufunc.atmin max prod)。
  • pandas - pd.DataFrame({'idx':idx, 'vals':vals}).groupby('idx').sum()等。

请注意,一些no-impl可能是不必要的,但我还没有费心让它们工作。

如在github上所解释的那样,accumarray现在支持以nan为前缀的函数(例如nansum),以及sortrsortarray。它还适用于多维索引。


干得好,伙计们。我正在尝试使用你们的程序。可惜我无法复制Matlab相同的结果,而且对于多维数组,我很难理解它是如何工作的。你能帮我一下吗? - Nikko
最好在 Github 存储库上发布错误报告(提供一个最小的代码示例会更有帮助)。 - dan-man
谢谢回答。我会提出一个问题,调用numpy groupies aggregate函数。 - Nikko

4

2
你可以使用 pandas DataFrame 在一行代码中完成此操作。
In [159]: df = pd.DataFrame({"y":np.arange(1,11),"x":[0,1,0,0,0,1,1,2,2,1]})

In [160]: df
Out[160]: 
   x   y
0  0   1
1  1   2
2  0   3
3  0   4
4  0   5
5  1   6
6  1   7
7  2   8
8  2   9
9  1  10

In [161]: pd.pivot_table(df,values='y',index='x',aggfunc=sum)
Out[161]: 
    y
x    
0  13
1  25
2  17

您可以告诉pivot_table使用特定的列作为索引和值,并获得一个新的DataFrame对象。当您将聚合函数指定为sum时,结果将与Matlab的accumarray相同。


1
不如被接受的答案好,但仍然有用:

[np.sum([a[x] for x in y]) for y in [list(np.where(accmap==z)) for z in np.unique(accmap).tolist()]]

这需要每次循环 108微秒(100000次循环,3次中的最佳结果)

被接受的答案 (np.bincount(accmap, weights=a) 需要每次循环 2.05微秒(100000次循环,3次中的最佳结果)


0

这取决于你想要做什么,但是numpy unique有一堆可选的输出,可以用来累积。如果你的数组有几个相同的值,那么通过将return_counts选项设置为true,unique将计算有多少个相同的值。在一些简单的应用中,这就是你需要做的全部。

numpy.unique(ar, return_index=False, return_inverse=False, return_counts=True, axis=None)

你也可以将索引设置为 true,并使用它来累加不同的数组。


0
以下怎么样:
import numpy

def accumarray(a, accmap):

    ordered_indices = numpy.argsort(accmap)

    ordered_accmap = accmap[ordered_indices]

    _, sum_indices = numpy.unique(ordered_accmap, return_index=True)

    cumulative_sum = numpy.cumsum(a[ordered_indices])[sum_indices-1]

    result = numpy.empty(len(sum_indices), dtype=a.dtype)
    result[:-1] = cumulative_sum[1:]
    result[-1] = cumulative_sum[0]

    result[1:] = result[1:] - cumulative_sum[1:]

    return result

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接