当前位置:首页 » 《随便一记》 » 正文

基于决策树模型的金融保险用户分类综合项目【数据可视化&相关性分析&填充缺失值&转码&建模&商业应用】_老吴的博客

5 人参与  2021年11月15日 14:43  分类 : 《随便一记》  评论

点击全文阅读


超详细的一篇数据分析实战案例,模拟真实业务,建议收藏慢慢阅读!

目录

  • 必看前言
  • 1 行业背景
    • 1.1 业务环境
    • 1.2 发展现状
    • 1.3 发展趋势
    • 1.4 衡量指标
  • 2 数据介绍
    • 2.1 基本信息
    • 2.2 家庭信息
    • 2.3 家庭成员
    • 2.4 疾病史
    • 2.5 金融信息
    • 2.6 个人习惯
    • 2.7 家庭状况
    • 2.8 居住城市
  • 3 案例分析
    • 3.1 分析流程
      • 3.1.1 导入数据,观察数据
      • 3.1.2 探索数据&数据可视化分析
        • 3.1.2.1 处理列标签名异常
        • 3.1.2.2 创建自定义翻译函数
        • 3.1.2.3 探索用户基本信息
        • 3.1.2.4 探索家庭成员字段信息
        • 3.1.2.5 探索疾病相关字段
        • 3.1.2.6 探索投资相关字段
        • 3.1.2.7 探索生活习惯
        • 3.1.2.8 探索家庭收入
        • 3.1.2.9 探索所处地区情况
      • 3.1.3 数据清洗
        • 3.1.3.1 删除特征
        • 3.1.3.2 删除重复值
        • 3.1.3.3 划分训练集与测试集
        • 3.1.3.4 填充缺失值
      • 3.1.4 转码
        • 3.1.4.1 0-1转码
        • 3.1.4.2 哑变量转码
        • 3.1.4.3 对测试集进行转码(总结)
      • 3.1.5 数据建模与评估
        • 3.1.5.1 初步建模
        • 3.1.5.2 网格搜索找最优参数
        • 3.1.5.3 模型评估
        • 3.1.5.4 输出规则
      • 3.1.6 输出结果的商业应用
        • 3.1.6.1 第一类
        • 3.1.6.2 第二类
        • 3.1.6.3 了解客户需求
        • 3.1.6.4 开发新的产品
  • 结束语

必看前言

本次文章将会介绍一篇基于决策树模型的金融保险用户分类综合项目,我会从行业背景讲起,将整个项目包括代码全po上来,大家看目录就知道有多么详细啦。

1 行业背景

1.1 业务环境

  • 宏观
    中国是世界第二大保险市场,但在保险密度上与世界平均水平仍有明显差距。
  • 业界
    保险行业2018年保费规模为3.8万亿,同比增长不足4%,过去"短平快"的发展模式已经不能适应新时代的行业发展需求,行业及用户长期存在难以解决的痛点,限制了行业发展。
  • 社会
    互联网经济的发展,为保险行业带来了增量市场,同时随着网民规模的扩大,用户的行为习惯已发生转变,这些都需要互联网的方式进行触达。保险科技∶当前沿科技不断应用于保险行业,互联网保险的概念将会与保险科技概念高度融合。

在这里插入图片描述
中国保险市场持续高速增长。根据保监会数据,2011~2018年,全国保费收入从1.4万亿增长至3.8万亿,年复合增长率17.2%。2014年,中国保费收入突破2万亿,成为全球仅次于美国、日本的第三大新兴保险市场市场;2016年,中国整体保费入突破3万亿,超过日本,成为全球第二大保险市场;2019年,中国保费收入突破4万亿。

1.2 发展现状

  • 概览
    受保险行业结构转型时期影响,互联网保险整体发展受阻,2018年行业保费收入为1889亿元,较17年持平,不同险种发展呈现分化格局,其中健康险增长迅猛,2018年同比增长108%,主要由短期医疗险驱动。
  • 格局
    供给端专业互联网保险公司增长迅速,但过高的固定成本及渠道费用使得其盈利问题凸显,加上发展现状强,自营渠道建设及科技输出是未来的破局方法,渠道端形成第三方平台为主,官网为辅的格局,第三方平台逐渐发展出B2C、B2A、B2B2C等多种创新业务模式。
  • 模式
    互联网保险不仅仅局限于渠道创新,其核心优势同样体现在产品设计的创新和服务体验的提升。

1.3 发展趋势

  • 竞合格局
    随着入局企业增多,流量争夺更加激烈,最终保险公司与第三方平台深度合作将成为常态。
  • 保险科技
    当前沿科技不断应用于保险行业,互联网保险的概念将会与保险科技概念高度融合。

1.4 衡量指标

在这里插入图片描述

2 数据介绍

  • 数据来源
    美国某保险公司,该公司的一款医疗产品准备上市。
  • 商品介绍
    这是一款针对65岁人群推出的医疗附加险,销售渠道是直邮。
  • 商业目的
    为该产品做用户画像,找到最具购买倾向的人群进行营销。

本次案例数据中共有76个字段,字段繁多,在处理数据时,需要先将数据按照类别进行划分,方便理解查看。

2.1 基本信息

变量名称变量含义备注
KBM_INDY_ID用户ID无意义特征
resp_flag用户是否购买保险我们的目标变量
age用户年龄本产品为针对65岁以上的人群保险
GEDN性别 
c210mys学历0-unknown; 1-初中; 2-高中不到; 3-高中毕业; 4-大学未毕业; 5-大专; 6-本科; 7-研究生; 8-专业院校毕业; 9-博士
POC19是否有小孩 
CA[XX]小孩年龄 

2.2 家庭信息

变量名称变量含义备注
NOC19家庭小孩个数 
NAH19家庭成年人个数 
NPH19家庭成员人数 
POEP家庭是否为有老人 

2.3 家庭成员

变量名称变量含义备注
U18是否有家庭成员小于18岁 
N1819是否有家庭成员在18-19岁之间 
N2029是否有家庭成员在20-29岁之间 
N3039是否有家庭成员在30-39岁之间 
N4049是否有家庭成员在40-49岁之间 
N5059是否有家庭成员在50-59岁之间 
N6064是否有家庭成员在60-64岁之间 
N65P是否有家庭成员在65岁以上 

2.4 疾病史

变量名称变量含义备注
AART是否有关节炎 
ADBT是否有糖尿病 
ADEP是否有抑郁症 
AHBP是否有高血压 
AHCH胆固醇含量是否过高 
ARES是否有呼吸疾病 
 

2.5 金融信息

变量名称变量含义备注
BANK是否有过破产记录 
FINI是否有过保险服务 
INLI是否投资过寿险 
INMEDI是否购买过医疗险 
INVE是否有投资 
 

2.6 个人习惯

变量名称变量含义备注
IOLP是否网上购买过产品 
MOBPLUS是否通过快递买过东西M-通过多种快递渠道购买;P-或许通过多种快递渠道购买;S-单一快递渠道购买;U-不知道;
ONLA是否上网 
SGFA是否喜欢美术 
SGLL是否经常有奢侈消费 
SGDE是否经常户外活动 
SGSE是否喜欢运动 
 

