一、背景
随着药店市场刮起了兼并重组风潮,大量资金涌入医药市场,药店行业竞争进入白热化。同时,“互联网+”及“大数据”为药店引来了新的机遇和挑战,大部分药店都在从传统零售行业转入“服务型”零售,实行“会员制”,进行会员差异化的管理和服务是其中最关键的一环。
本文主要利用药店会员销售数据,筛选出其中“已流失会员”进行挖掘分析,探索“已流失会员”的会员属性及购买行为——具有哪些特征的会员易流失?
我们挑选了一家在二线城市的连锁药店进行分析,这家连锁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)
输出:
十、结论总结
综合“ 交叉分析” 和 “特征重要程度排行” ,我们得出流失会员具有以下特征:
-
quantity_one_order=1:客品数偏低的会员(小于1.25盒)。
-
paidmoney_x_one_year=1:西药购药金额占比低的会员(小于5%)。
-
paidmoney_z_one_year=1:中成药购药金额占比低的会员(小于1%)。
-
paidmoney_t_one_year=1:全年有购买购“其他”品类产品的会员。
-
count_one_year=1:年购药次数偏低的会员(小于5次)。
-
carddate_diff_now=3,4:办卡距今时间越久的会员(609天以上)。
-
maxdatediff_one_year=4:购买时间最大间隔越长的会员(115天以上)。
-
count_one_order=1:平均客品次越低的会员(小于1.4种)。
-
paidmoney_q_one_year=0:基本不买医疗器械的会员。
-
avgdatediff_one_year=4:平均购药时间间隔越长的会员(44.25天以上)。
-
paidmoney_one_year=1:年消费总额偏低的会员(小于235.98元)。
-
paidmoney_one_order=1:客单价越低的会员(小于33元)。
-
paidmoney_9_one_year=0:基本不买慢病药品的会员。
-
paidmoney_w_one_year=0:基本不买外用药品的会员。
-
paidmoney_b_one_year=0:基本不买保健品的会员。
-
paidmoney_y_one_year=0:基本不买中药饮片的会员。
-
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)上面有所倾向,通过查询数据库我们发现,非药品多为类似以下的产品为主。
对于此种情况:
-
首先在医药政策允许的范围内,可以培养用户习惯,定期对此类用户发送类似“会员日通知”短信,并在会员日可实行抽奖活动,奖品可以是一些日常食品,如鸡蛋之类,增加此类用户参与的兴趣。
-
增加用户的沉没成本,实行会员等级,做积分制(年底会员积分清零提醒),微信发送满减券,会员卡充值赠送等等。