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

Posted

技术标签:

【中文标题】是否可以根据另一个向量的整数值组合(添加)一个向量的值【英文标题】:Is it possible to combine (add) values of a vector according to integer value of another vector 【发布时间】:2019-03-08 11:40:37 【问题描述】:

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

例如,如果我有:

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 的下一个值是 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】:

方法#1:我们可以使用np.bincountb 作为 bin,a 作为权重数组 -

In [203]: np.bincount(b,a)
Out[203]: array([1.5, 8.7, 4.5])

方法 #2: 另一种利用 matrix-multiplication -

In [210]: (b == np.arange(b.max()+1)[:,None]).dot(a)
Out[210]: array([1.5, 8.7, 4.5])

【讨论】:

你从哪里得到这些东西? bincount?奇妙!不过,矩阵乘法对于大型数组来说是个坏主意。但是bincount 比我的解决方案快一个数量级。 是的,这很棒。此外,这真的很快!谢谢【参考方案2】:

对于纯 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])

我肯定更喜欢使用 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 解决方案快一个数量级。

【讨论】:

很好的答案。这实际上比 Pandas 更有效吗?我希望它应该是因为它没有 pd.Series 对象的开销。 我打算计时看看,我也很好奇。不过np.insert() 的操作并不便宜。 @jpp 添加了计时---numpy 更快。实际上,我发现自己经常这样做(通过df['col'].diff()np.diff() 检测状态变化)。我目前更喜欢 pandas,因为它保持相同的长度,并且您可以指定填充值等,但由于 numpy 很快就会有它,我想我会很好! 你能尝试更大的数组(例如 100,000 个随机值)吗?当时间以微秒为单位时,我觉得它们被固定成本扭曲了。 (而且 Pandas 肯定有更多的固定成本。) @jpp 同意并且公平点,用一些新数组进行了编辑。大约 3.5 倍的加速,这似乎更合理。【参考方案3】:

您可以使用基于 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

【讨论】:

不需要DataFrame: pd.Series(a).groupby(b).sum().values 也可以工作【参考方案4】:

仅使用 numpy

c = [sum(a[b==i]) for i in sorted(set(b))]

注意:正如@jpp 指出的那样,写np.unique 而不是sorted(set(b)) 可能更好

【讨论】:

如果b = np.array([2,2,0,0,0,1,1,1,2,2,2,2]).astype(int) 那么输出会出错对吧? @RahulKP:为什么?什么是正确的?他的例子有连续的、有序的值。他没有具体说明否则会发生什么 他指定的值应该是在一起的。在您的情况下,您没有考虑连续值。 请参阅 this answer,了解为什么不应将 sum 之类的内置函数与 NumPy 数组一起使用。【参考方案5】:

使用itertools.groupby的原生python解决方案

from itertools import groupby

groups = (group for key, group in groupby(zip(a, b), key=lambda x: x[1]))
totals = [sum(a for a, b in group) for group in groups]
[1.5, 8.7, 4.5]

一个 numpy 替代方案,类似于 @blue_note 的解决方案,但使用 numpy 的强大功能而不是原生 python

[(a * (b == group)).sum() for group in np.unique(b)]

这仅适用于 b = np.array([2,2,0,0,0,1,1,1,2,2,2,2]) 不包含 2 个独特的组 2

【讨论】:

请参阅 this answer,了解为什么不应将 sum / zip 之类的内置函数与 NumPy 数组一起使用。 numpy 替代方案使用numpy.array.sum,而不是内置 是的,确实如此,认为它现在具有 O(m * n) 复杂度,其中 m 是数字ids 和 n 是值的行数。也就是说,我希望我的评论鼓励人们使用 NumPy 替代方案,这当然是意图。

以上是关于是否可以根据另一个向量的整数值组合(添加)一个向量的值的主要内容,如果未能解决你的问题,请参考以下文章

将向量的每个元素与 R 中的另一个向量组合

使用 SIMD 根据另一个向量位值计算值的乘积

根据另一个不同大小和类型的向量对点向量进行排序

向量与子向量长度 n 的组合

2.7 数值分析: 向量的范数

如何将值分配到向量中