4. 编码分类变量:从标签到数值的四种核心映射
分类变量(Categorical Variable)是数据中最常见的非数值特征,但绝大多数机器学习模型只能处理数值输入。因此,将分类变量转换为数值表示是特征工程中绕不开的基础操作。本章详细拆解四种最常用的编码方法:标签编码、独热编码、目标编码和频率编码,并给出各自适用场景与避坑指南。
核心原则:编码方式的选择取决于分类变量的基数(唯一类别数量)、是否有序、以及下游模型对数值尺度的敏感程度。没有“万能编码”,只有“最合适编码”。
4.1 四种编码方法总览
| 编码方法 | 映射方式 | 输出维度 | 是否保留顺序信息 | 典型适用模型 | 主要风险 |
|---|---|---|---|---|---|
| 标签编码 | 每个类别 → 一个整数(0,1,2,…) | 1 列 | 是(人为赋予顺序) | 树模型(决策树、随机森林、XGBoost) | 误导线性模型产生虚假顺序 |
| 独热编码 | 每个类别 → 一个二进制向量 | K 列(K 为类别数) | 否 | 线性模型、神经网络、KNN、SVM | 高基数时维度爆炸、稀疏性 |
| 目标编码 | 每个类别 → 目标变量均值(+平滑) | 1 列 | 否(基于目标统计) | 树模型、线性模型(需正则化) | 过拟合、目标泄露、需交叉验证 |
| 频率编码 | 每个类别 → 该类别在样本中的出现频次/频率 | 1 列 | 否(基于分布) | 树模型、线性模型 | 不同类别可能映射到相同频率值 |
4.2 标签编码(Label Encoding)
标签编码将每个类别映射为一个从 0 开始的整数。例如颜色特征:红→0,蓝→1,绿→2。实现简单,输出仅占一列,内存开销极小。
⚠️ 关键陷阱:标签编码引入了人为的数值顺序。线性回归、逻辑回归、SVM 等模型会认为“绿(2)是红(0)的两倍”,这通常毫无意义。因此标签编码仅推荐用于树模型(决策树、随机森林、梯度提升树),因为树模型基于分裂点而非数值距离做决策。
# 标签编码示例(Python + scikit-learn)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
data['color_encoded'] = le.fit_transform(data['color'])
# 输出:红→0, 蓝→1, 绿→2
4.3 独热编码(One-Hot Encoding)
独热编码为每个类别创建一个独立的二进制列(0 或 1)。例如颜色特征有 3 个类别,则生成 3 列:is_red、is_blue、is_green。每行只有一个位置为 1,其余为 0。
优点:完全消除顺序误导,适合线性模型和神经网络。
缺点:当类别基数高(如城市名、用户ID)时,特征维度急剧膨胀,导致稀疏矩阵和计算开销剧增。
# 独热编码示例(Python + pandas)
import pandas as pd
one_hot = pd.get_dummies(data['color'], prefix='color')
data = pd.concat([data, one_hot], axis=1)
# 输出:color_red, color_blue, color_green 三列
降维技巧:对于高基数独热编码,可结合主成分分析(PCA)或截断 SVD 进行降维;或只保留出现频次最高的前 N 个类别,其余归为“其他”类。
4.4 目标编码(Target Encoding)
目标编码用每个类别对应的目标变量均值来替换原始类别。例如在房价预测中,计算每个城市的历史平均房价,然后用该均值编码城市特征。为缓解过拟合,通常加入平滑(Smoothing)或交叉验证。
平滑公式:
encoded_value = (n_i * mean_i + m * global_mean) / (n_i + m)
# n_i:类别 i 的样本数
# mean_i:类别 i 的目标均值
# m:平滑参数(控制全局均值的权重)
⚠️ 目标泄露风险:如果在训练集上直接计算目标均值并用于同一训练集,模型会学到“类别→目标”的捷径,导致严重过拟合。正确做法是使用交叉验证目标编码:在每一折中,用其他折的数据计算目标均值,仅对当前折的样本进行编码。
# 目标编码示例(Python + category_encoders)
import category_encoders as ce
encoder = ce.TargetEncoder(cols=['city'], smoothing=10)
data['city_encoded'] = encoder.fit_transform(data['city'], data['price'])
4.5 频率编码(Frequency Encoding)
频率编码将每个类别替换为该类别在数据集中出现的频次或频率。例如“北京”出现 500 次,“上海”出现 200 次,则分别编码为 500 和 200。这种方法保留了类别的分布信息,且输出仅一列,不会增加维度。
适用场景:当类别出现频次与目标变量存在相关性时(例如高频类别往往对应更高或更低的目标值),频率编码非常有效。它天然对稀有类别进行了压缩,减少了噪声影响。
# 频率编码示例(Python + pandas)
freq_map = data['city'].value_counts(normalize=True) # 频率
data['city_freq'] = data['city'].map(freq_map)
# 也可使用频次(绝对计数)
freq_map_abs = data['city'].value_counts()
data['city_freq_abs'] = data['city'].map(freq_map_abs)
4.6 编码方法选择决策流程
4.7 实战对比:不同编码对模型的影响
以下表格展示了一个包含 3 个类别的城市特征,分别使用四种编码后的数值结果:
| 原始城市 | 标签编码 | 独热编码(3列) | 目标编码(目标:房价) | 频率编码 |
|---|---|---|---|---|
| 北京 | 0 | [1,0,0] | 85000(均价) | 0.45(频率) |
| 上海 | 1 | [0,1,0] | 78000(均价) | 0.35(频率) |
| 广州 | 2 | [0,0,1] | 62000(均价) | 0.20(频率) |
最佳实践总结:
- 线性模型 / 神经网络:优先使用独热编码(低基数)或频率编码(高基数)。
- 树模型:标签编码或目标编码通常效果更好,且内存效率高。
- 高基数(K > 50):避免直接独热编码;考虑目标编码(带交叉验证)或频率编码。
- 有序分类(如学历:小学 < 中学 < 大学):标签编码天然保留顺序,是合理选择。
- 测试集处理:所有编码的映射字典必须从训练集学习,并应用到测试集,避免数据泄露。