评分卡模型(二)基于评分卡模型的用户付费预测

news2024/11/25 16:40:56

评分卡模型(二)基于评分卡模型的用户付费预测

小P:小H,这个评分卡是个好东西啊,那我这想要预测付费用户,能用它吗

小H:尽管用~

(本想继续薅流失预测的,但想了想这样显得我的业务太单调了,所以就改成了付费预测。哈哈~)

数据探索

导入相关库

import pandas as pd
import numpy as np
import math
from sklearn.model_selection import train_test_split,cross_val_score  # 数据分区库
import xgboost as xgb
from sklearn.metrics import accuracy_score, auc, confusion_matrix, f1_score, \
    precision_score, recall_score, roc_curve, roc_auc_score, precision_recall_curve  # 导入指标库
from imblearn.over_sampling import SMOTE  # 过抽样处理库SMOTE
import matplotlib.pyplot as plt
import prettytable  # 导入表格库
from pandas_profiling import ProfileReport # 自动eda
import sweetviz as sv # 自动eda
import matplotlib.pyplot as plt
from matplotlib import ticker
import seaborn as sns
import os
import shutil 
import toad  
from sklearn.model_selection import GridSearchCV 
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
from toad.plot import  bin_plot, badrate_plot
from sklearn.preprocessing import LabelEncoder
from collections import defaultdict
from sklearn.linear_model import LogisticRegression 
from scipy.stats import scoreatpercentile
from toad.scorecard import ScoreCard 
%matplotlib inline
pd.set_option('display.max_columns', None) # 显示所有列
# 风格设置
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文
sns.set(style="ticks") # 设置风格

# 导入自定义模块
import sys
sys.path.append("/Users/heinrich/Desktop/Heinrich-blog/数据分析使用手册")
from keyIndicatorMapping import *

数据准备

上述自定义模块keyIndicatorMapping如果有需要的同学可关注公众号HsuHeinrich,回复【数据挖掘-自定义函数】自动获取~

以下数据如果有需要的同学可关注公众号HsuHeinrich,回复【数据挖掘-评分卡02】自动获取~

# 读取数据
raw_data = pd.read_csv('train_set.csv')  # 读取数据文件
raw_data.head()

image-20230206152930672

# 生成好坏定义列
raw_data['target']=1-raw_data['y'] # 1表示bad客户(即未购买),0表示dood客户(即购买了)
raw_data.drop('y', axis=1, inplace=True)
# 定义id列
ids_col = ['ID']
# 定义排除的特征列:一般包括ID列、日期列、目标列
ex_lis = ['ID', 'target']
# 定义排除列不含y
ex_lis_noy = ['ID']
# 定义y列
y_col = 'target'
# 查看变量统计信息
toad.detector.detect(raw_data)

image-20230206153030262

# 查看变量价值信息
toad.quality(raw_data.drop(ex_lis_noy, axis=1), y_col, iv_only=True)

image-20230206153051705

# 数据审查
na_count = raw_data.isnull().any().sum() # 缺失值样本量
n_samples, n_features = raw_data.shape  # 总样本量,总特征数
print('samples: {0}| features: {1} | na count: {2}'.format(n_samples, n_features, na_count))
samples: 25317| features: 18 | na count: 0

特征工程

样本拆分

# 样本拆分:训练样本、测试样本
train, test = train_test_split(
    raw_data, test_size=.3, random_state=0)

特征初筛

# 缺失率>0.7,IV<0.1(一般认为iv低于0.1的特征区分度较弱),相关系数>0.7
train_s, drop_lst= toad.selection.select(train, train[y_col], 
                                                   empty=0.7, iv=0.1, 
                                                   corr=0.7, 
                                                   return_drop=True, 
                                                   exclude=ex_lis)  
print("keep:", train_s.shape[1],  
      "drop empty:", len(drop_lst['empty']), 
      "drop iv:", len(drop_lst['iv']),  
      "drop corr:", len(drop_lst['corr']))
keep: 14 drop empty: 0 drop iv: 4 drop corr: 0

变量分箱

# 得到切分节点 卡方分箱
combiner = toad.transform.Combiner()  
combiner.fit(train_s, train_s[y_col], method='chi',
                min_samples=0.05, exclude=ex_lis)  
