kaggle竞赛——桑坦德银行客户满意度预测(三)

2023-12-18 13:20

本文主要是介绍kaggle竞赛——桑坦德银行客户满意度预测(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

桑坦德银行客户满意度预测(三)

  • 特征工程
    • 1、新特征提取
    • 2、过滤冗余特征
      • 删除与目标变量TARGET低相关的特征和彼此之间高度相关的特征
    • 3、数据预处理
      • 对数变换
      • 独热编码
      • 响应编码
      • 特征标准化
    • 总结

特征工程

1、新特征提取

我们读取一下之前保存的pickle文件并打印出此时的train和test,前面的探索性分析过程我们保留了143个特征,接下来我们还希望在这些特征中提取出一些隐藏的信息。

train = pd.read_pickle('./data/santander-customer-satisfaction/output/train.pkl')
test = pd.read_pickle('./data/santander-customer-satisfaction/output/test.pkl')
X_train = train.copy()
X_test = test.copy()
X_train.shape,X_test.shape
((76020, 144), (75818, 143))

在EDA过程中我们知道了训练集中包含了非常多的0值,那么我们是否可以构造一个特征,他表示每行样本中,所有特征值零或非零出现的次数呢?我们将其命名为no_zeros和no_nonzeros

def add_feature_no_zeros(train=X_train,test = X_test):#构造新特征,表示每行样本中143个特征取值为0和非零的出现次数col = [k for k in train.columns if k != 'TARGET']for df in [train,test]:df['no_zeros'] = (df.loc[:,col] != 0).sum(axis=1).valuesdf['no_nonzeros'] = (df.loc[:,col] == 0).sum(axis=1).values

除此之外,在样本的所有特征中,每一种前缀的特征都有其独特的分布规律,每行记录每种关键词的所有特征取值为零的个数。因此我们构造新特征,表示每一行样本中每一种关键词前缀的特征取值为零或者非零的出现次数。

def add_feature_no_zeros_keyword(keyword,train=X_train,test=X_test):col = [k for k in train.columns if keyword in k]# for k in col:for df in [train,test]:df['no_zeros_'+keyword] = (df.loc[:,col] != 0).sum(axis=1).valuesdf['no_nonzeros_'+keyword] = (df.loc[:,col] == 0).sum(axis=1).values
add_feature_no_zeros()
keywords = list(f_keywords.keys())
for k in keywords:add_feature_no_zeros_keyword(k)

此时我们再来查看一下训练集和测试集的shape:

X_train.shape,X_test.shape
((76020, 154), (75818, 153))

这样就新增了10个特征

除此之外,我们注意到imp和saldo前缀特征,他们的取值除了0以外,其他的值是一个右偏分布,而且分布比较零散,那么我们将其均值作为一个新特征。
考虑到一个均值对目标变量应该没有影响,构造新特征-获取唯一值个数处于(50,210]之间的’col’特征中取每一种唯一值的情况下,含imp和saldo前缀特征的均值

def average_col(col,features,train=X_train,test=X_test):'''获取'col'特征中每一种唯一值的情况下feature特征的均值,并令其为新特征'''for df in [train,test]:unique_values = df[col].unique()for feature in features:#对每一个特征求他在指定特征col的每一个唯一值下的均值avg_value = []for value in unique_values:#对于每一个特征列col,求其每一种唯一值的情况下feature特征的均值avg = df.loc[df[col] == value,feature].mean()avg_value.append(avg)avg_dict = dict(zip(unique_values,avg_value))new_col = 'avg_'+ col + '_' + featuredf[new_col] = np.zeros(df.shape[0])#新建新特征for value in unique_values:df.loc[df[col]==value,new_col] = avg_dict[value]
#含imp和saldo前缀的所有特征,不包括no_zeros_imp和no_zeros_saldo
features = [i for i in X_train.columns if (('imp' in i) | ('saldo' in i)) & ('no_zeros' not in i)]#唯一值个数处于(50,210]之间的特征列
columns = [i for i in X_train.columns if (X_train[i].nunique() <= 210) & (X_train[i].nunique() > 50)]
len(features),len(columns)
for col in tqdm(columns):average_col(col,features)

我们再来看看此时的shape:

X_train.shape, X_test.shape
((76020, 952), (75818, 951))

2、过滤冗余特征

现在我们的特征已经很多了,除了最开始进行了剔除零方差和稀疏特征等一些简单的操作,我们一直在新增信息,这其中肯定包含了很多重复提取的冗余信息。主要体现在两个方面:1、与目标变量相关度非常低。2、特征之间相关性非常高,在回归分析中我们称之为多重共线性。现在我们就开始着手对这些特征进行处理。

删除与目标变量TARGET低相关的特征和彼此之间高度相关的特征

这里我们将低相关性阈值设置为0.0001,高相关性阈值设置为0.95,即检索所有特征,如果与目标变量的相关系数小于0.0001,我们就将它剔除,如果特征彼此之间的相关系数大于0.95,我们就保留一个与目标变量TARGET相关系数最高的特征。

def remove_corr_var(train=X_train,test=X_test,target_threshold=10**-3,within_threshold=0.95):#删除与目标变量相关性低的特征,删除彼此之间相关性高的特征(保留一个)initial_feature = train.shape[1]corr = train.drop('ID',axis=1).corr().abs()corr_target = pd.DataFrame(corr['TARGET']).sort_values(by='TARGET')print('corr_target')print(corr_target)feat_df = corr_target[corr_target['TARGET']<=target_threshold]print('有 %i 个特征因为与目标变量TARGET的相关系数绝对值小于 %.3f而被删除' % (feat_df.shape[0],target_threshold))print('deleting...')for df in [train,test]:df.drop(feat_df.index,axis=1,inplace=True)print('已删除!')#删除彼此之间相关性高的特征(保留一个与TARGET相关性最高的特征)corr.sort_values(by='TARGET',ascending=False,inplace=True)#将相关矩阵每一行先按TARGET列降序排列corr = corr.reindex(columns=corr.index)corr.drop('TARGET',axis=1,inplace=True)#删除target列corr.drop('TARGET',axis=0,inplace=True)corr.drop(feat_df.index,axis=1,inplace=True)#删除feat_df中特征在corr表corr表里的列corr.drop(feat_df.index,inplace=True)upper = corr.where(np.triu(np.ones(corr.shape),k=1).astype(np.bool))  # 获取相关矩阵的上三角column = [col for col in upper.columns if any(upper[col] > within_threshold)]  # 获取与特征之一高度相关的所有列print("有 %i 个特征与另一个特征高度相关且相关系数为 %.3f 及以上而被删除" % (len(column), within_threshold))print("删除中.........")for df in [train, test]:df.drop(column, axis=1, inplace=True)print("已删除!")print("特征数从 %i 个变成 %i 个,其中 %i 个特征已被删除" %(initial_feature, test.shape[1], initial_feature - test.shape[1]))

(具体代码分析过程详见源代码)

至此数据清洗过程完成,将train和test保存为P文件,方便后续调用

解释一下这里为什么是保存X_train,X_test,我们之前是利用copy函数对train和test进行拷贝的。但是这里的拷贝是深度拷贝,就是X-train是train的一个指针,所以我们对train作出的修改一样会同步到X_train去

# 保存为P文件,方便后续调用
X_train.to_pickle('./data/santander-customer-satisfaction/output/X_train.pkl')
X_test.to_pickle('./data/santander-customer-satisfaction/output/X_test.pkl')# 读取上述P文件
X_train = pd.read_pickle('./data/santander-customer-satisfaction/output/X_train.pkl')
X_test = pd.read_pickle('./data/santander-customer-satisfaction/output/X_test.pkl')
X_train.shape, X_test.shape

现在我们的训练集和测试集的shape为

((76020, 338), (75818, 337))

3、数据预处理

对数变换

之前我们分析过,对imp和saldo类的特征,其数据通常不均衡,除去大量的0值以外,剩余的非零值往往呈现出右偏分布 所以这里我们对这两类特征进行对数变换

def apply_log1p(column,train=X_train,test=X_test):#对数变换列特征tr = train.copy()te = test.copy()for df in [tr,te]:for col in column:df.loc[df[col] >= 0, col] = np.log1p(df.loc[df[col] >= 0, col].values)return tr,te# 对所有最小值大于等于0的imp和saldo特征进行对数变换(var38在EDA中已经对数化,这里不再操作)
features = [i for i in X_train.columns if (('saldo' in i) | ('imp' in i)) & ((X_train[i].values >= 0).all())]
X_train_1, X_test_1 = apply_log1p(features)

将对数变换后的数据集保存为X_train_l,X_test_l数据集

X_train_1.to_pickle('./data/santander-customer-satisfaction/output/X_train_1.pkl')
X_test_1.to_pickle('./data/santander-customer-satisfaction/output/X_test_1.pkl'))

