数据挖掘案例——药店流失会员预测:什么样的会员容易流失?

一、背景

随着药店市场刮起了兼并重组风潮,大量资金涌入医药市场,药店行业竞争进入白热化。同时,“互联网+”及“大数据”为药店引来了新的机遇和挑战,大部分药店都在从传统零售行业转入“服务型”零售,实行“会员制”,进行会员差异化的管理和服务是其中最关键的一环。

本文主要利用药店会员销售数据,筛选出其中“已流失会员”进行挖掘分析,探索“已流失会员”的会员属性及购买行为——具有哪些特征的会员易流失?

我们挑选了一家在二线城市的连锁药店进行分析,这家连锁2018年年销售额差不多在6700万,其中,会员的销售额占比在61.21%,通过对比分析,我们发现会员销售额占比较好的连锁为50%~70%,说明该连锁已经通过初期的“会员拉新”阶段,进入了会员管理期。其中流失会员(近6个月未到店购药的会员)占比39.10%。且从会员趋势的堆积图上,看流失会员的人数占比在逐步提升。

那么这些流失会员的特征是什么呢?

二、流失会员定义

首先,我们需要来定义流失会员,并按照条件对流失会员进行打标。

我们定义

  • 流失会员:年购买次数在4次(含4次)以上,近6个月未购药。

  • 留存会员:年购买次数在4次(含4次)以上,近6个月有购药。

为什么是4次,以下是2018年全年会员购物次数频率图,全年大于等于4次总计13709人,占比45.49%。鉴于药品是低频消费,我们认为一年4次的消费是比较稳定的会员。

三、数据集描述

该数据是收集了2018年会员年消费次数在4次以上的会员购药数据,该数据集有27个变量,6380个数据样本。具体变量的解析如下图:0~25号变量为特征变量,26号变量为标签变量。

pd.merge(dataset_column,pd.DataFrame(columns,columns=['标签']),how='inner',on='标签')

输出:

columns=['uid','year','sex','maxdate_diff_now','carddate_diff_now','paidmoney_one_year','paidmoney_x_one_year',
                       'paidmoney_z_one_year','paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year'
                        ,'paidmoney_s_one_year','paidmoney_r_one_year','paidmoney_t_one_year','paidmoney_4_one_year','paidmoney_5_one_year'
                        ,'paidmoney_9_one_year','quantity_one_year','count_one_year','mindatediff_one_year','maxdatediff_one_year'
                        ,'avgdatediff_one_year','paidmoney_one_order','quantity_one_order','count_one_order','label']
dataset_=dataset.loc[:,columns]
dataset_.head()

输出:

 部分数据集数据如上,因为篇幅原因,无法展示全部字段。

四、基本分析思路

以上研究的问题是一个二分类(流失会员or留存会员)问题,且上面的特征变量除了性别,都为数值型变量,对于二分类各个变量的比较,在统计学的分析当中,我们可以总结为以下两种:

  • 数值型数据:均值比较——方差分析检验显著性
  • 分类型数据:频数分布比较(交叉分析)——卡方分析检验显著性

各个特征变量做完分析之后,我们去研究变量对结果的权重。对于此问题,我们可以运用多个分类模型去训练数据,选出其中模型精度最高的模型,输出变量的重要程度排行。接下来我们就可以根据变量的重要程度来逐一归纳流失会员特征了。

详细的步骤,见下:

五、python库导入

可以为分析过程用到的包:

