NumPy 数组 形状操作

在实际数据处理中,经常需要调整数组的结构,如改变维度、展平、转置、合并或分割数组。NumPy提供了丰富且高效的形状操作函数,本节将详细介绍这些常用操作。

1. 查看数组形状:shape 属性

使用 shape 属性可以获取数组的当前形状,也可以直接为它赋值来改变形状(要求元素总数不变)。

import numpy as np

arr = np.arange(12)
print("原数组:", arr)
print("原形状:", arr.shape)

# 直接修改 shape 属性(原地修改)
arr.shape = (3, 4)
print("修改 shape 后:\n", arr)
print("新形状:", arr.shape)

输出:

原数组: [ 0  1  2  3  4  5  6  7  8  9 10 11]
原形状: (12,)
修改 shape 后:
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
新形状: (3, 4)

2. reshape():返回新形状的视图

reshape(a, newshape) 返回一个具有新形状的视图(如果可能),不会修改原数组。如果无法返回视图(例如不连续内存),则返回副本。

arr = np.arange(12)
arr_2d = arr.reshape(3, 4)   # 返回新数组(视图)
print("reshape(3,4):\n", arr_2d)

# 使用 -1 自动推断维度
arr_auto = arr.reshape(2, -1)   # 自动计算列数 6
print("reshape(2,-1):\n", arr_auto)

# 验证是否为视图(共享内存)
arr[0] = 999
print("修改原数组后,arr_2d[0,0] =", arr_2d[0,0])  # 也会变

输出:

