查看全课大纲(第 12 / 35 节)

类别型数据特征构建

约 5 分钟

小象实战讲义 · 特征工程精讲与实践

在特征工程中,我们处理的第二大类数据是类别型数据。这类数据通常以字符串或文本形式表示,如NBA球队名称、汽车品牌、衣服颜色等。计算机无法直接处理这些文本信息,因此我们需要将其转换为数值形式,这个过程称为编码(encoding)。本节将系统讲解两种最核心的类别型数据编码方法:序号编码(Label Encoding)和独热编码(One-Hot Encoding),并深入分析它们在不同建模场景下的适用性。

💡 核心导读

  • 类别型数据的本质:理解字符串/文本型类别数据为何需要转换为数值形式
  • 序号编码(Label Encoding):将不同类别映射为有序整数的编码方法
  • 独热编码(One-Hot Encoding):将类别平铺为多个二值特征的编码方法
  • 编码方法的选择策略:根据模型类型(树模型 vs. 计算型模型)选择合适的编码方式
  • 实践注意事项:不同编码方法在pandas和scikit-learn中的实现细节与版本差异

类别型数据与编码需求

类别型数据(Categorical Data)是指那些表示类别、类型或分组的数据,它们通常是离散的、有限的取值。例如:

  • 动物种类:狗、猫、老鼠
  • 汽车品牌:丰田、本田、福特
  • 衣服颜色:红色、蓝色、绿色

这些数据在原始形式下是字符串或文本,计算机无法直接进行数学运算。为了能让机器学习算法处理这些特征,我们需要将其转换为数值形式,这个过程就是编码(Encoding)。

编码的核心目标是将类别信息转换为计算机能够理解和计算的数值表示,同时尽可能保留原始数据的语义信息。

序号编码(Label Encoding)

序号编码(Label Encoding)是最简单的编码方式之一,它将类别变量中的每一类赋予一个唯一的数值,从而完成从原始类别到数值型的转换。

编码原理

假设我们有一列动物数据:['狗', '猫', '狗', '老鼠', '猫']

通过序号编码,我们可以:

  • 将”狗”编码为数字1
  • 将”猫”编码为数字2
  • 将”老鼠”编码为数字3

这样,原始数据['狗', '猫', '狗', '老鼠', '猫']就变成了[1, 2, 1, 3, 2]。

适用场景与注意事项

序号编码最大的特点是引入了大小关系。编码后的数字1、2、3天然带有顺序关系(1<2<3),这会影响某些模型的训练。

适用场景:

  • 树模型:如决策树、随机森林、XGBoost、LightGBM、CatBoost等
  • 原因:树模型基于特征值的大小进行分裂,序号编码的大小关系不会对模型产生负面影响

不适用场景:

  • 计算型模型:如逻辑回归、神经网络、支持向量机等
  • 原因:这些模型会直接使用特征值进行计算,序号编码引入的大小关系会被误认为是真实的数值关系,从而影响模型性能

实践示例

下面我们通过一个完整的示例来演示序号编码的实现:

import pandas as pd
from sklearn.preprocessing import OrdinalEncoder

# 创建示例数据
data = pd.DataFrame({
    'animal': ['狗', '猫', '狗', '老鼠', '猫', '狗', '老鼠']
})

print("原始数据:")
print(data)

# 初始化序号编码器
encoder = OrdinalEncoder()

# 进行编码转换
# 注意:OrdinalEncoder默认输出为二维数组,需要转换为一维或赋值回DataFrame
encoded_data = encoder.fit_transform(data[['animal']])

# 将编码结果转换为DataFrame以便查看
encoded_df = pd.DataFrame(encoded_data, columns=['animal_encoded'])
print("\n序号编码结果:")
print(encoded_df)

# 查看编码映射关系
print("\n编码映射关系:")
for i, category in enumerate(encoder.categories_[0]):
    print(f"{category} -> {i}")
原始数据:
  animal
0      狗
1      猫
2      狗
3     老鼠
4      猫
5      狗
6     老鼠

序号编码结果:
   animal_encoded
0             0.0
1             1.0
2             0.0
3             2.0
4             1.0
5             0.0
6             2.0

编码映射关系:
狗 -> 0
猫 -> 1
老鼠 -> 2

重要说明:

  1. scikit-learn的OrdinalEncoder默认从0开始编码(0, 1, 2, …),而有些库(如category_encoders)可能从1开始编码
  2. 编码器的categories_属性保存了原始的类别顺序,这在需要反向解码时非常有用
  3. 输出是二维数组,需要根据实际情况转换为合适的形式

独热编码(One-Hot Encoding)