独热编码

对于分类问题,通常我们需要对分类型变量进行编码操作,但是唯一值过多的变量会使得数据集过于稀疏,所以我们这里设置一个阈值,唯一值大于1小于11的变量我们才进行编码。

# 选取唯一值的个数(2,10]的特征
cat_col = []
for col in X_train.columns:if (X_train[col].nunique() <= 10) & (col != 'TARGET') & (X_train[col].nunique() > 2):cat_col.append(col)
print("有 %i 个特征其唯一值数量(2,10]并使用它们创建独热编码和响应编码变量,同时删除原始特征" % (len(cat_col)))

有 17 个特征其唯一值数量(2,10]并使用它们创建独热编码和响应编码变量,同时删除原始特征

打印出需要编码的变量列表:

['var15','num_var4','num_var5_0','num_var12_0','num_var13_largo_0','num_var14_0','num_var25','num_var30','num_var40_0','num_var41_0','num_var42_0','num_var42','var36','num_aport_var13_hace3','num_meses_var5_ult3','num_meses_var8_ult3','num_meses_var39_vig_ult3']

接下来我们定义针对指定列进行one_hot编码的函数:

def one_hot_encoding(col,train=X_train,test=X_test):#对训练集和测试集中的特征进行独热编码#一般的训练过程是enc.transform(df).toarray(),就是默认返回稀疏矩阵,然后便于查看所以转换为数组的形式,但是直接sparse=False就可以直接返回数组矩阵而不需要toarray()#默认情况下,handle_unknown = error,当遇到 transform时遇到fit中没有出现过的特征类别时,会直接报错#get_features:展示编码后的特征名,默认有个前缀是x0,x1...例如:array(['x0_PHD', 'x0_master', 'x1_A', 'x1_B', 'x1_C'], dtype=object),但是这个x0也可以通过input_features参数指定#duplicated函数:找出df中的重复值ohe = OneHotEncoder(sparse=True, handle_unknown='ignore')ohe.fit(train[col])feature_names = list(ohe.get_feature_names(input_features=col))features = list(train.drop(col, axis=1).columns)features.extend(feature_names)# traindf = train.copy()temp = ohe.transform(df[col])df.drop(col, axis=1, inplace=True)train = pd.DataFrame(hstack([df.values, temp]).toarray(), columns=features)train = train.loc[:, ~train.columns.duplicated(keep='first')]  # 删除重复行# testdf = test.copy()temp = ohe.transform(df[col])df.drop(col, axis=1, inplace=True)features.remove('TARGET')test = pd.DataFrame(hstack([df.values, temp]).toarray(), columns=features)test = test.loc[:, ~test.columns.duplicated(keep='first')]return train, test