2.7 家庭状况

变量名称变量含义备注
LIVEWELL幸福指数值越大,说明越幸福
HOMSTAT是否有房子Y:有房子;P:可能有房子;R:租房;TU:不确定
HINSUB是否有医保补贴A-C,补贴依次增加
c210cip收入所处排名值越大,说明收入越高
c210ebi普查家庭有效购买收入值越大,说明有效购买收入越高
c210hmi家庭收入值越大,说明家庭收入越高
c210hva家庭房屋价值值越大,说明房屋价值越高
c210…家庭经济类数据值越大,说明经济地位越高

2.8 居住城市

变量名称变量含义备注
STATE_NAME是否网上购买过产品 
c210apvt贫穷以上人的比例值越大,说明比例越高
c210b200所处地区有多少居住小区在2000年后建立值越大,说明比例越高
c210blu所处地区蓝领所占百分比值越大,说明比例越高
c210bpvt贫穷以下人的比例值越大,说明比例越高
c210mob所处地区mobile home的比例值越大,说明比例越高
c210pdv离婚或者分局人群所占比例值越大,说明比例越高
居住地统计数据值越大,说明比例越高

3 案例分析

我们可以大概判别哪些特征很可能和用户是否购买保险会有相关关系。

也可以结合我们的业务经验,以及数据可视化,特征工程方法,先行探索一下,这些特征中哪些特征更重要一些。

可以在建模之后,再回顾我们这里认为比较重要或不重要的特征,看一下判断是否准确。

3.1 分析流程

  1. 导入数据,观察数据
  2. 探索数据 & 数据可视化分析(探索缺失值填充方案、转码方案、一些特殊字段的处理方案等)
  3. 空值填充
  4. 变量编码
  5. 数据建模
  6. 输出规则(决策树图)
#全部行都能输出
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 解决坐标轴刻度负号乱码
plt.rcParams['axes.unicode_minus'] = False

# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['Simhei']

3.1.1 导入数据,观察数据

data_00 = pd.read_csv('data/ma_resp_data_temp.csv')
feature_dict = pd.read_excel('保险案例数据字典.xlsx')
data_01 = data_00.copy()

3.1.2 探索数据&数据可视化分析

注意:在我们进行探索数据时,可以结合一下EXCEL,用EXCEL来记录数据后续需要进行的对应操作(可以分成删除、转换、填充三种情况),这样也方便我们后续进行数据清洗。

data_01.head()

在这里插入图片描述

feature_dict

在这里插入图片描述

3.1.2.1 处理列标签名异常

表中的字段与数据字典不匹配。

判断data_01中的列标签名是否都出现在数据字典的变量名中。

data_01.columns

在这里插入图片描述

feature_dict.变量名

在这里插入图片描述

#求补集
np.setxor1d(data_01.columns,feature_dict.变量名) 

在这里插入图片描述

'''
#数据表列标签
NY8Y9', 'N2N29', 'N3N39', 'N4N49', 'N5N59', 'N6N64'

#数据字典
'N1819', 'N2029','N3039','N4049', 'N5059','N6064'

meda # 这个莫名其妙,删掉
'''
# 删掉
data_01['meda'].nunique()

75

替换异常标签

a = ['NY8Y9', 'N2N29', 'N3N39', 'N4N49', 'N5N59', 'N6N64']
b = ['N1819', 'N2029','N3039','N4049', 'N5059','N6064']
# 要替换的列标签,做成映射字典
dic = dict(zip(a,b))
dic
#自定义要转化的向量化函数
def tran(x):
    if x in dic:
        return dic[x]
    else:
        return x
tran = np.vectorize(tran) #向量化

#使用向量化函数替换异常表头
data_01.columns = tran(data_01.columns)

在这里插入图片描述

3.1.2.2 创建自定义翻译函数

提高探索数据效率,创建自定义翻译函数,通过映射字典的方式,替换DataFrame列标签名为中文:

dic = {k:v for k,v in feature_dict[['变量名','变量说明']].values.reshape(-1,2)}

def chinese(x):
    y = x.copy()
    #将输入进来的字段名通过字典映射的方式去对应
    y.columns = pd.Series(y.columns).map(dic)
    return y
chinese(data_01).head()

在这里插入图片描述

3.1.2.3 探索用户基本信息

feature_dict.变量名[:5]

在这里插入图片描述

feature_dict.变量名[:5].tolist() #得到列表

在这里插入图片描述

data_01[feature_dict.变量名[:5].tolist()].head()

在这里插入图片描述

#将0_4列取出来并进行翻译
data0_4 = chinese(data_01[feature_dict.变量名[:5].tolist()])
data0_4.head()

在这里插入图片描述
下面来看一下数据的基本信息。

data0_4.info()

在这里插入图片描述
再看一下缺失值

data0_4.isnull().sum()

在这里插入图片描述
发现是有缺失的(后续要处理)。

同时,我们不难发现上面这些方法都是常用到的,可以考虑封装成函数,方便后续使用。

自定义探索特征频率函数

输入一个DataFrame,输出每个特征的频数分布:

def fre(x):
    for i in x.columns:
        print("字段名:",i)
        print("----------")
        print("字段数据类型:",x[i].dtype)
        print("----------------------------")
        print(x[i].value_counts()) #频数
        print("----------------------------")
        print("缺失值的个数:",x[i].isnull().sum())
        print("------------------------------------------------\n\n")

fre(data0_4)

输出结果如下:

字段名: Individual ID
----------
字段数据类型: int64
----------------------------
142936063    1
68797618     1
193209817    1
172309160    1
171816124    1
            ..
172641155    1
76214763     1
228943803    1
185032198    1
1966080      1
Name: Individual ID, Length: 43666, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否response
----------
字段数据类型: int64
----------------------------
0    26177
1    17489
Name: 是否response, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 年龄
----------
字段数据类型: float64
----------------------------
66.0     3967
67.0     3670
65.0     3475
69.0     3449
68.0     3423
70.0     2948
71.0     2943
72.0     2909
74.0     2817
73.0     2814
75.0     2448
76.0     2220
78.0     2038
77.0     2012
79.0     1823
80.0      691
91.0        2
86.0        2
88.0        2
96.0        1
99.0        1
82.0        1
95.0        1
90.0        1
98.0        1
87.0        1
94.0        1
101.0       1
Name: 年龄, dtype: int64
----------------------------
缺失值的个数: 4
------------------------------------------------


字段名: 性别
----------
字段数据类型: object
----------------------------
F    25405
M    18261
Name: 性别, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 学历
----------
字段数据类型: int64
----------------------------
4    18597
3    12437
6     7493
5     4474
2      462
7      130
0       60
1        9
8        4
Name: 学历, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------
  • Individual ID是我们要删的(后面也有一个ID);
  • 是否response则是我们的标签;
  • 年龄则是有些缺失值(年龄有高有低,最高都101岁,可以考虑用中位数填充);
  • 性别则是需要进行0-1编码;
  • 学历需要转成哑变量(即one-hot编码)。

