消费金融案例

案例目录:

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

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

单变量分析:

  • 单变量分析的目的是,通过对数据的整理、加工、组织和展示,并计算反应数据的集中趋势和离散程度的指标,对变量分布的特征和规律进行刻画和描述。不同类型的变量需要使用不同的方法和指标。
  • 单变量分析又称“单变量统计分析”,就是在一个时间点上对某一变量所进行的描述和统计,因而又可以分为单变量描述统计和单变量推论统计两种方式。

1.案例背景

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


2.分析目标

在客户申请时用来判断客户是否会逾期的条件,通过数据探查分析制定出可以有效降低首逾率的策略。

3.分析思路

在客户申请时,还原客户各个维度的数据,用单变量分析法从各个维度进行分析,找到区分好坏客户的变量,制定策略。

3.1 数据观察处理

  • 对字段进行更名处理


3.2 总体逾期率情况

dt['是否逾期(1是,0否)'].sum()/dt.shape[0]
[out]:0.307584667705824
  • 总体预期率达到30%,属于非常高的情况,下面需要使用单变量分析法,从各个维度单独分析,查看对预期的影响。

3.3 筛选有效变量

3.3.1 征信查询次数影响

首先对客户的征信查询次数字段进行分组,统计每个分组区间的逾期人数。

#征信查询次数分组
def judge_zhengxin(x):
    try:
        x = int(x)
        if 0 <= x and x < 3:
            return '1:[0,3)'
        elif 3 <= x and x < 6:
            return '2:[3,6)'
        elif 6 <= x and x < 12:
            return '3:[6,12)'
        elif 12 <= x and x < 21:
            return '4:[12,21)'
        elif 21 <= x:
            return '5:[21,∞)'
    # 如果 x 不为int类型,则返回错误,即输出"缺失"
    except Exception as e:
        return '6:缺失'
# 对征信查询次数字段进行分组
dt['征信分组'] = dt['近半年征信查询次数'].apply(lambda x: judge_zhengxin(x))
# 聚合统计
dt_info_zhengxin = dt.groupby('征信分组').agg({'用户id':'count','是否逾期(1是,0否)':'sum'}).reset_index().rename(columns = {'用户id':"区间客户数","是否逾期(1是,0否)":"逾期人数"})
dt_info_zhengxin

接下来计算区间用户占比、首逾率情况:

dt_info_zhengxin['区间用户占比'] = dt_info_zhengxin['区间客户数'].div(dt_info_zhengxin['区间客户数'].sum())
dt_info_zhengxin['首逾率'] = dt_info_zhengxin['逾期人数'] / dt_info_zhengxin['区间客户数']
# 绘图展示
plt.figure(figsize = (15,6))
plt.rcParams["font.size"] = 15

plt.subplot(121)
dt_info_zhengxin["区间客户数"].plot(kind = "pie",autopct='%.2f%%')
plt.legend(dt_info_zhengxin["征信分组"],loc = 3, fontsize = 11)

plt.subplot(122)
plt.title("首逾率")
dt_info_zhengxin["首逾率"].plot(kind = "bar")
plt.xticks(range(6),dt_info_zhengxin["征信分组"],rotation =0)

plt.tight_layout(pad = 2)
plt.show()
  • 大部分客户查询次数均在12次以下,其中0-3,3-6,6-12次区间人数相差不大;
  • 首逾率基本跟随征信查询次数增长而增长,查询次数越大,首逾率越高,其中查询21+人次的总体首逾率达到了惊人的60%左右;
  • 可以初步判断征信查询次数与首逾率是呈正相关的。
3.3.2 信用评级影响

将信用评级划分为5个分组,并进行聚合统计

def judge_pingji(x):
    if x =='A':
        return 'A'
    elif x == 'AA':
        return 'AA'
    elif x in ['B','C','D']:
        return 'BCD'
    elif x in ['E','HR','NC']:
        return 'ERC'
    else:
        return '缺失'
