根据给定索引进行就地numpy数组排序

时间:2014-10-07 15:34:24

标签: python numpy

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

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

让我们说我想根据旧数组的以下索引求最后一个维度:

new_order = [1,2,0]

我希望能够说:

a[:,:,new_order] = a

但这不符合预期。建议?

3 个答案:

答案 0 :(得分:6)

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也很重要。例如

b = a.T # a transpose

a[:]= b行将重新排序abdata继续共享相同的a=。使用b时,a保持不变。 b和{{1}}现已脱钩。

答案 1 :(得分:1)

不幸的是,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版本的内存消耗也非常高。自定义就地重新排序使用的金额可以忽略不计。

答案 2 :(得分:0)

你在这里,

a = a[:, :, new_order]

此外,对于Matlab用户的页面,这里有几个'numpy',当我开始使用时,我发现它很有用:

http://wiki.scipy.org/NumPy_for_Matlab_Users

http://mathesaurus.sourceforge.net/matlab-numpy.html