独热编码(One-Hot Encoding)是为了解决序号编码引入大小关系问题而设计的编码方法。它的核心思想是:将每个类别都视为平等的,没有大小关系。

编码原理

独热编码的处理方式是将整个类别空间平铺开来。假设有M个不同的类别取值,就会创建M个新的二值特征(列)。

对于每个样本:

  • 如果它属于第i个类别,那么第i个特征取值为1
  • 其他所有特征取值为0

例如,对于三种食物:苹果、鸡肉、香蕉

  • 苹果编码为:[1, 0, 0]
  • 鸡肉编码为:[0, 1, 0]
  • 香蕉编码为:[0, 0, 1]

适用场景

独热编码适用于所有类型的模型,特别是:

  • 计算型模型:逻辑回归、神经网络、支持向量机等
  • 任何需要避免人为引入大小关系的场景

实践示例

下面演示如何使用pandas的get_dummies函数进行独热编码:

import pandas as pd

# 创建示例数据
data = pd.DataFrame({
    'animal': ['狗', '猫', '狗', '老鼠', '猫', '狗', '老鼠']
})

print("原始数据:")
print(data)

# 使用pandas的get_dummies进行独热编码
# 注意:pandas 2.0+ 版本默认返回布尔类型,我们通常需要转换为整数
one_hot_encoded = pd.get_dummies(data['animal'], prefix='animal').astype(int)

print("\n独热编码结果:")
print(one_hot_encoded)

# 将编码结果与原始数据合并
final_data = pd.concat([data, one_hot_encoded], axis=1)
print("\n合并后的数据:")
print(final_data)
原始数据:
  animal
0      狗
1      猫
2      狗
3     老鼠
4      猫
5      狗
6     老鼠

独热编码结果:
   animal_狗  animal_猫  animal_老鼠
0         1         0          0
1         0         1          0
2         1         0          0
3         0         0          1
4         0         1          0
5         1         0          0
6         0         0          1

合并后的数据:
  animal  animal_狗  animal_猫  animal_老鼠
0      狗         1         0          0
1      猫         0         1          0
2      狗         1         0          0
3     老鼠         0         0          1
4      猫         0         1          0
5      狗         1         0          0
6     老鼠         0         0          1

独热编码的注意事项

  1. 维度爆炸问题:当类别数量很多时,独热编码会显著增加特征维度
  2. 多重共线性:独热编码的特征之间存在线性关系(所有特征之和为1),对于某些模型(如线性回归)可能需要删除一列以避免多重共线性
  3. 稀疏表示:大多数位置都是0,只有少数位置是1,这种稀疏表示在存储和计算上可能不够高效

两种编码方法的对比与选择

为了更直观地理解两种编码方法的差异,我们通过一个综合示例进行对比:

import pandas as pd
from sklearn.preprocessing import OrdinalEncoder

# 创建包含多种信息的示例数据
sample_data = pd.DataFrame({
    '宠物': ['狗', '猫', '狗', '仓鼠', '猫', '狗', '仓鼠'],
    '颜色': ['棕色', '白色', '黑色', '白色', '棕色', '黑色', '灰色'],
    '大小': ['大', '中', '大', '小', '中', '大', '小']
})

print("原始数据:")
print(sample_data)
print("\n" + "="*50)

# 序号编码示例
print("\n1. 序号编码结果:")
ordinal_encoder = OrdinalEncoder()
ordinal_encoded = ordinal_encoder.fit_transform(sample_data[['宠物']])
print(pd.DataFrame(ordinal_encoded, columns=['宠物_序号编码']))

# 独热编码示例  
print("\n2. 独热编码结果:")
one_hot_encoded = pd.get_dummies(sample_data['宠物'], prefix='宠物').astype(int)
print(one_hot_encoded)

# 对比说明
print("\n" + "="*50)
print("编码方法对比总结:")
print("-" * 30)
print("序号编码 (Label Encoding):")
print("  • 优点:保持特征维度不变")
print("  • 缺点:引入人为的大小关系")
print("  • 适用:树模型(决策树、随机森林等)")
print()
print("独热编码 (One-Hot Encoding):")
print("  • 优点:类别间完全平等,无大小关系")
print("  • 缺点:可能造成维度爆炸")
print("  • 适用:计算型模型(逻辑回归、神经网络等)")
原始数据:
   宠物  颜色 大小
0   狗  棕色  大
1   猫  白色  中
2   狗  黑色  大
3  仓鼠  白色  小
4   猫  棕色  中
5   狗  黑色  大
6  仓鼠  灰色  小

==================================================

1. 序号编码结果:
   宠物_序号编码
0      1.0
1      2.0
2      1.0
3      0.0
4      2.0
5      1.0
6      0.0

2. 独热编码结果:
   宠物_仓鼠  宠物_狗  宠物_猫