# 分组
dt['信用评级分组'] = dt['信用评级'].apply(lambda x:judge_pingji(x))
dt_info_pingji = dt.groupby('信用评级分组').agg({'用户id':'count','是否逾期(1是,0否)':'sum'}).reset_index().rename(columns = {'用户id':"区间客户数",'是否逾期(1是,0否)':'逾期人数'})
dt_info_pingji['区间客户占比'] = dt_info_pingji['区间客户数']/dt_info_pingji['区间客户数'].sum()
dt_info_pingji['首逾率'] = dt_info_pingji['逾期人数']/dt_info_pingji['区间客户数']
#绘图
plt.figure(figsize = (15,6))
plt.rcParams["font.size"] = 15

plt.subplot(121)
dt_info_pingji["区间客户数"].plot(kind = "pie",autopct='%.2f%%')
plt.legend(dt_info_pingji["信用评级分组"],loc = 3, fontsize = 13)

plt.subplot(122)
plt.title("首逾率")
dt_info_pingji["首逾率"].plot(kind = "bar")
plt.xticks(range(5),dt_info_pingji["信用评级分组"],rotation =0)

plt.tight_layout(pad = 2)
  • 除开缺失字段客户数,客户占比最高为评级BCD的用户,其次ERC,整体用户评级都比较低;
  • ERC评级用户首逾率最高达到52%左右,其次为BCD评级用户首逾率在36%;
3.3.3 年龄影响

先对年龄进行分组,并聚合统计分析

bins = [0,25,30,35,40,45,100]
labels = ["25岁及以下","26-30","31-35","36-40","41-45","45+"]
dt["年龄分组"] = pd.cut(dt["年龄"],bins = bins, labels = labels)

dt_info_age = dt.groupby("年龄分组").agg({"用户id":"count","是否逾期(1是,0否)":"sum"}).rename(columns={"用户id":"区间客户数","是否逾期(1是,0否)":"逾期人数"}).reset_index()
dt_info_age["区间客户占比"] = dt_info_age["区间客户数"]/dt_info_age["区间客户数"].sum(0)
dt_info_age["首逾率"] = dt_info_age["逾期人数"] / dt_info_age["区间客户数"]
plt.figure(figsize = (15,6))
plt.rcParams["font.size"] = 15

plt.subplot(121)
dt_info_age["区间客户数"].plot(kind = "pie",autopct='%.2f%%')
plt.legend(dt_info_age["年龄分组"],loc = 3, fontsize = 13)

plt.subplot(122)
plt.title("首逾率")
dt_info_age["首逾率"].plot(kind = "bar")
plt.xticks(range(5),dt_info_age["年龄分组"],rotation =0)

plt.tight_layout(pad = 2)
plt.show()
  • 客户中25岁及以下相对较多,其余人数占比均差不多
  • 首逾率情况都在30%左右,较为均衡
  • 初步判断首逾率与年龄关系不大

3.4 计算提升度

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

提升度:通俗的来说就是衡量拒绝最坏那一部分的客户之后,对整体的风险控制的提升效果。 提升度越高,说明该变量可以更有效的区分好坏客户,能够更少的误拒好客户。
计算公式:提升度 = 最坏分箱的首逾客户占总首逾客户的比例 /该分箱的区间客户数占比
例如:上表中征信总查询次数中,最坏分箱为4:[21,∞),首逾客户为1923,总逾期客户为17365,该分箱区间客户数占比为5.69%,所以提升度就是(1923/17365)/5.69%=11%/5.69%=1.94。
注意:
提升度不是和逾期率成正比的,提升度说明的是这个变量下面有很大一部分人是会逾期的,它的大小是告诉我们这个变量有必要写到放贷策略里面去做优化,但是如果这部分人占整体的比例不高,那最终对逾期率影响也不大,但确实是能为企业减少风险。

计算征信查询次数的提升度:(1.94)

p1 = dt_info_zhengxin.iloc[dt_info_zhengxin['首逾率'].idxmax()]['逾期人数']/dt_info_zhengxin['逾期人数'].sum()
p2 = dt_info_zhengxin.iloc[dt_info_zhengxin['首逾率'].idxmax()]['区间用户占比']
up_rate1 = p1/p2
up_rate1 
# 提升度
[out]:1.9458254325820934

计算信用评级的提升度:(1.71)

p1 = dt_info_pingji.iloc[dt_info_pingji['首逾率'].idxmax()]['逾期人数']/dt_info_pingji['逾期人数'].sum()
p2 = dt_info_pingji.iloc[dt_info_pingji['首逾率'].idxmax()]['区间客户占比']
up_rate2 = p1/p2
up_rate2
# 提升度
[out]:1.7147127611157038

