问题描述
我有以下numpy数组:
[[[1],[2],[3],[1],[3]],[[4],[5],[6],[4],[6]],[[7],[8],[9],[7],[9]]]
我希望最后一个维度中的每个元素[1]
,[2]
,[3]
等与第二个维度中的以下n
数组连接。如果发生溢出,则可以用0填充元素。例如,对于n = 2
:
[[[1,2,3],[2,3,1],[3,1,2],[1,0],0]],[[4,5,6],[5,6,4],[6,4,5],[4,[[7,8,9],[8,9,7],[9,7,8],[7,0]]]
我想使用内置的numpy函数来实现此目的,以获得良好的性能,并且还想反向执行此操作,即,将n = -2
换档是公平的游戏。该怎么做?
对于n = -2
:
[[[0,[0,3]],[[0,6]],9]]]
对于n = 3
[[[1,0]]]
如果数组的当前形状为(height,width,1)
,则在操作后,形状将为(height,abs(n) + 1)
。
如何对此进行概括,以便数字1、2、3等本身可以成为numpy数组?
解决方法
这是一种实现方法:
from skimage.util import view_as_windows
if n>=0:
a = np.pad(a.reshape(*a.shape[:-1]),((0,0),(0,n)))
else:
n *= -1
a = np.pad(a.reshape(*a.shape[:-1]),(n,0)))
b = view_as_windows(a,(1,n+1))
b = b.reshape(*b.shape[:-2]+(n+1,))
a
是您的输入数组,而b
是您的输出:
n=2
:
[[[1 2 3]
[2 3 1]
[3 1 2]
[1 2 3]
[2 3 0]
[3 0 0]]
[[4 5 6]
[5 6 4]
[6 4 5]
[4 5 6]
[5 6 0]
[6 0 0]]
[[7 8 9]
[8 9 7]
[9 7 8]
[7 8 9]
[8 9 0]
[9 0 0]]]
n=-2
:
[[[0 0 1]
[0 1 2]
[1 2 3]
[2 3 1]
[3 1 2]
[1 2 3]]
[[0 0 4]
[0 4 5]
[4 5 6]
[5 6 4]
[6 4 5]
[4 5 6]]
[[0 0 7]
[0 7 8]
[7 8 9]
[8 9 7]
[9 7 8]
[7 8 9]]]
说明:
-
np.pad(a.reshape(*a.shape[:-1]),n)))
在数组的右侧填充足够的零以防止窗口溢出(类似地,在负n
的左侧填充左侧) -
view_as_windows(a,n+1))
根据问题的需要从数组中创建形状为(1,n+1)
的窗口。 -
b.reshape(*b.shape[:-2]+(n+1,))
摆脱了(1,n+1)
形窗口创建的长度1的额外尺寸,并将b
重塑为所需的形状。请注意,参数*b.shape[:-2]+(n+1,)
只是两个元组的串联,以创建单个元组作为形状。
这听起来像是as_strided
怪物的教科书应用程序。它的优点之一是它不需要任何其他导入。总体思路是这样的:
-
您有一个数组,数组的形状为
(3,6,1)
,步幅为(6,1,1) * element_size
。x = ... n = ... # Must not be zero,but you can special-case it to return the original array
-
您想将其转换为形状为
(3,|n| + 1)
,因此跨度为(6 * (|n| + 1),|n| + 1,1) * element_size
的数组。 -
为此,您首先用
|n|
零填充左边或右边:pad = np.zeros((x.shape[0],np.abs(n),x.shape[2])) x_pad = np.concatenate([x,pad][::np.sign(n)],axis=1)
-
现在,您可以使用自定义形状直接索引到缓冲区中,并大步获得所需的结果。代替使用适当的步幅
(6 * (|n| + 1),1) * element_size
,我们将每个重复的元素直接索引到原始数组的相同缓冲区中,这意味着将调整步幅。中间尺寸将移动一个元素,而不是适当的|n| + 1
。这样一来,列就可以准确地从您想要的位置开始new_shape = (x.shape[0],x.shape[1],x.shape[2] + np.abs(n)) new_strides = (x_pad.strides[0],x_pad.strides[2],x_pad.strides[2]) result = np.lib.stride_tricks.as_strided(x_pad,shape=new_shape,strides=new_strides)
这里有很多警告。要注意的最大事情是多个数组元素访问同一内存。我的建议是,如果您打算除读取数据外还做其他任何事情,请制作一个充实的副本:
result = result.copy()
这将为您提供正确大小的缓冲区,而不是通过填充使您疯狂地查看原始数据。