# 导出箱的节点  
bins = combiner.export()  
# 变量分箱
train_t = combiner.transform(train_s)
test_t = combiner.transform(test[train_s.columns])
print(bins)
{'age': [], 'job': [['student', 'retired'], ['unemployed', 'management'], ['self-employed', 'admin.', 'unknown', 'technician'], ['services', 'housemaid'], ['blue-collar', 'entrepreneur']], 'balance': [-45, 61, 874, 1693], 'housing': [['no'], ['yes']], 'contact': [['cellular'], ['telephone'], ['unknown']], 'day': [5, 10, 17, 21, 28], 'month': [['mar', 'dec', 'sep', 'oct', 'apr', 'feb'], ['jan', 'aug', 'nov', 'jun', 'jul', 'may']], 'duration': [75, 114, 206, 366, 654], 'campaign': [2, 3, 4, 5, 8], 'pdays': [9, 211], 'previous': [1, 3], 'poutcome': [['success', 'other', 'failure', 'unknown']]}

WOE编码

w = toad.transform.WOETransformer()  
#对WOE的值进行转化,映射到原数据集上。对训练集用fit_transform,测试集用transform.
train_w = w.fit_transform(train_t, train_t[y_col], 
                                      exclude=ex_lis) 
test_w = w.transform(test_t[train_t.columns])   
data = pd.concat([train_w, test_w])

二次筛选

# psi筛选
np.seterr(divide='ignore',invalid='ignore') # 防止0/0产生的invalid value
psi_df = toad.metrics.PSI(train_w, test_w).sort_values(0)  
psi_df = psi_df.reset_index()  
psi_df = psi_df.rename(columns = {'index': 'feature', 0: 'psi'})
col_keep = list(set(list(psi_df[psi_df.psi<0.02].feature)).union(set(ex_lis))) # 保留低psi特征和不参与特征的并集
train_psi = train_w[col_keep]

print("keep:", train_psi.shape[1])
keep: 14
# 因为特征WOE编码后,部分变量的IV变低,且整体相关性变大。故再次进行特征筛选
train_psi_s2, drop_lst = toad.selection.select(train_psi,
                                               train_psi[y_col],
                                               empty=0.7,   
                                               iv=0.1, 
                                               corr=0.7, 
                                               return_drop=True, 
                                               exclude=ex_lis)  
print("keep:", train_psi_s2.shape[1],  
      "drop empty:", len(drop_lst['empty']),  
      "drop iv:", len(drop_lst['iv']),  
      "drop corr:", len(drop_lst['corr'])) 
keep: 9 drop empty: 0 drop iv: 4 drop corr: 1
# 逐步回归筛选变量
train_stp = toad.selection.stepwise(train_psi_s2,  
                                                  train_psi_s2[y_col],  
                                                  exclude=ex_lis,  
                                                  direction='both',   
                                                  criterion='aic',  
                                                  estimator='ols',
                                              intercept=False)  

print("keep:", train_stp.shape[1])
keep: 9

生成最终数据集

test_stp = test_w[train_stp.columns]  
data_finall = pd.concat([train_stp, test_stp]) 
print(data_finall.shape)
(25317, 9)

数据建模

模型训练

# 样本拆分
X, y = data_finall.drop(ex_lis, axis=1), data_finall[y_col]
X_train, y_train = train_stp.drop(ex_lis, axis=1), train_stp[y_col]
X_test, y_test = test_stp.drop(ex_lis, axis=1), test_stp[y_col]
# 模型训练
model_lr = LogisticRegression(C=0.1, class_weight='balanced')      
model_lr.fit(X_train, y_train)  
LogisticRegression(C=0.1, class_weight='balanced')

模型估

  • 核心指标评估
model_confusion_metrics(model_lr, X_test, y_test, 'test')
model_core_metrics(model_lr, X_test, y_test, 'test')
confusion matrix for test
 +----------+--------------+--------------+
|          | prediction-0 | prediction-1 |
+----------+--------------+--------------+
| actual-0 |     5336     |     1362     |
| actual-1 |     152      |     746      |
+----------+--------------+--------------+
core metrics for test
 +-------+----------+-----------+--------+-------+-------+
|  auc  | accuracy | precision | recall |   f1  |   ks  |
+-------+----------+-----------+--------+-------+-------+
| 0.881 |  0.801   |   0.972   | 0.797  | 0.876 | 0.631 |
+-------+----------+-----------+--------+-------+-------+
  • 模型区分与排序能力评估
fig = plt.figure(figsize=(18,12))
plt.subplot(221)
plot_roc(model_lr, X_test, y_test, name='test')
plt.subplot(222)
plot_ks(model_lr, X_test, y_test, name='test')
plt.subplot(223)
plot_pr(model_lr, X_test, y_test, name='test')
plt.subplot(224)
plot_lift(model_lr, X_test, y_test, name='test')
plt.tight_layout()
plt.show()

output_33_0

  • 模型泛化能力评估
