阶段 09 讲义
数学 11h:线代 · 矩阵运算与逆矩阵
编程 9h:元组与字符串深入
第一部分 · 数学
阶段 8 我们学了行列式,算出来是"一个数"。这一阶段学矩阵,它本身是"一张表"。
主角是逆矩阵。你可以先把它理解成"矩阵里的除法":有了它,$AX=b$ 就能像 $2x=6$ 那样"两边同除"解出来。
但矩阵比数麻烦得多。不是每个矩阵都有逆,乘法不能交换顺序,连"同除"都要小心。这一阶段就是把这些坑摸清。
1 矩阵是什么
直觉上,矩阵就是一张矩形的数表。
$$A = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}, \qquad B = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix}$$
记号约定:
| 说法 | 含义 | 例子 |
|---|---|---|
| $m \times n$ 矩阵 | $m$ 行、$n$ 列 | $B$ 是 $2\times3$ |
| $a_{ij}$ | 第 $i$ 行、第 $j$ 列的元素 | 上面的 $a_{12}=2$ |
| 方阵 | 行数 = 列数 | $A$ 是 $2$ 阶方阵 |
| 行向量 / 列向量 | 只有一行的 / 只有一列的矩阵 | — |
💡 行列式 vs 矩阵(阶段 8 讲过,这里再强调一次):行列式用竖线 $|\;|$,算出来是一个数;矩阵用圆括号或方括号,本身是一张表。写 $|A|$ 或 $\det(A)$ 的时候算的是矩阵的行列式,那才是数。
矩阵更深一层的身份,是一个线性变换。
一个矩阵乘以一个向量,相当于把这个向量旋转、拉伸或投影到别的地方。比如
$$\begin{pmatrix} 0 & -1 \\ 1 & 0 \end{pmatrix}\begin{pmatrix} 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 1 \end{pmatrix}$$
左边的矩阵把 $(1,0)$ 转到了 $(0,1)$,它是个旋转 $90°$ 的机器。
⭐ 换个角度看,矩阵不只是"存数据的表格",它是对空间做操作的动作。
认知科学里,感官输入到行为输出、神经活动到血氧信号,都可以建模成这种"操作"。
2 矩阵加法与数乘(简单)
同型(行数、列数都一样)的两个矩阵才能相加,对应位置相加:
$$\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} + \begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix} = \begin{pmatrix} 1+5 & 2+6 \\ 3+7 & 4+8 \end{pmatrix} = \begin{pmatrix} 6 & 8 \\ 10 & 12 \end{pmatrix}$$
数乘:一个数乘矩阵,每个元素都要乘:
$$3\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} = \begin{pmatrix} 3 & 6 \\ 9 & 12 \end{pmatrix}$$
💡 这两条和行列式不一样。行列式是"某行乘 $k$,整个行列式乘 $k$"(阶段 8 性质 3),不是每个元素乘。
3 矩阵乘法 ⭐⭐(重点,最容易错)
规则一句话:左边取"行",右边取"列",对应相乘再相加。
$$\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}\begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}$$
算第 1 行第 1 列的位置:取左边第 1 行 $(1,2)$,右边第 1 列 $(0,1)$,
$$1\times0 + 2\times1 = 2$$
算第 1 行第 2 列:左边第 1 行 $(1,2)$ × 右边第 2 列 $(1,0)$,
$$1\times1 + 2\times0 = 1$$
四个位置都算完:
$$\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}\begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} 2 & 1 \\ 4 & 3 \end{pmatrix}$$
维度规则(先查这个,再动手算):
$$(m\times n)\cdot(n\times p) = (m\times p)$$
左边列数必须等于右边行数,否则根本不能相乘。
3.1 ⚠️ 三条铁律
铁律一:顺序不能换
$$\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}\begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} 2 & 1 \\ 4 & 3 \end{pmatrix}, \qquad \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} = \begin{pmatrix} 3 & 4 \\ 1 & 2 \end{pmatrix}$$
同一对数,换个顺序,结果完全不同。在数的世界里 $3\times5=5\times3$,矩阵世界里这条不成立。
⭐ $AB \ne BA$ 是常态,$AB=BA$ 才是巧合。
铁律二:不满足消去律
在数的世界里,$ab=ac$ 且 $a\ne0$,就能约掉 $a$ 得 $b=c$。矩阵里不行:
$$\begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix}\begin{pmatrix} 1 & 1 \\ 1 & 1 \end{pmatrix} = \begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix}\begin{pmatrix} 1 & 1 \\ 5 & 5 \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 0 & 0 \end{pmatrix}$$
左边相等,但右边的两个矩阵明显不一样(一个是 $\begin{pmatrix}1&1\\1&1\end{pmatrix}$,另一个是 $\begin{pmatrix}1&1\\5&5\end{pmatrix}$)。
原因在于 $\begin{pmatrix}1&0\\0&0\end{pmatrix}$ 把第二行"抹掉"了,所以第二行是什么都无所谓。这个矩阵没有逆(下一节讲)。
💡 为什么会在意这个:回归分析里我们经常要"两边同乘 $X^T$"然后约掉,只有在相关矩阵可逆时才能这么做。这正是阶段 8 讲的多重共线性问题在矩阵语言下的样子。
铁律三:有零因子
两个都不是零矩阵的矩阵,乘起来可以是零矩阵:
$$\begin{pmatrix} 0 & 1 \\ 0 & 0 \end{pmatrix}\begin{pmatrix} 0 & 1 \\ 0 & 0 \end{pmatrix} = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix}$$
数的世界里"两数相乘为零 ⇒ 至少一个为零",矩阵里不成立。
4 转置
转置 $A^T$ 就是把行列互换(第 $i$ 行变成第 $i$ 列)。
$$\begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix}^T = \begin{pmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{pmatrix}$$
两条重要性质:
| 性质 | 说明 |
|---|---|
| $(A^T)^T = A$ | 转两次转回来 |
| $(A+B)^T = A^T + B^T$ | 加法可拆 |
| $(AB)^T = B^T A^T$ | ⚠️ 顺序反了! |
⚠️ $(AB)^T = B^TA^T$ 而不是 $A^TB^T$,这是初学者最常写错的一条。
记忆法是把它想成照镜子,"先穿袜子再穿鞋"在镜子里变成"先脱鞋再脱袜子",顺序必然颠倒。
5 单位矩阵与对角矩阵
单位矩阵 $I_n$ 的主对角线全是 1,其余全是 0。
$$I_2 = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}, \qquad I_3 = \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix}$$
它的作用就像数字里的 $1$:
$$AI = IA = A$$
对角矩阵只有主对角线可能非零。$n$ 个数的对角矩阵,作用就是把每个分量分别拉伸相应倍数。
6 逆矩阵 ⭐⭐(本阶段的核心)
6.1 定义
对方阵 $A$,如果存在矩阵 $B$ 使得
$$AB = BA = I$$
那么称 $A$ 可逆,$B$ 就是 $A$ 的逆矩阵,记作 $A^{-1}$。
直觉上它就是矩阵里的倒数。$A$ 做了一次变换,$A^{-1}$ 把它变回来。
⚠️ 两个前提:必须是方阵,而且 $\det(A) \ne 0$。
$\det(A) = 0$ 的矩阵叫奇异矩阵,没有逆,就像数字里 $0$ 没有倒数。
6.2 $2\times2$ 逆矩阵公式(必须背下来)
$$A = \begin{pmatrix} a & b \\ c & d \end{pmatrix} \quad\Longrightarrow\quad A^{-1} = \frac{1}{ad-bc}\begin{pmatrix} d & -b \\ -c & a \end{pmatrix}$$
口诀:主对角互换,副对角变号,除以行列式。
6.3 完整例子
求 $A = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}$ 的逆矩阵。
第一步:算行列式
$$\det(A) = 1\times4 - 2\times3 = 4 - 6 = -2 \ne 0 \quad ✓ \text{可逆}$$
第二步:套公式(主对角 $1,4$ 互换;副对角 $2,3$ 变号)
$$A^{-1} = \frac{1}{-2}\begin{pmatrix} 4 & -2 \\ -3 & 1 \end{pmatrix} = \begin{pmatrix} -2 & 1 \\ \dfrac{3}{2} & -\dfrac{1}{2} \end{pmatrix}$$
第三步:验算(这一步别省,它救过很多人)
$$AA^{-1} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}\begin{pmatrix} -2 & 1 \\ \frac{3}{2} & -\frac{1}{2} \end{pmatrix} = \begin{pmatrix} -2+3 & 1-1 \\ -6+6 & 3-2 \end{pmatrix} = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} = I \quad ✓$$
6.4 逆矩阵的性质(和"倒数"对照着记)
| 矩阵 | 数字 | 注意 |
|---|---|---|
| $(A^{-1})^{-1} = A$ | $(a^{-1})^{-1} = a$ | 一致 |
| $\det(A^{-1}) = \dfrac{1}{\det(A)}$ | 无对应 | 逆的行列式是倒过来 |
| $(A^T)^{-1} = (A^{-1})^T$ | 无对应 | 转置和求逆可交换 |
| $(AB)^{-1} = B^{-1}A^{-1}$ | $(ab)^{-1} = b^{-1}a^{-1}$ | ⚠️ 顺序反了 |
数值验证(拿 $P = \begin{pmatrix}1&2\\3&4\end{pmatrix}$,$Q = \begin{pmatrix}0&1\\1&0\end{pmatrix}$ 实算):
| 检查 | 结果 |
|---|---|
| $PQ = \begin{pmatrix}2&1\\4&3\end{pmatrix}$ | det $(PQ) = 2$ |
| $(PQ)^{-1}$ | $\begin{pmatrix}\frac{3}{2}&-\frac{1}{2}\\-2&1\end{pmatrix}$ |
| $Q^{-1}P^{-1}$ | $\begin{pmatrix}\frac{3}{2}&-\frac{1}{2}\\-2&1\end{pmatrix}$ ✓ 相等 |
| $P^{-1}Q^{-1}$ | $\begin{pmatrix}0&1\\1&0\end{pmatrix}$... ✗ 不相等 |
| $\det(P) = -2$,$\det(P^{-1})$ | $-\frac{1}{2}$ ✓ |
$(AB)^{-1} = B^{-1}A^{-1}$ 这一条必须记住顺序,它和 $(AB)^T = B^TA^T$ 是同一个"顺序颠倒"的规律。
7 用逆矩阵解方程组
有了逆矩阵,方程组 $AX = b$ 就能"两边同乘 $A^{-1}$":
$$AX = b \quad\Longrightarrow\quad \underbrace{A^{-1}A}_{I}X = A^{-1}b \quad\Longrightarrow\quad X = A^{-1}b$$
这就是矩阵版的两边同除。
7.1 完整例子
求解 $\begin{cases} 2x + y = 5 \\ x - 3y = -1 \end{cases}$
写成矩阵形式:
$$A = \begin{pmatrix} 2 & 1 \\ 1 & -3 \end{pmatrix}, \qquad X = \begin{pmatrix} x \\ y \end{pmatrix}, \qquad b = \begin{pmatrix} 5 \\ -1 \end{pmatrix}$$
第一步:算行列式
$$\det(A) = 2\times(-3) - 1\times1 = -7 \ne 0 \quad ✓$$
第二步:求逆(主对角互换,副对角变号)
$$A^{-1} = \frac{1}{-7}\begin{pmatrix} -3 & -1 \\ -1 & 2 \end{pmatrix} = \begin{pmatrix} \frac{3}{7} & \frac{1}{7} \\ \frac{1}{7} & -\frac{2}{7} \end{pmatrix}$$
第三步:乘 $b$
$$X = A^{-1}b = \begin{pmatrix} \frac{3}{7} & \frac{1}{7} \\ \frac{1}{7} & -\frac{2}{7} \end{pmatrix}\begin{pmatrix} 5 \\ -1 \end{pmatrix} = \begin{pmatrix} \frac{15-1}{7} \\ \frac{5+2}{7} \end{pmatrix} = \begin{pmatrix} 2 \\ 1 \end{pmatrix}$$
验根:$2(2)+1 = 5$ ✓,$2 - 3(1) = -1$ ✓
💡 和阶段 8 的克拉默法则比较:结果一样(都是 $\frac{D_i}{D}$),但逆矩阵法更"结构化"。它把"解方程组"变成了"求一个矩阵",$A^{-1}$ 一旦求出,换任何 $b$ 都能立刻得到解。
这在统计里很关键:$X^TX$ 的逆只有一个,但 $y$ 会变(换一批被试就要重解一次)。
8 三阶逆矩阵:伴随矩阵法
$2\times2$ 有口诀,$3\times3$ 就得用公式:
$$A^{-1} = \frac{1}{\det(A)}\operatorname{adj}(A)$$
其中 $\operatorname{adj}(A)$ 叫伴随矩阵:把每个元素换成它的代数余子式,再转置。
代数余子式 $A_{ij}$ 是划掉第 $i$ 行第 $j$ 列,剩下的行列式,再乘 $(-1)^{i+j}$。
8.1 完整例子
求 $B = \begin{pmatrix} 1 & 2 & 3 \\ 0 & 1 & 4 \\ 5 & 6 & 0 \end{pmatrix}$ 的逆。
第一步:算行列式(按第一行展开)
$$\det(B) = 1\cdot\begin{vmatrix}1&4\\6&0\end{vmatrix} - 2\cdot\begin{vmatrix}0&4\\5&0\end{vmatrix} + 3\cdot\begin{vmatrix}0&1\\5&6\end{vmatrix}$$
$$= 1(0-24) - 2(0-20) + 3(0-5) = -24 + 40 - 15 = 1$$
行列式正好是 1,这是个"设计过"的好例子,因为除以 1 最省事。
第二步:求伴随矩阵(9 个代数余子式再转置),结果:
$$\operatorname{adj}(B) = \begin{pmatrix} -24 & 18 & 5 \\ 20 & -15 & -4 \\ -5 & 4 & 1 \end{pmatrix}$$
第三步:除以行列式($\det = 1$,所以逆矩阵就是它本身)
$$B^{-1} = \begin{pmatrix} -24 & 18 & 5 \\ 20 & -15 & -4 \\ -5 & 4 & 1 \end{pmatrix}$$
验算:$B \cdot B^{-1} = I_3$ ✓(我用 sympy 实算过)
⚠️ 手算 3 阶逆矩阵非常累(9 个代数余子式,每个都是 2 阶行列式)。
实际计算用"高斯-约当消元",把 $[A \mid I]$ 用行变换化成 $[I \mid A^{-1}]$。和阶段 8 克拉默法则的结论一致:伴随矩阵法的价值在理论(给出显式公式),不在计算。真要算就用消元法或直接
numpy.linalg.inv。
9 常见的坑
| 坑 | 说明 |
|---|---|
| 以为 $AB = BA$ | 一般不相等。写"两边同乘 $A$"时必须说清是左乘还是右乘 |
| $(AB)^T$ 写成 $A^TB^T$ | 应该是 $B^TA^T$,顺序要颠倒 |
| $(AB)^{-1}$ 写成 $A^{-1}B^{-1}$ | 应该是 $B^{-1}A^{-1}$,同上 |
| 维度不匹配就硬乘 | 先查 $(m\times n)(n\times p)$,左边列数 = 右边行数 |
| $\det=0$ 还去求逆 | 奇异矩阵无逆。先算行列式,为 0 就停下 |
| 非方阵求逆 | 只有方阵才谈得上逆矩阵 |
| 以为"两边同时约掉 $A$"总成立 | 只有 $A$ 可逆时才能约。这正是阶段 8 共线性的根源 |
| 逆矩阵公式漏除行列式 | $\frac{1}{ad-bc}$ 是灵魂,漏了就全错 |
10 🧠 这跟认知科学有什么关系
这一节的对应关系是我用代码实算验证过的,但心理学结论请对照文献。
协方差矩阵的逆就是精度矩阵(precision matrix),是多元统计和脑连接分析的核心对象。
10.1 先看事实
我生成了 200 个"被试"的两个测量值(比如两项认知任务的反应时),让它们高度相关:
| 量 | 实算结果 |
|---|---|
| $x_1$ 与 $x_2$ 的相关系数 | $0.9516$ |
| 协方差矩阵 $\Sigma$ | $\begin{pmatrix}0.8668 & 0.8497 \\ 0.8497 & 0.9198\end{pmatrix}$ |
| $\det(\Sigma)$ | $0.0753$ |
| $\Sigma$ 的条件数 | $40.4$ |
| $\Sigma^{-1}$(精度矩阵) | $\begin{pmatrix}12.21 & -11.28 \\ -11.28 & 11.51\end{pmatrix}$ |
10.2 精度矩阵为什么重要
① 它的对角线元素就是偏方差(partial variance)。
$\Sigma^{-1}$ 的第 $i$ 个对角元素是 $1/\text{Var}(x_i \mid \text{其他所有变量})$,也就是扣掉其他变量的影响之后,$x_i$ 还剩多少变异。
② 它的非对角元素直接对应偏相关。
偏相关公式是
$$r_{ij\cdot\text{其余}} = \frac{-(\Sigma^{-1})_{ij}}{\sqrt{(\Sigma^{-1})_{ii}(\Sigma^{-1})_{jj}}}$$
注意那个负号。上面算出来非对角是 $-11.28$(负的),代入负号后偏相关是正的。这个符号约定是初学者最容易搞反的地方。
③ 非对角元素为 0 ⇔ 条件独立。
这是高斯图模型(Gaussian Graphical Model)的地基:两个节点之间没有边,等价于"给定其他所有节点后"两者条件独立。
⭐ 这解释了一个反直觉的现象:两个变量边际相关很强(上面 $0.95$),但偏相关可能接近 0,只要中间有个"中介变量"把它们的关系解释掉了。
"相关"和"偏相关"回答的是不同的问题,这在认知科学的因果推断里是核心议题。
10.3 为什么"相关高"会毁掉回归
上面的条件数是 $40.4$,还不算灾难。但 $x_1$ 和 $x_2$ 的相关再往上推(比如 $0.999$),$\det(\Sigma) \to 0$,条件数 $\to\infty$,$\Sigma^{-1}$ 的数值彻底不可信。
这和阶段 8 的多重共线性是同一件事:回归系数 $\hat\beta = (X^TX)^{-1}X^Ty$,有个逆矩阵在中间。逆矩阵一旦数值不稳,系数就崩。
| 现象 | 矩阵语言 | 检查方法 |
|---|---|---|
| 自变量高度相关 | $(X^TX)$ 接近奇异 | 算条件数 |
| 系数符号翻转、数值爆炸 | 逆矩阵数值不稳 | 换一批样本看系数稳不稳 |
| 两个变量"谁贡献多少"说不清 | 精度矩阵非对角占主导 | 看相关矩阵 + 偏相关 |
⚠️ 诚实说明:
- 上面的相关系数、行列式、条件数、精度矩阵都是我用 numpy 实算的(可复现),"协方差矩阵的逆是精度矩阵""非对角为 0 ⇔ 条件独立"是数学/统计事实。
- 但"认知任务之间相关高意味着什么""偏相关接近 0 该不该下因果结论"是研究问题,请以多元统计教材和方法论文献为准。我保证数学,不保证研究规范。
11 数学作业 S9
- [ ] 1. 计算 $\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}\begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix}$ 和 $\begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix}\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}$,比较两个结果
- [ ] 2. 下列乘法哪些不能做?说明理由:
(a) $(2\times3)\cdot(3\times4)$ (b) $(3\times2)\cdot(3\times4)$ (c) $(2\times3)\cdot(2\times3)$
- [ ] 3. 求 $\begin{pmatrix} 2 & 1 \\ 5 & 3 \end{pmatrix}$ 的逆矩阵,并验算 $AA^{-1} = I$
- [ ] 4. 求 $\begin{pmatrix} 4 & -1 \\ 2 & 3 \end{pmatrix}$ 的逆矩阵
- [ ] 5. 判断 $\begin{pmatrix} 1 & 3 \\ 2 & 6 \end{pmatrix}$ 是否可逆,说明理由
- [ ] 6. 用逆矩阵解 $\begin{cases} 3x - y = 7 \\ x + 2y = 0 \end{cases}$,并用代入法验根
- [ ] 7. 已知 $A = \begin{pmatrix} 1 & 2 \\ 0 & 1 \end{pmatrix}$,求 $A^{-1}$,再验证 $(A^T)^{-1} = (A^{-1})^T$
- [ ] 8.(重点)举例说明:找两个都不是零矩阵的 $2\times2$ 矩阵,使 $AB = O$(零矩阵)
- [ ] 9.(Python)用 numpy 验证作业 3 和作业 6,并比较三种解法:逆矩阵法、
np.linalg.solve、克拉默法则
第 9 题参考代码:
import numpy as np
# --- 作业 3:2x2 求逆 ---
A = np.array([[2., 1.],
[5., 3.]])
Ainv = np.linalg.inv(A)
print("A^-1 =\n", Ainv)
print("A @ A^-1 =\n", A @ Ainv) # 应该接近单位阵
# 手算公式对照:1/(ad-bc) * [[d, -b], [-c, a]]
a, b, c, d = A[0, 0], A[0, 1], A[1, 0], A[1, 1]
det = a * d - b * c
manual = np.array([[d, -b], [-c, a]]) / det
print("手算公式 =\n", manual)
print("和 numpy 一致?", np.allclose(Ainv, manual))
# --- 作业 6:三种解法对照 ---
M = np.array([[3., -1.],
[1., 2.]])
rhs = np.array([7., 0.])
sol_inv = np.linalg.inv(M) @ rhs # 逆矩阵法
sol_solve = np.linalg.solve(M, rhs) # 专门解方程(推荐)
# 克拉默法则:逐列替换
D = np.linalg.det(M)
cramer = []
for i in range(2):
Mi = M.copy() # ← 必须 copy,否则会改到 M
Mi[:, i] = rhs
cramer.append(np.linalg.det(Mi) / D)
print("逆矩阵法 :", sol_inv)
print("solve :", sol_solve)
print("克拉默 :", cramer)
# 代入验根
assert np.allclose(M @ sol_solve, rhs), "验根失败"
print("验根通过")
💡 三种解法选哪个:
解一次方程组,用np.linalg.solve(最快最稳)。
要反复用同一个 $A$ 解不同的 $b$,就先算一次inv(A)存起来。
克拉默法则只在考试和证明里用(阶段 8 讲过的原因:$O(n\cdot n!)$)。
第二部分 · 编程
12 元组:不能改的列表
元组(tuple)和列表长得像,但用圆括号,而且创建之后不能修改。
point = (3, 5) # 元组
lst = [3, 5] # 列表
print(point[0]) # 3 ← 取值和列表一样
print(point[1]) # 5
lst[0] = 10 # ✓ 列表可以改
print(lst) # [10, 5]
point[0] = 10 # ✗ TypeError: 'tuple' object does not support item assignment
为什么要有"不能改"的东西?三个理由:
| 理由 | 说明 |
|---|---|
| 安全 | 传给别人时不怕被改坏 |
| 能当字典的键 | 列表不行,元组行(阶段 8 讲过"可哈希") |
| 语义清楚 | "坐标""RGB 颜色"这种天生不该变的东西,用元组表达意图 |
# 元组能当字典的键,列表不能
d = {(1, 2): "左上角", (3, 4): "右下角"}
print(d[(1, 2)]) # 左上角
⚠️ 单个元素的元组要加逗号:
(5)是整数 5,(5,)才是元组。
这个坑非常隐蔽,建议永远写上逗号。
print(type((5))) # <class 'int'> ← 不是元组!
print(type((5,))) # <class 'tuple'> ← 这才是
12.1 元组解包(最常用的功能)
point = (3, 5)
x, y = point # 一次拆成两个变量
print(x, y) # 3 5
# 交换两个变量(不用临时变量!)
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
函数返回多个值时,返回的就是元组:
def min_max(nums):
return min(nums), max(nums) # 返回一个元组
lo, hi = min_max([3, 1, 4, 1, 5])
print(lo, hi) # 1 5
💡
a, b = b, a为什么成立:右边b, a先被打包成一个元组 $(2,1)$,然后左边解包赋给 $a,b$。中间不经过任何临时变量,因为元组是不可变的,打包那一刻值就固定了。
12.2 元组 vs 列表:一张表
列表 [] |
元组 () |
|
|---|---|---|
| 能改吗 | 能 | 不能 |
| 能当字典键吗 | 不能 | 能 |
| 方法多吗 | 多(append/sort/…) |
少(只有 count/index) |
| 什么时候用 | 会变的一串东西 | 不会变的一组值 |
⚠️ 元组"不可变"是浅层的。如果元组里装了个列表,那个列表还是能改:
t = (1, [2, 3])
t[1].append(4) # ✓ 可以!改的是里面的列表
print(t) # (1, [2, 3, 4])
t[1] = [9] # ✗ 不行,不能给元组换元素
这和阶段 7 的"浅拷贝"是同一个坑:不可变只保证"不能换掉盒子里的东西",不保证"盒子里的东西自己不变"。
13 字符串深入(一):切片
字符串是有顺序的字符序列,所以能切片。
切片三件套是 s[开始:结束:步长],含开始、不含结束。
s = "cognition"
# 0123456789 ← 索引(从 0 开始)
print(s[0]) # c 第 0 个
print(s[-1]) # n 倒数第 1 个
print(s[0:4]) # cogn 位置 0,1,2,3(不含 4!)
print(s[:4]) # cogn 省略开始 = 从头
print(s[4:]) # ition 省略结束 = 到末尾
print(s[:]) # cognition 全部(等于 s.copy,但字符串本来不可变)
print(s[::2]) # cgiin 每隔一个取一个
print(s[::-1]) # noitingoc 倒序!
⚠️ "含头不含尾"是 Python 的铁律,切片、
range()都这样。
理由是:这样s[i:i+3]就正好取 3 个字符,不用写成s[i:i+3-1]。
切片不会越界报错(这点和索引取值不同):
s = "abc"
print(s[0:100]) # abc ✓ 超出的部分自动截断,不报错
print(s[100]) # ✗ IndexError: string index out of range
13.1 字符串是不可变的
"不可变"的意思是:所有"修改"字符串的操作,都是返回一个新字符串。
s = "hello"
s.upper() # 返回 "HELLO",但没改变 s
print(s) # hello ← s 还是原样!
s = s.upper() # 要"改"就得重新赋值
print(s) # HELLO
⚠️ 这是初学者最常犯的错之一:
写s.upper()然后奇怪为什么s没变。必须写成s = s.upper()。
这和阶段 7 讲的"可变对象"是同一件事的两面:字符串不可变,所以方法都返回新值。
14 字符串深入(二):常用方法
| 我想干什么 | 写法 | 结果 |
|---|---|---|
| 全部大写 / 小写 | s.upper() / s.lower() |
返回新字符串 |
| 去掉两端空白 | s.strip() |
" hi " → "hi" |
| 去掉两端指定字符 | s.strip(".,") |
去掉两端的 . 和 , |
| 替换 | s.replace("a", "b") |
全部替换 |
| 按分隔符切开 | s.split(",") |
返回列表 |
| 按空白切开 | s.split() |
无参数 = 按任意空白切 |
| 拼接 | ",".join(lst) |
列表 → 字符串 |
| 找子串位置 | s.find("it") |
返回下标,找不到返回 -1 |
| 数出现次数 | s.count("a") |
返回次数 |
| 判断开头 / 结尾 | s.startswith("cog") / s.endswith("tion") |
True/False |
| 判断全是数字 | s.isdigit() |
"123" → True |
最常用的两个是 split 和 join(它们互为逆操作)。
line = "张三,18,认知科学"
parts = line.split(",") # 字符串 → 列表
print(parts) # ['张三', '18', '认知科学']
back = ",".join(parts) # 列表 → 字符串
print(back) # 张三,18,认知科学
💡
join的顺序容易记反:是"分隔符".join(列表),不是列表.join("分隔符")。
记法是:你先写好粘合剂,再把东西粘上去。
split() 不带参数时会自动处理连续空格(读数据文件时非常有用):
print(" a b c ".split()) # ['a', 'b', 'c'] ← 多个空格当一个
print(" a b c ".split(" ")) # ['', '', 'a', '', '', 'b', '', 'c', ''] ← 灾难
15 字符串深入(三):f-string 格式化
f-string 是拼字符串的首选方式(Python 3.6+)。
name = "张三"
age = 18
print(f"{name} 今年 {age} 岁") # 张三 今年 18 岁
格式控制在科研里极其常用,对齐表格、控制小数位都要靠它:
x = 3.14159265
print(f"{x:.2f}") # 3.14 保留 2 位小数
print(f"{x:.4f}") # 3.1416 保留 4 位小数
print(f"{x:10.2f}") # 3.14 总宽 10,右对齐
print(f"{x:<10.2f}|") # 3.14 | 左对齐
print(f"{x:>10.2f}|") # 3.14| 右对齐
print(f"{x:^10.2f}|") # 3.14 | 居中
print(f"{1234567:,}") # 1,234,567 千分位分隔
print(f"{0.856:.1%}") # 85.6% 百分比(自动 ×100)
对齐加小数位是打印数据表的标配:
print(f"{'被试':<6}{'反应时':>10}{'正确率':>10}")
print("-" * 26)
for name, rt, acc in [("A01", 0.5234, 0.95), ("A02", 1.2345, 0.80)]:
print(f"{name:<6}{rt:>10.2f}{acc:>10.1%}")
输出:
被试 反应时 正确率
--------------------------
A01 0.52 95.0%
A02 1.23 80.0%
💡
:>10表示"占 10 格、右对齐",:10默认右对齐。<左对齐,^居中。
中文字符占 2 格宽,所以含中文的表头对齐会有点偏,这是终端字体的问题,不是你写错了。
16 🔧 技能卡 9:字符串怎么切、怎么格式化
技能卡 6 教你读别人的函数签名;这张卡教你处理"文本数据",科研里 90% 的原始数据都是文本。
16.1 决策一:我要"一段字符串"里的哪部分?
| 我想要 | 用什么 | 例子 |
|---|---|---|
| 第 $i$ 个字符 | s[i] |
"abc"[1] → 'b' |
| 从 $a$ 到 $b$(不含 $b$) | s[a:b] |
"abcde"[1:3] → 'bc' |
| 去掉前 3 个 | s[3:] |
省略结束 |
| 去掉后 3 个 | s[:-3] |
负索引 |
| 倒过来 | s[::-1] |
步长 $-1$ |
| 每隔一个取 | s[::2] |
步长 2 |
16.2 决策二:我要"拆开成好几块"还是"拼起来"?
| 需求 | 用什么 | 记法 |
|---|---|---|
| 字符串 → 列表 | s.split("分隔符") |
切开 |
| 列表 → 字符串 | "分隔符".join(lst) |
粘上(分隔符写前面!) |
16.3 决策三:我要把变量"塞进"字符串
| 需求 | 写法 |
|---|---|
| 最常用 | f"{变量}" |
| 保留 2 位小数 | f"{x:.2f}" |
| 占 10 格右对齐 | f"{x:>10}" |
| 百分数 | f"{x:.1%}" |
16.4 三条新手铁律
① "含头不含尾",切片、range 都这样。s[0:3] 取的是 0、1、2。
② 字符串不可变,所有"修改"方法都返回新值,必须重新赋值:
s = "hello"
s.upper() # ✗ 白写了,s 没变
s = s.upper() # ✓ 正确
③ 先想清楚"数据长什么样"再动手。处理文本数据前,先 print 一行看看:
line = " 张三 , 18 , 认知科学 "
print(repr(line)) # 先看清有没有多余空格
parts = [p.strip() for p in line.split(",")]
print(parts) # ['张三', '18', '认知科学']
⚠️
repr()是你的好朋友:它会把字符串的真实内容(包括空格、换行符)显示出来。
直接" 18 "里有没有空格,repr一眼就能看到。💡 处理实验数据的第一原则是不要猜,先看。
print(repr(第一行))、print(type(值))、print(len(列表)),这三个动作能省掉你 80% 的调试时间。
17 🧠 这跟认知科学有什么关系(编程侧)
概念性内容,无法用代码验证,请对照文献。
字符串处理对应实验数据的"清洗"环节,而清洗占据你未来 70% 的工作时间。
认知科学实验的原始数据几乎都是文本:
| 实验平台 | 产出格式 |
|---|---|
| E-Prime | .txt / .csv 制表符分隔 |
| PsychoPy | .csv 逗号分隔 |
| jsPsych | .csv / .json |
| 问卷(Qualtrics 等) | .csv(常含大量空行和引号) |
| 眼动 / EEG | 自定义文本格式 |
这些数据几乎从不是"干净"的:多余空格、编码问题、缺失值标记成 "NA" 或 ""、表头有中文、时间戳格式不统一。
上面学的三个工具正好对应清洗的三个步骤:
| 清洗步骤 | 用的工具 | 典型问题 |
|---|---|---|
| 按行/按列切开 | split() |
分隔符是逗号还是制表符? |
| 去掉多余空白 | strip() |
" 18 " 和 "18" 不是一回事 |
| 转换类型 | int() / float() |
读进来全是字符串,得转成数字 |
一个真实的教训:" 18 " 转 int 会报错(ValueError),必须 strip 之后才行。这在批量处理数据时会卡在第一个文件,而且报错信息指向的行常常不是你真正出错的地方。
💡 "清洗"为什么占 70% 的时间:因为分析的方法论是有限的,而数据的脏法是无限的。
这不是你的能力问题,是这个领域的固有属性。
元组对应心理学里的"结构化记录"。
"一个被试的一次试次"是固定的一组字段:(被试 ID, 试次号, 反应时, 正确与否)。这个组合天生不该被改动(你改掉"第 2 个字段是试次号"这件事,整个数据集就乱了)。用元组表达"这是一条不可变的记录",比用列表更能表达你的意图。
⚠️ 诚实说明:
- 上面的数据格式和清洗步骤是领域常识,但各平台的具体细节(字段名、编码、缺失值约定)请以你用的软件的官方文档为准。
- "清洗占 70% 时间"是行业内流传的经验数字,不是精确统计。
- 这一节是让你提前知道"要花时间在哪",不是让你把它当结论。
18 编程作业 S9
- [ ] 1. 创建元组
rgb = (255, 128, 0),取出三个分量并打印;尝试修改其中一个,观察报错 - [ ] 2. 写
split_name(full),把"张三-认知科学-2026"拆成三部分并分别打印 - [ ] 3. 用一次赋值交换
a, b的值,打印交换前后 - [ ] 4. 给定
s = "Hello, World",用切片取出"Hello"、"World"、"oW"、倒序串 - [ ] 5. 给定一个带多余空格的字符串
" 认知科学 ",用strip()去掉两端空白,打印repr前后对比 - [ ] 6. 写
parse_record(line),把"A01, 0.523, 1"解析成("A01", 0.523, True)提示:先
split(","),再对每部分strip(),然后转类型 - [ ] 7.(重点)用 f-string 打印一张对齐的数据表(表头:被试、反应时、正确率)
- [ ] 8. 下面代码不会把
s变成大写。解释原因并改正:s = "cognition" s.upper() print(s)-
[ ] 9.(技能)给下面这行数据做"清洗":去掉多余空格、转成合适类型,输出一个字典
raw = " A03 , 1.204 , 0 "
- [ ] 10.(Python)写clean_data(lines),输入一串原始文本行,输出清洗后的元组列表
-
第 6 题参考写法:
def parse_record(line):
parts = line.split(",") # 切成 3 段
subject = parts[0].strip() # 去掉空格
rt = float(parts[1].strip()) # 字符串 → 小数
correct = bool(int(parts[2].strip())) # "1"/"0" → True/False
return (subject, rt, correct)
print(parse_record("A01, 0.523, 1"))
# ('A01', 0.523, True)
💡 为什么先
int再bool:bool("0")是True(非空字符串都是 True!),必须先转成整数 0,再转False。这是个非常容易错的坑。
第 10 题参考写法:
def clean_data(lines):
result = []
for line in lines:
line = line.strip()
if not line: # 跳过空行
continue
result.append(parse_record(line))
return result
raw_lines = ["A01, 0.523, 1", "", " ", "A02, 1.234, 0"]
print(clean_data(raw_lines))
# [('A01', 0.523, True), ('A02', 1.234, False)]
# 检查用例
assert clean_data([]) == []
assert len(clean_data(raw_lines)) == 2 # 空行被跳过了
assert clean_data(raw_lines)[0][0] == "A01"
print("检查用例通过")
第 7 题参考写法:
data = [("A01", 0.523, 0.95), ("A02", 1.234, 0.80), ("A03", 0.789, 1.00)]
print(f"{'被试':<8}{'反应时':>10}{'正确率':>10}")
print("-" * 28)
for name, rt, acc in data:
print(f"{name:<8}{rt:>10.2f}{acc:>10.1%}")
输出:
被试 反应时 正确率
----------------------------
A01 0.52 95.0%
A02 1.23 80.0%
A03 0.79 100.0%
第 8 题答案:
s = "cognition"
s.upper() # ⚠️ 返回了新字符串,但没人接住它
print(s) # cognition ← s 根本没变
s = s.upper() # ✓ 必须重新赋值
print(s) # COGNITION
字符串是不可变的,
upper()返回一个新字符串,原来的s一点没动。要"改"就必须赋值回去。
这和阶段 7 的可变性是同一件事的另一面。
本阶段自评
- 数学:$(m\times n)$ 和 $(n\times p)$ 相乘,结果是什么形状?为什么左边列数必须等于右边行数?
- 数学:$AB$ 和 $BA$ 一般相等吗?举一个本讲义里的反例。
- 数学:$(AB)^T$ 等于什么?$(AB)^{-1}$ 等于什么?这两个的"顺序"规律一样吗?
- 数学:$2\times2$ 逆矩阵的口诀是什么?写出公式。
- 数学:什么条件下矩阵不可逆?此时方程组会发生什么?
- 数学:为什么"两边约掉 $A$"在矩阵里不一定合法?这和多重共线性什么关系?
- 数学:为什么伴随矩阵法算 3 阶逆矩阵"理论上漂亮、实践上不实用"?
- 编程:元组和列表的三个区别是什么?为什么元组能当字典的键?
- 编程:
s[0:3]取几个字符?为什么 Python 用"含头不含尾"? - 编程:
s.upper()之后为什么s没变?正确写法是什么? - 技能:
split和join的方向怎么记?repr()在调试时有什么用? - 连接:为什么"两个变量相关 0.95,偏相关却可能接近 0"?
全部答得上 → 进阶段 10。
有任何一个答不上 → 回去补对应小节,别跳。