numpy数组的填充元素

3
假设我有以下的numpy数组:
[[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,填充必须是可变的,因为我正在根据具有最高分辨率的数组的因子上采样数据。假设有三个形状为(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]]
4个回答

7

以下是一种使用 零初始化 的方法 -

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的解决方案函数: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

这不是一种内存高效的方法,至少在你初始化一个包含零的数组时不是! - Mazdak
2
@Kasramvd 预期输出数组应该是那个形状,所以我不明白为什么这会是低效的。 - Divakar
确实,但是您正在创建具有数组长度的额外项,应该在稍后进行替换。 - Mazdak
不错的基准测试,但请注意优化始终涉及两个方面,即内存和运行时间,根据我们的需求,我们更关心其中一个。 - Mazdak
@Kasramvd 哇哦!我在这个过程中学到了很多关于内存分析的知识。请查看已编辑的部分,解决你的内存效率问题吧! :) - Divakar
显示剩余2条评论

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
@James 是的,在那种情况下,您需要将重复参数乘以2,以获得每个更多1次重复。还需要更改切片注释。 - Mazdak
1
@James 请查看编辑后的示例,其中有超过1个填充。 - Mazdak
你能否将其转换为函数格式,并将填充长度作为参数传入吗?我很想测试一下时间! - Divakar

1

将数组压平,将每个1转换为[0, 1, 0],然后再次进行重塑以形成3行。在以下代码中,ones数组存储在变量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]]

0

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

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

insert不可能比Divakarpadcols更有效率。

让我们看看我是否能清晰地复制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 的主要区别在于,此方法使用布尔掩码进行索引,而不是列号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接