fig = plt.figure(figsize=(18,12))
plt.subplot(221)
plot_cv_box(model_lr, X_test, y_test, name='test')
plt.subplot(222)
plot_learning_curve(model_lr, X_test, y_test, name='test')
plt.tight_layout()
plt.show()

output_35_0

  • 模型稳定性评估
# 模型PSI:小于10%,则无需更新模型;10%-20%, 需检查变化原因,加强监控频率;大于20%,则模型需要迭代
mpsi = model_psi(model_lr, X_train, X_test)
print('模型PSI:',mpsi)
模型PSI: 0.20931994818791816
  • 模型捕获报告评估
# 模型捕获率报告
y_test_prob = model_lr.predict_proba(X_test)[:, 1]
df_capture = capture_table(y_test_prob, y_test)
df_capture.columns=['KS', '负样本个数', '正样本个数', '负样本累计个数', '正样本累计个数', '捕获率', '负样本占比']
df_capture

image-20230206153116870

结果展示

评分卡

# 计算odds
bad_total=raw_data[y_col].sum()
good_total=raw_data.shape[0]-bad_total
odds=round(bad_total/good_total,2)
base_odds=round(good_total/bad_total,0)
print('bad_total:{0}\ngood_total:{1}\nodds:{2}\nbase_odds:{3}\n'.format(bad_total,good_total,odds,base_odds))
bad_total:22356
good_total:2961
odds:7.55
base_odds:0.0
# 生成评分报告 # 注意ScoreCard方法里求解A=𝑃0-𝐵∗𝑙𝑜𝑔(𝑜𝑑𝑑𝑠)。因此这里的base_odds使用好坏比,即(1-p)/p
card = ScoreCard(combiner=combiner, 
                    transer=w, C=0.1, 
                    class_weight='balanced', 
                    base_score=600,
                    base_odds=1/8,
                    pdo=60,
                    rate=2)  
card.fit(X_train, y_train)  
# 输出标准评分卡
final_card = card.export(to_frame=True)  
final_card 

image-20230206153142194

def get_score(X, card):
    '''
    X:X数据集
    card:评分卡对象名
    
    return:增加分值列的df
    '''
    df_score=pd.DataFrame(card.predict(X), index=X.index, columns=["score"])
    df_data_score = pd.concat([X,df_score], axis=1)
    return df_data_score

final_data_score=get_score(test, card)
# 得分的直方图
sns.histplot(final_data_score['score'])
plt.show()

output_44_0

评分卡区分能力评估

fig = plt.figure(figsize=(18,12))
plt.subplot(221)
plot_score_hist(final_data_score, y_col, 'score')
plt.subplot(222)
plot_lorenz(final_data_score, y_col, 'score')
plt.tight_layout()
plt.show()

output_46_0

  • 好坏客户的分布重合度较低,评分卡区分能力较好
  • 洛伦兹曲线较平缓,区分能力一般

确定评分卡cutoff点

%%time

# 搜索cutoff点
print('{:*^60}'.format('cutoff search result'))
_, cutoff_score=search_cutoff(final_data_score,y_col,'score')
print('{:*^60}'.format('set cutoff result'))
# 设定cutoff点,衡量有效性
matrix_df=rule_verify(final_data_score,y_col,'score',cutoff_score)
********************cutoff search result********************
最大KS值:0.631
KS最大的分数:783
*********************set cutoff result**********************
拒绝准确率:0.971
查全率:0.809
误伤率:0.178
规则拒绝率:0.734
CPU times: user 1min 4s, sys: 453 ms, total: 1min 4s
Wall time: 1min 5s
# 查看cutoff结果
plot_score_hist(final_data_score, y_col, 'score', cutoff=cutoff_score)
plt.show()

output_50_0

用户得分雷达图

