对于纯 numpy 解决方案,您可以检查 b 的 np.diff(),这将为您提供一个新的零数组,除了值发生变化的任何地方。但是,这需要一个小的调整,因为np.diff() 将数组的大小减少了一个元素,因此您的索引将减少一个。实际上,numpy 目前正在开发以使其更好(提供新参数以将输出填充回原始大小;请参阅此处的问题:https://github.com/numpy/numpy/issues/8132)
话虽如此......这里应该是有启发性的:
In [100]: a
Out[100]: array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 7.3, 0.8, 0.9, 1. , 1.2, 1.4])
In [101]: b
Out[101]: array([0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2])
In [102]: np.diff(b) # note it is one element shorter than b
Out[102]: array([0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0])
In [103]: np.flatnonzero(np.diff(b))
Out[103]: array([4, 7])
In [104]: np.flatnonzero(np.diff(b)) + 1
Out[104]: array([5, 8])
In [105]: np.insert(np.flatnonzero(np.diff(b)) + 1, 0, 0)
Out[105]: array([0, 5, 8]) # these are the indices of the start of each group
In [106]: indices = _
In [107]: np.add.reduceat(a, indices)
Out[107]: array([1.5, 8.7, 4.5])
In [108]: def sumatchanges(a, b):
...: indices = np.insert(np.flatnonzero(np.diff(b)) + 1, 0, 0)
...: return np.add.reduceat(a, indices)
...:
In [109]: sumatchanges(a, b)
Out[109]: array([1.5, 8.7, 4.5])
我肯定更喜欢使用 Pandas groupby 作为 jpp 在大多数设置中使用的答案,因为这很难看。希望通过对 numpy 的这些更改,这可能会在未来看起来更漂亮、更自然。
请注意,此答案等同于 Maarten 给出的itertools.groupby 答案(在输出中)。具体来说,就是假设这些组是连续的。即,这个
b = np.array([0,0,0,0,0,1,1,1,2,2,2,2]).astype(int)
会产生与使用相同的输出
b = np.array([0,0,0,0,0,1,1,1,0,0,0,0]).astype(int)
数字无关紧要,只要它改变。然而,对于 Maarten 给出的另一个解决方案,以及 jpp 的 pandas 解决方案,无论位置如何,它们都会对具有相同标签的所有事物求和。 OP不清楚你喜欢哪个。
时间:
在这里,我将创建一个用于求和的随机数组和一个递增值的随机数组,每个数组有 100k 个条目,并测试这两个函数的时间:
In [115]: import timeit
In [116]: import pandas as pd
In [117]: def sumatchangespd(a, b):
...: return pd.Series(a).groupby(b).sum().values
...:
In [125]: l = 100_000
In [126]: a = np.random.rand(l)
In [127]: b = np.cumsum(np.random.randint(2, size=l))
In [128]: sumatchanges(a, b)
Out[128]:
array([2.83528234e-01, 6.66182064e-01, 9.32624292e-01, ...,
2.98379765e-01, 1.97586484e+00, 8.65103445e-04])
In [129]: %timeit sumatchanges(a, b)
1.91 ms ± 47.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [130]: %timeit sumatchangespd(a, b)
6.33 ms ± 267 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
也只是为了确保它们是等效的:
In [139]: all(np.isclose(sumatchanges(a, b), sumatchangespd(a, b)))
Out[139]: True
所以 numpy 版本更快(不足为奇)。同样,根据您的输入,这些函数的作用可能略有不同:
In [120]: b # numpy solution grabs each chunk as a separate piece
Out[120]: array([0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2])
In [121]: b[-4:] = 0
In [122]: b # pandas will sum the vals in a that have same vals in b
Out[122]: array([0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0])
In [123]: sumatchanges(a, b)
Out[123]: array([1.5, 8.7, 4.5])
In [124]: sumatchangespd(a, b)
Out[124]: array([6. , 8.7])
Divakar 的主要解决方案非常出色,并且在上述所有速度方面都是最好的:
In [144]: def sumatchangesbc(a, b):
...: return np.bincount(b,a)
...:
In [145]: %timeit sumatchangesbc(a, b)
175 µs ± 1.16 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
比我的 numpy 解决方案快一个数量级。