(金融)线上消费贷风控优化(单变量分析)与用户留存(同期群分析)项目——数据分析

(金融)线上消费贷风控优化(单变量分析)与用户留存项目(同期群分析)——数据分析

一、首逾率单变量分析

案例背景:

    日常监控发现某款消费贷产品首逾率有逐渐升高的趋势,需要把首逾率降下来以减少产品带来的损失。

分析目标:

  • 通过数据探查分析制定出可以有效降低首逾率的策略。

分析思路:

  • 因为我们所要分析的策略时将要在客户申请时用来判断客户是否会逾期的条件,所以策略分析时的基本思路就是还原这些有首逾表现的客户在申请时的数据(这个还原是指提取出客户在申请时点各个维度的数据,越多越好) ,然后利用这些数据去找出能够区分好坏客户的变量,制定策略。

申请类指标:

  • 审批通过率: 审批通过人数/申请总人数

逾期类指标:

  • 首逾率:首期逾期的合同数/首期到了应还款的总合同数
  • Vintage-账龄:指公司尚未收回的应收账款的时间长度
  • Vintage30+:每个申请月份在随后的月份逾期30+的贷款余额占该审批月份的放款额的比例。
  • mob4vintage30+:某一月内贷款合同签署4个月后时,当前逾期超过30天的合同的未还金额/总放款合同金额。
import pandas as pd
import numpy as np

import sqlalchemy

1.0 数据读取

# 读取数据
engine = sqlalchemy.create_engine('mysql+pymysql://id:**********@xxx.xxx.xx.xx:3306/db')

sql_cmd = 'select * from load_datas'

# 执行sql语句,获取数据
dt=pd.read_sql(sql=sql_cmd,con=engine)
dt
user_idageoccupationwork_timescredit_scorecredit_levelcredit_check_timescredit_card_use_rateis_overdue
0122个体户45.0650.0A230.000
1228职员34.0680.0A330.320
2339其他113.0530.0A320.050
3424职员44.0800.0A350.010
4530职员82.0NaNA280.000
..............................
564515645240NoneNaNNaN缺失缺失NaN0
564525645321NoneNaNNaN缺失缺失NaN0
564535645443NoneNaNNaN缺失缺失NaN0
564545645520NoneNaNNaN缺失缺失NaN0
564555645633NoneNaNNaN缺失缺失NaN1

56456 rows × 9 columns

dt.rename(columns={'user_id':'用户id',
                   'age':'年龄',
                   'occupation':'职业',
                   'work_times':'工作时间',
                   'credit_score':'芝麻信用分',
                   'credit_level':'信用评级',
                   'credit_check_times':'近半年征信查询次数',
                   'credit_card_use_rate':'信用卡额度使用率',
                   'is_overdue':'是否有逾期记录',
},inplace=True)
dt.head()
用户id年龄职业工作时间芝麻信用分信用评级近半年征信查询次数信用卡额度使用率是否有逾期记录
0122个体户45.0650.0A230.000
1228职员34.0680.0A330.320
2339其他113.0530.0A320.050
3424职员44.0800.0A350.010
4530职员82.0NaNA280.000
dt.shape
(56456, 9)

1.1统计总体逾期情况

# 计算总体首逾率 = 首逾客户数/总申请客户数
总体首逾率=dt.是否有逾期记录.sum()/dt.是否有逾期记录.count()
总体首逾率
0.307584667705824

1.2 筛选出有效变量:

    这里用到单变量分析的方法,单变量分析的主要目的是筛选出好坏区分度较好的变量以便制定策略。在消金公司的日常工作中,会有专门负责爬取变量和计算加工变量数据的团队,他们在不断的去获取加工很多可能对风险控制有帮助的数据提供给我们风控团队,而我们风控人员就需要从这成千上万个变量中探查出能够控制逾期风险但同时又不会误拒很多好客户的变量。

    拿到数据的第一步就是针对每个变量单独分析,查看其对逾期的影响。