# 生成评分明细
final_data_score_detail = card.predict(test, return_sub=True)[1]
final_data_score_detail['score'] = final_data_score_detail[list(final_card['name'].unique())].sum(axis=1)
# 归一化处理
max_min_scaler = lambda x : (x-np.min(x))/(np.max(x)-np.min(x))
final_data_score_detail_scaler = final_data_score_detail.copy()
final_data_score_detail_scaler.iloc[:,:-1] = final_data_score_detail_scaler.iloc[:,:-1].apply(max_min_scaler)
# 画布基本设置
fig = plt.figure(figsize=(6,6))  # 建立画布
ax = fig.add_subplot(111, polar=True)  # 增加子网格,注意polar参数
labels = final_card['name'].unique()  # 设置要展示的数据标签
cor_list = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w']  # 定义不同类别的颜色
angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False)  # 计算各个区间的角度
angles = np.concatenate((angles, [angles[0]]))  # 建立相同首尾字段以便于闭合
labels = np.concatenate((labels,[labels[0]]))   # 新版本增加,对labels进行封闭
# 画雷达图
i = 14982
score = int(final_data_score_detail_scaler.loc[i]['score'])
data_tmp = np.array(final_data_score_detail_scaler.loc[i])[0:-1]  # 获得对应类数据
data = np.concatenate((data_tmp, [data_tmp[0]]))  # 建立相同首尾字段以便于闭合
ax.plot(angles, data, 'o-', c=cor_list[0], label=f'score:{score}')  # 画线
# 设置图像显示格式
ax.set_thetagrids(angles * 180 / np.pi, labels, fontproperties="SimHei")  # 设置极坐标轴
ax.set_title(f"用户{i}得分雷达图", fontproperties="SimHei")  # 设置标题放置
ax.set_rlim(-0.2, 1.2)  # 设置坐标轴尺度范围
plt.legend(loc=0)  # 设置图例位置
plt.show()

output_53_0

总结

只需要定义好什么是好人,什么是坏人,就可以按照标准流程构建评分卡了,是不是很方便~

共勉~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DevExpress WinForms电子表格控件,更快拥有现代办公体验!(二)

DevExpress WinForm Spreadsheet组件能读写XLSx、XLS、CSV、TXT文件、打印并导出为PDF等&#xff0c;为终端用户提供了当今流行的办公UI体验&#xff01;在上文中&#xff08;点击这里回顾>>&#xff09;&#xff0c;我们介绍了DevExpress WinForm中Excel启发式的电子表格…

【计算机网络-数据链路层】集线器、网桥、交换机

本文许多文字和图片使用了湖科大教书匠&#xff08;高军老师&#xff09;的 PPT&#xff0c;在此表示感谢。正是他让非科班的我能以奇妙的方式走进网络的世界。 文章目录1 【物理层】集线器&#xff08;Hub&#xff09;——共享式以太网1.1 为什么使用集线器&#xff1f;1.2 集…

救命,我好像发现了测试工程师面试通关秘籍

一、自我介绍 &#xff08;自我介绍不局限于下面模板&#xff0c;灵活表达&#xff09; 面试官你好&#xff0c;我叫xxx&#xff0c;今年xx岁&#xff0c;家乡是xx省xx市。20xx年毕业后一直从事软件测试工作&#xff0c;到现在已经x年了。 目前为止&#xff0c;经历过x家公司…

基于SpringBoot的大学生体质测试管理系统源码数据库论文

目录 目录 1 绪 论 1.1系统背景介绍 1.2课题研究的目的和意义 1.3系统的研究现状 1.4系统实现的功能 1.5系统的特点 2 开发工具和技术 2.1 B/S体系结构 2.2 Java语言简介 2.3 SpringBoot框架 2.4 MySQL简介 3 系统需求分析 3.1 系统可行性分析及目的…

JS数组reduce()方法详解及高级技巧

reduce()方法可以搞定的东西&#xff0c;for循环&#xff0c;或者forEach方法有时候也可以搞定&#xff0c;那为啥要用reduce()&#xff1f;这个问题&#xff0c;之前我也想过&#xff0c;要说原因还真找不到&#xff0c;唯一能找到的是&#xff1a;通往成功的道路有很多&#…

QtableWidget插入数据卡顿优化方法

最近要使用Qtablewidget保存4300多的数据&#xff0c;发现以下刷新4300条数据&#xff0c;界面会变得非常卡顿&#xff0c;于是想了优化一下&#xff1b;因为要对所有数据排序&#xff0c;想用一下Qtablewidget自动排序功能&#xff0c;而且数据量不多&#xff0c;不想采用动态…

【教学类-32-02】十二生肖2.0版(绘画+手工+排序+左右分类+玩牌)(中班:偏科学-数)

作品展示 2.0样式——动物头部方向随机向左、或者向右 背景需求 1.0样式——动物头部方向全部向右&#xff0c; 我希望孩子分类的时候还能够“判断生肖头部的方向做一个左右分类” 素材准备&#xff1a; 1、图片准备 office PPT2013里面有一个图标的功能&#xff0c;内置大量…

java继承类怎么写

继承类是通过把父类的方法和属性继承到一个类中&#xff0c;而子类的方法和属性是子类自己定义的。 Java中有一个很重要的概念叫做继承&#xff0c;这也是 Java语言的精髓所在。Java语言提供了一种机制&#xff0c;叫做派生类。在 Java中&#xff0c;如果没有实现了某个派生类方…

终端和文件运行python代码

