我有一个不同形状的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