按给定索引就地numpy数组排序

bxfogqkk  于 2023-01-13  发布在  其他
关注(0)|答案(3)|浏览(129)

有一些问题很接近,但我还没有找到一个具体的答案。我试图沿着给定的轴对一个numpy 3D数组进行一些就地排序。我不想简单排序,我想根据我自己的索引重新排序数组。例如

a = np.random.rand((3,3,3))

假设我想根据旧数组的以下索引重新排序最后一个维度:

new_order = [1,2,0]

我希望能够说:

a[:,:,new_order] = a

但这并不像预期的那样。建议?

ewm0tg9j

ewm0tg9j1#

np.ndarray.sort是唯一一个声称已经就位的排序,它没有给予您太多的控制权。
将顺序索引放在右边是可行的--但可能会给予不可预测的结果。显然,它正在进行某种顺序赋值,而在左边较早的赋值可能会影响右边的值。

In [719]: a=np.arange(12).reshape(3,4)
In [720]: a[:,[0,1,3,2]]=a
In [721]: a
Out[721]: 
array([[ 0,  1,  2,  2],
       [ 4,  5,  6,  6],
       [ 8,  9, 10, 10]])

要按预期进行这种赋值,需要某种缓冲。

In [728]: a[:,[0,1,3,2]]=a.copy()
In [729]: a
Out[729]: 
array([[ 0,  1,  3,  2],
       [ 4,  5,  7,  6],
       [ 8,  9, 11, 10]])

右边的索引绕过了这个问题,但这不是在原处,变量a指向一个新对象。

In [731]: a=a[:,[0,1,3,2]]
In [732]: a
Out[732]: 
array([[ 0,  1,  3,  2],
       [ 4,  5,  7,  6],
       [ 8,  9, 11, 10]])

然而,使用[:]赋值可以解决这个问题:

In [738]: a=np.arange(12).reshape(3,4)
In [739]: a.__array_interface__
Out[739]: 
{'data': (181868592, False),   # 181... is the id of the data buffer
 'descr': [('', '<i4')],
 'shape': (3, 4),
 'strides': None,
 'typestr': '<i4',
 'version': 3}
In [740]: a[:]=a[:,[0,1,3,2]]
In [741]: a.__array_interface__
Out[741]: 
{'data': (181868592, False),  # same data buffer
 'descr': [('', '<i4')],
 'shape': (3, 4),
 'strides': None,
 'typestr': '<i4',
 'version': 3}
In [742]: a
Out[742]: 
array([[ 0,  1,  3,  2],
       [ 4,  5,  7,  6],
       [ 8,  9, 11, 10]])

a.data id是相同的这一事实表明这是一个inplace操作,但是最好用其他索引来测试它,以确保它能做你想要的事情。
但是,“inplace”排序是必要的吗?如果数组非常大,可能需要它来避免内存错误。但是我们必须测试替代方法,看看它们是否有效。
inplace在其他变量使用相同数据时也很重要。

b = a.T # a transpose

使用a[:]=时,b的行将重新排序。ab继续共享同一个data。使用a=时,b不变。ab现在已去耦。

0pizxfdo

0pizxfdo2#

不幸的是,numpy没有针对此问题的内置解决方案,唯一的方法是要么使用一些聪明的赋值语句,要么编写自己的自定义方法。
使用循环检测、一个用于记忆索引的附加集合和一个用于缓存轴的辅助数组,我为此编写了一个定制方法,该方法对于重排序大型ndarrays非常有用:

import numpy as np

def put_at(index, axis=-1, slc=(slice(None),)):
    """Gets the numpy indexer for the given index based on the axis."""
    return (axis < 0)*(Ellipsis,) + axis*slc + (index,) + (-1-axis)*slc

def reorder_inplace(array, new_order, axis=0):
    """
    Reindex (reorder) the array along an axis.

    :param array: The array to reindex.
    :param new_order: A list with the new index order. Must be a valid permutation.
    :param axis: The axis to reindex.
    """
    if np.size(array, axis=axis) != len(new_order):
        raise ValueError(
            'The new order did not match indexed array along dimension %{0}; '
            'dimension is %{1} but corresponding boolean dimension is %{2}'.format(
                axis, np.size(array, axis=axis), len(new_order)
            )
        )

    visited = set()
    for index, source in enumerate(new_order):
        if index not in visited and index != source:
            initial_values = np.take(array, index, axis=axis).copy()

            destination = index
            visited.add(destination)
            while source != index:
                if source in visited:
                    raise IndexError(
                        'The new order is not unique; '
                        'duplicate found at position %{0} with value %{1}'.format(
                            destination, source
                        )
                    )

                array[put_at(destination, axis=axis)] = array.take(source, axis=axis)

                destination = source
                source = new_order[destination]

                visited.add(destination)
            array[put_at(destination, axis=axis)] = initial_values

示例:

In[4]: a = np.arange(15).reshape(3, 5)
In[5]: a
Out[5]: 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])

在轴0上重新排序:

In[6]: reorder_inplace(a, [2, 0, 1], axis=0)
In[7]: a
Out[7]: 
array([[10, 11, 12, 13, 14],
       [ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9]])

在轴1上重新排序:

In[10]: reorder_inplace(a, [3, 2, 0, 4, 1], axis=1)
In[11]: a
Out[11]: 
array([[ 3,  2,  0,  4,  1],
       [ 8,  7,  5,  9,  6],
       [13, 12, 10, 14, 11]]

1000 x 1000小阵列的时序和存储器

In[5]: a = np.arange(1000 * 1000).reshape(1000, 1000)
In[6]: %timeit reorder_inplace(a, np.random.permutation(1000))
8.19 ms ± 18.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In[7]: %memit reorder_inplace(a, np.random.permutation(1000))
peak memory: 81.75 MiB, increment: 0.49 MiB
In[8]: %timeit a[:] = a[np.random.permutation(1000), :]
3.27 ms ± 9.49 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In[9]: %memit a[:] = a[np.random.permutation(1000), :]
peak memory: 89.56 MiB, increment: 0.01 MiB

对于小阵列,内存消耗没有太大的不同,但numpy版本要快得多。
20000 x 20000的时序和存储器

In[5]: a = np.arange(20000 * 20000).reshape(20000, 20000)
In[6]: %timeit reorder_inplace(a, np.random.permutation(20000))
1.16 s ± 1.39 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In[7]: %memit reorder_inplace(a, np.random.permutation(20000))
peak memory: 3130.77 MiB, increment: 0.19 MiB
In[8]: %timeit a[:] = a[np.random.permutation(20000), :]
1.84 s ± 2.26 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In[9]: %memit a[:] = a[np.random.permutation(20000), :]
peak memory: 6182.80 MiB, increment: 3051.76 MiB

当数组的大小增加一个等级时,numpy版本的速度会慢很多。numpy版本的内存消耗也非常高。自定义就地重新排序使用的内存量可以忽略不计。

4sup72z8

4sup72z83#

给你,

a = a[:, :, new_order]

此外,这里有几个'numpy for Matlab用户'的页面,我发现有用的,当我开始:
Link
http://mathesaurus.sourceforge.net/matlab-numpy.html

相关问题