#1、数据处理
import pandas as pd
import numpy as np
from scipy import stats
from scipy.stats import chi2_contingency   #卡方检验
import numpy as np
import Ipynb_importer #自定义的导入notebook函数的启动包
import mysql_connect #自定义的连接数据库的包
#2、可视化
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.family']='SimHei' 
plt.rcParams['axes.unicode_minus']=False
#3、特征工程
import sklearn
from sklearn import preprocessing                            #数据预处理模块
from sklearn.preprocessing import LabelEncoder               #标签转码
from sklearn.preprocessing import StandardScaler             #归一化
from imblearn.over_sampling import SMOTE                     #过抽样
from sklearn.model_selection import train_test_split         #数据分区
from sklearn.decomposition import PCA  
#4、分类算法
from sklearn.ensemble import RandomForestClassifier     #随机森林
from sklearn.svm import SVC,LinearSVC                   #支持向量机
from sklearn.linear_model import LogisticRegression     #逻辑回归
from sklearn.neighbors import KNeighborsClassifier      #KNN算法
from sklearn.naive_bayes import GaussianNB             #朴素贝叶斯
from sklearn.tree import DecisionTreeClassifier        #决策树
#5、集成算法
import xgboost as xgb   
from xgboost import XGBClassifier 
from sklearn.ensemble import AdaBoostClassifier        
from sklearn.ensemble import GradientBoostingClassifier 
#6、模型评价
from sklearn.metrics import classification_report,precision_score,recall_score,f1_score  #分类报告
from sklearn.metrics import confusion_matrix           #混淆矩阵
from sklearn.metrics import silhouette_score           #轮廓系数
from sklearn.model_selection import GridSearchCV       #交叉验证
from sklearn.metrics import make_scorer
from sklearn.ensemble import VotingClassifier          #投票

六、数据清洗

1、检测缺失值

缺失的部分主要与销售金额相关,都为float类型。且存在大量的缺失值,部分超过90%。

根据实际的业务需求,我们暂时只对缺失值补0。都不删除,先看最后的效果,如果变量影响模型效果,可将缺失值较多的变量删除,变量的缺失值占比如下:

#缺失值占比
dataset_.isnull().sum()/len(dataset_)

输出:

#对缺失值补0
dataset_.fillna(0,inplace=True)
dataset_.info()

输出:

2、检测重复值

未检测到重复的样本。

dataset_.duplicated().sum()

输出:

0

3、检测数值类型

数值类型符合实际需求,暂时不用替换。

注意:数据要是转换为float,要重新检测缺失值。

dataset_.info()

输出:

4、检测标签分布

流失客户样本占比35.4%,留存客户样本占比64.6%,样本不均衡,后续进行模型训练的时候需要处理。  

label_value=dataset_['label'].value_counts()
labels=label_value.index
plt.figure(figsize=(5,5))
plt.pie(label_value,labels=labels, explode=(0.1,0),autopct='%1.1f%%', shadow=True)
plt.title("流失客户占比高达%s%%" %((label_value['lost']/label_value.sum()*100).round(2)))
plt.show()  

输出:

5、检测变量编码

对于分类数据,编码的方式有很多种,归纳总结,最常见的方式基本为两种:

  • 标签编码:代表性的算法例如sklearn中的LabelEncoder()、pandas中的factorize,或者通过replace和map函数进行替换。

  • 独热编码:代表性的算法例如sklearn中的OneHotEncoder(),pandas中的get_dummies。

两者的区别在于:标签编码所产生的结果,在数据上会有大小之分,而在具体的算法,分类的数据,一般是没有数值上的差异的。 而独热编码正好填补了这个缺陷,所以当我们进行简单分析时,一般标签编码即可,但是需要模型训练的数据,一般是要进行独热编码处理的。

print(dataset_['sex'].value_counts())
print(dataset_['label'].value_counts())

输出:

女    3704
男    2676
Name: sex, dtype: int64

no_lost    4121
lost       2259
Name: label, dtype: int64

通过检测变量,我们发现需要对'sex','label'变量进行再编码,否则影响模型训练,为了直观,我们用最基础的语法map函数进行编码。

#map方法
dataset_['sex']=dataset_['sex'].map({'男':1,'女':0})
dataset_['label']=dataset_['label'].map({'lost':1,'no_lost':0})
print(dataset_['sex'].value_counts())
print(dataset_['label'].value_counts())
#pandas的方法
#dataset_['sex']=pd.factorize(dataset_['sex'])[0]
#dataset_['label']=pd.factorize(dataset_['label'],)[0]

输出:

0    3704
1    2676
Name: sex, dtype: int64

0    4121
1    2259
Name: label, dtype: int64

'sex'字段:1代表男性,0代表女性;'label'字段:1代表lost(流失客户),0代表no_lost(留存客户)。

