消費(fèi)金融案例
一、單變量分析——用戶首逾率增高問題
二坛缕、用戶群組分析——對相同生命周期階段的用戶進(jìn)行垂直分析
三、用戶行為路徑漏斗轉(zhuǎn)化分析
一、單變量分析——用戶首逾率增高
案例背景
日常監(jiān)控發(fā)現(xiàn)某款消費(fèi)貸產(chǎn)品首逾率有逐漸升高的趨勢居暖,需要把首逾率降下來以減少產(chǎn)品帶來的損失,同時(shí)通過率降幅不能太明顯藤肢。
分析目標(biāo)
- 通過數(shù)據(jù)探查分析制定出可以有效降低首逾率的策略太闺。
分析思路
- 策略分析時(shí)的基本思路就是還原這些有首逾表現(xiàn)的客戶在申請時(shí)的數(shù)據(jù)(這個(gè)還原是指提取出客戶在申請時(shí)點(diǎn)各個(gè)維度的數(shù)據(jù),越多越好) 嘁圈,然后利用這些數(shù)據(jù)去找出能夠區(qū)分好壞客戶的變量省骂,制定策略蟀淮。
分析流程
- 首先確定分析目標(biāo),制定出降低首逾率的風(fēng)控策略钞澳;
- 提取數(shù)據(jù)分析樣本怠惶,提取出更多維度的客戶數(shù)據(jù),比如客戶年齡轧粟、地區(qū)策治、負(fù)債、月收入等等兰吟;
- 篩選有效變量通惫,通過計(jì)算提升度,來篩選出較優(yōu)的變量混蔼。
- 依據(jù)上一步擬定的策略履腋,模擬一下策略執(zhí)行后的首逾率降幅。
1拄丰、數(shù)據(jù)處理
import pandas as pd
import numpy as np
from sqlalchemy import create_engine
import matplotlib.pyplot as plt
%matplotlib inline
# 創(chuàng)建數(shù)據(jù)庫連接
engine = create_engine('mysql+pymysql://frogdata05:Frogdata!1321@localhost:3306/froghd')
sql_cmd = "select * from load_datas"
# 執(zhí)行sql語句府树,獲取數(shù)據(jù)
dt = pd.read_sql(sql=sql_cmd, con=engine)
# 查看數(shù)據(jù)
dt.head()
# 修改字段名
dt.rename(columns={
"user_id":"用戶id",
"age":"年齡",
"occupation":"職業(yè)",
"work_times":"工作時(shí)間",
"credit_score":"芝麻信用分",
"credit_level":"信用評級",
"credit_check_times":"近半年征信查詢次數(shù)",
"credit_card_use_rate":"信用卡額度使用率",
"is_overdue":"是否逾期(1是,0否)"
},inplace=True)
dt.head()
# 查看數(shù)據(jù)維度
dt.shape
(56456, 9)
2料按、查看產(chǎn)品總體情況
# 查看逾期人數(shù)占比
dt['是否逾期(1是奄侠,0否)'].value_counts().plot.pie(autopct='%.2f%%', figsize=(4,4))
總體首逾率為:30.76%
3、篩選出有效變量
- 單變量分析:主要目的是篩選出好壞區(qū)分度較好的變量以便制定策略载矿。
在消金公司的日常工作中垄潮,會有專門的數(shù)據(jù)團(tuán)隊(duì),他們在不斷的去獲取加工很多可能對風(fēng)險(xiǎn)控制有幫助的數(shù)據(jù)闷盔,提供給風(fēng)控團(tuán)隊(duì)弯洗,而風(fēng)控人員就需要從這成千上萬個(gè)變量中探查出能夠控制逾期風(fēng)險(xiǎn)但同時(shí)又不會誤拒很多好客戶的變量;拿到數(shù)據(jù)的第一步就是針對每個(gè)變量單獨(dú)分析逢勾,查看其對逾期的影響牡整。
3.1 征信查詢次數(shù)分組
# 定義一個(gè)征信分組的函數(shù)
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:缺失"
# 新建一個(gè)字段記錄分組信息
dt['征信分組']=dt['近半年征信查詢次數(shù)'].apply(judge_zhengxin)
dt.head()
# 分組統(tǒng)計(jì)各個(gè)分組的情況
dt_info = dt.groupby("征信分組").agg({
"用戶id":'count',
"是否逾期(1是,0否)":sum
}).reset_index().rename(columns={"用戶id":"區(qū)間客戶數(shù)",
"是否逾期(1是溺拱,0否)":"區(qū)間逾期客戶數(shù)"})
dt_info
# 計(jì)算區(qū)間用戶占比
dt_info2['區(qū)間用戶占比']=dt_info2["區(qū)間客戶數(shù)"]/dt_info2["區(qū)間客戶數(shù)"].sum()
# 計(jì)算區(qū)間首逾率
dt_info2["區(qū)間首逾率"]=dt_info2["區(qū)間逾期客戶數(shù)"]/dt_info2["區(qū)間客戶數(shù)"]
dt_info2
3.2 信用評級分組
# 定義一個(gè)征信分組的函數(shù)
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 "缺失"
# 新建一個(gè)字段記錄分組信息
dt['信用評級分組']=dt['信用評級'].apply(judge_pingji)
dt.head()
# 分組統(tǒng)計(jì)各個(gè)分組的情況
dt_info2 = dt.groupby("信用評級分組").agg({
"用戶id":'count',
"是否逾期(1是逃贝,0否)":sum
}).reset_index().rename(columns={"用戶id":"區(qū)間客戶數(shù)",
"是否逾期(1是,0否)":"區(qū)間逾期客戶數(shù)"})
dt_info2
# 計(jì)算區(qū)間用戶占比
dt_info2['區(qū)間用戶占比']=dt_info2["區(qū)間客戶數(shù)"]/dt_info2["區(qū)間客戶數(shù)"].sum()
# 計(jì)算區(qū)間首逾率
dt_info2["區(qū)間首逾率"]=dt_info2["區(qū)間逾期客戶數(shù)"]/dt_info2["區(qū)間客戶數(shù)"]
dt_info2
3.3年齡分組
bins=[0,25,30,35,40,45,50]
dt['工作時(shí)間分組']=pd.cut(dt['年齡'],bins=bins,right=True)
dt.head()
dt_info3=dt.groupby('工作時(shí)間分組').agg({'用戶id':'count',
'是否逾期(1是迫摔,0否)':sum})
.reset_index().rename(columns={'用戶id':'區(qū)間客戶數(shù)',
'是否逾期(1是沐扳,0否)':'區(qū)間逾期客戶數(shù)'})
dt_info3
# 計(jì)算區(qū)間用戶占比
dt_info3['區(qū)間用戶占比']=dt_info3['區(qū)間客戶數(shù)']/dt_info3['區(qū)間客戶數(shù)'].sum()
# 區(qū)間首逾率
dt_info3['區(qū)間首逾率']=dt_info3['區(qū)間逾期客戶數(shù)']/dt_info3['區(qū)間客戶數(shù)']
dt_info3
4、計(jì)算提升度
在進(jìn)行變量分析之后句占,這時(shí)我們就要從中篩選中較為有效的變量了沪摄,這里涉及到一個(gè)衡量變量是否有效的指標(biāo),提升度。
- 提升度:通俗的來說就是衡量拒絕最壞那一部分的客戶之后杨拐,對整體的風(fēng)險(xiǎn)控制的提升效果祈餐。提升度越高,說明該變量可以更有效的區(qū)分好壞客戶哄陶,能夠更少的誤拒好客戶昼弟。
- 計(jì)算公式:提升度=最壞分箱的首逾客戶占總首逾客戶的比例 /該分箱的區(qū)間客戶數(shù)占比
dt_info
dt_info2
dt_info3
- 最壞分箱:設(shè)定為首逾率最高的一組
4.1征信查詢次數(shù)提升度
# 獲取最大值所在行
index1_max=dt_info["區(qū)間首逾率"].idxmax()
# dt_info.iloc[dt_info["區(qū)間首逾率"].idxmax()]
bad_rate = dt_info.iloc[index1_max]["區(qū)間逾期客戶數(shù)"]/dt_info["區(qū)間逾期客戶數(shù)"].sum()
num_rate = dt_info.iloc[index1_max]["區(qū)間客戶數(shù)"]/dt_info["區(qū)間客戶數(shù)"].sum()
deg1=bad_rate/num_rate
print('征信查詢次數(shù)的提升度:{:.2f}'.format(deg1))
征信查詢次數(shù)的提升度:1.95
4.2信用評級的提升度
# 獲取最大值所在行
index2_max=dt_info2["區(qū)間首逾率"].idxmax()
bad_rate2 = dt_info2.iloc[index2_max]["區(qū)間逾期客戶數(shù)"]/dt_info2["區(qū)間逾期客戶數(shù)"].sum()
num_rate2 = dt_info2.iloc[index2_max]["區(qū)間客戶數(shù)"]/dt_info2["區(qū)間客戶數(shù)"].sum()
deg2=bad_rate2/num_rate2
print('信用評級的提升度:{:.2f}'.format(deg2))
信用評級的提升度:1.71
4.3年齡的提升度
# 年齡的提升度
# 獲取最大值所在行
index3_max=dt_info3["區(qū)間首逾率"].idxmax()
bad_rate3 = dt_info3.iloc[index3_max]["區(qū)間逾期客戶數(shù)"]/dt_info3["區(qū)間逾期客戶數(shù)"].sum()
num_rate3 = dt_info3.iloc[index3_max]["區(qū)間客戶數(shù)"]/dt_info3["區(qū)間客戶數(shù)"].sum()
deg3=bad_rate3/num_rate3
print('年齡的提升度:{:.2f}'.format(deg3))
年齡的提升度:1.08
4.4變量篩選
對比以上三個(gè)變量中,征信查詢次數(shù)的提升度最高
5奕筐、制定策略:
- 通過上一步的單變量分析舱痘,我們篩出了’征信查詢次數(shù)’作為提升度最高的變量。現(xiàn)在我們看一下如果將這個(gè)個(gè)變量的最壞分箱的客戶都拒絕之后离赫,對整體逾期的影響芭逝。
- 這個(gè)影響就是指假設(shè)我們將‘征信總查詢次數(shù)>=21的3213位客戶全部拒絕’之后,剩下的客戶逾期率相比拒絕之前的逾期率降幅是多少渊胸。
# 即假設(shè)我們設(shè)置征信查詢次數(shù)這個(gè)風(fēng)控指標(biāo)旬盯,并且把查詢次數(shù)21次以上的都拒絕掉
# 然后來看首逾率是多少,有降低多少
# 逾期率 = 逾期人數(shù) / 總?cè)藬?shù)
new_overdue_nums = dt_info2["區(qū)間逾期客戶數(shù)"].sum() - dt_info2.iloc[dt_info2["區(qū)間首逾率"].idxmax()]["區(qū)間逾期客戶數(shù)"]
new_overdue_rate = new_overdue_nums / dt_info[dt_info.index!=index1_max]['區(qū)間客戶數(shù)'].sum()
old_overdue_nums = dt_info2["區(qū)間逾期客戶數(shù)"].sum()
old_overdue_rate = old_overdue_nums / dt_info2["區(qū)間客戶數(shù)"].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%
二翎猛、用戶群組分析——對相同生命周期階段的用戶進(jìn)行垂直分析
群組分析:
通過字面意思即可理解胖翰,群組分析法就是按某個(gè)特征對數(shù)據(jù)進(jìn)行分組,通過分組比較切厘,得出結(jié)論的方法萨咳。
簡單舉例:將用戶數(shù)據(jù)按性別特征,可以分組為男性和女性疫稿,將用戶注冊時(shí)間作為特征培他,按月份分組,可以分為1月遗座,2月舀凛,3月進(jìn)行環(huán)比比較,以及對他的留存率途蒋、活躍率猛遍、付費(fèi)率等進(jìn)行分析。
群組分析的作用:
1.對處于相同生命周期階段的用戶進(jìn)行垂直分析号坡,從而比較得出相似群體隨時(shí)間的變化懊烤。
2.通過比較不同的同期群,可以從總體上看到筋帖,應(yīng)用的表現(xiàn)是否越來越好了奸晴。從而驗(yàn)證產(chǎn)品改進(jìn)是否取得了效果冤馏。
1.案例目的
通過用戶的訂單消費(fèi)情況日麸,對比同一月份的新用戶留存率的變化趨勢,以及不同時(shí)間期的新用戶在同周期時(shí)的留存率情況
2.案例過程
- 數(shù)據(jù)一共有7個(gè)字段
orderid:訂單編號
orderdate:訂單日期
userid:用戶編號
totalcharges:消費(fèi)金額
因?yàn)樵谶@里分析的是用戶留存率情況,其他字段暫時(shí)不需要
2.1 數(shù)據(jù)處理
需要從月份維度進(jìn)行分析代箭,先根據(jù)用戶訂單編號增加一個(gè)年月字段墩划,再選擇用戶最小消費(fèi)月份為首次消費(fèi)時(shí)間
# 增加年月字段
df['orderperiod'] = df.orderdate.apply(lambda x:x.strftime("%Y-%m"))
# 增加最早消費(fèi)時(shí)間
# 根據(jù)索引對齊,需先將orderid設(shè)置為索引列
df.set_index("userid",inplace = True)
df["chortgroup"] = df.groupby("userid").agg({"orderperiod":"min"}) #orderperiod:用戶消費(fèi)月份
df.reset_index(inplace = True) # chortgroup:用戶最早消費(fèi)時(shí)間(出現(xiàn)的時(shí)間點(diǎn))
2.2 按用戶按照最早消費(fèi)時(shí)間分組并編號
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)
# 定義編號函數(shù)
def cohorts_period(x):
x['cohortperiod'] = np.arange(len(x)) + 1
return x
# 編號
#cohorts.groupby('chortgroup') 后 每一個(gè)小群組分別是以orderperiod為index的群組
cohorts = cohorts.groupby('chortgroup').apply(cohorts_period)
cohorts = cohorts.reset_index().set_index(['chortgroup','cohortperiod']) (出現(xiàn)的時(shí)間點(diǎn))
cohorts
- 上圖中嗡综,已經(jīng)按照用戶首月時(shí)間對隨后幾個(gè)月的消費(fèi)情況進(jìn)行了統(tǒng)計(jì)
- 但其中可能是數(shù)據(jù)缺失的原因乙帮,消費(fèi)金額列出現(xiàn)為0的情況,但這并不影響我們對留存率的計(jì)算
2.3 計(jì)算用戶留存率
每個(gè)月的首月用戶數(shù)
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
計(jì)算每月留存率
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()