numpy02:数组变形与组合

数组的变形

数组常用属性

上一节介绍了 numpy 创建数组的方式。numpy 中的数组是一个 ndarray 对象,它提供了高效的存储与操作的方式,适合处理大量数据。

每个数组有 .nidm(数组的维度)、.shape(数组每个维度的大小)和 .size(数组的总大小)属性:

a3 = np.random.randint(10, size=(3, 4, 5))
print('a3 ndim  :', a3.ndim)
print('a3 shape :', a3.shape)
print('a3 size  :', a3.size)
a3 ndim : 3 a3 shape : (3, 4, 5) a3 size : 60

其它常用的属性包括表示每个数组元素字节大小的 .itemsize ,以及表示数组总字节大小的属性 .nbytes

print('itemsize :', a3.itemsize, 'bytes')
print('nbytes :', a3.nbytes, 'bytes')
itemsize : 4 bytes nbytes : 240 bytes

一般来说,可以认为 .nbytes.itemsize.size 的乘积大小相等。

数组变形

变形是一种实用的操作,通过变形可以产生有一定规则,但难以直接创建的数组。

下表总结了常用的变形函数或方法,接下来会详细介绍:

函数 作用 返回结果
.reshape(shape, order='C') 用数组现有元素重组变形 视图
.resize(new_shape) 用数组现有元素重组变形 原始数组
.T 转置 视图
.transpose(axes=None) 高维转置 视图
.flatten(order='C') 碾平为一维数组 副本
.ravel(order='C') 碾平为一维数组 视图
.squeeze(axis=None) 合并元素个数为 1 的维度 视图
.swapaxes(axis1, axis2) 交换数轴 视图

数组变形最灵活的实现方式是通过 .reshape() 方法实现。例如,以下通过变形将数字 0~9 放入一个 3×3 的矩阵中:

m1 = np.arange(1, 10).reshape((3, 3))
print(m1)
[[1 2 3] [4 5 6] [7 8 9]]

注意,原始数组的大小(元素个数)必须要和变形后数组的大小一致。可以使用负数来自动计算某一维度的元素个数:

np.arange(10).reshape((2, -1))
array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])

如果转换后形状无法兼容,则会产生 ValueError 异常。

该方法不会修改原有数组。如果想修改原有数组,可以调用 .resize() ,或者直接通过修改 .shape 属性实现:

m1.shape = (-1)
m1
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

.T 属性用于转置原有数组:

m2 = np.arange(6).reshape((2, 3))
m2
array([[0, 1, 2], [3, 4, 5]])
m2.T
array([[0, 3], [1, 4], [2, 5]])

或者可以使用 .transpose(axes=None) 方法,该方法的优点是可以转置更高维的矩阵:

m3 = np.arange(12).reshape((2, 3, 2))
m3.transpose((2, 0, 1)).shape
(2, 2, 3)

注意,两种转置返回的结果都是数组的视图(view)而不是副本。视图是原有数组中的部分元素重新按一定规律输出的结果,对视图内的元素做修改也会实时更新到原有数组内:

m2_T = m2.transpose()
m2_T[0] = 100
m2
array([[100, 2, 3], [100, 5, 6]])

.reshape() 方法返回的也是数组的视图,因为它只涉及部分元素的重新排布。这种方式在处理大量数据时运行会更快,并可以减少副本占用的缓存空间。

有关数组的索引与切片将在下一节介绍。

变形与维度转换

有时要对数组的维度做变换,此时就会涉及数组的变形。使用 .flatten(order='C') 可以将高维数组碾平为一维数组:

a4 = np.array([[[1, 2, 3], [4, 5, 6]]])
a4.flatten()
array([1, 2, 3, 4, 5, 6])

该方法不会修改原有数组,返回的是原有数组的副本。

.ravel(order='C') 也用于碾平数组,不过返回结果是数组的视图。

.squeeze(axis=None) 用于将元素个数为 1 的维度并入其它维度中,即去除 .shape 属性中值为 1 的维度:

a5 = np.array([[[[1], [2]], [[3], [4]]]])
a5.squeeze()
array([[1, 2], [3, 4]])

.swapaxes(axis1, axis2) 用于交换数值的两个维度。对于一个高维数组来说,最内层的数组的 axis 被记为 0 ,次外层为 1 ,以此类推:

m3
array([[[ 0, 1], [ 2, 3], [ 4, 5]], [[ 6, 7], [ 8, 9], [10, 11]]])
m3.swapaxes(1, 2)
array([[[ 0, 2, 4], [ 1, 3, 5]], [[ 6, 8, 10], [ 7, 9, 11]]])

该操作返回视图。交换最直观的反应就是 .shape 对应位置的值被互换了。

数组的拼接和拆分

