数据预处理:离散特征编码方法
数据预处理:离散特征编码方法
Python库category_encoders将离散特征的编码方法分为2类:有监督和无监督。
各种编码方法的原理如何呢?
无监督方法:
1.序号编码OrdinalEncoder
序号编码通常用于处理类别间具有大小关系的数据。如产品等级分为高、中、低三档,存在“高>中>低”的排序关系。序号编码则按照大小关系对类别型特征赋值一个数值ID,如高表示为3,中表示为2,低表示为1,转换后依然保持大小关系。
如果实际业务中,明确的是有序的离散特征,尝试 Ordinal(Integer)。
import category_encoders as ce
import pandas as pd
data = pd.DataFrame({'ID':[1,2,3,4,5,6,7,8],'Sex':['F','M','M','F','M',None,'F','M'],
'BloodType':['A','AB','O','B', None,'O','AB','B'],'Grade':['High', None,'Medium','Low', 'Low','Medium','Low','High'],
# 'Height':[156, None, 167, 175, 164, 180], 'Weight':[50, None, 65, 67, 48, 76],
'Education':['PhD','HighSchool','Bachelor','Master','HighSchool','Master','PhD','Bachelor'],
'Income':[28300, 4500, 7500, 12500, 4200, 15000, 25000, 7200]})
print('\nOriginal Dataset:\n', data)
ce_1 = ce.OrdinalEncoder(cols=['Grade', 'Education'],
mapping=[{'col':'Grade','mapping':{None:0, 'Low':1, 'Medium':2, 'High':3}},{
'col':'Education','mapping':{None:0,'HighSchool':1,'Bachelor':2, 'Master':3,'PhD':4}}]).fit_transform(data)
print('\nOrdinalEncoder Return the transformed dataset:\n', ce_1)
2.独热编码OneHotEncoder
独热编码通常用于处理类别间不具有大小关系的特征。例如血型,一共4个值,独热编码将其变成4维的稀疏向量。独热编码的特征向量只有一维取值为1,其余为0。缺点是它处理不好类别取值多的特征,类别数越大会带来过很多列的稀疏特征,消耗内存和训练时间。对于类别取值较多的情况要注意通过特征选择降低维度。
ce_2 = ce.OneHotEncoder(cols=['BloodType'], use_cat_names=True).fit_transform(data)
print('\nOneHotEncoder Return the transformed dataset:\n', ce_2)
3.二进制编码BinaryEncoder
二进制编码分2步,先用序号编码给每个类别赋予一个类别ID,然后将类别ID对应的二进制编码作为结果。本质 上是利用二进制对ID进行哈希映射,最终得到0/1特征向量,且维数少于独热编码,节省存储空间。
优点:
- 容易实现
- 分类很精确
- 可用于在线学习
缺点:
- 计算效率不高
- 不能适应可增长的类别
- 只适用于线性模型
- 对于大数据集,需要大规模的分布式优化
ce_3 = ce.BinaryEncoder(cols=['BloodType']).fit_transform(data)
print('\nBinaryEncoder Return the transformed dataset:\n', ce_3)
4.计数编码CountEncoder
对于给定的分类特征,按照每个类别分组,统计组计数,将每个类别都映射到该类别的样本数。清晰地反映了类别在数据集中的出现次数,缺点是忽略类别的物理意义,比如说两个类别出现频次相当,但是在业务意义上,模型的重要性也许不一样。这个编码可以指示每个类别的“可信度”,例如,机器学习算法可以决定仅考虑其类别计数高于某个阈值的类别所带来的信息。
ce_4 = ce.CountEncoder(cols=['Sex','BloodType','Grade','Education']).fit_transform(data)
print('\nCountEncoder Return the transformed dataset:\n', ce_4)
5.哈希编码HashingEncoder
哈希编码是使用二进制对标签编码做哈希映射。好处在于哈希编码器不需要维护类别字典,且输出长度是固定的。若后续出现训练集未出现的类别,哈希编码还能接受新值。另外,对于类别取值较多的特征,哈希法编码可以将原始的高维特征向量压缩成较低维特征向量,且尽量不损失原始特征的表达能力。但按位分开哈希编码,模型学习相对比较困难。
优点:
- 容易实现
- 模型训练成本更低
- 容易适应新类别
- 容易处理稀有类
- 可用于在线学习
缺点:
- 只适合线性模型或核方法
- 散列后的特征无法解释
- 精确度难以保证
ce_5 = ce.HashingEncoder(cols=['Education']).fit_transform(data)
print('\nReturn the transformed dataset:\n', ce_5)
需要注意的是哈希编码会报错:
Default value of ‘max_process’ is 1 on Windows because multiprocessing might cause issues, see in : https://github.com/scikit-learn-contrib/categorical-encoding/issues/215 https://docs.python.org/2/library/multiprocessing.html?highlight=process#windows
6.BaseNEncoder
Base-N 编码器将类别编码为它们的 base-N 表示的数组。 基数 1 等价于 one-hot 编码(不是真正的 base-1,但很有用),基数 2 等价于二进制编码。 N=实际类别的数量,相当于普通的序数编码。
不太清楚这种编码的实际应用。
这里以Base-3和Base-4为例。
ce_6_1 = ce.BaseNEncoder(cols=['BloodType'], base=3).fit_transform(data)
print('\nBaseNEncoder Return the transformed dataset 1(base=3):\n', ce_6_1)
ce_6_2 = ce.BaseNEncoder(cols=['BloodType'], base=4).fit_transform(data)
print('\nBaseNEncoder Return the transformed dataset 2(base=4):\n', ce_6_2)
7.Sum Contrast
“对比”编码是一种方法,通过与分类特征的其他取值比较,为分类特征中的每个不同的值设置列的值。
UCLA:R LIBRARY CONTRAST CODING SYSTEMS FOR CATEGORICAL VARIABLES
介绍了常见的对比编码方法和公式,并解释了系数与均值的关系,是category_encoders库引用的文章。
在回归分析中,一个有K 个类别的类别变量,通常作为 K-1 个变量的序列输入,例如作为 K-1 个虚拟变量。 随后,这些 K-1 个变量的回归系数对应于一组关于单元均值的线性假设。 在对分类变量进行编码时,我们可以选择多种编码系统来测试不同的线性假设集。 这些编码设计用于回归问题时具有特定的行为。换句话说,如果希望回归系数具有某些特定属性,则可以使用对比编码。
Sum Contrast该编码系统将给定水平(类别)的因变量的平均值与因变量的总体平均值进行比较。通过SumEncoder创建对比度矩阵,在下面的示例中,第一个Education_0将 PhD与所有教育水平进行比较,第二个Education_1将 HighSchool与所有教育水平进行比较,第三个Eductaion_2将 Bachelor与所有教育水平进行比较。编码是通过将 1 分配给类别PhD进行第一次比较(因为类别PhD是要与所有其他类别进行比较的类别),将 1 分配给类别HighSchool进行第二次比较(因为类别 HighSchool 将与所有其他类别进行比较),1 分配给类别Bachelor进行第三次比较(因为 Bachelor将与所有其他类别进行比较)。 请注意,所有三个比较都将 -1 分配给类别Master(因为它是从未与其他类别进行比较的类别),并且所有其他值都分配给 0。
level of Education | Eductaion_0(PhD vs. mean) | Eductaion_1(HighSchool vs. mean) | Eductaion_2(Bachelor vs. mean) |
---|---|---|---|
PhD | 1 | 0 | 0 |
HighSchool | 0 | 1 | 0 |
Bachelor | 0 | 0 | 1 |
Master | -1 | -1 | -1 |
ce_7 = ce.SumEncoder(cols=['Education']).fit_transform(data)
print('\nSumEncoder Return the transformed dataset:\n', ce_7)
lr = ols('Income ~ Education_0 + Education_1 + Education_2',data=ce_7).fit()
print(lr.summary())
通过一个回归问题:考虑教育水平与收入的相关性,来查看这种编码方式的特别效果:回归系数具有零和。构建Education的对比度(SumEncoder编码结果)与Income的线性回归方程。
可以计算Education各类别下因变量Income的均值,即分组求平均值:Bachelor组的Income均值7350,HighSchool组的Income均值4350,Master组Income均值13750,PhD组Income均值26650,4个类别的因变量Income平均值的平均值(后面我们称之为总平均值)即\((7350+4350+13750+26650)/4=13025\)。
查看这个线性回归结果:截距等于总平均值13025,其余系数(对比度估计)是当前组的Income均值减去总平均值,如Education_1(对应HightSchool VS mean)的系数:4350-13025=-8675。这个值与零(对比度系数为零的零假设)之间的差异具有统计显着性 (p = 0.001),并且此检验的 t 值为 -9.628。 接下来两个对比的结果以类似的方式计算。
from statsmodels.formula.api import ols
lr = ols('Income ~ Education_0 + Education_1 + Education_2',data=ce_7).fit()
print(lr.summary())
8.Backward Difference Contrast
在此编码系统中,将离散变量的一个类别的因变量的平均值与先前相邻类别的因变量的平均值进行比较。 在下面的示例中,第一个比较比较了 教育水平为HighSchool的收入Income平均值和 PhD的收入平均值。 第二个比较比较Bachelor与HighSchool的的收入平均值,第三个比较Master与Bachelor的收入平均值。这种类型的编码可能对名义变量(nominal variable,例如性别、民族、专业、国籍等)或有序变量(ordinal variable,例如学生年级、满意程度、学历水平等)有用。Backward Difference Coding方法,k 是类别变量的类别数(在本例中,k = 4):
level of variable | level 2 vs. level 1 | level 3 vs. level 2 | level 4 vs. level 3 |
---|---|---|---|
1 | -(k-1)/k | -(k-2)/k | -(k-3)/k |
2 | 1/k | -(k-2)/k | -(k-3)/k |
3 | 1/k | 2/k | -(k-3)/k |
4 | 1/k | 2/k | 3/k |
对于第一个比较,比较PhD和HighSchool,Eductaion_0 为PhD编码 -3/4,而其他类别编码为 1/4。 对于第二次比较,将HighSchool与Bachelor进行比较,Eductaion_1编码为 -1/2 -1/2 1/2 1/2,对于第三次比较,将Bachelor与Master进行比较,Eductaion_2编码为 -1/4 -1/4 -1/4 3/4。
level of Education | Eductaion_0(HighSchool vs. PhD) | Eductaion_1(Bachelor vs. HighSchool) | Eductaion_2(Master vs. Bachelor) |
---|---|---|---|
PhD | -3/4 | -1/2 | -1/4 |
HighSchool | 1/4 | -1/2 | -1/4 |
Bachelor | 1/4 | 1/2 | -1/4 |
Master | 1/4 | 1/2 | 3/4 |
ce_8 = ce.BackwardDifferenceEncoder(cols=['Education']).fit_transform(data)
print('\nBackwardDifferenceEncoder Return the transformed dataset:\n', ce_8)
lr = ols('Income ~ Education_0 + Education_1 + Education_2',data=ce_8).fit()
print(lr.summary())
使用这种编码系统,将类别变量的相邻类别进行比较,每个类别都与前一个类别进行比较。 因此,将类别HighSchool的因变量的平均值与类别PhD的因变量的平均值进行比较:4350 – 26650 = -22300,具有统计显着性。 对于Bachelor和HighSchool的比较,对比系数的计算为7350 - 4350=3000,不具有统计学意义(P=0.111),这意味着Bachelor的收入平均值与 HighSchool的收入平均值之间没有明显差异。 最后,比较 Master和 Bachelor,13750 –7350 = 6400,具有统计学意义的差异(P=0.012)。 从中可以得出结论,有2个相邻的教育水平在统计上存在显着差异。
9.Helmert Contrast
HelmertEncoder与BackwardDifferenceEncoder非常相似,但是不仅将其与上一个进行比较,还将每个类别与所有先前的类别进行比较。这种类型的编码系统对于诸如种族、性别、国籍之类的名义变量(nominal variable)没有多大意义。
Helmert 编码(也称为差异编码):不仅将类别变量的每个类别与前一个类别的平均值进行比较,还将每个类别与所有先前类别的平均值进行比较。在我们的示例中,第一个对比编码将教育水平HighSchool的因变量Income的平均值与PhD 的因变量Income的平均值进行比较。第二个比较将Bachelor因变量Income的平均值与HighSchool和PhD 进行比较,第三个比较将Master的因变量Income的平均值与类别 PhD、HighSchool 和 Bachelor 进行比较。
Helmert 编码如下所示。对于第一个比较,比较PhD和HighSchool,Education_0 被编码为 -1/2 和 1/2,其他为 0。对于第二次比较,Education_1编码为 -1/3 -1/3 2/3 和 0。最后,对于第三次比较,Education_2编码为 -1/4 -1/4 -/14 和 3/4。 Rcategory_encoders中内置的 HelmertEncoder等效于这种编码方案,每列中都有一个常数。
level of Education | Eductaion_0(HighSchool vs. PhD) | Eductaion_1(Bachelor vs. HighSchool,PhD) | Eductaion_2(Master vs. Bachelor,HighSchool,PhD) |
---|---|---|---|
PhD | -1/2 | -1/3 | -1/4 |
HighSchool | 1/2 | -1/3 | -1/4 |
Bachelor | 0 | 2/3 | -1/4 |
Master | 0 | 0 | 3/4 |
ce_9 = ce.HelmertEncoder(cols=['Education']).fit_transform(data)
print('\nHelmertEncoder Return the transformed dataset:\n', ce_9)
lr = ols('Income ~ Education_0 + Education_1 + Education_2',data=ce_9).fit()
print(lr.summary())
需要注意的是使用HelmertEncoder要求观测样本数不少于8个,否则会出现”ValueWarning: omni_normtest is not valid with less than 8 observations; 6 samples were given.“这样的提示。
查看回归结果:截距等于总平均值13025,此输出中显示的第一个比较的对比估计值是通过从类别变量Education的类别HighSchool 的因变量的平均值中减去类别变量Education的类别 PhD的因变量Income的平均值,再求平均值计算得出的:(4350-26650)/2=-11150,这个结果在统计上是显著的。第二次比较的对比估计值是通过从Bachelor的平均值中减去类别 PhD 和 HighSchool 的因变量的平均值,再求平均值计算得出的:[7350 – [(4350 + 26650) / 2] ]/3= -2716.6667,该结果具有统计学意义。对于类别Master 和之前类别的比较,先取这些类别的因变量的平均值,然后从类别Master的因变量的平均值中减去它,再求平均值:[13750 – [(7350+ 44350 + 26650) / 3] ]/4= 241.6667,这个结果在统计上是不显著的。
10.Polynomial Contrast
多项式编码是趋势分析的一种形式,因为它正在寻找分类变量中的线性、二次和三次趋势。 这种类型的编码系统只能与类别等间距的序数变量一起使用。基于假设: 基础分类变量具有不仅可观的而且均等间隔的级别。如果你考虑连续变量与离散变量是否具有线性(或二次、三次)关系,可以谨慎使用它。
这种变量的例子可能是收入或教育。 因为教育水平是有序的,从HighSchool、Bachelor、Master到PhD,通过序号编码,可以分别设置为1,2,3,4。下面是多项式编码Education的对比度矩阵:Education_0(Linear),Education_1(Quadratic),Education_2(Cubic)。
level of Education | Eductaion_0(Linear) | Eductaion_1(Quadratic) | Eductaion_2(Cubic) |
---|---|---|---|
PhD | -0.670820 | 0.5 | -0.223607 |
HighSchool | -0.223607 | -0.5 | 0.670820 |
Bachelor | 0.223607 | -0.5 | -0.670820 |
Master | 0.670820 | 0.5 | 0.223607 |
ce_10 = ce.PolynomialEncoder(cols=['Education']).fit_transform(ce_1)
print('\nPolynomialEncoder Return the transformed dataset:\n', ce_10)
lr = ols('Income ~ Education_0 + Education_1 + Education_2',data=ce_10).fit()
print(lr.summary())
回归结果表明 Education 对结果变量Income 有很强的线性影响(P=0.002),也有显著的二次效应或三次效应。
有监督方法:
11.TargetEncoder
在贝叶斯架构下,利用要预测的因变量(target variable),有监督地确定最适合这个类别特征的编码方式。有监督的编码方式,适用于分类和回归问题。
假设有2个变量,一个类别变量(x,有k个类别)一个目标变量/因变量(y),将类别变量编码时希望使用y的信息。一个想法是对x的每个类别取y的平均值,即
这个方法的问题是对x分组时,某些组可能太小或太大,不太稳定。所以有监督的编码通过选择y的组均值和全局均值之间的中间方法来解决问题,即:
其中权重\(w_i\)取值在0和1之间。三种有监督的编码算法(TargetEncoder,MEstimateEncoder,JamesSteinEncoder)基于\(w_i\)的定义不同而不同。
TargetEncoder的基本思想:充分利用已知数据,估算先验概率和后验概率,引入权重\(\lambda\)计算编码所用概率\(\hat{P}\).将类别变量x中的每一个类别\(i\)都表示为它所对应某个目标y值target概率,即后验概率:\(\hat{P}(y=\text{target}|x=i)\);基于已有数据可估算数据点为某个目标y值target的概率,即先验概率:\(\hat{P}(y=\text{target})\)。最终编码\(\hat{P}=\lambda * \hat{P}(y=\text{target}|x=i) + (1-\lambda)*\hat{P}(y=\text{target})\)或\(\hat{P}=\lambda * \text{posterior} + (1-\lambda)*\text{prior}\)。
\(\hat{P}(y=\text{target})=N_{y=\text{target}}/N_{\text{total}}\)
\(\hat{P}(y=\text{target}|x=i)=N_{y=\text{target} \;\text{and}\; x=i}/N_{x=i}\)
定义权重函数,输入是特征类别在训练集中出现的次数n,输出是对于这个特征类别的先验概率的权重\(\lambda\)。假设一个特征类别出现次数是n,则:
TargetEncoder计算公式:
如果\(y\)是连续值,则将各个样本量N换成相应的均值即可。
分类特征的目标编码。
分类特征的目标编码。
支持的目标变量类型:二项式和连续式。
对于分类目标的情况:特征被替换为给定特定分类值的目标后验概率和目标在所有训练数据上的先验概率的混合。
对于连续目标的情况:特征被替换为给定特定分类值的目标期望值和目标在所有训练数据上的期望值的混合。
Income_grand_mean = data['Income'].mean()
data['Income_grand_mean'] = [Income_grand_mean]*len(data)
Income_group = data.groupby('Education')['Income'].mean().rename('Income_level_mean').reset_index()
data_new = pd.merge(data, Income_group)
# data_new.sort_values(by=['ID'],inplace=True)
# data_new.reset_index(drop=True, inplace=True)
print('New Dataset:\n', data_new)
features = list(data_new.columns)
features.remove('Income')
ce_11_1 = ce.TargetEncoder(cols=['Education'], smoothing=0).fit_transform(data_new[features], data_new['Income'])
print('\nTargetEncoder Return the transformed dataset(smoothing=0):\n', ce_11_1)
ce_11_2 = ce.TargetEncoder(cols=['Education'], smoothing=1).fit_transform(data_new[features], data_new['Income'])
print('\nTargetEncoder Return the transformed dataset(smoothing=1):\n', ce_11_2)
ce_11_3 = ce.TargetEncoder(cols=['Education'], smoothing=2).fit_transform(data_new[features], data_new['Income'])
print('\nTargetEncoder Return the transformed dataset(smoothing=2):\n', ce_11_3)
计算数据集因变量Income的总平均值,以及分组平均值,如下所示。
查看TargetEncoder设置不同的平滑参数(smothing=0,1,2)下类别变量Education的编码表示。平滑为0(smothing=0)时,仅用组均值。随着平滑的增加,全局平均权重越来越大,从而导致更强的正则化。
12.MEstimateEncoder
MEstimateEncoder支持的目标类型:二项式和连续式。
这是目标编码器的简化版本,其名称为 m 概率估计(m-probability estimate)或已知发生率的加性平滑(additive smoothing with known incidence rates)。 与目标编码器相比,m 概率估计只有一个可调参数(m),而目标编码器有两个可调参数(min_samples_leaf 和smoothing)。该参数设置全局平均值应按绝对值加权的大小。
它是TargetEncoder的一个特例(n是特征类别在训练集中出现的次数,即这个分类特征\(x\)的某一特征取值\(x_i\)下的所有样本),\(p_c\)是指先验概率,\(n_c\)是属于目标y=target且分类特征取值为c的样本量:
MEstimateEncoder计算公式:
如果\(y\)是连续值,则将各个样本量N换成相应的均值即可。\(m\)越大,过拟合程度越小。
ce_12_1 = ce.MEstimateEncoder(cols=['Education'], random_state=10, m=0).fit_transform(data_new[features], data_new['Income'])
print('\nMEstimateEncoder Return the transformed dataset(m=0):\n', ce_12_1)
ce_12_2 = ce.MEstimateEncoder(cols=['Education'], random_state=10, m=1).fit_transform(data_new[features], data_new['Income'])
print('\nMEstimateEncoder Return the transformed dataset(m=1):\n', ce_12_2)
ce_12_3 = ce.MEstimateEncoder(cols=['Education'], random_state=10, m=2).fit_transform(data_new[features], data_new['Income'])
print('\nMEstimateEncoder Return the transformed dataset(m=2):\n', ce_12_3)
13.JamesSteinEncoder
詹姆斯-斯坦估计器。
支持的目标类型:二项式和连续式。
对于特征值 i,James-Stein 估计器返回以下加权平均值:
- 观察到的特征值 i 的平均目标值。
- 平均目标值(与特征值无关)。
可以表示为:\(JS_i=(1-B)*mean(y_i)+B*mean(y)\)
问题是,权重 B 应该是多少? 如果我们过分强调条件均值,我们就会过拟合。 如果我们过分强调全局均值,我们就会欠拟合。 机器学习中的典型解决方案是执行交叉验证。 然而,Charles Stein 为这个问题提供了一个封闭形式的解决方案。 一个直觉是:如果对\(mean(y_i)\)的估计不可靠(\(y_i\) 具有高方差),我们应该对全局均值\(mean(y)\)赋予更多的权重。 Stein 将其代入方程为:\(B = var(y_i) / (var(y_i)+var(y))\).
剩下的唯一问题是我们不知道\(var(y)\),更不用说 \(var(y_i)\)。 因此,我们必须估计方差。 但是,当我们已经在努力估计平均值时,我们如何才能可靠地估计方差呢? 有多种解决方案:
- 如果我们对每个特征值 i 有相同的观察计数并且所有 $y_i $彼此接近,我们可以假设所有 \(var(y_i)\) 都是相同的。 这称为池化模型。
- 如果观察计数不相等,用平方标准误差代替方差是有意义的,这会惩罚小的观察计数:\(SE^2 = var(y)/count(y)\).这称为独立模型。
然而,James-Stein 估计器有一个实际限制 - 它仅针对正态分布定义。 如果要将其应用于仅允许值 {0, 1} 的二分类,最好先将平均目标值从有界区间 <0,1> 转换为无界区间,方法是将 \(mean(y)\) 替换为对数优势比:\(\text{log-odds\;ratio}_i = \log(mean(y_i)/mean(y_{\text{not}_i}))\).这称为二元模型。 然而,这个模型的参数估计很棘手,有时它会导致致命的失败。 在这些情况下,最好使用 beta 模型,它的准确度通常比二元模型稍差,但不会出现致命失败。
TargetEncoder和MEstimateEncoder既取决于组计数,也取决于用户设置的参数值(分别是平滑smoothing和m)。这不方便,因为设置这些权重是一项手动任务。
这就引申出一个自然的问题:是否有一种方法可以在不需要任何人工干预的情况下设置最佳权重?JamesSteinEncoder尝试以统计为基础的方式执行此操作。一个直觉是,具有较高方差的组均值应被较少信任。因此,组方差越高,权重越低。
JamesSteinEncoder具有两个显着优点:与最大似然估计器相比,它提供了更好的估计,并且不需要任何参数设置。
ce_13 = ce.JamesSteinEncoder(cols=['Education'],).fit_transform(data_new[features], data_new['Income'])
print('\nJamesSteinEncoder Return the transformed dataset:\n', ce_13)
14.Generalized Linear Mixed Model Encoder
广义线性混合模型。
支持的目标类型:二项式和连续式。
这是一个类似于 TargetEncoder 或 MEstimateEncoder 的监督编码器,但有一些优点:1)该技术背后有扎实的统计理论。混合效应模型是统计学的一个成熟分支。 2) 没有要调整的超参数。收缩量是通过估计过程自动确定的。简而言之,一个类别的观察数越少,(和/或)一个类别的结果变化越大,那么对“先验”或“总体均值(grand mean)”的正则化就越高。 3) 该技术适用于连续目标和二项式目标。如果目标是连续的,则编码器返回观测类别与全局平均值的正则化差异。如果目标是二项式,则编码器返回每个类别的正则化对数赔率(log odds)。
与 JamesSteinEstimator 相比,此编码器利用 statsmodels 库中的广义线性混合模型。
这种方法利用了以下事实:线性混合效应模型是专为处理同类观察组而设计的。因此,该想法是使模型不具有回归变量(仅包含截距),并将类别用作组。就是截距和组的随机效应之和。
ce_14 = ce.GLMMEncoder(cols=['Education']).fit_transform(data_new[features], data_new['Income'])
print('\nGeneralized Linear Mixed Model Encoder Return the transformed dataset:\n', ce_14)
15.WOEEncoder
WOE(Wieght of Evidence)Encoder只能用于二进制目标变量,即0/1的目标变量。
WOE describes the relationship between a predictive variable and a binary target variable.
WOE(Weight of Evidence)叫做证据权重,那么WOE在业务中常有哪些应用呢?
- 处理缺失值:当数据源没有100%覆盖时,那就会存在缺失值,此时可以把null单独作为一个分箱。这点在分数据源建模时非常有用,可以有效将覆盖率哪怕只有20%的数据源利用起来。
- 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
- 业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在着非线性关系,此时可经过WOE变换。
计算WOE步骤:
- 对于连续型变量,进行分箱(binning),可以选择等频、等距,或者自定义间隔;对于离散型变量,如果分箱太多,则进行分箱合并。
- 统计每个分箱里的好人数(bin_goods)和坏人数(bin_bads)。
- 分别除以总的好人数(total_goods)和坏人数(total_bads),得到每个分箱内的边际好人占比(margin_good_rate)和边际坏人占比(margin_bad_rate)。
- 计算每个分箱里的 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXrIQFuC-1642905395291)(https://www.zhihu.com/equation?tex=WOE+%3D+ln(\frac{margin\_badrate}{margin\_goodrate}))]
- 检查每个分箱(除null分箱外)里woe值是否满足单调性,若不满足,返回step1。注意⚠️:null分箱由于有明确的业务解释,因此不需要考虑满足单调性。
- 计算每个分箱里的IV,最终求和,即得到最终的IV。
在WOEEncoder里:只有两种分布:
- 1的分布(每组y=1的数量/所有y=1的数量)
- 0的分布(每组y=0的数量/所有y=0的数量)
算法核心:对每个分组,将1的分布除以0的分布;这个值越高,越有信心偏向该组的1,反之亦然。
WOEEncoder的计算公式如下,每个特征取值\(x_i\)表示为\(WoE_i\):
from sklearn.datasets import load_boston
bunch = load_boston()
y = bunch.target > 22.5
X = pd.DataFrame(bunch.data, columns=bunch.feature_names)
ce_15 = ce.WOEEncoder(cols=['CHAS', 'RAD']).fit_transform(X, y)
print('\nOriginal Dataset (Boston):\n', X)
print('\nWOEEncoder Return the transformed dataset:\n', ce_15)
16.Leave One Out Encoder
以上15个编码器都具有唯一的映射。
Leave One Out Encoder与Target Encoder非常相似,但在计算一个类别的平均目标时会排除当前行的目标,以减少异常值的影响。
如果您打算将编码用作预测模型的输入(例如,梯度增强),则可能会出现问题。实际上,假设您使用TargetEncoder。这意味着您将在X_train中引入有关y_train的信息,这可能会导致严重的过度拟合风险。
关键是:如何在限制过度拟合风险的同时保持监督编码?LeaveOneOutEncoder提供了一个出色的解决方案。它执行原始目标编码,但是对于每一行,它不考虑对该行观察到的y值。这样,避免了行泄漏。
LeaveOneOutEncoder计算公式:
ce_16 = ce.LeaveOneOutEncoder(cols=['Education']).fit_transform(data_new[features], data_new['Income'])
print('\nLeaveOneOutEncoder Return the transformed dataset:\n', ce_16)
y_level表示的是每个Education类别包含的所有y值。如ID为1的Education类别为PhD被编码为25000,是这样做的:原始对应的目标变量Income的值(y)为28300,该类别下所有的y值有2个(28300, 25000),排除当前行的y值28300,对剩下的y值求平均值。
17.CatBoost Encoder
CatBoostEncoder支持的目标类型:二项式和连续式。
这与留一法编码非常相似,但会“即时”计算值。 因此,这些值在训练阶段自然会发生变化,并且没有必要添加随机噪声。
请注意,训练数据必须随机排列。 例如。:
# 随机排列
perm = np.random.permutation(len(X))
X = X.iloc[perm].reset_index(drop=True)
y = y.iloc[perm].reset_index(drop=True)
这是必要的,因为某些数据集是根据目标值排序的,并且此编码器在单次传递中即时对特征进行编码。
CatBoost是一种梯度增强算法(例如XGBoost或LightGBM),在各种问题上都表现得非常出色。
CatboostEncoder的工作原理基本上与LeaveOneOutEncoder相似,但是遵循一种在线方法。
但是,如何在离线设置中模拟在线行为?假设您有一张桌子。然后,在桌子中间的某处划一排。CatBoost的行为是假装当前行上方的行先前已被及时观察到,而下方行尚未被观察到(即将来会被观察到)。然后,该算法仅根据已经观察到的行进行留一法编码。
ce_17 = ce.CatBoostEncoder(cols=['Education']).fit_transform(data_new, data_new['Income'])
print('\nCatBoostEncoder Return the transformed dataset:\n', ce_17)
y_level_before表示的是每个Education类别的当前行上方的所有y值。如ID为1的Education类别为PhD被编码为13025,是这样做的:原始对应的目标变量Income的值(y)为28300,该行上方的行为空即[],编码为y的全局平均值13025;ID为2的Education类别为PhD被编码为20662.5,是这样计算的:改行上方行包含的y值列表为[28300],编码为(13025+28300)/2=20662.5,即y的全局平均值和当前行的上方行的所有y值的平均值。
实验效果分析
利用Boston的房价预测数据,使用以上的方法来处理2个离散特征(CHAS, RAD),看看GradientBoostingRegressor回归模型的拟合效果如何?
首先是不使用交叉验证,对离散特征编码,再拟合得到的模型\(R^2\):
使用交叉验证后,对离散特征编码,再拟合得到的模型\(R^2\):
可见在这个回归问题上各种编码方法得到的效果差异不大。
总结
关于这17个编码方法的使用建议:
-
OrdinalEncode比较直观,最适用于类别特征的类别之间存在顺序的相关性;如果没有大小关系,如男、女,贸然使用会增加噪声,即随机增加不存在的顺序关系。
-
对于类别特征的类别数过多的情况不宜使用OneHot Encoder,因为会导致生成特征的数量太多且过于稀疏。
-
SumEncoder比较某一类别特征取值\(x_i(i=1,...,k)\)下对应目标\(y\)的均值与目标\(y\)的全局平均值之间的差别来对特征进行编码。因为这个编码导致这个类别特征携带过多的目标变量信息(\(y\)),容易过拟合,需要配合留一法或者交叉验证进行。
-
HelmetEncoder与SumEncoder不同的是,比较某一类别特征取值\(x_i(i=1,...,k)\)下对应目标\(y\)的均值与它之前特征取值\(x_j(j=1,2,..,i-1)\)下的均值之间的差别,不是和所有特征的均值比较。这个方法也容易出现过拟合。
-
TargetEncoder有监督地选择某一类别特征取值\(x_i(i=1,...,k)\)下对应目标\(y\)的均值与目标\(y\)的全局平均值,结合已知数据同时考虑先验概率和后验概率,可以忽略每一类别特征取值下的样本数量大小。这个方法同样会引起过拟合,防止过拟合可以考虑:使用交叉验证、训练集中加入噪声、调整正则项大小等。
-
MEstimateEncoder是TargetEncoder的一个特例,对权重函数做了简化,只需要调整一个参数\(m\)即可。\(m\)越大过拟合程度越小。
-
Jame-Stein Encoder也是基于TargetEncoder的,不过有一个实际限制:目标\(y\)必须符合正态分布。不同的是权重函数考虑了方差估计。
-
Leave-One-Out Encoder考虑消除过拟合,即在计算每个特征取值\(x_i\)的编码时把该样本剔除。
-
对于有序离散特征,可以尝试使用OrdinalEncoder,BinaryEncoder,OneHotEncoder,LeaveOneOutEncoder,TargetEncoder。
-
HelmetEncoder、SumEncoder、BackwarDifferenceEncoder、PloynominalEncoder不轻易使用,一是因为容易过拟合,二是适配的场景要求严苛(如果是很明确的业务理解)。
-
对于回归问题,TargetEncoder和LeaveOneOutEncoder效果可能不太好,可能是因为离散变量编码后过多携带了因变量的信息。
-
对于离散特征包含的类别数很高的情况,LeaveOneOutEncoder、WOEEncoder、James-SteinEncoder、MEstimateEncoder会更适合。
-
WOEEncoder只能用于二分类问题。
Reference
[1] Beyond One-Hot. 17 Ways of Transforming Categorical Features Into Numeric Features, https://towardsdatascience.com/beyond-one-hot-17-ways-of-transforming-categorical-features-into-numeric-features-57f54f199ea4
[2] https://stats.oarc.ucla.edu/r/library/r-library-contrast-coding-systems-for-categorical-variables/
[3] http://www.ultravioletanalytics.com/blog/using-category-encoders-library-in-scikit-learn
[4] https://www.yuque.com/hshtpy/mlblog/kaggle
[5] https://zhuanlan.zhihu.com/p/441258095
[6] https://copyfuture.com/blogs-details/20200815213500577wghtmumgery2gi9
[7] https://bigdata.51cto.com/art/202012/637918.htm
[8] https://zhuanlan.zhihu.com/p/361140784
[9] https://zhuanlan.zhihu.com/p/80134853
[10] https://github.com/aksenov7/categorical-encoding
[11] https://github.com/scikit-learn-contrib/category_encoders