📊 第11章 二手房房价分析与预测系统
📄 一句话概括

一个函数一张图,界面那边只传数据、不管怎么画。 饼图看占比、折线看趋势、条形看排名,三种图各有各管的事,用错就白画。 存图三件事:先设 Agg 再导 pyplot、中文字体、tight_layout。

📝 本章和前面几章的关系

第 5 章是「会画图」,这一篇是「把画图变成能复用的零件」。 零件的好坏不看画得多漂亮,看换个数据还能不能直接用。

一、为什么要把画图写成函数

画三张图要写三遍 fig、ax、savefig;界面那边还要再调三次。 写成一个函数一张图,界面只传数据,不管怎么画。这就是「模块」的意思。 好处有三个:改一次全部生效、界面代码干净、每张图能单独测。

先用手写的五组小数据试,确认函数能画,再接真数据——这是写模块的常规顺序。 手写数据试函数、真数据再出结论。这个顺序反了,就是「先有结论再找数」。

二、饼图:只适合占比

ax.pie(values, labels=keys, autopct="%.1f%%", startangle=90),四个参数就够。 autopct 让每块自动标百分比,startangle=90 从正上方开始切,读起来顺。 饼图只适合「加起来正好是百分之百」的数据。区域房源量是,区域均价不是。

把均价画成饼图,等于把「元每平米」当成了「占比」,那是一种很常见的错。 饼图的块数也别太多。超过七块,小块挤在一起,谁也看不清——那时候该换条形图。

三、折线图:横轴要有顺序

ax.plot(xs, ys, marker="o"),加个圆点标记,几个数据点一眼能数清。 横轴放面积段、月份、年份这类「有先后」的东西,折线才有意义。 区域是并列的,不是有顺序的,用折线连起来就是暗示「城东到城西有过渡」,那是假的。

这一篇折线用的是手写的五个面积段均价,只为了让函数有东西可画。 真数据是不是这个形状,11.5 节见分晓——到时候你会发现不完全一样。

四、条形图:比长短,不比角度

ax.barh(names, vals) 是横向条形,名字长的时候比竖条好读。 排序很关键:sorted(data, key=lambda k: data[k]) 从小到大,画出来就是从上到下递增。 条形比饼图清楚,因为它比的是长短。人眼判长短很准,判角度很差。

再加一句 ax.text(v + 200, i, "%d" % v),把数值直接标在条尾,省得读者去对坐标轴。

五、存图三件事

第一,先 matplotlib.use("Agg") 再导入 pyplot。顺序反了,后端已经定下来,改不动。 第二,font.sans-serif 写 SimHei,axes.unicode_minus 设 False,中文和负号才正常。 第三,fig.tight_layout() 再 savefig,不然标题和坐标轴标签会被裁掉。

plt.show() 在没有显示器的环境里什么也不做,不报错也不弹窗。要留证据就存图。 三张图存下来都是二十来 KB,下一篇洗完数据,用同样三个函数画真数据。

六、动手 Demo:图表工具模块:饼、折线、条形

三个函数各管一张图,都吃「一个字典或两个列表」,都存成 PNG 返回路径。 先用五组手写数据把函数调通,确认没问题再接真数据。

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

plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False


def save(fig, name):
    fig.tight_layout()
    fig.savefig(name)
    plt.close(fig)
    return name


def draw_pie(data, name, title):
    fig, ax = plt.subplots(figsize=(5.2, 4.0), dpi=110)
    ax.pie(list(data.values()), labels=list(data.keys()),
           autopct="%.1f%%", startangle=90)
    ax.set_title(title)
    return save(fig, name)


QU = {"城中": 88, "城东": 142, "城西": 133, "城南": 120, "城北": 130}
PJ = {"城中": 20800, "城东": 18400, "城西": 17100, "城南": 15700, "城北": 14100}
BAND = ["50 以下", "50-90", "90-130", "130-180", "180 以上"]
BP = [17600, 15200, 14100, 13600, 12900]

print(draw_pie(QU, "区域占比.png", "各区房源占比"))