来看一下购买情况。

import seaborn as sns

#中文编码
sns.set_style("darkgrid",{"font.sans-serif":['simhei','Droid Sans Fallback']})
#sns.set()

plt.figure(1,figsize=(6,2))
sns.countplot(y='是否response',data=data0_4)
plt.show()

在这里插入图片描述
差别不是很大,后续使用class_weight参数与否也不重要。

#根据年龄   概率密度图
sns.kdeplot(data0_4.年龄[data0_4.是否response==1],label='购买')
sns.kdeplot(data0_4.年龄[data0_4.是否response==0],label='不购买')
sns.kdeplot(data0_4.年龄.dropna(),label='所有人')

plt.xlim([60,90])
plt.xlabel('Age')
plt.ylabel('Density')

在这里插入图片描述

3.1.2.4 探索家庭成员字段信息

#将5_22列取出来并进行翻译
data5_22 = chinese(data_01[feature_dict.变量名[5:23].tolist()])
data5_22.head()

在这里插入图片描述
调用一下刚自定好的函数。

fre(data5_22)

输出结果为:

字段名: 是否有小孩
----------
字段数据类型: object
----------------------------
U    24500
Y    10225
P     8941
Name: 是否有小孩, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 小孩是否在0-2岁之间
----------
字段数据类型: int64
----------------------------
0    40677
4     2856
1       57
2       48
3       16
6        9
5        3
Name: 小孩是否在0-2岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 小孩是否在3-5岁之间
----------
字段数据类型: int64
----------------------------
0    41087
4     2068
1      204
2      202
3       45
5       30
6       29
7        1
Name: 小孩是否在3-5岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 小孩是否在6-10岁之间
----------
字段数据类型: int64
----------------------------
0    38969
4     2960
1      553
2      528
6      240
5      209
3      176
7       31
Name: 小孩是否在6-10岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 小孩是否在11-15岁之间
----------
字段数据类型: int64
----------------------------
0    39430
4     2281
1      757
2      651
3      211
5      176
6      140
7       20
Name: 小孩是否在11-15岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 小孩是否在16-18岁之间
----------
字段数据类型: int64
----------------------------
0    40243
4     1590
1      826
2      718
3      164
5       70
6       54
7        1
Name: 小孩是否在16-18岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 家庭小孩个数
----------
字段数据类型: int64
----------------------------
0    33441
1     5089
2     2365
3     1298
4     1008
5      290
6      116
7       30
8       26
9        3
Name: 家庭小孩个数, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 家庭成年人个数
----------
字段数据类型: int64
----------------------------
1    15792
2    13414
3     7633
4     3942
5     1292
0     1105
6      349
7      101
8       29
9        9
Name: 家庭成年人个数, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 家庭成员人数量
----------
字段数据类型: int64
----------------------------
1     14970
2     11148
3      6120
4      4123
5      2499
6      1547
0      1105
7       994
8       569
9       316
10      152
11       79
12       25
13       12
15        3
14        3
16        1
Name: 家庭成员人数量, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员小于18岁
----------
字段数据类型: object
----------------------------
N    43665
Y        1
Name: 是否有家庭成员小于18岁, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员在18-19岁之间
----------
字段数据类型: object
----------------------------
N    43579
Y       78
Name: 是否有家庭成员在18-19岁之间, dtype: int64
----------------------------
缺失值的个数: 9
------------------------------------------------


字段名: 是否有家庭成员在20-29岁之间
----------
字段数据类型: object
----------------------------
N    41318
Y     2348
Name: 是否有家庭成员在20-29岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员在30-39岁之间
----------
字段数据类型: object
----------------------------
N    36624
Y     7042
Name: 是否有家庭成员在30-39岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员在40-49岁之间
----------
字段数据类型: object
----------------------------
N    34740
Y     8926
Name: 是否有家庭成员在40-49岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员在50-59岁之间
----------
字段数据类型: object
----------------------------
N    38345
Y     5321
Name: 是否有家庭成员在50-59岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员在60-64岁之间
----------
字段数据类型: object
----------------------------
N    39544
Y     4121
0        1
Name: 是否有家庭成员在60-64岁之间, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有家庭成员在65岁以上
----------
字段数据类型: object
----------------------------
Y    42461
N     1205
Name: 是否有家庭成员在65岁以上, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 家里是否有老人
----------
字段数据类型: object
----------------------------
N    33522
Y    10136
Name: 家里是否有老人, dtype: int64
----------------------------
缺失值的个数: 8
------------------------------------------------
  • 是否有小孩这个字段有三种值:U代表不知道,Y代表有,P表示可能有(美国老人和子女间的联系比较少,不知道自己有没有孙子孙女),后续转成哑变量;
  • 小孩是否在0~2岁之间:我们发现有4个0~2岁的小孩的数量有2856,如果是子女的话很不科学,所以我们猜测是孙子孙女(所以上面的P才解释得通);
  • 是否有家庭成员是小于18岁的:发现只有1个数据是有小于18岁的成员的,与上述数据完全不符,说明这个数据是有问题的,后续需要删掉
  • 是有有家庭成员在18~19岁之间:存在缺失值,考虑用众数填充;
  • 是否有家庭成员在60~64岁之间:发现有个数据是0,怀疑可能原本是N弄成了0,后续可以将0替换成N;
  • 家里是否有老人:我们统计的对象本来就是老人,但是数据却很多没有老人,推测可能存在对象觉得自己不算在其中的情况,我们可以考虑将这个字段删掉;
  • 其他字段可以将N和P转成0和1;

3.1.2.5 探索疾病相关字段

老套路:

#将23_35列取出来并进行翻译
data23_35 = chinese(data_01[feature_dict.变量名[23:35].tolist()])
data23_35.head()

在这里插入图片描述

fre(data23_35)
字段名: 是否有关节炎
----------
字段数据类型: object
----------------------------
N    38369
Y     5297
Name: 是否有关节炎, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有糖尿病
----------
字段数据类型: object
----------------------------
N    40554
Y     3112
Name: 是否有糖尿病, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有抑郁症
----------
字段数据类型: object
----------------------------
N    41674
Y     1992
Name: 是否有抑郁症, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有高血压
----------
字段数据类型: object
----------------------------
N    38102
Y     5564
Name: 是否有高血压, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 胆固醇含量是否过高
----------
字段数据类型: object
----------------------------
N    37395
Y     6271
Name: 胆固醇含量是否过高, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有呼吸疾病
----------
字段数据类型: object
----------------------------
N    40900
Y     2766
Name: 是否有呼吸疾病, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有心脏病
----------
字段数据类型: object
----------------------------
N    41733
Y     1933
Name: 是否有心脏病, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有过敏性鼻炎
----------
字段数据类型: object
----------------------------
N    36824
Y     6832
Name: 是否有过敏性鼻炎, dtype: int64
----------------------------
缺失值的个数: 10
------------------------------------------------


