1 案例背景介绍
通过对业务数据分析了解信贷业务状况
数据集说明(从开源数据改造而来,基本反映真实业务数据)
-
销售,客服可以忽略,销售和客服对于风控业务分析不重要。
-
账单周期,放款日期
-
账单金额 - 实收金额 = 未收金额
-
应付日期为还款时间
-
账期分成两种 60天和90天
-
实际到账日为空白说明没还钱
业务数据分析目标:
-
每个季度账单金额和坏账率(逾期90天以上的账单为坏账)
- 所有未收金额 / 所有账单金额
- 未收金额 = 账单金额-实收金额
-
每个季度60天账期入催率,90天账单入催率
- 不同逾期天数的回款情况
- 历史逾期天数: 有逾期,已经还完了。曾经有欠钱,但是现在没有。
- 当前逾期天数: 现在还欠着钱,没还完
通过目标得到对于业务的处理思路:
- 目标是按照季度进行统计计算一些参数,所以处理数据的时候需要将时间处理为季度。
- 按照季度进行分组聚合:未收金额和账单金额
2 数据分析代码
import pandas as pd
import datetime
from pyecharts.charts import *
from pyecharts import options as opts
df = pd.read_excel('data/业务数据.xlsx')
# 拷贝的目的是防止算错之后,重新进行加载
# 小数据集重新加载灭有关系
# 大数据集重新加载会比较慢
df1 = df.copy()
# 实收金额 4470 non-null float64
# 实收金额有缺失值,表示没有还钱,可以填充0
df1.info()
- 需要确定以哪一天为基准进行计算,正常来讲应该以当前时间作为基础进行计算,当前项目选择日期最大的作为日期基础
# 取出实际到账日最近的一天, 作为计算指标的基准日期
# 先填充'0'再取出最大值
# 最后将其转换为Timestamp时间对象
# Timestamp('2019-05-17 00:00:00')
today_time = pd.to_datetime(df1['实际到账日'].fillna('0').max())
- 处理缺失值
# 实收金额和未收金额缺失都填充为0
df1['实收金额'].fillna(0,inplace=True)
df1['未收金额'].fillna(0,inplace=True)
# 将时间转换为时间对象类型
# 实际到帐日的缺失值,用最近的到账日填充
df1['账单周期'] = pd.to_datetime(df1['账单周期'])
df1['应付日期'] = pd.to_datetime(df1['应付日期'])
df1['实际到账日'] = pd.to_datetime(df1['实际到账日']).fillna(today_time)
- 判断否到期:应付日期 - 实付日期 < 0表示,到期了,否则没有到期
# 添加一列,每个账单是否到期
df1['是否到期'] = df1['应付日期'].apply(lambda x: 0 if x>today_time else 1)
- 查看到期和没到期的人数
# 查看到期和未到期的人数
df1['是否到期'].value_counts()
- 查看每个账单周期的账单数量
# 查看账单周期
df1['账单周期'].value_counts()
- 查看账单是否到期90天
# 今天(还款日的最近一天) - 应付日期 得到一个series
# 当前天 - series中的每一行的数据
d = (today_time - df1['应付日期'])
d.map(lambda x : 1 if x.days >= 90 else 0).rename('是否到期90天')
# 在df1中添加一列:是否逾期90天
df1['是否到期90天'] = (today_time-df1['应付日期']).apply(lambda x: 1 if x.days>=90 else 0)
- 查看到期90天的账单数
df1['是否到期90天'].value_counts()
- 计算未收金额
- 未收金额 = 账单金额 - 实收金额
df1['未收金额2'] = (df1['账单金额']-df1['实收金额'])
- 计算历史逾期天数和当前逾期天数
- 历史逾期:说明在还款过程中有逾期,不代表现在就逾期了。
- 当前逾期:现在还没有换完,说明当前逾期。
- 历史逾期天数 = 实际付款日 - 应付款日
- 当前逾期天数 = today_time - 应付款日
- 未收金额 > 0,表示当前逾期,可以直接计算当前逾期天数
- 未收金额 = 0,表示当前没有逾期,需要计算实际到帐日 - 应付款日,判断是否有历史逾期
- 实际到账日 <= 应付款日,没有历史逾期
- 实际到账日 >= 应付款日,有历史逾期
# axis = 1 表示对于df的每一行数据进行运算
df1['历史逾期天数'] = df1.apply(lambda x : (x['实际到账日']-x['应付日期']).days if x['未收金额2']==0 else (today_time-x['应付日期']).days,axis = 1)
df1['当前逾期天数'] = df1.apply(lambda x : x['历史逾期天数'] if x['未收金额2']>0 else 0, axis = 1)
- 将账单日期转化为季度信息,并根据季度选择数据
# 把数据转换成季度
# 实际到账日期为2019年5月17日,有些贷款没到还款日,没有办法统计DPD90的数据,这里只统计2019年之前的情况
df1['账单季度'] = df1['账单周期'].apply(lambda x: x.to_period('Q'))
# 从数据中截取 2017年3季度到2018年4季度的数据
df2 = df1[(df1['账单季度']<='2018Q4') & (df1['账单季度']>='2017Q3')]
-
计算每个季度的账单金额和坏账率
-
计算每个季度的账单总金额
# 按照季度统计账单金额,到期金额和逾期金额
# 账单金额表示放贷多少强
fn1 = df2.groupby('账单季度')[['账单金额']].sum()
- 计算每个季度超过90天的账单金额
df3 = df2[df1['是否到期90天']==1] # 选择账单到期90天的数据
fn2 = df3.groupby('账单季度')[['账单金额']].sum() # 按照季度进行分组统计
fn2.columns = ['到期金额']
- 计算每个季度未收金额
fn3 = df3.groupby('账单季度')[['未收金额2']].sum()
fn3.columns = ['当前逾期90+金额']
# 3个df合并到一起
final1 = pd.concat([fn1,fn2,fn3],axis=1)
- 计算坏账率
# 坏账率 = 逾期金额 / 到期金额
final1['90+净坏账率'] = round(final1['当前逾期90+金额']/final1['到期金额'],3)
final1
绘图
bar = (
Bar() #柱状图
.add_xaxis(list(final1.index.values.astype(str))) # 柱状图X坐标的值
.add_yaxis("账单金额",list(final1.账单金额),yaxis_index=0,color="#5793f3") # 柱状图y坐标的值 y坐标索引(yaxis_index) 颜色
.set_global_opts(title_opts=opts.TitleOpts(title="90+净坏账率"))
.extend_axis( # 添加一个右侧的Y轴
yaxis=opts.AxisOpts(
name="90+净坏账率",type_="value",min_=0,max_=0.014,
position="right",
axisline_opts=opts.AxisLineOpts(
linestyle_opts=opts.LineStyleOpts(color="#d14a61")
),
axislabel_opts=opts.LabelOpts(formatter="{value}"),
)
)
)
line = ( # 折线图
Line()
.add_xaxis(list(final1.index.values.astype(str)))
.add_yaxis("90+净坏账率",list(final1['90+净坏账率']),yaxis_index=1,
color="#675bba",label_opts=opts.LabelOpts(is_show=False),
)
)
bar.overlap(line).render('./90天坏账率.html')
-
坏账率 = 逾期90天账单金额 / 到期金额
- 增加一列是否到期,用于判断和筛选到期
- 增加一列是否到期90天,用于筛选数据
- 按照季度分组,计算每一组的账单总金额
- 选出逾期90天的数据,按照季度分组,计算每一组的账单金额
- 每个季度的60天账单的催收率和90天账单的催收率
# 选择到期的并且账期为60天的数据
df4 = df1[(df1['账期']==60) & (df1['是否到期']==1)]
# 按照季度进行分组聚合操作,得到账单金额
fn1 = df4.groupby('账单季度')[['账单金额']].sum()
fn1.columns=['60天账期的账单金额']
# 选择进入到60天催收的账单
# 账期60天、已经到期并且历史逾期天数>0
df5 = df1[(df1['账期']==60) & (df1['是否到期']==1) & (df1['历史逾期天数']>0)]
# 计算入催的账单金额
fn2 = df5.groupby('账单季度')[['未收金额2']].sum()
fn2.columns=['60天账期的入催金额']
df6 = df1[(df1['账期']==90) & (df1['是否到期']==1)]
fn3 = df6.groupby('账单季度')[['账单金额']].sum()
fn3.columns=['90天账期的账单金额']
df7 = df1[(df1['账期']==90) & (df1['是否到期']==1) & (df1['历史逾期天数']>0)]
fn4 = df7.groupby('账单季度')[['未收金额2']].sum()
fn4.columns=['90天账期的入催金额']
final2 = pd.concat([fn1,fn2,fn3,fn4],axis=1)
final2['60天账期的入催率'] = final2['60天账期的入催金额']/final2['60天账期的账单金额']
final2['90天账期的入催率'] = final2['90天账期的入催金额']/final2['90天账期的账单金额']
line = (
Line()
.add_xaxis(list(final2.index.values.astype(str)))
.add_yaxis("60天账期入催率",list(final2['60天账期的入催率']),yaxis_index=0,
color="#675bba",label_opts=opts.LabelOpts(is_show=False),)
.set_global_opts(title_opts=opts.TitleOpts(title="不同账期入催率"),)
.add_xaxis(list(final1.index.values.astype(str)))
.add_yaxis(
"90天账期入催率",list(final2['90天账期的入催率']),yaxis_index=0,
color="#d14a61",label_opts=opts.LabelOpts(is_show=False),)
)
line.render('./入催率.html')
- 已经回收的账单的逾期情况
# 未收金额为0 表示已经还完了
# 并且到期的账单
df8 = df2[(df2['未收金额2']==0) & (df2['是否到期']==1)]
- 将历史逾期天数进行分组
df8['历史逾期天数2'] = pd.cut(df8['历史逾期天数'],bins=[-999,0,7,15,30,60,90,999],labels=['0','1-7','8-15','16-30','31-60','61-90','90+'])
- 计算每个历史逾期天数区间内,账单的数量
final3 = df8.groupby('历史逾期天数2')[['账期']].count()
final3.columns=['回收账单数']
final3
#%%
ydata = final3['回收账单数'].values.tolist()
bar = (
Bar()
.add_xaxis(list(final3.index.values.tolist()))
.add_yaxis("收回账单数",ydata,yaxis_index=0,color="#675bba")
.set_global_opts(
title_opts=opts.TitleOpts(title="不同逾期天数的已收回账单数"),
)
)
bar.render("./不同逾期天数的已收回账单数.html")