Python 有效使用numpy_索引输出

Python 有效使用numpy_索引输出,python,numpy,numpy-indexed,Python,Numpy,Numpy Indexed,我想对大型集合(约1m行)上由第一列聚集的第二列的子集执行计算。是否有一种有效(和/或矢量化)的方法来使用group\u bybynumpy\u index的输出,以便为这些计算的输出添加一个新列?在上面的sum示例中,我希望生成以下输出 如果有一种有效的方法可以做到这一点,而不首先使用numpy\u index,那也会非常有用 >>> import numpy_indexed as npi >>> import numpy as np >>>

我想对大型集合(约1m行)上由第一列聚集的第二列的子集执行计算。是否有一种有效(和/或矢量化)的方法来使用
group\u by
by
numpy\u index
的输出,以便为这些计算的输出添加一个新列?在上面的
sum
示例中,我希望生成以下输出

如果有一种有效的方法可以做到这一点,而不首先使用
numpy\u index
,那也会非常有用

>>> import numpy_indexed as npi
>>> import numpy as np
>>> a = np.array([[0,0,1,1,2,2], [4,4,8,8,10,10]]).T
>>> a
array([[ 0,  4],
       [ 0,  4],
       [ 1,  8],
       [ 1,  8],
       [ 2, 10],
       [ 2, 10]])
>>> npi.group_by(a[:, 0]).sum(a[:,1])

(array([0, 1, 2]), array([ 8, 16, 20], dtype=int32))
一种方法是生成这些唯一标记和区间移位索引,然后用于
区间求和
-

array([[ 0,  4,  8],
       [ 0,  4,  8],
       [ 1,  8, 16],
       [ 1,  8, 16],
       [ 2, 10, 20],
       [ 2, 10, 20]])
另一种避免使用
np.unique
并可能对性能有益的方法如下-

_,idx,tags = np.unique(a[:,0], return_index=1, return_inverse=1)
out = np.c_[a, np.add.reduceat(a[:,1],idx)[tags]]
为了进一步提高性能,我们应该使用
np.bincount
。因此,
np.add.reduceat(a[:,1],idx)
可以替换为
np.bincount(tags,a[:,1])

样本运行-

idx = np.r_[0,np.flatnonzero(a[1:,0] > a[:-1,0])+1]
tag_arr = np.zeros(a.shape[0], dtype=int)
tag_arr[idx[1:]] = 1
tags = tag_arr.cumsum()
out = np.c_[a, np.add.reduceat(a[:,1],idx)[tags]]
现在,列出的方法假设第一列已经排序。如果不是这样,我们需要按第一列对数组进行排序
argsort
,然后使用建议的方法。因此,对于未排序的案例,我们需要以下内容作为预处理-

In [271]: a    # Using a more generic sample
Out[271]: 
array([[11,  4],
       [11,  4],
       [14,  8],
       [14,  8],
       [16, 10],
       [16, 10]])

In [272]: _,idx,tags = np.unique(a[:,0], return_index=1, return_inverse=1)

In [273]: np.c_[a, np.add.reduceat(a[:,1],idx)[tags]]
Out[273]: 
array([[11,  4,  8],
       [11,  4,  8],
       [14,  8, 16],
       [14,  8, 16],
       [16, 10, 20],
       [16, 10, 20]])]

对抗
np.unique

让我们根据内置的
np.unique
对自定义
flatnonzero
+
cumsum
方法计时,以创建移位索引:
idx
和基于唯一性的ID/tags:
tags
。对于这种情况,我们事先知道标签列已经排序,我们避免任何排序,就像使用
np.unique
一样。这给了我们性能上的优势。那么,让我们来验证一下

接近-

a = a[a[:,0].argsort()]
使用自定义func运行示例-

def nonzero_cumsum_based(A):
    idx = np.concatenate(( [0] ,np.flatnonzero(A[1:] > A[:-1])+1 ))
    tags = np.zeros(len(A), dtype=int)
    tags[idx[1:]] = 1
    np.cumsum(tags, out = tags)
    return idx, tags

def unique_based(A):
    _,idx,tags = np.unique(A, return_index=1, return_inverse=1)
    return idx, tags
时间安排-

In [438]: a
Out[438]: 
array([[11,  4],
       [11,  4],
       [14,  8],
       [14,  8],
       [16, 10],
       [16, 10]])

In [439]: idx, tags = nonzero_cumsum_based(a[:,0])

In [440]: idx
Out[440]: array([0, 2, 4])

In [441]: tags
Out[441]: array([0, 0, 1, 1, 2, 2])

每个索引对象都有一个反向属性,将减少的值映射回其原始范围;为了举例说明,我们可以写:

In [444]: a = np.c_[np.sort(randi(10,10000,(100000))), randi(0,10000,(100000))]

In [445]: %timeit unique_based(a[:,0])
100 loops, best of 3: 4.3 ms per loop

In [446]: %timeit nonzero_cumsum_based(a[:,0])
1000 loops, best of 3: 486 µs per loop

In [447]: a = np.c_[np.sort(randi(10,10000,(1000000))), randi(0,10000,(1000000))]

In [448]: %timeit unique_based(a[:,0])
10 loops, best of 3: 50.2 ms per loop

In [449]: %timeit nonzero_cumsum_based(a[:,0])
100 loops, best of 3: 3.98 ms per loop
一般来说,numpy_索引的来源可以启发我们如何执行此类常见操作;例如,group_by.var面临着同样的问题,将每个组的平均值广播回它所组成的组的每个元素,以计算每个组中的错误。但更好的辅导当然也不会有什么坏处


你能对你试图解决的问题给出更高层次的描述吗?当您能够更轻松地思考npi方便使用的设计模式时,您可能会从更高的级别进一步简化代码。

第一列是否已经排序?此外,它是否总是以
0
开头,并且只有序列号?带有类别的列总是排序的,但组号不一定从
0
开始,而是递增的数字。谢谢谢谢,我可以问一下如何使用解包的第一个值,
\uu
。@Tony这一步有三个输出。有了这个
,我们只是不存储它,也就是说,我们不需要第一次输出。谢谢您的回答。如果我在第一篇文章中定义
a
,然后运行上面的两行,第一行返回
npi
对象,但第二行返回
TypeError:只有整数标量数组可以转换为标量索引
。我相信答案对你来说是显而易见的,但对我来说不是,所以有什么帮助吗?拉拉队,我确实犯了个小错误;仍然没有实际运行我的代码,但我相当肯定它现在应该是正确的!Eelco Hoogendoorn
npi
非常有效,感谢您在这里发布。我有一个一维的
np.array
用于我的组,一个二维的
array
用于我的xs,我非常喜欢这样一个事实,即当我做
,means=groups.means(xs\u-df)时,它会拾取第二个维度并分别为每列执行;意思是[groups.inverse]
我有个问题;因为我的集合相当大,由0/1个假人组成(.5m x 4k),所以计算每组每列的平均值需要相当长的时间。是否有可能利用
npi
scipy
的稀疏矩阵使其运行更快?这听起来比这里的空间所允许的要复杂得多;也许你可以把你的问题做得比原来的问题多一点?
index = npi.as_index(keys)
unique_keys = index.unique
unique_keys[index.inverse] == keys  # <- should be all true
groups = npi.group_by(a[:, 0])
unique, sums = groups.sum(a[:, 1])
new_column = sums[groups.inverse]