字段名: 是否有消化不良
----------
字段数据类型: object
----------------------------
N    37381
Y     6285
Name: 是否有消化不良, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否耳聋
----------
字段数据类型: object
----------------------------
N    42082
Y     1584
Name: 是否耳聋, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有皮肤病
----------
字段数据类型: object
----------------------------
N    41161
Y     2497
Name: 是否有皮肤病, dtype: int64
----------------------------
缺失值的个数: 8
------------------------------------------------


字段名: 是否视力不好
----------
字段数据类型: object
----------------------------
N    38399
Y     5267
Name: 是否视力不好, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------
  • 是否有过敏性鼻炎:有10个缺失值,考虑用众数填充;
  • 是否有皮肤病:有8个缺失值,考虑用众数填充;
  • N和P转成0和1。

从常理性来讲,疾病之间很可能是存在相关性的,所以我们可以看看这些字段之间的相关性(画热力图),找到高度相关的字段并删除。

那我们得先将字段数据 转换成0-1编码:

#0 1 转码
def zero_one(x):
    for i in x.columns:
        if x[i].dtype == 'object':
            dic = dict(zip(list(x[i].value_counts().index),range(x[i].nunique())))
            x[i] = x[i].map(dic)
    return x

zero_one(data23_35).corr()

在这里插入图片描述
单看表可能比较头疼,下面我们画张热力图看看:

import matplotlib.pyplot as plt
import seaborn as sns

sns.heatmap(zero_one(data23_35).corr(),cmap='Blues')

在这里插入图片描述
越蓝相关性越高。肉眼可以看出来相关性了,不过还是写个函数直接输出相关性高的字段更好,这里我们输出相关性高于0.65的字段(可以删,但不然删太多,对决策树的创建也不好)。

def higt_cor(x,y=0.65):
    data_cor = (x.corr()>y)
    a=[]
    
    for i in data_cor.columns:
        if data_cor[i].sum()>=2:  # 有两个或两个以上的TRUE
            a.append(i)

    return a  #这些是我们要考虑删除的

higt_cor(data23_35) #是否有关节炎  胆固醇含量是否过高  是否有过敏性鼻炎

[‘是否有关节炎’, ‘是否有高血压’, ‘胆固醇含量是否过高’, ‘是否有过敏性鼻炎’, ‘是否有消化不良’]

我们可以再结合一下图,考虑删除是否有关节炎 胆固醇含量是否过高 是否有过敏性鼻炎这三个字段。

3.1.2.6 探索投资相关字段

老套路:

#将35_41列取出来并进行翻译
data35_41 = chinese(data_01[feature_dict.变量名[35:41].tolist()])
data35_41.head()

在这里插入图片描述

  • 都是要改成0-1编码。
fre(data35_41)
字段名: 是否有过破产记录
----------
字段数据类型: object
----------------------------
N    40599
Y     3067
Name: 是否有过破产记录, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否大学毕业
----------
字段数据类型: object
----------------------------
N    39236
Y     4422
Name: 是否大学毕业, dtype: int64
----------------------------
缺失值的个数: 8
------------------------------------------------


字段名: 是否用过保险服务
----------
字段数据类型: object
----------------------------
N    42793
Y      873
Name: 是否用过保险服务, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否投资过寿险
----------
字段数据类型: object
----------------------------
N    35871
Y     7795
Name: 是否投资过寿险, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否购买过医疗险
----------
字段数据类型: object
----------------------------
N    40016
Y     3650
Name: 是否购买过医疗险, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否有投资
----------
字段数据类型: object
----------------------------
N    33896
Y     9770
Name: 是否有投资, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------

  • 是否大学毕业:存在缺失值,不过这个数据明显和之前的学历信息重复了,可以删掉。

接下来还是画图看一下属性间的相关性:

sns.heatmap(zero_one(data35_41).corr(),cmap='Blues')

在这里插入图片描述
可以看到后下角颜色明显很深。

是否有过投资和是否投资过寿险明显相关,当然我们也可以再调用一下函数查看结果:

higt_cor(data35_41)

[‘是否投资过寿险’, ‘是否有投资’]

结果确实是这样,那我们这个时候要考虑删除其中哪一个?

我们结合一下我们的目的——推荐医疗附加险。是否投资过寿险感觉更贴合我们的目的,所以考虑删掉是否有投资这个字段。

3.1.2.7 探索生活习惯

老套路:

#将41_51列取出来并进行翻译
data41_51 = chinese(data_01[feature_dict.变量名[41:51].tolist()])
data41_51.head()

在这里插入图片描述
还是都要改成0-1编码。

我们接着在调用一下函数:

fre(data41_51)
字段名: 是否网上购买过产品
----------
字段数据类型: object
----------------------------
N    36197
Y     7469
Name: 是否网上购买过产品, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否通过快递买过东西
----------
字段数据类型: object
----------------------------
M    27450
S     9947
U     3912
P     2350
Name: 是否通过快递买过东西, dtype: int64
----------------------------
缺失值的个数: 7
------------------------------------------------


字段名: 所处的县的大小
----------
字段数据类型: object
----------------------------
A    26539
B    12687
C     3823
D      607
Name: 所处的县的大小, dtype: int64
----------------------------
缺失值的个数: 10
------------------------------------------------


字段名: 是否上网
----------
字段数据类型: object
----------------------------
Y    28473
N    15193
Name: 是否上网, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否喜欢美术
----------
字段数据类型: object
----------------------------
N    37029
Y     6637
Name: 是否喜欢美术, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否经常有奢侈消费
----------
字段数据类型: object
----------------------------
N    33801
Y     9865
Name: 是否经常有奢侈消费, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否经常户外活动
----------
字段数据类型: object
----------------------------
N    35728
Y     7938
Name: 是否经常户外活动, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否喜欢运动
----------
字段数据类型: object
----------------------------
N    31867
Y    11799
Name: 是否喜欢运动, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 是否热爱科技
----------
字段数据类型: object
----------------------------
N    31370
Y    12296
Name: 是否热爱科技, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 幸福指数
----------
字段数据类型: float64
----------------------------
4.0    16093
3.0    11975
1.0     8837
2.0     6395
6.0      361
Name: 幸福指数, dtype: int64
----------------------------
缺失值的个数: 5
------------------------------------------------
  • 是否通过快递买过东西:转成哑变量,同时用众数填补缺失值;
  • 所处的县的大小:转成哑变量,同时用众数填补缺失值;
  • 其他字段则改成0-1编码。

我们也可以来看下县的大小和购买结果的关系:

sns.countplot(x='N2NCY',hue='resp_flag',data=data_01)
plt.xlabel('县的大小')
plt.ylabel('购买数量')

在这里插入图片描述
不难看出,县越大的购买人数就越多。跟我们的日常理解差不多。

3.1.2.8 探索家庭收入

老套路:

#将51_59列取出来并进行翻译
data51_59 = chinese(data_01[feature_dict.变量名[51:59].tolist()])
data51_59.head()

在这里插入图片描述

fre(data51_59)