6、根据业务的变量转换

paidmoney_x_one_year,paidmoney_z_one_year ,paidmoney_b_one_year,paidmoney_q_one_year ,paidmoney_y_one_year, paidmoney_w_one_year ,paidmoney_s_one_year ,paidmoney_r_one_year,paidmoney_4_one_year ,paidmoney_5_one_year ,paidmoney_9_one_year这11个字段现在是数值型,其表示的是细分领域(西药、中成药、保健品、医疗器械、)的销售金额,根据实际的业务场景,此处我们建议将其转换为百分比,各字段同除于paidmoney_one_year。

dataset_1=dataset_.copy()
list_paidmoney=['paidmoney_x_one_year','paidmoney_z_one_year','paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year','paidmoney_s_one_year','paidmoney_r_one_year','paidmoney_4_one_year','paidmoney_5_one_year','paidmoney_9_one_year']
for i in list_paidmoney:
    dataset_1[i]=dataset_1[i]/dataset_1['paidmoney_one_year']
dataset_1.head()

输出:

 因为篇幅过长,仅展示部分数据。从上图中我们看到,转换为百分比的paidmoney_x_one_year等数据存在大于1的情况,这明显是异常的数据点,结合药店实际的业务场景,这应该是药店将退货的数据以负数的形式录入系统,为了后期模型的健壮性,我们对于这类数据进行删除。

7、检测异常值

将转换为百分比的list_paidmoney中的字段,大于1及小于0的样本点删除掉。

list_paidmoney=['paidmoney_x_one_year','paidmoney_z_one_year','paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year','paidmoney_s_one_year','paidmoney_r_one_year','paidmoney_5_one_year','paidmoney_9_one_year']

dataset_1.drop(dataset_1[((dataset_1.loc[:,list_paidmoney]>1) | (dataset_1.loc[:,list_paidmoney]<0)).sum(1)>0].index,inplace=True)
len(dataset_1)

输出:

6376

共处理掉四个异常样本点,剩余6576个样本点。 

七、筛选特征

1、相关系数矩阵

去掉uid,计算相关系数矩阵:

#提取特征
feature=dataset_1.iloc[:,1:27]
#相关系数矩阵
corr=feature.corr().round(2)

#相关系数矩阵的可视化
plt.figure(figsize=(15,12))
ax = sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns, 
                 linewidths=0.2, cmap="RdYlGn",annot=True)
plt.title("相关系数矩阵")

输出:

2、查看label和特征变量之间的相关性  

plt.figure(figsize=(15,6))
corr['label'].sort_values(ascending=False).plot(kind='bar')
plt.title('label(用户是否流失)与变量间的相关性 ')

输出: 

从相关系数排行上看,我们看到maxdate_diff_now(最晚购药日期距今时间)和label存在高度相关,从业务上考虑,最后一次购药时间距今越长,越容易流失,所以我们将其删除。另外,我们也将paidmoney_4_one_year删除(业务逻辑考虑)。

feature=feature.drop(['maxdate_diff_now','paidmoney_4_one_year'],axis=1)
feature.info()

输出:

另外,从“图——label(用户是否流失)与变量间的相关性”看:mindatediff_one_year(最小时间间隔)、quantity_one_year(年销售量)相关性几乎为0,故这两个变量可以删除。   

我们筛选出以下特征变量(21个)进行下一步的分析,将以上维度合并成一个列表list_columns,方便后续选取特征。

list_columns=['paidmoney_t_one_year','carddate_diff_now','maxdatediff_one_year','avgdatediff_one_year','count_one_order','paidmoney_one_order','year', 'paidmoney_r_one_year', 'paidmoney_s_one_year', 'sex',
'paidmoney_q_one_year', 'paidmoney_y_one_year',  'paidmoney_w_one_year',
'paidmoney_5_one_year','paidmoney_b_one_year','paidmoney_one_year','paidmoney_9_one_year','count_one_year','quantity_one_order','paidmoney_z_one_year','paidmoney_x_one_year']
list_columns

输出:

八、统计描述

