第 5 章 形状操作
学习目标
- 掌握
reshape与-1自动推断 - 区分
ravel与flatten(视图与副本) - 掌握转置
T与swapaxes - 掌握拼接:
concatenate、vstack、hstack、stack - 掌握拆分:
split、vsplit、hsplit - 会用
newaxis、squeeze增删维度
5.1 形状与 reshape
形状(shape)是描述数组各维度长度的元组。reshape(新形状) 在不改变元素个数与顺序的前提下重新排列维度,第 2 章已见基本用法。
一个实用技巧:-1 表示「这个维度自动推断」,一次 reshape 只需指定一个维度,另一个交给 NumPy:
import numpy as np
a = np.arange(12)
print(a.reshape(2, -1)) # 2 行,列数自动 = 12/2 = 6
print(a.reshape(-1, 4)) # 4 列,行数自动 = 12/4 = 3输出:
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]注意 -1 只能出现一次。元素总数不能被指定维度整除时,-1 推断失败会报错:
>>> np.arange(12).reshape(5, -1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: cannot reshape array of size 12 into shape (5,newaxis)5.2 ravel 与 flatten:展平
展平是把多维数组变成一维。ravel() 返回视图(尽量不复制,修改会影响原数组);flatten() 返回副本(总是新数组)。
import numpy as np
b = np.arange(6).reshape(2, 3)
r = b.ravel() # 视图
r[0] = 99
print(b) # 原数组被修改输出:
[[99 1 2]
[ 3 4 5]]改用 flatten() 则不互相影响:
import numpy as np
b = np.arange(6).reshape(2, 3)
f = b.flatten() # 副本
f[0] = -1
print(b) # 原数组不变
print(f)输出:
[[0 1 2]
[3 4 5]]
[-1 1 2 3 4 5]5.3 转置
转置交换行列:原 (m, n) 数组变成 (n, m),用属性 T:
import numpy as np
m = np.arange(6).reshape(2, 3)
print(m)
print(m.T)
print(m.T.shape)输出:
[[0 1 2]
[3 4 5]]
[[0 3]
[1 4]
[2 5]]
(3, 2)m.T 是视图,修改它也会影响原数组。三维以上数组的轴交换用 swapaxes(轴1, 轴2):
import numpy as np
m = np.arange(6).reshape(2, 3)
print(m.swapaxes(0, 1)) # 等价于转置输出:
[[0 3]
[1 4]
[2 5]]5.4 拼接:把数组连起来
np.concatenate([数组1, 数组2, ...]) 沿已有轴拼接,默认 axis=0。
一维拼接直接连成一串:
import numpy as np
x = np.array([1, 2, 3])
y = np.array([4, 5])
print(np.concatenate([x, y]))输出:
[1 2 3 4 5]二维沿行(axis=0,上下叠)或沿列(axis=1,左右拼):
import numpy as np
m1 = np.array([[1, 2], [3, 4]])
m2 = np.array([[5, 6]]) # 1 行 2 列
m3 = np.array([[7], [8]]) # 2 行 1 列
print(np.concatenate([m1, m2], axis=0)) # 行方向:上下
print(np.concatenate([m1, m3], axis=1)) # 列方向:左右输出:
[[1 2]
[3 4]
[5 6]]
[[1 2 7]
[3 4 8]]记忆法:axis=0 是行方向(数组向下生长),axis=1 是列方向(数组向右生长)。axis 的完整含义第 6 章讲。
concatenate 要求:除拼接轴外,其余维度必须一致——上例中 axis=0 拼接要求列数相同(都是 2),axis=1 拼接要求行数相同(都是 2)。
vstack(垂直拼接)与 hstack(水平拼接)是常用简写,省去写 axis:
import numpy as np
m1 = np.array([[1, 2], [3, 4]])
m2 = np.array([[5, 6]])
m3 = np.array([[7], [8]])
print(np.vstack([m1, m2])) # 等价 axis=0
print(np.hstack([m1, m3])) # 等价 axis=1输出:
[[1 2]
[3 4]
[5 6]]
[[1 2 7]
[3 4 8]]np.stack 与 concatenate 不同:它新增一个维度,把每个数组作为新维度的元素:
import numpy as np
s = np.stack([np.array([1, 2]), np.array([3, 4])])
print(s)
print(s.shape) # 2 个一维数组 → (2, 2)输出:
[[1 2]
[3 4]]
(2, 2)5.5 拆分:把数组切开
np.split(数组, 份数) 沿 axis=0 均分,返回数组的列表:
import numpy as np
a = np.arange(12)
parts = np.split(a, 3)
print(parts) # 3 份的列表
print(parts[1]) # 取第 2 份输出:
[array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([ 8, 9, 10, 11])]
[4 5 6 7]二维拆分:按行拆用 vsplit,按列拆用 hsplit:
import numpy as np
m = np.arange(12).reshape(3, 4)
print(np.vsplit(m, 3)) # 3 行,每行一块
print(np.hsplit(m, 2)[0]) # 4 列均分 2 块,取第 1 块输出:
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8, 9, 10, 11]])]
[[0 1]
[4 5]
[8 9]]注意:均分要求总长度能被份数整除,否则报 ValueError。
5.6 增删维度:newaxis 与 squeeze
数组[:, np.newaxis] 在指定位置插入一个长度为 1 的维度,把一维变「列向量」:
import numpy as np
v = np.array([1, 2, 3])
print(v[:, np.newaxis])
print(v[:, np.newaxis].shape)
print(v[np.newaxis, :].shape)输出:
[[1]
[2]
[3]]
(3, 1)
(1, 3)用途:让一维数组与二维数组「对齐」后广播(第 7 章的关键操作)。等价写法是 np.expand_dims(v, axis=1)。
squeeze() 删除所有长度为 1 的维度:
import numpy as np
t = np.array([[1, 2, 3]]) # (1, 3)
print(t.shape, t.squeeze(), t.squeeze().shape)输出:
(1, 3) [1 2 3] (3,)动手实践
- 创建
np.arange(24),分别 reshape 成(2, 12)、(4, 6)、(-1, 8),打印各形状。 - 创建 3×4 数组,用
T转置并打印形状;验证转置是视图:修改转置结果,检查原数组是否变化。 - 把两个 2×3 数组分别做
vstack与hstack,观察形状差异。 - 创建 12 个元素的数组,用
np.split(a, 3)拆成 3 份并分别求和,验证三份之和等于原数组总和。
常见错误
| 错误写法 | 现象 | 原因 |
|---|---|---|
a.reshape(5, -1)(12 元素) | ValueError: cannot reshape array of size 12 into shape (5,newaxis) | 5 不能整除 12,-1 推断不出整数 |
np.concatenate 两个列数不同的数组(axis=0) | ValueError: all the input array dimensions except for the concatenation axis must match exactly | 拼接时非拼接轴必须一致 |
m.resize(3, 2) 后打印 | 输出 None | resize 是就地修改,返回 None;返回新数组用 np.resize |
修改 ravel() 结果 | 原数组变了 | ravel 返回视图;要副本用 flatten() |
np.split(a, 3) 时长度不能整除 | ValueError: array split does not result in an equal division | 均分要求整除 |
用 stack 想拼接二维数组 | 多出一个维度 | stack 是加维度堆叠;拼接用 concatenate/vstack/hstack |
章末练习
基础
np.arange(15).reshape(-1, 5)的结果形状是什么?写出结果。- 创建 2×4 数组,用
ravel与flatten各展平一次,打印两个结果。 - 用
T转置 2×5 数组,打印转置后的形状。
提高
- 把三个一维数组
[1, 2]、[3, 4]、[5, 6]用vstack拼成 3×2 数组,再转置成 2×3,打印最终结果。 - 用
hsplit把 4×6 数组按列拆成 3 块 4×2 数组,验证各块拼接回去等于原数组。
挑战
- 解释
np.stack([a, b], axis=1)与np.concatenate([a, b], axis=1)在二维输入下的形状差异,并用代码验证。 - 写代码把一维数组
[1, 2, 3]变成(3, 1)的列向量,再用np.broadcast_to或逐元素运算让它与np.array([[10, 20, 30]])相加,观察广播效果(第 7 章预告,先动手试试)。
章末自测
每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。
np.arange(12).reshape(2, -1)的结果形状是?- A.
(2, 6) - B.
(2, 12) - C.
(6, 2) - D. 报错
- A.
- 下列哪个返回视图(不是副本)?
- A.
flatten() - B.
ravel() - C.
copy() - D.
np.array(a)
- A.
np.array([[1, 2], [3, 4]]).T的形状是?- A.
(2, 2) - B.
(2, 1) - C.
(1, 2) - D.
(4,)
- A.
np.vstack相当于concatenate的哪个 axis?- A.
axis=1 - B.
axis=0 - C. 不需要 axis
- D.
axis=-1
- A.
np.concatenate([np.array([[1, 2]]), np.array([[3, 4]])], axis=0)的结果形状是?- A.
(1, 4) - B.
(2, 2) - C.
(4, 1) - D. 报错
- A.
np.hsplit(m, 2)对 4 列数组的结果是?- A. 2 个 2 列的数组
- B. 2 个 4 列的数组
- C. 4 个数组
- D. 报错
v[:, np.newaxis]把形状(3,)变成?- A.
(3, 3) - B.
(3, 1) - C.
(1, 3) - D.
(1, 1)
- A.
np.array([[1, 2, 3]]).squeeze()的形状是?- A.
(1, 3) - B.
(3,) - C.
(1, 1) - D. 报错
- A.
np.stack([np.array([1, 2]), np.array([3, 4])])的形状是?- A.
(2,) - B.
(2, 2) - C.
(4,) - D.
(1, 4)
- A.
- reshape 报错
cannot reshape array of size 12 into shape (5,newaxis)的原因?- A. 12 个元素太多
- B. 5 不能整除 12
- C. newaxis 拼写错误
- D. reshape 只能用于一维
