reshape 只改「怎么看」,不改数据,新数组和原数组共享内存。 reshape 里写 -1,让 NumPy 自己算那一维,一行最多一个 -1。 append 每次都新建整个数组,循环里用它就是灾难。
这一篇前半段讲 reshape、转置、展平,后半段讲增删改查。 共同点是:它们几乎都返回新数组,原数组不动。唯一例外是写视图。
一、reshape:只改看法,不改数据
12 个元素的一维数组,形状是 (12,)。
reshape(3, 4) 之后形状变成 (3, 4),元素个数还是 12。
它不改数据本身,只改「怎么看它」。内存里还是那 12 个数。
所以 B 和 A 共享同一块内存。B[0, 0] = 99 之后,A 的第 0 个也变成 99。
元素总数必须对得上。12 个元素只能塑成 1×12、2×6、3×4、4×3、6×2、12×1。
对不上就报错,不会帮你补 0 或者截掉。
二、-1 让 NumPy 自己算
A.reshape(3, -1) 给形状 (3, 4),那个 4 是它自己算出来的。
A.reshape(-1, 6) 给 (2, 6),它算出 2。
一行里最多只能有一个 -1。两个 -1 它没法唯一确定,直接报错。
最常用的是 reshape(-1, 1),把一维数组竖成一列。
做矩阵乘法、拼 DataFrame 的时候,行向量和列向量不是一个东西,这一步躲不开。
三、转置与展平
2 行 3 列的矩阵,.T 之后是 3 行 2 列,行变列、列变行。
转置对二维是翻转,对一维没效果——一维没有行列可分。
展平有两个函数:ravel() 和 flatten(),结果都是 6 个数排成一行。
差别在返回什么。ravel 可能返回视图,flatten 一定返回复制。 省内存用 ravel,要改又要安全用 flatten。名字不好记就记「flatten 更笨更安全」。
四、增删:append 每次都新建
np.append(A, 4) 给 [1 2 3 4],np.delete(A, 1) 删掉第 2 个。
看着像原地修改,其实每次都新建一个数组,原数组 A 一个字都没变。
所以循环里 append 是灾难:每加一个元素,就要把前面所有元素复制一遍。
1000 次 append 大约要复制 50 万次元素。这和 Pandas 里逐行 concat 是同一个毛病。 NumPy 没有原地插入。真要频繁增删,用 Python 列表,最后再一次性转数组。
五、改与查:where
np.where(条件) 只给一个参数时,返回满足条件的下标。
np.where(条件, 值1, 值2) 给三个参数时,条件真取值 1,假取值 2。
所以「把不及格的改成 60」写成 np.where(A < 60, 60, A)。
注意参数顺序:先写真值再写假值,不是先旧值后新值。这里最容易写反。 where 返回新数组,不改原数组。它和 Pandas 里的 mask 是同一件事。
六、动手 Demo:重塑与增、删、改、查
先验证 reshape 和原数组共享内存,再看 -1 怎么自动算。 然后转置、展平、append、where,每步都打出来看形状。
import numpy as np
A = np.arange(12)
print("原数组 %d 个元素,形状 %s" % (A.size, A.shape))
B = A.reshape(3, 4)
print("reshape 后形状 %s,元素还是 %d" % (B.shape, B.size))
print(B)
B[0, 0] = 99
print("改 B 的第一个,A 变成 %s" % A[:4])
A = np.arange(12)
print("reshape(3, -1) 形状 %s" % (A.reshape(3, -1).shape,))
print("reshape(-1, 6) 形状 %s" % (A.reshape(-1, 6).shape,))
M = np.arange(1, 7).reshape(2, 3)
print("2 行 3 列:")
print(M)
print("转置形状 %s:" % (M.T.shape,))
print(M.T)
print("ravel %s" % M.ravel())
print("flatten %s" % M.flatten())
A = np.array([1, 2, 3])
print("append 一个 4:%s" % np.append(A, 4))
print("append 两个:%s" % np.append(A, [4, 5]))
print("delete 第 2 个:%s" % np.delete(A, 1))
print("原数组还是 %s" % A)
S = np.array([88, 92, 75, 85, 60])
print("S > 80 的位置:%s" % np.where(S > 80)[0])
print("不满足的填 0:%s" % np.where(S > 80, S, 0))
print("低于 60 的改成 60:%s" % np.where(S < 60, 60, S))
实跑输出:
--------------------------------------------------------------------
重塑与增、删、改、查
--------------------------------------------------------------------
① reshape:只改看法,不改数据
--------------------------------------------------------------------
原数组 12 个元素,形状 (12,)。
reshape(3, 4) 之后形状 (3, 4),元素个数还是 12。
[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
reshape 不改数据本身,只改「怎么看它」。B 和 A 共享同一块内存。
改 B 的第一个,A 的第 0 个也跟着变了:[99 1 2 3]
! 元素总数必须对得上。12 个元素只能重塑成 1×12、2×6、3×4、4×3、6×2、12×1。
② -1 让 NumPy 自己算
--------------------------------------------------------------------
reshape(3, -1) → 形状 (3, 4),它自己算出 4。
reshape(-1, 6) → 形状 (2, 6),它自己算出 2。
一行里最多只能有一个 -1。两个 -1 它没法算。
! -1 是「这一维你替我算」。写 reshape(-1, 1) 把一维变成列向量,这个最常用。
③ 转置与展平
--------------------------------------------------------------------
2 行 3 列:
[1, 2, 3]
[4, 5, 6]
转置 .T 之后形状 (3, 2):
[1, 4]
[2, 5]
[3, 6]
展平 ravel 是视图,flatten 是复制:[1 2 3 4 5 6] / [1 2 3 4 5 6]
! ravel 可能返回视图,flatten 一定返回复制。要改就用 flatten,要省内存用 ravel。
④ 增删:append 和 delete
--------------------------------------------------------------------
append 一个 4:[1 2 3 4]
append 整个数组:[1 2 3 4 5]
delete 第 1 个:[1 3]
append 每次都新建一个数组,不改原数组。原数组 A 还是 [1 2 3]。
! 循环里 append 是灾难:每次都要复制整个数组,和 Pandas 的逐行 concat 一个道理。
⑤ 改与查:where
--------------------------------------------------------------------
where(条件) 给位置:A > 80 的位置是 [0 1 3]
where(条件, 值1, 值2) 给替换:[88 92 0 85 0]
把不及格的改成 60:[88 92 75 85 60]
where 返回新数组,不改原数组。
! where 是「按条件换值」的标准写法,等价于 Pandas 里的 mask。
最后一行打出来和原数组一模一样。因为 S 里最低就是 60,一个都没被改到。 「跑了但没变化」也是一种信号:先确认条件真的命中了,再怀疑写法。 append 那三行能看出原数组一直是 [1 2 3],这就是「每次都新建」的直接证据。
30 秒实验
- 把 np.where(S < 60, 60, S) 里的 60 改成 70,看这次哪些数被改了。
- 把 reshape(3, -1) 改成 reshape(5, -1),看报什么错。
- 想一想:为什么 NumPy 不支持原地插入?这和它「一整块连续内存」的设计有什么关系?
常见误区
- 以为 reshape 复制了数据:它只改看法,改新数组会连带改原数组。
- 写两个 -1:没法唯一确定,直接报错。
- 循环里 append:每次都复制整个数组,数据一大就卡死。
- where 三个参数的顺序写反:先真值后假值,写反了逻辑正好颠倒。
自测三问(点开对答案)
1. reshape 之后,新数组和原数组是什么关系? —— 共享同一块内存,改新数组会改动原数组。它就是同一份数据的另一种看法。 2. ravel 和 flatten 的区别? —— ravel 可能返回视图,flatten 一定返回复制。要改就用 flatten。 3. np.where(A < 60, 60, A) 是什么意思? —— 条件真的位置取 60,假的保持 A 原值。也就是把低于 60 的抬到 60。形状和内容都能改了。下一篇讲矩阵:星号和 @ 到底差在哪。
- ☐ (本篇完,下一篇 6.9 矩阵的基本操作)