NumPy 数组的间隔 argmax,间隔由另一个数组定义

Intervaled argmax for a NumPy array with intervals defined by another array

我有一个一维数组,其中包含已排序的非唯一数字。他们重复的次数是随机的。 它与具有相同大小的权重数组相关联。对于给定的相同元素系列,相关联的权重系列也可能有也可能没有重复元素,并且在整个权重数组中,可能有也可能没有重复元素。例如:

pos     = np.array([3, 3, 7, 7, 9, 9, 9, 10, 10])
weights = np.array([2, 10, 20, 8, 5, 7, 15, 7, 2])

我需要提取 pos 的唯一元素数组,但唯一元素是权重最大的元素。

我提出的工作解决方案涉及循环:

pos     = np.array([3, 3, 7, 7, 9, 9, 9, 10, 10])
weights = np.array([2, 10, 20, 8, 5, 7, 15, 7, 2])
# Get the number of occurences of the elements in pos but throw away the unique array, it's not the one I want.
_, ucounts = np.unique(pos, return_counts=True)
# Initialize the output array.
unique_pos_idx = np.zeros([ucounts.size], dtype=np.uint32)

last = 0
for i in range(ucounts.size):
    maxpos = np.argmax( weights[last:last+ucounts[i]] )
    unique_pos_idx[i] = last + maxpos
    last += ucounts[i]

# Result is:
# unique_pos_idx = [1 2 6 7]

但我没有使用很多 Python 语言或 Numpy(除了使用 numpy 数组)所以我想知道是否有更多 Pythonesque and/or甚至比上面的 Cython 版本更有效的解决方案?

谢谢

这是一种矢量化方法 -

sidx = np.lexsort([weights,pos])
out = sidx[np.r_[np.flatnonzero(pos[1:] != pos[:-1]), -1]]

可能的性能改进 -

1] 使用 scaling -

获取排序索引 sidx 的更快方法
sidx = (pos*(weights.max()+1) + weights).argsort()

2] 使用 boolean-indexing 可以使最后的索引更快,特别是在处理许多这样的 intervals/groupings -

out = sidx[np.concatenate((pos[1:] != pos[:-1], [True]))]

运行时测试

所有方法:

def org_app(pos, weights):
    _, ucounts = np.unique(pos, return_counts=True)
    unique_pos_idx = np.zeros([ucounts.size], dtype=np.uint32)    
    last = 0
    for i in range(ucounts.size):
        maxpos = np.argmax( weights[last:last+ucounts[i]] )
        unique_pos_idx[i] = last + maxpos
        last += ucounts[i]
    return unique_pos_idx

def vec_app(pos, weights):
    sidx = np.lexsort([weights,pos])
    return sidx[np.r_[np.flatnonzero(pos[1:] != pos[:-1]), -1]]

def vec_app_v2(pos, weights):
    sidx = (pos*(weights.max()+1) + weights).argsort()
    return sidx[np.concatenate((pos[1:] != pos[:-1], [True]))]

时间和验证 -

对于设置,让我们使用示例并通过缩放将其平铺 10000 次,因为我们打算创建 1000 倍的间隔数。另外,让我们在 weights 中使用唯一数字,这样 argmax 索引就不会被相同的数字混淆:

In [155]: # Setup input
     ...: pos = np.array([3, 3, 7, 7, 9, 9, 9, 10, 10,])
     ...: pos = (pos + 10*np.arange(10000)[:,None]).ravel()
     ...: weights = np.random.choice(10*len(pos), size=len(pos), replace=0)
     ...: 
     ...: print np.allclose(org_app(pos, weights), vec_app(pos, weights))
     ...: print np.allclose(org_app(pos, weights), vec_app_v2(pos, weights))
     ...: 
True
True

In [156]: %timeit org_app(pos, weights)
     ...: %timeit vec_app(pos, weights)
     ...: %timeit vec_app_v2(pos, weights)
     ...: 
10 loops, best of 3: 56.4 ms per loop
100 loops, best of 3: 14.8 ms per loop
1000 loops, best of 3: 1.77 ms per loop

In [157]: 56.4/1.77 # Speedup with vectorized one over loopy
Out[157]: 31.864406779661017