【作者主页】Francek Chen
【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联和异常的过程,旨在提取有价值的信息和知识。机器学习为数据挖掘提供了强大的分析工具,而数据挖掘则是机器学习应用的重要领域,两者相辅相成,共同推动数据科学的发展。本专栏介绍机器学习与数据挖掘的相关实战案例。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/ML-DM_cases。
文章目录
- 一、目标分析
- (一)背景
- (二)数据说明
- (三)分析目标
- 二、数据探索
- 三、数据预处理
- (一)数据清洗
- (二)属性构建
- 四、分析与建模
- (一)聚类参数寻优
- (二)构建信用卡高风险客户识别模型
- (三)信用卡客户风险分析
- 五、模型评价
- 小结
一、目标分析
(一)背景
1. 信用卡的定义
信用卡是银行或其他财务机构签发给那些资信状况良好的人士,用于在指定的商家购物和消费,或在指定银行机构存取现金的特制卡片,是一种特殊的信用凭证。
2. 信用卡的分类
随着信用卡业务的发展,信用卡种类不断增多,一般有广义信用卡和狭义信用卡之分。
- 广义信用卡:是指凡是能够为持卡人提供信用证明、消费信贷或持卡人可凭卡购物、消费或享受特定服务的特制卡片,包括贷记卡、准贷记卡、借记卡、储蓄卡、提款卡(ATM 卡)、支票卡及赊账卡等。
- 狭义信用卡:是指由银行或其他财务机构发行的贷记卡,即无需预先存款就可以贷款消费的信用卡,是先消费后还款的信用卡。(注:本案例所讨论的信用卡,主要指狭义上的信用卡,即“贷记卡”。)
3. 信用卡的功能和实质
功能:包括支付结算、转账结算、消费贷款、循环授信和提取现金。其中,消费贷款是信用卡最重要的功能,改变了传统的消费支付方式,扩大了社会的信用规模,促进了社会的消费需求。
实质:循环消费信贷的新型支付工具。
4. 信用卡支付与传统支付
5. 信用卡的产生和发展
某地区的信用卡产业始于20世纪80年代,1989年开始向外资银行开放市场后,以花旗银行、汇丰银行、美国运通为代表的信用卡巨头依托成熟的信用卡业务模式,依托合理风险下的经营理念,特别是开创了信用卡销售外包模式,迅速占领了某地区信用卡市场。发卡量从1989年的50多万张一跃到1995年的500多万张。90年代中后期,以中国信托银行、台新银行等银行开始在信用卡业务上发力,以犀利的市场营销战略重新夺得信用卡市场。
为了推进信用卡业务良性发展,减少坏账风险,各大银行都进行了信用卡客户风险识别相关工作,建立了相应的客户风险识别模型。某银行因旧的风险识别模型随时间推移,很难适应业务发展需求,需要重新进行风险识别模型构建。对不同客户类别进行特征分析,比较不同客户的风险。评估该机构的信用卡业务风险,针对目前的情况提出风控建议。
(二)数据说明
属性名称 | 属性取值说明 | 示例 |
---|---|---|
顾客编号 | CDMS0000001 | |
申请书来源 | 1.Take-One邮寄件 2.现场办卡 3.电访 4.亲签亲访 5.亲访 6.亲签 7.本行VIP、PB 8.其他 | 1 |
瑕疵户 | 1.是 2.否 | 2 |
逾期 | 1.是 2.否 | 1 |
呆账 | 1.是 2.否 | 2 |
借款余额 | 1.是 2.否 | 1 |
退票 | 1.是 2.否 | 2 |
拒往记录 | 1.是 2.否 | 1 |
强制停卡记录 | 1.是 2.否 | 2 |
频率 | 1.天天用 2.经常用 3.偶而用 4.很少用 5.没有用 | 2 |
个人月收入 | 1.无收入 2.10000元以下 3.10001-20000元 4.20001-30000元 5.30001-40000元 6.40001-50000元 7.50001-60000元 8.60001元以上 | 4 |
个人月开销 | 1.10000元以下 2.10001-20000元 3.20001-30000元 4.30001-40000元 5.40001元以上 | 5 |
家庭月收入 | 1.20000元以下 2.20001-40000元 3.40001-60000元 4.60001-80000元 5.80001-100000元 6.100001元以上 | 3 |
月刷卡额 | 1.20000元以下 2.20001-40000元 3.40001-60000元 4.60001-80000元 5.80001-100000元 6.100001-150000元 7.150001-200000元 8.200000以上 | 4 |
人口数 | 1.1人 2.2人 3.3人 4.4人 5.5人 6.6人 7.7人 8.8人 9.9人以上 | 2 |
家庭经济 | 1.上 2.中上 3.中 4.中下 5.下 | 1 |
都市化程度 | 1.都会 2.都市 3.城镇 | 2 |
张数 | 1.1张 2.2张 3.3张 4.4张 5.大于4张 | 5 |
学历 | 1.小学及以下 2.国初中 3.高中职 4.专科 5.大学及以上 | 2 |
职业 | 1.管理职 2.专门职 3.技术职 4.事务职 5.销售职 6.劳务职 7.服务职 8.农林渔牧自营 9.商工服务自营(员工9人以下) 10.自由业自营 11.经营者(员工10人以上) 12.家庭主妇(没有兼副业) 13.家庭主妇(有兼副业) 14.无职 15.其他 | 3 |
住家 | 1.租赁 2.宿舍 3.本人所有 4.父母所有 5.配偶所有 6.其他 | 2 |
户籍 | 1.北部 2.中部 3.南部 4.东部 | 3 |
性别 | 1.女 2.男 | 1 |
宗教信仰 | 1.宗教1 2.宗教2 3.宗教3 4.宗教4 5.宗教5 6.宗教6 7.其他 | 2 |
年龄 | 1.此信用卡持有人之15-19岁 2.20-24岁 3.25-29岁 4.30-34岁 5.35-39岁 6.40-44岁 7.45-49岁 8.50-54岁 9.55-59岁 | 5 |
婚姻 | 1.未婚 2.已婚 3.其他 | 1 |
血型 | 1.A型 2.B型 3.AB型 4.O型 | 1 |
星座 | 1.白羊座 2.金牛座 3.双子座 4.巨蟹座 5.狮子座 6.处女座 7.天秤座 8.天蝎座 9.射手座 10.魔羯座 11.双鱼座 | 1 |
(三)分析目标
结合信用卡高风险客户识别的数据情况,利用K-Means聚类模型,可以实现以下目标。
- 识别出哪些客户为高风险类客户,哪些客户为禁入类客户。
- 对不同客户类别进行特征分析,比较不同客户的风险。
- 评估该机构的信用卡业务风险,针对目前的情况提出风控建议。
信用卡高风险客户识别数据挖掘主要步骤如下。
- 了解不同客户类别的特征背景、数据说明和分析目标。
- 分析客户历史信用记录、经济情况、经济风险情况,对不同客户类别进行数据探索。
- 对数据定义冲突数据进行数据清洗、属性构造。
- 构建K-Means聚类模型,对信用卡客户进行风险分析。
- 根据构建后的模型结果进行模型评价。
- 根据聚类模型得到的客户风险分类结果提出风控建议。
二、数据探索
1. 描述性统计分析
描述性统计分析的本质是对数据概况的了解,进行描述性统计分析不仅能检查数据是否存在质量问题,而且也有助于之后数据特征信息的选取。对信用卡信息数据进行描述性统计分析,得到部分描述性统计结果如下表。
# 描述性统计分析
import pandas as pd# 读取数据文件
credit = pd.read_csv('../data/credit_card.csv', encoding='GBK')
# 删除信用卡顾客编号属性
credit = credit.drop('信用卡顾客编号', axis=1)
length = len(credit) # 计算数据量
# 定义描述性统计函数,且将结果保留3位小数
def status(x): return pd.Series([x.count(), length - x.count(), len(credit.groupby(by=x)), x.max() - x.min(),x.quantile(.75) - x.quantile(.25), x.mode()[0], format(x.var(), '.3f'), format(x.skew(), '.3f'), format(x.kurt(), '.3f')], index=['非空值数', '缺失值数','类别数', '极差', '四分位差', '众数', '方差', '偏度', '峰度'])# 应用描述性统计函数
describe_tb = credit.apply(status)
describe_tb
2. 客户历史信用记录与瑕疵户的关系
凡有迟缴,逾期,呆账,退票,停卡,银行拒往,保证人信用不良,配偶信用不良者,均属信用瑕疵范围。查看瑕疵户在客户中分布并绘制柱形图。瑕疵户在客户中分布所占比例较少。
根据瑕疵户的定义,查看逾期、呆账、强制停卡记录、退票记录和拒往记录5个历史信用记录情况在瑕疵户中的占比。
import matplotlib.pyplot as plt
from collections import OrderedDict
plt.rcParams['font.family'] = 'SimHei' # 正常显示中文plt.figure(figsize=(5, 4)) # 设置画布大小
plt.bar(['是'], credit['瑕疵户'].value_counts()[1], color='r', width=0.3)
plt.bar(['否'], credit['瑕疵户'].value_counts()[2], color='b', width=0.3)
plt.ylabel('客户数量', fontsize=12) # 设置y轴坐标和字体大小
plt.title('瑕疵户', fontsize=12) # 设置标题和字体大小
plt.show()# 编写瑕疵户与信用记录之间的关系函数
def credit_plot(column, i):ax = plt.subplot(3, 2, i) # 子画布is_data = credit[credit['瑕疵户'] == 1][column] # 瑕疵户数据not_data = credit[credit['瑕疵户'] == 2][column] # 非瑕疵户数据is_y = is_data.value_counts() / is_data.shape[0] # y数据ax.bar(1, is_y[1], color='r', label='是', width=0.3) # 绘制柱状图if len(is_y) == 2:ax.bar(1, is_y[2], bottom=is_y[1], color='b', width=0.3) # 柱堆叠not_y = not_data.value_counts() / not_data.shape[0] # y数据ax.bar(2, not_y[1], color='r', width=0.3) # 绘制柱形图ax.bar(2, not_y[2], bottom=not_y[1], color='b', label='否', width=0.3) # 绘制柱形图ax.set_xticks([1, 2]) # 设置x轴坐标ax.set_xticklabels(['是', '否'], fontsize=14) # 设置x轴坐标标签plt.ylabel('占比', fontsize=14) # 设置y标题plt.title(column, fontsize=14) # 设置标题plt.tight_layout(1.5) # 调整子图间距
plt.figure(figsize=(9, 9)) # 设置画布大小# 绘制瑕疵户与信用记录关系图
credit_plot('逾期', 1)
credit_plot('呆账', 2)
credit_plot('强制停卡记录', 3)
credit_plot('退票', 4)
credit_plot('拒往记录', 5)
plt.legend(loc=[2.3, 3.3], fontsize=12, handlelength=1) # 添加图例
plt.show()
3. 客户经济情况
判断客户是否为高风险客户,不仅要分析客户的历史信用记录,还要考虑客户现有的经济情况及还款能力,因此分析客户的个人月开销、月刷卡额、个人月收入和家庭月收入等属性。
# 定义绘制客户经济情况分析直方图的函数
def economic_plot(column, tick, a): ax = plt.subplot(2, 2, a) # 子图situ = sorted(credit[column].unique()) # 排序x = [i for i in range(len(situ))] # x轴坐标数据y = [credit[column].value_counts()[i] for i in situ] # y轴数据ax.bar(x, y, width=0.3) # 绘制柱状图plt.ylabel('数量', fontsize=14) # y轴坐标轴标题plt.xticks(rotation=30) # x轴坐标轴标签倾斜程度ax.set_xticks([i for i in range(len(x))]) # 重设x轴坐标数据 ax.set_xticklabels(tick, fontsize=14) # 设置x轴显示坐标数据ax.set_xlabel(column+'(万元)', fontsize=14) # y轴坐标轴标题plt.tight_layout(3) # 控制子图之间的距离
plt.figure(figsize=(10, 8))# 设置x轴坐标
tick1 = ['1以下', '1~2', '2~3', '3~4', '4以上'] # 个人月开销
tick2 = ['2以下', '2~4', '4~6', '6~8', '8~10', '10~15', '15~20', '20以上'] # 月刷卡额
tick3 = ['无收入', '0~1', '1~2', '2~3', '3~4', '4~5', '5~6', '6以上'] # 个人月收入
tick4 = ['未知', '2以下', '2~4', '4~6', '6~8', '8~10', '10以上'] # 家庭月收入
economic_plot('个人月开销', tick1, 1)
economic_plot('月刷卡额', tick2, 2)
economic_plot('个人月收入', tick3, 3)
economic_plot('家庭月收入', tick4, 4)
plt.show()
观察图可以发现家庭月收入有一个未知具体意义的属性值0。
4. 客户经济风险
自定义包color.py
的代码如下:
import colorsys
import randomdef get_n_hls_colors(num):hls_colors = []i = 0step = 360.0 / numwhile i < 360:h = is = 90 + random.random() * 10l = 50 + random.random() * 10_hlsc = [h / 360.0, l / 100.0, s / 100.0]hls_colors.append(_hlsc)i += stepreturn hls_colorsdef ncolors(num):rgb_colors = []if num < 1:return rgb_colorshls_colors = get_n_hls_colors(num)for hlsc in hls_colors:_r, _g, _b = colorsys.hls_to_rgb(hlsc[0], hlsc[1], hlsc[2])r, g, b = [int(x * 255.0) for x in (_r, _g, _b)]rgb_colors.append([r, g, b])return rgb_colorsdef color(value):digit = list(map(str, range(10))) + list("ABCDEF")if isinstance(value, tuple):string = '#'for i in value:a1 = i // 16a2 = i % 16string += digit[a1] + digit[a2]return stringelif isinstance(value, str):a1 = digit.index(value[1]) * 16 + digit.index(value[2])a2 = digit.index(value[3]) * 16 + digit.index(value[4])a3 = digit.index(value[5]) * 16 + digit.index(value[6])return (a1, a2, a3)
# 导入自行编写的绘制指定数量颜色的函数
from color import color, ncolors# 编写个人月收入,家庭月收入与月刷卡额之间的关系函数
def risk_plot(column1, column2, xlabel_list=[], ylabel_list=[]):fig, ax = plt.subplots(figsize=(8, 6)) # 画布大小x_data = credit[column1] # x轴数据co = list(map(lambda x:color(tuple(x)), ncolors(len(ylabel_list)))) # 指定数量的颜色# 循环绘制柱状堆叠图for i in sorted(x_data.unique()):y_data = credit[x_data == i][column2] part = sorted(y_data.unique())exp = 0if part[0] == 0:for j in part:exp1 = y_data.value_counts()[j] / len(y_data)ax.bar(i, exp1, bottom=exp, width=0.5, color=co[j], label=ylabel_list[j])exp += exp1 else:for j in part:exp1 = y_data.value_counts()[j] / len(y_data)ax.bar(i, exp1, bottom=exp, width=0.5, color=co[j-1], label=ylabel_list[j-1])exp += exp1 ax.set_xticks([i+1 for i in range(len(x_data.unique()))]) # 重设x轴坐标数据ax.set_xticklabels(xlabel_list, fontsize=10) # 设置x轴坐标显示数据ax.set_xlabel(column1 + '(万元)', fontsize=10) # 设置x轴标题plt.ylabel('占比', fontsize=12) # 设置y轴标题# 图例去重handles, labels = plt.gca().get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles))plt.legend(by_label.values(), by_label.keys(), loc=[1.01, 0], fontsize=10, title=column2+'(万元)')# 调整子图位置fig.subplots_adjust(right=0.8)
print('\n')
risk_plot('个人月收入', '家庭月收入', ['无收入', '0~1', '1~2', '2~3', '3~4', '4~5', '5~6', '6以上'], ['未知', '2以下', '2~4', '4~6', '6~8', '8~10', '10以上'])
plt.show()
risk_plot('月刷卡额', '个人月收入', ['2以下', '2~4', '4~6', '6~8', '8~10', '10~15', '15~20', '20以上'], ['无收入', '0~1', '1~2', '2~3', '3~4', '4~5', '5~6', '6以上'])
plt.show()
risk_plot('月刷卡额', '家庭月收入', ['2以下', '2~4', '4~6', '6~8', '8~10', '10~15', '15~20', '20以上'], ['未知', '2以下', '2~4', '4~6', '6~8', '8~10', '10以上'])
plt.show()
(1)个人月收入与家庭月收入的关系
个人月收入基本小于等于家庭月收入,且当个人月收入分别为3~4万元,4~5万元时,家庭月收入所对应的分别8~10万元及10万以上,而未知属性所对应的个人月收入为5~6万以及6万以上。
(2)月刷卡额与个人月收入的关系
客户的月刷卡额普遍大于个人月收入。
(3)月刷卡额与家庭月收入的关系
客户的月刷卡额甚至大于家庭月收入,因此客户的经济风险也属于高风险客户的范畴之一。
三、数据预处理
(一)数据清洗
根据变量定义,删除不符合定义的记录。
变量名 | 定义 |
---|---|
瑕疵户 | 凡有迟缴,逾期,呆账,退票,停卡,银行拒往,保证人信用不良,配偶信用不良者,均属瑕疵范围 |
逾期 | 此信用卡是否在本行逾期超过30天 |
呆账 | 已过偿付期限,经催讨尚不能回收,长期处于呆滞状态,有可能成为坏账的款项 |
强制停卡 | 信用卡若超过三个月没有缴款,银行会将您的信用卡停止使用,在停卡前会先经过催收等过程。 |
退票 | 开票人在一定时间内无法将钱存入账户中,银行视为退票。 |
拒往记录 | 信用记录不好,有退票记录,停卡记录,呆账记录,均会被银行拒绝往来。 |
查看数据属性,发现频率属性存在“不使用”这一取值,所以对应的刷卡额应该在20000以下,对于刷卡额超过两万的数据,则视为异常数据,应予以删除。
# 数据清洗
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
carddata = pd.read_csv('../data/credit_card.csv', engine='python')# 筛选逾期但是不是瑕疵户的数据
exp1 = (carddata['逾期'] == 1) & (carddata['瑕疵户'] == 2)
# 筛选呆账但是不是瑕疵户的数据
exp2 = (carddata['呆账'] == 1) & (carddata['瑕疵户'] == 2)
# 筛选有强制停卡记录但是不是瑕疵户的数据
exp3 = (carddata['强制停卡记录'] == 1) & (carddata['瑕疵户'] == 2)
# 筛选退票但是不是瑕疵户的数据
exp4 = (carddata['退票'] == 1) & (carddata['瑕疵户'] == 2)
# 筛选有拒收记录但是不是瑕疵户的数据
exp5 = (carddata['拒往记录'] == 1) & (carddata['瑕疵户'] == 2)
# 筛选有呆账但是没有拒收记录的数据
exp6 = (carddata['呆账'] == 1) & (carddata['拒往记录'] == 2)
# 筛选有强制停卡记录但是没有拒收记录的数据
exp7 = (carddata['强制停卡记录'] == 1) & (carddata['拒往记录'] == 2)
# 筛选退票但是没有拒收记录的数据
exp8 = (carddata['退票'] == 1) & (carddata['拒往记录'] == 2)
# 筛选频率为5但是月刷卡额大于1的数据
exp9 = (carddata['频率'] == 5) & (carddata['月刷卡额'] > 1)
# 筛选异常数据
Final = carddata.loc[(exp1 | exp2 | exp3 | exp4 | exp5 | exp6 | exp7 | exp8 | exp9).apply(lambda x:not(x)), :]
Final.reset_index(inplace = True)
Final
家庭月收入存在为“未知”的情况,而在数据说明表中并未有指出其家庭月收入的范围。考虑家庭月收入和个人月收入存在一定的相关关系,经过探索发现家庭月收入为5、6的客户与个人月收入为5、6的客户存在一一对应的关系。同时个人月收入7、8对应的家庭月收入正是未知。鉴于此,求出个人月收入为5和6的客户的个人月收入占家庭月收入的比值,从而根据这个比值求出家庭月收入为未知的客户的家庭月收入等级。经求取得出家庭月收入在15万元~19万元之间,因此将家庭月收入未知的等级更改为6。
# 个人月收入(万元)
PersonalMonthIncome = [0, 1, 2, 3, 4, 5, 6, 7, 8]
for i in range(8):Final.loc[Final['个人月收入'] == i + 1, '个人月收入'] = PersonalMonthIncome[i]
# 根据5 、6的情况计算个人月收入和家庭月收入的比值,确定家庭月收入为未知的情况
FamilyMonthIncome = [2, 4, 6, 8, 10, 12]
m = (Final.loc[: , '家庭月收入'] == 5)
Final.loc[m, '家庭月收入'] = FamilyMonthIncome[4]
ratio5 = Final.loc[m, '个人月收入'] / Final.loc[m, '家庭月收入']
m1 = Final.loc[: , '家庭月收入'] == 6
Final.loc[m1, '家庭月收入'] = FamilyMonthIncome[5]
ratio6 = Final.loc[m1, '个人月收入'] / Final.loc[m1, '家庭月收入']# 家庭月收入(万元)
FamilyMonthIncome = [2, 4, 6, 8, 10, 15]
Final.loc[Final['家庭月收入'] == 0, '家庭月收入'] = 6
for i in range(6):m2 = Final.loc[: , '家庭月收入'] == i + 1Final.loc[m2, '家庭月收入'] = FamilyMonthIncome[i]# 月刷卡额(万元)
MonthCardPay = [2, 4, 6, 8, 10, 15, 20, 25]
for i in range(8):m = Final.loc[: , '月刷卡额'] == i + 1Final.loc[m, '月刷卡额'] = MonthCardPay[i]# 个人月开销(万元)
PersonalMonthOutcome = [1, 2, 3, 4, 6]
for i in range(5):m = Final['个人月开销'] == i + 1Final.loc[m, '个人月开销'] = PersonalMonthOutcome[i]
因为属性个人月收入、个人月开销、家庭月收入、月刷卡额的范围不相同,区间大小不统一,对后期进行运算带来了诸多不便,故将属性的单位统一为“万元”,取每个区间的最大值代表这个区间。
(二)属性构建
在信用卡相关的征信工作中,主要从“历史信用,经济状况,收入稳定情况”三个方向判定客户的信用等级,他们所包含的变量如下。
历史信用 | 经济状况 | 收入稳定情况 |
---|---|---|
呆账 | 个人月收入 | 住家 |
瑕疵户 | 家庭月收入 | 职业 |
逾期 | 月刷卡额 | 年龄 |
强制停卡记录 | 个人月开销 | |
退票 | 借款余额 | |
拒往记录 |
对这3个方向所包含的变量进行打分,并赋予相应的权重,得到相加后的总分,得分越高,则风险越高。
相关代码如下。
# 属性值为1(是)的记为1分,属性值为2(否)的记为0分
def GetScore(x):if x == 2 :a = 0else:a = 1return(a)BuguserSocre = Final['瑕疵户'].apply(GetScore)
OverdueScore = Final['逾期'].apply(GetScore)
BaddebtScore = Final['呆账'].apply(GetScore)
CardstopedScore = Final['强制停卡记录'].apply(GetScore)
BounceScore = Final['退票'].apply(GetScore)
RefuseScore = Final['拒往记录'].apply(GetScore)
Final['历史信用风险'] = (BuguserSocre + OverdueScore * 2 + BaddebtScore * 3 + CardstopedScore * 3 + BounceScore * 3 + RefuseScore * 3)# 月刷卡额/个人月收入
CardpayPersonal = Final['月刷卡额'] / Final['个人月收入']
# 月刷卡额/家庭月收入
CardpayFamily = Final['月刷卡额'] / Final['家庭月收入']
EconomicScore = []
for i in range(Final.shape[0]):if CardpayPersonal[i] <= 1:if Final.loc[i, '借款余额'] == 1:EconomicScore.append(1)else:EconomicScore.append(0)if CardpayPersonal[i] > 1:if CardpayFamily[i] <= 1:if Final.loc[i, '借款余额'] == 1:EconomicScore.append(2)else:EconomicScore.append(1)if CardpayFamily[i] > 1:if Final.loc[i, '借款余额'] == 1:EconomicScore.append(4)else:EconomicScore.append(2)# 个人月开销/月刷卡额
OutcomeCardpay = Final['个人月开销'] / Final['月刷卡额']
OutcomeCardpayScore = []
for i in range(Final.shape[0]):if(OutcomeCardpay[i] <= 1):OutcomeCardpayScore.append(1)else:OutcomeCardpayScore.append(0)Final['经济风险情况'] = np.array(EconomicScore) + np.array(OutcomeCardpayScore)# 判断用户是否具有稳定的收入
HouseScore = []
for i in range(Final.shape[0]):if 3 <= Final.loc[i, '住家'] <= 5:HouseScore.append(0)else:HouseScore.append(1)JobScore = []
for i in range(Final.shape[0]):if(Final.loc[i, '职业'] <= 7 | Final.loc[i, '职业'] == 19 | Final.loc[i, '职业'] == 21):JobScore.append(2)if(Final.loc[i, '职业'] >= 8 & Final.loc[i, '职业'] <= 11):JobScore.append(1)if(Final.loc[i, '职业'] <= 18 & Final.loc[i, '职业'] >= 12 | Final.loc[i, '职业'] == 20 | Final.loc[i, '职业'] == 22):JobScore.append(0)AgeScore = []
for i in range(Final.shape[0]):if Final.loc[i, '年龄'] <= 2:AgeScore.append(1)else:AgeScore.append(0)Final['收入风险情况'] = np.array(HouseScore) + np.array(JobScore) + np.array(AgeScore)# 输出查看结果
Final[['历史信用风险', '经济风险情况', '收入风险情况']]
新属性构建后,对每个新属性的数据分布情况进行分析,其数据取值范围如下表所示。从数据中可以发现,3个新属性的取值范围差异比较大,为了消除取值差异带来的影响,需要对数据进行标准差标准化处理。
# 计算最大值和最小值
max_values = Final[['历史信用风险', '经济风险情况', '收入风险情况']].max()
min_values = Final[['历史信用风险', '经济风险情况', '收入风险情况']].min()# 创建一个新的DataFrame来存储结果
results = pd.DataFrame({'变量名称': ['最小值', '最大值'],'历史信用': [min_values['历史信用风险'], max_values['历史信用风险']],'经济风险': [min_values['经济风险情况'], max_values['经济风险情况']],'收入风险': [min_values['收入风险情况'], max_values['收入风险情况']]
})
# 设置索引,使得'变量名称'列成为行索引
results.set_index('变量名称', inplace=True)
# 打印结果
results
StdScaler = StandardScaler().fit(Final[['历史信用风险', '经济风险情况', '收入风险情况']])
ScoreModel = StdScaler.transform(Final[['历史信用风险', '经济风险情况', '收入风险情况']])
ScoreModel
四、分析与建模
构建信用卡高风险客户识别模型步骤:
- 对K-Means聚类算法进行参数寻优,确定合适的聚类数目。
- 在确定好聚类数目后根据构建的3个指标对客户进行聚类分群。
- 结合业务对每个客户群进行特征分析,分析其风险,并对每个客户群进行风险排名。
(一)聚类参数寻优
在K-Means聚类算法中,最关键的一个参数是k值,选择一个合适的k值有助于对数据进行更准确的分析。在评估Python聚类分群质量的指标中,当未知真实类别标签时,可以通过轮廓系数(Silhouette Coefficient)和簇内误差平方和(SSE)可以确定K-Means聚类算法中k的取值。
import numpy as np
from sklearn.cluster import KMeans
import collections
from sklearn import metrics
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'SimHei' # 正常显示中文# 参数寻优
inertia = []
silhouettteScore = []
# 计算聚类数目为2至9时的轮廓系数值和簇内误差平方和
for i in range(2, 10):km = KMeans(n_clusters=i, random_state=12).fit(ScoreModel)y_pred = km.predict(ScoreModel)center_ = km.cluster_centers_score = metrics.silhouette_score(ScoreModel, km.labels_)silhouettteScore.append([i, score])inertia.append([i, km.inertia_])# 绘制轮廓系数图
silhouettteScore = np.array(silhouettteScore)
plt.plot(silhouettteScore[: , 0], silhouettteScore[: , 1])
plt.title('轮廓系数值 - 聚类数目')
plt.show()
#绘制簇内误差平方和图
inertia = np.array(inertia)
plt.plot(inertia[: , 0], inertia[: , 1])
plt.title('簇内误差平方和 - 聚类数目')
plt.show()
通过轮廓系数值图和簇内误差平方和图可以看出,聚类数目为2至3和3至4时平均畸变程度较大,簇内误差平方和的值也在聚类数目为3和4时出现了较大的拐点,再结合信用卡客户类型的实际业务情况,认为将聚类数目定k=4时比较合适。
(二)构建信用卡高风险客户识别模型
# 构建K-Means聚类模型
KMeansModel = KMeans(n_clusters=4, random_state=12).fit(ScoreModel)
Cou = collections.Counter(KMeansModel.labels_)
print(Cou)
KMeansModel.cluster_centers_ # 查看中心点
center = KMeansModel.cluster_centers_
print(center) # 聚类中心
names = ['历史信用风险', '经济风险情况', '收入风险情况']
采用K-Means聚类算法进行客户分群,聚成4类,得到聚类分群的结果如下表。
聚类类别 | 类别个数 | 聚类中心 | ||
---|---|---|---|---|
ZL | ZR | ZF | ||
类别1 | 26856 | -0.22948566 | 0.14699398 | -0.83046269 |
类别2 | 3010 | 4.30068322 | 3.03600632 | 0.05968195 |
类别3 | 20419 | -0.22948566 | 0.06608303 | 1.08775743 |
类别4 | 9134 | -0.22948566 | -1.58040275 | -0.00959664 |
(三)信用卡客户风险分析
根据每种客户的类型特征,对每类客户进行归类,绘制信用卡客户分析的雷达图。
# 绘制雷达图
fig = plt.figure(figsize=(10, 8.5))
ax = fig.add_subplot(111, polar=True) # 定义polar参数为True,设置为极坐标格式
angles = np.linspace(0, 2 * np.pi, 3, endpoint=False)
angles = np.concatenate((angles, [angles[0]])) # 闭合
Linecolor = ['bo-', 'r+:', 'gD--', 'kv-.'] # 点线颜色
Fillcolor = ['b', 'r', 'g', 'k']
# 设置每个标签的位置
plt.xticks(angles, names)
for label, i in zip(ax.get_xticklabels(), range(0,len(names))):if i < 1:angle_text = angles[i] * (-180 / np.pi) + 90label.set_horizontalalignment('left')else:angle_text = angles[i] * (-180 / np.pi) - 90label.set_horizontalalignment('right')label.set_rotation(angle_text)
# 绘制ylabels
ax.set_rlabel_position(0)
# 设置雷达图参数
for i in range(4):data = np.concatenate((center[i], [center[i][0]])) # 闭合ax.plot(angles, data, Linecolor[i], linewidth=2) # 画线ax.fill(angles, data, facecolor=Fillcolor[i], alpha=0.25) # 填充颜色ax.set_title('客户分群雷达图', va='bottom') # 设定标题
ax.set_rlim(-2, 5) # 设置各指标的最终范围
ax.grid(True)
plt.legend(['类别1', '类别2', '类别3', '类别4'])
plt.savefig('../img/客户分群雷达图.jpg', dpi=720) #指定分辨率保存客户分群雷达图
plt.show()
4个类别在雷达图中按照面积的大小排序,从高到底依次是类别2、类别3、类别1和类别4。而面积越大代表着风险综合值越高,说明越容易发生信用卡违约情况。
总结出每个客户类型的显著特征如下表。(注:加粗字体表述最大值,斜体表示最小值,正常字体为次大值。)
客户类别 | 显著特征 | ||
---|---|---|---|
类别1 | 经济风险 | 收入风险 | |
类别2 | 历史信用风险 | 经济风险 | 收入风险 |
类别3 | 收入风险 | 历史信用风险 | |
类别4 | 经济风险 | 历史信用风险 |
通过上述特征分析的图表说明,每个客户类型都有显著的不同特征。基于该特征描述本案例的挖掘目标,定义客户类型为禁入类客户、高风险客户、潜在高风险客户、一般风险客户、一般客户,具体解释如下。
- 禁入类客户。这类客户历史信用糟糕,在历史信用风险上得分最高,且经济情况不佳,目前的经济收入稳定情况也不好。他们是银行的禁入客户,不少客户曾有过呆账、强制停卡记录,给银行的信用卡工作开展带来了麻烦,甚至造成了直接损失。
- 高风险客户。这类客户经济状况糟糕,历史行为中或多或少有瑕疵,收入稳定情况一般。因经济状况糟糕,这类客户可能无法按时按量还款,有极大的可能性给银行带来坏账或者逾期的情况。
- 潜在高风险客户。这类客户收入稳定情况糟糕,信用风险中等,经济状况中等。这一类客户因收入稳定性糟糕,缺乏稳定的收入来源,一旦发生工作变动,房屋变动,自身的资金无法周转开来就可能发生逾期,给银行信用卡工作开展造成一定的麻烦。
- 一般风险客户。这类客户的经济风险偏高,收入风险中等,是银行的关注对象,在某些特殊情况,例如,当资金周转困难时,有可能会导致延迟还款或逾期。
- 一般客户。这类客户历史行为记录良好,经济情况不错,收入稳定情况上佳。他们是银行的理想客户,能按时还款,收入稳定,刷卡符合自身收入水平。
根据客户特征分析与客户类型定义,得到客户风险排名如下表。
客户类型 | 排名 | 排名含义 |
---|---|---|
类型1 | 3 | 一般风险客户 |
类型2 | 1 | 禁入类客户及高风险客户 |
类型3 | 2 | 潜在高风险客户 |
类型4 | 4 | 一般客户 |
五、模型评价
聚类参数寻优时介绍了使用轮廓系数和簇内误差平方和进行K-Means聚类算法k参数的寻优情况,也从轮廓系数值图和簇内误差平方和图中看出,当聚类数目为4时平均畸变程度较大以及簇内误差平方和也较小。此外,当聚类数目为4时,能较为合理地对客户进行分类,说明模型的聚类效果较好。
根据对信用卡客户风险的分析,绘制不同客户类型客户数量占比饼图。
import collections
import matplotlib.pyplot as plt# 绘制不同客户类型客户数量饼图
TypeRate = collections.Counter(KMeansModel.labels_)
name_list = ['潜在高风险客户', '禁入类客户及高风险客户', '一般风险客户', '一般客户']
num_list = TypeRate.values()
print('查看各类客户数量:', num_list)
plt.figure(figsize=(8, 8))
# 绘制饼图
explode = [0, 0.1, 0, 0] # 分离禁入类客户和高风险客户
plt.pie(num_list, labels=name_list, autopct='%1.1f%%', pctdistance=1.15, explode=explode, labeldistance=1.05, startangle=90)
plt.title('不同客户类型客户数量占比饼图')
plt.savefig('../img/不同客户类型客户数量占比饼图.jpg', dpi=720) #指定分辨率保存饼图
plt.show()
从输出结果可以看出,一般风险客户26856位,占比45.2%;潜在高风险客户20419位,占比34.4%;一般客户9134位,占比15.4%;禁入类客户及高风险客户3010位,占比5.1%。禁入类客户是银行设置的黑名单,此类客户会不断积累银行的坏账数目,给银行带来直接的经济损失。高风险客户因月刷卡额过高,极有可能存在套现行为。大量现金流出,非常不利于银行业务的健康发展。
鉴于以上分析,综合后提出以下风控建议。
- 针对禁入类客户及高风险类客户,加强对其监督,密切关注还款情况,一旦出现逾期情况需要有专人跟进还款情况,一旦出现逾期不归还情况,立刻对其进行强制停卡处理,并及时上报公安机关备案。同时将其信用记录及时上报中国人民银行征信中心,与合作银行建立起黑名单机制,拒绝为黑名单内人员办理贷款,信用卡等信用相关的业务。
- 针对潜在高风险客户,对其提额申请慎重考虑,同时密切关注其每月刷卡额,一旦刷卡额过高,进行预警,提醒其分期减少资金压力。并定期发送其在本行的信用情况,提醒其保持良好的信用。
- 针对一般风险客户,允许其小幅提额请求。但是需要定时提醒其还款,以及关注目前个人信用程度情况。在刷卡额超出月均刷卡额时进行提醒。
- 针对一般客户,并不用对其设置严格管控要求,允许甚至鼓励其提额请求,定期推送信用卡相关业务介绍,以及有针对性的进行人性化服务,增加用户黏性。
小结
本案例主要目的是通过K-Means聚类算法判别出信用卡客户风险级别。重点介绍了数据探索、属性规约、属性构造。
- 建立客户风险K-Means聚类模型,分析了每一类客户的特征。
- 分析目前银行的信用卡客户结构。
- 提出了风险控制相关的建议。
附:以上文中的数据集及相关资源下载地址:
链接:https://pan.quark.cn/s/ace5291eefb1
提取码:w1Wc