4、特征编码技术:标签编码、独热编码、目标编码、频率编码、WOE编码

好,咱们进入第四讲。特征编码,说白了就是把非数值型数据变成模型能吃的数字。你想想看,模型它只认识数字,什么「男」「女」、「北京」「上海」,它根本理解不了。所以这一步,是特征工程里绕不开的硬骨头。

我个人习惯把编码技术分成两类:无序类别有序类别。处理方式完全不同,搞混了容易出大问题。下面我挨个讲,每种我都会给出代码示例和避坑指南。

4.1 标签编码(Label Encoding)

标签编码最简单,就是把每个类别映射成一个整数。比如「红、黄、蓝」变成 0、1、2。

我在项目中遇到过有人拿标签编码处理颜色,结果模型学出了「蓝色 > 黄色 > 红色」的排序关系。这显然不合理。所以标签编码只适合有序类别,比如「小、中、大」这种。

适用场景:学历(小学、中学、大学)、评分等级(A、B、C)等有明显顺序的变量。
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
data['education_encoded'] = le.fit_transform(data['education'])
# 小学→0,中学→1,大学→2
注意:我曾经犯过一个错——对「城市」这种无序变量用了标签编码。结果模型把「北京=0,上海=1,深圳=2」当成了数值大小关系,导致预测完全跑偏。记住:无序类别慎用标签编码

4.2 独热编码(One-Hot Encoding)

独热编码是处理无序类别的标准方法。每个类别变成一个独立的 0/1 列。比如颜色有红、黄、蓝,就变成三列:is_red、is_yellow、is_blue。

嗯,这里要注意:如果类别太多,维度会爆炸。比如「城市」有 100 个取值,就会多出 100 列。我建议类别超过 10 个时,先做频率编码或者分组,再用独热。

import pandas as pd

# 方法一:pandas 自带
df_encoded = pd.get_dummies(data, columns=['color'], drop_first=True)

# 方法二:sklearn
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False, drop='first')
encoded = ohe.fit_transform(data[['color']])
小技巧:我习惯用 drop='first' 去掉第一列,避免多重共线性。说白了就是少一列不影响信息,还能让模型更稳定。

4.3 目标编码(Target Encoding)

目标编码是我在 Kaggle 比赛里最常用的技巧之一。它的思路很简单:用目标变量的均值来替换类别。比如某个城市的用户转化率是 0.3,那这个城市就编码成 0.3。

但这里有个大坑——数据泄露。如果你用全量数据计算均值,模型会「偷看」到未来的信息。我建议一定要做交叉验证式的目标编码。

from category_encoders import TargetEncoder

# 推荐使用交叉验证版本
te = TargetEncoder(cols=['city'], smoothing=10)
# smoothing 参数控制正则化强度,防止过拟合
data['city_encoded'] = te.fit_transform(data['city'], data['target'])
避坑指南:我曾经在风控项目里直接用全量目标编码,结果线上效果暴跌。后来发现是因为训练集和测试集分布不一致。记住:目标编码一定要做交叉验证,并且保存编码映射

4.4 频率编码(Frequency Encoding)

频率编码就是用类别出现的次数或频率来替换。比如「北京」出现了 1000 次,就编码成 1000。这个方法的好处是:不会过拟合,而且能捕捉到「稀有类别」的信息。

我个人的经验是:频率编码适合类别很多(比如 50 个以上)且目标变量与类别频次相关的场景。比如电商里「热门城市」和「冷门城市」的购买行为差异很大。

# 频率编码
freq_map = data['city'].value_counts(normalize=True)  # 频率
data['city_freq'] = data['city'].map(freq_map)

# 或者用计数
count_map = data['city'].value_counts()
data['city_count'] = data['city'].map(count_map)
小提示:频率编码可以和独热编码组合使用。比如先用频率编码把低频类别合并成「其他」,再对剩下的高频类别做独热。这样既控制了维度,又保留了信息。

4.5 WOE编码(Weight of Evidence)

WOE 编码在金融风控领域用得特别多。它衡量的是「好客户」和「坏客户」在每个类别上的分布差异。公式有点复杂,但说白了就是:这个类别对目标变量的区分度有多大

我记得在做一个信用评分卡项目时,WOE 编码帮我们提升了 5% 的 KS 值。它的好处是自带单调性,特别适合逻辑回归这类线性模型。

import numpy as np

def woe_encoding(data, feature, target):
    # 计算每个类别的 WOE 值
    grouped = data.groupby(feature)[target].agg(['count', 'sum'])
    grouped['good'] = grouped['count'] - grouped['sum']
    grouped['bad'] = grouped['sum']
    
    # 计算分布
    grouped['dist_good'] = grouped['good'] / grouped['good'].sum()
    grouped['dist_bad'] = grouped['bad'] / grouped['bad'].sum()
    
    # 计算 WOE
    grouped['woe'] = np.log(grouped['dist_good'] / grouped['dist_bad'])
    
    # 处理无穷值
    grouped['woe'] = grouped['woe'].replace([np.inf, -np.inf], 0)
    
    return grouped['woe'].to_dict()

woe_map = woe_encoding(data, 'city', 'is_default')
data['city_woe'] = data['city'].map(woe_map)
注意:WOE 编码要求每个类别都有足够的样本量。如果某个类别只有几个样本,WOE 值会非常不稳定。我一般会要求每个类别至少 50 个样本,否则就合并到「其他」类别。

4.6 如何选择编码方式?

我整理了一个简单的决策表,方便你快速选择:

变量类型 类别数量 推荐编码 适用模型
有序类别 任意 标签编码 树模型、线性模型
无序类别 < 10 独热编码 所有模型
无序类别 10-50 频率编码 + 独热 树模型、线性模型
无序类别 > 50 目标编码 / 频率编码 树模型
风控场景 任意 WOE 编码 逻辑回归

最后说一句:没有银弹。我每次做项目都会试 2-3 种编码方式,看哪个在验证集上效果最好。你想想看,数据分布千变万化,死守一种方法肯定不行。

下一讲我们会聊特征缩放——标准化和归一化。到时候我会分享一个我踩过的坑:为什么标准化之后模型反而变差了。咱们下期见。