2023“桂林银行杯”Z世代的信用卡消费行为分析

本文分享了一种在预赛中针对‘识别信用卡客户的分期付款意愿’问题的解决方案。通过数据预处理、特征工程(包括特征交叉和分箱)、模型构建(使用LightGBM和CatBoost)以及模型融合,实现了较高的预测性能,最终在143支队伍中排名2位。关键操作包括特征的非线性增强和稳定性提升。
摘要由CSDN通过智能技术生成

本文主要是分享预赛环节(打榜赛)的一些思路,最终排名为 2/143。
代码链接:思路源代码分享

赛事简介

2023“桂林银行杯”B赛道

赛题背景

预赛以信用卡消费场景为切入点,以“识别信用卡客户的分期付款意愿”为主题,提供包含用户基本信息、借贷信息、财务信息、行为信息和外部数据等脱敏后数据,要求参赛队伍对数据进行洞察、探索、分析,利用数理统计、机器学习、深度学习等方法建立模型,实现2种不同label(0或1)信用卡持卡客户是否会办理分期付款业务的识别,提升参赛队伍对金融场景的工程实践能力。

赛题理解与分析

本次大赛预赛提供的数据包括脱敏后的ID(客户唯一识别号)、基础信息、行为特征、借贷信息和财务信息,以及是否办理了分期付款业务的标签项(label)。该任务的关键是如何对提供的训练集数据进行数据预处理、数据探索性分析、特征工程、特征筛选等步骤,并选择合适的算法构建模型对测试集中的客户是否会办理分期付款业务。我们团队认为,以上步骤都是紧密联系的,只有先对数据进行探索性分析,挖掘数据中潜在的规律,才能够从现有的特征中构造和衍生对影响客户是否会办理分期业务的有效特征,如此才能提升预测模型的效果。

基本思路

image.png
总体设计思路包含数据预处理、数据探索性(EDA)分析、特征工程、特征筛选、算法选择与模型构建、模型加权融合、模型预测这些部分。

数据预处理

分别读取训练集数据和测试集数据,其中训练集数据包括客户基础信息、行为特征、财务信息和是否办理了分期付款业务的标签项这四个部分,测试集数据包括客户基础信息、行为特征、财务信息这三个部分;根据唯一索引“客户ID”对各部分数据进行拼接,得到训练集数据和测试集数据;另外,该数据中包含类别型特征,这些特征是字符型的,并不能直接放入模型中训练,所以本方法采用字符型特征编码方法对三个类别型特征"b2"、“b3”、"b28"进行编码处理。注意在编码之前,先对这三个特征中的缺失值进行处理,直接采用’nan’进行填充。

特征工程

特征交叉

特征交叉可以将样本映射至高维空间,从而增加模型的非线性能力, 提升模型的预测效果。因此,本方法在获取每个原始特征的重要性之后,选取其中较重要的20个特征[“b26”, “b22”, “b18”, “b25”, “b10”, “b11”, “b23”, “b21”, “b20”, “b13”, “b27”, “b24”, “b17”, “b14”, “b12”, “b15”, “b16”, “b29”, “b8”, “b19”]进行特征间的交叉,即进行加、减、乘、除四则运算,增强特征的非线性表达能力,从而提升模型的效果。

topn = ["b26", "b22", "b18", "b25", "b10", "b11", "b23", "b21", 
        "b20", "b13", "b27", "b24", "b17", "b14", "b12", "b15", 
        "b16", "b29", "b8", "b19"] 
for i in range(len(topn)):
    for j in range(i+1, len(topn)):
        data[f"{topn[i]}+{topn[j]}"] = data[topn[i]] + data[topn[j]]
        data[f"{topn[i]}-{topn[j]}"] = data[topn[i]] - data[topn[j]]
        data[f"{topn[i]}*{topn[j]}"] = data[topn[i]] * data[topn[j]]
        data[f"{topn[i]}/{topn[j]}"] = data[topn[i]] / (data[topn[j]]+1e-5)

特征分箱

本方法通过数据探索性分析发现,部分重要特征在不同的取值上标签取值的概率不一样,并且这种关系是非线性的,且具有较强的区分能力。因此本方法考虑对部分重要特征进行分箱处理,分箱离散化之后更能够刻画这种关系,且能够去噪,模型会更稳定,降低过拟合的风险。
离散型特征中以“b1(年龄)”为例,绘制该特征的核密度概率分布图,如下图所示:
image.png

