21 个特征、1184 条样本,R 方 0.9422、RMSE 15.21 万、MAPE 7.72%。 只用面积一个特征,R 方就有 0.6998——分数高是因为特征好用,不是模型厉害。 估价 161.88 万,区间 146.66 到 177.09 万;随机切而不是按时间切。
第 10 章用一列广告费预测销售额,这一篇用二十一列特征预测总价。 模型还是那个线性回归,变的是特征工程的份量——这一篇的重点全在特征上。
一、特征怎么选:先划清谁能进 X
要预测的是总价,所以总价是 y,不能进 X。这一条错了,模型会给出 100% 的准确率,然后毫无用处。 单价也得踢掉。它和总价是一回事,放进 X 等于把答案抄进去。 留下这些:面积、房龄(连续),楼层、装修、区域、户型(分类)。
分类变量不能直接进模型,得先变成 0/1 哑变量,get_dummies 一句搞定。
drop_first=True 去掉每类第一档,避免「几档加起来必然等于 1」造成的完全共线。
结果是 6 列变 21 列。特征列数比原始字段多,是正常现象,别以为是哪里写错了。
二、随机切:和第 10 章正好相反
1184 条样本,训练 947 条、测试 237 条,八成对两成。 这里可以随机切,因为房价数据没有时间先后。第 10 章那是日销售流水,一切就漏未来。 同一个操作,两个项目里的做法正好相反。判断依据不是习惯,是「数据有没有时间序」。
有时间的(销售流水、股价、气象)必须按时间切;没时间的(房源、用户、商品)随便切。
切之前先设 random_state。不设的话每次跑出来的分数都不一样,你会以为模型在抽风。
三、四个指标,外加一个对照
| 指标 | 这次的值 | 怎么读 |
|---|---|---|
| R 方 | 0.9422 | 解释了多少波动 |
| RMSE | 15.21 万 | 均方根误差,罚大错 |
| MAE | 10.18 万 | 平均差多少 |
| MAPE | 7.72% | 平均差百分之几 |
R 方 0.9422,比第 10 章那个 0.5005 高得多。但这不是模型变强了。 只用面积一个特征重跑一遍,R 方是 0.6998。一列就解释掉七成的波动。 剩下那 0.24,是区域、装修、房龄、楼层一起挣来的。
这个对照一定要做。不做,你会把「特征好用」记成「模型厉害」,下次换数据就翻车。 MAE 10.18 万,相对一套百来万的房子,误差接近一成;MAPE 7.72% 说的也是这件事。
四、加个岭回归对照
套一个 StandardScaler 加 Ridge(alpha=1.0) 的流水线,重跑一次。
R 方 0.9419、RMSE 15.25 万、MAPE 7.72%——和线性回归几乎一模一样。
这是正常的:特征不多、也不高度共线,岭回归那根缰绳根本用不上。
岭回归不是万灵药。它的用处是「特征多、共线重」的时候稳住系数,不是无条件提分。 加了标准化之后系数能横向比大小,这是岭回归流水线顺带的好处。
五、估一套房:点估计加区间
城东、89.5 平米、房龄 6 年、中楼层、精装、2 室 2 厅,模型估价 161.88 万元。 给区间:上下各留一个 RMSE,146.66 到 177.09 万。一套房差 10 万很正常,报一个数就是在骗人。 换算成单价:161.88 万除 89.5 平米,合 18086 元每平米,落在城东 16577 的均线上方。
高出来的部分能解释:房龄只有 6 年,还是精装,两样都加分。 模型给的不是市场价,是这份数据里的「合理价」。 实际挂牌还要看楼层朝向、小区品质、业主心态,模型一个都不知道。
六、系数怎么读,以及这一章的小结
面积那一项是 1.4740,意思是每多一平米,总价多 1.4740 万元,也就是 14740 元。 最负的三个:区域_郊县 -78.903、区域_经开 -51.504、区域_城北 -36.706,单位都是万元。 最正的三个:装修_精装 6.006、区域_城中 18.552、装修_豪华 25.288。
系数的单位和特征尺度绑在一起,面积每平米加多少万,和「郊县比基准区少多少万」不能直接比大小。 想比重要性得先标准化。不标准化就比系数,是这一章最容易犯的错。 六篇走完:概述定问题、图表成模块、清洗靠规则、分组看结构、模型看特征。
第 10 章的重点是「怎么评分」,这一章的重点是「怎么选特征」。两件事凑齐,一个预测项目才算完整。
七、动手 Demo:二手房房价预测
选特征、转哑变量、随机切、线性回归加岭回归对照,最后估一套房并读系数。 第三节那个「只用面积」的对照是本篇的重点,别看漏。
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
df = pd.read_csv("二手房数据_clean.csv")
X = pd.get_dummies(df[ ["面积", "房龄", "楼层", "装修", "区域", "户型"]],
drop_first=True)
y = df["总价"].values
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42)
lr = LinearRegression().fit(Xtr, ytr)
pred = lr.predict(Xte)
print("特征 %d 列,训练 %d,测试 %d" % (X.shape[1], len(Xtr), len(Xte)))
print("R2 %.4f RMSE %.2f MAE %.2f MAPE %.2f%%"
% (r2_score(yte, pred), mean_squared_error(yte, pred) ** 0.5,
mean_absolute_error(yte, pred),
np.mean(np.abs((yte - pred) / yte)) * 100))
r2a = r2_score(yte, LinearRegression().fit(Xtr[ ["面积"]], ytr)
.predict(Xte[ ["面积"]]))
print("只用面积,R2 %.4f" % r2a)
rd = make_pipeline(StandardScaler(), Ridge(alpha=1.0)).fit(Xtr, ytr)
print("岭回归 R2 %.4f" % r2_score(yte, rd.predict(Xte)))
实跑输出:
--------------------------------------------------------------------
二手房房价预测
--------------------------------------------------------------------
① 特征怎么选
--------------------------------------------------------------------
要预测的是「总价」,所以总价是 y,不能进 X。单价也得踢掉——它和总价是一回事。
留下这些:面积(连续)、房龄(连续)、楼层、装修、区域、户型(分类)。
分类变量不能直接进模型,得先变成 0/1 的哑变量,get_dummies 一句搞定。
drop_first=True 是去掉每个分类的第一档,避免「几档加起来必然等于 1」的完全共线。
② 随机切,不是按时间切
--------------------------------------------------------------------
特征从 6 列变成 21 列,1184 条样本。
训练 947 条,测试 237 条。
这里可以随机切,因为房价数据没有时间先后。第 10 章那是日销售流水,一切就漏未来。
! 同一种操作,两个项目里的做法正好相反。判断依据不是习惯,是「数据有没有时间序」。
③ 线性回归,四个指标
--------------------------------------------------------------------
| 指标 | 这次的值 | 怎么读 |
|---|---|---|
| R 方 | 0.9422 | 解释了多少波动 |
| RMSE | 15.21 万 | 均方根误差,罚大错 |
| MAE | 10.18 万 | 平均差多少 |
| MAPE | 7.72% | 平均差百分之几 |
R 方 0.9422,比第 10 章那个 0.5005 高得多。
只用面积一个特征,R 方就有 0.6998。房价里一大半的差异,这一列就解释掉了。
所以这次分数高,不是模型变强了,是特征里多了一个几乎决定性的变量。
平均差 10.18 万,相对一套百来万的房子,这个误差还能接受。
④ 加个岭回归对照
--------------------------------------------------------------------
| 模型 | R 方 | RMSE(万) | MAPE |
|---|---:|---:|---:|
| 线性回归 | 0.9422 | 15.21 | 7.72% |
| 岭回归 alpha=1 | 0.9419 | 15.25 | 7.72% |
两个几乎一样。这是正常的:特征不多、也不高度共线,岭回归那根缰绳用不上。
岭回归不是万灵药。它的用处是「特征多、共线重」的时候稳住系数,不是无条件提分。
⑤ 估一套房
--------------------------------------------------------------------
一套城东、89.5 平米、房龄 6 年、中楼层、精装的 2 室 2 厅。
模型估价 161.88 万元。
给区间:上下各留一个 RMSE,146.66 到 177.09 万。
别忘了单价:161.88 万除 89.5 平米,合 18086 元每平米。
! 报估价必须报区间。一套房差 10 万很正常,报一个数就是在骗人。
⑥ 系数看谁最重
--------------------------------------------------------------------
面积那一项是 1.4740,意思是每多一平米,总价多 1.4740 万元,也就是 14740 元。
系数最负的三个:区域_郊县 -78.903、区域_经开 -51.504、区域_城北 -36.706。
系数最正的三个:装修_精装 6.006、区域_城中 18.552、装修_豪华 25.288。
系数的单位和特征的尺度绑在一起,面积每多一平米加多少万,和「城东比城中差多少万」不能直接比大小。
想比重要性,得先标准化,或者看第 8 章讲过的那些办法。
! 模型能跑出数,不等于结论能直接用。每个系数都要能说清「它到底在比什么」。
y = df["总价"] 之后,总价和单价都不能再出现在 X 里。漏一个,R 方就会跳到 1.0,那是抄答案。
get_dummies 要在切分之前做,这样训练集和测试集的列完全一致;切完再做容易对不上列。
random_state=42 固定切法。不固定的话,同一份代码跑两次分数不同,很难判断改动有没有用。
30 秒实验
- 把
drop_first改成 False,看特征从 21 列变成多少列,R 方有没有变。 - 把
test_size从 0.2 改成 0.3,看 MAPE 往哪边动。 - 想一想:如果把单价放回 X,R 方会变成多少?为什么这个结果没有意义?
常见误区
- 把总价或单价放进 X:等于把答案抄进去,R 方接近 1,模型其实什么也没学到。
- 分类变量不转哑变量:模型会把「区域」当数字大小来算,城东城西变成 2 和 3,毫无意义。
- 切完再 get_dummies:训练集和测试集列对不齐,预测时直接报错。
- 不设 random_state:分数每次不同,改了半天也不知道是改动生效还是随机波动。
自测三问(点开对答案)
1. 为什么房价数据可以随机切,销售流水不行? —— 房价没有时间先后,随机切不会让训练集混进未来;流水有时间序,随机切就是让模型提前看到未来。 2. R 方 0.9422 说明模型很厉害吗? —— 说明特征好用。只用面积一个特征就有 0.6998,剩下的 0.24 才是其他特征挣来的。 3. 岭回归这次为什么没提分? —— 特征不多也不高度共线,缰绳用不上。它的用处在特征多、共线重的时候。第 11 章收尾。第 12 章换个题目:用 RFM 模型加聚类,把客户分成能分别对待的几类。
- ☐ (本篇完,下一篇 12.1 项目概述、效果与准备)