【问题标题】:Generalise slicing operation in a NumPy array在 NumPy 数组中泛化切片操作
【发布时间】:2018-02-18 11:27:45
【问题描述】:

此问题基于this 较早的问题:

给定一个数组:

In [122]: arr = np.array([[1, 3, 7], [4, 9, 8]]); arr
Out[122]: 
array([[1, 3, 7],
       [4, 9, 8]])

并给出它的索引:

In [127]: np.indices(arr.shape)
Out[127]: 
array([[[0, 0, 0],
        [1, 1, 1]],

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

我怎样才能将它们整齐地堆叠在一起以形成 一个新的二维数组?这就是我想要的:

array([[0, 0, 1],
       [0, 1, 3],
       [0, 2, 7],
       [1, 0, 4],
       [1, 1, 9],
       [1, 2, 8]])

This solution by Divakar 是我目前用于二维数组的方法:

def indices_merged_arr(arr):
    m,n = arr.shape
    I,J = np.ogrid[:m,:n]
    out = np.empty((m,n,3), dtype=arr.dtype)
    out[...,0] = I
    out[...,1] = J
    out[...,2] = arr
    out.shape = (-1,3)
    return out

现在,如果我想传递一个 3D 数组,我需要修改这个函数:

def indices_merged_arr(arr):
    m,n,k = arr.shape   # here
    I,J,K = np.ogrid[:m,:n,:k]   # here
    out = np.empty((m,n,k,4), dtype=arr.dtype)   # here
    out[...,0] = I
    out[...,1] = J
    out[...,2] = K     # here
    out[...,3] = arr
    out.shape = (-1,4)   # here
    return out

但是这个函数现在只适用于 3D 数组 - 我不能将 2D 数组传递给它。

有没有某种方法可以将其概括为适用于任何维度?这是我的尝试:

def indices_merged_arr_general(arr):
    tup = arr.shape   
    idx = np.ogrid[????]   # not sure what to do here....
    out = np.empty(tup + (len(tup) + 1, ), dtype=arr.dtype) 
    for i, j in enumerate(idx):
        out[...,i] = j
    out[...,len(tup) - 1] = arr
    out.shape = (-1, len(tup)
    return out

我遇到了这条线的问题:

idx = np.ogrid[????]   

我怎样才能让它工作?

【问题讨论】:

  • 看起来像np.ndenumerate
  • @hpaulj 这看起来是另一个不错的选择。我现在正在想办法解开这些索引。
  • @coldspeed 太糟糕了,ndenumerate 的例子被删除了,用 int 表示 i,j (x,y) 和 k (z) 表示 float 构造一个索引数组是最简单的。
  • @NaN 我删除了我的答案,因为 hpaulj 发布了他自己的答案。

标签: python arrays numpy indexing


【解决方案1】:

这是处理通用 ndarray 的扩展 -

def indices_merged_arr_generic(arr, arr_pos="last"):
    n = arr.ndim
    grid = np.ogrid[tuple(map(slice, arr.shape))]
    out = np.empty(arr.shape + (n+1,), dtype=np.result_type(arr.dtype, int))

    if arr_pos=="first":
        offset = 1
    elif arr_pos=="last":
        offset = 0
    else:
        raise Exception("Invalid arr_pos")        

    for i in range(n):
        out[...,i+offset] = grid[i]
    out[...,-1+offset] = arr
    out.shape = (-1,n+1)

    return out

样本运行

二维案例:

In [252]: arr
Out[252]: 
array([[37, 32, 73],
       [95, 80, 97]])

In [253]: indices_merged_arr_generic(arr)
Out[253]: 
array([[ 0,  0, 37],
       [ 0,  1, 32],
       [ 0,  2, 73],
       [ 1,  0, 95],
       [ 1,  1, 80],
       [ 1,  2, 97]])

In [254]: indices_merged_arr_generic(arr, arr_pos='first')
Out[254]: 
array([[37,  0,  0],
       [32,  0,  1],
       [73,  0,  2],
       [95,  1,  0],
       [80,  1,  1],
       [97,  1,  2]])

3D 案例:

In [226]: arr
Out[226]: 
array([[[35, 45, 33],
        [48, 38, 20],
        [69, 31, 90]],

       [[73, 65, 73],
        [27, 51, 45],
        [89, 50, 74]]])

In [227]: indices_merged_arr_generic(arr)
Out[227]: 
array([[ 0,  0,  0, 35],
       [ 0,  0,  1, 45],
       [ 0,  0,  2, 33],
       [ 0,  1,  0, 48],
       [ 0,  1,  1, 38],
       [ 0,  1,  2, 20],
       [ 0,  2,  0, 69],
       [ 0,  2,  1, 31],
       [ 0,  2,  2, 90],
       [ 1,  0,  0, 73],
       [ 1,  0,  1, 65],
       [ 1,  0,  2, 73],
       [ 1,  1,  0, 27],
       [ 1,  1,  1, 51],
       [ 1,  1,  2, 45],
       [ 1,  2,  0, 89],
       [ 1,  2,  1, 50],
       [ 1,  2,  2, 74]])

【讨论】:

  • Divakar,据我所知,广义切片是如何工作的?
  • 是的,这是有道理的。令人困惑的一点是ogrid 中的切片,但在玩弄切片之后,它似乎很明显。
  • @cᴏʟᴅsᴘᴇᴇᴅ 是的,我最近通过this post学会了这个技巧。
【解决方案2】:

对于大型数组,AFAIK,senderle's cartesian_product 是使用 NumPy 生成笛卡尔积的最快方法1


In [372]: A = np.random.random((100,100,100))

In [373]: %timeit indices_merged_arr_generic_using_cp(A)
100 loops, best of 3: 16.8 ms per loop

In [374]: %timeit indices_merged_arr_generic(A)
10 loops, best of 3: 28.9 ms per loop

这是我用来进行基准测试的设置。 下面,indices_merged_arr_generic_using_cp 是对 senderle 的 cartesian_product 的修改,在笛卡尔积旁边包含扁平数组:

import numpy as np
import functools

def indices_merged_arr_generic_using_cp(arr):
    """
    Based on cartesian_product
    http://stackoverflow.com/a/11146645/190597 (senderle)
    """
    shape = arr.shape
    arrays = [np.arange(s, dtype='int') for s in shape]
    broadcastable = np.ix_(*arrays)
    broadcasted = np.broadcast_arrays(*broadcastable)
    rows, cols = functools.reduce(np.multiply, broadcasted[0].shape), len(broadcasted)+1
    out = np.empty(rows * cols, dtype=arr.dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    out[start:] = arr.flatten()
    return out.reshape(cols, rows).T

def indices_merged_arr_generic(arr):
    """
    https://stackoverflow.com/a/46135084/190597 (Divakar)
    """
    n = arr.ndim
    grid = np.ogrid[tuple(map(slice, arr.shape))]
    out = np.empty(arr.shape + (n+1,), dtype=arr.dtype)
    for i in range(n):
        out[...,i] = grid[i]
    out[...,-1] = arr
    out.shape = (-1,n+1)
    return out

1请注意,上面我实际上使用了senderle的cartesian_product_transpose。对我来说,这是 最快的版本。对于其他人,包括 senderle,cartesian_product 是 更快。

【讨论】:

  • 我只是在看这个,想知道为什么当我对你的代码进行基准测试时,我在 Divakar 的函数上得到了更快的计时......很奇怪。
  • 我最早的帖子使用 pd.concat 将笛卡尔积合并到 OP 的扁平 DataFrame 中。这不是速度的最佳主意。您可能已经对该版本进行了基准测试。
【解决方案3】:

ndenumerate 迭代元素,而不是其他解决方案中的维度。所以我不指望它会赢得速度测试。但这里有一种使用方式

In [588]:  arr = np.array([[1, 3, 7], [4, 9, 8]])
In [589]: arr
Out[589]: 
array([[1, 3, 7],
       [4, 9, 8]])
In [590]: list(np.ndenumerate(arr))
Out[590]: [((0, 0), 1), ((0, 1), 3), ((0, 2), 7), ((1, 0), 4), ((1, 1), 9), ((1, 2), 8)]

在py3中*解包可以在元组中使用,所以嵌套的元组可以展平:

In [591]: [(*ij,v) for ij,v in np.ndenumerate(arr)]
Out[591]: [(0, 0, 1), (0, 1, 3), (0, 2, 7), (1, 0, 4), (1, 1, 9), (1, 2, 8)]
In [592]: np.array(_)
Out[592]: 
array([[0, 0, 1],
       [0, 1, 3],
       [0, 2, 7],
       [1, 0, 4],
       [1, 1, 9],
       [1, 2, 8]])

它可以很好地推广到更多维度:

In [593]: arr3 = np.arange(24).reshape(2,3,4)
In [594]: np.array([(*ij,v) for ij,v in np.ndenumerate(arr3)])
Out[594]: 
array([[ 0,  0,  0,  0],
       [ 0,  0,  1,  1],
       [ 0,  0,  2,  2],
       [ 0,  0,  3,  3],
       [ 0,  1,  0,  4],
       [ 0,  1,  1,  5],
       ....
       [ 1,  2,  3, 23]])

有了这些小样本,它实际上比@Diakar 的函数更快。 :)

In [598]: timeit indices_merged_arr_generic(arr)
52.8 µs ± 271 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [599]: timeit indices_merged_arr_generic(arr3)
66.9 µs ± 434 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [600]: timeit np.array([(*ij,v) for ij,v in np.ndenumerate(arr)])
21.2 µs ± 40.5 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [601]: timeit np.array([(*ij,v) for ij,v in np.ndenumerate(arr3)])
59.4 µs ± 1.28 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

但对于大型 3d 数组,它要慢得多

In [602]: A = np.random.random((100,100,100))
In [603]: timeit indices_merged_arr_generic(A)
50.3 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [604]: timeit np.array([(*ij,v) for ij,v in np.ndenumerate(A)])
2.39 s ± 11.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

还有 `@unutbu's - 小号慢,大号快:

In [609]: timeit indices_merged_arr_generic_using_cp(arr)
104 µs ± 1.78 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [610]: timeit indices_merged_arr_generic_using_cp(arr3)
141 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [611]: timeit indices_merged_arr_generic_using_cp(A)
31.1 ms ± 1.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

【讨论】:

  • 对于需要整数索引和浮点值的 2D 和 3D 数组非常有用。您可以更轻松地构建结构化或重新排列
【解决方案4】:

我们可以使用下面的oneliner:

from numpy import hstack, array, meshgrid

hstack((
        array(meshgrid(*map(range, t.shape))).T.reshape(-1,t.ndim),
        t.flatten().reshape(-1,1)
       ))

这里我们首先使用map(range, t.shape) 构造一个ranges 的可迭代对象。通过使用np.meshgrid(..).T.reshape(-1, t.dim),我们构造了表格的第一部分:一个n×m矩阵,其中nt的元素个数,m 维数,然后我们在右侧添加一个扁平化版本的t

【讨论】:

    猜你喜欢
    • 2015-07-05
    • 1970-01-01
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 2021-10-13
    • 1970-01-01
    • 2020-12-11
    • 2011-08-30
    相关资源
    最近更新 更多