前面的步骤中,我们构造了一些新的强特征字段,用来帮助模型的学习。接下来我们继续处理数据集中的数据。
# 2.3 有序质量列 -> 数值映射
# 这些列原来是文本(Ex/Gd/TA/Fa/Po),本质是“有序类别”
# 映射为 5~1 的分值;缺失/NA 映射为 0,表示“无/不可用”
ORD_MAP = {"Ex": 5, "Gd": 4, "TA": 3, "Fa": 2, "Po": 1, np.nan: 0, "NA": 0}
ORD_COLS = [
"ExterQual", "ExterCond", "BsmtQual", "BsmtCond", "HeatingQC",
"KitchenQual", "FireplaceQu", "GarageQual", "GarageCond"
]
def map_ordered_qualities(df):
df = df.copy()
for c in ORD_COLS:
if c in df.columns:
# map + fillna 双保险;astype(float) 方便后续数值处理
df[c] = df[c].map(ORD_MAP).fillna(0).astype(float)
return df
在第一个项目Rossmann销售预测中,我们提到过,最终参与机器学习的字段,都必须是数值类型的字段,因此,有些代表分类的字段,我们必须要把分类的枚举值,映射成数字。
如上述代码所示,ORD_COLS中存储的各字段,都是“分类”类型的字段,其取值可枚举且有限,所以统一映射为5~1,空值映射为0.
处理完有序值,我们要处理一些异常值。
# 2.4 修正 GarageYrBlt 异常(例如年份越界等)
# 若出现不合理年份:小于 1800 或 大于售出年份 YrSold,则用 YearBuilt 回填;否则置 NaN
def fix_garage_year(df):
df = df.copy()
if "GarageYrBlt" in df.columns:
mask = (df["GarageYrBlt"].notna())
if "YrSold" in df.columns:
bad = (df["GarageYrBlt"] < 1800) | (df["GarageYrBlt"] > df["YrSold"])
else:
bad = (df["GarageYrBlt"] < 1800)
if "YearBuilt" in df.columns:
df.loc[mask & bad, "GarageYrBlt"] = df.loc[mask & bad, "YearBuilt"]
else:
df.loc[mask & bad, "GarageYrBlt"] = np.nan
return df
车库的建造年份GarageYrBlt,如果早于1800年,或者晚于当前售卖年份,都是不合理的。1885年第一辆汽车才发明出来,你1800年前哪来的车库?或者你车库建造年份比当前售卖年份都晚,你玩期房呢?这些都会给模型产生噪声,降低预测准确性。所以,对数据集进行细致的分析和清洗,是提高模型学习效果的一大助力。
好,下面就进入了我认为本项目最“枯燥”确最有技术水平的部分之一,对Neighborhood列进行Out-of-Fold Target Encoding(分层目标编码)。
聊之前不得不提一下对“分类”类型字段的传统处理方式One-Hot独热编码。这个让AI来讲解最合适。
🧩 一、为什么需要独热编码?
机器学习模型(尤其是线性模型、树模型)不能直接理解文字或类别。
例如这一列数据 👇
| 房屋ID | Neighborhood(社区) |
|---|---|
| 1 | CollgCr |
| 2 | OldTown |
| 3 | Edwards |
| 4 | CollgCr |
这里的 Neighborhood 是“类别型特征”,
模型看不懂 “CollgCr”、“OldTown” 是什么意思。
我们必须把它们变成数字。
🔢 二、错误示范:直接映射成数字会误导模型
如果你这么做:
| Neighborhood | 编码 |
|---|---|
| CollgCr | 1 |
| OldTown | 2 |
| Edwards | 3 |
那模型会以为:
“OldTown > CollgCr,Edwards > OldTown”
产生错误的“大小顺序”假设!
但实际上社区之间并没有大小关系。
这就是“伪数值列误导模型”的典型问题。
🧠 三、正确做法:独热编码(One-Hot Encoding)
思路:
为每一个类别新建一列,用 0/1 表示“是否属于这个类别”。
对于上面的 3 个社区,独热编码结果如下👇
| 房屋ID | CollgCr | OldTown | Edwards |
|---|---|---|---|
| 1 | 1 | 0 | 0 |
| 2 | 0 | 1 | 0 |
| 3 | 0 | 0 | 1 |
| 4 | 1 | 0 | 0 |
解释:
- 第1行是 CollgCr →
CollgCr=1,其余为 0; - 第2行是 OldTown →
OldTown=1; - 第3行是 Edwards →
Edwards=1; - 第4行再次是 CollgCr → 同样只激活 CollgCr 列。
“独热” 就是说在这几列中,同一行只有一个 1,其余都是 0。
🧮 四、用 pandas 实现独热编码
在 Python 中非常简单:
import pandas as pd
df = pd.DataFrame({
"Neighborhood": ["CollgCr", "OldTown", "Edwards", "CollgCr"]
})
# 独热编码
df_encoded = pd.get_dummies(df, prefix="Neighborhood", dtype=int)
print(df_encoded)
输出:
Neighborhood_CollgCr Neighborhood_Edwards Neighborhood_OldTown
0 1 0 0
1 0 0 1
2 0 1 0
3 1 0 0
🧩 五、在房价预测项目中的意义
像 Neighborhood, HouseStyle, Exterior1st 这类字段都是类别特征。
在特征工程里,我们会用:
all_dummy_df = pd.get_dummies(all_df, dtype=int)
它会自动对所有 object 类型的列做独热编码,
生成大量新的 0/1 特征,让模型可以识别每个类别的存在与否。
💡 六、优点与缺点
| 优点 | 缺点 |
|---|---|
| 简单易懂,几乎所有模型都能直接用 | 当类别很多时,列数暴增(高维稀疏) |
| 不引入虚假的“大小关系” | 不能捕捉类别之间的相似度(A 和 B 之间完全独立) |
所以在类别很多(上百个)的场景下,我们会考虑更高级的做法,比如你前面问到的 Target Encoding(目标编码)。
🚀 七、小结一句话
独热编码(One-Hot Encoding):
把每个类别拆成一组 0/1 特征,
每行数据“只点亮一个”,让模型能识别不同类别而不会被误导。
好,独热编码的介绍完了,是不是觉得很妙?是的,我也这么觉得。但是分层目标编码更妙,我们下篇文章再说。
独热编码详解与应用
635

被折叠的 条评论
为什么被折叠?