对于二分类的问题分析我们可以逐一对其特征进行对比性的分析,对比性的分析主要有以下两种:

  • 数值型数据:均值比较——方差分析检验显著性
  • 分类型数据:频数分布比较(交叉分析)——卡方分析检验显著性

本数据集,特征变量主要为数值型变量(除了“sex”(性别)),我们先对其进行均值比较,要使均值比较有意义,我们需先对特征变量进行方差分析,检验其显著性。

1、方差齐性检验

做方差分析之前,我们需要进行方差齐性检验,只有通过方差齐性检验,方差分析的结果才是有意义的。

#齐性检验
def ANOVA(x):
    li_index=feature['label'].value_counts().index.tolist()
    args=[]
    for i in li_index:
        args.append(feature[feature['label']==i][x])
    w,p=stats.levene(*args)            
    ano_one=[x,w,p,'齐性' if p>0.05 else '非齐性' ]
    return ano_one

pd.DataFrame(list(map(ANOVA, list_columns)),columns=['columns','W值','p值','方差是否齐性']).round(4)

输出:

通过方差齐性检验,我们发现:paidmoney_one_order、year、quantity_one_order方差齐性,我们可以对其进行方差分析。我们将三个维度放入列表list_columns_F,方便后续反复调用。

list_columns_F=['paidmoney_one_order','year','quantity_one_order']

2、方差分析

对以上通过方差齐性检验的特征变量进行方差分析,结果如下:

#方差分析
def F_test(x):
    li_index=feature['label'].value_counts().index.tolist()
    args=[]
    for i in li_index:
        args.append(feature[feature['label']==i][x])
    f,p=stats.f_oneway(*args)            
    f_one=[x,f,p,'均值有显著差异' if p<0.05 else '均值无显著差异' ]
    return f_one

pd.DataFrame(list(map(F_test, list_columns_F)),columns=['columns','F值','p值','均值是否有显著差异']).round(4)

输出:

通过方差分析,我们发现,仅有变量quantity_one_order(客品数)对于label(流失会员or留存会员)的均值比较是有意义的。

3、均值比较

我们发现仅仅有quantity_one_order(客品数)能够比较均值。

quantity_one_order_m=feature.groupby('label')['quantity_one_order'].mean().round(2)
plt.figure(figsize=(5,5))
plt.bar(quantity_one_order_m.index.astype('str'),quantity_one_order_m.values,width=0.4,label='quantity_one_order(盒)',zorder=6)
for i in range(len(quantity_one_order_m)):
    plt.text(quantity_one_order_m.index[i],quantity_one_order_m.values[i],quantity_one_order_m.values[i],ha='center',va='bottom',fontsize=12,rotation=0)
plt.title('均值比较_quantity_one_order by label')

输出:

流失会员的“平均客品数”为1.58盒,留存会员的“平均客品数”为3.31盒。

4、特征变量离散化

经过前面方差分析,我们发现,大部分数值型特征变量无法进行均值比较(方差分析未通过)。这个时候,我们可以对特征进行离散化,特征离散化的作用在于:

  • 特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
  • 离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
  • 特征离散化以后,起到了简化了模型的作用,降低了模型过拟合的风险。
  • 离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
  • 离散特征的增加和减少都很容易,易于模型的快速迭代;
  • 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;

我们先来看list_columns中各特征变量的分布:

fig=plt.figure(figsize=(20,20)) #表示绘制图形的画板尺寸为6*4.5;
for x in list_columns:
    ax=fig.add_subplot(5,5,list_columns.index(x)+1)
    sns.distplot(feature[x].dropna(),axlabel=False)
    plt.title('pdf for %s'%(x))

输出:

从数据的分布上我们看到:除了year和sex之外,其他变量的分布都趋向偏态分布,对于偏态分布的离散化,我们推荐用分位数进行分区,结合实际的业务需求,我们对以上特征变量分成三类进行分区操作:

先将特征变量(21个)和标签变量重组。

feature_1=feature.loc[:,list_columns+['label']]
feature_1.info()

然后我们对特征变量离散化。 

  • 变量year,sex:sex(性别)不动;year(年龄):{<40:1,>=40:2,>=60:3},其中1:青年,2:中年,3:老年。

