哑变量编码
一、直观理解:把类别变成模型能算的 0/1 开关
哑变量编码(Dummy Encoding / One-Hot 的回归写法)解决的问题很具体:城市、季节、品牌、天气类型这类名义变量没有数值大小关系,却常常要进入回归、logistic、SVM 等数值模型。
如果直接把“春=1、夏=2、秋=3、冬=4”塞进线性模型,软件能算出系数,但数学含义是错的——它会假定“冬比春大 3,且间距相等”。正确做法是:为每个类别设置一盏 0/1 指示灯。
例如季节有 4 类,可生成:
| 季节 | 是夏 | 是秋 | 是冬 |
|---|---|---|---|
| 春 | 0 | 0 | 0 |
| 夏 | 1 | 0 | 0 |
| 秋 | 0 | 1 | 0 |
| 冬 | 0 | 0 | 1 |
这里“春”作为基准类,不单独占一列。某个样本若三列全为 0,就表示它属于基准类。
一句话:
哑变量不是把文字变成数字标签,而是把“属于哪一类”拆成一组互斥的是/否问题。
二、适用场景与优缺点
| 类型 | 说明 |
|---|---|
| 适用场景 | 名义型类别变量要进入线性模型、广义线性模型、距离型模型;需要解释“相对某基准类的效应”;类别数不多且每类有足够样本。 |
| 核心优点 | 不引入虚假顺序;系数可直接解释为相对基准的差异;实现标准,便于复现与答辩。 |
| 局限与缺点 | 类别很多时维度膨胀;稀有类系数不稳定;树模型通常不必手动 one-hot;若误留全部 列,线性模型会撞上完全共线。 |
使用前检查清单:
- 变量是名义型还是有序型?有序型可考虑整数编码或样条,不必一律 one-hot;
- 各类频数是否过低(如只有 1~2 个样本);
- 是否存在“未知/其他/缺失”类,需要单独编码规则;
- 下游模型是否包含截距,从而决定是否去掉基准列;
- 训练集与测试集的类别字典是否一致。
对随机森林、XGBoost 等树模型,类别处理方式因实现而异;有的支持类别特征,有的仍需编码。不能默认“有类别就必须 dummy”。
三、核心原理:指示变量与基准类
设类别变量 有 个水平:。
1. 完整 one-hot
定义 个指示变量:
则对任意样本恰好有 。
2. 回归中的哑变量(去掉一列)
若模型含截距 ,再放入全部 个指示列,设计矩阵会完全共线,称为虚拟变量陷阱。因此通常只保留 列,把某一类 作为基准:
此时:
- :基准类 的平均水平(在无其他变量时);
- :类 相对基准类的平均差异。
3. 与效应编码等变体
除“丢一个水平”的 dummy 外,还有 effect coding(以总体均值为对照)等。数学建模竞赛中最常用、最好解释的仍是:one-hot + 含截距时丢基准类。
四、关键决策
1. 基准类如何选
优先选择:
- 样本量最大的类(估计更稳);
- 业务上最自然的对照(如“对照组”“工作日”“默认套餐”);
- 需要重点报告“相对谁”的那一类。
基准类不同,系数数值会变,但模型整体拟合(在同一线性空间下)通常等价,解释语句要跟着改。
2. 训练/测试必须共用字典
正确流程:
- 仅用训练集确定类别水平集合;
- 按该集合生成列名;
- 测试集出现未见类别时,映射到“其他”类,或整行标记后单独处理,不能静默错位。
3. 高基数类别
省份、商品 ID、用户 ID 动辄成百上千维。可考虑:
- 合并稀有水平为“其他”;
- 目标编码(严格在折内计算,防泄漏);
- 嵌入或频次特征;
- 先聚合再建模。
4. 缺失值
“缺失”不要随便当成某一已知类。更稳妥是新增 is_missing 指示,或在业务允许时单列“未知”。
五、MATLAB 示例
% 原始类别数据
city = categorical(["重庆"; "北京"; "重庆"; "上海"; "北京"]);
y = [12; 18; 13; 20; 17]; % 假设的响应变量
% 1) 生成完整 one-hot(列顺序按 categorical 的 categories)
Dfull = dummyvar(city);
catNames = categories(city);
disp("完整 one-hot:");
disp(array2table(Dfull, "VariableNames", catNames));
% 2) 回归用:去掉第一类作为基准,避免虚拟变量陷阱
% 这里以 categories 的第一个水平为基准
D = Dfull(:, 2:end);
dummyNames = catNames(2:end);
% 3) 带截距的线性回归:y ~ 1 + 哑变量
X = [ones(height(Dfull), 1), D];
beta = X \ y;
fprintf("基准类:%s\n", string(catNames(1)));
fprintf("截距(基准类均值)= %.4f\n", beta(1));
for j = 1:numel(dummyNames)
fprintf("系数[%s 相对基准] = %.4f\n", string(dummyNames(j)), beta(j+1));
end
% 4) 用 fitlm 更易读(表格方式)
tbl = table(city, y, "VariableNames", ["city", "y"]);
mdl = fitlm(tbl, "y ~ city"); % 自动处理类别变量与基准
disp(mdl);
% 5) 测试集对齐:固定训练字典
cityTest = categorical(["上海"; "广州"], catNames); % 限定同一水平集合
% “广州”不在训练水平中时,会被标为 <undefined>
disp(cityTest);
若需手动处理未见类,可先把训练水平外的值合并为 "其他",再 dummyvar。
六、如何解释结果
哑变量系数必须带上“相对谁”:
- 若基准为重庆,上海的系数 ,应说“在其他条件相同下,上海比重庆平均高 5”,而不是“上海的效应是 5”;
- 多个哑变量同时为 0,对应基准类;
- 不要把 dummy 系数解释成类别的绝对评分,除非明确做了额外约束。
完整报告建议包含:
- 原始类别水平及频数;
- 基准类及其选择理由;
- 最终哑变量列名与维数;
- 未见类别与缺失的处理规则;
- 主要类别的效应大小与置信区间(如有);
- 是否因高基数做了合并。
七、常见误区
1. 把名义变量当成连续整数编码
“红=1、蓝=2、绿=3”会向模型注入不存在的顺序和间距,结论不可信。
2. 含截距还保留全部 个指示列
这会引发完全共线,系数不稳定或软件自动删列。要么去一列,要么去掉截距(后者更少用,解释也别扭)。
3. 测试集重新 dummyvar 导致列错位
测试集若类别缺失,dummyvar 列数可能变少,与训练模型维度不一致。必须锁定训练字典。
4. 稀有类硬编码
某类只有两三个样本时,其 dummy 系数方差极大,容易过拟合。应合并或改用正则化/层级模型。
5. 解释时忘记基准类
只报“夏季系数显著”却不说明对照的是春季还是全年平均,阅卷者无法还原含义。
八、小结
哑变量编码的核心思想可以概括为:
用 个 0/1 指示变量表示 类名义特征,使类别信息能进入数值模型,并以相对基准类的差异给出可解释系数。
它是类别特征进入回归家族模型的标准入口。用对了,模型透明、可复核;用错了,最常见的坑是虚假序数编码、虚拟变量陷阱和训练测试列不一致。把字典、基准类和稀有水平处理写进报告,这项预处理就站得住。