📈 第10章 回归
📄 一句话概括

散点图只回答一个问题:这两个量有没有一起变大。它给方向,不给数。 平均值图形把横轴切段、段内取平均,六个平均从 4.58% 一路升到 9.22%,趋势就露出来了。 但折线是断的,只能给六个档的答案;要给 73 分的答案,得有一条连续的线。

📝 本章和前面几章的关系

上一篇用分档把误差降到 1.00,代价是台阶。这一篇先把数据摊开看,再想办法抹平台阶。 书里这段用 Excel 画散点图。这台机器上没有 Excel,用 matplotlib 画,逻辑一样。

一、先把两个变量摆在一张图上

横轴放绩效点数,纵轴放加薪幅度,一个人一个点,60 个点撒在平面上。 这张图不计算任何东西,它只是把两张表并成一张图。 但它一眼就能回答一个之前回答不了的问题:这两个量是不是一起动。

这 60 个点从左下往右上走——分数高的人,加薪幅度也偏高。方向有了。 散点图的第一价值是「有没有关系」,不是「关系有多强」。后者要靠数。

二、散点图能说的和不能说的

能说三件事:方向(往上还是往下)、形状(直不直、弯不弯)、散布(点贴着还是散着)。 不能说三件事:斜率到底多少、预测误差多大、某一个人该加多少。 很多人看着散点图就下结论,其实只完成了「看」,还没开始「量」。

这里还有一层:点云的形状大致是直的,没有明显的弯。这个判断很重要—— 如果点云是弯的,后面那条直线就会两头都错。直线模型的前提是「大致线性」。

三、平均值图形:横轴切段,段内取平均

散点太密,看不出趋势的具体形状。一个办法是切段取平均。 把 40 到 96 切成六段,每段算一个平均加薪幅度,得到六个点,连起来成一条折线。 六个平均:4.58%、5.78%、6.77%、7.20%、8.04%、9.22%。

每一段都比上一段高,一次都没有回头。这是「单调上升」最直接的证据。 这条折线比散点干净得多,因为它把段内的散布平均掉了,只留下段与段之间的差别。

四、平均值图形好在哪,差在哪

好在:它把「趋势」和「散布」拆成了两件事。趋势看折线怎么走,散布看每一段点的高低差。 看表就知道:40~50 那一段,最低 3.4%、最高 6.7%,跨度 3.3 个点;而六段平均只差 4.6 个点。 也就是说,同一档内部的人差得和不同档之间差不多。这句话是后面误差分析的伏笔。

差在:段边界是画图人划的。换六个边界,折线就换一个形状——这跟第 9 章的箱宽一模一样的毛病。 还有一个更硬的毛病:折线是断的。73 分的人,折线上根本没有对应的点。

五、它还差一步

要回答「73 分该加多少」,需要一条能对任意分数给值的线。 而且要一条尽量贴合这 60 个点的线,不是随手画的。 「尽量贴合」这句话听着含糊,其实可以写成一个精确的条件:让所有点的竖直距离的平方和最小。

下一篇就按这个条件把线算出来。这一步是从「看图」到「建模」的分界。

六、动手 Demo:散点图与平均值图形

60 个点撒在平面上,再切六段算平均,把六个平均连成折线。 两张图都存成 PNG,顺便把六个平均和每段的跨度打出来。

import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

S = [20261102]

def rnd():
    S[0] = (S[0] * 1103515245 + 12345) % 2147483648
    return S[0] / 2147483648.0

DATA = []
for i in range(60):
    x = 40 + int(round(i * 55 / 59.0))
    y = 1.2 + 0.085 * x + (rnd() - 0.5) * 3.2
    DATA.append((x, round(y, 1)))

XS = [d[0] for d in DATA]
YS = [d[1] for d in DATA]
BINS = [(40, 50), (50, 60), (60, 70), (70, 80), (80, 90), (90, 96)]

for lo, hi in BINS:
    seg = [y for x, y in DATA if lo <= x < hi]
    print("%2d~%2d  人数 %2d  平均 %.2f%%  跨度 %.1f"
          % (lo, hi, len(seg), sum(seg) / len(seg), max(seg) - min(seg)))