以上所有的操作都是针对单一数组的,但有时也需要将多个数组合并为一个,或者将一个数组分裂为多个。

数组的拼接

下表列出了拼接或连接数组常用的方法:

函数 效果
concatenate(arrays, axis=0) 拼接多个数组
append(arr, values, axis=None) 拼接两个数组,未指定维度则碾平后拼接
vstack(arrays)row_stack(arrays) 垂直拼接多个数组,即将数组的行之间拼接
hstack(arrays)column_stack(arrays) 水平拼接多个数组,即将数组的列之间拼接
dstack(arrays) 沿第三个维度,即高度方向拼接多个数组
stack(arrays, axis=0) 拼接多个数组,且拼接完之后会变高一维

concatenate() 将数组元组或数组列表作为第一个参数,例如:

x1 = np.array([1, 2, 3])
y1 = np.array([3, 2, 1])
np.concatenate([x1, y1])
array([1, 2, 3, 3, 2, 1])

该函数也可以一次性拼接多个数组,或用于二维数组的拼接。

对于多维数组,有时还需要向别的方向拼接。在了解如何拼接前,需要进一步认识 numpy 的坐标轴。最外层的元素(维度 1 )的轴记为 0 ,它们包含的数组(维度 2 )轴记为 1 ,这些数组包含的数组(维度 3 )记为 2 ,以此类推:

可以通过 axis 参数指定拼接的轴。下图展示了“在 axis=1 轴上拼接”的原理:

此时最内层的数组之间被拼接,示例如下:

m2
array([[1, 2, 3], [4, 5, 6]])
np.concatenate([m2, m2], axis=1)
array([[1, 2, 3, 1, 2, 3], [4, 5, 6, 4, 5, 6]])

append() 函数用于拼接两个数组,如果不指定 axis 会先调用 .ravel() 碾平(不改变原数组)再拼接,因此可用于拼接不同形状的数组:

np.append(m2, x1)
array([0, 1, 2, 3, 4, 5, 1, 2, 3])

拼接完成之后再将其修改为需要的形状即可。

沿着固定维度处理数组时,使用 vstack() 函数进行垂直拼接,或使用 hstack() 函数进行水平拼接会更加方便:

np.vstack([x1, m2])  # vertical
array([[1, 2, 3], [1, 2, 3], [4, 5, 6]])
y2 = np.array([[100], [200]])
np.hstack([m2, y2])  # horizontal
array([[ 1, 2, 3, 100], [ 4, 5, 6, 200]])

这两个函数还分别有别名 row_stack()column_stack() ,从名字上也可以看出来它们分别用于拼接行和列。

与此类似,dstack() 函数用于将三维数组沿着高度作拼接。

stack() 有时也用于拼接数组,其特点是拼接得到的结果必定比原数组高一个维度:

np.stack([m2, m2], axis=1)
array([[[0, 1, 2], [0, 1, 2]], [[3, 4, 5], [3, 4, 5]]])

数组的拆分

与拼接过程相反的是分裂。分裂主要通过以下函数来实现:

函数 效果
split(arr, indices_or_sections, axis) 分裂成多个数组
hsplit(arr, indices_or_sections) 沿水平方向分裂,即分裂数组的列
vsplit(arr, indices_or_sections) 沿垂直方向分裂,即分裂数组的行
dsplit(arr, indices_or_sections) 沿第三个维度分裂,即分裂数组的高

可以向以上函数传递一个索引列表作为参数,索引列表记录的是分裂点的位置:

a6 = [1, 2, 3, 4, 4, 3, 2, 1]
sub1, sub2, sub3 = np.split(a6, [3, 5])
print(sub1, sub2, sub3)
[1 2 3] [4 4] [3 2 1]

每遇到对应位置便会拆出一个新数组,因此 N 个分裂点会得到 N+1 个子数组。

高维数组的分裂可以使用 axis 参数指定分裂的维度,例如:

sub4, sub5, sub6 = np.split(np.arange(12).reshape(2, -1), [1, 3], axis=1)
sub4
array([[0], [6]])

下图指出了“在 axis=1 轴上分裂”的原理:

相关的 hsplit()vsplit() 的用法也类似:

m3 = np.arange(16).reshape((4, 4))
upper, lower = np.vsplit(m3, [2])
print(upper)
print(lower)
[[0 1 2 3] [4 5 6 7]] [[ 8 9 10 11] [12 13 14 15]]
left, right = np.hsplit(m3, [2])
print(left)
print(right)
[[ 0 1] [ 4 5] [ 8 9] [12 13]] [[ 2 3] [ 6 7] [10 11] [14 15]]

同样,dsplit() 函数将沿着第三个维度分裂。

京ICP备2021034974号
contact me by hello@frozencandles.fun