📊 第6章 图解数组计算模块 NumPy
📄 一句话概括

numpy 的 std 默认 ddof=0,pandas 默认 ddof=1,同一列数会给出两个结果。 percentile 传 0 到 100 的百分数;pandas 的 quantile 传 0 到 1 的比例。 axis=0 压掉行、axis=1 压掉列,记「压掉哪一维」比记方向靠谱。

📝 本章和前面几章的关系

这一篇收尾:先把常用统计函数列成表,再拿两个案例把 axis 用熟。 案例用的是同一批 10 个学生的三科成绩,和第 4 章那批数是同一组。

一、一张表看完常用统计函数

sum 合计 827,mean 均值 82.70,median 中位数 84.0。 std 标准差 8.53,var 方差 72.81,两者差一个开平方。 min 和 max 给 68 和 95;argmin 和 argmax 给位置 5 和 6。

arg 系列给的是下标,不是值。要和名字对上,还得拿位置回去查。 percentile 给 87.55,也就是 65 分位那个数。

二、ddof 那点差别

同一列数,numpy 的 std 给 8.53,pandas 的 std 给 8.99。 原因只在分母:numpy 默认除以 n,pandas 默认除以 n 减 1。 这叫自由度(ddof)。numpy 默认 ddof=0,pandas 默认 ddof=1。

n 是 10 的时候差得挺明显;n 到几千,两个数几乎一样。 要两个工具对上,就显式写:A.std(ddof=1)。别靠猜默认值。

三、axis 方向再确认一次

2 行 3 列的矩阵,sum(axis=0) 给 [5, 7, 9],三个数。 sum(axis=1) 给 [6, 15],两个数。 axis=0 压掉行,所以结果有列数个;axis=1 压掉列,结果有行数个。

结果长度不一样,这是最快的判断办法:数一下出来几个,就知道压掉了哪一维。

四、案例一:一次算完三科

10 个学生 3 科成绩,组成一个 10 行 3 列的数组。 mean(axis=0) 一次给三个均值:82.7、83.0、81.9。 sum(axis=1) 一次给十个总分,最高那个 277 分。

不用循环三次。这是数组相对列表最实在的好处。 argmax 给的位置是 1,从 0 数起,也就是第 2 个学生。位置和序号差 1,这里最容易看错。

五、案例二:按列标准化

标准化的公式是:(每个数减所在列的均值) 除以所在列的标准差。 写成 (S - S.mean(axis=0)) / S.std(axis=0),一行就完。 这就是广播的用处。10 行 3 列减一个长度 3 的数组,每列各减各的。

算完每列均值是 -0.0、0.0、-0.0,标准差都是 1.0,验算通过。 均值里出现 -0.0 不是 bug,是浮点表示法里负零的写法,数值上就是 0。 标准化是机器学习预处理的第一步:不同量纲的列,标准化之后才能放在一起比。

六、第 6 章到此结束

NumPy 是 Pandas、Matplotlib、scikit-learn 共同的底座,这一章挖的是地基。 回头看:第 3 到 5 章用的每一列数据,底下都是一个 ndarray。 第 7 章换个方向,从「怎么算」转到「怎么判断」:抽样、检验、相关性。

判断自己掌握了没有:能不能不查文档写出「按列标准化」和「按行求和」这两句。

七、动手 Demo:统计函数与两个综合案例

先把常用统计函数一次打完,顺便把 ddof 的差别摆出来。 两个案例:一次算完三科均值与每人总分,再整表标准化。

import numpy as np

A = np.array([88, 92, 75, 85, 90, 68, 95, 72, 83, 79])
print("合计 %d,均值 %.2f" % (A.sum(), A.mean()))
print("标准差 %.2f(ddof=0),方差 %.2f" % (A.std(), A.var()))
print("最小 %d,最大 %d" % (A.min(), A.max()))
print("最小位置 %d,最大位置 %d" % (A.argmin(), A.argmax()))
print("中位数 %.1f,65 分位 %.2f" % (np.median(A), np.percentile(A, 65)))
print("ddof=1 时标准差 %.3f" % A.std(ddof=1))

M = np.arange(1, 7).reshape(2, 3)
print("按列求和 %s" % M.sum(axis=0).tolist())
print("按行求和 %s" % M.sum(axis=1).tolist())

S = np.array([[88, 92, 85], [92, 95, 90], [75, 68, 80], [85, 88, 79],
              [90, 73, 95], [68, 82, 71], [95, 91, 88], [72, 65, 60],
              [83, 86, 84], [79, 90, 87]])
print("每科均值 %s" % np.round(S.mean(axis=0), 2).tolist())
print("每人总分 %s" % S.sum(axis=1).tolist())
print("最高分位置 %d,%d 分" % (S.sum(axis=1).argmax(), S.sum(axis=1).max()))

