2.4 聚合、缺失值与数值稳定性
向量化在保留数组形状的同时转换元素。聚合(aggregation)则向相反方向移动:通过折叠一条或多条轴,把许多值汇总成较少的值。只有知道哪条轴消失、哪些标签保留下来,结果才有意义。
继续使用日期 × 区域矩阵:
times = np.array([
[31.0, 38.0, 44.0, 36.0],
[29.0, 41.0, 47.0, 35.0],
[33.0, 39.0, 42.0, 37.0],
])它的形状是 (3 个日期, 4 个区域)。不提供 axis 时,NumPy 会聚合全部 12 个值:
overall_mean = times.mean()指定轴时,该轴会被消耗:
zone_means = times.mean(axis=0) # 形状 (4,):每个区域一个值
daily_means = times.mean(axis=1) # 形状 (3,):每天一个值axis=0 不是“返回行”,而是“折叠第 0 轴”。日期消失,区域保留。axis=1 折叠列轴,因此每天保留一个结果。
对于 个有效值 ,算术平均值是:
这个公式让分母清晰可见。缺失和被排除的观测会改变 ,而且不同组的变化可能不同。
在后续运算需要时保留维度
聚合通常会删除被折叠的轴。keepdims=True 会保留这条轴,但把长度变成 1:
zone_means_row = times.mean(axis=0, keepdims=True)
print(zone_means_row.shape) # (1, 4)
centered = times - zone_means_row形状 (1, 4) 的均值可以直接广播到 (3, 4)。保留维度能够让轴的意图更明显,也减少后续重塑。
其他常见归约包括 sum、min、max、std、argmin 和 argmax。argmax(axis=0) 返回每列最大值所在的位置,不是最大值本身。位置结果必须结合原轴标签,才能转换成有意义的日期或区域名。
用 NaN 表示缺失浮点值
NumPy 常用 np.nan 表示缺失的浮点观测,含义是“非数(not a number)”。它是一种特殊浮点值,不是零,也不是空字符串:
times_with_missing = np.array([
[31.0, np.nan, 44.0],
[29.0, 41.0, np.nan],
[33.0, np.nan, 42.0],
])普通算术通常会传播 NaN:
print(times_with_missing.mean(axis=0))
# [31. nan nan]传播缺失很有用,因为它不会让问题悄悄消失。当分析规则明确允许使用现有观测汇总时,可以调用忽略 NaN 的函数,同时报告有效数量:
valid_counts = np.sum(~np.isnan(times_with_missing), axis=0)
zone_means = np.nanmean(times_with_missing, axis=0)
print(valid_counts) # [3 1 2]
print(zone_means) # [31. 41. 43.]中区的均值 41 只来自一条观测,北区均值却来自三条。如果只显示均值,这种证据差异就会被隐藏。
np.nanmean 不会推断缺失值,也不能证明缺失没有影响。它只是把缺失值排除在计算外。仍要追问为何缺失、各组缺失程度是否不同,以及剩余观测是否还能代表目标总体。
浮点数是近似表示
计算机以有限精度保存浮点数。许多十进制小数不能用二进制精确表示:
print(0.1 + 0.2 == 0.3) # False比较时,应使用与业务问题相符的容差:
np.isclose(0.1 + 0.2, 0.3)
np.allclose(array_a, array_b)容差不等于可以忽略任何差异。它应根据测量精度和决策要求选择。适合配送分钟数的容差,可能完全不适合财务对账。
精度也与数值量级有关。float32 大约保留七位十进制有效数字。在 100,000,000 附近,它可能无法区分相差 1 的两个值:
large = np.array([100_000_000.0, 100_000_001.0], dtype=np.float32)
print(large[1] - large[0]) # 可能得到 0.0两个值可能被舍入成同一个可表示数。这不是随机损坏,而是有限精度的必然结果。
改写不稳定计算
如果细小的表示或舍入误差不会在结果中放大,这项计算就具有数值稳定性(numerical stability)。两个非常接近的大数相减风险较高,因为共同的高位数字先占用了精度,真正关心的小差异才被取出。这种现象常称为抵消(cancellation)。
如果业务含义允许,可以先减去共同基准,再转换为较低精度:
timestamps64 = np.array([100_000_000.0, 100_000_001.0], dtype=np.float64)
relative32 = (timestamps64 - timestamps64[0]).astype(np.float32)
print(relative32[1] - relative32[0]) # 1.0相对表示保存的是 [0, 1],重要差异相对于数值本身已经足够大。其他稳定性做法还包括使用足够的 dtype、避免不必要的类型往返转换、在整数运算前检查溢出,以及使用成熟库函数而不是自行拼装脆弱公式。
第 2 章已经建立 NumPy 的核心心智模型:同质数组拥有形状和带类型的轴;选择依赖坐标或对齐掩码;向量化表达整体转换;广播对齐兼容形状;聚合则在明确的缺失与精度策略下折叠指定维度。第 3 章将继续重塑、堆叠、排序、抽样和分析数组,并深入讨论视图、副本、内存与性能。