# 年龄 b1
# 观察取值来分箱
def get_b1_seg(x):
    if x >=21 and x <= 22:
        return 0 # 在以上取值区间内,返回0
    elif x > 22 and x <= 24:
        return 1 # 返回1
    elif x > 24 and x <= 25:
        return 2 # 返回2
    elif x > 25 and x <= 26:
        return 3 # 返回3
    elif x > 26 and x <= 27:
        return 4 # 返回4
    elif x > 27 and x <= 28:
        return 5 # 返回5
data["b1_seg"] = data["b1"].apply(lambda x: get_b1_seg(x)) # 对年龄特征进行分箱

上图表明,各个年龄段在标签为“0”和标签为“1”的概率分布区别较大,说明对该特征进行分箱离散化处理可以让模型学习到该规律,提升模型的预测效果。
连续型字段以“b26(近三个月最大月消费金额)”为例,绘制该特征的核密度概率分布图,如下图所示:
image.png
上图表明,在b26取值为“0-10000”区间内,标签为“0”和标签为“1”的概率分布区别较大,说明将b26在该区间内的取值归为一类,将更有利于模型学习到这种差异性。

# b26 近3个月最大消费金额
def get_b26_seg(x):
    if x <=0 : # 存在异常值
        return 0
    if x >=0 and x <= 8000:
        return 1
    if x >=8000 and x <= 40000:
        return 2
    elif x > 40000 and x <= np.max(data["b26"]):
        return 3
data["b26_seg"] = data["b26"].apply(lambda x: get_b26_seg(x))
data = data.drop(["b26"], axis=1)

最终,特征分箱离散化的结果为:三类(0,1,2)分箱的特征:b21、b27;四类(0,1,2,3)分箱的特征:b10、b11、b20、b23、b26;六类(0,1,2,3,4,5):b1(年龄)。
另外,发现b20、b21、b26、b27这四个特征存在异常值,因此采用特征分箱技术能够有效地解决这个问题,去除数据中噪声对模型的影响。以特征“b20”为例,绘制该特征的核密度概率分布图,如下图所示:
image.png
该特征的最小值是一个异常值,因此在特征分箱的时候,本方法将该特征特征值小于0的归为一类,如此便可以处理异常值,去除数据中的噪声。同理,b21、b26、b27也是这样处理,能够使模型更加稳定。

计算额度使用率、账单金额、月消费金额的变异系数

为了了解用户信用卡在额度使用率、账单金额、月消费金额这三个方面的稳定性,本方法分别计算近3个月内以及3个月间(近半年的前3个月与近3个月)这三个特征的变异系数。首先是近3个月内,以额度使用率为例,已知“b13”表示近3个月的额度使用率的均值,可以将其乘以3然后减去“b14”和“b15”,如此就得到近3个月里每个月的额度使用率。然后就可以计算每个用户这三个月额度使用率的变异系数,具体为先求这三个月的标准差,然后除以均值,就可以得到变异系数。同理,账单金额、月消费金额也是如此计算。其次是3个月间(近半年的前3个月与近3个月),以额度使用率为例,已知“b10”表示近半年的额度使用率的均值,“b13”表示近3个月的额度使用率的均值,可以分别将它们乘以6和3,得到近半年总的额度使用率和近3个月总的额度使用率。将近半年总的额度使用率减去近3个月总的额度使用率就得到近半年前3个月的额度使用率。然后就可以计算近3个月总的额度使用率与近半年前3个月的额度使用率之间的变异系数。同理,账单金额、月消费金额也是如此计算。

# 计算变异系数以了解用户信用卡额度使用率、账单金额、月消费金额的稳定性

# 变异系数计算方式为 标准差 / 均值
# 3个月内变异系数
# 计算近三个月额度使用率的变异系数
data["ed_mid"] = data["b13"]*3 - data["b14"]- data["b15"] # 如此就知道近三个月中每个月的额度使用率
data['ed_byxs'] = data[['b14', 'ed_mid', 'b15']].apply(lambda x: np.std(x) / np.mean(x), axis=1)
# 计算近三个月账单的变异系数
data["zd_mid"] = data["b19"]*3 - data["b20"]- data["b21"] 
data['zd_byxs'] = data[['b20', 'zd_mid', 'b21']].apply(lambda x: np.std(x) / (np.mean(x)), axis=1)
# 计算近三个月月消费金额的变异系数
data["xf_mid"] = data["b25"]*3 - data["b26"]- data["b27"] 
data['xf_byxs'] = data[['b26', 'xf_mid', 'b27']].apply(lambda x: np.std(x) / (np.mean(x)), axis=1)