将其应用于X_train_1数据集并查看这一步:

X_train_ohe, X_test_ohe = one_hot_encoding(cat_col)
X_train_1_ohe, X_test_1_ohe = one_hot_encoding(cat_col, X_train_1, X_test_1)
X_train_ohe.shape, X_test_ohe.shape, X_train_1_ohe.shape, X_test_1_ohe.shape
((76020, 426), (75818, 425), (76020, 426), (75818, 425))

响应编码

响应编码是一种对分类数据进行矢量化的技术。假设我们有一个名为“grade_category”的分类特征,它具有以下唯一标签 - [‘grades_3_5’,‘grades_prek_2’,‘grades_9_12’,‘grades_6_8’]。 假设我们正在处理目标类标签为 0 和 1 的分类问题
在响应编码中,您必须为我们特征中的每个标签输出概率值,该标签出现在特定的类标签中 例如,grades_prek_2 = [发生在 class_0 的概率,发生在 class 1 的概率]
响应编码的介绍

def response_encoding_return(df, column, target, alpha=5000):"""使用带有拉普拉斯平滑的响应编码到分类列column,并在训练、测试、验证数据集中转换相应的列。此函数用来训练出最优的参数alpha"""unique_values = set(df[column].values)#求得所有唯一值dict_values = {}for value in unique_values:total = len(df[df[column] == value])sum_promoted = len(df[(df[column] == value) & df[target] == 1])dict_values[value] = np.round((sum_promoted + alpha) / (total + alpha * len(unique_values)), 2)return dict_values# 寻找最好的alpha
def find_alpha(seed):random.seed(seed)ran_in = random.randint(0, 10)  # 随机生成0-9的整数col = [col for col in cat_col if X_train[col].nunique() > 3][ran_in]print('Feature: "%s"' % (col))for alpha in [100, 500, 1000, 2500, 5000, 10000]:print('for alpha %i:%s' % (alpha, response_encoding_return(X_train, col, "TARGET", alpha=alpha)))
find_alpha(seed=100)