fig, ax = plt.subplots(figsize=(5.6, 3.6), dpi=110)
ax.plot(BAND, BP, marker="o", linewidth=2)
ax.set_title("面积段与平均单价")
ax.set_ylabel("元/平方米")
ax.grid(alpha=0.3)
print(save(fig, "面积段单价.png"))

实跑输出:

--------------------------------------------------------------------
图表工具模块:饼、折线、条形
--------------------------------------------------------------------

  ① 为什么要把画图写成函数
  --------------------------------------------------------------------
  画三张图要写三遍 fig、ax、savefig,界面里还要再调三次。
  写成一个函数一张图,界面那边只传数据,不管怎么画。
  下面三个函数各管一件事:饼图看占比、折线看趋势、条形看排名。
  先用手写的五组小数据试,确认函数能画,再接真数据——这是写模块的常规顺序。

  ② 饼图:看占比
  --------------------------------------------------------------------
  autopct 让每块自动标百分比,startangle=90 从正上方开始切,读起来顺。
  占比图只适合「加起来是百分之百」的数据。区域房源量是,区域均价不是。
  存成 区域占比.png,23,968 字节。

  ③ 折线图:看趋势
  --------------------------------------------------------------------
  横轴是面积段,纵轴是该段的平均单价(元每平米)。
  五个点:17600、15200、14100、13600、12900。
  这五个数是手写的,只为了让函数有东西可画。真数据是不是这个形状,下一篇见分晓。
  手写数据试函数、真数据再出结论,这个顺序不能反。反了就是「先有结论再找数」。
  存成 面积段单价.png,23,915 字节。
  ! 折线图的横轴必须是「有顺序」的东西:面积段、月份、年份。区域是并列的,用折线就是错的。

  ④ 条形图:看排名
  --------------------------------------------------------------------
  区域均价排序,条形比饼图清楚得多——它比的是长短,不是角度。
  barh 是横向条形,名字长的时候比竖条好读。
  存成 区域均价.png,17,518 字节。

  ⑤ 存图要记三件事
  --------------------------------------------------------------------
  第一,先 matplotlib.use("Agg") 再导入 pyplot。顺序反了,后端已经定下来,改不动。
  第二,font.sans-serif 写 SimHei,axes.unicode_minus 设 False,中文和负号才正常。
  第三,fig.tight_layout() 再 savefig,不然标题和坐标轴标签会被裁掉。
  三张图都存好了:区域占比、面积段单价、区域均价。下一篇洗完数据,用同样三个函数画真数据。
  ! plt.show() 在无显示器环境里什么也不做。要留证据就存图,别指望它弹窗。

matplotlib.use("Agg") 必须写在 import pyplot 之前。写在后面,后端已经定死,白写。 save() 里先 tight_layout() 再 savefig(),顺序反了,标题会被裁掉一截。 函数返回文件名而不是 None,是为了让调用方知道图存到哪了——界面那边要拿它去显示。

30 秒实验

  1. 把饼图的块从 5 个加到 10 个,看还看不看得清。
  2. 把折线图的横轴换成五个区名,想想为什么这个画法是错的。
  3. 把 startangle 改成 0,看饼图的第一块从哪个方向开始。

常见误区

  1. 先导入 pyplot 再 use("Agg"):后端改不动,无显示器环境直接报错。
  2. 用饼图画非占比数据:均价、金额都不是占比,画出来没有意义。
  3. 折线图连并列类别:区域之间没有过渡,连线是在编造关系。
  4. savefig 前不 tight_layout:标题和坐标轴标签被裁掉,还不报错。
自测三问(点开对答案) 1. 把画图写成函数,最实际的好处是什么? —— 改一次全部生效,界面代码干净,而且每张图能单独测——不用每次都从头画。 2. 为什么均价不能画饼图? —— 饼图的每块代表占比、加起来必须是 100%。均价是绝对量,画进去等于把单位偷换了。 3. `plt.show()` 不弹窗,是程序出错了吗? —— 不是。无显示器环境里它什么也不做。要留证据就 `savefig` 存成 PNG。

三个零件齐了。下一篇回到脏活:1206 行数据里的五类毛病,一样样清。

相关笔记