0      0     1     0
1      0     0     1
2      0     1     0
3      1     0     0
4      0     0     1
5      0     1     0
6      1     0     0

==================================================
编码方法对比总结:
------------------------------
序号编码 (Label Encoding):
  • 优点:保持特征维度不变
  • 缺点:引入人为的大小关系
  • 适用:树模型(决策树、随机森林等)

独热编码 (One-Hot Encoding):
  • 优点:类别间完全平等,无大小关系
  • 缺点:可能造成维度爆炸
  • 适用:计算型模型(逻辑回归、神经网络等)

选择策略总结

在实际项目中,选择编码方法时应考虑以下因素:

  1. 模型类型:

    • 树模型 → 优先考虑序号编码
    • 计算型模型 → 优先考虑独热编码
  2. 类别数量:

    • 类别数少(<10)→ 两种方法都可考虑
    • 类别数多 → 谨慎使用独热编码,避免维度爆炸
  3. 类别间关系:

    • 如果类别有自然顺序(如”小”、“中”、“大”)→ 可考虑序号编码并手动指定顺序
    • 如果类别完全无序 → 优先使用独热编码
  4. 计算资源:

    • 资源有限 → 考虑序号编码或降维后的独热编码
    • 资源充足 → 可根据模型需求自由选择

📝 动手练一练

  1. 概念辨析:为什么在逻辑回归模型中不建议使用序号编码(Label Encoding),而在决策树模型中却可以使用?
👉 点击查看参考答案

参考答案:

  • 逻辑回归模型:是计算型模型,会直接使用特征值进行加权求和和sigmoid变换。序号编码将类别转换为有序数字(如狗→1,猫→2,老鼠→3),模型会误认为这些数字代表真实的数值关系(1<2<3),从而错误地认为”老鼠”比”狗”的数值更大、影响更强。实际上这些类别应该是平等的,没有大小关系。
  • 决策树模型:基于特征值的大小进行分裂,但只关心”是否小于某个阈值”,不关心具体的数值大小和线性关系。序号编码虽然引入了数字大小,但树模型只利用这些数字进行二值判断(如”动物编码≤1.5”),不会误用数值的大小关系。因此序号编码对树模型是安全的。
  1. 编码实践:对于以下数据集,请分别使用序号编码和独热编码处理”季节”列,并观察两种编码结果的差异:
    # 原始数据
    季节 = ['春', '夏', '秋', '冬', '春', '夏']
👉 点击查看参考答案

参考答案:

import pandas as pd
from sklearn.preprocessing import OrdinalEncoder

# 创建数据
data = pd.DataFrame({'季节': ['春', '夏', '秋', '冬', '春', '夏']})

# 序号编码
ordinal_enc = OrdinalEncoder()
ordinal_result = ordinal_enc.fit_transform(data[['季节']])
print("序号编码结果(二维数组):")
print(ordinal_result)
print("\n类别映射:", dict(zip(ordinal_enc.categories_[0], range(len(ordinal_enc.categories_[0])))))

# 独热编码
one_hot_result = pd.get_dummies(data['季节'], prefix='季节').astype(int)
print("\n独热编码结果:")
print(one_hot_result)
序号编码结果(二维数组):
[[2.]
 [1.]
 [3.]
 [0.]
 [2.]
 [1.]]

类别映射: {'冬': 0, '夏': 1, '春': 2, '秋': 3}

独热编码结果:
   季节_冬  季节_夏  季节_春  季节_秋
0     0     0     1     0
1     0     1     0     0
2     0     0     0     1
3     1     0     0     0
4     0     0     1     0
5     0     1     0     0

差异分析:

  1. 维度差异:序号编码保持1维,独热编码扩展为4维
  2. 数值含义:序号编码中数字有顺序(0<1<2<3),独热编码中只有0和1,无顺序关系
  3. 信息保留:序号编码可能引入”秋>春>夏>冬”的错误顺序假设(按编码数值 3>2>1>0),独热编码完全保留类别平等性

本章小结

本节深入讲解了类别型数据特征构建的两种核心编码方法。序号编码简单高效但引入大小关系,适合树模型;独热编码保持类别平等性但可能造成维度爆炸,适合计算型模型。在实际项目中,我们需要根据模型类型、类别数量和数据特点灵活选择合适的编码策略。

📋 行动清单

  • 掌握序号编码(Label Encoding)的原理与实现,理解其适用场景
  • 掌握独热编码(One-Hot Encoding)的原理与实现,理解其优缺点
  • 能够根据模型类型(树模型 vs. 计算型模型)选择合适的编码方法
  • 在实际项目中实践两种编码方法,观察它们对模型性能的影响

—— 小象教研组