【问题标题】:How to take elements along a given axis, given by their indices?如何沿给定轴获取元素,由它们的索引给出?
【发布时间】:2017-12-05 17:00:07
【问题描述】:

我有一个 3D 数组,我需要将它“挤压”到最后一个轴上,以便得到一个 2D 数组。我需要通过以下方式进行。对于前两个维度的每个索引值,我都知道第三个维度的索引值应该从哪里获取。

例如,我知道如果i1 == 2i2 == 7 然后i3 == 11。这意味着out[2,7] = inp[2,7,11]。这种从前两个维度到第三个维度的映射在另一个二维数组中给出。换句话说,我有一个数组,其中2,7 的位置有11 作为值。

所以,我的问题是如何组合这两个数组(3D 和 2D)来获得输出数组(2D)。

【问题讨论】:

    标签: python arrays numpy


    【解决方案1】:
    In [635]: arr = np.arange(24).reshape(2,3,4)
    In [636]: idx = np.array([[1,2,3],[0,1,2]])
    
    
    In [637]: I,J = np.ogrid[:2,:3]
    In [638]: arr[I,J,idx]
    Out[638]: 
    array([[ 1,  6, 11],
           [12, 17, 22]])
    In [639]: arr
    Out[639]: 
    array([[[ 0,  1,  2,  3],   # 1
            [ 4,  5,  6,  7],   # 6
            [ 8,  9, 10, 11]],  # ll
    
           [[12, 13, 14, 15],
            [16, 17, 18, 19],
            [20, 21, 22, 23]]])
    

    I,J 一起广播选择一组 (2,3) 值,匹配idx

    In [640]: I
    Out[640]: 
    array([[0],
           [1]])
    In [641]: J
    Out[641]: array([[0, 1, 2]])
    

    这是对更简单的 2d 问题的 3d 的概括 - 从每一行中选择一项:

    In [649]: idx
    Out[649]: 
    array([[1, 2, 3],
           [0, 1, 2]])
    In [650]: idx[np.arange(2), [0,1]]
    Out[650]: array([1, 1])
    

    实际上我们可以将 3d 问题转换为 2d 问题:

    In [655]: arr.reshape(6,4)[np.arange(6), idx.ravel()]
    Out[655]: array([ 1,  6, 11, 12, 17, 22])
    

    概括原来的情况:

    In [55]: arr = np.arange(24).reshape(2,3,4)                                     
    In [56]: idx = np.array([[1,2,3],[0,1,2]])                                      
    In [57]: IJ = np.ogrid[[slice(i) for i in idx.shape]]                           
    In [58]: IJ                                                                     
    Out[58]: 
    [array([[0],
            [1]]), array([[0, 1, 2]])]
    In [59]: (*IJ,idx)                                                              
    Out[59]: 
    (array([[0],
            [1]]), array([[0, 1, 2]]), array([[1, 2, 3],
            [0, 1, 2]]))
    In [60]: arr[_]                                                                 
    Out[60]: 
    array([[ 1,  6, 11],
           [12, 17, 22]])
    

    关键在于将IJ 数组列表与idx 组合成一个新的索引元组。如果idx 不是最后一个索引,那么构造元组会有点麻烦,但它仍然是可能的。例如

    In [61]: (*IJ[:-1],idx,IJ[-1])                                                  
    Out[61]: 
    (array([[0],
            [1]]), array([[1, 2, 3],
            [0, 1, 2]]), array([[0, 1, 2]]))
    In [62]: arr.transpose(0,2,1)[_]                                                
    Out[62]: 
    array([[ 1,  6, 11],
           [12, 17, 22]])
    

    如果将arr 转置到idx 维度更容易,则最后一个维度。关键是索引操作采用索引数组的元组,这些数组相互广播以选择特定项目。 这就是ogrid 正在做的事情,创建与idx 一起使用的数组。

    【讨论】:

    • @hpaulf 我如何将此解决方案用于任意维度的“idx”?也就是说,这在 idx 为 2x3 时有效。如果我有一个函数需要适应“idx”是 3x3 或 2x3x4 的情况怎么办?注意:在我的示例中,“arr”总是多维,“idx”的值是有效索引。我知道我可以使用 idx.shape 获得尺寸,但我想我需要将生成的元组转换为形式':d1,:d2,:d3,...'其中'd1'是第一个尺寸大小, 'd2' 是第二个,依此类推。
    • 我添加了一个概括。
    【解决方案2】:
    inp = np.random.random((20, 10, 5)) # simulate some input
    i1, i2 = np.indices(inp.shape[:2])
    i3 = np.random.randint(0, 5, size=inp.shape) # or implement whatever mapping
                                                 # you want between (i1,i2) and i3
    out = inp[(i1, i2, i3)]
    

    详情请见https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#integer-array-indexing

    【讨论】:

      【解决方案3】:

      使用numpy.einsum

      这可以通过array indexingnumpy.einsum 的组合使用来实现:

      >>> numpy.einsum('ijij->ij', inp[:, :, indices])
      

      inp[:, :, indices] 创建一个四维数组,其中对于前两个索引(前两个维度)中的每一个,索引数组的所有索引都应用于第三个维度。因为索引数组是二维的,所以结果是 4D。但是,您只需要与前两个维度中的索引相对应的索引数组的索引。然后通过使用字符串ijij->ij 来实现。这告诉einsum,您只想选择那些第 1 轴和第 3 轴以及第 2 轴和第 4 轴的索引相似的元素。因为最后两个维度(第 3 和第 4 维)是由索引数组添加的,所以这类似于只为 inp 的第三维选择索引 index[i, j]

      请注意,这种方法确实会炸毁内存消耗。特别是如果inp.shape[:2]inp.shape[2] 大得多,那么inp[:, :, indices].size 将近似于inp.size ** 2

      手动构建索引

      首先我们准备新的索引数组:

      >>> idx = numpy.array(list(
      ...     numpy.ndindex(*inp.shape[:2], 1)  # Python 3 syntax
      ... ))
      

      然后我们更新第三轴对应的列:

      >>> idx[:, 2] = indices[idx[:, 0], idx[:, 1]]
      

      现在我们可以选择元素并简单地重塑结果:

      >>> inp[tuple(idx.T)].reshape(*inp.shape[:2])
      

      使用numpy.choose

      注意:numpy.choose 允许选择的轴的最大尺寸为 32。


      根据this answernumpy.choose 的文档,我们还可以使用以下内容:

      # First we need to bring the last axis to the front because
      # `numpy.choose` chooses from the first axis.
      >>> new_inp = numpy.moveaxis(inp, -1, 0)
      # Now we can select the elements.
      >>> numpy.choose(indices, new_inp)
      

      尽管文档不鼓励将单个数组用于第二个参数(选项)

      为了减少误解的机会,即使名义上支持以下“滥用”,choices 既不应该也不应该被认为是单个数组,即最外层的类似序列容器应该是列表或元组。

      这似乎只是为了防止误解:

      选择数组序列

      选择数组。 a 和所有的选择必须可以广播到相同的形状。如果 choices 本身是一个数组(不推荐),那么它的最外层维度(即对应于choices.shape[0] 的维度)被视为定义“序列”。

      所以从我的角度来看,以这种方式使用 numpy.choose 没有任何问题,只要人们知道他们在做什么。

      【讨论】:

      • choices 有一个最大尺寸,我相信是 32。
      • 在您的链接问题中检查divakars 的答案。
      • @hpaulj 是的,已确认。谢谢!更新了答案。
      【解决方案4】:

      我认为应该这样做:

      for i in range(n):
          for j in range(m):
              k = index_mapper[i][j]
              value = input_3d[i][j][k]
              out_2d[i][j] = value 
      

      【讨论】:

      • 我没有对此投票。但是您只是将 OP out[i,j] = inp[i,j,k] 包装在 2 个循环中。这是嵌套列表的正确方法,但索引样式和标记表明 OP 想要一个 numpy 数组解决方案。
      猜你喜欢
      • 1970-01-01
      • 2011-07-25
      • 2021-06-03
      • 1970-01-01
      • 2011-12-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多