如何创建python文件&#xff1f;新建一个txt&#xff0c;然后修改后缀名为 .py&#xff0c; 然后修改打开方式为记事本&#xff0c;写入一行代码&#xff0c;然后关闭 接下来如何运行呢&#xff1f;让他输出这一段代码 winR cmd 回车 &#xff0c;然后输入python 文件地址 我…

ChatGPT 未来的前景以及发展趋势

当谈到ChatGPT的未来和发展趋势时&#xff0c;需要考虑人工智能技术以及文本生成和交互的迅速发展。在这方面&#xff0c;ChatGPT的前景非常有希望&#xff0c;因为它是一种迄今为止最先进的人工智能技术之一。 ChatGPT是一种基于机器学习的自然语言处理技术&#xff0c;它能够…

MyBatis学习总结(四) MyBatis 延迟加载策略MyBatis 一级缓存、二级缓存MyBatis注解开发

MyBatis学习总结&#xff08;四&#xff09; MyBatis 延迟加载策略/MyBatis 一级缓存、二级缓存/MyBatis注解开发 一、 MyBatis 延迟加载策略 通过前面的学习&#xff0c;我们已经掌握了 MyBatis 中一对一&#xff08;多对一&#xff09;、一对多、多对多关系的配置及实现&am…

【Android入门到项目实战-- 5.1】—— 广播(一):接收系统广播

目录 一、什么是广播&#xff1f; 二、广播的类型 标准广播 有序广播 三、接收系统广播 1、动态注册监听网络变化 如何注册广播接收器&#xff1f; 2、静态注册实现开机启动 使用快捷方式创建广播接收器 实现开机广播 一、什么是广播&#xff1f; android广播机制就是…

全网最详细,Jmeter性能测试-性能进阶, 多协议实战Websocket/Dubbo(七)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 Jmeter是目前主流的…

自行车及电动自行车出口美国GCC认证要求

所有进口商和美国本土制造商都必须签发普通合格证书&#xff08;GCC&#xff09;证明其产品符合具体规定要求。进口商和本土制造商应向认可的第三方实验室提交样品测试其产品是否符合相关安全法规。提供第三方实验室测试报告&#xff0c;合格证书由进口商和本土制造商提供。在外…

pytorch通过不同的维度提高cifar10准确率

各个维度通过模型通过优化器通过batchsize通过数据增强总结当前网络的博客上都是普遍采用某个迁移学习训练cifar10&#xff0c;无论是vgg&#xff0c;resnet还是其他变种模型&#xff0c;最后通过实例代码&#xff0c;将cifar的acc达到95以上&#xff0c;本篇博客将采用不同的维…

九龙证券|300亿空袭,港股吓懵了!

港股再度大幅回调&#xff0c;腾讯成了“导火索”。 当地时刻4月11日&#xff0c;腾讯大股东Prosus发布公告称拟再度进行回购&#xff0c;作为回购方案的一部分&#xff0c;Prosus本周将采纳行动&#xff0c;把9600万股腾讯股票以凭据方式移入香港中心结算系统&#xff0c;以便…

八、市场活动-创建

需求分析 用户在市场活动主页面,点击"创建"按钮,弹出创建市场活动的模态窗口; 用户在创建市场活动的模态窗口填写表单,点击"保存"按钮,完成创建市场活动的功能. *所有者是动态的(//在现实市场活动主页面时&#xff0c;就从数据库中查询出所有用户并且…

基于ChatGLM-6b+Streamlit+QDrant+DuckDuckGo搭建本地问答机器人及缓解时效性问题方案

本地部署chatglm及缓解时效性问题的思路&#xff1a; 模型使用chatglm-6b 4bit&#xff0c;推理使用hugging face&#xff0c;前端应用使用streamlit或者gradio。 微调对显存要求较高&#xff0c;还没试验。可以结合LoRA进行微调。 缓解时效性问题&#xff1a;通过本地数据库…

word页码从指定页开始,具体设置步骤

word页码从指定页开始&#xff0c;具体设置步骤1、在文档页面&#xff0c;以目录后第一页为页码开始页为例&#xff0c;也就是正文第一页&#xff0c;首先将鼠标光标定位到目录页最后的位置2、在【布局】选项下点击【分隔符】选项。3、在分隔符中选择【分节符】下的【下一页】。…

深度学习-第T4周——猴痘病识别

深度学习-第T4周——猴痘病识别深度学习-第T4周——猴痘病识别一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目3、查看数据四、数据预处理1、 加载数据1、设置图片格式2、划分训练集3、划分验证集4、查看标签2、数据可视化3、检查数据4、配置数据集五、搭建CNN网…