精华内容
下载资源
问答
  • Python项目实践-客户流失预测

    热门讨论 2020-01-05 18:00:08
    一、概述1.1 项目背景: 客户是企业的重要资源,也是企业的无形资产,客户的流失,也就意味着资产的流失,因此进行流失分析是十分重要的,进行客户流失分析的目的,就是阻止或者避免客户的流失,特高企业的盈利水平...

    一、概述

    1.1 项目背景:

    客户是企业的重要资源,也是企业的无形资产,客户的流失,也就意味着资产的流失,因此进行流失分析是十分重要的,进行客户流失分析的目的,就是阻止或者避免客户的流失,特高企业的盈利水平和竞争力。

    1.2 目的:

    深入了解用户画像及行为偏好,挖掘出影响用户流失的关键因素,并通过算法预测客户访问的转化结果,从而更好地完善产品设计、提升用户体验。

    1.3 数据说明:

    此次数据是携程用户一周的访问数据,为保护客户隐私,已经将数据经过了脱敏,和实际商品的订单量、浏览量、转化率等有一些差距,不影响问题的可解性。

    二、读取数据

    2.1 理解数据

    可以看到变量比较的多,先进行分类,除去目标变量label,此数据集的字段可以分成三个类别:订单相关指标、客户行为相关指标、酒店相关指标。

    v2-fd05bb4803eb1eec99cad7001048033d_b.jpg

    v2-c5765d9f706c2e8f45e29e5b512d5a52_b.jpg

    v2-e36516db5611249bf25a3608c4f3924b_b.jpg

    2.2 导入相关库及数据

    import numpy as np
    import pandas as pd
    import seaborn as sns
    import matplotlib.pyplot as plt
    %matplotlib inline
    
    # 显示全部特征
    pd.set_option('display.max_columns', None)
    df = pd.read_csv(r"./userlostprob.txt",sep="\t",encoding="UTF-8")
    df.head()

    v2-fceffdb86d7edbc09f8fc389b6e32521_b.jpg
    # 查看数据维度
    df.shape

    总共689945条样本数据,除去标签和id列,总共49个特征。

    # 查看数据类型
    df.info()

    v2-c58c466ba6d914946d313faa0c67d42a_b.jpg
    只有d和arrival列为字符型,其余均为数值型,后面将d和arrival列处理为数值型即可。
    # 查看缺失值占比
    df.isnull().mean()

    v2-7fc63c6fccfca916a96a70d8fb47def7_b.jpg
    数据集的缺失情况比较严重,有44列存在缺失情况
    # 查看标签分布
    df['label'].value_counts()

    v2-53a4e55f281ae3d70efb77dc175c8450_b.jpg
    # 描述性统计
    df.describe()

    v2-927b67298680d786d5cfc7c483bcd8a8_b.jpg
    数据存在负值和极值

    三、特征工程

    # 将数据copy一份
    rawdf=df.copy()

    3.1 数据预处理

    3.1.1 数据类型转换

    字符串类型的特征需要处理成数值型才能建模,将arrival和d相减得到"提前预定的天数",作为新的特征.

    ## 增加列
    # 将两个日期变量由字符串转换为日期型格式
    rawdf['arrival']=pd.to_datetime(rawdf['arrival'])
    rawdf['d']=pd.to_datetime(rawdf['d'])
    # 生成提前预定时间列(衍生变量)
    rawdf['day_advanced']=(rawdf['arrival']-rawdf['d']).dt.days
    
    ## 删除列
    rawdf=rawdf.drop(['sampleid','d','arrival'],axis=1)

    3.1.2 异常值处理

    • 将customer_value_profit、ctrip_profits中的负值按0处理
    • 将delta_price1、delta_price2、lowestprice中的负值按中位数处理
    filter_one=['customer_value_profit','ctrip_profits']
    filter_two=['delta_price1','delta_price2','lowestprice']
    
    for f in filter_one:
        rawdf.loc[rawdf[f]<0,f] = 0
    
    for f in filter_two:
        rawdf.loc[rawdf[f]<0,f] = rawdf[f].median()
    
    rawdf[['customer_value_profit','ctrip_profits','delta_price1','delta_price2','lowestprice']].describe()

    v2-711cb9090a30fe8cd591f2e4bd9a78b8_b.jpg

    3.1.3 缺失值处理

    3.1.3.1 空值删除

    特征值中只有iforderpv_24h、sid、h、day_advanced这四个是不存在缺失的,其他的44个特征都是存在缺失值的,并且大部分的缺失值都挺多的,因此需要对缺失值进行处理.

    # 定义删除空值行列的函数
    def nan_drop(df, axi, rate=0.5):
        df.dropna(axis=axi,thresh=df.shape[1-axi]*rate,inplace=True)
        
    # 删除缺失值比例大于80%的行和列
    print('删除空值前数据维度是:{}'.format(rawdf.shape))
    
    nan_drop(rawdf,axi=0,rate=0.2)
    nan_drop(rawdf,axi=1,rate=0.2)
    
    print('删除空值后数据维度是:{}'.format(rawdf.shape))

    v2-980dc859bc1c62a0a03067e6b3c5ead1_b.png

    3.1.3.2 缺失值填充

    趋于正态分布的字段,使用均值填充:businessrate_pre2、cancelrate_pre、businessrate_pre;偏态分布的字段,使用中位数填充.

    v2-cca90c09488d2787f9c9dcfaa4fa8c78_b.jpg
    def nan_fill(df):
        filter_mean=['businessrate_pre2','cancelrate_pre','businessrate_pre']
        for col in df.columns:
            if col in filter_mean:
                df[col]=df[col].fillna(df[col].mean())
            else:
                df[col]=df[col].fillna(df[col].median())
        return df
    
    rawdf=nan_fill(rawdf)

    3.1.4 极值处理

    有些特征明显有异常大和异常小的值,这里分别使用1%和99%分位数替换超过上下限的值.

    for i in rawdf.columns:
        rawdf.loc[rawdf[i]<np.percentile(rawdf[i],1),i]=np.percentile(rawdf[i],1)
        rawdf.loc[rawdf[i]>np.percentile(rawdf[i],99),i]=np.percentile(rawdf[i],99)

    3.2 相关性分析

    # 用户特征的相关性分析
    # 用户特征提取
    user_features=['visitnum_oneyear','starprefer','sid','price_sensitive','ordernum_oneyear','ordercanncelednum','ordercanceledprecent','lastpvgap',
                   'lasthtlordergap','landhalfhours','iforderpv_24h','historyvisit_totalordernum','historyvisit_avghotelnum','h',
                   'delta_price2','delta_price1','decisionhabit_user','customer_value_profit','ctrip_profits','cr','consuming_capacity','avgprice']
    # 生成用户特征的相关性矩阵
    corr_mat=rawdf[user_features].corr()
    
    # 绘制用户特征的相关性矩阵热度图
    fig,ax = plt.subplots(figsize=(18, 12))
    sns.heatmap(corr_mat, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges')
    plt.savefig('./Photo/用户特征的相关性分析.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-b63b507402aaefe35a5c3c2eb84ed20a_b.jpg
    # 酒店信息特征的相关性分析
    hotel_features=['hotelcr','hoteluv','commentnums','novoters','cancelrate','lowestprice','cr_pre','uv_pre','uv_pre2','businessrate_pre',
                    'businessrate_pre2','customereval_pre2','commentnums_pre','commentnums_pre2','cancelrate_pre','novoters_pre','novoters_pre2',
                    'deltaprice_pre2_t1','lowestprice_pre','lowestprice_pre2','firstorder_bu','historyvisit_visit_detailpagenum']
    # 生成用户特征的相关性矩阵
    corr_mat1=rawdf[hotel_features].corr()
    
    fig,ax = plt.subplots(figsize=(18, 12))
    sns.heatmap(corr_mat1, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges_r')
    plt.savefig('./Photo/酒店信息特征的相关性分析.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-d2cfeaec1b3532c2c543c14f5749de19_b.jpg

    3.3 降维

    3.3.1 主成分分析法(PCA)

    c_value=['customer_value_profit','ctrip_profits']                   # 用户价值
    consume_level=['avgprice','consuming_capacity']                     # 用户消费水平
    price_prefer=['delta_price1','delta_price2']                        # 用户偏好价格
    hotel_hot=['commentnums','novoters']                                # 酒店热度
    hotel_hot_pre=['commentnums_pre','novoters_pre']                    # 24小时内浏览次数最多的酒店热度
    hotel_hot_pre2=['commentnums_pre2','novoters_pre2']                 # 24小时内浏览酒店的平均热度
    
    from sklearn.decomposition import PCA
    pca=PCA(n_components=1)
    rawdf['c_value']=pca.fit_transform(rawdf[c_value])
    rawdf['consume_level']=pca.fit_transform(rawdf[consume_level])
    rawdf['price_prefer']=pca.fit_transform(rawdf[price_prefer])
    rawdf['hotel_hot']=pca.fit_transform(rawdf[hotel_hot])
    rawdf['hotel_hot_pre']=pca.fit_transform(rawdf[hotel_hot_pre])
    rawdf['hotel_hot_pre2']=pca.fit_transform(rawdf[hotel_hot_pre2])
    
    rawdf.drop(c_value,axis=1,inplace=True)
    rawdf.drop(consume_level,axis=1,inplace=True)
    rawdf.drop(price_prefer,axis=1,inplace=True)
    rawdf.drop(hotel_hot,axis=1,inplace=True)
    rawdf.drop(hotel_hot_pre,axis=1,inplace=True)
    rawdf.drop(hotel_hot_pre2,axis=1,inplace=True)
    rawdf.drop('historyvisit_totalordernum',axis=1,inplace=True)  ###把重复的一列删了
    print('PCA降维后数据维度是:{}'.format(rawdf.shape))

    v2-cb8aa94041df9cc557294e3a09fdd35b_b.png

    3.4 数据标准化

    # 数据标准化
    from sklearn.preprocessing import StandardScaler
    
    y=rawdf['label']
    x=rawdf.drop('label',axis=1)
    
    scaler = StandardScaler()
    scaler.fit(x)
    
    X= scaler.transform(x)

    四、建模与模型评估

    数据集的划分原则上应当在缺失值和异常值处理之前就进行,也就是说从数据预处理阶段开始,验证集和测试集就不应参与到模型构建的各个阶段中来,而应仅使用训练集得到的信息,这样才能有效严谨的保证模型验证和评估不会出现潜在的过拟合问题。

    from sklearn.model_selection import train_test_split, GridSearchCV
    
    X_train,X_test,y_train,y_test = train_test_split(X,y,test_size= 0.2,random_state=420)

    4.1 逻辑回归

    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import classification_report
    from sklearn import metrics
    
    lr = LogisticRegression()                                        # 实例化一个LR模型
    lr.fit(X_train,y_train)                                          # 训练模型
    y_prob = lr.predict_proba(X_test)[:,1]                           # 预测1类的概率
    y_pred = lr.predict(X_test)                                      # 模型对测试集的预测结果
    fpr_lr,tpr_lr,threshold_lr = metrics.roc_curve(y_test,y_prob)    # 获取真阳率、伪阳率、阈值
    auc_lr = metrics.auc(fpr_lr,tpr_lr)                              # AUC得分
    score_lr = metrics.accuracy_score(y_test,y_pred)                 # 模型准确率
    print('模型准确率为:{0},AUC得分为:{1}'.format(score_lr,auc_lr))
    print('============================================================')
    print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))

    v2-f48826eaaa777eab4d1aa7f77e5d11e7_b.jpg

    4.2 朴素贝叶斯

    from sklearn.naive_bayes import GaussianNB
    
    gnb = GaussianNB()                                                # 实例化一个LR模型
    gnb.fit(X_train,y_train)                                          # 训练模型
    y_prob = gnb.predict_proba(X_test)[:,1]                           # 预测1类的概率
    y_pred = gnb.predict(X_test)                                      # 模型对测试集的预测结果
    fpr_gnb,tpr_gnb,threshold_gnb = metrics.roc_curve(y_test,y_prob)  # 获取真阳率、伪阳率、阈值
    auc_gnb = metrics.auc(fpr_gnb,tpr_gnb)                            # AUC得分
    score_gnb = metrics.accuracy_score(y_test,y_pred)                 # 模型准确率
    
    
    print('模型准确率为:{0},AUC得分为:{1}'.format(score_gnb,auc_gnb))
    print('============================================================')
    print(classification_report(y_test, y_pred, labels=None, target_names=None, sample_weight=None, digits=2))

    v2-d70d474ce86023b71d5da1e76a77cb2d_b.jpg

    4.3 支持向量机

    from sklearn.svm import SVC
    
    svc = SVC(kernel='rbf',C=1,max_iter=100).fit(X_train,y_train)
    y_prob = svc.decision_function(X_test)                              # 决策边界距离
    y_pred = svc.predict(X_test)                                        # 模型对测试集的预测结果
    fpr_svc,tpr_svc,threshold_svc = metrics.roc_curve(y_test,y_prob)    # 获取真阳率、伪阳率、阈值
    auc_svc = metrics.auc(fpr_svc,tpr_svc)                              # 模型准确率
    score_svc = metrics.accuracy_score(y_test,y_pred)
    print('模型准确率为:{0},AUC得分为:{1}'.format(score_svc,auc_svc))
    print('============================================================')
    print(classification_report(y_test, y_pred, labels=None, target_names=None, sample_weight=None, digits=2))

    v2-5ae9e8074dbded78ee23ee435cd98256_b.jpg

    4.4 决策树

    from sklearn import tree
    
    dtc = tree.DecisionTreeClassifier()                              # 建立决策树模型
    dtc.fit(X_train,y_train)                                         # 训练模型
    y_prob = dtc.predict_proba(X_test)[:,1]                          # 预测1类的概率
    y_pred = dtc.predict(X_test)                                     # 模型对测试集的预测结果 
    fpr_dtc,tpr_dtc,threshod_dtc= metrics.roc_curve(y_test,y_prob)   # 获取真阳率、伪阳率、阈值
    score_dtc = metrics.accuracy_score(y_test,y_pred)                
    auc_dtc = metrics.auc(fpr_dtc,tpr_dtc) 
    print('模型准确率为:{0},AUC得分为:{1}'.format(score_dtc,auc_dtc))
    print('============================================================')
    print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))

    v2-070c3f439973fcaecc9ed6100cb26b95_b.jpg

    4.5 随机森林

    from sklearn.ensemble import RandomForestClassifier
    
    rfc = RandomForestClassifier()                                     # 建立随机森林分类器
    rfc.fit(X_train,y_train)                                           # 训练随机森林模型
    y_prob = rfc.predict_proba(X_test)[:,1]                            # 预测1类的概率
    y_pred=rfc.predict(X_test)                                         # 模型对测试集的预测结果
    fpr_rfc,tpr_rfc,threshold_rfc = metrics.roc_curve(y_test,y_prob)   # 获取真阳率、伪阳率、阈值  
    auc_rfc = metrics.auc(fpr_rfc,tpr_rfc)                             # AUC得分
    score_rfc = metrics.accuracy_score(y_test,y_pred)                  # 模型准确率
    print('模型准确率为:{0},AUC得分为:{1}'.format(score_rfc,auc_rfc))
    print('============================================================')
    print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))

    v2-bdfe38781c2d4b81fa8d326fbdce26ec_b.jpg

    4.6 XGBoost

    import xgboost as xgb
    
    # 读入训练数据集和测试集
    dtrain=xgb.DMatrix(X_train,y_train)
    dtest=xgb.DMatrix(X_test)
    
    # 设置xgboost建模参数
    params={'booster':'gbtree','objective': 'binary:logistic','eval_metric': 'auc',
        'max_depth':8,'gamma':0,'lambda':2,'subsample':0.7,'colsample_bytree':0.8,
        'min_child_weight':3,'eta': 0.2,'nthread':8,'silent':1}
    
    # 训练模型
    watchlist = [(dtrain,'train')]
    bst=xgb.train(params,dtrain,num_boost_round=500,evals=watchlist)
    
    # 输入预测为正类的概率值
    y_prob=bst.predict(dtest)
    
    # 设置阈值为0.5,得到测试集的预测结果
    y_pred = (y_prob >= 0.5)*1
    
    # 获取真阳率、伪阳率、阈值
    fpr_xgb,tpr_xgb,threshold_xgb = metrics.roc_curve(y_test,y_prob)   
    auc_xgb = metrics.auc(fpr_xgb,tpr_xgb)    # AUC得分
    score_xgb = metrics.accuracy_score(y_test,y_pred)    # 模型准确率
    print('模型准确率为:{0},AUC得分为:{1}'.format(score_xgb,auc_xgb))
    print('============================================================')
    print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))

    v2-9ce0a9cfbc247de75e40d3ef48a2a41e_b.jpg

    4.7 模型比较

    plt.style.use('bmh')
    plt.figure(figsize=(13,10))
    
    plt.plot(fpr_lr,tpr_lr,label='lr: {0:.3f}'.format(score_lr))            # 逻辑回归
    plt.plot(fpr_gnb,tpr_gnb,label='gnb:{0:.3f}'.format(score_gnb))         # 朴素贝叶斯
    plt.plot(fpr_svc,tpr_svc,label='svc:{0:.3f}'.format(score_svc))         # 支持向量机
    plt.plot(fpr_dtc,tpr_dtc,label='dtc:{0:.3f}'.format(score_dtc))         # 决策树
    plt.plot(fpr_rfc,tpr_rfc,label='rfc:{0:.3f}'.format(score_rfc))         # 随机森林
    plt.plot(fpr_rfc,tpr_rfc,label='xgb:{0:.3f}'.format(score_xgb))         # XGBoost
    
    plt.legend(loc='lower right',prop={'size':25})
    plt.xlabel('伪阳率')
    plt.ylabel('真阳率')
    plt.title('ROC曲线')
    plt.savefig('./Photo/模型比较图.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-d81ecd83218728fb8d3ef414b30c57ba_b.jpg

    4.8 重要特征

    from xgboost import plot_importance
    fig,ax = plt.subplots(figsize=(15,15))
    plot_importance(bst,height=0.5,ax=ax,max_num_features=40,color='chocolate')
    plt.savefig('./Photo/重要特征图.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-6d57a371aa1dc94ef5fd70b9fcba7360_b.jpg

    重要的特征:24小时内是否访问订单填写页(24小时内是否访问订单填写页)、近3个月用户历史日均访问酒店数(historyvisit_avghotelnum)、当前酒店转换率(hotelcr)、当前酒店历史订单取消率(ordercanceledprecent)、星级偏好(starprefer)、用户历史取消率(cancelrate)、 7天内访问酒店详情页数(historyvisit_visit_detailpagenum)、价格敏感指数price_sensitive)、当前酒店访客量(hoteluv)、浏览最多的酒店商务属性(businessrate_pre)。

    五、 RFM模型和用户画像

    5.1 RFM分析

    RFM模型,即为:

    • R(Rencency):最近一次消费
    • F(Frequency):消费频率
    • M(Monetary):消费金额

    v2-e697454bdadc7e84430adca118810731_b.jpg

    选择lasthtlordergap(距离上次下单的时长)、ordernum_oneyear(用户年订单数)、consume_level(用户消费水平)分别作为R、F、M的值,对我们的用户群体进行聚类.

    rfm = rawdf[['lasthtlordergap','ordernum_oneyear','consume_level']]
    
    # 归一化
    from sklearn.preprocessing import MinMaxScaler
    scaler = MinMaxScaler()
    scaler.fit(rfm)
    rfm = pd.DataFrame(scaler.transform(rfm),columns=['recency','frequency','monetary'])
    
    # 分箱
    rfm['R']=pd.qcut(rfm["recency"], 2)
    rfm['F']=pd.qcut(rfm["frequency"], 2)
    rfm['M']=pd.qcut(rfm["monetary"], 2)
    
    # 编码
    from sklearn.preprocessing import LabelEncoder
    rfm['R']=LabelEncoder().fit(rfm['R']).transform(rfm['R'])
    rfm['F']=LabelEncoder().fit(rfm['F']).transform(rfm['F'])
    rfm['M']=LabelEncoder().fit(rfm['M']).transform(rfm['M'])
    
    def get_label(r,f,m):
        if (r==0)&(f==1)&(m==1):
            return '高价值客户'
        if (r==1)&(f==1)&(m==1):
            return '重点保持客户'
        if((r==0)&(f==0)&(m==1)):
            return '重点发展客户'
        if (r==1)&(f==0)&(m==1):
            return '重点挽留客户'
        if (r==0)&(f==1)&(m==0):
            return '一般价值客户'
        if (r==1)&(f==1)&(m==0):
            return '一般保持客户'
        if (r==0)&(f==0)&(m==0):
            return '一般发展客户'
        if (r==1)&(f==0)&(m==0):
            return '潜在客户'
    
    def RFM_convert(df):
        df['Label of Customer']=df.apply(lambda x:get_label(x['R'],x['F'],x['M']),axis=1)
        
        df['R']=np.where(df['R']==0,'高','低')
        df['F']=np.where(df['F']==1,'高','低')
        df['M']=np.where(df['M']==1,'高','低')
        
        return df[['R','F','M','Label of Customer']]
    
    rfm0=RFM_convert(rfm)
    rfm0.head(10)

    v2-3c46e4e49c1a02ae706e8c24c4330917_b.jpg
    temp=rfm0.groupby('Label of Customer').size()
    
    plt.figure(figsize=(12,18))
    colors=['orangered','lightsalmon','sienna','seashell','chocolate','peru','sandybrown','peachpuff']
    plt.pie(temp,radius=1,autopct='%.1f%%',pctdistance=0.75,colors=colors)
    plt.pie([1],radius=0.6,colors='w')
    plt.title('客户细分情况')
    plt.legend(temp.index)
    plt.savefig('./Photo/客户细分情况.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-9ced3a2cfff92ab4286426cda39f08a3_b.jpg
    • 潜在客户占比达12.3%,这类客户是rmf指标均不是很好的客户,有待开发;
    • 高价值客户11%,重点保持客户10.1%,重点发展客户7%,这是要重点关注的客户群体.

    5.2 用户画像

    # 选取出几个刻画用户的重要指标
    user_feature = ['decisionhabit_user','ordercanncelednum','ordercanceledprecent','consume_level','starprefer','lasthtlordergap','lastpvgap','h','sid',
                    'c_value','landhalfhours','price_sensitive','price_prefer','day_advanced','historyvisit_avghotelnum','ordernum_oneyear']
    user_attributes = rawdf[user_feature]
    user_attributes.head()
    
    # 数据标准化
    from sklearn.preprocessing import StandardScaler
    
    scaler = StandardScaler()
    scaler.fit(user_attributes)
    
    user_attributes = scaler.transform(user_attributes)
    
    from sklearn.cluster import KMeans
    
    Kmeans=KMeans(n_clusters=3)                                                     # 建立KMean模型
    Kmeans.fit(user_attributes)                                                     # 训练模型
    k_char=Kmeans.cluster_centers_                                                  # 得到每个分类的质心
    personas=pd.DataFrame(k_char.T,index=user_feature,columns=['0类','1类','2类'])  # 用户画像表
    
    plt.figure(figsize=(5,10))
    sns.heatmap(personas, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges')
    plt.savefig('./Photo/用户画像表.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-190aea17425f08feefd2a6262b78e3bd_b.jpg

    2类用户的R(lasthtlordergap)为-0.17非常小(R越小越好,这里是反的),F(ordernum_oneyear)为1.1比较高了,M(consume_level)为1.3也几乎是最高的.很明显,2类客户为我们的"高价值客户";而0类中几乎都是白格子,无论是客户价值还是消费水平值都是最低的,很明显,这一类我们将其归为"低价值客户";剩下的1类我们将其称为"中等群体".

    plt.figure(figsize=(9,9))
    
    class_k=list(Kmeans.labels_)                          # 每个类别的用户个数
    percent=[class_k.count(1)/len(user_attributes),class_k.count(0)/len(user_attributes),class_k.count(2)/len(user_attributes)]   # 每个类别用户个数占比
    
    fig, ax = plt.subplots(figsize=(10,10))
    colors=['chocolate','sandybrown','peachpuff']
    types=['中等群体','低价值用户','高价值用户']
    ax.pie(percent,radius=1,autopct='%.2f%%',pctdistance=0.75,colors=colors,labels=types)
    ax.pie([1], radius=0.6,colors='w')
    plt.savefig('./Photo/用户画像.jpg',dpi=400, bbox_inches='tight')
    plt.show()

    v2-a08ae0da5948c6ae861d2002fac3bfdf_b.jpg

    5.3 用户画像分析

    5.3.1 高价值用户分析

    消费水平高,客户价值大,追求高品质,对酒店星级要求高,访问频率和预定频率都较高,提前预定的时间都较短,决策一般都较快(日均访问数少),订单取消率较高,可以分析出这类客户商务属性偏重,可能随时要出差,因此都不会提前预定,可能出差随时会取消,因此酒店取消率也会更高一点。sid的值较大,说明高价值客户群体多集中在老客户中。价格敏感度较高,说明可能比较要求性价比。h值非常小,可能访问和预定时间多在半夜或是清晨。

    这部分客户对于我们而言是非常重要的,因此我们需要对其实施个性化的营销:

    • 1、为客户提供更多差旅地酒店信息;
    • 2、多推荐口碑好、性价比高的商务酒店连锁酒店房源吸引用户;
    • 3、在非工作日的11点、17点等日间流量小高峰时段进行消息推送。

    5.3.2 中等群体分析

    消费水平和客户价值都偏低,对酒店品质也不太追求,访问和预定频率也都较高,提前预定的时间是三类中最长的,最值得注意的是,0类客户中有两个颜色非常深的蓝色格子,是用户决策和近3个月的日均访问数。可以看出,这类客户通常很喜欢逛酒店界面,在决定要订哪家酒店前通常会花费非常多的时间进行浏览才能做出选择,并且一般都会提前很久订好房。我们可以给这类客户打上“谨慎”的标签。我们可以合理推断,这一类客户,可能预定酒店的目的多为出门旅行。

    针对这部分客户,我们需要:

    • 1、在节假日前两、三星期定期推送国外高星级酒店,尽可能多地进行推送,因为此类客户通常比较喜欢浏览;
    • 2、推送高端酒店以及当地的旅行资讯,吸引用户关注,因为这类客户旅游出行的概率较大;
    • 3、和景区酒店代理商合作,针对此类用户制定个性化推荐,多推荐价格相对实惠的酒店。

    5.3.3 低价值用户分析

    消费水平和客户价值极低,对酒店品质不追求,偏好价格较低,决策时间很短,访问和预定频率很低,sid值很低,说明新客户居多。

    针对这部分客户,我们需要:

    • 1、不建议花费过多营销成本,但因为新用户居多,属于潜在客户,建议把握用户初期体验(如初期消费有优惠、打卡活动等),还可以定期推送实惠的酒店给此类用户,以培养客户消费惯性为主;
    • 2、推送的内容应多为大减价、大酬宾、跳楼价之类的;
    • 3、由于这部分用户占比较多,可结合该群体流失情况分析流失客户因素,进行该群体市场的开拓,进一步进行下沉分析,开拓新的时长。
    展开全文
  • Python 银行信用卡客户流失预测(kaggle)

    千次阅读 多人点赞 2020-12-08 08:08:00
    1.背景越来越多的客户不再使用信用卡服务,银行的经理对此感到不安。如果有人能为他们预测哪些客户即将流失,他们将不胜感激,因为这样他们可以主动向客户提供更好的服务,并挽回这些即将流失客户...

    1.背景

    越来越多的客户不再使用信用卡服务,银行的经理对此感到不安。如果有人能为他们预测哪些客户即将流失,他们将不胜感激,因为这样他们可以主动向客户提供更好的服务,并挽回这些即将流失的客户。

    2.数据集

    该数据集由10,000个客户组成,其中包含了他们的年龄,工资,婚姻状况,信用卡限额,信用卡类别等。

    不过,这里面只有16%的客户是流失的,因此拿来预测客户是否会流失有点难度。

    在Python实用宝典后台回复 预测客户流失 下载这份数据和源代码。

    译自kaggle并对原文进行了修改和补充,感谢原作者:
    https://www.kaggle.com/thomaskonstantin/bank-churn-data-exploration-and-churn-prediction/

    3.代码与分析

    开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

    此外,推荐大家用VSCode编辑器,因为它可以在编辑器下方的终端运行命令安装依赖模块:Python 编程的最好搭档—VSCode 详细指南

    本文具备流程性,建议使用 VSCode 的 Jupiter Notebook 扩展,新建一个名为 test.ipynb 的文件,跟着教程一步步走下去。

    Windows环境下打开 Cmd (开始-运行-CMD),苹果系统环境下请打开 Terminal (command+空格输入Terminal),准备开始输入命令安装依赖。

    所需依赖:

    pip install numpy
    pip install pandas
    pip install plotly
    pip install scikit-learn
    pip install scikit-plot
    
    # 最后模型预测需要用到,安装需要conda
    # 如果只是想探索性分析数据,可以不导入 imblearn
    conda install -c conda-forge imbalanced-learn

    3.1 导入需要的模块

    本文比较长,涉及到的模块比较多,如果只是想探索性分析数据,可以不导入 imblearn。

    import numpy as np # linear algebra
    import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
    import matplotlib.pyplot as plt
    import seaborn as sns
    import plotly.express as ex
    import plotly.graph_objs as go
    import plotly.figure_factory as ff
    from plotly.subplots import make_subplots
    import plotly.offline as pyo
    pyo.init_notebook_mode()
    sns.set_style('darkgrid')
    from sklearn.decomposition import PCA
    from sklearn.model_selection import train_test_split,cross_val_score
    from sklearn.ensemble import RandomForestClassifier,AdaBoostClassifier
    from sklearn.svm import SVC
    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import StandardScaler
    from sklearn.metrics import f1_score as f1
    from sklearn.metrics import confusion_matrix
    import scikitplot as skplt
    
    plt.rc('figure',figsize=(18,9))
    %pip install imbalanced-learn
    from imblearn.over_sampling import SMOTE


    遇到任何 No module named "XXX" 都可以尝试pip install一下。

    如果pip install没解决,可以谷歌/百度一下,看看别人是怎么解决的。

    3.2 加载数据

    c_data = pd.read_csv('./BankChurners.csv')
    c_data = c_data[c_data.columns[:-2]]
    c_data.head(3)

    这里去掉了最后两列的朴素贝叶斯分类结果。

    显示前三行数据, 可以看到所有的字段:

    3.3 探索性数据分析

    下面看看这20+列数据中,哪一些是对我们有用的。

    首先,我想知道数据集中的客户年龄分布:

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Customer_Age'],name='Age Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Customer_Age'],name='Age Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of Customer Ages")
    fig.show()

    可以看到,客户的年龄分布大致遵循正态分布,因此使用可以在正态假设下进一步使用年龄特征。

    同样滴,我想知道性别分布如何:

    ex.pie(c_data,names='Gender',title='Propotion Of Customer Genders')

    可见,在我们的数据集中,女性的样本比男性更多,但是差异的百分比不是那么显著,所以我们可以说性别是均匀分布的。

    每个客户的家庭人数的分布怎么样?

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Dependent_count'],name='Dependent count Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Dependent_count'],name='Dependent count Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of Dependent counts (close family size)")
    fig.show()

    大致符合正态分布,偏右一点,或许后续分析能用得上。

    客户的受教育水平如何?

    ex.pie(c_data,names='Education_Level',title='Propotion Of Education Levels')

    假设大多数教育程度不明(Unknown)的顾客都没有接受过任何教育。我们可以指出,超过70%的顾客都受过正规教育,其中约35%的人受教育程度达到硕士以上水平,45%的人达到本科以上水准。

    他们的婚姻状态如何?

    ex.pie(c_data,names='Marital_Status',title='Propotion Of Different Marriage Statuses')


    看来,这家银行几乎一半的客户都是已婚人士,有趣的是,另一半客户几乎都是单身人士,另外只有7%的客户离婚了。

    看看收入分布和卡片类型的分布:

    ex.pie(c_data,names='Income_Category',title='Propotion Of Different Income Levels')

    ex.pie(c_data,names='Card_Category',title='Propotion Of Different Card Categories')


    可见大部分人的年收入处于60K美元以下。

    在持有的卡片的类型上,蓝卡占了绝大多数。

    每月账单数量有没有特征?

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Months_on_book'],name='Months on book Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Months_on_book'],name='Months on book Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of months the customer is part of the bank")
    fig.show()

    可以看到中间的峰值特别高,显然这个指标不是正态分布的。

    每位客户持有的银行业务数量有没有特征呢?

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Total_Relationship_Count'],name='Total no. of products Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Total_Relationship_Count'],name='Total no. of products Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of Total no. of products held by the customer")
    fig.show()

    基本上都是均匀分布的,显然这个指标对于我们而言也没太大意义。

    用户不活跃月份数量是不是好用的特征?

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Months_Inactive_12_mon'],name='number of months inactive Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Months_Inactive_12_mon'],name='number of months inactive Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of the number of months inactive in the last 12 months")
    fig.show()

    这个似乎有点用处,会不会越不活跃的用户越容易流失呢?我们先往后看。

    信用卡额度的分布如何?

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Credit_Limit'],name='Credit_Limit Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Credit_Limit'],name='Credit_Limit Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of the Credit Limit")
    fig.show()

    大部分人的额度都在0到10k之间,这比较正常,暂时看不出和流失有什么关系。

    客户总交易额的分布怎么样?

    fig = make_subplots(rows=2, cols=1)
    
    tr1=go.Box(x=c_data['Total_Trans_Amt'],name='Total_Trans_Amt Box Plot',boxmean=True)
    tr2=go.Histogram(x=c_data['Total_Trans_Amt'],name='Total_Trans_Amt Histogram')
    
    fig.add_trace(tr1,row=1,col=1)
    fig.add_trace(tr2,row=2,col=1)
    
    fig.update_layout(height=700, width=1200, title_text="Distribution of the Total Transaction Amount (Last 12 months)")
    fig.show()


    这个有点意思,总交易额的分布体现出“多组”分布,如果我们根据这个指标将客户聚类为不同的组别,看他们之间的相似性,并作出不同的画线,也许对我们最终的流失分析有一定的意义。

    接下来,最重要的流失用户分布

    ex.pie(c_data,names='Attrition_Flag',title='Proportion of churn vs not churn customers')


    我们可以看到,只有16%的数据样本代表流失客户,在接下来的步骤中,我将使用SMOTE对流失样本进行采样,使其与常规客户的样本大小匹配,以便给后面选择的模型一个更好的机会来捕捉小细节。

    3.4 数据预处理

    使用SMOTE模型前,需要根据不同的特征对数据进行One Hot编码:

    c_data.Attrition_Flag = c_data.Attrition_Flag.replace({'Attrited Customer':1,'Existing Customer':0})
    c_data.Gender = c_data.Gender.replace({'F':1,'M':0})
    c_data = pd.concat([c_data,pd.get_dummies(c_data['Education_Level']).drop(columns=['Unknown'])],axis=1)
    c_data = pd.concat([c_data,pd.get_dummies(c_data['Income_Category']).drop(columns=['Unknown'])],axis=1)
    c_data = pd.concat([c_data,pd.get_dummies(c_data['Marital_Status']).drop(columns=['Unknown'])],axis=1)
    c_data = pd.concat([c_data,pd.get_dummies(c_data['Card_Category']).drop(columns=['Platinum'])],axis=1)
    c_data.drop(columns = ['Education_Level','Income_Category','Marital_Status','Card_Category','CLIENTNUM'],inplace=True)


    显示热力图:

    sns.heatmap(c_data.corr('pearson'),annot=True)

    3.5 SMOTE模型采样

    SMOTE模型经常用于解决数据不平衡的问题,它通过添加生成的少数类样本改变不平衡数据集的数据分布,是改善不平衡数据分类模型性能的流行方法之一。

    oversample = SMOTE()
    X, y = oversample.fit_resample(c_data[c_data.columns[1:]], c_data[c_data.columns[0]])
    usampled_df = X.assign(Churn = y)
    ohe_data =usampled_df[usampled_df.columns[15:-1]].copy()
    usampled_df = usampled_df.drop(columns=usampled_df.columns[15:-1])
    sns.heatmap(usampled_df.corr('pearson'),annot=True)

    3.6 主成分分析

    我们将使用主成分分析来降低单次编码分类变量的维数,从而降低方差。同时使用几个主成分而不是几十个单次编码特征将帮助我构建一个更好的模型。

    N_COMPONENTS = 4
    
    pca_model = PCA(n_components = N_COMPONENTS )
    
    pc_matrix = pca_model.fit_transform(ohe_data)
    
    evr = pca_model.explained_variance_ratio_
    cumsum_evr = np.cumsum(evr)
    
    ax = sns.lineplot(x=np.arange(0,len(cumsum_evr)),y=cumsum_evr,label='Explained Variance Ratio')
    ax.set_title('Explained Variance Ratio Using {} Components'.format(N_COMPONENTS))
    ax = sns.lineplot(x=np.arange(0,len(cumsum_evr)),y=evr,label='Explained Variance Of Component X')
    ax.set_xticks([i for i in range(0,len(cumsum_evr))])
    ax.set_xlabel('Component number #')
    ax.set_ylabel('Explained Variance')
    plt.show()

    usampled_df_with_pcs = pd.concat([usampled_df,pd.DataFrame(pc_matrix,columns=['PC-{}'.format(i) for i in range(0,N_COMPONENTS)])],axis=1)
    usampled_df_with_pcs

    特征变得越来越明显:

    sns.heatmap(usampled_df_with_pcs.corr('pearson'),annot=True)

    4.模型选择及测试

    选择出以下特征划分训练集并进行训练:

    X_features = ['Total_Trans_Ct','PC-3','PC-1','PC-0','PC-2','Total_Ct_Chng_Q4_Q1','Total_Relationship_Count']
    
    X = usampled_df_with_pcs[X_features]
    y = usampled_df_with_pcs['Churn']
    
    train_x,test_x,train_y,test_y = train_test_split(X,y,random_state=42)

    4.1 交叉验证

    分别看看随机森林、AdaBoost和SVM模型三种模型的表现如何:

    rf_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",RandomForestClassifier(random_state=42)) ])
    ada_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",AdaBoostClassifier(random_state=42,learning_rate=0.7)) ])
    svm_pipe = Pipeline(steps =[ ('scale',StandardScaler()), ("RF",SVC(random_state=42,kernel='rbf')) ])
    
    
    f1_cross_val_scores = cross_val_score(rf_pipe,train_x,train_y,cv=5,scoring='f1')
    ada_f1_cross_val_scores=cross_val_score(ada_pipe,train_x,train_y,cv=5,scoring='f1')
    svm_f1_cross_val_scores=cross_val_score(svm_pipe,train_x,train_y,cv=5,scoring='f1')
    plt.subplot(3,1,1)
    ax = sns.lineplot(x=range(0,len(f1_cross_val_scores)),y=f1_cross_val_scores)
    ax.set_title('Random Forest Cross Val Scores')
    ax.set_xticks([i for i in range(0,len(f1_cross_val_scores))])
    ax.set_xlabel('Fold Number')
    ax.set_ylabel('F1 Score')
    plt.show()
    plt.subplot(3,1,2)
    ax = sns.lineplot(x=range(0,len(ada_f1_cross_val_scores)),y=ada_f1_cross_val_scores)
    ax.set_title('Adaboost Cross Val Scores')
    ax.set_xticks([i for i in range(0,len(ada_f1_cross_val_scores))])
    ax.set_xlabel('Fold Number')
    ax.set_ylabel('F1 Score')
    plt.show()
    plt.subplot(3,1,3)
    ax = sns.lineplot(x=range(0,len(svm_f1_cross_val_scores)),y=svm_f1_cross_val_scores)
    ax.set_title('SVM Cross Val Scores')
    ax.set_xticks([i for i in range(0,len(svm_f1_cross_val_scores))])
    ax.set_xlabel('Fold Number')
    ax.set_ylabel('F1 Score')
    plt.show()


    看看三种模型都有什么不同的表现:

    看得出来随机森林 F1分数是最高的,达到了0.92。

    4.2 模型预测

    对测试集进行预测,看看三种模型的效果:

    rf_pipe.fit(train_x,train_y)
    rf_prediction = rf_pipe.predict(test_x)
    
    ada_pipe.fit(train_x,train_y)
    ada_prediction = ada_pipe.predict(test_x)
    
    svm_pipe.fit(train_x,train_y)
    svm_prediction = svm_pipe.predict(test_x)
    
    print('F1 Score of Random Forest Model On Test Set - {}'.format(f1(rf_prediction,test_y)))
    print('F1 Score of AdaBoost Model On Test Set - {}'.format(f1(ada_prediction,test_y)))
    print('F1 Score of SVM Model On Test Set - {}'.format(f1(svm_prediction,test_y)))

    4.3 对原始数据(采样前)进行模型预测

    接下来对原始数据进行模型预测:

    ohe_data =c_data[c_data.columns[16:]].copy()
    pc_matrix = pca_model.fit_transform(ohe_data)
    original_df_with_pcs = pd.concat([c_data,pd.DataFrame(pc_matrix,columns=['PC-{}'.format(i) for i in range(0,N_COMPONENTS)])],axis=1)
    
    unsampled_data_prediction_RF = rf_pipe.predict(original_df_with_pcs[X_features])
    unsampled_data_prediction_ADA = ada_pipe.predict(original_df_with_pcs[X_features])
    unsampled_data_prediction_SVM = svm_pipe.predict(original_df_with_pcs[X_features])

    效果如下:



    F1最高的随机森林模型有0.63分,偏低,这也比较正常,毕竟在这种分布不均的数据集中,查全率是比较难拿到高分数的。

    4.4 结果

    让我们看看最终在原数据上使用随机森林模型的运行结果:

    ax = sns.heatmap(confusion_matrix(unsampled_data_prediction_RF,original_df_with_pcs['Attrition_Flag']),annot=True,cmap='coolwarm',fmt='d')
    ax.set_title('Prediction On Original Data With Random Forest Model Confusion Matrix')
    ax.set_xticklabels(['Not Churn','Churn'],fontsize=18)
    ax.set_yticklabels(['Predicted Not Churn','Predicted Churn'],fontsize=18)
    
    plt.show()

    可见,没有流失的客户命中了7709人,未命中791人。

    流失客户命中了1130人,未命中497人。

    整体而言,是一个比较优秀的模型了。

    我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

    有任何问题,可以在公众号后台回复:加群,回答相应红字验证信息,进入互助群询问。

    原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

    点击下方阅读原文可获得更好的阅读体验

    Python实用宝典 (pythondict.com)
    不只是一个宝典
    欢迎关注公众号:Python实用宝典

    展开全文
  • 源自:数据分析不是个事儿作者:启方客户流失是所有与消费者挂钩行业都会关注的点。因为发展一个新客户是需要一定成本的,一旦客户流失,成本浪费不说,挽回一个客户的成本更大。今天分享一个用户流失...

    源自:数据分析不是个事儿

    作者:启方

    客户流失是所有与消费者挂钩行业都会关注的点。因为发展一个新客户是需要一定成本的,一旦客户流失,成本浪费不说,挽回一个客户的成本更大。

    今天分享一个用户流失预测,以电信行业为例。

    所以,电信行业在竞争日益激烈当下,如何挽留更多用户成为一项关键业务指标。为了更好运营用户,这就要求要了解流失用户的特征,分析流失原因,预测用户流失,确定挽留目标用户并制定有效方案。

    一、提出问题

    1、哪些用户可能会流失?

    2、流失概率更高的用户有什么共同特征?

    二、理解数据

    1、采集数据
    本数据集来自DF ,数据源地址:
    https://www.datafountain.cn/dataSets/35/details#

    本数据集描述了电信用户是否流失以及其相关信息,共包含7044条数据,共20个字段,介绍下各个字段:

    • customerID :用户ID。

    • gender:性别。(Female & Male)

    • SeniorCitizen :老年人 (1表示是,0表示不是)

    • Partner :是否有配偶 (Yes or No)

    • Dependents :是否经济独立 (Yes or No)

    • tenure :客户的职位(0-72,共73个职位)

    • PhoneService :是否开通电话服务业务 (Yes or No)

    • MultipleLines:是否开通了多线业务(Yes 、No or No phoneservice 三种)

    • InternetService:是否开通互联网服务 (No, DSL数字网络,fiber optic光纤网络 三种)

    • OnlineSecurity:是否开通网络安全服务(Yes,No,No internetserive 三种)

    • OnlineBackup:是否开通在线备份业务(Yes,No,No internetserive 三种)

    • DeviceProtection:是否开通了设备保护业务(Yes,No,No internetserive 三种)

    • TechSupport:是否开通了技术支持服务(Yes,No,No internetserive 三种)

    • StreamingTV:是否开通网络电视(Yes,No,No internetserive 三种)

    • StreamingMovies:是否开通网络电影(Yes,No,No internetserive 三种)

    • Contract:签订合同方式 (按月,一年,两年)

    • PaperlessBilling:是否开通电子账单(Yes or No)

    • PaymentMethod:付款方式(bank transfer,credit card,electronic check,mailed check)

    • MonthlyCharges:月费用

    • TotalCharges:总费用

    • Churn:该用户是否流失(Yes or No)

    2、导入数据


    3、查看数据集信息

    三、数据清洗

    1、查找缺失值

    数据集中有5174名用户没流失,有1869名客户流失,数据集不均衡。

    2、查看数据类型

    TotalCharges表示总费用,这里为对象类型,需要转换为float类型

    3、转换类型

    再次查找缺失值:

    这里存在11个缺失值,由于数量不多我们可以直接删除这些行

    4、处理缺失值

    5、数据归一化处理

    四、数据可视化呈现

    1、查看流失客户占比


    由图中结果可以看出,流失客户占整体客户的26.6%。

    2、性别、老年人、配偶、亲属对流客户流失率的影响

    性别、老年人占比结果

    配偶、亲属占比结果

    可以看出,男性与女性用户之间的流失情况基本没有差异,而在老年用户中流失占比明显比非老年用户更高,在所有数据中未婚与已婚人数基本持平,但未婚中流失人数比已婚中的流失人数高出了快一倍,从经济独立情况来看,经济未独立的用户流失率要远远高于经济独立的用户。

    3、提取特征

    4、构造相关性矩阵

    5、使用热地图显示相关系数

    结论:

    从上图可以看出,互联网服务、网络安全服务、在线备份业务、设备保护业务、技术支持服务、网络电视和网络电影之间存在较强的相关性,多线业务和电话服务之间也有很强的相关性,并且都呈强正相关关系。

    6、使用one-hot编码

    7、电信用户是否流失与各变量之间的相关性

    由图上可以看出,变量gender 和 PhoneService 处于图形中间,其值接近于 0 ,这两个变量对电信客户流失预测影响非常小,可以直接舍弃。

    8、网络安全服务、在线备份业务、设备保护业务、技术支持服务、网络电视、网络电影和无互联网服务对客户流失率的影响

    由上图可以看出,在网络安全服务、在线备份业务、设备保护业务、技术支持服务、网络电视和网络电影六个变量中,没有互联网服务的客户流失率值是相同的,都是相对较低。

    这可能是因为以上六个因素只有在客户使用互联网服务时才会影响客户的决策,这六个因素不会对不使用互联网服务的客户决定是否流失产生推论效应。

    9、签订合同方式对客户流失率的影响

    由图上可以看出,签订合同方式对客户流失率影响为:按月签订 > 按一年签订 > 按两年签订,这可能表明,设定长期合同对留住现有客户更有效。

    10、付款方式对客户流失率的影响

    由图上可以看出,在四种支付方式中,使用Electronic check的用户流流失率最高,其他三种支付方式基本持平,因此可以推断电子账单在设计上影响用户体验。

    五、数据预处理

    由前面结果可知,CustomerID表示每个客户的随机字符,对后续建模不影响,我这里选择删除CustomerID列;gender 和 PhoneService 与流失率的相关性低,可直接忽略。

    对客户的职位、月费用和总费用进行去均值和方差缩放,对数据进行标准化:

    使用箱线图查看数据是否存在异常值:

    由以上结果可以看出,在三个变量中不存在明显的异常值。

    查看对象类型字段中存在的值:

    综合之前的结果来看,在六个变量中存在No internet service,即无互联网服务对客户流失率影响很小,这些客户不使用任何互联网产品,因此可以将No internet service 和 No 是一样的效果,可以使用 No 替代 No internet service。

    使用Scikit-learn标签编码,将分类数据转换为整数编码:

    六、构建模型

    1、建立训练数据集和测试数据集

    2、选择机器学习算法

    3、训练模型

    4、评估模型

    召回率(recall)的含义是:原本为对的当中,预测为对的比例(值越大越好,1为理想状态)
    精确率、精度(precision)的含义是:预测为对的当中,原本为对的比例(值越大越好,1为理想状态)
    F1分数(F1-Score)指标综合了Precision与Recall的产出的结果

    F1-Score的取值范围从0到1的,1代表模型的输出最好,0代表模型的输出结果最差。

    综上所述,在10种分类算法中朴素贝叶斯(Naive Bayes)的F1分数最大为63.31%,所以使用朴素贝叶斯模型效果最好。

    七、实施方案

    八、结论

    通过上述分析,我们可以大致勾勒出容易流失的用户特征:

    老年用户与未婚且经济未独立的青少年用户更容易流失。

    电话服务对用户的流失没有直接的影响。

    提供的各项网络服务项目能够降低用户的流失率。

    签订合同越久,用户的留存率越高。

    采用electronic check支付的用户更易流失。

    针对上述诊断结果,可有针对性的对此提出建议:
    推荐老年用户与青少年用户采用数字网络,且签订2年期合同(可以各种辅助优惠等营销手段来提高2年期合同的签订率),若能开通相关网络服务可增加用户粘性,因此可增加这块业务的推广,同时考虑改善电子账单支付的用户体验。

    最后,分享源码:

    # coding: utf-8
    
    
    # # 电信客户流失预测
    
    
    # ## 1、导入数据
    
    
    # In[1]:
    
    
    import numpy as np
    import pandas as pd 
    import os
    
    
    
    
    # In[2]:
    
    
    # 导入相关的包
    import matplotlib.pyplot as plt
    import seaborn as sns
    from pylab import rcParams
    import matplotlib.cm as cm
    
    
    import sklearn
    from sklearn import preprocessing
    from sklearn.preprocessing import LabelEncoder               # 编码转换
    from sklearn.preprocessing import StandardScaler
    from sklearn.model_selection import StratifiedShuffleSplit
    
    
    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              # 决策树分类器
    from xgboost import XGBClassifier
    from catboost import CatBoostClassifier
    from sklearn.ensemble import AdaBoostClassifier
    from sklearn.ensemble import GradientBoostingClassifier     
    
    
    from sklearn.metrics import classification_report, precision_score, recall_score, f1_score
    from sklearn.metrics import confusion_matrix
    from sklearn.model_selection import GridSearchCV
    from sklearn.metrics import make_scorer
    from sklearn.ensemble import VotingClassifier
    
    
    from sklearn.decomposition import PCA
    from sklearn.cluster import KMeans
    from sklearn.metrics import silhouette_score
    
    
    import warnings
    warnings.filterwarnings('ignore')
    
    
    get_ipython().magic('matplotlib inline')
    
    
    
    
    # In[3]:
    
    
    # 读取数据文件
    telcom=pd.read_csv(r"F:\data\WA_Fn-UseC_-Telco-Customer-Churn.csv")
    
    
    
    
    # ## 2、查看数据集信息
    
    
    # In[4]:
    
    
    telcom.head(10)
    
    
    
    
    # In[5]:
    
    
    # 查看数据集大小
    telcom.shape
    
    
    
    
    # In[6]:
    
    
    # 获取数据类型列的描述统计信息
    telcom.describe()
    
    
    
    
    # ## 3、数据清洗
    
    
    # In[7]:
    
    
    # 查找缺失值
    pd.isnull(telcom).sum()
    
    
    
    
    # In[8]:
    
    
    telcom["Churn"].value_counts()
    
    
    
    
    # 数据集中有5174名用户没流失,有1869名客户流失,数据集不均衡。
    
    
    # In[9]:
    
    
    telcom.info()
    
    
    
    
    # TotalCharges表示总费用,这里为对象类型,需要转换为float类型  
    
    
    # In[10]:
    
    
    telcom['TotalCharges']=telcom['TotalCharges'].convert_objects(convert_numeric=True) # convert_numeric=True表示强制转换数字(包括字符串),不可转换的值变为NaN
    telcom["TotalCharges"].dtypes
    
    
    
    
    # In[11]:
    
    
    # 再次查找是否存在缺失值
    pd.isnull(telcom["TotalCharges"]).sum()
    
    
    
    
    # 这里存在11个缺失值,由于数量不多我们可以直接删除这些行
    
    
    # In[12]:
    
    
    # 删除缺失值所在的行
    telcom.dropna(inplace=True)
    telcom.shape
    
    
    
    
    # In[13]:
    
    
    # 数据归一化处理
    # 对Churn 列中的值 Yes和 No分别用 1和 0替换,方便后续处理
    telcom['Churn'].replace(to_replace = 'Yes', value = 1,inplace = True)
    telcom['Churn'].replace(to_replace = 'No', value = 0,inplace = True)
    telcom['Churn'].head()
    
    
    
    
    # In[14]:
    
    
    telcom['Churn'].replace(to_replace='Yes', value=1, inplace=True)
    telcom['Churn'].replace(to_replace='No',  value=0, inplace=True)
    telcom['Churn'].head()
    
    
    
    
    # ## 4、数据可视化呈现
    
    
    # In[15]:
    
    
    # 查看流失客户占比
    """
    画饼图参数:
    labels  (每一块)饼图外侧显示的说明文字
    explode  (每一块)离开中心距离
    startangle  起始绘制角度,默认图是从x轴正方向逆时针画起,如设定=90则从y轴正方向画起
    shadow   是否阴影
    labeldistance label  绘制位置,相对于半径的比例, 如<1则绘制在饼图内侧
    autopct   控制饼图内百分比设置,可以使用format字符串或者format function
         '%1.1f'指小数点前后位数(没有用空格补齐)
    pctdistance 类似于labeldistance,指定autopct的位置刻度
    radius   控制饼图半径
    """
    churnvalue=telcom["Churn"].value_counts()
    labels=telcom["Churn"].value_counts().index
    
    
    rcParams["figure.figsize"]=6,6
    plt.pie(churnvalue,labels=labels,colors=["whitesmoke","yellow"], explode=(0.1,0),autopct='%1.1f%%', shadow=True)
    plt.title("Proportions of Customer Churn")
    plt.show()
    
    
    
    
    # In[16]:
    
    
    # 性别、老年人、配偶、亲属对流客户流失率的影响
    f, axes = plt.subplots(nrows=2, ncols=2, figsize=(10,10))
    
    
    plt.subplot(2,2,1)
    gender=sns.countplot(x="gender",hue="Churn",data=telcom,palette="Pastel2") # palette参数表示设置颜色,这里设置为主题色Pastel2
    plt.xlabel("gender")
    plt.title("Churn by Gender")
    
    
    plt.subplot(2,2,2)
    seniorcitizen=sns.countplot(x="SeniorCitizen",hue="Churn",data=telcom,palette="Pastel2")
    plt.xlabel("senior citizen")
    plt.title("Churn by Senior Citizen")
    
    
    plt.subplot(2,2,3)
    partner=sns.countplot(x="Partner",hue="Churn",data=telcom,palette="Pastel2")
    plt.xlabel("partner")
    plt.title("Churn by Partner")
    
    
    plt.subplot(2,2,4)
    dependents=sns.countplot(x="Dependents",hue="Churn",data=telcom,palette="Pastel2")
    plt.xlabel("dependents")
    plt.title("Churn by Dependents")
    
    
    
    
    # In[17]:
    
    
    # 提取特征
    charges=telcom.iloc[:,1:20]
    # 对特征进行编码
    """
    离散特征的编码分为两种情况:
    1、离散特征的取值之间没有大小的意义,比如color:[red,blue],那么就使用one-hot编码
    2、离散特征的取值有大小的意义,比如size:[X,XL,XXL],那么就使用数值的映射{X:1,XL:2,XXL:3}
    """
    corrDf = charges.apply(lambda x: pd.factorize(x)[0])
    corrDf .head()
    
    
    
    
    # In[18]:
    
    
    # 构造相关性矩阵
    corr = corrDf.corr()
    corr
    
    
    
    
    # In[19]:
    
    
    # 使用热地图显示相关系数
    '''
    heatmap    使用热地图展示系数矩阵情况
    linewidths 热力图矩阵之间的间隔大小
    annot      设定是否显示每个色块的系数值
    '''
    plt.figure(figsize=(20,16))
    ax = sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns, 
                     linewidths=0.2, cmap="YlGnBu",annot=True)
    plt.title("Correlation between variables")
    
    
    
    
    # 结论:从上图可以看出,互联网服务、网络安全服务、在线备份业务、设备保护业务、技术支持服务、网络电视和网络电影之间存在较强的相关性,多线业务和电话服务之间也有很强的相关性,并且都呈强正相关关系。
    
    
    # In[20]:
    
    
    # 使用one-hot编码
    tel_dummies = pd.get_dummies(telcom.iloc[:,1:21])
    tel_dummies.head()
    
    
    
    
    # In[21]:
    
    
    # 电信用户是否流失与各变量之间的相关性
    plt.figure(figsize=(15,8))
    tel_dummies.corr()['Churn'].sort_values(ascending = False).plot(kind='bar')
    plt.title("Correlations between Churn and variables")
    
    
    
    
    # 由图上可以看出,变量gender 和 PhoneService 处于图形中间,其值接近于 0 ,这两个变量对电信客户流失预测影响非常小,可以直接舍弃。
    
    
    # In[22]:
    
    
    # 网络安全服务、在线备份业务、设备保护业务、技术支持服务、网络电视、网络电影和无互联网服务对客户流失率的影响
    covariables=["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies"]
    fig,axes=plt.subplots(nrows=2,ncols=3,figsize=(16,10))
    for i, item in enumerate(covariables):
        plt.subplot(2,3,(i+1))
        ax=sns.countplot(x=item,hue="Churn",data=telcom,palette="Pastel2",order=["Yes","No","No internet service"])
        plt.xlabel(str(item))
        plt.title("Churn by "+ str(item))
        i=i+1
    plt.show()
    
    
    
    
    # 由上图可以看出,在网络安全服务、在线备份业务、设备保护业务、技术支持服务、网络电视和网络电影六个变量中,没有互联网服务的客户流失率值是相同的,都是相对较低。
    # 
    # 这可能是因为以上六个因素只有在客户使用互联网服务时才会影响客户的决策,这六个因素不会对不使用互联网服务的客户决定是否流失产生推论效应。
    
    
    # In[23]:
    
    
    # 签订合同方式对客户流失率的影响
    sns.barplot(x="Contract",y="Churn", data=telcom, palette="Pastel1", order= ['Month-to-month', 'One year', 'Two year'])
    plt.title("Churn by Contract type")
    
    
    
    
    # 由图上可以看出,签订合同方式对客户流失率影响为:按月签订 > 按一年签订 > 按两年签订,这可能表明,设定长期合同对留住现有客户更有效。
    
    
    # In[24]:
    
    
    # 付款方式对客户流失率的影响
    plt.figure(figsize=(10,5))
    sns.barplot(x="PaymentMethod",y="Churn", data=telcom, palette="Pastel1", order= ['Bank transfer (automatic)', 'Credit card (automatic)', 'Electronic check','Mailed check'])
    plt.title("Churn by PaymentMethod type")
    
    
    
    
    # 由图上可以看出,在四种支付方式中,使用Electronic check的用户流流失率最高,其他三种支付方式基本持平,因此可以推断电子账单在设计上影响用户体验。
    
    
    
    
    
    
    
    
    # ## 5、数据预处理
    
    
    # 由前面结果可知,CustomerID表示每个客户的随机字符,对后续建模不影响,我这里选择删除CustomerID列;gender 和 PhoneService 与流失率的相关性低,可直接忽略。
    
    
    # In[26]:
    
    
    telcomvar=telcom.iloc[:,2:20]
    telcomvar.drop("PhoneService",axis=1, inplace=True)
    
    
    # 提取ID
    telcom_id = telcom['customerID']
    
    
    telcomvar.head()
    
    
    
    
    # In[27]:
    
    
    # 对客户的职位、月费用和总费用进行去均值和方差缩放,对数据进行标准化
    """
    标准化数据,保证每个维度的特征数据方差为1,均值为0,使得预测结果不会被某些维度过大的特征值而主导。
    """
    scaler = StandardScaler(copy=False)
    # fit_transform()的作用就是先拟合数据,然后转化它将其转化为标准形式
    scaler.fit_transform(telcomvar[['tenure','MonthlyCharges','TotalCharges']])
    
    
    
    
    # In[28]:
    
    
    # tranform()的作用是通过找中心和缩放等实现标准化
    telcomvar[['tenure','MonthlyCharges','TotalCharges']]=scaler.transform(telcomvar[['tenure','MonthlyCharges','TotalCharges']])
    
    
    
    
    # In[29]:
    
    
    # 使用箱线图查看数据是否存在异常值
    plt.figure(figsize = (8,4))
    numbox = sns.boxplot(data=telcomvar[['tenure','MonthlyCharges','TotalCharges']], palette="Set2")
    plt.title("Check outliers of standardized tenure, MonthlyCharges and TotalCharges")
    
    
    
    
    # 由以上结果可以看出,在三个变量中不存在明显的异常值
    
    
    # In[30]:
    
    
    # 查看对象类型字段中存在的值
    def uni(columnlabel):
        print(columnlabel,"--" ,telcomvar[columnlabel].unique())  # unique函数去除其中重复的元素,返回唯一值
    
    
    telcomobject=telcomvar.select_dtypes(['object'])
    for i in range(0,len(telcomobject.columns)):
        uni(telcomobject.columns[i])
    
    
    
    
    # 综合之前的结果来看,在六个变量中存在No internet service,即无互联网服务对客户流失率影响很小,这些客户不使用任何互联网产品,因此可以将No internet service 和 No 是一样的效果,可以使用 No 替代 No internet service
    
    
    # In[31]:
    
    
    telcomvar.replace(to_replace='No internet service', value='No', inplace=True)
    telcomvar.replace(to_replace='No phone service', value='No', inplace=True)
    for i in range(0,len(telcomobject.columns)):
        uni(telcomobject.columns[i])
    
    
    
    
    # In[32]:
    
    
    # 使用Scikit-learn标签编码,将分类数据转换为整数编码
    def labelencode(columnlabel):
        telcomvar[columnlabel] = LabelEncoder().fit_transform(telcomvar[columnlabel])
    
    
    for i in range(0,len(telcomobject.columns)):
        labelencode(telcomobject.columns[i])
    
    
    for i in range(0,len(telcomobject.columns)):
        uni(telcomobject.columns[i])
    
    
    
    
    # ## 6、构建模型
    
    
    # ### (1)建立训练数据集和测试数据集
    
    
    # In[33]:
    
    
    """
    我们需要将数据集拆分为训练集和测试集以进行验证。
    由于我们所拥有的数据集是不平衡的,所以最好使用分层交叉验证来确保训练集和测试集都包含每个类样本的保留人数。
    交叉验证函数StratifiedShuffleSplit,功能是从样本数据中随机按比例选取训练数据(train)和测试数据(test)
    参数 n_splits是将训练数据分成train/test对的组数,可根据需要进行设置,默认为10
    参数test_size和train_size是用来设置train/test对中train和test所占的比例
    参数 random_state控制是将样本随机打乱
    """
    X=telcomvar
    y=telcom["Churn"].values
    
    
    sss=StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
    print(sss)
    print("训练数据和测试数据被分成的组数:",sss.get_n_splits(X,y))
    
    
    
    
    # In[34]:
    
    
    # 建立训练数据和测试数据
    for train_index, test_index in sss.split(X, y):
        print("train:", train_index, "test:", test_index)
        X_train,X_test=X.iloc[train_index], X.iloc[test_index]
        y_train,y_test=y[train_index], y[test_index]
    
    
    
    
    # In[35]:
    
    
    # 输出数据集大小
    print('原始数据特征:', X.shape,
          '训练数据特征:',X_train.shape,
          '测试数据特征:',X_test.shape)
    
    
    print('原始数据标签:', y.shape,
          '   训练数据标签:',y_train.shape,
          '   测试数据标签:',y_test.shape)
    
    
    
    
    # ### (2)选择机器学习算法
    
    
    # In[36]:
    
    
    # 使用分类算法,这里选用10种分类算法
    Classifiers=[["Random Forest",RandomForestClassifier()],
                 ["Support Vector Machine",SVC()],
                 ["LogisticRegression",LogisticRegression()],
                 ["KNN",KNeighborsClassifier(n_neighbors=5)],
                 ["Naive Bayes",GaussianNB()],
                 ["Decision Tree",DecisionTreeClassifier()],
                 ["AdaBoostClassifier", AdaBoostClassifier()],
                 ["GradientBoostingClassifier", GradientBoostingClassifier()],
                 ["XGB", XGBClassifier()],
                 ["CatBoost", CatBoostClassifier(logging_level='Silent')]  
    ]
    
    
    
    
    # ### (3)训练模型
    
    
    # In[37]:
    
    
    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)
        class_eva=pd.DataFrame([recall,precision])
        Classify_result.append(class_eva)
        name=pd.Series(name)
        names.append(name)
        y_pred=pd.Series(y_pred)
        prediction.append(y_pred)
    
    
    
    
    # ### (4)评估模型
    
    
    # In[38]:
    
    
    # 评估模型
    """
    召回率(recall)的含义是:原本为对的当中,预测为对的比例(值越大越好,1为理想状态)
    精确率、精度(precision)的含义是:预测为对的当中,原本为对的比例(值越大越好,1为理想状态)
    F1分数(F1-Score)指标综合了Precision与Recall的产出的结果
    F1-Score的取值范围从0到1的,1代表模型的输出最好,0代表模型的输出结果最差。
    """
    
    
    names=pd.DataFrame(names)
    names=names[0].tolist()
    result=pd.concat(Classify_result,axis=1)
    result.columns=names
    result.index=["recall","precision","f1score"]
    result
    
    
    
    
    # 综上所述,在10种分类算法中朴素贝叶斯(Naive Bayes)的F1分数最大为63.31%,所以使用朴素贝叶斯模型效果最好。
    
    
    # ## 7、实施方案
    
    
    # 预测数据集特征(由于没有提供预测数据集,这里选取后10行作为需要预测的数据集)
    pred_X = telcomvar.tail(10)
    
    
    # 提取customerID
    pre_id = telcom_id.tail(10)
    
    
    # 使用朴素贝叶斯方法,对预测数据集中的生存情况进行预测
    model = GaussianNB()
    model.fit(X_train,y_train)
    pred_y = model.predict(pred_X)
    
    
    # 预测结果
    predDf = pd.DataFrame({'customerID':pre_id, 'Churn':pred_y})
    predDf
    

         精 彩 文 章 

    END
    最后说个题外话,相信大家都知道视频号了,随着灰度范围扩大,越来越多的小伙伴都开通了视频号。小詹也开通了一个视频号,会分享互联网那些事、读书心得与副业经验,欢迎扫码关注,和小詹一起向上生长!「没有开通发布权限的尽量多互动,提升活跃度可以更快开通哦」
    
    
    展开全文
  • 之前我们用Python写了员工流失预测模型,这次我们试试Python预测电信用户的流失。 01、商业理解 流失客户是指那些曾经使用过产品或服务,由于对产品失去兴趣等种种原因,不再使用产品或服务的顾客。 电信服务公司...

     

     CDA数据分析师 出品  

    作者:真达、Mika

    数据:真达  

    【导读】

    今天教大家如何用Python写一个电信用户流失预测模型。之前我们用Python写了员工流失预测模型,这次我们试试Python预测电信用户的流失。

    01、商业理解

    流失客户是指那些曾经使用过产品或服务,由于对产品失去兴趣等种种原因,不再使用产品或服务的顾客。

    电信服务公司、互联网服务提供商、保险公司等经常使用客户流失分析和客户流失率作为他们的关键业务指标之一,因为留住一个老客户的成本远远低于获得一个新客户。

    预测分析使用客户流失预测模型,通过评估客户流失的风险倾向来预测客户流失。由于这些模型生成了一个流失概率排序名单,对于潜在的高概率流失客户,他们可以有效地实施客户保留营销计划。

    下面我们就教你如何用Python写一个电信用户流失预测模型,以下是具体步骤和关键代码。

    02、数据理解

    此次分析数据来自于IBM Sample Data Sets,统计自某电信公司一段时间内的消费数据。共有7043笔客户资料,每笔客户资料包含21个字段,其中1个客户ID字段,19个输入字段及1个目标字段-Churn(Yes代表流失,No代表未流失),输入字段主要包含以下三个维度指标:用户画像指标、消费产品指标、消费信息指标。字段的具体说明如下:

    03、数据读入和概览

    首先导入所需包。

    # 数据处理
    import numpy as np 
    import pandas as pd 
    
    # 可视化
    import matplotlib.pyplot as plt 
    import seaborn as sns 
    import plotly as py 
    import plotly.graph_objs as go 
    import plotly.figure_factory as ff 
    
    # 前处理
    from sklearn.preprocessing import LabelEncoder
    from sklearn.preprocessing import StandardScaler
    
    # 建模
    from sklearn.linear_model import LogisticRegression
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.tree import DecisionTreeClassifier
    from sklearn import tree 
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.naive_bayes import GaussianNB
    from sklearn.neural_network import MLPClassifier
    from sklearn.svm import SVC
    from lightgbm import LGBMClassifier
    from xgboost import XGBClassifier
    
    # 模型评估
    from sklearn.model_selection import train_test_split, GridSearchCV
    from sklearn.metrics import confusion_matrix, accuracy_score, classification_report
    from sklearn.metrics import roc_auc_score, roc_curve, scorer
    from sklearn.metrics import recall_score, precision_score, f1_score, cohen_kappa_score
    
    pd.set_option('display.max_columns', None) 
    

    读入数据集

    df = pd.read_csv('./Telco-Customer-Churn.csv')
    df.head()  

    04、数据初步清洗

    首先进行初步的数据清洗工作,包含错误值和异常值处理,并划分类别型和数值型字段类型,其中清洗部分包含:

    • OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport、StreamingTV、StreamingMovies:错误值处理
    • TotalCharges:异常值处理
    • tenure:自定义分箱
    • 定义类别型和数值型字段
    # 错误值处理
    repl_columns = ['OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 
                    'TechSupport','StreamingTV', 'StreamingMovies']
    
    for i in repl_columns:
        df[i]  = df[i].replace({'No internet service' : 'No'}) 
    
    # 替换值SeniorCitizen
    df["SeniorCitizen"] = df["SeniorCitizen"].replace({1: "Yes", 0: "No"}) 
    
    # 替换值TotalCharges
    df['TotalCharges'] = df['TotalCharges'].replace(' ', np.nan) 
    
    # TotalCharges空值:数据量小,直接删除
    df = df.dropna(subset=['TotalCharges']) 
    df.reset_index(drop=True, inplace=True)  # 重置索引
    
    # 转换数据类型
    df['TotalCharges'] = df['TotalCharges'].astype('float')
    
    # 转换tenure
    def transform_tenure(x):
        if x <= 12:
            return 'Tenure_1'
        elif x <= 24:
            return 'Tenure_2'
        elif x <= 36:
            return 'Tenure_3'
        elif x <= 48:
            return 'Tenure_4'
        elif x <= 60:
            return 'Tenure_5'
        else:
            return 'Tenure_over_5' 
    
    df['tenure_group'] = df.tenure.apply(transform_tenure)
    
    # 数值型和类别型字段
    Id_col = ['customerID']
    target_col = ['Churn']
    
    cat_cols = df.nunique()[df.nunique() < 10].index.tolist() 
    num_cols = [i for i in df.columns if i not in cat_cols + Id_col] 
    
    print('类别型字段:\n', cat_cols)
    print('-' * 30) 
    print('数值型字段:\n', num_cols)
    类别型字段:
     ['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'PhoneService', 
      'MultipleLines', 'InternetService', 'OnlineSecurity',
      'OnlineBackup', 'DeviceProtection', 'TechSupport',
      'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling', 
      'PaymentMethod', 'Churn', 'tenure_group']
    ------------------------------
    数值型字段:
     ['tenure', 'MonthlyCharges', 'TotalCharges']

    05、探索性分析

    对指标进行归纳梳理,分用户画像指标,消费产品指标,消费信息指标。探索影响用户流失的关键因素。

    1. 目标变量Churn分布

    经过初步清洗之后的数据集大小为7032条记录,其中流失客户为1869条,占比26.6%,未流失客户占比73.4%。

    df['Churn'].value_counts() 
    No     5163
    Yes    1869
    Name: Churn, dtype: int64

     

    trace0 = go.Pie(labels=df['Churn'].value_counts().index, 
                    values=df['Churn'].value_counts().values,
                    hole=.5,
                    rotation=90,
                    marker=dict(colors=['rgb(154,203,228)', 'rgb(191,76,81)'], 
                                line=dict(color='white', width=1.3))
                   )
    data = [trace0] 
    layout = go.Layout(title='目标变量Churn分布')
    
    fig = go.Figure(data=data, layout=layout)
    py.offline.plot(fig, filename='./html/整体流失情况分布.html')

    2.性别

    分析可见,男性和女性在客户流失比例上没有显著差异。

    plot_bar(input_col='gender', target_col='Churn', title_name='性别与是否流失的关系') 

    3. 老年用户

    老年用户流失比例更高,为41.68%,比非老年用户高近两倍,此部分原因有待进一步探讨。

    plot_bar(input_col='SeniorCitizen', target_col='Churn', title_name='老年用户与是否流失的关系') 

    4. 是否有配偶

    从婚姻情况来看,数据显示,未婚人群中流失的比例比已婚人数高出13%。

    plot_bar(input_col='Partner', target_col='Churn', title_name='是否有配偶与是否流失的关系') 

    5. 上网时长

    经过分析,这方面可以得出两个结论:

    • 用户的在网时长越长,表示用户的忠诚度越高,其流失的概率越低;
    • 新用户在1年内的流失率显著高于整体流失率,为47.68%。
    plot_bar(input_col='tenure_group', target_col='Churn', title_name='在网时长与是否流失的关系') 

    6. 付款方式

    支付方式上,支付上,选择电子支票支付方式的用户流失最高,达到45.29%,其他三种支付方式的流失率相差不大。

    pd.crosstab(df['PaymentMethod'], df['Churn']) 

    plot_bar(input_col='PaymentMethod', target_col='Churn', title_name='付款方式与是否流失关系') 

    7. 月费用

    整体来看,随着月费用的增加,流失用户的比例呈现高高低低的变化,月消费80-100元的用户相对较高。

    plot_histogram(input_col='MonthlyCharges', title_name='月费用与是否流失关系')

    8. 数值型属性相关性

    从相关性矩阵图可以看出,用户的往来期间和总费用呈现高度相关,往来期间越长,则总费用越高。月消费和总消费呈现显著相关。

    plt.figure(figsize=(15, 10))  
    sns.heatmap(df.corr(), linewidths=0.1, cmap='tab20c_r', annot=True)
    plt.title('数值型属性的相关性', fontdict={'fontsize': 'xx-large', 'fontweight':'heavy'}) 
    plt.xticks(fontsize=12)
    plt.yticks(fontsize=12)
    plt.show() 

    06、特征选择

    使用统计检定方式进行特征筛选。

    # 删除tenure
    df = df.drop('tenure', axis=1) 
    
    from feature_selection import Feature_select
    
    # 划分X和y
    X = df.drop(['customerID', 'Churn'], axis=1) 
    y = df['Churn']   
    
    fs = Feature_select(num_method='anova', cate_method='kf', pos_label='Yes')
    x_sel = fs.fit_transform(X, y)  
    2020 09:30:02 INFO attr select success!
    After select attr: ['DeviceProtection', 'MultipleLines', 'OnlineSecurity', 
                        'TechSupport', 'tenure_group', 'PaperlessBilling',
                        'InternetService', 'PaymentMethod', 'SeniorCitizen', 
                        'MonthlyCharges', 'Dependents', 'Partner', 'Contract', 
                        'StreamingTV', 'TotalCharges', 'StreamingMovies', 'OnlineBackup']

    经过特征筛选,gender和PhoneService字段被去掉。

    07、建模前处理

    在python中,为满足建模需要,一般需要对数据做以下处理:

    • 对于二分类变量,编码为0和1;
    • 对于多分类变量,进行one_hot编码;
    • 对于数值型变量,部分模型如KNN、神经网络、Logistic需要进行标准化处理。
    # 筛选变量
    select_features = x_sel.columns
    
    # 建模数据
    df_model = pd.concat([df['customerID'], df[select_features], df['Churn']], axis=1)
    
    Id_col = ['customerID']
    target_col = ['Churn']
    
    # 分类型
    cat_cols = df_model.nunique()[df_model.nunique() < 10].index.tolist() 
    # 二分类属性
    binary_cols = df_model.nunique()[df_model.nunique() == 2].index.tolist()
    # 多分类属性
    multi_cols = [i for i in cat_cols if i not in binary_cols] 
    
    # 数值型
    num_cols = [i for i in df_model.columns if i not in cat_cols + Id_col] 
    
    # 二分类-标签编码
    le = LabelEncoder()
    
    for i in binary_cols:
        df_model[i] = le.fit_transform(df_model[i]) 
    
    # 多分类-哑变量转换
    df_model = pd.get_dummies(data=df_model, columns=multi_cols) 
    df_model.head() 

    08、模型建立和评估

    首先使用分层抽样的方式将数据划分训练集和测试集。

    # 重新划分
    X = df_model.drop(['customerID', 'Churn'], axis=1) 
    y = df_model['Churn']  
    
    # 分层抽样
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y) 
    print(X_train.shape, X_test.shape, y_train.shape, y_test.shape) 
    
    #修正索引
    for i in [X_train, X_test, y_train, y_test]:
        i.index = range(i.shape[0]) 

     

    (5625, 31) (1407, 31) (5625,) (1407,)

     

    # 保存标准化训练和测试数据
    st = StandardScaler()
    num_scaled_train = pd.DataFrame(st.fit_transform(X_train[num_cols]), columns=num_cols)
    num_scaled_test = pd.DataFrame(st.transform(X_test[num_cols]), columns=num_cols) 
    
    X_train_sclaed = pd.concat([X_train.drop(num_cols, axis=1), num_scaled_train], axis=1)
    X_test_sclaed = pd.concat([X_test.drop(num_cols, axis=1), num_scaled_test], axis=1) 

    然后建立一系列基准模型并比较效果。

    假如我们关注roc指标,从模型表现效果来看,Naive Bayes效果最好。我们也可以对模型进行进一步优化,比如对决策树参数进行调优。

    parameters = {'splitter': ('best','random'),
                  'criterion': ("gini","entropy"),
                  "max_depth": [*range(3, 20)],
                 }
    
    clf = DecisionTreeClassifier(random_state=25)
    GS = GridSearchCV(clf, parameters, scoring='f1', cv=10)
    GS.fit(X_train, y_train)
    
    print(GS.best_params_) 
    
    print(GS.best_score_) 
    {'criterion': 'entropy', 'max_depth': 5, 'splitter': 'best'}
    0.585900839405024
    clf = GS.best_estimator_
    
    test_pred = clf.predict(X_test)
    print('测试集:\n', classification_report(y_test, test_pred)) 
    测试集:
                   precision    recall  f1-score   support
    
               0       0.86      0.86      0.86      1033
               1       0.61      0.61      0.61       374
    
        accuracy                           0.79      1407
       macro avg       0.73      0.73      0.73      1407
    weighted avg       0.79      0.79      0.79      1407

    将这棵树绘制出来。

    import graphviz
    dot_data = tree.export_graphviz(decision_tree=clf, max_depth=3,
                                     out_file=None, 
                                     feature_names=X_train.columns,
                                     class_names=['not_churn', 'churn'], 
                                     filled=True,
                                     rounded=True
                                    )
    graph = graphviz.Source(dot_data) 

    输出决策树属性重要性排序:

    imp = pd.DataFrame(zip(X_train.columns, clf.feature_importances_))
    imp.columns = ['feature', 'importances']
    imp = imp.sort_values('importances', ascending=False)
    imp = imp[imp['importances'] != 0]
    
    table  = ff.create_table(np.round(imp, 4))
    py.offline.iplot(table)  

    后续优化方向:

    • 数据:分类技术应用在目标类别分布越均匀的数据集时,其所建立之分类器通常会有比较好的分类效能。针对数据在目标字段上分布不平衡,可采用过采样和欠采样来处理类别不平衡问题;
    • 属性:进一步属性筛选方法和属性组合;
    • 算法:参数调优;调整预测门槛值来增加预测效能。

     

    展开全文
  • 作者:真达、Mika数据:真达【导读】今天教大家如何用Python写一个电信用户流失预测模型。公众号后台,回复关键字“电信”获取完整数据。之前我们用Python写了员工流失预测模型,...
  • 作者|飞谷云 编辑|丹顶鹤5号Python技术实战案例“流失率”是描述客户离开或停止支付产品或服务费率的业务术语。这在许多企业中是一个关键的数字,因为通常情况下,获取新客...
  • 作者:Carl Dawson编译:ronghuaiyang导读借用生存分析的方法来进行用户流失的预测,用到了Cox Proportional Hazards模型。客户流失率很难预测。在你...
  • 分类问题属于机器学习问题的类别,其中给定一组功能,任务是预测离散值。分类问题的一些常见示例是,预测肿瘤是否为癌症,或者学生是否可能...因此,我们的任务是根据各种客户特征预测客户流失。 $ pip ins...
  • python数据分析实例:客户流失预警模型 客户流失是电信行业最重要的服务方面之一。客户流失的广义说法是因为客户自己或运营商违反服务协议而终止客户服务的行为。 流失预测流程一共分为四个步骤,分别为(1)数据...
  • 利用python建立客户流失预警模型(下)——建立模型 前言 上一部分已经完成了对数据的整理与分析,接下来建立数据分析模型。 首先,建立分类模型来预测客户是否会流失。逻辑回归,决策树,随机森林,xgboost等可用于...
  • 业务部门希望数据部门能对流失用户做分析,找到流失用户的典型特征,例如:到底流失用户的哪些特征最显著,当客户在哪些特征的什么条件下比较容易发生流失行为,并送到业务部门。 分析: 1.这是关于特征提取的分析...
  • 客户流失是所有与消费者挂钩行业都会关注的点,因为发展一个新客户是需要一定成本的,一旦客户流失,成本浪费不说,挽回一个客户的成本更大。 所以,电信行业在竞争日益激烈当下,如何挽留更多用户成为一项关键业务...
  • 文章目录前言四、数据可视化呈现1、查看流失...这篇文章紧跟用python进行分析的用户流失预测实操,以电信行业为例的后续内容,聚焦可视化操作。 四、数据可视化呈现 1、查看流失客户占比 ##数据可视化分析 # #1,查看
  • 这次我们用python来对客户流失数据进行预测 相关完整代码与csv文件可以从我的GitHub地址获取 读取客户流失数据 import pandas df = pandas.read_csv('../data/customer_churn.csv', index_col=0, header = 0) df....
  • 在对客户流失与否的影响因素进行模型研究之前,首先对各解释变量与被解释变量进行两变量独立性分析,以初步判断影响流失的因素,进而建立客户流失预测模型 二、数据集 主要变量说明如下: #subscriberID="个人客户的...
  • 客户流失预测该项目包含美国马萨诸塞州零售超市的数据。 超市连锁店面临着很高的客户流失率,结果导致整体销售额下降,从而导致利润下降。 为了扭转顾客流失,商店希望向他们不会流失的顾客分发折扣券。 为了确定将...
  • 文章目录前言1.删除无用列2.对客户的职位、月费用和总费用进行去均值和方差缩放,对数据进行标准化3.使用箱线图查看数据是否存在异常值4.查看对象类型字段中存在的值5....gender 和 PhoneService 与流失率的相关性
  • 机器学习分类模型可预测银行的客户流失。 嗨,访客们! 我是数据科学的新手,但我正在通过使用在线可用数据集将所有学习实践付诸实践。 我已经使用Python生成了机器学习分类模型! 我仍在学习,随着我学到更多,我...
  • 此外,我们将使用流行的Python库(例如Tensorflow,Keras)和机器学习技术(例如Adam Optimizer)来训练ANN模型并预测客户流失率。 数据:客户数据存储在: 论文:ANN案例研究论文: 研究论文 代码:Artificial_...
  • 现在,该银行近来一直在经历快速的客户流失。 这意味着客户以异常高的速度离开银行,管理层希望找出问题所在,以及如何做才能留住客户。 我使用称为Gretl的软件工具完成了该项目的原型。 但是在这里,我将使用...
  • 电信用户流失预警分析(python)

    千次阅读 2020-05-02 23:34:48
    1用户流失预测分析 简单介绍下使用的基本场景是:业务部门希望数据部门能对流失用户做分析,找到流失用户的典型特征。 · 当客户在哪些特征的什么条件下比较容易发生流失行为 · 到底流失客户的哪些特性最显著 · ...
  • 本实战课程通过一个详细的动手实验帮助你使用IBM Watson Studio构建一个银行业中常见的预测客户流失的模型。通过动手实验,你将会掌握如何使用Python创建SK-Learn模型,以及如何使用可视化的画布模式训练模型。
  • 注意: 3X项目仅使用Python 3.X和Tableau 10.0及更高版本进行分析 PPT-包含业务问题和转换为DS问题 Tableau-EDA洞察 功能选择 各种分类模型 最终PPT-解释 报告 安装 $ pip install imblearn # For Smote 问题陈述 ...
  • 高效办公!Python 批量生成PDF文档Python 分析邓紫棋微博粉丝分布实战教程Python 银行信用卡客户流失预测(kaggle)Python 炫技操作:五种 Python 转义表...
  • 客户流失分析 失去一个老用户会带来巨大的损失,大概需要公司拉新10个新用户才能予以弥补。如何预测客户即将流失,让公司采取合适的挽回措施,是每个公司都要关注的重点问题。 目标 利用类神经网络构建用户流失...
  • KNN算法--python实现

    2019-08-13 17:19:37
    一叶舟鸣 KNN算法–python实现 邻近算法 或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思...行业应用: 客户流失预测、欺诈侦测等(更...

空空如也

空空如也

1 2 3 4
收藏数 64
精华内容 25
关键字:

python客户流失预测

python 订阅