我们分别在两个种子下寻找最优alpha:
在这里插入图片描述
== 查看以上两个特征,发现最好的 alpha 为 alpha=100,因为每个类别对此 alpha 具有最高的不同响应编码值, 随着 alpha 的增加,所有唯一值的响应编码值变得相似==

接下来定义响应编码函数:

def response_encoding(df,test_df,column,target='TARGET',alpha=5000):"""在这里,我们使用带有拉普拉斯平滑的响应编码到分类列,并在训练、测试、验证数据集中转换相应的列。在这里,我们将重复每个类别的值 alpha 时间。"""feature = column + '_1'feature_ = column + '_0'unique_values = set(df[column].values)dict_values = {} #存储target=1的响应编码值dict_values_ = {} #存储target=0的响应编码值for value in unique_values:total = len(df[df[column] == value])#此类别值在df中的个数#类别为某‘value’且目标变量为1时在df中的总个数sum_promoted = len(df[(df[column] == value) & (df[target] == 1)])sum_unpromoted = total - sum_promoted# 类别为某'vale'值且目标变量取0时在df中的总个数dict_values[value] = np.round((sum_promoted + alpha) / (total + alpha*len(unique_values)),2)dict_values[value] = np.round((sum_unpromoted + alpha) / (total + alpha*len(unique_values)),2)#假定了在某个字段中训练集出现的取值不完整,有些只在测试集中出现,这就是未知类别dict_values['unknown'] = 0.5#在训练集上观测不到的未知类别将被分配为0.5dict_values_['unknown'] = 0.5df[feature] = (df[column].map(dict_values)).valuesdf[feature_] = (df[column].map(dict_values_)).values# print('dict_values: ')# print(dict_values)# print('dict_values_: ')# print(dict_values_)df.drop(column, axis=1, inplace=True)unique_values_test = set(test_df[column])#找出亮哥set中的不同元素并赋值为unknowntest_df[column] = test_df[column].apply(lambda x: 'unknown' if x in (unique_values_test-unique_values) else x)test_df[feature] = (test_df[column].map(dict_values)).valuestest_df[feature_] = (test_df[column].map(dict_values_)).valuestest_df.drop(column, axis=1, inplace=True)

