7.1 axis聚合的降维之路 本节摘要:聚合函数沿指定轴把数组压薄:axis 指的是被消除的维度,结果形状等于原形状去掉那一维。这个机械规则可以推算一切聚合结果,配合 keepdims 保留长度 1 的轴以衔接广播。本节从二维到三维演练形状推算,并给出 argmin 家族与 nan 安全版本的实战组合。 axis 的机械理解法 忘掉"按行压缩还是按列压缩"这种绕口令,记一条规则:axis=n 表示第 n 个维度被消掉。 验证推算法:原形状 (3, 4),axis=0 结果形状去掉第 0 维得 (4,);axis=1 得 (3,)。三维同理,一层一层剥: 聚合的形状推算规则 聚合的形状推算规则 keepdims 与聚合后的广播 第 6.
本节摘要:聚合函数沿指定轴把数组压薄:axis 指的是被消除的维度,结果形状等于原形状去掉那一维。这个机械规则可以推算一切聚合结果,配合 keepdims 保留长度 1 的轴以衔接广播。本节从二维到三维演练形状推算,并给出 argmin 家族与 nan 安全版本的实战组合。
忘掉"按行压缩还是按列压缩"这种绕口令,记一条规则:axis=n 表示第 n 个维度被消掉。
import numpy as np m = np.arange(12).reshape(3, 4) print(m) # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] print(m.sum(axis=0)) # [12 15 18 21],形状 (4,) # 维度0(行数3)被消掉:每列的3个数压成1个,剩4列 print(m.sum(axis=1)) # [ 6 22 38],形状 (3,) # 维度1(列数4)被消掉:每行的4个数压成1个,剩3行 print(m.sum()) # 66,全消成标量
验证推算法:原形状 (3, 4),axis=0 结果形状去掉第 0 维得 (4,);axis=1 得 (3,)。三维同理,一层一层剥:
import numpy as np cube = np.arange(24).reshape(2, 3, 4) # 两批 3x4 print(cube.sum(axis=0).shape) # (3, 4),批次被消:两批对应位置相加 print(cube.sum(axis=1).shape) # (2, 4),每批的行被消:列向合计 print(cube.sum(axis=2).shape) # (2, 3),每批的列被消:行向合计 print(cube.sum(axis=(0, 2)).shape) # (3,),一次消两维

第 6.2 节已经预告,这里系统演练:每行减去行均值是数据标准化的日常步骤。
import numpy as np data = np.array([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]]) row_mean = data.mean(axis=1, keepdims=True) # (2, 1) print(row_mean) # [[ 2.] # [20.]] centered = data - row_mean # (2,3) 对 (2,1) 广播 print(centered) # [[-1. 0. 1.] # [-10. 0. 10.]] # 没有 keepdims 的写法要手动插轴 centered2 = data - data.mean(axis=1)[:, np.newaxis] print(np.array_equal(centered, centered2)) # True
argmin 与 argmax 返回位置而非数值,是"找到谁"类问题的钥匙:
import numpy as np sales = np.array([120, 350, 90, 410, 280]) print("最佳月份:", sales.argmax()) # 3(四月,零起算) print("最差月份:", sales.argmin()) # 2 # 二维时 argmax 沿轴给每行/列的最佳位置 m = np.array([[1, 9, 3], [8, 2, 5]]) print(m.argmax(axis=1)) # [1 0],每行最大值所在列号 # 真实数据常有 nan:普通聚合会被一个 nan 毁掉 dirty = np.array([1.0, np.nan, 3.0, 2.0]) print(dirty.mean()) # nan print(np.nanmean(dirty)) # 2.0,忽略 nan print(np.nansum(dirty)) # 6.0 print(np.isnan(dirty).sum()) # 1,顺带统计缺失数
nan 前缀家族(nanmean、nanstd、nanmax)是清洗不完全数据的救生圈,比"先删 nan 再算"少一次拷贝。
完整过程。背景:3 个班、4 次测验、每班 5 名学生,模拟 (3, 5, 4) 成绩张量,做多级统计。
操作:
import numpy as np rng = np.random.default_rng(11) scores = rng.normal(loc=75, scale=10, size=(3, 5, 4)).clip(0, 100) # 每个学生的平均分:消掉测验维 student_avg = scores.mean(axis=2) # (3, 5) print(student_avg.round(1)) # [[78.6 76.2 74.8 79.4 71.2] # [66.9 72.4 75.9 70.6 74.7] # [78.1 69.8 73.5 77.9 75.6]] # 每个班的平均分:再消掉学生维 class_avg = student_avg.mean(axis=1) # (3,) print("班级均分:", class_avg.round(2)) # [76.04 72.1 74.98] # 每次测验全校平均:消掉班级与学生 exam_avg = scores.mean(axis=(0, 1)) # (4,) print("各次测验:", exam_avg.round(2)) # [74.66 73.61 74.53 75.46] # 每班的尖子生(学生维上取最大) top_per_class = student_avg.max(axis=1) print("班级最高:", top_per_class.round(1)) # [79.4 75.9 78.1]
结果与解读:三次聚合覆盖"学生、班级、测验"三个观察角度,全部靠形状推算法直接写出,零循环。第二次测验全校均分最低(73.61),可进一步查 diff = scores[:,:,1] - scores[:,:,0] 定位是哪个班拖了后腿。变式:加 weights 权重算加权平均 np.average(scores, axis=2, weights=[0.1,0.2,0.3,0.4]),模拟"期末占比更高"的评分规则。
⚠️ 常见坑:axis 越界不总是立刻报错(负数 axis 是合法的,-1 指最后一维),想当然写 axis=1 处理一维数组才会炸。写完聚合顺手打印结果形状,是最便宜的验收。
下一节进入线性代数:矩阵乘的缓存分块、solve 与 inv 的取舍、SVD 降噪实战。