计算年龄的提升度:(1.08)

p1 = dt_info_age.iloc[dt_info_age["首逾率"].idxmax()]["逾期人数"]/dt_info_age["逾期人数"].sum()
p2 = dt_info_age.iloc[dt_info_age["首逾率"].idxmax()]["区间客户占比"]
up_rate3 = p1/p2
up_rate3
[out]:1.0786323478681992

结果:征信查询次数提升度为1.94,信用评级提升度为1.71,年龄的提升度为1.08,选择征信查询次数的提升度。

3.5 解决方案

在以上过程中,我们根据计算得知提升度最高的征信查询次数,也就是说征信查询次数对于首逾率的影响最大。我们不妨将其最坏分箱的人全部拒绝,计算提出后的首逾率降幅为多少。

new_yuqi_nums = dt_info_zhengxin["逾期人数"].sum() - dt_info_zhengxin.iloc[dt_info_zhengxin["首逾率"].idxmax()]["逾期人数"]
new_yuqi_rate = new_yuqi_nums / (dt_info_zhengxin["区间客户数"].sum() - dt_info_zhengxin.iloc[dt_info_zhengxin["首逾率"].idxmax()]["区间客户数"])
# 原始整体首逾率为:
old_rate = dt['是否逾期(1是,0否)'].sum()/dt.shape[0]
# 下降幅度
diff = (dt['是否逾期(1是,0否)'].sum()/dt.shape[0])  -  new_yuqi_rate
print("新的首逾率:",new_yuqi_rate)
print("- "*20)
print("原始的首逾率:",old_rate )
print("- "*20)
print("首逾率下降幅度:",diff )
新的首逾率: 0.2900287361718911
- - - - - - - - - - - - - - - - - - - - 
原始的首逾率: 0.307584667705824
- - - - - - - - - - - - - - - - - - - - 
首逾率下降幅度: 0.017555931533932867
  • 通过对征信次数大于21次的用户拒绝后,首逾率为29%,下降了1.76%;
  • 虽然首逾率下降比例较少,但这部分的确能够起到影响作用。

二、用户群组分析——对相同生命周期阶段的用户进行垂直分析

群组分析:

  • 通过字面意思即可理解,群组分析法就是按某个特征对数据进行分组,通过分组比较,得出结论的方法。
  • 简单举例:将用户数据按性别特征,可以分组为男性和女性,将用户注册时间作为特征,按月份分组,可以分为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"})
df.reset_index(inplace = True)

orderperiod:用户消费月份
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()

3.总结

  • 整体用户留存率偏低,在5月就已经没有用户;
  • 其中3、4月份用户在3月已经消失,而1、2月用户生命周期相对较长,在5月才消失;
  • 预测可能1月、2月有活动,特别是1月份的,能够让用户的留存较高,对于此类情况的产生需要想办法增加用户留存,比如持续推出新品、给用户发短信、推出优惠活动等。

三、用户行为路径漏斗转化分析

用户行为路径分析:
用户行为路径分析是一种监测用户流向,从而统计产品使用深度的分析方法。它主要根据每位用户在App或网站中的点击行为日志,分析用户在App或网站中各个模块的流转规律与特点,挖掘用户的访问或点击模式,进而实现一些特定的业务用途,如App核心模块的到达率提升、特定用户群体的主流路径提取与浏览特征刻画,App产品设计的优化与改版等。
行为路径分析有如下作用:
1.可视化用户流向,对海量用户的行为习惯形成宏观了解。
2.定位影响转化的主次因素,使产品的优化与改进有的放矢。

用户路径的分析转化结果通常以桑基图形式展现,以目标事件为起点/终点,详细查看后续/前置路径的流向,可以详细查看某个节点事件的转化情况。


桑基图

1.案例背景

案例基于网络消费贷款形式,对消费贷借款进行复盘分析,增加用户借贷率。

2.案例过程

2.1 数据源观察

每日访问信息
用户信息
  • data:日期
  • PV:访问量
  • UV:独立用户访问量(访问用户数)
  • rigist_cnt:注册数
  • regist_rate:访客注册率
  • active_cnt:激活数
  • new_cus:是否为新用户(1:是,0:否)
  • lending:是否放贷(1:是,0:否)

