【问题标题】:Padding elements of a numpy array填充numpy数组的元素
【发布时间】:2016-08-18 12:38:35
【问题描述】:

假设我有以下numpy array

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

我需要在数组中的每个元素两边都用零填充(而不是 numpy.pad() 填充行和列)。结果如下:

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

有没有比创建一个空数组并使用嵌套循环更有效的方法呢?

注意:我的偏好是尽可能快且记忆力尽可能轻。单个数组最多可以包含 12000^2 个元素,我同时使用其中的 16 个,所以我的边距在 32 位中非常薄

编辑: 应该指定,但填充并不总是 1,填充必须是可变的,因为我正在通过一个取决于具有最高分辨率的数组的因子对数据进行上采样。给定 3 个形状为 (121,121) 的数组; (1200,1200) ; (12010,12010) 我需要能够将前两个数组填充为 (12010,12010) 的形状(我知道这些数字不共享公因数,这不是问题,因为在索引或两个实际位置是可以接受的,这个填充只是为了让它们变成相同的形状,通过在末端填充行来四舍五入是可以接受的)

工作解决方案:调整@Kasramvd 解决方案就可以了。这是适合我的问题应用的代码。

import numpy as np

a = np.array([[1, 2, 3],[1, 2, 3], [1, 2, 3]])

print(a)

x, y = a.shape
factor = 3
indices = np.repeat(np.arange(y + 1), 1*factor*2)[1*factor:-1*factor]

a=np.insert(a, indices, 0, axis=1)

print(a)

结果:

 [[1 2 3]
  [1 2 3]
  [1 2 3]]

 [[0 0 0 1 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0]
  [0 0 0 1 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0]
  [0 0 0 1 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0]]