#年龄分区
feature_1['year']=pd.cut(feature_1['year'],[0,40,60,100],labels=['1','2','3'])
  • 我们从最原始的数据缺失情况上看:特征变量'paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year','paidmoney_s_one_year','paidmoney_r_one_year', 'paidmoney_t_one_year', 'paidmoney_5_one_year', 'paidmoney_9_one_year',缺失值超过50%,我们起初对其进行补0,对以上变量,我们建议建议对其二值化,即分为等于0(未购买该类产品),大于0(购买该类产品)。我们将以上特征变量定义列表list_columns_cut放入其中。

#将大于0的值替换为1
list_columns_cut=['paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year','paidmoney_s_one_year','paidmoney_r_one_year', 'paidmoney_t_one_year', 'paidmoney_5_one_year', 'paidmoney_9_one_year']

for i in list_columns_cut:
    feature_1[i]=feature_1[i].apply(lambda x: 1 if x>0 else 0)
  • 其他变量:'carddate_diff_now','paidmoney_one_year','paidmoney_x_one_year','paidmoney_z_one_year','count_one_year','maxdatediff_one_year','avgdatediff_one_year', 'quantity_one_order', 'count_one_order', 'paidmoney_one_order'。按照四分位数进行分区,我们将以上特征变量定义列表list_columns_qcut放入其中。

#四分位分区
list_columns_qcut=['carddate_diff_now','paidmoney_one_year','paidmoney_x_one_year','paidmoney_z_one_year','count_one_year','maxdatediff_one_year','avgdatediff_one_year', 'quantity_one_order', 'count_one_order', 'paidmoney_one_order']
for i in list_columns_qcut:
    feature_1[i]=pd.qcut(feature_1[i],4,labels=['1','2','3','4'])

离散化后输出,检验数据:

feature_1.info()

输出:

需要对部分数据类型进行转换,此处我们统一处理:

feature_1=feature_1.astype('int64')
feature_1.info()

输出:

5、卡方检验

离散化的数据我们可以进一步进行交叉分析,探索不同label(流失or留存)的会员在各特征变量的差异。为了检验差异是否显著,我们可事先对各变量进行卡方检验。

#自定义卡方检验函数
def Chi(x):
    df1=pd.crosstab(feature_1['label'],feature_1[x])
    crosstab=np.array(df1)
    chi=chi2_contingency(crosstab)
    chi_one=[x,chi[0],chi[1],'组间有显著差异' if chi[1]<0.05 else '组间无显著差异' ]
    return chi_one

pd.DataFrame(list(map(Chi, list_columns)),columns=['columns','chi值','p值','组间是否有显著差异']).round(4)

 输出:

结果:仅有year(年龄)未通过卡方检验,说明会员是否流失确实和年龄没有太大的关系。另外其他特征变量均可以进行下一步的交叉分析。

6、交叉分析

前面我们已经得知“样本不均衡”(流失客户样本占比35.4%,留存客户样本占比64.6%),基数不一样,故不能直接通过比较“频数”来得出结果。正好,交叉分析可以为我们输出百分比,解决“样本不均衡的频数比较”问题。

rint('list_columns列表中的特征与label标签交叉分析结果:','\n')
for i in list_columns:
    print('......................label BY %s......................'%(i))
    print(pd.crosstab(feature_1['label'],feature_1[i],normalize=0),'\n') #交叉分析,同行百分比

输出:

以上结果我们是对比离散化之前的数据特征得出来的,离散前的数据特征(分位数,均值)如下:

feature.loc[:,list_columns].describe().T.loc[:,['mean','25%','50%','75%']].round(2)

输出:

7、统计分析结论总结

