跳到主要内容

哑变量编码

一、直观理解:把类别变成模型能算的 0/1 开关​

哑变量编码(Dummy Encoding / One-Hot 的回归写法)解决的问题很具体:城市、季节、品牌、天气类型这类名义变量没有数值大小关系,却常常要进入回归、logistic、SVM 等数值模型。

如果直接把“春=1、夏=2、秋=3、冬=4”塞进线性模型,软件能算出系数,但数学含义是错的——它会假定“冬比春大 3,且间距相等”。正确做法是:为每个类别设置一盏 0/1 指示灯。

例如季节有 4 类,可生成:

季节是夏是秋是冬
春000
夏100
秋010
冬001

这里“春”作为基准类,不单独占一列。某个样本若三列全为 0,就表示它属于基准类。

一句话:

哑变量不是把文字变成数字标签,而是把“属于哪一类”拆成一组互斥的是/否问题。

二、适用场景与优缺点​

类型说明
适用场景名义型类别变量要进入线性模型、广义线性模型、距离型模型;需要解释“相对某基准类的效应”;类别数不多且每类有足够样本。
核心优点不引入虚假顺序;系数可直接解释为相对基准的差异;实现标准,便于复现与答辩。
局限与缺点类别很多时维度膨胀;稀有类系数不稳定;树模型通常不必手动 one-hot;若误留全部 KK 列,线性模型会撞上完全共线。

使用前检查清单:

  1. 变量是名义型还是有序型?有序型可考虑整数编码或样条,不必一律 one-hot;
  2. 各类频数是否过低(如只有 1~2 个样本);
  3. 是否存在“未知/其他/缺失”类,需要单独编码规则;
  4. 下游模型是否包含截距,从而决定是否去掉基准列;
  5. 训练集与测试集的类别字典是否一致。

对随机森林、XGBoost 等树模型,类别处理方式因实现而异;有的支持类别特征,有的仍需编码。不能默认“有类别就必须 dummy”。

三、核心原理:指示变量与基准类​

设类别变量 xx 有 KK 个水平:c1,…,cKc_1,\ldots,c_K。

1. 完整 one-hot​

定义 KK 个指示变量:

Dik=1(xi=ck)={1,xi=ck0,否则D_{ik}=\mathbf{1}(x_i=c_k)= \begin{cases} 1,&x_i=c_k\\ 0,&\text{否则} \end{cases}

则对任意样本恰好有 ∑k=1KDik=1\sum_{k=1}^{K}D_{ik}=1。

2. 回归中的哑变量(去掉一列)​

若模型含截距 β0\beta_0,再放入全部 KK 个指示列,设计矩阵会完全共线,称为虚拟变量陷阱。因此通常只保留 K−1K-1 列,把某一类 c1c_1 作为基准:

yi=β0+β2Di2+⋯+βKDiK+εiy_i=\beta_0+\beta_2 D_{i2}+\cdots+\beta_K D_{iK}+\varepsilon_i

此时:

  • β0\beta_0:基准类 c1c_1 的平均水平(在无其他变量时);
  • βk\beta_k:类 ckc_k 相对基准类的平均差异。

3. 与效应编码等变体​

除“丢一个水平”的 dummy 外,还有 effect coding(以总体均值为对照)等。数学建模竞赛中最常用、最好解释的仍是:one-hot + 含截距时丢基准类。

四、关键决策​

1. 基准类如何选​

优先选择:

  • 样本量最大的类(估计更稳);
  • 业务上最自然的对照(如“对照组”“工作日”“默认套餐”);
  • 需要重点报告“相对谁”的那一类。

基准类不同,系数数值会变,但模型整体拟合(在同一线性空间下)通常等价,解释语句要跟着改。

2. 训练/测试必须共用字典​

正确流程:

  1. 仅用训练集确定类别水平集合;
  2. 按该集合生成列名;
  3. 测试集出现未见类别时,映射到“其他”类,或整行标记后单独处理,不能静默错位。

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,应说“在其他条件相同下,上海比重庆平均高 5”,而不是“上海的效应是 5”;
  • 多个哑变量同时为 0,对应基准类;
  • 不要把 dummy 系数解释成类别的绝对评分,除非明确做了额外约束。

完整报告建议包含:

  1. 原始类别水平及频数;
  2. 基准类及其选择理由;
  3. 最终哑变量列名与维数;
  4. 未见类别与缺失的处理规则;
  5. 主要类别的效应大小与置信区间(如有);
  6. 是否因高基数做了合并。

七、常见误区​

1. 把名义变量当成连续整数编码​

“红=1、蓝=2、绿=3”会向模型注入不存在的顺序和间距,结论不可信。

2. 含截距还保留全部 KK 个指示列​

这会引发完全共线,系数不稳定或软件自动删列。要么去一列,要么去掉截距(后者更少用,解释也别扭)。

3. 测试集重新 dummyvar 导致列错位​

测试集若类别缺失,dummyvar 列数可能变少,与训练模型维度不一致。必须锁定训练字典。

4. 稀有类硬编码​

某类只有两三个样本时,其 dummy 系数方差极大,容易过拟合。应合并或改用正则化/层级模型。

5. 解释时忘记基准类​

只报“夏季系数显著”却不说明对照的是春季还是全年平均,阅卷者无法还原含义。

八、小结​

哑变量编码的核心思想可以概括为:

用 K−1K-1 个 0/1 指示变量表示 KK 类名义特征,使类别信息能进入数值模型,并以相对基准类的差异给出可解释系数。

它是类别特征进入回归家族模型的标准入口。用对了,模型透明、可复核;用错了,最常见的坑是虚假序数编码、虚拟变量陷阱和训练测试列不一致。把字典、基准类和稀有水平处理写进报告,这项预处理就站得住。