第22课:类别特征编码:One-Hot、Label 与 Target Encoding
1. 学习目标
完成本课后,你将能够:
- 理解为什么机器学习模型通常不能直接使用文本类别的特征(如“红”、“黄”、“蓝”)。
- 掌握三种最常用的类别特征编码方法:独热编码(One-Hot Encoding)、标签编码(Label Encoding)和目标编码(Target Encoding)的原理与实现。
- 能够根据数据特点和模型类型,初步判断应选择哪种编码方法。
- 实践使用
scikit-learn和pandas库对数据集中的类别特征进行编码处理。
2. 核心概念
想象一下,你要让电脑理解“红绿灯”的信息。你直接告诉它“红灯停,绿灯行”,但电脑只认识数字(0, 1, 2...)。类别特征编码就是把“红”、“黄”、“绿”这样的文本标签,转换成数字表示的过程,让机器学习模型能够“读懂”它们。
常见的编码方法各有优劣,就像用不同的工具处理不同的任务:
-
独热编码(One-Hot Encoding):为每个类别创建一个新的二进制(0/1)列。就像是为“红灯”、“黄灯”、“绿灯”分别设立一个“开关”。
- 优点:不会引入错误的顺序关系,适用于大多数模型(如线性模型、神经网络)。
- 缺点:如果类别很多(如上百个城市名),会显著增加数据的维度(列数),可能导致“维度灾难”和计算变慢。
-
标签编码(Label Encoding):直接为每个类别分配一个唯一的整数(如
红=0, 黄=1, 绿=2)。- 优点:简单,不增加维度。
- 缺点:引入了人为的顺序(
0<1<2),模型可能会误认为“绿(2)”比“红(0)”数值上更大、更重要,这可能对某些模型(如线性回归)产生误导。它更适合本身就有顺序的类别(如“低、中、高”),或者树模型(决策树、随机森林、XGBoost),因为树模型主要关心类别是否相同,而不关心数字大小。
-
目标编码(Target Encoding):用该类别对应的目标变量(通常是因变量)的某种统计值(如均值)来替换原类别。例如,在预测房价时,“海淀区”的房价均值可能是10万/平米,那么“海淀区”这个类别就用10来编码。
- 优点:能有效利用目标变量信息,尤其对高基数(类别很多)的特征有效,并且不增加维度。
- 缺点:非常容易导致数据泄露(如果用整个数据集计算均值)和过拟合。必须在交叉验证的循环中或使用留一法谨慎计算。
3. 代码示例
下面我们将在一个简单的数据集上演示这三种编码方法。
import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
from category_encoders import TargetEncoder # 需要安装: pip install category-encoders
# 创建一个示例数据集
data = {
'城市': ['北京', '上海', '广州', '深圳', '北京', '上海', '广州'],
'交通灯': ['红', '黄', '绿', '红', '黄', '绿', '红'],
'目标房价': [8.5, 7.0, 5.5, 6.0, 8.8, 7.2, 5.7] # 单位:万/平米
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
print("-" * 40)
# 1. 独热编码 (One-Hot Encoding)
# 方法一:使用 pandas 的 get_dummies,非常方便
df_onehot = pd.get_dummies(df[['城市', '交通灯']], drop_first=False) # drop_first=True 可以避免多重共线性,但初学可先设为False
print("独热编码结果:")
print(df_onehot)
print("-" * 40)
# 2. 标签编码 (Label Encoding)
# 通常一次只对一列进行编码
le = LabelEncoder()
df['交通灯_label'] = le.fit_transform(df['交通灯']) # 将文本映射为数字
print("标签编码结果(‘交通灯’列):")
print(df[['交通灯', '交通灯_label']])
print(f"\n标签映射关系: {list(zip(le.classes_, le.transform(le.classes_)))}")
print("-" * 40)
# 3. 目标编码 (Target Encoding)
# 注意:这里我们演示一个简单但可能有数据泄露风险的方式(实际应用中需更谨慎)
encoder = TargetEncoder(cols=['城市'])
df['城市_target'] = encoder.fit_transform(X=df['城市'], y=df['目标房价']) # 用每个城市的平均房价来编码
print("目标编码结果(‘城市’列):")
print(df[['城市', '城市_target']])
print("\n说明:‘北京’的编码值是其所有记录的‘目标房价’的平均值:(8.5+8.8)/2 = 8.65")
print("-" * 40)
# 查看最终整合的数据
print("最终编码后数据(部分列):")
print(df[['城市', '城市_target', '交通灯', '交通灯_label']])
4. 实践练习
-
基础应用:对以下数据中的“水果”列进行独热编码。
fruits = pd.DataFrame({'水果': ['苹果', '香蕉', '橙子', '香蕉', '苹果'], '价格': [5, 3, 4, 3.5, 5.5]})要求:输出编码后的新DataFrame。
-
场景选择:你正在处理一个包含“客户等级”(普通、银卡、金卡、钻石)的客户流失预测数据集。你打算使用逻辑回归模型。你会选择哪种编码方法?为什么?
-
挑战(结合上节课知识):假设你有一个包含“商品类别”(有50多种)和“商品价格”的数据集,价格列有一些异常高值。请设计一个处理流程: a. 首先处理“价格”列的异常值(例如,使用 IQR 方法截断)。 b. 然后,对“商品类别”进行目标编码(以处理后的“价格”作为目标变量)。 要求:写出伪代码或关键步骤。
5. 常见错误
- 对所有特征盲目使用独热编码:如果类别特征有上百个取值(如“用户ID”、“邮编”),独热编码会导致特征维度爆炸,使模型训练缓慢且容易过拟合。应优先考虑标签编码或目标编码。
- 对无序类别使用标签编码:例如,将“颜色:红0、黄1、蓝2”编码后,模型可能会错误地认为蓝色(2)在数值上比红色(0)更重要,或存在“蓝 > 黄 > 红”的顺序关系,这对于颜色这类名义变量是不合理的。
- 在目标编码中发生数据泄露:在训练集上计算目标变量的均值时,错误地使用了整个数据集(包含验证集和测试集)的信息。这会导致模型性能评估过于乐观,在实际新数据上表现变差。正确做法是只在训练集的每个折(fold)中计算。
- 忘记处理缺失值:很多编码器无法直接处理缺失值(NaN)。在编码前,需要决定是填充缺失值(如用“未知”类别)还是删除含有缺失值的行。
6. 小结
本节课我们学习了处理类别特征的关键技术——编码:
- 必要性:机器学习模型需要数值输入,因此必须将文本类别转换为数字。
- 三大方法:
- One-Hot Encoding:创建二进制列,无序且通用,但易导致维度灾难。
- Label Encoding:映射为整数,简单但可能引入虚假顺序,适合树模型或有序类别。
- Target Encoding:用目标变量统计值编码,信息丰富但易泄露,需谨慎使用。
- 选择依据:应考虑类别基数(多少类)、是否有自然顺序、以及所用机器学习模型的类型。
掌握类别特征编码,是你构建有效机器学习模型的重要一步。在下一课,我们将更深入地探讨如何基于领域知识构建全新的、更有意义的特征。