1.2.1 征信查询次数分组
# 定义一个征信分组的函数
def group_check(n):
    try:
        n=int(n)
        if n>=0 and n<3:
            return '1:[0,3)'
        elif n>=3 and n<6:
            return '2:[3,6)'
        elif n>=6 and n<12:
            return '3:[6,12)'
        elif n>=12 and n<21:
            return '4:[12:21'
        elif n>=21:
            return '5:[21,+∞)'
    except Exception as e:
        return '6:缺失'
# 新建一个字段记录分组信息
dt['征信分组']=dt.近半年征信查询次数.apply(group_check)
dt.head()
用户id年龄职业工作时间芝麻信用分信用评级近半年征信查询次数信用卡额度使用率是否有逾期记录征信分组
0122个体户45.0650.0A230.0005:[21,+∞)
1228职员34.0680.0A330.3205:[21,+∞)
2339其他113.0530.0A320.0505:[21,+∞)
3424职员44.0800.0A350.0105:[21,+∞)
4530职员82.0NaNA280.0005:[21,+∞)
# 分组统计各个分组的情况
# 区间用户占比、未逾期客户数要另外计算
dt_info=dt.groupby('征信分组').agg({'用户id':'count',
                                '是否有逾期记录':sum}).reset_index().rename(columns={'用户id':'区间客户数',
                                                                              '是否有逾期记录':'区间逾期客户数'})