通过交叉分析我们获得,流失会员的特征为:

  • paidmoney_t_one_year=1:全年有购买购“其他”品类产品的会员。

  • carddate_diff_now=3,4:办卡距今时间越久的会员(609天以上)。

  • maxdatediff_one_year=4:购买时间最大间隔越长的会员(115天以上)

  • avgdatediff_one_year=4:平均购药时间间隔越长的会员(44.25天以上)。

  • count_one_order=1:平均客品次越低的会员(小于1.4种)。

  • paidmoney_one_order=1:客单价越低的会员(小于33元)。

  • paidmoney_q_one_year=0:基本不买医疗器械的会员。

  • paidmoney_y_one_year=0:基本不买中药饮片的会员。

  • paidmoney_w_one_year=0:基本不买外用药品的会员。

  • paidmoney_5_one_year=0:基本不买阶段性药品的会员。

  • paidmoney_b_one_year=0:基本不买保健品的会员。

  • paidmoney_one_year=1:年消费总额偏低的会员(小于235.98元)。

  • paidmoney_9_one_year=0:基本不买慢病药品的会员。

  • count_one_year=1:年购药次数偏低的会员(小于5次)。

  • quantity_one_order=1:客品数偏低的会员(小于1.25盒)。

  • paidmoney_z_one_year=1:中成药购药金额占比低的会员(小于1%)。

  • paidmoney_x_one_year=1:西药购药金额占比低的会员(小于5%)。

其余特征:

  • year:卡方检验没有通过。

  • paidmoney_r_one_year:交叉分析对比不明显。

  • paidmoney_s_one_year:交叉分析对比不明显。

  • sex:交叉分析对比不明显。

归纳了流失会员的以上特征,运营人员在进行流失会员管理的时候可以根据以上量化的特征通过sql在数据库中筛选会员,并进行干预。当然,条件越多,流失会员的筛选也就越精准,但是相应的,就像过拟合一样,人数筛选出来的也就越少。所以,我们下一步要做的是对以上特征对结果(label)的影响重要程度进行排序。

九、探索特征重要程度

1、处理样本不均衡

流失客户样本占比35.4%,留存客户样本占比64.6%,样本不均衡。

解决样本不均衡有以下方法可以选择:  

  • 分层抽样  

  • 过抽样 

  • 欠抽样 

抽样是为了让模型测试的结果更加精准,本着这个目的,我们可以对以上三种方法都试一遍,最后以模型的准确度来确定使用哪种抽样方法,本文我们经过数据模型验证,“过抽样”过的数据训练的模型准确度是最高的,我们直接用这个方法处理数据。

1.1、抽样前标签分布

feature_1['label'].value_counts()

输出:

0    4118
1    2258
Name: label, dtype: int64

1.2、过抽样

x=feature_1.iloc[:,:-1]
y=feature_1.iloc[:,-1]
model_smote=SMOTE()
x_smote,y_smote=model_smote.fit_sample(x,y)
y_smote=pd.DataFrame(y_smote,columns=['label'])

y_smote['label'].value_counts()

输出:

1    4118
0    4118
Name: label, dtype: int64

2、拆分训练集和测试集数据

x_train,x_test,y_train,y_test=train_test_split(x_smote,y_smote,test_size=0.3,random_state=0)
print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)

输出:

(5765, 21)
(5765, 1)
(2471, 21)
(2471, 1)

 按照7:3拆分训练集和测试集。

3、数据建模

采用常见的有监督分类器(随机森林、支持向量机、逻辑回归、最近邻分类器、朴素贝叶斯分类器、决策树分类器、AdaBoost、GBDT、xgboost)训练训练集数据,并用测试集数据回测模型。

Classifiers=[["Random Forest",RandomForestClassifier()],  #随机森林
             ["Support Vector Machine",SVC()],            #支持向量机
             ["LogisticRegression",LogisticRegression()], #逻辑回归
             ["KNN",KNeighborsClassifier(n_neighbors=5)], #最近邻分类器
             ["Naive Bayes",GaussianNB()],                 #朴素贝叶斯分类器
             ["Decision Tree",DecisionTreeClassifier()],   #决策树分类器
             ["AdaBoostClassifier", AdaBoostClassifier()], #AdaBoost
             ["GradientBoostingClassifier", GradientBoostingClassifier()],  #GBDT
             ["XGB", XGBClassifier()]                      #xgboost
]