输出结果:

字段名: 是否有房子
----------
字段数据类型: object
----------------------------
Y    31478
U     4747
P     4604
R     2623
T      204
Name: 是否有房子, dtype: int64
----------------------------
缺失值的个数: 10
------------------------------------------------


字段名: 是否有医保补贴
----------
字段数据类型: object
----------------------------
U    21612
C     8972
A     6765
B     6306
Name: 是否有医保补贴, dtype: int64
----------------------------
缺失值的个数: 11
------------------------------------------------


字段名: 收入所处排名
----------
字段数据类型: float64
----------------------------
79.0    713
61.0    648
55.0    647
82.0    636
88.0    607
       ... 
25.0    258
46.0    254
17.0    246
23.0    225
42.0    162
Name: 收入所处排名, Length: 99, dtype: int64
----------------------------
缺失值的个数: 2
------------------------------------------------


字段名: 普查家庭有效购买收入
----------
字段数据类型: int64
----------------------------
48     1034
45      994
44      980
47      967
49      957
       ... 
230       1
198       1
154       1
164       1
191       1
Name: 普查家庭有效购买收入, Length: 208, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 家庭收入
----------
字段数据类型: float64
----------------------------
49.0     746
46.0     713
63.0     710
59.0     687
60.0     683
        ... 
246.0      1
221.0      1
218.0      1
207.0      1
241.0      1
Name: 家庭收入, Length: 224, dtype: int64
----------------------------
缺失值的个数: 7
------------------------------------------------


字段名: 家庭房屋价值
----------
字段数据类型: float64
----------------------------
999.0    1371
138.0     244
168.0     233
125.0     232
175.0     207
         ... 
956.0       1
796.0       1
983.0       1
940.0       1
20.0        1
Name: 家庭房屋价值, Length: 983, dtype: int64
----------------------------
缺失值的个数: 15
------------------------------------------------


字段名: 社会经济地位评分
----------
字段数据类型: int64
----------------------------
77     1194
75     1153
78     1119
80     1119
79     1114
       ... 
166      13
169      12
162      11
165      11
168       8
Name: 社会经济地位评分, Length: 111, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 家庭自成立日起的时间
----------
字段数据类型: float64
----------------------------
55.0    3257
56.0    3110
54.0    3034
53.0    2866
57.0    2826
        ... 
28.0       3
25.0       2
26.0       1
81.0       1
20.0       1
Name: 家庭自成立日起的时间, Length: 64, dtype: int64
----------------------------
缺失值的个数: 15
------------------------------------------------
  • 是否有房子:转成哑变量,同时用众数填补缺失值;
  • 是否有医保补贴:转成哑变量,同时用众数填补缺失值;
  • 收入所处排名:有缺失值,用中位数填补缺失值;
  • 家庭收入:有缺失值,用中位数填补缺失值;
  • 家庭自成立的时间:有缺失值,用中位数填补缺失值;

接下来我们同样来查看属性间的相关性,看看哪些是需要删除的。

sns.heatmap(zero_one(data51_59).corr(),cmap='Blues')

在这里插入图片描述
其实主要就是中间这一部分颜色特别深。

higt_cor(data51_59)

[‘收入所处排名’, ‘普查家庭有效购买收入’, ‘家庭收入’, ‘家庭房屋价值’, ‘社会经济地位评分’]

从我们的认知来讲,这些自然是存在高度相关性的,我们可以考虑保留普查家庭有效购买收入这一个属性。

3.1.2.9 探索所处地区情况

老套路:

#将59列之后取出来并进行翻译
data59 = chinese(data_01[feature_dict.变量名[59:].tolist()])
data59.head()

在这里插入图片描述
很明显,像这个贫穷以下人的比例和所处地区mobile home的比例这两个属性,我们一看就可以知道是相关性很高的,当然其他字段也是,我们还是用定义好的函数来看一下情况。

fre(data59)

输出结果为:

字段名: 该客户被多少个名单source 包含
----------
字段数据类型: int64
----------------------------
8     4168
9     4152
10    4131
7     3878
11    3735
6     3654
5     3400
4     3144
3     3044
12    3038
2     2229
13    2017
14    1243
1      728
15     630
16     310
17     104
18      48
19      13
Name: 该客户被多少个名单source 包含, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 所处的省份
----------
字段数据类型: object
----------------------------
CA    11700
OH     8959
NY     6481
IN     4954
CT     2873
MO     2202
WI     1884
GA     1687
KY     1375
NH      904
ME      647
Name: 所处的省份, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 贫穷以上人的比例
----------
字段数据类型: int64
----------------------------
99    4081
98    3624
97    3440
96    3307
95    2885
      ... 
17       1
25       1
35       1
22       1
15       1
Name: 贫穷以上人的比例, Length: 81, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 所处地区有多少居住小区在2000年及以后建立
----------
字段数据类型: float64
----------------------------
4.0     2467
2.0     2401
3.0     2364
5.0     2317
1.0     2305
        ... 
81.0       5
90.0       4
95.0       4
86.0       3
94.0       2
Name: 所处地区有多少居住小区在2000年及以后建立, Length: 100, dtype: int64
----------------------------
缺失值的个数: 5
------------------------------------------------


字段名: 所处地区蓝领所占百分比
----------
字段数据类型: int64
----------------------------
18    1717
16    1706
20    1696
17    1679
22    1670
      ... 
61       3
64       2
63       2
67       1
58       1
Name: 所处地区蓝领所占百分比, Length: 66, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 贫穷以下人的比例
----------
字段数据类型: int64
----------------------------
2     3604
3     3464
4     3308
1     3082
5     2884
      ... 
65       1
99       1
83       1
78       1
85       1
Name: 贫穷以下人的比例, Length: 82, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 所处地区mobile home的比例
----------
字段数据类型: int64
----------------------------
0     33478
1      1677
2      1442
3       906
4       658
      ...  
85        2
66        2
73        1
99        1
78        1
Name: 所处地区mobile home的比例, Length: 96, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 离婚或者分居人群所占比例
----------
字段数据类型: int64
----------------------------
14    3288
15    3254
16    3162
13    3100
17    3073
12    2826
18    2805
11    2525
19    2345
10    2139
20    2055
9     1727
21    1724
22    1484
8     1228
23    1154
24    1050
7      999
25     656
26     517
6      500
27     414
5      282
28     266
29     199
30     175
31     135
4      130
32     106
0       62
33      56
3       45
34      39
36      38
2       22
35      21
37      13
38      12
42       7
39       6
41       5
1        5
50       4
40       4
55       3
44       2
48       1
47       1
46       1
43       1
Name: 离婚或者分居人群所占比例, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 已婚人群所占比例
----------
字段数据类型: int64
----------------------------
53    1221
52    1209
48    1164
51    1161
40    1157
      ... 
81       4
79       2
80       2
78       1
82       1
Name: 已婚人群所占比例, Length: 83, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 有房子人所占比例
----------
字段数据类型: int64
----------------------------
85    972
89    913
88    890
81    877
86    856
     ... 
