大家好,我是东哥。本篇继续分享风控的内容,关于如何用python实现vintage报表及可视化图的实战。
账龄分析(vintage)是风控中非常重要的报表之一,通过它可以将不同月份的资产数据拉齐对比贷后表现,也可以用于指导制定风控模型Y标签的成熟表现期。
那么账龄分析是如何做的呢?
账龄分析需要客户的还款计划表数据,即客户历史的还款记录,包括放款金额、每期到期日期、每期还款日期、每期应该金额、每期实还金额、期数等等。
基于这些数据就可以做出vintage报表,以及相应的可视化图。下面通过一个实战我们来学习一下通过Python如何加工出vintage报表(关于理论部分、加工逻辑详细解释可以见报表篇)。
首先导入数据,每家机构的数据字段可能不尽相同,但核心逻辑都是一样的,可以基于已有的数据进行加工出我们想要的样子,比如下面这个比较原始的表结构,没有非常完善的字段。数据和完整代码获取:风控圈子
以下是核心字段的加工逻辑。我们以每月月底为观测点对各个账龄进行DPD30+金额口径逾期率的计算。
# 定义当前观测时点
now_date = '2023-08-31'
df['repay_time'] = pd.to_datetime(df['repay_time'])
df['due_time'] = pd.to_datetime(df['due_time'])
df['loan_time'] = pd.to_datetime(df['loan_time'])
df['loan_month'] = df['loan_time'].astype(str).map(lambda x:x[:7])
df['order_id'] = df['order_id'].astype(str)
# 加工出mob月底观测日期
def get_month(x) : return dt.datetime(x.year,x.month,int(x.days_in_month))
df['due_month_end'] = df.loc[df['due_time'].isna()==False,'due_time'].apply(get_month)
# 加工应还实还金额
df['shouldpay_permonth'] = df['loan_amt']/df['loan_term']
df['shouldpay_permonth'] = df['shouldpay_permonth'].round(2)
df['actualpay_permonth'] = df['shouldpay_permonth'].where(cond = df['repay_time'].isnull()==False)
df['balance'] = df['loan_amt']-df['mob']*df['actualpay_permonth']
df['balance'] = df['balance'].round(0).ffill()
# 观测日期前未归还余额
cond = (df['repay_time'].isnull()==True) | ((df['due_month_end'] - df['repay_time']).dt.days < 0)
df['overdue_amt'] = df['balance'].where(cond, other=0)
# 筛选当前观测时点之前的数据,未来数据还未发生
df = df[df['due_time'] < now_date]
print(df.shape)
加工出我们需要的数据以后,通过pandas的pivot_table
对mob账龄和所有放款月份进行透视,这样我们就得到了以上口径下截止每个月的累积逾期金额。
然后再分组计算求得每个月的放款金额总和,与累积逾期金额合并,以逾期金额为分子,以放款总金额为分母,相除即可得到累积的逾期率。
df_mob = pd.pivot_table(df,index='loan_month',columns='mob',values='overdue_amt',aggfunc='sum')
# 放款月的放款本金求和
df_loan = df.groupby(['loan_month'])['shouldpay_permonth'].sum()
# 拼接计算金额dpd30+逾期率
df_mob_all = pd.concat([df_loan.to_frame(name='loan_amt'),df_mob],axis=1)
df_mob_rate = df_mob_all.divide(df_mob_all['loan_amt'],axis=0)
两种方式。
第一种使用seaborn
的heatmap
热力图可以完美输出,并有颜色渐变的趋势。
# cohort分析
import seaborn as sns
plt.figure(figsize=(15, 8))
plt.title('cohort vintage DPD30+')
sns.heatmap(data=df_mob_rate,annot = True,fmt = '.1%',vmin = 0.0,vmax = 0.5, cmap="BuPu_r")
plt.show()
第二种是比较常规的折线图,可以更加直观的对比各个月的走势。
# vintage折线图
palette = sns.color_palette("husl", 9)
plt.figure(figsize=(15, 8))
plt.title('lineplot vintage DPD30+')
plt.ylim(0,0.2)
plt.xticks(df_mob_rate.loc[:,1:].columns.tolist())
sns.lineplot(data=df_mob_rate.loc[:,1:].T, markers=True, dashes=True, lw=3, palette=palette)
plt.show()