#训练模型
Classify_result=[]
names=[]
prediction=[]
for name,classifier in Classifiers:
    classifier=classifier
    classifier.fit(x_train,y_train)   #训练数据
    y_pred=classifier.predict(x_test)  #回测数据
    recall=recall_score(y_test,y_pred)  #召回率
    precision=precision_score(y_test,y_pred)  #准确率
    f1score = f1_score(y_test, y_pred) #得分
    class_eva=pd.DataFrame([recall,precision,f1score]) 
    Classify_result.append(class_eva)
    name=pd.Series(name)
    names.append(name)
    y_pred=pd.Series(y_pred)
    prediction.append(y_pred)

4、模型评价

names=pd.DataFrame(names)
names=names[0].tolist()
result=pd.concat(Classify_result,axis=1)
result.columns=names
result.index=["recall","precision","f1score"]
result.T

输出:

recall表示召回率,precision表示查准率,F1 score则是结合recall和precision对模型的综合考量,我们以F1 score作为评价模型的指标,其中随机森林的得分是最高的,达到82.83%。下面我们基于随机森林,探索特征重要程度排行。

5、特征重要程度排行

基于随机森林探索特征重要程度排行。

model = RandomForestClassifier()
model.fit(x_train,y_train)
#特征重要性可视化
RFC=pd.DataFrame(columns=['feature','feature_importance'])
RFC['feature']=x.columns
RFC['feature_importance']=model.feature_importances_

RFC=RFC.sort_values('feature_importance',ascending=False) #降序排列
plt.figure(figsize=(10,10))
plt.title('特征重要性')
sns.barplot(x='feature_importance',y='feature',data=RFC)

 输出:

十、结论总结

综合“ 交叉分析” 和 “特征重要程度排行” ,我们得出流失会员具有以下特征:

  1. quantity_one_order=1:客品数偏低的会员(小于1.25盒)。

  2. paidmoney_x_one_year=1:西药购药金额占比低的会员(小于5%)。

  3. paidmoney_z_one_year=1:中成药购药金额占比低的会员(小于1%)。

  4. paidmoney_t_one_year=1:全年有购买购“其他”品类产品的会员。

  5. count_one_year=1:年购药次数偏低的会员(小于5次)。

  6. carddate_diff_now=3,4:办卡距今时间越久的会员(609天以上)。

  7. maxdatediff_one_year=4:购买时间最大间隔越长的会员(115天以上)。

  8. count_one_order=1:平均客品次越低的会员(小于1.4种)。

  9. paidmoney_q_one_year=0:基本不买医疗器械的会员。

  10. avgdatediff_one_year=4:平均购药时间间隔越长的会员(44.25天以上)。

  11. paidmoney_one_year=1:年消费总额偏低的会员(小于235.98元)。

  12. paidmoney_one_order=1:客单价越低的会员(小于33元)。

  13. paidmoney_9_one_year=0:基本不买慢病药品的会员。

  14. paidmoney_w_one_year=0:基本不买外用药品的会员。

  15. paidmoney_b_one_year=0:基本不买保健品的会员。

  16. paidmoney_y_one_year=0:基本不买中药饮片的会员。

  17. paidmoney_5_one_year=0:基本不买阶段性药品的会员。

例如,我们可以以前四个条件筛选会员:

feature_filter=feature[(feature['quantity_one_order']<1.25) & (feature['paidmoney_z_one_year']<0.1) & (feature['paidmoney_x_one_year']<0.5) 
                        & (feature['paidmoney_t_one_year']>0)] 

feature_filter['label'].value_counts()

输出:

1    1103
0      13
Name: label, dtype: int64

筛选出来的流失会员准确率为98.83%。

十一、运营建议

结合以上的分析,我们发现,流失会员在购买非药品(quantity_t_one_year=1)上面有所倾向,通过查询数据库我们发现,非药品多为类似以下的产品为主。

对于此种情况:

  • 首先在医药政策允许的范围内,可以培养用户习惯,定期对此类用户发送类似“会员日通知”短信,并在会员日可实行抽奖活动,奖品可以是一些日常食品,如鸡蛋之类,增加此类用户参与的兴趣。

  • 增加用户的沉没成本,实行会员等级,做积分制(年底会员积分清零提醒),微信发送满减券,会员卡充值赠送等等。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xia ge tou lia

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值