消费金融案例

消费金融案例

一、单变量分析——用户首逾率增高问题
二、用户群组分析——对相同生命周期阶段的用户进行垂直分析
三、用户行为路径漏斗转化分析

一、单变量分析——用户首逾率增高

案例背景

日常监控发现某款消费贷产品首逾率有逐渐升高的趋势,需要把首逾率降下来以减少产品带来的损失,同时通过率降幅不能太明显。


分析目标
  • 通过数据探查分析制定出可以有效降低首逾率的策略。
分析思路
  • 策略分析时的基本思路就是还原这些有首逾表现的客户在申请时的数据(这个还原是指提取出客户在申请时点各个维度的数据,越多越好) ,然后利用这些数据去找出能够区分好坏客户的变量,制定策略。
分析流程
  1. 首先确定分析目标,制定出降低首逾率的风控策略;
  2. 提取数据分析样本,提取出更多维度的客户数据,比如客户年龄、地区、负债、月收入等等;
  3. 筛选有效变量,通过计算提升度,来筛选出较优的变量。
  4. 依据上一步拟定的策略,模拟一下策略执行后的首逾率降幅。

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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353