fig, ax = plt.subplots(figsize=(5.0, 3.2), dpi=110)
ax.scatter(XS, YS, s=14, color="#4a6fa5", alpha=0.8)
ax.set_xlabel("performance points", fontsize=8)
ax.set_ylabel("raise percent", fontsize=8)
ax.set_title("60 records: points vs raise", fontsize=9)
fig.tight_layout()
fig.savefig("scatter.png")
plt.close(fig)
print("散点图 %d 字节" % os.path.getsize("scatter.png"))

实跑输出:

--------------------------------------------------------------------
散点图与平均值图形
--------------------------------------------------------------------

  ① 把两个变量摆在一张图上
  --------------------------------------------------------------------
  横轴放绩效点数,纵轴放加薪幅度,一个人一个点。60 个点,就是散点图。
  散点图只干一件事:看这两个量有没有一起变大。
  点云从左下往右上走,说明分数高的人加薪也多——方向有了。

  ② 点云告诉你的和没告诉你的
  --------------------------------------------------------------------
  告诉了:整体趋势是向上的,而且大致是直的,没有明显拐弯。
  没告诉:具体斜率是多少,误差有多大,某个人该加多少。
  所以散点图是「先看一眼」,不是「得出结论」。
  ! 散点图只能看趋势,不能读数。要读数,得在它上面压一条线。

  ③ 平均值图形:把横轴切段,段内取平均
  --------------------------------------------------------------------
  把 40~96 切成六段,每段算一个平均加薪幅度,把这六个点连起来。
  这条折线比散点干净得多:它把每段内部的散布平均掉了,只留下段与段之间的差别。
  六个平均分别是 4.58%、5.78%、6.77%、7.20%、8.04%、9.22%。
  每一段都比上一段高,没有哪一段掉下去——这就是「单调上升」的直接证据。

  ④ 平均值图形好在哪,差在哪
  --------------------------------------------------------------------
  好在:它把「趋势」和「散布」拆开了。趋势看折线,散布看每段的高低差。
  差在:段边界是我划的。换六个边界,折线就换一个形状。这跟第 9 章的箱宽是同一个毛病。
  还有:折线是断的,只能给出六个档的答案,给不出 73 分该加多少。

  ⑤ 图存下来
  --------------------------------------------------------------------
  散点图存成 PNG:16790 字节。
  平均值图形存成 PNG:19203 字节。连线是六个档的平均,点还是原来那 60 个。

六段的平均是单调上升的,但每一段内部跨度都在 2.5 到 3.3 个点之间——段内的差别不小于段间的差别, 这是这条折线说不清的地方。 图里的字用英文:这台机器没装中文字体,中文会变成一排方框。

30 秒实验

  1. 把六段改成三段(40~60、60~80、80~96),折线会变成什么形状?趋势还看得出来吗?
  2. 把段边界改成按人数等分(每段 10 人),六个平均会怎么变?
  3. 想一想:如果散点图上的点云是弯的(比如先升后降),用一条直线去拟,错在哪儿?

常见误区

  1. 看散点图就报结论:散点图给方向,不给斜率和误差。要报数就得算。
  2. 用折线代替模型:折线只能给固定几档的答案,档与档之间没有定义。
  3. 忽略段内跨度:只看六个平均一路向上,会误以为这关系很干净。实际段内差得一样多。
  4. 随手画一条「看着差不多」的线:不写清「怎么算最贴合」,线就没法复现,也没法评价。
自测三问(点开对答案) 1. 散点图能回答什么、不能回答什么? —— 能回答方向、形状、散布;不能回答具体斜率、预测误差、某一个人的取值。 2. 平均值图形为什么比散点图好读? —— 因为它把段内的散布平均掉了,只留下段与段之间的差别。代价是段边界要人来定。 3. 为什么说「段内差别不小于段间差别」是个警告? —— 说明分数只解释了一部分差异。剩下那一部分,任何只用分数的模型都盖不住。

折线是断的,要连续的线就得定一个「最贴合」的标准。下一篇把它写成一个能算的条件。

相关笔记