连接许多不同形状的nd数组(填充值直到边缘)



我有一个不同形状的2d数组列表:lst(参见下面的示例(。

我想将它们连接成形状为(len(lst), maxx, maxy)的3d阵列,其中maxx是所有阵列中的最大.shape[0]maxy是最大.shape[1]

如果一个数组的形状小于(maxx, maxy),那么这个数组应该从左上角开始,所有缺失的值都应该用一些选择的值填充(例如0或np.nan(。

示例:

lst = [np.array([[1, 2],
[3, 4]]),
np.array([[1, 2, 3],
[4, 5, 6]]),
np.array([[1, 2],
[3, 4],
[5, 6]])]
# maxx == 3
# maxy == 3
result = np.array([[[1, 2, 0],
[3, 4, 0],
[0, 0, 0]],
[[1, 2, 3],
[4, 5, 6],
[0, 0, 0]],
[[1, 2, 0],
[3, 4, 0],
[5, 6, 0]]])

注意:

np.concatenate要求所有阵列的形状匹配。

这个问题是类似的,但它只适用于1d数组。


一个子问题:

作为一种特殊情况,您可以假设.shape[1] == maxy对于所有阵列都是相同的。例如:

lst = [np.array([[1, 2, 3],
[4, 5, 6]]),
np.array([[1, 2, 3]]),
np.array([[1, 2, 3],
[4, 5, 6]
[7, 8, 9]])]

奖金(一个难题(:

这可以应用于更多维度吗?例如,当将三维阵列串联成4d阵列时,所有三维阵列(长方体(都将从同一开始,如果它们的形状太小,则缺失的值(直到边缘(将填充0或np.nan


如何做到这一点?如何有效地做到这一点(可能针对数千个阵列,每个阵列都有数千个元素(?

  • 也许创建一个最终形状的数组,并以矢量化的方式填充它?

  • 或者将所有数组转换为数据帧并将它们与pd.concat连接?

  • 也许SciPy对此有一些有用的功能?

通用维度的解决方案,非矢量化,但避免了缓慢的np.pad调用。(速度快约20倍,以lst*10000为例(。

import numpy as np
def fill_axis(lst):
shapes = np.array([arr.shape for arr in lst])
res = np.zeros((len(shapes),) + (*shapes.max(0),), int)
for x, arr in enumerate(lst):
slices = [x]
slices += (slice(None, shape) for shape in arr.shape)
res[tuple(slices)] = arr
return res
lst = lst * 10000
%timeit fill_axis(lst)
# 77.3 ms ± 2.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# solution by @TimRoberts https://stackoverflow.com/a/73536898/14277722
def pad_fill(lst):
maxx = max(x.shape[0] for x in lst)
maxy = max(x.shape[1] for x in lst)
res = [np.pad( k, [(0,maxx-k.shape[0]),(0,maxy-k.shape[1])] ) for k in lst]
return np.array(res)
%timeit pad_fill(lst)
# 1.82 s ± 82.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
np.testing.assert_equal(pad_fill(lst), fill_axis(lst))

堆叠式3D阵列示例

lst_4D = [np.arange(1*2*3*3).reshape(1,2,3,3),
np.arange(2*3*2*2).reshape(2,3,2,2)]
fill_axis(lst_4D)

输出

array([[[[[ 0,  1,  2],
[ 3,  4,  5],
[ 6,  7,  8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]],
[[ 0,  0,  0],
[ 0,  0,  0],
[ 0,  0,  0]]],

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

[[[[ 0,  1,  0],
[ 2,  3,  0],
[ 0,  0,  0]],
[[ 4,  5,  0],
[ 6,  7,  0],
[ 0,  0,  0]],
[[ 8,  9,  0],
[10, 11,  0],
[ 0,  0,  0]]],

[[[12, 13,  0],
[14, 15,  0],
[ 0,  0,  0]],
[[16, 17,  0],
[18, 19,  0],
[ 0,  0,  0]],
[[20, 21,  0],
[22, 23,  0],
[ 0,  0,  0]]]]])

对@divakar的2D阵列解决方案的改编~使用更大的列表比我的基准测试中更通用的解决方案快2倍,但更难推广到更多维度。

def einsum_fill(lst):
shapes = np.array([arr.shape for arr in lst])
a = np.arange(shapes[:,0].max()) < shapes[:,[0]]
b = np.arange(shapes[:,1].max()) < shapes[:,[1]]
mask = np.einsum('ij,ik->ijk', a, b)
res = np.zeros_like(mask, int)
res[mask] = np.concatenate([arr.ravel() for arr in lst])
return res

%timeit einsum_fill(lst)
# 46.7 ms ± 1.26 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
np.testing.assert_equal(einsum_fill(lst), fill_axis(lst))

您可以使用numpy.pad来执行此操作。

import numpy as np
lst = [np.array([[1, 2],
[3, 4]]),
np.array([[1, 2, 3],
[4, 5, 6]]),
np.array([[1, 2],
[3, 4],
[5, 6]])]
maxx = max(x.shape[0] for x in lst)
maxy = max(x.shape[1] for x in lst)
lst = [np.pad( k, [(0,maxx-k.shape[0]),(0,maxy-k.shape[1])] ) for k in lst]
print(lst)

输出:

[array([[1, 2, 0],
[3, 4, 0],
[0, 0, 0]]),
array([[1, 2, 3],
[4, 5, 6],
[0, 0, 0]]),
array([[1, 2, 0],
[3, 4, 0],
[5, 6, 0]])]

此过程适用于任意数量的维度。您必须使用循环而不是maxx/maxy计算。

以下代码可以在44 ms中运行,例如使用lst * 10000:

def new_(lst):
maxx = max(x.shape[0] for x in lst)
maxy = max(x.shape[1] for x in lst)
arr = np.zeros((len(lst), maxx, maxy))
for i in range(len(lst)):
arr[i, :lst[i].shape[0], :lst[i].shape[1]] = lst[i]
return arr

可以通过numba加速为:

lst_nb = nb.typed.List(lst)
@nb.njit(nb.float64[:, :, :](nb.types.ListType(nb.int_[:, ::1])))
def numba_(lst):
maxx = 0
maxy = 0
for x in lst:
maxx = max(x.shape[0], maxx)
maxy = max(x.shape[1], maxy)
arr = np.zeros((len(lst), maxx, maxy))
for i in range(len(lst)):
arr[i, :lst[i].shape[0], :lst[i].shape[1]] = lst[i]
return arr

最新更新