给定一个自我索引(不确定这是否是正确的术语)numpy数组,例如:
a = np.array([3, 2, 0, 1])
这表示此permutation(=>
是箭头):
0 => 3
1 => 2
2 => 0
3 => 1
我正在尝试创建一个表示逆变换的数组,而不是在python中“手动”执行它,也就是说,我想要一个纯 numpy解决方案。在上述情况下我想要的结果是:
array([2, 3, 1, 0])
相当于
0 <= 3 0 => 2
1 <= 2 or 1 => 3
2 <= 0 2 => 1
3 <= 1 3 => 0
看起来很简单,但我想不出怎么做。我试过谷歌搜索,但没有找到任何相关的。
答案 0 :(得分:29)
排序在这里是一种过度杀伤。这只是一种具有恒定内存要求的单程线性时间算法:
from __future__ import print_function
import numpy as np
p = np.array([3, 2, 0, 1])
s = np.empty(p.size, dtype=np.int32)
for i in np.arange(p.size):
s[p[i]] = i
print('s =', s)
上面的代码打印
s = [2 3 1 0]
根据要求。
答案的其余部分涉及上述for
循环的有效矢量化。 如果您只是想知道解决方案,请跳到此答案的末尾。
(2014年8月27日的原始答案;时间对NumPy 1.8有效。稍后会更新NumPy 1.11。)
单程线性时间算法预计比np.argsort
快;有趣的是,上述s[p] = xrange(p.size)
循环的平凡向量化(for
,see index arrays)实际上比np.argsort
略慢p.size < 700 000
(好吧,在我的身上)机器,你的里程 会有所不同):
import numpy as np
def np_argsort(p):
return np.argsort(p)
def np_fancy(p):
s = np.zeros(p.size, p.dtype) # np.zeros is better than np.empty here, at least on Linux
s[p] = xrange(p.size)
return s
def create_input(n):
np.random.seed(31)
indices = np.arange(n, dtype = np.int32)
return np.random.permutation(indices)
来自我的IPython笔记本:
p = create_input(700000)
%timeit np_argsort(p)
10 loops, best of 3: 72.7 ms per loop
%timeit np_fancy(p)
10 loops, best of 3: 70.2 ms per loop
最终,渐近复杂性开始(单O(n log n)
与argsort
对O(n)
进行单次通过算法,并且单次通过算法在足够大{n = p.size
之后会持续更快{ {1}}(我的机器上的阈值约为700k)。
但是,使用np.put
对上述for
循环进行矢量化的方法并不那么简单:
def np_put(p):
n = p.size
s = np.zeros(n, dtype = np.int32)
i = np.arange(n, dtype = np.int32)
np.put(s, p, i) # s[p[i]] = i
return s
给出n = 700 000
(与上面大小相同):
p = create_input(700000)
%timeit np_put(p)
100 loops, best of 3: 12.8 ms per loop
这是一个不错的5.6倍加速,几乎没有!
公平地说,np.argsort
对于较小的np.put
(我的计算机上的临界点约为n
)仍然优于n = 1210
方法:
p = create_input(1210)
%timeit np_argsort(p)
10000 loops, best of 3: 25.1 µs per loop
%timeit np_fancy(p)
10000 loops, best of 3: 118 µs per loop
%timeit np_put(p)
10000 loops, best of 3: 25 µs per loop
这很可能是因为我们使用np.arange()
方法分配并填写了一个额外的数组(在np_put
调用中)。
虽然您没有要求提供Cython解决方案,但出于好奇,我还使用typed memoryviews计算了以下Cython解决方案:
import numpy as np
cimport numpy as np
def in_cython(np.ndarray[np.int32_t] p):
cdef int i
cdef int[:] pmv
cdef int[:] smv
pmv = p
s = np.empty(p.size, dtype=np.int32)
smv = s
for i in xrange(p.size):
smv[pmv[i]] = i
return s
时序:
p = create_input(700000)
%timeit in_cython(p)
100 loops, best of 3: 2.59 ms per loop
因此,np.put
解决方案仍然没有尽可能快(对于此输入大小运行12.8 ms; argsort需要72.7 ms)。
def invert_permutation(p):
s = np.empty(p.size, p.dtype)
s[p] = np.arange(p.size)
return s
时序:
p = create_input(880)
%timeit np_argsort(p)
100000 loops, best of 3: 11.6 µs per loop
%timeit invert_permutation(p)
100000 loops, best of 3: 11.5 µs per loop
确实有了显着改善!
总而言之,我会选择
def invert_permutation(p):
'''The argument p is assumed to be some permutation of 0, 1, ..., len(p)-1.
Returns an array s, where s[i] gives the index of i in p.
'''
s = np.empty(p.size, p.dtype)
s[p] = np.arange(p.size)
return s
代码清晰度的方法。在我看来,它不像argsort
那么模糊,对于大输入大小也更快。如果速度成为一个问题,我会选择Cython解决方案。
答案 1 :(得分:26)
p
的排列np.arange(n)
的倒数是排序s
的索引数组p
,即
p[s] == np.arange(n)
一定都是真的。这样的s
正是np.argsort
返回的内容:
>>> p = np.array([3, 2, 0, 1])
>>> np.argsort(p)
array([2, 3, 1, 0])
>>> p[np.argsort(p)]
array([0, 1, 2, 3])
答案 2 :(得分:8)
我想为larsmans正确答案提供更多背景知识。当您使用permutation by a matrix的表示时,可以找到原因为什么argsort
正确的原因。置换矩阵 P
的数学优点是矩阵“对向量进行操作”,即置换矩阵乘以向量置换向量。
你的排列看起来像:
import numpy as np
a = np.array([3,2,0,1])
N = a.size
rows = np.arange(N)
P = np.zeros((N,N),dtype=int)
P[rows,a] = 1
[[0 0 0 1]
[0 0 1 0]
[1 0 0 0]
[0 1 0 0]]
给定置换矩阵,我们可以通过乘以它的倒数P^-1
来“撤消”乘法。置换矩阵的美妙之处在于它们是正交的,因此P*P^(-1)=I
,或者换句话说P(-1)=P^T
,逆是转置。这意味着我们可以使用转置矩阵的索引来找到倒置置换向量:
inv_a = np.where(P.T)[1]
[2 3 1 0]
如果您考虑它,与找到对P
列进行排序的索引完全相同!