# 3个月间变异系数
# ed
data["half_year_ed_sum"] = data["b10"]*6 # 近6个月
data["3_month_ed_sum"] = data["b13"]*3 # 近3个月
data["half_year-3_month_ed"] = data["half_year_ed_sum"]-data["3_month_ed_sum"]
data['ed_byxs_3_month'] = data[['half_year-3_month_ed', '3_month_ed_sum']].apply(lambda x: np.std(x) / np.mean(x), axis=1)

# zd
data["half_year_zd_sum"] = data["b16"]*6 # 近6个月
data["3_month_zd_sum"] = data["b19"]*3 # 近3个月
data["half_year-3_month_zd"] = data["half_year_zd_sum"]-data["3_month_zd_sum"]
data['zd_byxs_3_month'] = data[['half_year-3_month_zd', '3_month_zd_sum']].apply(lambda x: np.std(x) / np.mean(x), axis=1)

# xf
data["half_year_xf_sum"] = data["b22"]*6 # 近6个月
data["3_month_xf_sum"] = data["b25"]*3 # 近3个月
data["half_year-3_month_xf"] = data["half_year_xf_sum"]-data["3_month_xf_sum"]
data['xf_byxs_3_month'] = data[['half_year-3_month_xf', '3_month_xf_sum']].apply(lambda x: np.std(x) / np.mean(x), axis=1)

计算额度使用率、账单金额、月消费金额的偏度和峰度

计算偏度和峰度以了解用户的信用卡额度使用率、账单金额、月消费金额是否具有偏态和峰态。得到近三个月里每个月的这三个特征的值之后,采用scipy库下的skew()和kurtosis()函数来计算它们的偏度和峰度。

## 了解用户的信用卡额度使用率、账单金额、月消费金额是否具有偏态和峰态

# 3个月内偏度和峰度
from scipy.stats import kurtosis, skew
data['ed_sk'] = data[['b14', 'ed_mid', 'b15']].apply(lambda x: skew(x), axis=1) # 注意加上 axis=1
data['ed_ku'] = data[['b14', 'ed_mid', 'b15']].apply(lambda x: kurtosis(x), axis=1)

data['zd_sk'] = data[['b20', 'zd_mid', 'b21']].apply(lambda x: skew(x), axis=1)
data['zd_ku'] = data[['b20', 'zd_mid', 'b21']].apply(lambda x: kurtosis(x), axis=1)

data['xf_sk'] = data[['b26', 'xf_mid', 'b27']].apply(lambda x: skew(x), axis=1)
data['xf_ku'] = data[['b26', 'xf_mid', 'b27']].apply(lambda x: kurtosis(x), axis=1)

时间特征构造

本方法基于“b5”、“b6”、“b7”这三个特征分别计算激活时间与发卡时间的差值、首次交易时间与激活时间的差值、首次交易时间与发卡时间的差值,以挖掘客户在发卡时间、激活时间、首次交易时间这三个时间的潜在规律。

#时间差值
data['6-5'] = data['b6'] - data['b5'] # 激活时间与发卡时间的差值
data['7-6'] = data['b7'] - data['b6'] # 首次交易时间与激活时间的差值
data['7-5'] = data['b7'] - data['b5'] # 首次交易时间与发卡时间的差值

特征筛选

删除只有单一取值的特征:

# 删除单一取值的特征
drop_cols= ["id", "label"]
for f in data.columns:
    if data[f].nunique() < 2: # nunique表示特征取不同值的数量 < 2表示只有单一取值
        drop_cols.append(f)

删除缺失率过高的特征:

# 删除缺失率过高的特征
def dropNaN(df, p, col):
    na_sum = df[col].isna().sum()
    percent_value = na_sum / len(df[col])
    if percent_value >= p:
        df = df.drop([col], axis=1)
    return df
for c in data.columns:
    data = dropNaN(data, 0.95, c) # 设置阈值为0.95,删除缺失率超出该阈值的特征

模型构建

本方法采用两套参数训练两个LightGBM模型,采用一套参数训练一个CatBoost模型。第一个LightGBM模型在训练集上的AUC值为0.8295;第二个LightGBM模型在训练集上的AUC值为0.8697;CatBoost模型在训练集上的AUC值为0.8814,随后计算三个模型在融合时候的权重,分别为0.321、0.337、0.342,通过这三个权重将三个模型进行加权融合,并通过迭代式搜索最佳分类阈值,最终得到的线下分数为0.7636,线上分数为0.6191。
具体见代码。

总结与思考

  • 这个思路比较能提高分数的操作是特征分箱与计算变异系数、偏度和峰度。
  • 本题更多的还是结合银行相关的业务知识来构建特征才能够更多地提升分数。
  • 继续学习!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值