reshape(3,4):
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
reshape(2,-1):
 [[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
修改原数组后,arr_2d[0,0] = 999
注意: reshape 通常返回视图,但并非绝对。如果需要确保独立副本,可以先 copy() 再 reshape。

3. resize():改变形状(原地或返回新数组)

NumPy中有两个 resize:一个是 ndarray.resize() 方法(原地修改),另一个是 np.resize() 函数(返回新数组,可调整总元素数)。

# ndarray.resize() 原地修改(无返回值)
arr = np.arange(6)
arr.resize(2, 3)   # 直接改变原数组
print("原地 resize(2,3):\n", arr)

# np.resize() 返回新数组,可以改变总元素数(重复或截断)
arr2 = np.arange(6)
new_arr = np.resize(arr2, (3, 4))   # 原数组6个元素,新形状12个元素,会重复原数组
print("np.resize(3,4):\n", new_arr)

输出:

原地 resize(2,3):
 [[0 1 2]
 [3 4 5]]
np.resize(3,4):
 [[0 1 2 3]
 [4 5 0 1]
 [2 3 4 5]]

注意:np.resize 在元素不足时会循环使用原数组的数据填充。

4. 展平数组:ravel()flatten()

ravel() 返回一维视图(尽可能),而 flatten() 返回一维副本。

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

# ravel() 返回视图(如果可能)
r = arr.ravel()
print("ravel:", r)
r[0] = 999
print("修改 ravel 后原数组:\n", arr)   # 原数组改变(因为是视图)

# flatten() 返回副本
f = arr.flatten()
f[0] = 888
print("修改 flatten 后原数组:\n", arr)   # 原数组不变

输出:

ravel: [1 2 3 4 5 6]
修改 ravel 后原数组:
 [[999   2   3]
 [  4   5   6]]
修改 flatten 后原数组:
 [[999   2   3]
 [  4   5   6]]

5. 转置:transpose()T 属性

对于二维数组,Ttranspose() 效果相同(交换轴)。对于高维数组,transpose() 可以指定轴顺序。

arr = np.arange(6).reshape(2, 3)
print("原数组:\n", arr)

# T 属性
print("arr.T:\n", arr.T)

# transpose() 方法,默认也是交换轴
print("arr.transpose():\n", arr.transpose())

# 高维数组转置,指定轴顺序
arr3d = np.arange(24).reshape(2, 3, 4)
print("原三维数组 shape:", arr3d.shape)
arr_t = arr3d.transpose(1, 0, 2)   # 将轴0和轴1交换
print("transpose(1,0,2) shape:", arr_t.shape)

输出:

原数组:
 [[0 1 2]
 [3 4 5]]
arr.T:
 [[0 3]
 [1 4]
 [2 5]]
arr.transpose():
 [[0 3]
 [1 4]
 [2 5]]
原三维数组 shape: (2, 3, 4)
transpose(1,0,2) shape: (3, 2, 4)

6. 交换轴:swapaxes()

swapaxes(axis1, axis2) 交换数组的两个轴,返回视图。

arr = np.arange(24).reshape(2, 3, 4)
print("原数组 shape:", arr.shape)
swapped = arr.swapaxes(0, 2)   # 交换第1轴和第3轴
print("swapaxes(0,2) shape:", swapped.shape)

输出:

原数组 shape: (2, 3, 4)
swapaxes(0,2) shape: (4, 3, 2)

7. 增加或删除维度:newaxis, expand_dims(), squeeze()

np.newaxisNone 可以增加一个新维度。expand_dims() 显式指定位置。squeeze() 删除所有长度为1的维度。

arr = np.array([1, 2, 3])   # shape (3,)

# 使用 newaxis
row_vec = arr[np.newaxis, :]   # shape (1, 3)
col_vec = arr[:, np.newaxis]   # shape (3, 1)
print("row_vec shape:", row_vec.shape)
print("col_vec shape:", col_vec.shape)

# 使用 expand_dims
expanded = np.expand_dims(arr, axis=1)   # 在索引1处增加维度 -> (3,1)
print("expand_dims(axis=1) shape:", expanded.shape)

# squeeze 去除单维度
arr_sq = np.array([[[1], [2], [3]]])   # shape (1,3,1)
squeezed = np.squeeze(arr_sq)           # shape (3,)
print("squeeze 后 shape:", squeezed.shape)

输出:

row_vec shape: (1, 3)
col_vec shape: (3, 1)
expand_dims(axis=1) shape: (3, 1)
squeeze 后 shape: (3,)

8. 合并数组

NumPy提供了多种合并数组的方式:concatenate, stack, vstack, hstack 等。

np.concatenate()

沿现有轴拼接数组,要求除拼接轴外其他维度相同。

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
# 沿0轴拼接(垂直方向)
c = np.concatenate((a, b), axis=0)
print("concatenate axis=0:\n", c)

# 沿1轴拼接(水平方向),需要列数匹配
d = np.concatenate((a, b.T), axis=1)   # b.T 变成 (2,1)
print("concatenate axis=1:\n", d)

输出:

concatenate axis=0:
 [[1 2]
 [3 4]
 [5 6]]
concatenate axis=1:
 [[1 2 5]
 [3 4 6]]

np.vstack()np.hstack()

分别垂直(行)和水平(列)堆叠,更方便。

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print("vstack:\n", np.vstack((a, b)))   # 变成二维 (2,3)
print("hstack:", np.hstack((a, b)))     # 一维拼接 (6,)

输出:

vstack:
 [[1 2 3]
 [4 5 6]]
hstack: [1 2 3 4 5 6]

np.stack()

沿新轴堆叠,增加一个维度。

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.stack((a, b), axis=0)   # shape (2,3)
d = np.stack((a, b), axis=1)   # shape (3,2)
print("stack axis=0:\n", c)
print("stack axis=1:\n", d)

输出:

stack axis=0:
 [[1 2 3]
 [4 5 6]]
stack axis=1:
 [[1 4]
 [2 5]
 [3 6]]

9. 分割数组

使用 split, vsplit, hsplit 将数组分割成多个子数组。

arr = np.arange(12).reshape(3, 4)
print("原数组:\n", arr)

# 水平分割(按列):将数组分成3块
h1, h2, h3 = np.hsplit(arr, 3)   # 每块1列(4列/3?实际如果无法均分会报错,这里4列分成3块会报错,所以改为2块)
h1, h2 = np.hsplit(arr, 2)   # 分成2块,每块2列
print("hsplit 第1块:\n", h1)
print("hsplit 第2块:\n", h2)

# 垂直分割(按行)
v1, v2, v3 = np.vsplit(arr, 3)   # 每块1行,共3行
print("vsplit 第1块:\n", v1)

# 通用的 split,指定轴
s1, s2 = np.split(arr, [1], axis=1)   # 在第1列之后分割,得到两块:前1列,后3列
print("split at [1] axis=1:\n", s1, "\n", s2)

输出:

原数组:
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
hsplit 第1块:
 [[0 1]
 [4 5]
 [8 9]]
hsplit 第2块:
 [[ 2  3]
 [ 6  7]
 [10 11]]
vsplit 第1块:
 [[0 1 2 3]]
split at [1] axis=1:
 [[0]
 [4]
 [8]]
 [[ 1  2  3]
 [ 5  6  7]
 [ 9 10 11]]

10. 视图与副本总结

操作返回类型说明
reshape()视图(尽可能)不改变原数组
resize() (方法)原地修改ndarray.resize() 无返回值
np.resize()副本可调整总元素数
ravel()视图(尽可能)返回一维视图
flatten()副本返回一维副本
transpose()/T视图转置
swapaxes()视图交换轴
squeeze()视图去除单维度
concatenate()/stack()副本合并数组(总是新数组)
split()视图列表分割后每个子数组是原数组的视图
提示: 掌握视图与副本的区别可以避免意外修改数据,并在性能优化时做出正确选择。

总结

本节介绍了NumPy中常用的数组形状操作,包括改变形状、展平、转置、增加/删除维度、合并与分割。灵活运用这些函数,可以高效地准备和组织数据,为后续的数学运算和分析打下基础。接下来,我们将学习NumPy的数组运算。