对特征进行响应编码:

alpha = 100
X_train_re = X_train.copy()
X_test_re = X_test.copy()
X_train_1_re = X_train_1.copy()
X_test_1_re = X_test_1.copy()
for col in tqdm(cat_col):response_encoding(X_train_re, X_test_re, col, alpha=alpha)response_encoding(X_train_1_re, X_test_1_re, col, alpha=alpha)X_train_re.shape, X_test_re.shape, X_train_1_re.shape, X_test_1_re.shape

此时的shape为:

((76020, 354), (75818, 353), (76020, 354), (75818, 353))

特征标准化

def stdzation(train,test):col = [i for i in train.columns if (i != 'TARGET') & (i != 'ID')]scaler = StandardScaler()train[col] = scaler.fit_transform(train[col])test[col] = scaler.transform(test[col])
datasets = [(X_train, X_test), (X_train_re, X_test_re), (X_train_ohe, X_test_ohe),(X_train_1, X_test_1), (X_train_1_re, X_test_1_re), (X_train_1_ohe, X_test_1_ohe)]
for train, test in datasets:stdzation(train, test)

dataset之所以有很多种数据集组合是因为,上面的过程对数据集进行了几种操作:1、对分类变量进行响应编码2、对分类变量进行one-hot编码3、对imp和saldo关键字特征进行对数变换4、对数变换和响应编码的组合5、对数变换和独热编码的组合

总结

  • 保存最终特征工程后的数据集

    • normal: Normal       normal_re: Normal with Response encoding       normal_ohe: Normal with One Hot Encoding

    • log: Log Transformed    log_re: Log Transformed with Response encoding    log_ohe: Log Transformed with One Hot Encoding

我们查看一下各数据集的特征数:

datasets_labels = ["normal", 'normal_re',"normal_ohe", "log", 'log_re', "log_ohe"]
print("不同数据集最终的特征数是:")
for i, (train, test) in enumerate(datasets):print("%s:\t%i" % (datasets_labels[i], test.shape[1]))
不同数据集最终的特征数是:
normal:	337
normal_re:	353
normal_ohe:	425
log:	337
log_re:	353
log_ohe:	425

至此,模型训练前所有的准备工作均已完成,下一节我们将使用不同的模型对测试集上的分类效果进行综合评估。