7     120
3     116
97     69
98      9
99      8
Name: 有房子人所占比例, Length: 100, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 独宅住户所占比例
----------
字段数据类型: float64
----------------------------
99.0    6996
98.0    1213
0.0     1193
97.0    1043
95.0     994
        ... 
14.0     142
19.0     139
17.0     138
27.0     123
24.0     122
Name: 独宅住户所占比例, Length: 100, dtype: int64
----------------------------
缺失值的个数: 23
------------------------------------------------


字段名: 有小孩的家庭所占比例
----------
字段数据类型: int64
----------------------------
32    1777
33    1754
34    1719
30    1704
31    1690
      ... 
79       2
76       2
99       1
86       1
83       1
Name: 有小孩的家庭所占比例, Length: 86, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 白领所占比例
----------
字段数据类型: float64
----------------------------
61.0    1198
59.0    1098
63.0    1098
57.0    1072
64.0    1071
        ... 
16.0       3
11.0       2
13.0       1
1.0        1
14.0       1
Name: 白领所占比例, Length: 91, dtype: int64
----------------------------
缺失值的个数: 14
------------------------------------------------


字段名: 所处地区居住年限
----------
字段数据类型: float64
----------------------------
19.0    6287
12.0    2756
1.0     2319
17.0    2125
7.0     2013
        ... 
96.0       1
59.0       1
68.0       1
98.0       1
67.0       1
Name: 所处地区居住年限, Length: 70, dtype: int64
----------------------------
缺失值的个数: 6
------------------------------------------------


字段名: Individual ID
----------
字段数据类型: int64
----------------------------
142936063    1
68797618     1
193209817    1
172309160    1
171816124    1
            ..
172641155    1
76214763     1
228943803    1
185032198    1
1966080      1
Name: Individual ID, Length: 43666, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: 所在地区处方药计划覆盖的比例
----------
字段数据类型: int64
----------------------------
46    5765
65    2634
47    2518
61    2002
42    1782
      ... 
86       1
88       1
27       1
93       1
95       1
Name: 所在地区处方药计划覆盖的比例, Length: 69, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------


字段名: zip level的家庭收入排名
----------
字段数据类型: int64
----------------------------
0    6326
9    5119
8    4784
1    4657
7    4191
2    3998
6    3896
3    3611
5    3549
4    3535
Name: zip level的家庭收入排名, dtype: int64
----------------------------
缺失值的个数: 0
------------------------------------------------
  • 所处地区有多少居住小区在2000年及以后建立:有5个缺失值,可以用中位数填充缺失值;

  • 独宅住户所占比例:有23个缺失值,可以用中位数填充缺失值;

  • 白领所占比例:有14个缺失值,可以用中位数填充缺失值;

  • 所处地区居住年限:有6个缺失值,可以用中位数填充缺失值;

  • Individual ID:前面就说过啦,可以删掉这个无用数据;

  • 所在地区处方药计划覆盖的比例:和是否有医疗补贴感觉也存在相关性,可以先mark一下;

  • 所处的省份:转成哑变量。

可以看下省份之间跟购买结果的情况;

sns.countplot(x='STATE_NAME',hue='resp_flag',data=data_01)
plt.xlabel('所处的省份')
plt.ylabel('购买数量')

在这里插入图片描述
从图可以看出,我们后面营销时重点可以放在NY和OH这两个地方,特别是NY这个地方。

接着我们可以单独将与人群有关的数据拿出来看下彼此之间的相关性。

a = chinese(data_01[["c210apvt","c210blu","c210bpvt","c210mob","c210wht","zhip19"]])
sns.heatmap(a.corr(),cmap='Blues')

在这里插入图片描述
可以看到这里还会出现一些负的相关性,主要是因为我们的数据是比例数据。

从产品的目的来讲,我们的主要目标人群肯定是贫穷以上的人(穷人都这么穷了,应该不会再花钱购买保险了)

接着我们再调用一下方法看下那些相关性高的属性。

higt_cor(data59)

[‘贫穷以上人的比例’, ‘已婚人群所占比例’, ‘有房子人所占比例’, ‘独宅住户所占比例’]

但是这里确实不太适合用这种方法,因为数据主要是比例而不是0-1数据。

当然我们可以看一下所处地区情况的所有字段之间的相关性:

sns.heatmap(data59.corr(),cmap='brg')

在这里插入图片描述
这里越绿相关性越好。

我们这里考虑删掉所处地区蓝领所占百分比、贫穷以下人的比例、有房子人所占比例这三个字段给删掉。

好,到这里,我们数据探索的过程就搞定了,接下来就是通过数据,将我们做好标记的(哪些是要转的、哪些是要删的、哪些是要填充的)数据进行处理。

3.1.3 数据清洗

这里我们还是先备份一下数据:

data_02 = data_01.copy()
data_02.shape

(43666, 76)

3.1.3.1 删除特征

我们从EXCEL找出要删除的字段,直接全部删掉,就不用一个个找啦。

del_col = ["KBM_INDV_ID","U18","POEP","AART","AHCH","AASN","COLLEGE",
 "INVE","c210cip","c210hmi","c210hva","c210kses","c210blu","c210bpvt","c210poo","KBM_INDV_ID","meda"]

data_02 = data_02.drop(columns=del_col)
data_02.shape

(43666, 60)

3.1.3.2 删除重复值

我记得我们是没有发现重复值的,不过一般我们还是会进行这一环节的(而且也很简单)。

data_02.drop_duplicates().shape

(43666, 60)

果然,没有重复值。

3.1.3.3 划分训练集与测试集

一定要先划分数据集再填充、转码,别急!

from sklearn.model_selection import train_test_split

y = data_02.pop('resp_flag') #标签
X = data_02  #特征

Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=100)

# 最好还是再备份一下,以防万一
Xtrain_01=Xtrain.copy()
Xtest_01=Xtest.copy()
Ytrain_01=Ytrain.copy()
Ytest_01=Ytest.copy()

3.1.3.4 填充缺失值

填充中位数

老规矩,我们打开EXCEL,找到我们标记了需要填充中位数的字段。

fil = ["age","c210mah","c210b200","c210psu","c210wht","ilor"]

Xtrain_01[fil].median()

输出结果为:

age         71.0
c210mah     53.0
c210b200    10.0
c210psu     77.0
c210wht     61.0
ilor        15.0
dtype: float64
dic = dict(zip(Xtrain_01[fil].median().index,Xtrain_01[fil].median()))
dic

输出结果为:

{'age': 71.0,
 'c210mah': 53.0,
 'c210b200': 10.0,
 'c210psu': 77.0,
 'c210wht': 61.0,
 'ilor': 15.0}
#向训练集填充中位数
Xtrain_01 = Xtrain_01.fillna(dic) 

填充众数

基本同样的操作:

mod = ["N1819","ASKN","MOBPLUS","N2NCY","LIVEWELL","HOMSTAT","HINSUB"]

dic_mod = dict(zip(Xtrain_01[mod].mode().columns,Xtrain_01[mod].iloc[0,:]))

