一、项目背景
项目实战背景, 数据集来自kaggle:https://www.kaggle.com/anmolkumar/health-insurance-cross-sell-prediction
保险公司最近新推出了一款汽车保险,想知道去年的投保人是否会对这款汽车保险感兴趣,以便有针对性的进行推广销售。我们要做的是建立模型,来预测客户是否对汽车保险感兴趣。这对保险公司来说是非常有帮助的,保险公司可以据此制定沟通策略,接触这些客户,并优化其商业模式和收入。
这里介绍下保险的概念,保险单指的是,保险公司承诺为特定类型的损失、损害、疾病或死亡提供赔偿保证,客户则需要定期向保险公司支付一定的保险费。和医疗保险一样,买了车险的话,每年都需要向保险公司支付一定数额的保险费,这样在车辆发生意外事故时,保险公司将向客户提供赔偿(称为“保险金额”)。
二、数据探索
2.1数据理解
为了预测客户是否对车辆保险感兴趣,我们需要了解一些客户信息 (性别、年龄等)、车辆(车龄、损坏情况)、保单(保费、采购渠道)等信息。
数据划分为训练集和测试集,训练数据包含381109笔客户资料,每笔客户资料包含12个字段,1个客户ID字段、10个输入字段及1个目标字段-Response是否响应(1代表感兴趣,0代表不感兴趣)。测试数据包含127037笔客户资料;字段个数与训练数据相同,目标字段没有值。字段的定义可参考下文。
2.2数据读入
# 读入训练集
train= pd.read_csv('train.csv')
# 读入测试集
test= pd.read_csv('test.csv')
print("查看基本信息")
print(train.info())
print(train.shape)
2.3探索性分析
下面,我们基于训练数据集进行探索性数据分析。
2.3.1描述性分析
首先对数据集中数值型属性进行描述性统计分析。
desc_table = train.drop(['id''], axis=1).describe().T
通过描述性分析后,可以得到以下结论。从以上描述性分析结果可以得出:
客户年龄:客户的年龄范围在20 ~ 85岁之间,平均年龄是38岁,青年群体居多;
是否有驾照:99.89%客户都持有驾照;
之前是否投保:45.82%的客户已经购买了车辆保险;
年度保费:客户的保费范围在2630 ~ 540165之间,平均的保费金额是30564。
往来时长:此数据基于过去一年的数据,客户的往来时间范围在10~299天之间,平均往来时长为154天。
是否响应:平均来看,客户对车辆保险感兴趣的概率为12.25%。
2.3.2 可视化分析
2.3.2.1目标变量的分布
训练集共有381109笔客户资料,其中感兴趣的有46710人,占比12.3%,不感兴趣的有334399人,占比87.7%。
values= train['Response'].value_counts().values.tolist()
plt.pie(values,labels=['Not interested','Interested'],startangle=90,autopct='%1.1f%%')
plt.legend(loc="upper right")
plt.savefig('explore/目标变量分布.png')
2.3.2.2性别因素
从条形图可以看出,男性的客户群体对汽车保险感兴趣的概率稍高,是13.84%,相较女性客户高出3个百分点。
print("Gender:\n",train['Gender'].value_counts())
data_Gender=train.groupby(['Gender','Response'])['Response'].count().unstack()
data_Gender['interestedRate']=data_Gender[1]/(data_Gender[1]+data_Gender[0])
f,ax1=plt.subplots(figsize=(16,9))
data_Gender[[0,1]].plot(kind='bar',ax=ax1,rot=0,fontsize=14)
ax2=ax1.twinx()
data_Gender['interestedRate'].plot(ax=ax2,style='g.-',fontsize=14)
plt.title('Response by Gender',fontsize=20)
ax1.set_xlabel('Gender',fontsize=16)
ax1.set_ylabel('Count',fontsize=16)
ax2.set_ylabel('interestRate',fontsize=16)
ax2.legend(loc='center right',fontsize=14)
ax1.legend(fontsize=14)
plt.savefig("explore/Gender-Response.png")
2.3.2.3之前是否投保
没有购买汽车保险的客户响应概率更高,为22.54%,有购买汽车保险的客户则没有这一需求,感兴趣的概率仅为0.09%。
data_Previously_Insured=train.groupby(['Previously_Insured','Response'])['Response'].count().unstack()
data_Previously_Insured['interestedRate']=data_Previously_Insured[1]/(data_Previously_Insured[1]+data_Previously_Insured[0])
f,ax1=plt.subplots(figsize=(16,9))
data_Previously_Insured[[0,1]].plot(kind='bar',ax=ax1,rot=0,fontsize=14)
ax2=ax1.twinx()
data_Previously_Insured['interestedRate'].plot(ax=ax2,style='g.-',fontsize=14)
plt.title('Response by Previously_Insured',fontsize=20)
ax1.set_xlabel('Previously_Insured',fontsize=16)
ax1.set_ylabel('Count',fontsize=16)
ax2.set_ylabel('interestRate',fontsize=16)
ax2.legend(loc='center right',fontsize=14)
ax1.legend(fontsize=14)
plt.savefig("explore/Previously_Insured-Response.png")
2.3.2.4车龄因素
车龄越大,响应概率越高,大于两年的车龄感兴趣的概率最高,为29.37%,其次是1~2年车龄,概率为17.38%。小于1年的仅为4.37%。
2.3.2.5车辆损坏情况
车辆曾经损坏过的客户有较高的响应概率,为23.76%,相比之下,客户过去车辆没有损坏的响应概率仅为0.26%。
2.3.2.6. 不同年龄
从直方图中可以看出,年龄较高的群体和较低的群体响应的概率较低,30~60岁之前的客户响应概率较高。80-85以上是最感兴趣的。
temp= pd.DataFrame()
temp1= pd.cut(train.loc[train['Response']==0,'Age'],15).value_counts()
Index= pd.cut(train.loc[train['Response']==0,'Age'],15).value_counts().index
temp1.reset_index(drop=True,inplace=True)
temp2= pd.cut(train.loc[train['Response']==1,'Age'],15).value_counts()
temp2.reset_index(drop=True,inplace=True)
temp= pd.concat([temp1,temp2],axis=1)
temp.columns= ['not interest',"interest"]
temp['interestRate']=temp['interest']/(temp['interest']+temp['not interest'])
temp.index= Index
f,ax=plt.subplots(figsize=(16,9))
_list= []
_list2= []
print(type(pd.cut(train.loc[train['Response']==0,'Age'],15).iloc[:-1]),
pd.cut(train.loc[train['Response']==0,'Age'],15).iloc[:-1].count())
for i,vin pd.cut(train.loc[train['Response']==0,'Age'],15).iloc[:-1].items():
_list.append(v.left)
_list.append(v.right)
_list2.append((v.left+v.right)/2)
_list= sorted(list(set(_list)))
_list2= sorted(list(set(_list2)))
print(_list,len(_list))
print(_list2,len(_list2))
train.loc[(train['Response']==0),'Age'].plot(xticks =_list,
kind='hist',bins=15,ax=ax,label='not interest')
train.loc[(train['Response']==1),'Age'].plot(xticks =_list,
kind='hist',bins=15,ax=ax,label='interest')
ax.legend(loc='best',fontsize=14)
ax2.legend(loc='center right',fontsize=14)
ax2=ax.twinx()
ax2.plot(_list2,temp['interestRate'].values.tolist(),"green")
ax.set_title('Response by Age',fontsize=20)
ax.set_xlabel('Age',fontsize=16)
ax.set_ylabel('Count',fontsize=16)
ax2.set_ylabel('interestRate',fontsize=16)
plt.savefig('explore/Age-Response.png')
三、数据预处理
此部分工作主要包含字段选择,数据清洗和数据编码,字段的处理如下:
Annual_Premium:异常值处理
Gender、Vehicle_Age、Vehicle_Damage:分类型数据转换为数值型编码
# 盖帽法处理异常值
f_max= train['Annual_Premium'].mean()+ 3*train['Annual_Premium'].std()
f_min= train['Annual_Premium'].mean()- 3*train['Annual_Premium'].std()
train.loc[train['Annual_Premium']> f_max,'Annual_Premium']= f_max
train.loc[train['Annual_Premium']< f_min,'Annual_Premium']= f_min
# 数据编码
train['Gender']= train['Gender'].map({'Male': 1,'Female': 0})
train['Vehicle_Damage']= train['Vehicle_Damage'].map({'Yes': 1,'No': 0})
train['Vehicle_Age']= train['Vehicle_Age'].map({'< 1 Year': 0,'1-2 Year': 1,'> 2 Years': 2})
四、数据建模
处理样本不平衡,对0类样本进行降采样
from imblearn.under_samplingimport RandomUnderSampler
under_model= RandomUnderSampler(sampling_strategy={0:133759,1:37368},random_state=0)
X, y= under_model.fit_sample(X, y)
print("下采样后的数据",y.value_counts())
X_train, X_val, y_train, y_val= train_test_split(X, y,test_size=0.2,stratify=y,random_state=0)
数据归一化
from sklearn.preprocessingimport StandardScaler, MinMaxScaler
mms= MinMaxScaler()
X_train_scaled= pd.DataFrame(mms.fit_transform(X_train),columns=X.columns)
X_val_scaled= pd.DataFrame(mms.transform(X_val),columns=X.columns)
print("归一化的数据:\n",X_train_scaled.head())
KNN算法,使用学习曲线得到最优k值8,最优准确率0.781
knn_scores= []
for iin range(3,10,1):
knn= KNeighborsClassifier(n_neighbors=i,n_jobs=-1)
knn.fit(X_train_scaled, y_train)
y_pred= knn.predict(X_val_scaled)
knn_scores.append(accuracy_score(y_true=y_val,y_pred=y_pred))
plt.figure(figsize=(10,10))
plt.plot(range(3,10,1),knn_scores)
plt.title("knn_scores by k")
plt.savefig("explore/knn_scores.png")