这篇关于kaggle竞赛——桑坦德银行客户满意度预测(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/508542

相关文章

客户案例:安全海外中继助力知名家电企业化解海外通邮困境

1、客户背景 广东格兰仕集团有限公司(以下简称“格兰仕”),成立于1978年,是中国家电行业的领军企业之一。作为全球最大的微波炉生产基地,格兰仕拥有多项国际领先的家电制造技术,连续多年位列中国家电出口前列。格兰仕不仅注重业务的全球拓展,更重视业务流程的高效与顺畅,以确保在国际舞台上的竞争力。 2、需求痛点 随着格兰仕全球化战略的深入实施,其海外业务快速增长,电子邮件成为了关键的沟通工具。

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

2024年AMC10美国数学竞赛倒计时两个月:吃透1250道真题和知识点(持续)

根据通知,2024年AMC10美国数学竞赛的报名还有两周,正式比赛还有两个月就要开始了。计划参赛的孩子们要记好时间,认真备考,最后冲刺再提高成绩。 那么如何备考2024年AMC10美国数学竞赛呢?做真题,吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。通过做真题,可以帮助孩子找到真实竞赛的感觉,而且更加贴近比赛的内容,可以通过真题查漏补缺,更有针对性的补齐知识的短板。

2024 年高教社杯全国大学生数学建模竞赛题目——2024 年高教社杯全国大学生数学建模竞赛题目的求解

2024 年高教社杯全国大学生数学建模竞赛题目 (请先阅读“ 全国大学生数学建模竞赛论文格式规范 ”) 2024 年高教社杯全国大学生数学建模竞赛题目 随着城市化进程的加快、机动车的快速普及, 以及人们活动范围的不断扩大,城市道 路交通拥堵问题日渐严重,即使在一些非中心城市,道路交通拥堵问题也成为影响地方经 济发展和百姓幸福感的一个“痛点”,是相关部门的棘手难题之一。 考虑一个拥有知名景区

2024 年高教社杯全国大学生数学建模竞赛 C 题 农作物的种植策略 参考论文 无水印

持续更新中,2024年数学建模比赛思路代码论文都会发布到专栏内,只需订阅一次!  完整论文+代码+数据结果链接在文末!  订阅后可查看参考论文文件 第一问 1.1 问题重述 这个问题围绕的是华北山区的某乡村,在有限的耕地条件下,如何制定最优的农作物种植策略。乡村有 34 块露天耕地和 20 个大棚,种植条件包括粮食作物、蔬菜、水稻和食用菌。除了要考虑地块的面积、种植季节等,还要确保

kaggle竞赛宝典 | Mamba模型综述!

本文来源公众号“kaggle竞赛宝典”,仅用于学术分享,侵权删,干货满满。 原文链接:Mamba模型综述! 型语言模型(LLMs),成为深度学习的基石。尽管取得了令人瞩目的成就,Transformers仍面临固有的局限性,尤其是在推理时,由于注意力计算的平方复杂度,导致推理过程耗时较长。 最近,一种名为Mamba的新型架构应运而生,其灵感源自经典的状态空间模型,成为构建基础模型的有力替代方案

Tensorflow lstm实现的小说撰写预测

最近,在研究深度学习方面的知识,结合Tensorflow,完成了基于lstm的小说预测程序demo。 lstm是改进的RNN,具有长期记忆功能,相对于RNN,增加了多个门来控制输入与输出。原理方面的知识网上很多,在此,我只是将我短暂学习的tensorflow写一个预测小说的demo,如果有错误,还望大家指出。 1、将小说进行分词,去除空格,建立词汇表与id的字典,生成初始输入模型的x与y d

临床基础两手抓!这个12+神经网络模型太贪了,免疫治疗预测、通路重要性、基因重要性、通路交互作用性全部拿下!

生信碱移 IRnet介绍 用于预测病人免疫治疗反应类型的生物过程嵌入神经网络,提供通路、通路交互、基因重要性的多重可解释性评估。 临床实践中常常遇到许多复杂的问题,常见的两种是: 二分类或多分类:预测患者对治疗有无耐受(二分类)、判断患者的疾病分级(多分类); 连续数值的预测:预测癌症病人的风险、预测患者的白细胞数值水平; 尽管传统的机器学习提供了高效的建模预测与初步的特征重

【数据库实战】1_Oracle_命中关联人或黑名单或反洗钱客户

一、字段名称 1、CST_ID :客户编号 2、IDV_LGL_NM :客户姓名 3、关联方标志 RELPARTY_IND,0-否 未命中,1-是 命中 4、TBPC1010表,RSRV_FLD1_INF(备用字段)中的 第6位:黑名单标志,0无,1是。 第10位:反洗钱风险等级1-5。 反洗钱风险等级5级: 1级-低风险客户 2级-较低风险客户 3级-中风险客户 4级-较高风险客户 5级-高风

上海市计算机学会竞赛平台2024年7月月赛丙组求和问题

题目描述 给定 nn 个整数 a1,a2,…,ana1​,a2​,…,an​,请问这个序列最长有多少长的前缀,满足元素的和大于或等于 00?如果任何长度大于 00 的前缀之和都为负数,则输出 00 输入格式 第一行:单个整数表示 nn第二行:nn 个整数表示 a1,a2,…,ana1​,a2​,…,an​ 输出格式 单个整数:表示最长的前缀长度,使得前缀的和大于等于 00 数据范围