我正在尝试根据来自另一个向量的整数值添加向量的浮点值。
例如,如果我有:
import numpy as np
a = np.array([0.1,0.2,0.3,0.4,0.5,0.6,07.3,0.8,0.9,1.,1.2,1.4])
b = np.array([0,0,0,0,0,1,1,1,2,2,2,2]).astype(int)
我想把a向量的5个第一个值加在一起(因为b的5个第一个值是0),3个下一个值加在一起(因为b的3个下一个值是1)等等。所以最后我希望有
c = function(a,b)
c = [0.1+0.2+0.3+0.4+0.5, 0.6+7.3+0.8, 0.9+1.+1.2+1.4]
方法#1:我们可以使用np. bincount
,其中b
作为bins,a
作为权重数组-
In [203]: np.bincount(b,a)
Out[203]: array([1.5, 8.7, 4.5])
方法#2:另一种利用矩阵乘法
-
In [210]: (b == np.arange(b.max()+1)[:,None]).dot(a)
Out[210]: array([1.5, 8.7, 4.5])
对于纯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])
我肯定更喜欢使用Pandasgroupby
作为大多数设置中使用的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的熊猫解决方案,这些将对所有具有相同标签的东西求和,而不管位置如何。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解决方案快一个数量级。
您可以使用基于NumPy构建的Pandas:
import pandas as pd
c = pd.Series(a).groupby(b).sum().values
# array([ 1.5, 8.7, 4.5])
或者更冗长的选择:
c = pd.DataFrame({'a': a, 'b': b})\
.groupby('b')['a'].sum().values