是否可以根据另一个向量的整数值组合(添加)一个向量的值
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.bincount
,b
作为 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 解决方案,您可以检查 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 解决方案快一个数量级。
【讨论】:
很好的答案。这实际上比 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 替代方案,这当然是意图。以上是关于是否可以根据另一个向量的整数值组合(添加)一个向量的值的主要内容,如果未能解决你的问题,请参考以下文章