2.2 计算新、老用户的放贷数和申请数

新用户:

dt_check_1 = dt_check.query("new_cus == 1")
pt_1 = pd.pivot_table(data=dt_check_1,index='date',values='lending',aggfunc=[np.sum,'count'])
pt_1.columns = pt_1.columns.droplevel(1)
pt_1.columns = ['新用户放贷数','新用户申请数']
pt_1['放贷率'] = pt_1['新用户放贷数'] / pt_1['新用户申请数']
pt_1 = pt_1.reset_index()
pt_1.head()
# 平均放贷率
pt_1["新用户放贷率"].mean()
# 平均放贷率
[out]:0.18942741473488545

老用户:

dt_check_0 = dt_check.query("new_cus == 0")
pt_0 = pd.pivot_table(dt_check_0,index = 'date',values='lending',aggfunc=[np.sum,'count'])
pt_0.columns = pt_0.columns.droplevel(1)
pt_0.columns = ['老用户放贷数','老用户申请数']
pt_0['放贷率'] = pt_0['老用户放贷数'] / pt_0['老用户申请数']
pt_0.reset_index(inplace = True)
pt_0.head()
pt_0['放贷率'].mean()
# 平均放贷率
[out]:0.28226736226736227
  • 老用户的平均放贷率比新用户要高出许多,这也是对于老用户的信赖。

2.3 计算复借率

老用户定义:在这里,我们定义前一天借贷的新用户,第二天继续借款就为老用户
先创建老用户人数列表,5.1-5.29的新用户为5.2-5.30日的老用户,再在首位随机插入一个数值表示5.1日的老用户

old = list(pt_1['新用户放贷数'])[0:29] 
old.insert(0,24) 

将数据与老用户放贷率表按日期进行匹配,计算复借率(复借率 = 老用户申请数 / 老用户数)

data = {'date':pt_1['date'],'老用户数':old}   
dt_old = pd.DataFrame(data)
pt_0_m = pd.merge(pt_0,dt_old,on='date')
pt_0_m['老用户复借率'] = pt_0_m['老用户申请数'] / pt_0_m['老用户数']
pt_0_m['老用户复借率'].mean()
复借率走势
# 平均复借率
[out]:0.3471876898455787
  • 5月复借率走势跌宕起伏,最低不到10%,最高却超过90%,猜测在复借率较高的那几天可能有营销活动在促进借贷,
  • 整月平均复借率为34.7%左右

2.4 计算各节点转换率并绘制漏斗图

节点结算:
计算当月每日PV、UV、注册数、活跃数、新用户申请数、新用户放贷数

dt = pd.merge(pt_1,dt_flow,on='date',how='left')  #合并新客户
dt_1 = pd.merge(dt,pt_0_m,on='date',how='left')   #合并老客户复借率
# 汇总
dt_sum = dt_1.drop('date',axis=1)
dt_sum.loc['Row_sum'] = dt_sum.apply(lambda x:x.sum())
# 提取需要的字段
dt_desc = dt_sum[dt_sum.index == 'Row_sum'][['PV','UV','regist_cnt','active_cnt','新用户申请数','新用户放贷数']]
dt_desc = dt_desc.T.reset_index()
dt_desc.columns = ['指标','汇总']

当月总体指标汇总

绘制漏斗图:

from plotly import graph_objects as go
trace = go.Funnel(
    y = dt_desc['指标'],
    x = dt_desc['汇总'],
    # 表示图片展示内容样式  “value+percent previous”:数值+百分比 占前一层比例(可选)
    textinfo = 'value+percent previous',
    marker = dict(color=["deepskyblue", "lightsalmon", "tan", "teal", "silver", "yellow"]),  #颜色参数
    connector = {"line": {"color": "royalblue", "dash": "solid", "width": 2}})
data = [trace]
fig = go.Figure(data)
fig.show()
月度各指标漏斗图
  • 转换率在 UV → regist_cnt 即访问用户→注册用户这一步骤转换率很低,需要进行策划活动提高用户注册数,比如新用户注册送礼等;
  • 注册用户转为活跃用户,以及活跃用户转为申请用户指标均处于较高的水平,需要维持下去;
  • 放贷成功率比较低,可以优化算法模型,在保证资金安全的情况下提高放贷率。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351