消费金融案例
一、单变量分析——用户首逾率增高问题
二、用户群组分析——对相同生命周期阶段的用户进行垂直分析
三、用户行为路径漏斗转化分析
一、单变量分析——用户首逾率增高
案例背景
日常监控发现某款消费贷产品首逾率有逐渐升高的趋势,需要把首逾率降下来以减少产品带来的损失,同时通过率降幅不能太明显。
分析目标
- 通过数据探查分析制定出可以有效降低首逾率的策略。
分析思路
- 策略分析时的基本思路就是还原这些有首逾表现的客户在申请时的数据(这个还原是指提取出客户在申请时点各个维度的数据,越多越好) ,然后利用这些数据去找出能够区分好坏客户的变量,制定策略。
分析流程
- 首先确定分析目标,制定出降低首逾率的风控策略;
- 提取数据分析样本,提取出更多维度的客户数据,比如客户年龄、地区、负债、月收入等等;
- 筛选有效变量,通过计算提升度,来筛选出较优的变量。
- 依据上一步拟定的策略,模拟一下策略执行后的首逾率降幅。
1、数据处理
import pandas as pd
import numpy as np
from sqlalchemy import create_engine
import matplotlib.pyplot as plt
%matplotlib inline
# 创建数据库连接
engine = create_engine('mysql+pymysql://frogdata05:Frogdata!1321@localhost:3306/froghd')
sql_cmd = "select * from load_datas"
# 执行sql语句,获取数据
dt = pd.read_sql(sql=sql_cmd, con=engine)
# 查看数据
dt.head()
# 修改字段名
dt.rename(columns={
"user_id":"用户id",
"age":"年龄",
"occupation":"职业",
"work_times":"工作时间",
"credit_score":"芝麻信用分",
"credit_level":"信用评级",
"credit_check_times":"近半年征信查询次数",
"credit_card_use_rate":"信用卡额度使用率",
"is_overdue":"是否逾期(1是,0否)"
},inplace=True)
dt.head()
# 查看数据维度
dt.shape
(56456, 9)
2、查看产品总体情况
# 查看逾期人数占比
dt['是否逾期(1是,0否)'].value_counts().plot.pie(autopct='%.2f%%', figsize=(4,4))
总体首逾率为:30.76%
3、筛选出有效变量
- 单变量分析:主要目的是筛选出好坏区分度较好的变量以便制定策略。
在消金公司的日常工作中,会有专门的数据团队,他们在不断的去获取加工很多可能对风险控制有帮助的数据,提供给风控团队,而风控人员就需要从这成千上万个变量中探查出能够控制逾期风险但同时又不会误拒很多好客户的变量;拿到数据的第一步就是针对每个变量单独分析,查看其对逾期的影响。
3.1 征信查询次数分组
# 定义一个征信分组的函数
def judge_zhengxin(nums):
try:
nums = int(nums)
if 0<=nums and nums<3:
return '1:[0,3)'
elif 3<=nums and nums<6:
return '2:[3,6)'
elif 6<=nums and nums<12:
return '3:[6,12)'
elif 12<=nums and nums<21:
return '4:[12,21)'
elif 21<=nums:
return '5:[21,无穷)'
except Exception as e:
return "6:缺失"
# 新建一个字段记录分组信息
dt['征信分组']=dt['近半年征信查询次数'].apply(judge_zhengxin)
dt.head()
# 分组统计各个分组的情况
dt_info = dt.groupby("征信分组").agg({
"用户id":'count',
"是否逾期(1是,0否)":sum
}).reset_index().rename(columns={"用户id":"区间客户数",
"是否逾期(1是,0否)":"区间逾期客户数"})
dt_info
# 计算区间用户占比
dt_info2['区间用户占比']=dt_info2["区间客户数"]/dt_info2["区间客户数"].sum()
# 计算区间首逾率
dt_info2["区间首逾率"]=dt_info2["区间逾期客户数"]/dt_info2["区间客户数"]
dt_info2
3.2 信用评级分组
# 定义一个征信分组的函数
def judge_pingji(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(judge_pingji)
dt.head()
# 分组统计各个分组的情况
dt_info2 = dt.groupby("信用评级分组").agg({
"用户id":'count',
"是否逾期(1是,0否)":sum
}).reset_index().rename(columns={"用户id":"区间客户数",
"是否逾期(1是,0否)":"区间逾期客户数"})
dt_info2
# 计算区间用户占比
dt_info2['区间用户占比']=dt_info2["区间客户数"]/dt_info2["区间客户数"].sum()
# 计算区间首逾率
dt_info2["区间首逾率"]=dt_info2["区间逾期客户数"]/dt_info2["区间客户数"]
dt_info2
3.3年龄分组
bins=[0,25,30,35,40,45,50]
dt['工作时间分组']=pd.cut(dt['年龄'],bins=bins,right=True)
dt.head()
dt_info3=dt.groupby('工作时间分组').agg({'用户id':'count',
'是否逾期(1是,0否)':sum})
.reset_index().rename(columns={'用户id':'区间客户数',
'是否逾期(1是,0否)':'区间逾期客户数'})
dt_info3
# 计算区间用户占比
dt_info3['区间用户占比']=dt_info3['区间客户数']/dt_info3['区间客户数'].sum()
# 区间首逾率
dt_info3['区间首逾率']=dt_info3['区间逾期客户数']/dt_info3['区间客户数']
dt_info3
4、计算提升度
在进行变量分析之后,这时我们就要从中筛选中较为有效的变量了,这里涉及到一个衡量变量是否有效的指标,提升度。
- 提升度:通俗的来说就是衡量拒绝最坏那一部分的客户之后,对整体的风险控制的提升效果。提升度越高,说明该变量可以更有效的区分好坏客户,能够更少的误拒好客户。
- 计算公式:提升度=最坏分箱的首逾客户占总首逾客户的比例 /该分箱的区间客户数占比
dt_info
dt_info2
dt_info3
- 最坏分箱:设定为首逾率最高的一组
4.1征信查询次数提升度
# 获取最大值所在行
index1_max=dt_info["区间首逾率"].idxmax()
# dt_info.iloc[dt_info["区间首逾率"].idxmax()]
bad_rate = dt_info.iloc[index1_max]["区间逾期客户数"]/dt_info["区间逾期客户数"].sum()
num_rate = dt_info.iloc[index1_max]["区间客户数"]/dt_info["区间客户数"].sum()
deg1=bad_rate/num_rate
print('征信查询次数的提升度:{:.2f}'.format(deg1))
征信查询次数的提升度:1.95
4.2信用评级的提升度
# 获取最大值所在行
index2_max=dt_info2["区间首逾率"].idxmax()
bad_rate2 = dt_info2.iloc[index2_max]["区间逾期客户数"]/dt_info2["区间逾期客户数"].sum()
num_rate2 = dt_info2.iloc[index2_max]["区间客户数"]/dt_info2["区间客户数"].sum()
deg2=bad_rate2/num_rate2
print('信用评级的提升度:{:.2f}'.format(deg2))
信用评级的提升度:1.71
4.3年龄的提升度
# 年龄的提升度
# 获取最大值所在行
index3_max=dt_info3["区间首逾率"].idxmax()
bad_rate3 = dt_info3.iloc[index3_max]["区间逾期客户数"]/dt_info3["区间逾期客户数"].sum()
num_rate3 = dt_info3.iloc[index3_max]["区间客户数"]/dt_info3["区间客户数"].sum()
deg3=bad_rate3/num_rate3
print('年龄的提升度:{:.2f}'.format(deg3))
年龄的提升度:1.08
4.4变量筛选
对比以上三个变量中,征信查询次数的提升度最高
5、制定策略:
- 通过上一步的单变量分析,我们筛出了’征信查询次数’作为提升度最高的变量。现在我们看一下如果将这个个变量的最坏分箱的客户都拒绝之后,对整体逾期的影响。
- 这个影响就是指假设我们将‘征信总查询次数>=21的3213位客户全部拒绝’之后,剩下的客户逾期率相比拒绝之前的逾期率降幅是多少。
# 即假设我们设置征信查询次数这个风控指标,并且把查询次数21次以上的都拒绝掉
# 然后来看首逾率是多少,有降低多少
# 逾期率 = 逾期人数 / 总人数
new_overdue_nums = dt_info2["区间逾期客户数"].sum() - dt_info2.iloc[dt_info2["区间首逾率"].idxmax()]["区间逾期客户数"]
new_overdue_rate = new_overdue_nums / dt_info[dt_info.index!=index1_max]['区间客户数'].sum()
old_overdue_nums = dt_info2["区间逾期客户数"].sum()
old_overdue_rate = old_overdue_nums / dt_info2["区间客户数"].sum()
print('原来逾期率是:{:.2%}'.format(old_overdue_rate))
print('新的逾期率是:{:.2%}'.format(new_overdue_rate))
print('-'*20)
print('新的策略逾期率比原来提升了:{:.2%}'.format(old_overdue_rate-new_overdue_rate))
原来逾期率是:30.76%
新的逾期率是:24.63%
--------------------
新的策略逾期率比原来提升了:6.13%
二、用户群组分析——对相同生命周期阶段的用户进行垂直分析
群组分析:
通过字面意思即可理解,群组分析法就是按某个特征对数据进行分组,通过分组比较,得出结论的方法。
简单举例:将用户数据按性别特征,可以分组为男性和女性,将用户注册时间作为特征,按月份分组,可以分为1月,2月,3月进行环比比较,以及对他的留存率、活跃率、付费率等进行分析。
群组分析的作用:
1.对处于相同生命周期阶段的用户进行垂直分析,从而比较得出相似群体随时间的变化。
2.通过比较不同的同期群,可以从总体上看到,应用的表现是否越来越好了。从而验证产品改进是否取得了效果。
1.案例目的
通过用户的订单消费情况,对比同一月份的新用户留存率的变化趋势,以及不同时间期的新用户在同周期时的留存率情况
2.案例过程
- 数据一共有7个字段
orderid:订单编号
orderdate:订单日期
userid:用户编号
totalcharges:消费金额
因为在这里分析的是用户留存率情况,其他字段暂时不需要
2.1 数据处理
需要从月份维度进行分析,先根据用户订单编号增加一个年月字段,再选择用户最小消费月份为首次消费时间
# 增加年月字段
df['orderperiod'] = df.orderdate.apply(lambda x:x.strftime("%Y-%m"))
# 增加最早消费时间
# 根据索引对齐,需先将orderid设置为索引列
df.set_index("userid",inplace = True)
df["chortgroup"] = df.groupby("userid").agg({"orderperiod":"min"}) #orderperiod:用户消费月份
df.reset_index(inplace = True) # chortgroup:用户最早消费时间(出现的时间点)
2.2 按用户按照最早消费时间分组并编号
cohorts = df.groupby(['chortgroup','orderperiod',]).agg({'userid':pd.Series.nunique,'orderid':pd.Series.nunique,'totalcharges':'sum'})
cohorts.rename(columns={"userid":"totalusers",
"orderid":"totalorders"},inplace=True)
# 定义编号函数
def cohorts_period(x):
x['cohortperiod'] = np.arange(len(x)) + 1
return x
# 编号
#cohorts.groupby('chortgroup') 后 每一个小群组分别是以orderperiod为index的群组
cohorts = cohorts.groupby('chortgroup').apply(cohorts_period)
cohorts = cohorts.reset_index().set_index(['chortgroup','cohortperiod']) (出现的时间点)
cohorts
- 上图中,已经按照用户首月时间对随后几个月的消费情况进行了统计
- 但其中可能是数据缺失的原因,消费金额列出现为0的情况,但这并不影响我们对留存率的计算
2.3 计算用户留存率
每个月的首月用户数
cohort_group_size = cohorts["totalusers"].groupby(level=0).first()
chortgroup
2009-01 20
2009-02 28
2009-03 12
2009-04 8
Name: totalusers, dtype: int64
计算每月留存率
cohorts_liucun = cohorts["totalusers"].unstack(0).div(cohort_group_size,axis = 1).fillna(0)
cohorts_liucun
绘图
# 绘图
plt.rcParams["font.size"] = 14
plt.rcParams["font.sans-serif"] = ["SimHei"]
fig, ax = plt.subplots(1,figsize = (10,6),dpi = 80)
ax.plot(cohorts_liucun)
ax.set_xticks(range(1,13))
ax.set_title('留存率')
ax.grid(0.5)
ax.legend(cohorts_liucun.columns)
plt.show()
# 热力图
import seaborn as sns
sns.set(style="white")
plt.rcParams["font.size"] = 16
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.figure(figsize=(12,6))
plt.title("用户留存率:热力图")
sns.heatmap(cohorts_liucun.T,mask=cohorts_liucun.T.isnull(),annot=True,fmt=".0%")
plt.show()