作者:陈颖祥、杨子晗
编译:AI有道
基于 Jupyter 的特征工程手册:数据预处理的上一篇:
专栏 | 基于 Jupyter 的特征工程手册:数据预处理(一)
项目地址:
https://github.com/YC-Coder-Chen/feature-engineering-handbook
本项目将探讨数据预处理部分:介绍了如何利用 scikit-learn 处理静态的连续变量,利用 Category Encoders 处理静态的类别变量以及利用 Featuretools 处理常见的时间序列变量。
目录
特征工程的数据预处理我们将分为三大部分来介绍:
- 静态连续变量
- 静态类别变量
- 时间序列变量
本文将介绍 1.2 静态类别变量的数据预处理(上部分,即1.2.1-1.2.6)。下面将结合 Jupyter,使用 sklearn,进行详解。
1.2 Static Categorical Variables 静态类别变量
真实世界的数据集还往往包含类别特征。但是由于scikit-learn中的模型只能处理数值特征,因此我们需要将类别特征编码为数值特征但是,很多新的模型开始直接提供类别变量支持,例如lightGBM和Catboost。这里我们使用category_encoders包,因为它涵盖了更多的编码方法。
1.2.1 Ordinal Encoding 序数编码
序数编码将类别变量转化为一列序数变量,包含从1到类别数量之间的整数
代码语言:javascript复制import numpy as np
import pandas as pd
from category_encoders import OrdinalEncoder
# category_encoders 直接支持dataframe
# 随机生成一些训练集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10],
['female',20],['female',15]]),
columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])
# 随机生成一些测试集, 并有意让其包含未在训练集出现过的类别与缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15],
['male',20],['female',40], ['male', 25]]),
columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
代码语言:javascript复制train_set # 原始训练集
代码语言:javascript复制test_set # 原始测试集
代码语言:javascript复制encoder = OrdinalEncoder(cols = ['Sex', 'Type'],
handle_unknown = 'value',
handle_missing = 'value').fit(train_set,train_y) # 在训练集上训练
# 将 handle_unknown设为‘value’,即测试集中的未知特征值将被标记为-1
# 将 handle_missing设为‘value’,即测试集中的缺失值将被标记为-2
# 其他的选择为:‘error’:即报错;‘return_nan’:即未知值/缺失之被标记为nan
encoded_train = encoder.transform(train_set) # 转换训练集
encoded_test = encoder.transform(test_set) # 转换测试集
# 以测试集结果为例
encoded_test
# 在序数编码中:
# 变量Sex中: 'male' => 1.0, 'female' => 2.0, 未知 => -1.0, 缺失值 => -2.0
# (事实上,测试集中完全有可能出现未知与缺失情况)
# 在我们的例子中, Sex这一变量中的'other' 类别从未在训练集中出现过
# 变量 Type 中: 10 => 1.0, 20 => 2.0, 15 => 3.0, 未知 => -1.0, 缺失值 => -2.0
代码语言:javascript复制encoded_train.astype(float) # 训练集结果
1.2.2 One-hot Encoding 独热编码
Scikit-learn中也提供来独热编码函数,其可以将具有n_categories个可能值的一个分类特征转换为n_categories个二进制特征,其中一个为1,所有其他为0在category_encoders中,它包含了附加功能,即指示缺失或未知的值。在这里,我们继续使用category_encoders
代码语言:javascript复制import numpy as np
import pandas as pd
from category_encoders import OneHotEncoder
# category_encoders 直接支持dataframe
# 随机生成一些训练集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10],
['female',20],['female',15]]),
columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])
# 随机生成一些测试集, 并有意让其包含未在训练集出现过的类别与缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15],
['male',20],['female',40], ['male', 25]]),
columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
代码语言:javascript复制train_set # 原始训练集
代码语言:javascript复制test_set # 原始测试集
代码语言:javascript复制encoder = OneHotEncoder(cols=['Sex', 'Type'],
handle_unknown='indicator',
handle_missing='indicator',
use_cat_names=True).fit(train_set,train_y) # 在训练集上训练
encoded_train = encoder.transform(train_set) # 转换训练集
encoded_test = encoder.transform(test_set) # 转换测试集
# 将 handle_unknown设为‘indicator’,即会新增一列指示未知特征值
# 将 handle_missing设为‘indicator’,即会新增一列指示缺失值
# 其他的handle_unknown/handle_missing 的选择为:
# ‘error’:即报错; ‘return_nan’:即未知值/缺失之被标记为nan; ‘value’:即未知值/缺失之被标记为0
# 以测试集结果为例
encoded_test
# 在独热编码中:
# 变量 Sex => 变为了4个新变量: 'male' => [1 ,0 ,0, 0];
# 'female' => [0 ,1 ,0, 0];
# 未知 => [0 ,0 ,0, 1];
# 缺失 => [0, 0, 1, 0];
# 变量 Type => 变为了5个新变量: 10 => [1, 0, 0, 0, 0];
# 20 => [0, 1, 0, 0, 0];,
# 15 => [0, 0, 1, 0, 0];
# 未知 => [0, 0, 0, 0, 1];
# 缺失 => [0, 0, 0, 1, 0];
1.2.3 Hashing Encoding 哈希编码
哈希编码基于特征哈希的方法。它将哈希函数应用于变量,将任意数量的变量以一定的规则映射到给定数量的变量。特征哈希可能会导致要素之间发生冲突。但哈希编码的优点是它不需要制定和维护原变量与新变量之间的映射关系。因此,哈希编码器的大小及复杂程度不随数据类别的增多而增多。
代码语言:javascript复制import numpy as np
import pandas as pd
from category_encoders.hashing import HashingEncoder
# category_encoders 直接支持dataframe
# 随机生成一些训练集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10],
['female',20],['female',15]]),
columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])
# 随机生成一些测试集, 并有意让其包含未在训练集出现过的类别与缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15],
['male',20],['female',40], ['male', 25]]),
columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
代码语言:javascript复制train_set # 原始训练集
代码语言:javascript复制test_set # 原始测试集
代码语言:javascript复制encoder = HashingEncoder(cols=['Sex', 'Type'],
n_components = 5).fit(train_set,train_y)
encoded_train = encoder.transform(train_set) # 转换训练集
encoded_test = encoder.transform(test_set) # 转换测试集
# 将两列的数据集哈希编码为5列
# 哈希编码结果与训练集/测试集中的内容无关
# 只要列名匹配,我们就可以在任何新数据集上使用哈希编码方法
# 编码结果仅由哈希函数确定
# 通常哈希编码应用于更高和更稀疏的维空间,这里以两个变量作为哈希编码的例子
# 以测试集结果为例
encoded_test
代码语言:javascript复制encoded_train # 训练集结果
1.2.4 Helmert Encoding Helmert 编码
Helmert编码通常在计量经济学中使用。在Helmert编码(分类特征中的每个值对应于Helmert矩阵中的一行)之后,线性模型中编码后的变量系数可以反映在给定该类别变量某一类别值的情形下因变量的平均值与给定该类别其他类别值的情形下因变量的平均值的差值。在category_encoders包中实现的Helmert编码为反向Helmert编码。更多信息:
https://www.statsmodels.org/devel/contrasts.html
代码语言:javascript复制import numpy as np
import pandas as pd
from category_encoders import HelmertEncoder
# category_encoders 直接支持dataframe
# 随机生成一些训练集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10],
['female',20],['female',15]]),
columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])
# 随机生成一些测试集, 并有意让其包含未在训练集出现过的类别与缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15],
['male',20],['female',40], ['male', 25]]),
columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
代码语言:javascript复制train_set # 原始训练集
代码语言:javascript复制test_set # 原始测试集
代码语言:javascript复制encoder = HelmertEncoder(cols=['Sex', 'Type'],
handle_unknown='indicator',
handle_missing='indicator').fit(train_set,train_y) # 在训练集上训练
encoded_train = encoder.transform(train_set) # 转换训练集
encoded_test = encoder.transform(test_set) # 转换测试集
# 将 handle_unknown设为‘indicator’,即会新增一列指示未知特征值
# 将 handle_missing设为‘indicator’,即会新增一列指示缺失值
# 其他的handle_unknown/handle_missing 的选择为:
# ‘error’:即报错; ‘return_nan’:即未知值/缺失之被标记为nan; ‘value’:即未知值/缺失之被标记为0
# 以测试集结果为例
encoded_test
# 在Helmert编码中:
# 变量 Sex => 变为了4个新变量(包含常数项): 'male' => [ 1. -1. -1. -1.];
# 'female' => [ 1. 1. -1. -1.];
# 未知 => [ 1. 0. 0. 3.];
# 缺失 => [ 1. 0. 2. -1.];
# 变量 Type => 变为了5个新变量(包含常数项): 10 => [ 1. -1. -1. -1. -1.];
# 20 => [ 1. 1. -1. -1. -1.];,
# 15 => [ 1. 0. 2. -1. -1.];
# 未知 => [ 1. 0. 0. 0. 4.];
# 缺失 => [ 1. 0. 0. 3. -1.];
代码语言:javascript复制# 可以通过如下代码计算变量Type的Helmert 矩阵
from patsy.contrasts import Helmert
levels = [1,2,3,4,5] # 3个变量值 1个未知值 1个缺失值
contrast = Helmert().code_with_intercept(levels)
print(contrast.matrix) # 第一列为常数项
代码语言:javascript复制[[ 1. -1. -1. -1. -1.]
[ 1. 1. -1. -1. -1.]
[ 1. 0. 2. -1. -1.]
[ 1. 0. 0. 3. -1.]
[ 1. 0. 0. 0. 4.]]
代码语言:javascript复制encoded_train # 训练集结果
1.2.5 Sum (Deviation) Encoding 偏差编码
偏差编码也通常在计量经济学中被使用。偏差编码后,线性模型的系数可以反映该给定该类别变量值的情况下因变量的平均值与全局因变量的平均值的差异。更多信息:
https://www.statsmodels.org/devel/contrasts.html
代码语言:javascript复制import numpy as np
import pandas as pd
from category_encoders.sum_coding import SumEncoder
# category_encoders 直接支持dataframe
# 随机生成一些训练集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10],
['female',20],['female',15]]),
columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])
# 随机生成一些测试集, 并有意让其包含未在训练集出现过的类别与缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15],
['male',20],['female',40], ['male', 25]]),
columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
代码语言:javascript复制train_set # 原始训练集
代码语言:javascript复制test_set # 原始测试集
代码语言:javascript复制encoder = SumEncoder(cols=['Sex', 'Type'],
handle_unknown='indicator',
handle_missing='indicator').fit(train_set,train_y) # 在训练集上训练
encoded_train = encoder.transform(train_set) # 转换训练集
encoded_test = encoder.transform(test_set) # 转换测试集
# 将 handle_unknown设为‘indicator’,即会新增一列指示未知特征值
# 将 handle_missing设为‘indicator’,即会新增一列指示缺失值
# 其他的handle_unknown/handle_missing 的选择为:
# ‘error’:即报错; ‘return_nan’:即未知值/缺失之被标记为nan; ‘value’:即未知值/缺失之被标记为0
# 以测试集结果为例
encoded_test
# 在Helmert编码中:
# 变量 Sex => 变为了4个新变量(包含常数项): 'male' => [ 1. 1. 0. 0.];
# 'female' => [ 1. 0. 1. 0.];
# 未知 => [ 1. -1. -1. -1.];
# 缺失 => [ 1. 0. 0. 1.];
# 变量 Type => 变为了5个新变量(包含常数项): 10 => [ 1. 1. 0. 0. 0.];
# 20 => [ 1. 0. 1. 0. 0.];,
# 15 => [ 1. 0. 0. 1. 0.];
# 未知 => [ 1. -1. -1. -1. -1.];
# 缺失 => [ 1. 0. 0. 0. 1.];
代码语言:javascript复制# 可以通过如下代码计算变量Type的Deviation 矩阵
from patsy.contrasts import Sum
levels = [1,2,3,4,5] # 3个变量值 1个未知值 1个缺失值
contrast = Sum().code_with_intercept(levels)
print(contrast.matrix) # 第一列为常数项
代码语言:javascript复制[[ 1. 1. 0. 0. 0.]
[ 1. 0. 1. 0. 0.]
[ 1. 0. 0. 1. 0.]
[ 1. 0. 0. 0. 1.]
[ 1. -1. -1. -1. -1.]]
代码语言:javascript复制encoded_train # 训练集结果
1.2.6 Target Encoding 目标编码
目标编码是一种不仅基于特征值本身,还基于相应因变量的类别变量编码方法。对于分类问题:将类别特征替换为给定某一特定类别值的因变量后验概率与所有训练数据上因变量的先验概率的组合。对于连续目标:将类别特征替换为给定某一特定类别值的因变量目标期望值与所有训练数据上因变量的目标期望值的组合。该方法严重依赖于因变量的分布,但这大大减少了生成编码后特征的数量。
公式:
其中min_samples_leaf和smoothing是用户定义的参数; min_samples_leaf:计算类别平均值时的最小样本数(即若该类别出现次数少,则将被忽略),用以控制过拟合; smoothing:平衡分类平均值与先验平均值的平滑系数。其值越高,则正则化越强; ????′????是类别特征X中类别为k的编码值; Prior Prob:目标变量的先验概率/期望; n:类别特征X中,类别为k的样本数; ???? :不仅在类别特征X中具有类别k,而且具有正结果的样本数(分类问题);
参考文献: Micci-Barreca, D. (2001). A preprocessing scheme for high-cardinality categorical attributes in classification and prediction problems. ACM SIGKDD Explorations Newsletter, 3(1), 27-32.
代码语言:javascript复制import numpy as np
import pandas as pd
from category_encoders.target_encoder import TargetEncoder
# category_encoders 直接支持dataframe
# 随机生成一些训练集
train_set = pd.DataFrame(np.array([['male',10],['female', 20], ['male',10],
['female',20],['female',15]]),
columns = ['Sex','Type'])
train_y = np.array([False, True, True, False, False])
# 随机生成一些测试集, 并有意让其包含未在训练集出现过的类别与缺失值
test_set = pd.DataFrame(np.array([['female',20],['male', 20], ['others',15],
['male',20],['female',40], ['male', 25]]),
columns = ['Sex','Type'])
test_set.loc[4,'Type'] = np.nan
代码语言:javascript复制train_set # 原始训练集
代码语言:javascript复制test_set # 原始测试集
代码语言:javascript复制encoder = TargetEncoder(cols=['Sex','Type'],
handle_unknown='value',
handle_missing='value').fit(train_set,train_y) # 在训练集上训练
encoded_train = encoder.transform(train_set) # 转换训练集
encoded_test = encoder.transform(test_set) # 转换测试集
# handle_unknown 和 handle_missing 被设定为 'value'
# 在目标编码中,handle_unknown 和 handle_missing 仅接受 ‘error’, ‘return_nan’ 及 ‘value’ 设定
# 两者的默认值均为 ‘value’, 即对未知类别或缺失值填充训练集的因变量平均值
encoded_test # 编码后的变量数与原类别变量数一致
代码语言:javascript复制# 验证一下计算的结果,在测试集中,‘male’类别的编码值为 0.473106
prior = train_y.mean() # 先验概率
min_samples_leaf = 1.0 # 默认为1.0
smoothing = 1.0 # 默认为1.0
n = 2 # 训练集中,两个样本包含‘male’这个标签
n_positive = 1 # 在训练集中,这两个包含‘male’标签的样本中仅有一个有正的因变量标签
???????????????????????? = 1 / (1 np.exp(-(n - min_samples_leaf) / smoothing))
male_encode = prior * (1-????????????????????????) ???????????????????????? * n_positive/n
male_encode # return 0.4731058578630005,与要验证的值吻合
代码语言:javascript复制0.4731058578630005
代码语言:javascript复制encoded_train # 训练集结果
好了,以上就是关于静态类别变量(上部分)的数据预处理介绍。建议读者结合代码,在 Jupyter 中实操一遍。
目前该项目完整中文版正在制作中,请持续关注哦~
中文版 Jupyter 地址:
http://localhost:8888/notebooks/feature-engineering-handbook-master/中文版/1. 数据预处理.ipynb#Ordinal-Encoding-序数编码