提问者:小点点

是否可以根据另一个向量的整数值组合(添加)一个向量的值


我正在尝试根据来自另一个向量的整数值添加向量的浮点值。

例如,如果我有:

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]

共3个答案

匿名用户

方法#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解决方案,您可以检查bnp. 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