Numpy: 通过多维数组对多维数组进行排序

13

如果这个问题重复或者太基础请不要介意,我是从R语言转到Python/Numpy的。但是我对于反转思考方式感到有些困难。

我有一个n维数组,希望能够使用另外一个n维索引值数组来进行排序。我知道我可以用循环实现,但是貌似应该有更加简洁的Numpyonic方法可以解决这个问题。下面是我的示例代码,其中n=2:

a1 = random.standard_normal(size=[2,5]) 
index = array([[0,1,2,4,3] , [0,1,2,3,4] ]) 

现在我有一个由随机数组成的 2 x 5 数组和一个 2 x 5 的索引。我已经大概阅读了关于 take() 的帮助十次,但是我的大脑还没有完全领会。

我认为这样做可能会让我理解它:

take(a1, index)

array([[ 0.29589188, -0.71279375, -0.18154864, -1.12184984,  0.25698875],
       [ 0.29589188, -0.71279375, -0.18154864,  0.25698875, -1.12184984]])
但显然这只是重新排列了第一个元素(我猜测是因为扁平化)。有没有什么提示可以帮助我从现在的状态到达一个解决方案,该方案通过索引的元素0对a1的元素0进行排序...直到元素n?

所以如果我理解正确,您想使用index中每行的索引来重新排序a1的每个“行”?换句话说,如果您是1D,则为a1.take(index),但对于每一行都要这样做? - Wes McKinney
是的。因此,按照a1的第一行排序,其中第一行按索引排序,第二行按索引排序。随着a1增长到n维,索引也会增长。 - JD Long
2个回答

13

我还想不出如何在N维中解决这个问题,但是这里有一个2D版本:

>>> a = np.random.standard_normal(size=(2,5))
>>> a
array([[ 0.72322499, -0.05376714, -0.28316358,  1.43025844, -0.90814293],
       [ 0.7459107 ,  0.43020728,  0.05411805, -0.32813465,  2.38829386]])
>>> i = np.array([[0,1,2,4,3],[0,1,2,3,4]]) 
>>> a[np.arange(a.shape[0])[:,np.newaxis],i]
array([[ 0.72322499, -0.05376714, -0.28316358, -0.90814293,  1.43025844],
       [ 0.7459107 ,  0.43020728,  0.05411805, -0.32813465,  2.38829386]])

以下是N维版本:

>>> a[list(np.ogrid[[slice(x) for x in a.shape]][:-1])+[i]]

以下是它的工作原理:

好的,让我们从一个三维数组开始进行说明。

>>> import numpy as np
>>> a = np.arange(24).reshape((2,3,4))
>>> a
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])
您可以通过以下方式指定每个轴的索引来访问此数组的元素:
>>> a[0,1,2]
6
这相当于a[0][1][2],如果我们在处理列表而不是数组时,访问同一元素也是这样做的。
NumPy允许您在切片数组时变得更加高级:
>>> a[[0,1],[1,1],[2,2]]
array([ 6, 18])
>>> a[[0,1],[1,2],[2,2]]
array([ 6, 22])
这些例子相当于 [a[0][1][2],a [1][1][2]][a[0][1][2],a[1][2][2]] (如果我们正在处理列表)。
甚至可以省略重复的索引,numpy会理解你想要的。例如,上面的例子可以等效地写成:
>>> a[[0,1],1,2]
array([ 6, 18])
>>> a[[0,1],[1,2],2]
array([ 6, 22])
在每个维度中,您使用的数组(或列表)的形状只会影响返回的数组的形状。换句话说,numpy 并不关心您正在尝试使用一个形状为 (2,3,4) 的数组对其进行索引,除了它将向您返回一个形状为 (2,3,4) 的数组。例如:
>>> a[[[0,0],[0,0]],[[0,0],[0,0]],[[0,0],[0,0]]]
array([[0, 0],
       [0, 0]])
在这种情况下,我们一遍又一遍地获取相同的元素a [0,0,0],但numpy返回一个与我们传入的形状相同的数组。
好的,接下来是你的问题。你想使用你的index数组中的数字沿着最后一个轴对数组进行索引。因此,对于你问题中的示例,你想要的是[[a [0,0],a [0,1],a [0,2],a [0,4],a [0,3]],a [1,0],a [1,1],... 正如我之前所说,你的索引数组是多维的,并不能告诉numpy从哪里提取这些索引;它只指定输出数组的形状。因此,在你的示例中,你需要告诉numpy前5个值应该从a [0]中提取,而后5个值应该从a [1]中提取。很容易!
>>> a[[[0]*5,[1]*5],index]

在N维中会变得复杂,但我们可以先以我之前定义的三维数组a为例。假设我们有以下索引数组:

>>> i = np.array(range(4)[::-1]*6).reshape(a.shape)
>>> i
array([[[3, 2, 1, 0],
        [3, 2, 1, 0],
        [3, 2, 1, 0]],

       [[3, 2, 1, 0],
        [3, 2, 1, 0],
        [3, 2, 1, 0]]])

所以,这些值都是针对最后一个轴的索引。我们需要告诉numpy从第一和第二个轴上取哪些索引;也就是说,我们需要告诉numpy,第一个轴的索引是:

i1 = [[[0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]],

      [[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]]]

第二轴的索引是:

i2 = [[[0, 0, 0, 0],
       [1, 1, 1, 1],
       [2, 2, 2, 2]],

      [[0, 0, 0, 0],
       [1, 1, 1, 1],
       [2, 2, 2, 2]]]

然后我们可以这样做:

>>> a[i1,i2,i]
array([[[ 3,  2,  1,  0],
        [ 7,  6,  5,  4],
        [11, 10,  9,  8]],

       [[15, 14, 13, 12],
        [19, 18, 17, 16],
        [23, 22, 21, 20]]])

生成i1i2的方便numpy函数称为np.mgrid。在我的答案中,我使用了np.ogrid,因为我之前提到的numpy的魔法使它在这种情况下是等效的。

希望这能有所帮助!


我认为你已经完美地实现了我的想法。非常感谢!不想太贪心,但你能解释一下多维版本是如何工作的吗?我一直在尝试,但还没有理解它的操作。 - JD Long
没问题。我添加了一份解释,顺便说一下,写这个解释花的时间比找到答案还要长! - user545424
您,先生,应该得到一枚奖章!感谢您提供如此出色的答案。 - JD Long

3

今天我进一步尝试后发现,如果我在 take 方法中使用 mapper 函数,就可以像这样简单地解决二维版本:

a1 = random.standard_normal(size=[2,5]) 
index = array([[0,1,2,4,3] , [0,1,2,3,4] ]) 
map(take, a1, index)

我需要对a1中的每个元素进行map()take()操作。

当然,被接受的答案解决了n维版本。但是回顾一下,我确定我实际上不需要n维解决方案,只需要2D版本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接