【问题讨论】:

    标签: python arrays numpy


    【解决方案1】:

    这是一种使用zeros-initialization的方法-

    def padcols(arr,padlen):
        N = 1+2*padlen
        m,n = arr.shape
        out = np.zeros((m,N*n),dtype=arr.dtype)
        out[:,padlen+np.arange(n)*N] = arr
        return out
    

    示例运行 -

    In [118]: arr
    Out[118]: 
    array([[21, 14, 23],
           [52, 70, 90],
           [40, 57, 11],
           [71, 33, 78]])
    
    In [119]: padcols(arr,1)
    Out[119]: 
    array([[ 0, 21,  0,  0, 14,  0,  0, 23,  0],
           [ 0, 52,  0,  0, 70,  0,  0, 90,  0],
           [ 0, 40,  0,  0, 57,  0,  0, 11,  0],
           [ 0, 71,  0,  0, 33,  0,  0, 78,  0]])
    
    In [120]: padcols(arr,2)
    Out[120]: 
    array([[ 0,  0, 21,  0,  0,  0,  0, 14,  0,  0,  0,  0, 23,  0,  0],
           [ 0,  0, 52,  0,  0,  0,  0, 70,  0,  0,  0,  0, 90,  0,  0],
           [ 0,  0, 40,  0,  0,  0,  0, 57,  0,  0,  0,  0, 11,  0,  0],
           [ 0,  0, 71,  0,  0,  0,  0, 33,  0,  0,  0,  0, 78,  0,  0]])
    

    基准测试

    在本节中,我将对运行时和内存使用情况进行基准测试,使用本文中发布的方法:padcols@Kasramvd's solution func : padder 在一个大小适中的数组上针对各种填充长度。

    时序分析

    In [151]: arr = np.random.randint(10,99,(300,300))
               # Representative of original `3x3` sized array just bigger
    
    In [152]: %timeit padder(arr,1)
    100 loops, best of 3: 3.56 ms per loop
    
    In [153]: %timeit padcols(arr,1)
    100 loops, best of 3: 2.13 ms per loop
    
    In [154]: %timeit padder(arr,2)
    100 loops, best of 3: 5.82 ms per loop
    
    In [155]: %timeit padcols(arr,2)
    100 loops, best of 3: 3.66 ms per loop
    
    In [156]: %timeit padder(arr,3)
    100 loops, best of 3: 7.83 ms per loop
    
    In [157]: %timeit padcols(arr,3)
    100 loops, best of 3: 5.11 ms per loop
    

    内存分析

    用于这些内存测试的脚本 -

    import numpy as np
    from memory_profiler import profile
    
    arr = np.random.randint(10,99,(300,300))
    padlen = 1 # Edited to 1,2,3 for the three cases
    n = padlen
    
    @profile(precision=10)
    def padder():    
        x, y = arr.shape
        indices = np.repeat(np.arange(y+1), n*2)[n:-n]
        return np.insert(arr, indices, 0, axis=1)
        
    @profile(precision=10)
    def padcols():    
        N = 1+2*padlen
        m,n = arr.shape
        out = np.zeros((m,N*n),dtype=arr.dtype)
        out[:,padlen+np.arange(n)*N] = arr
        return out
    
    if __name__ == '__main__':
        padder()
    
    if __name__ == '__main__':
        padcols()  
    

    内存使用输出 -

    案例#1:

    $ python -m memory_profiler timing_pads.py
    Filename: timing_pads.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
         8  42.4492187500 MiB   0.0000000000 MiB   @profile(precision=10)
         9                             def padder():    
        10  42.4492187500 MiB   0.0000000000 MiB       x, y = arr.shape
        11  42.4492187500 MiB   0.0000000000 MiB       indices = np.repeat(np.arange(y+1), n*2)[n:-n]
        12  44.7304687500 MiB   2.2812500000 MiB       return np.insert(arr, indices, 0, axis=1)
    
    
    Filename: timing_pads.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
        14  42.8750000000 MiB   0.0000000000 MiB   @profile(precision=10)
        15                             def padcols():    
        16  42.8750000000 MiB   0.0000000000 MiB       N = 1+2*padlen
        17  42.8750000000 MiB   0.0000000000 MiB       m,n = arr.shape
        18  42.8750000000 MiB   0.0000000000 MiB       out = np.zeros((m,N*n),dtype=arr.dtype)
        19  44.6757812500 MiB   1.8007812500 MiB       out[:,padlen+np.arange(n)*N] = arr
        20  44.6757812500 MiB   0.0000000000 MiB       return out
    

    案例#2:

    $ python -m memory_profiler timing_pads.py
    Filename: timing_pads.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
         8  42.3710937500 MiB   0.0000000000 MiB   @profile(precision=10)
         9                             def padder():    
        10  42.3710937500 MiB   0.0000000000 MiB       x, y = arr.shape
        11  42.3710937500 MiB   0.0000000000 MiB       indices = np.repeat(np.arange(y+1), n*2)[n:-n]
        12  46.2421875000 MiB   3.8710937500 MiB       return np.insert(arr, indices, 0, axis=1)
    
    
    Filename: timing_pads.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
        14  42.8476562500 MiB   0.0000000000 MiB   @profile(precision=10)
        15                             def padcols():    
        16  42.8476562500 MiB   0.0000000000 MiB       N = 1+2*padlen
        17  42.8476562500 MiB   0.0000000000 MiB       m,n = arr.shape
        18  42.8476562500 MiB   0.0000000000 MiB       out = np.zeros((m,N*n),dtype=arr.dtype)
        19  46.1289062500 MiB   3.2812500000 MiB       out[:,padlen+np.arange(n)*N] = arr
        20  46.1289062500 MiB   0.0000000000 MiB       return out
    

    案例#3:

    $ python -m memory_profiler timing_pads.py
    Filename: timing_pads.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
         8  42.3906250000 MiB   0.0000000000 MiB   @profile(precision=10)
         9                             def padder():    
        10  42.3906250000 MiB   0.0000000000 MiB       x, y = arr.shape
        11  42.3906250000 MiB   0.0000000000 MiB       indices = np.repeat(np.arange(y+1), n*2)[n:-n]
        12  47.4765625000 MiB   5.0859375000 MiB       return np.insert(arr, indices, 0, axis=1)
    
    
    Filename: timing_pads.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
        14  42.8945312500 MiB   0.0000000000 MiB   @profile(precision=10)
        15                             def padcols():    
        16  42.8945312500 MiB   0.0000000000 MiB       N = 1+2*padlen
        17  42.8945312500 MiB   0.0000000000 MiB       m,n = arr.shape
        18  42.8945312500 MiB   0.0000000000 MiB       out = np.zeros((m,N*n),dtype=arr.dtype)
        19  47.4648437500 MiB   4.5703125000 MiB       out[:,padlen+np.arange(n)*N] = arr
        20  47.4648437500 MiB   0.0000000000 MiB       return out
    

    【讨论】:

    • 这不是一种节省内存的方法,至少在初始化包含零的数组时!
    • @Kasramvd 输出数组应该是那种形状,所以我不明白为什么这应该是低效的。
    • 确实,但是您正在创建额外的项目,这些项目的长度应稍后替换。
    • @Kasramvd 是的,在这个解决方案中,索引部分将是最耗时的,因为初始化部分应该相对可以忽略不计
    • 不错的基准测试,但请注意,优化总是关于内存和运行时这两个方面,根据我们的需要,我们更关心其中之一。
    【解决方案2】:

    您可以根据数组的形状使用np.repeat 创建相关索引,然后在该索引中插入 0。

    >>> def padder(arr, n):
    ...     x, y = arr.shape
    ...     indices = np.repeat(np.arange(y+1), n*2)[n:-n]
    ...     return np.insert(arr, indices, 0, axis=1)
    ... 
    >>> 
    >>> padder(a, 1)
    array([[0, 1, 0, 0, 1, 0, 0, 1, 0],
           [0, 1, 0, 0, 1, 0, 0, 1, 0],
           [0, 1, 0, 0, 1, 0, 0, 1, 0]])
    >>> 
    >>> padder(a, 2)
    array([[0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
           [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
           [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0]])
    >>> padder(a, 3)
    array([[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
           [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
           [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]])
    

    上述方法在一行中:

    np.insert(a, np.repeat(np.arange(a.shape[1] + 1), n*2)[n:-n], 0, axis=1)
    

    【讨论】:

    • 我喜欢这个解决方案,但它似乎不适用于一般情况(应该在我的问题中指定它并不总是填充 1),当我调整 np.repeat 中的重复时它们并不一致,有没有更通用的解决方案?
    • @James 是的,在这种情况下,您需要将重复参数乘以 2,每重复 1 次。并更改切片符号。
    • @James 查看编辑以获取具有超过 1 个填充的示例。
    • 你能转换成以pad长度为参数的函数格式吗?很想为这些计时!
    【解决方案3】:

    展平数组,将每个 1 转换为 [0, 1, 0],然后再次整形为 3 行。在下面的代码中,这些数组在 var a 中:

    a = np.ones([3,3])
    
    b = [[0, x, 0] for x in a.ravel()]
    c = np.reshape(b, (a.shape[0], -1))
    
    print(c)
    

    输出:

    [[0 1 0 0 1 0 0 1 0]
     [0 1 0 0 1 0 0 1 0]
     [0 1 0 0 1 0 0 1 0]]
    

    【讨论】:

      【解决方案4】:

      这些方法的时间和内存比较的一个问题是它将insert 视为一个黑盒。但该函数是可以读取和复制的 Python 代码。虽然它可以处理各种输入,但在这种情况下我认为它

      • 生成一个大小合适的new目标数组
      • 计算采用填充值的列的索引
      • 为旧值创建掩码
      • 将填充值复制到新的
      • 将旧值复制到新值

      insert 不可能比 Divakar's padcols 更有效率。

      看看能不能清晰复制insert

      In [255]: indices = np.repeat(np.arange(y + 1), 1*factor*2)[1*factor:-1*factor]
      In [256]: indices
      Out[256]: array([0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3])
      In [257]: numnew = len(indices)
      In [258]: order = indices.argsort(kind='mergesort')
      In [259]: indices[order] += np.arange(numnew)
      In [260]: indices
      Out[260]: 
      array([ 0,  1,  2,  4,  5,  6,  7,  8,  9, 11, 12, 13, 14, 15, 16, 18, 19,
             20])
      

      这些列将采用0 填充值。

      In [266]: new = np.empty((3,21),a.dtype)
      In [267]: new[:,indices] = 0     # fill
      # in this case with a lot of fills
      # np.zeros((3,21),a.dtype)   would be just as good
      
      In [270]: old_mask = np.ones((21,),bool)
      In [271]: old_mask[indices] = False
      In [272]: new[:,old_mask] = a
      
      In [273]: new
      Out[273]: 
      array([[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0],
             [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0],
             [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0]])
      

      padcols 的主要区别在于它使用布尔掩码进行索引,而不是列号。

      【讨论】:

        猜你喜欢
        • 2018-05-07
        • 2016-11-06
        • 1970-01-01
        • 1970-01-01
        • 2017-04-02
        • 1970-01-01
        • 2020-12-28
        • 2018-10-05
        • 2017-10-24
        相关资源
        最近更新 更多