Xtrain_01 = Xtrain_01.fillna(dic_mod) 

替换填充

这里可不要忘了我们最开始发现的0换N。

Xtrain_01['N6064'] = Xtrain_01['N6064'].replace('0','N')  #0  替换成  N

Xtrain_01.isnull().sum()[Xtrain_01.isnull().sum()!=0]

Series([], dtype: int64)

对测试集进行填充(总结)

这里测试集也是一样的方式:

# 需要填的字段
fil = ["age","c210mah","c210b200","c210psu","c210wht","ilor"]

#填充中位数--测试集

dic = dict(zip(Xtest_01[fil].median().index,Xtest_01[fil].median()))

Xtest_01 = Xtest_01.fillna(dic) 

# #填充众数--测试集
mod = ["N1819","ASKN","MOBPLUS","N2NCY","LIVEWELL","HOMSTAT","HINSUB"]

dic_mod = dict(zip(Xtest_01[mod].mode().columns,Xtest_01[mod].iloc[0,:]))

Xtest_01 = Xtest_01.fillna(dic_mod) 

# #替换填充
Xtest_01['N6064'] = Xtest_01['N6064'].replace('0','N') 

Xtest_01.isnull().sum()[Xtest_01.isnull().sum() !=0]

3.1.4 转码

打开我们记录好的EXCEL文件,找到需要进行转码的数据。

不过我们可以发现确实是有点多,就不适合一个个复制出来啦,我们可以写一个方法,读取EXCEL文件,把变量名和转码的两列字段复制到另一个sheet里,方便读取。

encod_col = pd.read_excel('保险案例数据字典_清洗.xlsx',sheet_name=2)
encod_col.head()

在这里插入图片描述

# 查看Xtrain_01中object类型
object_tr =Xtrain_01.describe(include='O').columns
object_tr

输出结果为:

Index(['GEND', 'ADBT', 'ADEP', 'AHBP', 'ARES', 'AHRT', 'ADGS', 'AHRL', 'ASKN',
       'AVIS', 'BANK', 'FINI', 'INLI', 'INMEDI', 'IOLP', 'MOBPLUS', 'N2NCY',
       'N1819', 'N2029', 'N3039', 'N4049', 'N5059', 'N6064', 'N65P', 'ONLA',
       'SGFA', 'SGLL', 'SGOE', 'SGSE', 'SGTC', 'POC19', 'HOMSTAT', 'HINSUB',
       'STATE_NAME'],
      dtype='object')
#检查一下转码的目标是否出现
np.setdiff1d(object_tr,encod_col['变量名'])

array([], dtype=object)

3.1.4.1 0-1转码

# 获取0-1 转码的变量名
z_0_list = encod_col[encod_col['转']=='0-1'].变量名
z_0_list.head()

输出结果为:

0     GEND
3    N1819
4    N2029
5    N3039
6    N4049
Name: 变量名, dtype: object
# 重新定义一个Xtrain_02,用于转码
Xtrain_02 = Xtrain_01[z_0_list]
Xtrain_02.head()

在这里插入图片描述

#sklearn的预处理模块
from sklearn.preprocessing import OrdinalEncoder

#fit_transform 直接转
new_arr = OrdinalEncoder().fit_transform(Xtrain_02)
new_arr

在这里插入图片描述
下面我们来做下合并。

# columns 设置表头为原来的   index 索引也是原来
Xtrain_02 = pd.DataFrame(data=new_arr,columns=Xtrain_02.columns,index=Xtrain_02.index)
Xtrain_02.head()

在这里插入图片描述
将转好的Xtrain_02 0-1编码变量 替换掉Xtrain_01:

Xtrain_01[z_0_list] = Xtrain_02

Xtrain_01.head()

在这里插入图片描述

3.1.4.2 哑变量转码

注意:哑变量转码有两种方法,一种是pandas中的get_dummies方法,另一种是sklearn中的onehot方法。区别主要在于pandas中的方法只能处理字符变量不能处理数值变量,而sklearn都可以,所以如果用get_dummies处理数值型数据的话得先将数值型数据转换成字符型数据。

下面我们用get_dummies来实现转码。

#获取哑变量---转码的变量名

o_h_list = encod_col[encod_col['转']=='哑变量'].变量名
o_h_list

输出结果为:

1        c210mys
2          POC19
24       MOBPLUS
25         N2NCY
32      LIVEWELL
33       HOMSTAT
34        HINSUB
35    STATE_NAME
Name: 变量名, dtype: object
Xtrain_01[o_h_list].head()

在这里插入图片描述

o_h_01 = ['c210mys','LIVEWELL'] #非字符型的变量
o_h_02 = [i for i in o_h_list if i not in o_h_01] #字符类型的变量

#先转o_h_02
Xtrain_02 = Xtrain_01.copy()
chinese(Xtrain_02[o_h_02]).head()

在这里插入图片描述

Xtrain_02 = pd.get_dummies(chinese(Xtrain_02[o_h_02]))
Xtrain_02.head()

在这里插入图片描述

#w我们再转 o_h_01
Xtrain_03 = Xtrain_01.copy()

#转成字符类型
Xtrain_03 = Xtrain_03[o_h_01].astype(str)
#转化覆盖
Xtrain_03 = pd.get_dummies(chinese(Xtrain_03[o_h_01]))

Xtrain_03.head()

在这里插入图片描述
Xtrain_02 Xtrain_03 是转好的 先删除原转码的字段再将转好的插入到数据集中。

# Xtrain_04 删除原转码的字段
Xtrain_04 = Xtrain_01.copy() 
Xtrain_04 = chinese(Xtrain_04.drop(columns=o_h_01+o_h_02))
Xtrain_04.head()

在这里插入图片描述

Xtrain_04.shape

(30566, 51)

Xtrain_02.shape #字符的哑变量

(30566, 31)

Xtrain_03.shape #非字符的哑变量

(30566, 14)

#将 Xtrain_04  Xtrain_02 Xtrain_03 合并
Xtrain_05 = pd.concat([Xtrain_04,Xtrain_02,Xtrain_03],axis=1)
Xtrain_05.shape

(30566, 96)

Xtrain_05.head()

在这里插入图片描述

3.1.4.3 对测试集进行转码(总结)

0-1 转码总结:

#获取需要转码的字段
encod_col = pd.read_excel('保险案例数据字典_清洗.xlsx',sheet_name=2)

# 查看Xtest_01中object类型
object_tr =Xtest_01.describe(include='O').columns

#检查一下转码的目标是否出现
np.setdiff1d(object_tr,encod_col['变量名'])

#0-1 转码
# 获取0-1 转码的变量名
z_0_list = encod_col[encod_col['转']=='0-1'].变量名

Xtest_02 = Xtest_01[z_0_list]

#sklearn的预处理模块
from sklearn.preprocessing import OrdinalEncoder

#fit_transform 直接转
new_arr = OrdinalEncoder().fit_transform(Xtest_02)
# columns 设置表头为原来的   index 索引也是原来
Xtest_02 = pd.DataFrame(data=new_arr,columns=Xtest_02.columns,index=Xtest_02.index)

