6.11. 归约¶
归约(reduction)会沿一个或多个轴对数组进行折叠,比如求和、求平均、取最小值等等。每次归约都是针对整个数组的一次库调用,比等价的 Python 循环要快得多。numpy 涵盖了日常常用的归约操作:
sum()—— 所有元素的总和mean()—— 算术平均值(总和除以元素个数)std()—— 标准差,ddof=用于调整除数(N - ddof)median()—— 元素排序后的中间值(第 50 百分位数)
6.11.1. 不带 axis 关键字¶
在不带 axis= 调用时,归约会返回一个覆盖整个数组的标量:
a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a) # 10.0
np.mean(a) # 2.5
np.std(a) # 1.118...
np.median(a) # 2.5
b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b) # 40.0
np.argmax(b) # 0 (index of the maximum)
6.11.2. 带 axis 关键字¶
axis= 会收缩指定的某个轴而保留其余的轴。结果是一个比输入低一阶的数组:
m = np.arange(12, dtype=np.float).reshape((3, 4))
np.sum(m) # 66.0 - scalar
np.sum(m, axis=0) # length-4 - column sums
np.sum(m, axis=1) # length-3 - row sums
同样的形状规则适用于所有归约:axis=0 折叠第一个轴,axis=1 折叠第二个轴,以此类推。例如,沿一行求平均值 / 标准差分别写作 np.mean(m, axis=1) 和 np.std(m, axis=1)。结果的长度由另一个轴决定。
keepdims=True 关键字会把被收缩的轴以长度 1 的形式保留在原处,而不是将其丢弃。当归约后的结果需要广播回原数组时,这一区别就很重要:keepdims 保持了阶数,从而让广播规则逐轴对齐。
从每一行中减去该行的均值是其典型用途:
m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean
如果不带 keepdims,np.mean(m, axis=1) 会返回形状为 (3,) 的一维结果。广播 (3, 4) - (3,) 时,(3,) 在前置补阶后被对齐为 (1, 3),这与 (3, 4) 不兼容:最后一个轴不一致(4 对 3)且都不为 1,因此 numpy 会抛出 ValueError。正是 keepdims=True 让这次减法保持有效。
6.11.3. 布局很重要¶
结合 形状与步幅 中介绍的行优先布局,沿最后一个轴归约是开销最小的情况。归约会按数据存储的方向遍历数据块,无需在行与行之间跳转:
m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1) # cheap - long axis is the inner one
np.sum(m, axis=0) # has to jump rows on every step
当应用程序可以选择缓冲区的布局方式时,应把长轴放在最后,这样沿它归约时就能以快速方向运行。
6.11.4. 以可迭代对象作为输入¶
大多数归约都接受 Python 可迭代对象(list、range、元组)来代替 ndarray。这种便利性会因隐式转换而花费几微秒——在循环中累积起来会很快变多。当同一份数据被多次归约时,应只构建一次 ndarray 并传递它。