dt_info
征信分组区间客户数区间逾期客户数
01:[0,3)146673352
12:[3,6)154694157
23:[6,12)153164786
34:[12:2165772739
45:[21,+∞)32131923
56:缺失1214408
# 区间客户占比、未逾期客户数要另外计算
dt_info['区间未逾期客户数']=dt_info.区间客户数-dt_info.区间逾期客户数
dt_info['区间客户占比']=dt_info.区间客户数/dt_info.区间客户数.sum()
dt_info['区间首逾率']=dt_info.区间逾期客户数/dt_info.区间客户数
dt_info
征信分组区间客户数区间逾期客户数区间未逾期客户数区间客户占比区间首逾率
01:[0,3)146673352113150.2597950.228540
12:[3,6)154694157113120.2740010.268731
23:[6,12)153164786105300.2712910.312484
34:[12:216577273938380.1164980.416451
45:[21,+∞)3213192312900.0569120.598506
56:缺失12144088060.0215030.336079
1.2.2 信用评级分组
dt.信用评级.unique()
array(['A', 'AA', 'B', 'C', 'D', 'E', 'HR', 'NC', '缺失'], dtype=object)
# 定义一个征信分组的函数
def group_score(level):
    if level=='A':
        return 'A'
    elif level=='AA':
        return 'AA'
    elif level in ('B','C','D'):
        return 'BCD'
    elif level in ('E','HR','NC'):
        return 'ERC'
    else:
        return '缺失'
# 新建一个字段记录分组信息
dt['信用评级分组']=dt.信用评级.apply(group_score)
dt.head()
用户id年龄职业工作时间芝麻信用分信用评级近半年征信查询次数信用卡额度使用率是否有逾期记录征信分组信用评级分组
0122个体户45.0650.0A230.0005:[21,+∞)A
1228职员34.0680.0A330.3205:[21,+∞)A
2339其他113.0530.0A320.0505:[21,+∞)A
3424职员44.0800.0A350.0105:[21,+∞)A
4530职员82.0NaNA280.0005:[21,+∞)A
# 分组统计各个分组的情况
# 区间用户占比、未逾期客户数要另外计算
dt_info2=dt.groupby('信用评级分组').agg({'用户id':'count',
                                   '是否有逾期记录':sum}).reset_index().rename(columns={'用户id':'区间客户数',
                                                                                '是否有逾期记录':'区间逾期客户数'})
dt_info2
信用评级分组区间客户数区间逾期客户数
0A3369851
1AA3784640
2BCD163205920
3ERC80604251
4缺失249235703
# 区间用户占比、未逾期客户数要另外计算
dt_info2['区间未逾期客户数']=dt_info2.区间客户数-dt_info2.区间逾期客户数
dt_info2['区间客户占比']=dt_info2.区间客户数/dt_info2.区间客户数.sum()
dt_info2['区间首逾率']=dt_info2.区间逾期客户数/dt_info2.区间客户数
dt_info2
信用评级分组区间客户数区间逾期客户数区间未逾期客户数区间客户占比区间首逾率
0A336985125180.0596750.252597
1AA378464031440.0670260.169133
2BCD163205920104000.2890750.362745
3ERC8060425138090.1427660.527419
4缺失249235703192200.4414590.228825

1.3 计算提升度:

    在进行变量分析之后,这时我们就要从中筛选中较为有效的变量了,这里涉及到一个衡量变量是否有效的指标,提升度。

提升度:通俗的来说就是衡量拒绝最坏那一部分的客户之后,对整体的风险控制的提升效果。
提升度越高,说明该变量可以更有效的区分好坏客户,能够更少的误拒好客户。

计算公式:提升度=最坏分箱的首逾客户占总首逾客户的比例 /该分箱的区间客户数占比

  • 例如:上表中征信总查询次数的最坏分箱提升度就是(1923/17365)/(3213/56456)=11%/5.69%=1.93
    提升度这个指标一般来说都是用来一批变量分析里做相对比较,很多时候都是在有限的变量里尽可能选提升度更高的变量来做策略。

如下,通过对所有变量的提升度进行倒序排列,发现个人征信总查询次数和客户信用评级的提升度最高,达到1.93和1.71。

# 获取最大值所在行,argmax 也可以,但是会有警告
dt_info.区间首逾率.idxmax()
4
# 计算各个指标的提升度
# 提升度=最坏分箱的首逾客户占总首逾客户的比例 /该分箱的区间客户数占比
# 这里最坏分箱就是根据征信分组后,首逾率最高的一组
worst_rate=dt_info.loc[dt_info.区间首逾率.idxmax()]['区间逾期客户数']/dt_info.区间逾期客户数.sum()
num_rate=dt_info.loc[dt_info.区间首逾率.idxmax()]['区间客户数']/dt_info.区间客户数.sum()
worst_rate/num_rate
1.9458254325820934
dt_info2.区间首逾率.idxmax()
3
# 信用评级的提升度
# dt_info2.iloc[dt_info2["区间首逾率"].idxmax()]
worst_rate2=dt_info2.loc[dt_info2.区间首逾率.idxmax()]['区间逾期客户数']/dt_info2.区间逾期客户数.sum()
num_rate2=dt_info2.loc[dt_info2.区间首逾率.idxmax()]['区间客户数']/dt_info2.区间客户数.sum()
worst_rate2/num_rate2
1.7147127611157038

1.4 制定优化策略:

    通过上一步的单变量分析,我们筛出了’征信查询次数’、‘信用评级’这两个提升度最高的变量。现在我们看一下如果将这两个变量的最坏分箱的客户都拒绝之后,对整体逾期的影响。
这个影响就是指假设我们将‘征信总查询次数>=21的3213位客户全部拒绝’之后,剩下的客户逾期率相比拒绝之前的逾期率降幅是多少。

1.4.1 依据征信查询次数优化风控策略
# 即假设我们设置征信查询次数这个风控指标,并且把查询次数21次以上的都拒绝掉
# 然后来看首逾率是多少,有降低多少
# 逾期率 = 逾期人数 / 总人数
check_new_yuqi_kehu=dt_info.区间逾期客户数.sum()-dt_info.loc[dt_info.区间首逾率.idxmax()]['区间逾期客户数']
check_new_yuqi_rate=check_new_yuqi_kehu/dt_info.区间客户数.sum()
check_new_yuqi_rate
0.27352274337537197
check_old_yuqi_rate=dt_info.区间逾期客户数.sum()/dt_info.区间客户数.sum()
check_old_yuqi_rate
0.307584667705824
# 新的逾期率下降了 3.41%
check_optimiz=check_new_yuqi_rate-check_old_yuqi_rate
check_optimiz
-0.03406192433045202
1.4.2 依据芝麻信用分优化风控策略
# 即假设我们设置征芝麻信用分这个风控指标,并且把芝麻信用评级为ERC的人群都拒绝掉
# 然后来看首逾率是多少,有降低多少
# 逾期率 = 逾期人数 / 总人数
score_new_yuqi_kehu=dt_info2.区间逾期客户数.sum()-dt_info2.loc[dt_info2.区间首逾率.idxmax()]['区间逾期客户数']
score_new_yuqi_rate=score_new_yuqi_kehu/dt_info2.区间客户数.sum()
score_new_yuqi_rate
0.23228709083179822
score_old_yuqi_rate=dt_info2.区间逾期客户数.sum()/dt_info2.区间客户数.sum()
score_old_yuqi_rate
0.307584667705824
# 新的逾期率下降了 7.52%
score_optimiz=score_new_yuqi_rate-score_old_yuqi_rate
score_optimiz
-0.07529757687402577

二、用户留存率的同期群分析

什么是同期群分析:

  • 用户在产品使用中都有一个用户行为流程,不同时期的用户表现情况可能不一样,群组分析的主要目的是分析相似群体随时间的变化,核心就是对比、分析不同时间群组的用户,在相同周期下的行为差异, 所以也称同期群分析。
    在这里插入图片描述
    同期群分析的作用:
  • 对处于相同生命周期阶段的用户进行垂直分析(横向比较),从而比较得出相似群体随时间的变化(下图可以看到客单价随着时间推移在逐渐降低)。
  • 通过比较不同的同期群,可以从总体上看到,应用的表现是否越来越好了。从而验证产品改进是否取得了效果。(下图可以看到客单价在逐渐提高)
    在这里插入图片描述

2.0 读取数据

engine=sqlalchemy.create_engine('mysql+pymysql://frogdata05:Frogdata!1321@localhost:3306/froghd')
# 执行sql语句,获取数据
sql_cmd='select * from groups_data '
df=pd.read_sql(sql=sql_cmd,con=engine)
#数据显示有用户的OrederId、OrderDate、UserId、TotalCharges(我们只会用到这四个),
#其他的字段没有用
df[['orderid','orderdate','userid','totalcharges']].head()
orderidorderdateuseridtotalcharges
02622009-01-111250.67
12782009-01-121326.60
22942009-01-131438.71
33012009-01-141553.38
43022009-01-151614.28
# 生成一个新字段,用户订单月份
df['order_month']=df.orderdate.apply(lambda x:x.strftime('%Y-%m'))
df.head()
orderidorderdateuseridtotalchargescommonldpupldpickupdateorder_month
02622009-01-111250.67TRQKD2.02009-01-122009-01
12782009-01-121326.604HH2S3.02009-01-202009-01
22942009-01-131438.713TRDC2.02009-02-042009-01
33012009-01-141553.38NGAZJ2.02009-02-092009-01
43022009-01-151614.28FFYHD2.02009-02-092009-01

2.1构建用户群组并聚合总用户数、总订单数

# 设置userid为索引
df.set_index('userid',inplace= True)
# 这里的level=0表示第一层索引即userid,并且每次分组之后都会形成很多个dataframe
# 按照每个用户的订单的最早时期,生成用户群组
df['cohort_group']=df.groupby(level=0)['orderdate'].min().apply(lambda x:x.strftime('%Y-%m'))
df.reset_index(inplace=True)
df.head()
useridorderidorderdatetotalchargescommonldpupldpickupdateorder_monthcohort_group
0122622009-01-1150.67TRQKD2.02009-01-122009-012009-01
1132782009-01-1226.604HH2S3.02009-01-202009-012009-01
2142942009-01-1338.713TRDC2.02009-02-042009-012009-01
3153012009-01-1453.38NGAZJ2.02009-02-092009-012009-01
4163022009-01-1514.28FFYHD2.02009-02-092009-012009-01
# 根据用户群组和月份字段进行分组
group_month=df.groupby(['cohort_group','order_month'])
# 求每个用户群下每一个月份的用户数量、订单数量、购买金额
cohorts=group_month.agg({'userid':pd.Series.nunique,
                        'orderid':pd.Series.nunique,
                        'totalcharges':sum})
cohorts
# 重命名
cohorts.rename(columns={'userid':'totaluser',
                        'orderid':'totalorders'},inplace=True)
cohorts.head()
totalusertotalorderstotalcharges
cohort_grouporder_month
2009-012009-012021326.44
2009-0210100.00
2009-038110.00
2009-05440.00
2009-022009-0228280.00

2.2 对每个用户群组重新编号,并统计各群组用户总数

# 把每个群组继续购买的日期字段进行改变
def cohort_period(df):
    # 给首次购买日期进行编号,第二次购买为2,第三次购买为3
    df['cohort_period']=np.arange(len(df))+1
    return df
# 注意的是apply后面传入的是一个个dataframe
cohorts=cohorts.groupby(level=0).apply(cohort_period)
cohorts.head()
# level=0表示第一级索引
totalusertotalorderstotalchargescohort_period
cohort_grouporder_month
2009-012009-012021326.441
2009-0210100.002
2009-038110.003
2009-05440.004
2009-022009-0228280.001
# 得到每个群组的用户量
# 重新设置索引
cohorts.reset_index(inplace=True)
cohorts.set_index(['cohort_group','cohort_period'],inplace=True)
cohorts.head()
order_monthtotalusertotalorderstotalcharges
cohort_groupcohort_period
2009-0112009-012021326.44
22009-0210100.00
32009-038110.00
42009-05440.00
2009-0212009-0228280.00
# 得到每个群组的用户量,就是第一天的用户数据量,用作留存率的分母
cohort_group_size=cohorts['totaluser'].groupby(level=0).first()
cohort_group_size.head()
cohort_group
2009-01    20
2009-02    28
2009-03    12
2009-04     8
Name: totaluser, dtype: int64

2.3 统计留存率

# 计算每个群组的留存
# unstack 是把行索引index转化为列column,https://www.jianshu.com/p/5ab1019836c9
cohorts_v=cohorts['totaluser'].unstack(0)
cohorts_v
cohort_group2009-012009-022009-032009-04
cohort_period
120.028.012.08.0
210.05.06.05.0
38.04.0NaNNaN
44.0NaNNaNNaN
# 计算留存
user_retention=cohorts_v/cohort_group_size
user_retention
cohort_group2009-012009-022009-032009-04
cohort_period
11.01.0000001.01.000
20.50.1785710.50.625
30.40.142857NaNNaN
40.2NaNNaNNaN

2.4绘图展示

# 折线图展示
import matplotlib.pyplot as plt
import matplotlib as mpl
pd.set_option('max_column',50)
mpl.rcParams['lines.linewidth']=2
%matplotlib inline
user_retention[['2009-01','2009-02','2009-03']].plot(figsize=(6,4))
plt.title('user retention')
plt.xticks(np.arange(1,13,1))
plt.xlim(1,12)
plt.ylabel('%of cohort')
Text(0, 0.5, '%of cohort')

在这里插入图片描述

# 热力图展示
import seaborn as sns
sns.set(style='whitegrid')

plt.figure(figsize=(10,5))
plt.title('heat map:user_retention')
sns.heatmap(user_retention.T,mask=user_retention.T.isnull(),annot=True,annot_kws={'color':'tan','weight':'bold'},fmt='.2%')
<matplotlib.axes._subplots.AxesSubplot at 0x7f05ae875c90>

在这里插入图片描述

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值