为了方便后续调用此前定义的一系列种类繁多的特征衍生方法,我们考虑将所有的函数代码封装到一个名叫“feature_creation”的模块中,同时后续所有和特征衍生相关的代码也将一并写入该模块内。
从一个更标准模块定义角度考虑,我们首先在模块的开头进行必要的标注,包括模块编译环境、编码格式、模块内容说明、以及作者和版本号等:
然后进入到代码部分。这里需要注意的是,由于我们会将所有的代码写到一个模块内,而不同的函数彼此之间会存在相互调用关系,因此不同层的函数需要进行简单标记从而完成代码阅读层面上的分割。这里我们用#号分割大的代码模块,然后模块的Part 1是自定义特征衍生函数的依赖库,这里记录了所有需要导入的第三方库:
第二部分则是此前我们定义的所有的函数,这些函数我们统一划归为基本方法的实现函数部分,后续我们还将围绕这些“基本”函数进行更高层次的封装,以补充一些实用功能同时简化调用过程:
封装完成后即可简单测试封装结果:
import features_creation as fc
fc?
# Type: module
# String form:
# File: d:\work\jupyter\telco\正式课程\features_creation.py
# Docstring:
# 自动化批量特征衍生模块
# =========================
# 总共分为四个板块:
# Part 1.相关第三方库
# Part 2.基本方法实现函数
# Part 3.高阶函数辅助函数
# Part 4.高阶函数
# =========================
# 使用过程中最常调用高阶函数进行批量自动化特征衍生。
# 高阶函数能够区分执行训练集和测试集的特征衍生过程,
# 并且支持测试集特征自动补全、目标编码等额外功能,
# 具体包括:
# 交叉组合特征衍生函数:
# Cross_Combination
# 多项式特征衍生函数:
# Polynomial_Features
# 分组统计特征衍生函数:
# Group_Statistics
# 目标编码函数:
# Target_Encode
# 时序字段特征衍生函数:
# timeSeries_Creation
# NLP特征衍生函数:
# NLP_Group_Stat
fc.__version__
#'0.1'
fc.Binary_Cross_Combination?
#Signature: fc.Binary_Cross_Combination(colNames, features, OneHot=True)
#Docstring:
#分类变量两两组合交叉衍生函数
#
#:param colNames: 参与交叉衍生的列名称
#:param features: 原始数据集
#:param OneHot: 是否进行独热编码
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
fc.Multi_Cross_Combination?
#Signature: fc.Multi_Cross_Combination(colNames, features, OneHot=True)
#Docstring:
#多变量组合交叉衍生
#
#:param colNames: 参与交叉衍生的列名称
#:param features: 原始数据集
#:param OneHot: 是否进行独热编码
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
当然也可以采用如下方法,一次性导入模块内的所有函数:
from features_creation import *
Multi_Cross_Combination?
#Signature: Multi_Cross_Combination(colNames, features, OneHot=True)
#Docstring:
#多变量组合交叉衍生
#
#:param colNames: 参与交叉衍生的列名称
#:param features: 原始数据集
#:param OneHot: 是否进行独热编码
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
接下来,我们考虑对特征衍生模块进行优化。这里我们重点考虑从三个方面对模块功能进行优化,其一是函数调用的便捷性,此前我们是分方法进行的函数定义,每个函数实现的是一种特征衍生方法,尽管这么划分能够让函数的目标更加清晰明确,但同时由于特征衍生方法众多,从而导致了函数名称众多,在调用的过程中并不方便。因此这里我们可以化繁为简,将双变量和多变量的相同目标的函数合而为一,从而简化调用过程:
其二,在实际建模过程中,是需要划分训练集和测试集的,而根据训练集/测试集的定义,某些特征衍生方法在训练集和测试集的执行过程并无差异,但有些特征衍生方法(主要是基于分组统计特征衍生)则需要严格的执行在训练集上训练、在测试集上测试的过程,也就是说测试集上的分组统计衍生,必须填入训练集上分组汇总的结果(就类似于标准化过程中用训练集上统计的
其三,仍然还是分组统计汇总特征衍生,此前我们只介绍了带入数据集特征进行分组汇总,此外,有的时候我们也能带入标签进行分组统计,即根据keyCol对标签进行分组统计,该过程也被称为目标编码。目标编码强大而危险,在很多时候目标编码能够有效的提升模型效果但同时极容易造成模型过拟合,因此目标编码的过程往往也需要配合交叉验证共同执行。接下来我们也将在分组统计汇总特征衍生中补充这部分内容。
接下来,我们分不同大类的函数进行优化和改写。需要注意的是,接下来定义的函数都是基于此前定义的基础函数创建而来,因此在模块中我们将其称作高阶封装函数。
首先我们将交叉组合衍生的两个函数封装到一个函数Cross_Combination中,该函数能够灵活调用双变量交叉衍生函数Binary_Cross_Combination和多变量交叉衍生函数的功能,并在实际执行过程中根据输入的训练集、测试集的不同进行不同数据的特征衍生,整体过程并不复杂,函数定义如下。
Binary_Cross_Combination?
#Signature: Binary_Cross_Combination(colNames, features, OneHot=True)
#Docstring:
#分类变量两两组合交叉衍生函数
#
#:param colNames: 参与交叉衍生的列名称
#:param features: 原始数据集
#:param OneHot: 是否进行独热编码
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
# 参数定义
colNames = ['SeniorCitizen', 'Partner', 'Dependents']
# 函数执行
features_new, colNames_new = Binary_Cross_Combination(colNames, features)
# 结果查看
features_new.head(5)
Multi_Cross_Combination?
#Signature: Multi_Cross_Combination(colNames, features, OneHot=True)
#Docstring:
#多变量组合交叉衍生
#
#:param colNames: 参与交叉衍生的列名称
#:param features: 原始数据集
#:param OneHot: 是否进行独热编码
#
#:return:交叉衍生后的新特征和新列名称
#File: ~/features_creation.py
#Type: function
# 参数定义
colNames = ['SeniorCitizen', 'Partner', 'Dependents']
# 函数执行
features_new, colNames_new = Multi_Cross_Combination(colNames, features)
# 结果查看
features_new.head(5)
接下来,进一步将上述交叉衍生函数封装为一个更高阶的函数,并实现训练集、测试集分开输入、输出的目的。对于交叉组合特征来说,我们并不需要“在训练集上进行训练、在测试集上进行测试”,因此我们完全可以分别单独在训练集和测试集上分别进行完全相同的交叉组合衍生。但需要注意的是,有的时候由于训练集和测试集的数据本身存在差异,导致组合出来的特征有些差异,例如:
此时,如果从更严格的角度出发,应该现在训练集上进行特征不同取值的组合,然后根据这些组合的结果,查看测试集是否有相应的取值,如果有的话可以直接填入,没有的话则记为缺失值或者0,以此避免有时测试集不存在部分特征取值组合的情况。我们可以通过如下过程完成测试集特征少于训练集时的测试集特征创建:
# 创建数据集
X_train = pd.DataFrame({'Partner': [0, 0, 1, 1], 'SerniorCitizen': [0, 1, 0, 1]})
X_test = pd.DataFrame({'Partner': [1, 1], 'SerniorCitizen': [1, 0]})
X_train
# 尝试进行交叉组合
features_train_new, colNames_train_new = Binary_Cross_Combination(colNames=list(X_train.columns), features=X_train)
features_test_new, colNames_test_new = Binary_Cross_Combination(colNames=list(X_test.columns), features=X_test)
colNames_train_new
#['Partner&SerniorCitizen_0&0',
# 'Partner&SerniorCitizen_0&1',
# 'Partner&SerniorCitizen_1&0',
# 'Partner&SerniorCitizen_1&1']
colNames_test_new
#['Partner&SerniorCitizen_1&0', 'Partner&SerniorCitizen_1&1']
接下来,首先通过集合运算,求得训练集比测试集多的列:
set(colNames_train_new) - set(colNames_test_new)
#{'Partner&SerniorCitizen_0&0', 'Partner&SerniorCitizen_0&1'}
sub_colNames = list(set(colNames_train_new) - set(colNames_test_new))
sub_colNames
#['Partner&SerniorCitizen_0&1', 'Partner&SerniorCitizen_0&0']
然后在test中创建两个全是0值的列。在pandas中我们直接将新的列复制为0即可:
features_test_new
features_test_new['Partner&SerniorCitizen_0&0'] = 0
features_test_new
for col in sub_colNames:
features_test_new[col] = 0
features_test_new
最后,我们需要调整测试集衍生特征的列顺序,保持和训练集一致:
features_test_new[colNames_train_new]
features_test_new = features_test_new[colNames_train_new]
features_test_new
当然,这里需要注意的是,如果情况相反,即测试集特征数量多于训练集特征数,则上述过程相反即可。不过如果测试集中衍生出了训练集中不存在的特征,在交叉组合特征衍生情况下,则说明测试集的变量取值水平多于训练集,此时不仅特征衍生过程会受到影响,模型训练过程也将收到很大影响。接下来,我们将上述过程封装为一个完整的函数:
def Features_Padding(features_train_new,
features_test_new,
colNames_train_new,
colNames_test_new):
"""
特征零值填补函数
:param features_train_new: 训练集衍生特征
:param features_test_new: 测试集衍生特征
:param colNames_train_new: 训练集衍生列名称
:param colNames_test_new: 测试集衍生列名称
:return:0值填补后的新特征和特征名称
"""
if len(colNames_train_new) > len(colNames_test_new):
sub_colNames = list(set(colNames_train_new) - set(colNames_test_new))
for col in sub_colNames:
features_test_new[col] = 0
features_test_new = features_test_new[colNames_train_new]
colNames_test_new = list(features_test_new.columns)
elif len(colNames_train_new) < len(colNames_test_new):
sub_colNames = list(set(colNames_test_new) - set(colNames_train_new))
for col in sub_colNames:
features_train_new[col] = 0
features_train_new = features_train_new[colNames_test_new]
colNames_train_new = list(features_train_new.columns)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
简单测试该函数效果:
# 创建数据集
X_train = pd.DataFrame({'Partner': [0, 0, 1, 1], 'SerniorCitizen': [0, 1, 0, 1]})
X_test = pd.DataFrame({'Partner': [1, 1], 'SerniorCitizen': [1, 0]})
# 尝试进行交叉组合
features_train_new, colNames_train_new = Binary_Cross_Combination(colNames=list(X_train.columns), features=X_train)
features_test_new, colNames_test_new = Binary_Cross_Combination(colNames=list(X_test.columns), features=X_test)
features_train_new
features_test_new
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Features_Padding(features_train_new = features_train_new,
features_test_new = features_test_new,
colNames_train_new = colNames_train_new,
colNames_test_new = colNames_test_new)
features_train_new
features_test_new
这里我们统一将这些辅助函数写入模块的Part 3.高阶函数辅助函数部分。
借助这些辅助函数,我们再将整个交叉组合特征衍生封装为一个完整的函数:
def Cross_Combination(colNames,
X_train,
X_test,
multi=False,
OneHot=True):
"""
交叉组合特征衍生函数
:param colNames: 参与交叉衍生的列名称
:param X_train: 训练集特征
:param X_test: 测试集特征
:param multi: 是否进行多变量交叉组合
:param OneHot: 是否进行独热编码
:return:交叉衍生后的新特征和特征名称
"""
# 首先,训练集和测试集单独进行交叉组合特征衍生
if multi == False:
features_train_new, colNames_train_new = Binary_Cross_Combination(colNames=colNames, features=X_train, OneHot=OneHot)
features_test_new, colNames_test_new = Binary_Cross_Combination(colNames=colNames, features=X_test, OneHot=OneHot)
else:
features_train_new, colNames_train_new = Multi_Cross_Combination(colNames=colNames, features=X_train, OneHot=OneHot)
features_test_new, colNames_test_new = Multi_Cross_Combination(colNames=colNames, features=X_test, OneHot=OneHot)
# 然后判断训练集和测试集的衍生特征是否存在差异
if colNames_train_new != colNames_test_new:
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Features_Padding(features_train_new = features_train_new,
features_test_new = features_test_new,
colNames_train_new = colNames_train_new,
colNames_test_new = colNames_test_new)
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
接下来测试函数效果:
colNames=list(X_train.columns)
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Cross_Combination(colNames, X_train, X_test)
features_train_new
features_test_new
X_train, X_test = train_test_split(features)
# 参数定义
colNames = ['SeniorCitizen', 'Partner', 'Dependents']
# 执行双变量两两交叉组合衍生
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Cross_Combination(colNames, X_train, X_test, multi=False)
# 结果查看
features_train_new.head(5)
features_test_new.head(5)
features_train_new.shape
#(5282, 12)
features_test_new.shape
#(1761, 12)
在后续进行交叉组合分组时,我们将优先考虑使用该函数。
接下来,进一步考虑多项式特征衍生函数。
Binary_PolynomialFeatures?
#Signature: Binary_PolynomialFeatures(colNames, degree, features)
#Docstring:
#连续变量两变量多项式衍生函数
#
#:param colNames: 参与交叉衍生的列名称
#:param degree: 多项式最高阶
#:param features: 原始数据集
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
# 参数定义
colNames = ['tenure', 'MonthlyCharges', 'TotalCharges']
degree = 3
# 函数执行
features_new, colNames_new = Binary_PolynomialFeatures(colNames, degree, features)
# 结果查看
features_new.head(5)
Multi_PolynomialFeatures?
#Signature: Multi_PolynomialFeatures(colNames, degree, features)
#Docstring:
#连续变量多变量多项式衍生函数
#
#:param colNames: 参与交叉衍生的列名称
#:param degree: 多项式最高阶
#:param features: 原始数据集
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
# 参数定义
colNames = ['tenure', 'MonthlyCharges', 'TotalCharges']
degree = 3
# 函数执行
features_new, colNames_new = Multi_PolynomialFeatures(colNames, degree, features)
# 结果查看
features_new.head(5)
很明显,对于多项式特征衍生来说,并不存在“在训练集上进行训练,在测试集上进行测试”的过程,我们只需要针对训练集和测试集分别进行独立的特征衍生即可。接下来将这两个函数封装到一个Polynomial_Features函数中,同样我们通过参数multi来控制是否是执行多特征的多项式计算,并且参照Cross_Combination来完成训练数据和测试数据分开输出的目的。
def Polynomial_Features(colNames,
degree,
X_train,
X_test,
multi=False):
"""
多项式衍生函数
:param colNames: 参与交叉衍生的列名称
:param degree: 多项式最高阶
:param X_train: 训练集特征
:param X_test: 测试集特征
:param multi: 是否进行多变量多项式组衍生
:return:多项式衍生后的新特征和新列名称
"""
if multi == False:
features_train_new, colNames_train_new = Binary_PolynomialFeatures(colNames=colNames, degree=degree, features=X_train)
features_test_new, colNames_test_new = Binary_PolynomialFeatures(colNames=colNames, degree=degree, features=X_test)
else:
features_train_new, colNames_train_new = Multi_PolynomialFeatures(colNames=colNames, degree=degree, features=X_train)
features_test_new, colNames_test_new = Multi_PolynomialFeatures(colNames=colNames, degree=degree, features=X_test)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
# 参数定义
colNames = ['tenure', 'MonthlyCharges', 'TotalCharges']
degree = 3
# 执行多变量多项式特征衍生
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Polynomial_Features(colNames, degree, X_train, X_test, multi=True)
# 结果查看
features_train_new.head(5)
features_test_new.head(5)
features_train_new.shape
#(5282, 16)
features_test_new.shape
#(1761, 16)
接下来我们继续改写分组统计特征衍生函数,我们需要借助基础函数编写两个高阶函数,其一是创建能实现训练集和测试集独立特征衍生的Group_Statistics函数,其二则是实现目标编码的Targer_Encode函数。
首先我们先简单测试基础函数功能:
Binary_Group_Statistics?
#Signature:
#Binary_Group_Statistics(
# keyCol,
# features,
# col_num=None,
# col_cat=None,
# num_stat=['mean', 'var', 'max', 'min', 'skew', 'median'],
# cat_stat=['mean', 'var', 'max', 'min', 'median', 'count', 'nunique'],
# quant=True,
#)
#Docstring:
#双变量分组统计特征衍生函数
#
#:param keyCol: 分组参考的关键变量
#:param features: 原始数据集
#:param col_num: 参与衍生的连续型变量
#:param col_cat: 参与衍生的离散型变量
#:param num_stat: 连续变量分组统计量
#:param cat_num: 离散变量分组统计量
#:param quant: 是否计算分位数
#
#:return:交叉衍生后的新特征和新特征的名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
# 参数定义
col_num = ['MonthlyCharges']
col_cat = ['SeniorCitizen']
keyCol = 'tenure'
# 函数执行
features_new, colNames_new = Binary_Group_Statistics(keyCol, features, col_num, col_cat)
# 结果查看
features_new.head(5)
Multi_Group_Statistics?
#Signature:
#Multi_Group_Statistics(
# keyCol,
# features,
# col_num=None,
# col_cat=None,
# num_stat=['mean', 'var', 'max', 'min', 'skew', 'median'],
# cat_stat=['mean', 'var', 'max', 'min', 'median', 'count', 'nunique'],
# quant=True,
#)
#Docstring:
#多变量分组统计特征衍生函数
#
#:param keyCol: 分组参考的关键变量
#:param features: 原始数据集
#:param col_num: 参与衍生的连续型变量
#:param col_cat: 参与衍生的离散型变量
#:param num_stat: 连续变量分组统计量
#:param cat_num: 离散变量分组统计量
#:param quant: 是否计算分位数
#
#:return:交叉衍生后的新特征和新特征的名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
# 参数定义
col_num = ['MonthlyCharges']
keyCol = ['tenure', 'SeniorCitizen']
# 函数执行
features_new, colNames_new = Multi_Group_Statistics(keyCol, features, col_num)
# 结果查看
features_new.head(5)
Group_Statistics_Extension?
#Signature: Group_Statistics_Extension(colNames, keyCol, features)
#Docstring:
#双变量分组统计二阶特征衍生函数
#
#:param colNames: 参与衍生的特征
#:param keyCol: 分组参考的关键变量
#:param features: 原始数据集
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
# 参数定义
colNames = ['MonthlyCharges', 'SeniorCitizen']
keyCol = 'tenure'
# 函数执行
features_new, colNames_new = Group_Statistics_Extension(colNames, keyCol, features)
# 结果查看
features_new.head(5)
接下来,我们进入到高阶函数的编写环节中。首先是能够实现训练集、测试集独立特征衍生的Group_Statistics函数。这里有两点需要注意,其一是同样可能出现测试集中衍生特征少于训练集的情况(测试集的关键变量分组水平少于训练集),其二则是我们需要“在训练集上进行训练、在测试集上进行测试”,即将训练集上的结果填补至测试集中:
关于测试集和训练集特征数量不一致的情况,我们可以通过调用Features_Padding函数来完成零值填充,而采用训练集上的统计结果填充测试集的过程,该过程我们能够通过如下代码实现:
train = pd.DataFrame({'tenure':[1, 1, 2, 2], 'SeniorCitizen':[1, 0, 1, 1]})
train
test = pd.DataFrame({'tenure':[1, 2, 1], 'SeniorCitizen':[1, 0, 0]})
test
借助Binary_Group_Statistics函数完成分组均值特征衍生:
tf, tc = Binary_Group_Statistics('tenure', train, col_cat=['SeniorCitizen'], cat_stat=['mean'], quant=False)
tf
tc
#['SeniorCitizen_tenure_mean']
接下来,在衍生的特征矩阵中添加tenure列:
tf['tenure'] = train['tenure']
tf
然后对衍生列进行分组求均值,此时因为组内的统计结果一致,因此求均值的结果其实也就是相当于按行去重之后的结果:
tf.groupby('tenure').mean().reset_index()
tf.drop_duplicates()
然后我们将分组求均值之后的结果以tenure为主键拼接到test数据集中:
当然这里也可以采用map方法进行操作,但相比之下map方法在多列合并过程中的效率并不高。
此外,这里也可以不去重直接拼接,merge会取主键的第一个值进行拼接。但为了整体代码逻辑与可读性,建议加上去重的代码。
tf_temp = tf.groupby('tenure').mean().reset_index()
tf_temp
test
pd.merge(test, tf_temp, on='tenure', how='left')
至此,我们就完成了在训练集上分组统计、然后填补到测试集的全过程。同样,我们继续尝试在更复杂的情况下执行该流程,并尝试对这个流程进行优化,以适应后续函数封装过程。此处我们继续尝试多变量交叉组合、同时统计多个统计量的情况:
train = pd.DataFrame({'tenure':[1, 1, 2, 2, 2], 'SeniorCitizen':[1, 0, 1, 0, 1], 'MonthlyCharges':[1, 2, 3, 4, 5]})
train
test = pd.DataFrame({'tenure':[1, 2, 1], 'SeniorCitizen':[1, 0, 0], 'MonthlyCharges':[1, 2, 3]})
test
注意此时test中的tenure与SeniorCitizen的交叉组合情况比train数据集更少
# 定义关键参数
keyCol = ['tenure', 'SeniorCitizen']
col_num = ['MonthlyCharges']
num_stat=['mean','max']
features_train_new, colNames_train_new = Multi_Group_Statistics(keyCol, train, col_num, num_stat=num_stat, quant=False)
features_train_new
train_key, train_col = Multi_Cross_Combination(colNames=keyCol, features=train, OneHot=False)
test_key, test_col = Multi_Cross_Combination(colNames=keyCol, features=test, OneHot=False)
train_key
test_key
train_col, test_col
#('tenure&SeniorCitizen', 'tenure&SeniorCitizen')
train_key[train_col]
#0 1&1
#1 1&0
#2 2&1
#3 2&0
#4 2&1
#Name: tenure&SeniorCitizen, dtype: object
在后续目标编码中,需要用到如下过程,对主键列进行reset_index(),在当前情况下并无影响:
train_key[train_col].reset_index()[train_col]
#0 1&1
#1 1&0
#2 2&1
#3 2&0
#4 2&1
#Name: tenure&SeniorCitizen, dtype: object
features_train_new[train_col] = train_key[train_col]
features_train_new
features_train_new.groupby(train_col).mean().reset_index()
features_test_or = features_train_new.groupby(train_col).mean().reset_index()
然后将features_test_or与test_key进行merge:
pd.merge(test_key, features_test_or, on=train_col, how='left')
features_test_new = pd.merge(test_key, features_test_or, on=train_col, how='left')
features_test_new.drop([train_col], axis=1, inplace=True)
features_train_new.drop([train_col], axis=1, inplace=True)
features_test_new
features_train_new
至此,我们即完成了将训练集上分组统计的结果带入测试集的全过程,整个过程并不复杂,唯一需要注意的是关于添加keyCol和merge的过程。接下来我们将上述过程封装到一个完整的函数中,即利用训练集输出结果对测试集衍生特征进行填充的函数:
def test_features(keyCol,
X_train,
X_test,
features_train_new,
multi=False):
"""
测试集特征填补函数
:param keyCol: 分组参考的关键变量
:param X_train: 训练集特征
:param X_test: 测试集特征
:param features_train_new: 训练集衍生特征
:param multi: 是否多变量参与分组
:return:分组统计衍生后的新特征和新特征的名称
"""
# 创建主键
# 创建带有主键的训练集衍生特征df
# 创建只包含主键的test_key
if multi == False:
keyCol = keyCol
features_train_new[keyCol] = X_train[keyCol].reset_index()[keyCol]
test_key = pd.DataFrame(X_test[keyCol])
else:
train_key, train_col = Multi_Cross_Combination(colNames=keyCol, features=X_train, OneHot=False)
test_key, test_col = Multi_Cross_Combination(colNames=keyCol, features=X_test, OneHot=False)
assert train_col == test_col
keyCol = train_col
features_train_new[keyCol] = train_key[train_col].reset_index()[train_col]
# 利用groupby进行去重
features_test_or = features_train_new.groupby(keyCol).mean().reset_index()
# 和测试集进行拼接
features_test_new = pd.merge(test_key, features_test_or, on=keyCol, how='left')
# 删除keyCol列,只保留新衍生的列
features_test_new.drop([keyCol], axis=1, inplace=True)
features_train_new.drop([keyCol], axis=1, inplace=True)
# 输出列名称
colNames_train_new = list(features_train_new.columns)
colNames_test_new = list(features_test_new.columns)
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
测试函数效果,当multi=False时:
X_train = pd.DataFrame({'tenure':[1, 1, 2, 2], 'SeniorCitizen':[1, 0, 1, 1]})
X_train
X_test = pd.DataFrame({'tenure':[1, 2, 1], 'SeniorCitizen':[1, 0, 0]})
X_test
keyCol = 'tenure'
features_train_new, colNames_train_new = Binary_Group_Statistics('tenure',
X_train,
col_cat=['SeniorCitizen'],
cat_stat=['mean'],
quant=False)
features_train_new
features_train_new, features_test_new, colNames_train_new, colNames_test_new = test_features(keyCol,
X_train,
X_test,
features_train_new,
multi=False)
features_train_new
features_test_new
然后考虑multi=True的情况:
X_train = pd.DataFrame({'tenure':[1, 1, 2, 2, 2], 'SeniorCitizen':[1, 0, 1, 0, 1], 'MonthlyCharges':[1, 2, 3, 4, 5]})
X_train
X_test = pd.DataFrame({'tenure':[1, 2, 1], 'SeniorCitizen':[1, 0, 0], 'MonthlyCharges':[1, 2, 3]})
X_test
# 定义关键参数
keyCol = ['tenure', 'SeniorCitizen']
col_num = ['MonthlyCharges']
num_stat=['mean','max']
features_train_new, colNames_train_new = Multi_Group_Statistics(keyCol, X_train, col_num, num_stat=num_stat, quant=False)
features_train_new
features_train_new, features_test_new, colNames_train_new, colNames_test_new = test_features(keyCol,
X_train,
X_test,
features_train_new,
multi=True)
features_test_new
测试集填补结果和手动实现过程结果一致。接下来,我们将test_features也同样填入Part 2.5中,作为高阶函数重要的辅助函数。
接下来,我们借助test_features辅助函数,来定义能够对测试集特征进行填补的分组统计衍生特征的高阶函数:
Group_Statistics_Extension?
#Signature: Group_Statistics_Extension(colNames, keyCol, features)
#Docstring:
#双变量分组统计二阶特征衍生函数
#
#:param colNames: 参与衍生的特征
#:param keyCol: 分组参考的关键变量
#:param features: 原始数据集
#
#:return:交叉衍生后的新特征和新列名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
def Group_Statistics(keyCol,
X_train,
X_test,
col_num=None,
col_cat=None,
num_stat=['mean', 'var', 'max', 'min', 'skew', 'median'],
cat_stat=['mean', 'var', 'max', 'min', 'median', 'count', 'nunique'],
quant=True,
multi=False,
extension=False):
"""
分组统计特征衍生函数
:param keyCol: 分组参考的关键变量
:param X_train: 训练集特征
:param X_test: 测试集特征
:param col_num: 参与衍生的连续型变量
:param col_cat: 参与衍生的离散型变量
:param num_stat: 连续变量分组统计量
:param cat_num: 离散变量分组统计量
:param quant: 是否计算分位数
:param multi: 是否进行多变量的分组统计特征衍生
:param extension: 是否进行二阶特征衍生
:return:分组统计衍生后的新特征和新特征的名称
"""
# 进行训练集的特征衍生
if multi == False:
# 进行双变量的交叉衍生
features_train_new, colNames_train_new = Binary_Group_Statistics(keyCol = keyCol,
features = X_train,
col_num = col_num,
col_cat = col_cat,
num_stat = num_stat,
cat_stat = cat_stat,
quant = quant)
# 是否进一步进行二阶特征衍生
if extension == True:
if col_num == None:
colNames = col_cat
elif col_cat == None:
colNames = col_num
else:
colNames = col_num + col_cat
features_train_new_ex, colNames_train_new_ex = Group_Statistics_Extension(colNames = colNames,
keyCol = keyCol,
features = X_train)
features_train_new = pd.concat([features_train_new, features_train_new_ex], axis=1)
colNames_train_new.extend(colNames_train_new_ex)
else:
# 进行多变量的交叉衍生
features_train_new, colNames_train_new = Multi_Group_Statistics(keyCol = keyCol,
features = X_train,
col_num = col_num,
col_cat = col_cat,
num_stat = num_stat,
cat_stat = cat_stat,
quant = quant)
# 对测试集结果进行填补
features_train_new, features_test_new, colNames_train_new, colNames_test_new = test_features(keyCol,
X_train,
X_test,
features_train_new,
multi=multi)
# 如果特征不一致,则进行0值填补
# 对于分组统计特征来说一般不会出现该情况
if colNames_train_new != colNames_test_new:
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Features_Padding(features_train_new = features_train_new,
features_test_new = features_test_new,
colNames_train_new = colNames_train_new,
colNames_test_new = colNames_test_new)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
此外,我们也可以借助map过程实现,相关代码如下,函数输出结果完全一致,同学们可以课后自行参阅:
def Group_Statistics(keyCol,
X_train,
X_test,
col_num=None,
col_cat=None,
num_stat=['mean', 'var', 'max', 'min', 'skew', 'median'],
cat_stat=['mean', 'var', 'max', 'min', 'median', 'count', 'nunique'],
quant=True,
multi=False):
"""
分组统计特征衍生函数
:param keyCol: 分组参考的关键变量
:param X_train: 训练集特征
:param X_test: 测试集特征
:param col_num: 参与衍生的连续型变量
:param col_cat: 参与衍生的离散型变量
:param num_stat: 连续变量分组统计量
:param cat_num: 离散变量分组统计量
:param quant: 是否计算分位数
:param multi: 是否进行多变量的分组统计特征衍生
:return:分组统计衍生后的新特征和新特征的名称
"""
features_test_new = pd.DataFrame()
if multi == False:
features_train_new, colNames_train_new = Binary_Group_Statistics(keyCol=keyCol, features=X_train, col_num=col_num, col_cat=col_cat, num_stat=num_stat, cat_stat=cat_stat, quant=quant)
features_train_new[keyCol] = X_train[keyCol].reset_index()[keyCol] # 注:这里的reset_index()是为了后面交叉验证调用做准备
for col in colNames_train_new:
order_label = features_train_new.groupby([keyCol]).mean()[col]
features_test_new[col] = X_test[keyCol].map(order_label)
features_train_new.drop([keyCol], axis=1, inplace=True)
colNames_test_new = list(features_test_new.columns)
else:
features_train_new, colNames_train_new = Multi_Group_Statistics(keyCol=keyCol, features=X_train, col_num=col_num, col_cat=col_cat, num_stat=num_stat, cat_stat=cat_stat, quant=quant)
train_key, train_col = Multi_Cross_Combination(colNames=keyCol, features=X_train, OneHot=False)
test_key, test_col = Multi_Cross_Combination(colNames=keyCol, features=X_test, OneHot=False)
features_train_new[train_col] = train_key[train_col].reset_index()[train_col]
for col in colNames_train_new:
order_label = features_train_new.groupby([train_col]).mean()[col]
features_test_new[col] = test_key[test_col].map(order_label)
features_train_new.drop([train_col], axis=1, inplace=True)
colNames_test_new = list(features_test_new.columns)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
接下来测试函数性能,首先带入前述简单示例中的数据进行测试:
X_train = pd.DataFrame({'tenure':[1, 1, 2, 2], 'SeniorCitizen':[1, 0, 1, 1]})
X_train
X_test = pd.DataFrame({'tenure':[1, 2, 1], 'SeniorCitizen':[1, 0, 0]})
X_test
keyCol = 'tenure'
col_cat = ['SeniorCitizen']
cat_stat = ['mean']
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Group_Statistics(keyCol,
X_train=X_train,
X_test=X_test,
col_cat=col_cat,
cat_stat=cat_stat,
quant=False,
extension=True)
features_train_new
features_test_new
X_train = pd.DataFrame({'tenure':[1, 1, 2, 2], 'SeniorCitizen':[1, 0, 0, 0], 'MonthlyCharges':[1, 2, 3, 4]})
X_train
X_test = pd.DataFrame({'tenure':[1, 2, 1], 'SeniorCitizen':[1, 0, 0], 'MonthlyCharges':[1, 2, 3]})
X_test
keyCol = ['tenure', 'SeniorCitizen']
col_num = ['MonthlyCharges']
num_stat = ['mean', 'max']
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Group_Statistics(keyCol,
X_train=X_train,
X_test=X_test,
col_num=col_num,
num_stat=num_stat,
quant=False,
multi=True)
features_train_new
features_test_new
X_train, X_test = train_test_split(features, random_state=42)
# 参数定义
col_num = ['MonthlyCharges']
col_cat = ['SeniorCitizen']
keyCol = 'tenure'
# 函数执行
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Group_Statistics(keyCol,
X_train=X_train,
X_test=X_test,
col_num=col_num,
col_cat=col_cat,
quant=False,
extension=True)
# 结果查看
features_train_new.head(5)
features_test_new.head(5)
# 参数定义
keyCol = ['tenure', 'SeniorCitizen']
col_num = ['MonthlyCharges']
# 函数执行
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Group_Statistics(keyCol,
X_train,
X_test,
col_num=col_num,
multi=True)
# 结果查看
features_train_new.head()
features_test_new.head()
至此,划分训练集和测试集的分组统计函数就定义好了。接下来我们进一步讨论目标编码的相关实现方法。
这里的目标编码,就是指将标签在某特征上的分组统计结果作为特征。尽管这个过程看起来并不复杂,无非就是将标签带入进行分组即可,但实际上有两点需要注意,其一是由于目标编码带入了标签数据,而测试集标签未知,因此目标编码是一定需要划分训练集和测试集的,并且将训练集上的编码结果带入测试集。其二需要注意的是,带入标签信息进行特征衍生是极容易造成模型过拟合的。例如极端情况下一条数据一个编码,则目标编码结果如下:
此时训练集准确率为100%,但测试集的标签分布未必和训练集中标签在tenure字段上的分布保持一致,因此模型会有极大的过拟合风险。因此,为了避免目标编码可能导致的过拟合问题,一方面我们要尽可能控制分组时分组的数量(即计量避免过多特征的交叉组合),其二则是可以借助K折交叉统计方法来执行目标编码,以期尽量避免标签的直接泄露。交叉验证统计过程如下。首先假设原始数据如下:
然后对训练集进行5折等分,分为fold1-5,每一折数据包含两条原始数据,此处我们假设ID=1、2的数据划分在fold1内,ID=3、4的数据划分在fold2内,以此类推。然后我们进行五轮运算:第一轮(1st iter)计算时将fold1数据视作验证集(深绿色),仅对fold2-4中的数据(浅绿色)进行Churn在tenure上的分组均值计算,此时计算结果为tenure取值为1时churn均值为0.75,取值为0时churn均值为0.25,并将0.75、0.25作为测试集的预估结果填入fold1的churn列中;然后进行第二轮(2nd iter)运算,仍然重复第一轮的计算过程,只不过此时验证集变为了fold2中数据,我们利用fold1、fold3-5中的数据进行churn在tenure不同取值下的均值计算,并将计算结果填入fold2中;后面三轮计算也以此类推,最终计算结果如下:
最后,我们提取上图中深蓝色框体内的数据,也就是每一轮验证集上的结果组合出新的一列拼在原始数据集中,也就是下图的Churn_tenure_mean_kfold列,当然上图五轮的测试集最终拼接出来的只有训练集上的结果:
接下来,围绕训练集上的Churn_tenure_mean_kfold对tenure进行分组求均值,然后将算得按照测试集的tenure取值结果填入测试集的Churn_tenure_mean_kfold列中:
至此我们就完成了借助K折交叉统计进行目标编码的基本过程。不难发现,其实相比原始的分组汇总过程,借助K折交叉统计相当于是先在验证集上进行了一轮间接的统计,然后用这些交叉统计的统计结果作为训练集的分组汇总结果,然后再在训练集的分组汇总结果上进行分组统计,并最终将统计结果填入测试集中。相比不带交叉统计过程的分组统计汇总,上述过程相当于添加了一个间接统计的过程,以此来消除目标编码可能带来的过拟合问题。
上述过程和K折交叉验证非常类似,只不过上述过程衍生的是K折统计结果,而非建模结果。
此外,对于测试集的填充运算,可以利用训练集衍生特征求均值,也可以直接对标签分组求均值,二者并无差异。
接下来我们通过代码来实现上述过程,首先创建训练集和测试集:
a = np.array([[1, 2]*5, [0, 1, 1, 1, 1, 0, 0, 0, 1, 0]]).T
train = pd.DataFrame(a, columns=['tenure', 'Churn'])
train
test = pd.DataFrame([[1],[2]], columns=['tenure'])
test
然后我们可以借助sklearn中的KFold来执行K折交叉统计过程:
from sklearn.model_selection import KFold
folds = KFold(n_splits=5, shuffle=True, random_state=42)
KFold进行数据集划分实际上是进行的训练集和验证集的index的划分,五折划分结果如下:
for trn_idx, val_idx in folds.split(train):
print("train_index:", trn_idx)
print("validation_index:", val_idx)
#train_index: [0 2 3 4 5 6 7 9]
#validation_index: [1 8]
#train_index: [1 2 3 4 6 7 8 9]
#validation_index: [0 5]
#train_index: [0 1 3 4 5 6 8 9]
#validation_index: [2 7]
#train_index: [0 1 2 3 5 6 7 8]
#validation_index: [4 9]
#train_index: [0 1 2 4 5 7 8 9]
#validation_index: [3 6]
然后我们尝试进行一轮计算,即利用上述划分结果,通过训练集的交叉统计结果给验证集赋值,这里我们尝试使用map方法进行操作:
trn_idx, val_idx
#(array([0, 1, 2, 4, 5, 7, 8, 9]), array([3, 6]))
trn_temp = train.iloc[trn_idx]
trn_temp
val_temp = train.iloc[val_idx]
val_temp
# 在训练集集上进行分组求均值
order_label = trn_temp.groupby('tenure')['Churn'].mean()
order_label
#tenure
#1 0.75
#2 0.25
#Name: Churn, dtype: float64
# 然后利用训练集上计算结果给验证集赋值
df1 = val_temp['tenure'].map(order_label)
df1
#3 0.25
#6 0.75
#Name: tenure, dtype: float64
据此,我们就算出了3号、6号样本的交叉验证的统计结果,按照该过程循环5次,即可计算训练集上全部样本的交叉验证统计结果:
df_l = []
for trn_idx, val_idx in folds.split(train):
trn_temp = train.iloc[trn_idx]
val_temp = train.iloc[val_idx]
order_label = trn_temp.groupby('tenure')['Churn'].mean()
df_temp = val_temp['tenure'].map(order_label)
df_l.append(df_temp)
df_l[0]
#1 0.25
#8 0.50
#Name: tenure, dtype: float64
df_l[2]
#2 0.5
#7 0.5
#Name: tenure, dtype: float64
pd.concat(df_l).sort_index(ascending=True)
#0 0.75
#1 0.25
#2 0.50
#3 0.25
#4 0.50
#5 0.50
#6 0.75
#7 0.50
#8 0.50
#9 0.50
#Name: tenure, dtype: float64
将训练集上交叉验证的统计结果单独创建一列,取名为Churn_tenure_mean_kfold
colname = 'Churn_tenure_mean' + '_kfold'
train[colname] = pd.concat(df_l).sort_index(ascending=True)
train
然后,我们再围绕Churn_tenure_mean_kfold这一列进行分组均值计算,并借助该结果对测试集进行填充:
order_label = train.groupby(['tenure'])['Churn'].mean()
test[colname] = test['tenure'].map(order_label)
test
当然,一种更为简便的方法,则是借助刚刚定义的Group_Statistics函数,实现借助一部分数据的统计结果给另一部分数据的特征赋值的功能:
# 重新定义数据
a = np.array([[1, 2]*5, [0, 1, 1, 1, 1, 0, 0, 0, 1, 0]]).T
train = pd.DataFrame(a, columns=['tenure', 'Churn'])
test = pd.DataFrame([[1],[2],[1]], columns=['tenure'])
# 定义关键参数
keyCol = 'tenure'
col_cat = ['Churn']
cat_stat = ['mean', 'max', 'min']
df_l = []
for trn_idx, val_idx in folds.split(train):
trn_temp = train.iloc[trn_idx]
val_temp = train.iloc[val_idx]
trn_temp_new, val_temp_new, colNames_trn_temp_new, colNames_val_temp_new = Group_Statistics(keyCol,
X_train=trn_temp,
X_test=val_temp,
col_cat=col_cat,
cat_stat=cat_stat,
quant=False)
val_temp_new.index = val_temp.index
df_l.append(val_temp_new)
df_l[0]
df_l[1]
pd.concat(df_l).sort_index(ascending=True)
features_train_new = pd.concat(df_l).sort_index(ascending=True)
接下来修改列名称:
[col + '_kfold' for col in features_train_new.columns]
#['Churn_tenure_mean_kfold', 'Churn_tenure_max_kfold', 'Churn_tenure_min_kfold']
colNames_train_new = [col + '_kfold' for col in features_train_new.columns]
features_train_new.columns = colNames_train_new
features_train_new
然后计算测试集上的结果。这里我们直接调用此前定义的test_features函数进行填补即可:
features_train_new, features_test_new, colNames_train_new, colNames_test_new = test_features(keyCol = keyCol,
X_train = train,
X_test = test,
features_train_new = features_train_new,
multi = False)
features_test_new
接下来,我们将上述过程封装为一个完整的函数:
def Target_Encode(keyCol,
X_train,
y_train,
X_test,
col_num=None,
col_cat=None,
num_stat=['mean', 'var', 'max', 'min', 'skew', 'median'],
cat_stat=['mean', 'var', 'max', 'min', 'median', 'count', 'nunique'],
quant=True,
multi=False,
extension=False,
n_splits=5,
random_state=42):
"""
目标编码
:param keyCol: 分组参考的关键变量
:param X_train: 训练集特征
:param y_train: 训练集标签
:param X_test: 测试集特征
:param col_num: 参与衍生的连续型变量
:param col_cat: 参与衍生的离散型变量
:param num_stat: 连续变量分组统计量
:param cat_num: 离散变量分组统计量
:param quant: 是否计算分位数
:param multi: 是否进行多变量的分组统计特征衍生
:param extension: 是否进行二阶特征衍生
:param n_splits: 进行几折交叉统计
:param random_state: 随机数种子
:return:目标编码后的新特征和新特征的名称
"""
# 获取标签名称
target = y_train.name
# 合并同时带有特征和标签的完整训练集
train = pd.concat([X_train, y_train], axis=1)
folds = KFold(n_splits=n_splits, shuffle=True, random_state=random_state)
# 每一折验证集的结果存储容器
df_l = []
# 进行交叉统计
for trn_idx, val_idx in folds.split(train):
trn_temp = train.iloc[trn_idx]
val_temp = train.iloc[val_idx]
trn_temp_new, val_temp_new, colNames_trn_temp_new, colNames_val_temp_new = Group_Statistics(keyCol,
X_train = trn_temp,
X_test = val_temp,
col_num = col_num,
col_cat = col_cat,
num_stat = num_stat,
cat_stat = cat_stat,
quant = quant,
multi = multi,
extension = extension)
val_temp_new.index = val_temp.index
df_l.append(val_temp_new)
# 创建训练集的衍生特征
features_train_new = pd.concat(df_l).sort_index(ascending=True)
colNames_train_new = [col + '_kfold' for col in features_train_new.columns]
features_train_new.columns = colNames_train_new
# 对测试集结果进行填补
features_train_new, features_test_new, colNames_train_new, colNames_test_new = test_features(keyCol = keyCol,
X_train = X_train,
X_test = X_test,
features_train_new = features_train_new,
multi = multi)
# 如果特征不一致,则进行0值填补
if colNames_train_new != colNames_test_new:
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Features_Padding(features_train_new = features_train_new,
features_test_new = features_test_new,
colNames_train_new = colNames_train_new,
colNames_test_new = colNames_test_new)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
上述函数尽管看起来复杂,但实际上主体结构和Group_Statistics函数基本一致,可以相互对照进行理解。
接下来检测该函数效果:
# 重新定义数据
a = np.array([[1, 2]*5, [0, 1, 1, 1, 1, 0, 0, 0, 1, 0]]).T
train = pd.DataFrame(a, columns=['tenure', 'Churn'])
X_test = pd.DataFrame([[1],[2],[1]], columns=['tenure'])
train
X_test
X_train = pd.DataFrame(train['tenure'])
X_train
y_train = train['Churn']
# 定义关键参数
keyCol = 'tenure'
col_cat = ['Churn']
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Target_Encode(keyCol,
X_train,
y_train,
X_test,
col_cat=col_cat,
extension=True)
features_train_new
features_test_new
labels
#0 0
#1 0
#2 1
#3 0
#4 1
# ..
#7038 0
#7039 0
#7040 0
#7041 1
#7042 0
#Name: Churn, Length: 7043, dtype: int64
X_train, X_test, y_train, y_test = train_test_split(features, labels, random_state=42)
X_train.head(5)
y_train
#6607 1
#2598 0
#2345 0
#4093 0
#693 1
# ..
#3772 1
#5191 0
#5226 0
#5390 1
#860 0
#Name: Churn, Length: 5282, dtype: int64
然后带入进行计算:
# 参数定义
col_cat = ['Churn']
keyCol = 'tenure'
# 函数执行
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Target_Encode(keyCol,
X_train,
y_train,
X_test,
col_cat=col_cat,
extension=True)
# 结果查看
features_train_new.head(5)
features_test_new.head(5)
# 参数定义
keyCol = ['tenure', 'SeniorCitizen']
col_cat = ['Churn']
# 函数执行
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Target_Encode(keyCol,
X_train,
y_train,
X_test,
col_cat=col_cat,
multi=True)
# 结果查看
features_train_new.head(5)
features_test_new.head()
注1:课上定义的函数代码还是会比较注重可读性,部分代码在执行效率上还有可以提升的空间,并且为了尽可能减少不同函数彼此嵌套带来的逻辑混乱,部分代码执行功能上会有重复;
注2:对于目标编码来说,在分组数量过多时,可能会有部分组内数据较少而导致统计结果失效,此时可以通过增加平滑因子来一定程度解决该问题,这部分内容将在案例部分再进行讨论。
接下来进一步改写时序特征衍生函数,时序特征衍生函数的高阶封装过程和多项式特征衍生函数的封装过程非常类似,对于时序特征来说,同样也不需要在训练集上训练、在测试集上测试的过程,我们只需要单独对训练集和测试集进行特征衍生即可。
首先,简单回顾此前定义的时序特征衍生的基础函数:
timeSeriesCreation?
#Signature: timeSeriesCreation(timeSeries, timeStamp=None, precision_high=False)
#Docstring:
#时序字段的特征衍生
#
#:param timeSeries:时序特征,需要是一个Series
#:param timeStamp:手动输入的关键时间节点的时间戳,需要组成字典形式,字典的key、value分别是时间戳的名字与字符串
#:param precision_high:是否精确到时、分、秒
#
#:return features_new, colNames_new:返回创建的新特征矩阵和特征名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
t = pd.DataFrame()
t['time'] = ['2022-01-03;02:31:52',
'2022-07-01;14:22:01',
'2022-08-22;08:02:31',
'2022-04-30;11:41:31',
'2022-05-02;22:01:27']
t
timeStamp = {'p1':'2022-03-25 23:21:52', 'p2':'2022-02-15 08:51:02'}
features_new, colNames_new = timeSeriesCreation(timeSeries=t['time'], timeStamp=timeStamp, precision_high=True)
features_new
接下来,将上述时序特征衍生函数改写为分训练集和测试集进行输入和输出的高阶函数:
def timeSeries_Creation(timeSeries_train,
timeSeries_test,
timeStamp=None,
precision_high=False):
"""
时序字段的特征衍生
:param timeSeries_train:训练集的时序特征,需要是一个Series
:param timeSeries_test:测试集的时序特征,需要是一个Series
:param timeStamp:手动输入的关键时间节点的时间戳,需要组成字典形式,字典的key、value分别是时间戳的名字与字符串
:param precision_high:是否精确到时、分、秒
:return features_new, colNames_new:返回创建的新特征矩阵和特征名称
"""
features_train_new, colNames_train_new = features_new, colNames_new = timeSeriesCreation(timeSeries = timeSeries_train,
timeStamp = timeStamp,
precision_high = precision_high)
features_test_new, colNames_test_new = features_new, colNames_new = timeSeriesCreation(timeSeries = timeSeries_test,
timeStamp = timeStamp,
precision_high = precision_high)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
接下来,测试函数性能:
t_train, t_test = train_test_split(t,random_state=42)
t_train
features_train_new, features_test_new, colNames_train_new, colNames_test_new = timeSeries_Creation(t_train['time'],
t_test['time'],
timeStamp=timeStamp,
precision_high=True)
features_train_new
features_test_new
最后,我们来对NLP特征衍生函数来进行高阶函数改写和封装,首先进行简单的基础函数功能测试。
NLP_Group_Statistics?
#Signature:
#NLP_Group_Statistics(
# features,
# col_cat,
# keyCol=None,
# tfidf=True,
# countVec=True,
#)
#Docstring:
#NLP特征衍生函数
#
#:param features: 原始数据集
#:param col_cat: 参与衍生的离散型变量,只能带入多个列
#:param keyCol: 分组参考的关键变量,输入字符串时代表按照单独列分组,输入list代表按照多个列进行分组
#:param tfidf: 是否进行tfidf计算
#:param countVec: 是否进行CountVectorizer计算
#
#:return:NLP特征衍生后的新特征和新特征的名称
#File: d:\work\jupyter\telco\正式课程\features_creation.py
#Type: function
tar_col = ['OnlineSecurity', 'OnlineBackup', 'DeviceProtection']
keycol = 'tenure'
features_OE = pd.DataFrame()
features_OE[keycol] = features[keycol]
for col in tar_col:
features_OE[col] = (features[col] == 'Yes') * 1
features_OE.head(5)
col_cat = ['OnlineBackup', 'DeviceProtection']
keycol = ['tenure', 'OnlineSecurity']
features_new, colNames_new = NLP_Group_Statistics(features_OE, col_cat, keycol)
features_new.head()
然后进行高阶函数封装。根据此前的改写经验我们不难判断,该函数的改写分为三个方面,其一是需要训练集和测试集分开输入,并且如果是先分组后进行NLP特征衍生,则需要支持“在训练集上进行训练、在测试集上进行测试”,同时如果出现了训练集和测试集的特征不一致的情况时,需要用零值进行填补。不难发现,该函数的改写和分组统计特征衍生高阶函数Group_Statistics改写过程类似。
def NLP_Group_Stat(X_train,
X_test,
col_cat,
keyCol=None,
tfidf=True,
countVec=True):
"""
NLP特征衍生函数
:param X_train: 训练集特征
:param X_test: 测试集特征
:param col_cat: 参与衍生的离散型变量,只能带入多个列
:param keyCol: 分组参考的关键变量,输入字符串时代表按照单独列分组,输入list代表按照多个列进行分组
:param tfidf: 是否进行tfidf计算
:param countVec: 是否进行CountVectorizer计算
:return:NLP特征衍生后的新特征和新特征的名称
"""
# 在训练集上进行NLP特征衍生
features_train_new, colNames_train_new = NLP_Group_Statistics(features = X_train,
col_cat = col_cat,
keyCol = keyCol,
tfidf = tfidf,
countVec = countVec)
# 如果不分组,则测试集单独计算NLP特征
if keyCol == None:
features_test_new, colNames_test_new = NLP_Group_Statistics(features = X_test,
col_cat = col_cat,
keyCol = keyCol,
tfidf = tfidf,
countVec = countVec)
# 否则需要用训练集上统计结果应用于测试集
else:
if type(keyCol) == str:
multi = False
else:
multi = True
features_train_new, features_test_new, colNames_train_new, colNames_test_new = test_features(keyCol = keyCol,
X_train = X_train,
X_test = X_test,
features_train_new = features_train_new,
multi = multi)
# 如果训练集和测试集衍生特征不一致时
if colNames_train_new != colNames_test_new:
features_train_new, features_test_new, colNames_train_new, colNames_test_new = Features_Padding(features_train_new = features_train_new,
features_test_new = features_test_new,
colNames_train_new = colNames_train_new,
colNames_test_new = colNames_test_new)
assert colNames_train_new == colNames_test_new
return features_train_new, features_test_new, colNames_train_new, colNames_test_new
接下来测试函数性能,首先是不包含分组变量时:
X_train, X_test = train_test_split(features_OE, random_state=42)
col_cat = ['OnlineBackup', 'DeviceProtection', 'OnlineSecurity']
features_train_new, features_test_new, colNames_train_new, colNames_test_new = NLP_Group_Stat(X_train = X_train,
X_test = X_test,
col_cat = col_cat)
features_train_new.head(5)
features_test_new.head(5)
col_cat = ['OnlineBackup', 'DeviceProtection', 'OnlineSecurity']
keycol = 'tenure'
features_train_new, features_test_new, colNames_train_new, colNames_test_new = NLP_Group_Stat(X_train,
X_test,
col_cat,
keycol)
X_train
features_train_new.head(5)
features_test_new.head(5)
col_cat = ['OnlineBackup', 'DeviceProtection']
keycol = ['tenure', 'OnlineSecurity']
features_train_new, features_test_new, colNames_train_new, colNames_test_new = NLP_Group_Stat(X_train = X_train,
X_test = X_test,
col_cat = col_cat,
keyCol = keycol)
features_train_new.head(5)
features_test_new.head(5)
函数功能执行无误。
至此,我们就完成了全部批量特征衍生的函数创建。接下来我们快速回顾下features_creation模块的基本结构与使用方法。并最终将这些方法应用于本案例的数据集中。