Z = (S - S.mean(axis=0)) / S.std(axis=0)
print("标准化后每列均值 %s" % np.round(Z.mean(axis=0), 10).tolist())
print("标准化后每列标准差 %s" % np.round(Z.std(axis=0), 4).tolist())

实跑输出:

--------------------------------------------------------------------
统计函数与两个综合案例
--------------------------------------------------------------------

  ① 常用的统计函数
  --------------------------------------------------------------------

| 函数 | 给什么 | 这次的值 |
|---|---|---:|
| sum | 合计 | 827 |
| mean | 均值 | 82.70 |
| std | 标准差(ddof=0) | 8.53 |
| var | 方差 | 72.81 |
| min / max | 最小最大 | 68 / 95 |
| argmin / argmax | 最小最大的位置 | 5 / 6 |
| median | 中位数 | 84.0 |
| percentile | 分位数 | 87.55 |

  numpy 的 std 默认 ddof=0,pandas 默认 ddof=1。同一列数两个结果,差在分母。
  ! percentile 第二个参数是 0 到 100 的百分数;pandas 的 quantile 是 0 到 1 的比例。

  ② axis 方向再确认一次
  --------------------------------------------------------------------
  原矩阵:
  [1, 2, 3]
  [4, 5, 6]
  sum(axis=0):[5, 7, 9]——三列各自的和。
  sum(axis=1):[6, 15]——两行各自的和。
  axis=0 压掉行,axis=1 压掉列。记「压掉哪一维」比记「方向」靠谱。
  ! 两个方向的结果长度不同:axis=0 给 3 个(列数),axis=1 给 2 个(行数)。

  ③ 案例一:一次算完三科
  --------------------------------------------------------------------
  10 个学生 3 科,组成一个 10 行 3 列的数组。
  每科均值:[82.7, 83.0, 81.9]
  每人总分:[265, 277, 223, 252, 258, 221, 274, 197, 253, 256]
  总分最高的第 1 个人,277 分。
  一次 mean(axis=0) 就出三科均值,不用循环三次。
  ! argmax 给位置不给值。要名字还得拿位置回原表查——和第 4 章的 idxmax 一个道理。

  ④ 案例二:标准化
  --------------------------------------------------------------------
  标准化 =(每个数减均值)除以标准差。
  标准化之后每列的均值:[-0.0, 0.0, -0.0]
  标准化之后每列的标准差:[1.0, 1.0, 1.0]
  这就是广播的用处:一个 10 行 3 列减一个长度 3 的数组,每列各减各的均值。
  ! 标准化之后不同量纲的列才能放在一起比。这是机器学习预处理的第一步。

  ⑤ 第 6 章到此结束
  --------------------------------------------------------------------
  NumPy 是 Pandas、Matplotlib、scikit-learn 共同的底座,这里是它的地基。
  第 7 章换个方向:从「算数」转到「统计推断」——抽样、检验、相关性。
  ! 判断自己掌握了没有:能不能不查文档写出「按列标准化」和「按行求和」这两句。

最后两行是验算:标准化之后均值该是 0、标准差该是 1。打出来对上了,说明公式没写错。 标准化用的是 S.std(axis=0),不是 pandas 的 std,所以这里是 ddof=0。换成 pandas 数会略差一点。 np.round(..., 10) 是为了把 -0.0 附近那些浮点尾巴抹平,不然会打出一串 1e-17。

30 秒实验

  1. 把 percentile 的 65 改成 0.65,看会不会报错或者给出别的数。
  2. 把标准化的分母换成 S.std(axis=0, ddof=1),看每列标准差还算不算 1。
  3. 想一想:如果某一列的标准差是 0(所有值都一样),标准化会出什么问题?

常见误区

  1. 以为 numpy 和 pandas 的 std 一样:一个 ddof=0 一个 ddof=1,小样本差得明显。
  2. percentile 传 0.65:它要 0 到 100,传 0.65 等于要 0.65 分位,结果不对。
  3. argmax 的返回值当序号用:它从 0 数起,和「第几个」差 1。
  4. 标准化时忘了按列:写成 axis=1 就变成每行各减各的,整张表都歪了。
自测三问(点开对答案) 1. numpy 和 pandas 的 std 差在哪? —— 分母不同。numpy 默认 ddof=0 除以 n,pandas 默认 ddof=1 除以 n 减 1。 2. 怎么判断 axis=0 和 axis=1 哪个是压掉行? —— 看结果长度:出来几个数就说明留下了几维。2 行 3 列,给 3 个数的是压掉行。 3. 标准化之后,每列的均值和标准差应该是多少? —— 均值 0、标准差 1。算完要验算这两条,对上了才说明公式没写错。

第 6 章到此收尾。第 7 章从「算数」转到「判断」:一组数字能不能代表整体。

相关笔记