Xtest_01[z_0_list] = Xtest_02

Xtest_01.head()

在这里插入图片描述
哑变量 总结

#获取哑变量转码的变量
o_h_list = encod_col[encod_col['转']=='哑变量'].变量名

o_h_01 = ['c210mys','LIVEWELL'] #非字符型的变量
o_h_02 = [i for i in o_h_list if i not in o_h_01] #字符类型的变量

#先转o_h_02 字符类型
Xtest_02 = Xtest_01.copy()
Xtest_02 = pd.get_dummies(chinese(Xtest_02[o_h_02]))

#w我们再转 o_h_01  非字符
Xtest_03 = Xtest_01.copy()
#转成字符类型
Xtest_03 = Xtest_03[o_h_01].astype(str)
#转化覆盖
Xtest_03 = pd.get_dummies(chinese(Xtest_03[o_h_01]))


# Xtrain_04 删除原转码的字段
Xtest_04 = Xtest_01.copy() 
Xtest_04 = chinese(Xtest_04.drop(columns=o_h_01+o_h_02))


#将 Xtest_04  Xtest_02 Xtest_03 合并
Xtest_05 = pd.concat([Xtest_04,Xtest_02,Xtest_03],axis=1)
Xtest_05.shape

(13100, 96)

Xtest_05.head()

在这里插入图片描述

3.1.5 数据建模与评估

3.1.5.1 初步建模

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score

clf = DecisionTreeClassifier(random_state=420,class_weight='balanced')
cvs = 
cross_val_score(clf,Xtrain_05,Ytrain)

cvs.mean()

0.5993914576104883

3.1.5.2 网格搜索找最优参数

from sklearn.model_selection import GridSearchCV

#测试参数
param_test = {
             'splitter':('best','random'),
             'criterion':('gini','entropy'), #基尼  信息熵
             'max_depth':range(3,15) #最大深度
             #,min_samples_leaf:(1,50,5)
}

gsearch= GridSearchCV(estimator=clf, #对应模型
                param_grid=param_test,#要找最优的参数
                scoring='roc_auc',#准确度评估标准 
                n_jobs=-1,# 并行数  个数   -1:跟CPU核数一致
                cv = 5,#交叉验证 5折
                iid=False,# 默认是True  与各个样本的分布一致 
                verbose=2#输出训练过程
                )

gsearch.fit(Xtrain_05,Ytrain_01)

输出结果如下:

GridSearchCV(cv=5,
             estimator=DecisionTreeClassifier(class_weight='balanced',
                                              random_state=420),
             iid=False, n_jobs=-1,
             param_grid={'criterion': ('gini', 'entropy'),
                         'max_depth': range(3, 15),
                         'splitter': ('best', 'random')},
             scoring='roc_auc', verbose=2)
#优化期间观察到的最高评分
gsearch.best_score_

0.691856415170639

gsearch.best_params_

{‘criterion’: ‘entropy’, ‘max_depth’: 6, ‘splitter’: ‘best’}

3.1.5.3 模型评估

from sklearn.metrics import accuracy_score #准确率
from sklearn.metrics import precision_score #精准率
from sklearn.metrics import recall_score #召回率
from sklearn.metrics import roc_curve
y_pre = gsearch.predict(Xtest_05)

accuracy_score(y_pre,Ytest) 

0.6090076335877863

precision_score(y_pre,Ytest)

0.748152359295054

recall_score(y_pre,Ytest)

0.5100116264048572

接下来我们来画图看一下ROC曲线:

fpr,tpr,thresholds = roc_curve(y_pre,Ytest) #roc参数
import matplotlib.pyplot as plt

plt.plot(fpr,tpr,c='b',label='roc曲线')
plt.plot(fpr,fpr,c='r',ls='--')

在这里插入图片描述
所以综上来看,我们的模型效果算是不好不坏吧。

3.1.5.4 输出规则

#最优参数
#{'criterion': 'entropy', 'max_depth': 6, 'splitter': 'best'}
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree

import graphviz

#将最优参数放到分类器
clf = DecisionTreeClassifier(criterion='entropy',max_depth=6,splitter='best')
clf = clf.fit(Xtrain_05,Ytrain)


features = Xtrain_05.columns
dot_data = tree.export_graphviz(clf,
                     feature_names=features,
                     class_names=['Not Buy','Buy'],
                     filled=True,
                     rounded=True,
                     leaves_parallel=False)

graph= graphviz.Source(dot_data)

graph

我们来看下图:
在这里插入图片描述

3.1.6 输出结果的商业应用

我们来看一下购买比例最高的两类客户的特征是什么?

结合上图,我们只要看蓝色的部分,可以得到以下结果。

3.1.6.1 第一类

  • 处于医疗险覆盖率比例较低区域
  • 居住年限小于7年
  • 65-72岁群体

那么我们对业务人员进行建议的时候就是,建议他们在医疗险覆盖率比例较低的区域进行宣传推广,然后重点关注那些刚到该区域且年龄65岁以上的老人,向这些人群进行保险营销,成功率应该会更高。

3.1.6.2 第二类

  • 处于医疗险覆盖率比例较低区域
  • 居住年限大于7年(小于)年
  • 居住房屋价值较高

这一类人群,是区域内常住的高端小区的用户。这些人群也同样是我们需要重点进行保险营销的对象。

除此之外,我们还可以做些什么?

3.1.6.3 了解客户需求

我们需要了解客户的需求,并根据客户的需求举行保险营销。PIOS数据∶向客户推荐产品,并利用个人的数据(个人特征)向客户推荐保险产品。旅行者∶根据他们自己的数据(家庭数据),生活阶段信息推荐的是财务保险、人寿保险、保险、旧保险和用户教育保险。外部数据、资产保险和人寿保险都提供给高层人士,利用外部数据,我们可以改进保险产品的管理,增加投资的收益。

3.1.6.4 开发新的产品

保险公司还应协助外部渠道开发适合不同商业环境的保险产品,例如新的保险类型,如飞行延误保险、旅行时间保险和电话盗窃保险。目的是提供其他保险产品,而不是从这些保险中受益,而是寻找潜在的客户。此外,保险公司将通过数据分析与客户联系,了解客户。外部因素将降低保险的营销成本,并直接提高投资回报率。

结束语

本次项目真的非常非常的详细,从这次项目我们也不难发现,数据探索和可视化这一部分是最花时间的,而这一部分也就是为了数据清洗来服务的(当然最终也可以结合可视化得到的结论),后续建模部分反而花的工夫不是很多。因为数据质量的好坏其实是决定最终效果的最大因素,如果我们不先了解清楚数据并进行清洗转换,后续工作也变得没有什么意义。

总之,过程虽然繁琐,但也饱含着乐趣,能从繁琐的数据中得到更多有用的结论,也显得那么的有意义。

CSDN@报告,今天也有好好学习


点击全文阅读


本文链接:http://zhangshiyu.com/post/30893.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1