精华内容
下载资源
问答
  • 电信客户流失
    2022-06-21 00:30:40

     Datawhale干货 

    作者:鱼佬,武汉大学硕士

    2022科大讯飞:电信客户流失预测挑战赛

    赛事地址(持续更新):

    https://challenge.xfyun.cn/topic/info?type=telecom-customer&ch=ds22-dw-zs01

    赛题介绍

    随着市场饱和度的上升,电信运营商的竞争也越来越激烈,电信运营商亟待解决减少用户流失,延长用户生命周期的问题。对于客户流失率而言,每增加5%,利润就可能随之降低25%-85%。因此,如何减少电信用户流失的分析与预测至关重要。

    鉴于此,运营商会经常设有客户服务部门,该部门的职能主要是做好客户流失分析,赢回高概率流失的客户,降低客户流失率。某电信机构的客户存在大量流失情况,导致该机构的用户量急速下降。面对如此头疼的问题,该机构将部分客户数据开放,诚邀大家帮助他们建立流失预测模型来预测可能流失的客户。

    赛题任务

    给定某电信机构实际业务中的相关客户信息,包含69个与客户相关的字段,其中“是否流失”字段表明客户会否会在观察日期后的两个月内流失。任务目标是通过训练集训练模型,来预测客户是否会流失,以此为依据开展工作,提高用户留存。

    赛题数据

    赛题数据由训练集和测试集组成,总数据量超过25w,包含69个特征字段。为了保证比赛的公平性,将会从中抽取15万条作为训练集,3万条作为测试集,同时会对部分字段信息进行脱敏。

    特征字段

    客户ID、地理区域、是否双频、是否翻新机、当前手机价格、手机网络功能、婚姻状况、家庭成人人数、信息库匹配、预计收入、信用卡指示器、当前设备使用天数、在职总月数、家庭中唯一订阅者的数量、家庭活跃用户数、....... 、过去六个月的平均每月使用分钟数、过去六个月的平均每月通话次数、过去六个月的平均月费用、是否流失

    评分标准

    赛题使用AUC作为评估指标,即:

    from sklearn import metrics
    
    auc = metrics.roc_auc_score(data['default_score_true'], data['default_score_pred'])

    赛题baseline

    导入模块

    import pandas as pd
    import os
    import gc
    import lightgbm as lgb
    import xgboost as xgb
    from catboost import CatBoostRegressor
    from sklearn.linear_model import SGDRegressor, LinearRegression, Ridge
    from sklearn.preprocessing import MinMaxScaler
    from gensim.models import Word2Vec
    import math
    import numpy as np
    from tqdm import tqdm
    from sklearn.model_selection import StratifiedKFold, KFold
    from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
    import matplotlib.pyplot as plt
    import time
    import warnings
    warnings.filterwarnings('ignore')

    数据预处理

    train = pd.read_csv('train.csv')
    test = pd.read_csv('test.csv')
    data = pd.concat([train, test], axis=0, ignore_index=True)

    训练数据/测试数据准备

    features = [f for f in data.columns if f not in ['是否流失','客户ID']]
    
    train = data[data['是否流失'].notnull()].reset_index(drop=True)
    test = data[data['是否流失'].isnull()].reset_index(drop=True)
    
    x_train = train[features]
    x_test = test[features]
    
    y_train = train['是否流失']

    构建模型

    def cv_model(clf, train_x, train_y, test_x, clf_name):
        folds = 5
        seed = 2022
        kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
    
        train = np.zeros(train_x.shape[0])
        test = np.zeros(test_x.shape[0])
    
        cv_scores = []
    
        for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
            print('************************************ {} ************************************'.format(str(i+1)))
            trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
    
            if clf_name == "lgb":
                train_matrix = clf.Dataset(trn_x, label=trn_y)
                valid_matrix = clf.Dataset(val_x, label=val_y)
    
                params = {
                    'boosting_type': 'gbdt',
                    'objective': 'binary',
                    'metric': 'auc',
                    'min_child_weight': 5,
                    'num_leaves': 2 ** 5,
                    'lambda_l2': 10,
                    'feature_fraction': 0.7,
                    'bagging_fraction': 0.7,
                    'bagging_freq': 10,
                    'learning_rate': 0.2,
                    'seed': 2022,
                    'n_jobs':-1
                }
    
                model = clf.train(params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix], 
                                  categorical_feature=[], verbose_eval=3000, early_stopping_rounds=200)
                val_pred = model.predict(val_x, num_iteration=model.best_iteration)
                test_pred = model.predict(test_x, num_iteration=model.best_iteration)
                
                print(list(sorted(zip(features, model.feature_importance("gain")), key=lambda x: x[1], reverse=True))[:20])
                    
            if clf_name == "xgb":
                train_matrix = clf.DMatrix(trn_x , label=trn_y)
                valid_matrix = clf.DMatrix(val_x , label=val_y)
                test_matrix = clf.DMatrix(test_x)
                
                params = {'booster': 'gbtree',
                          'objective': 'binary:logistic',
                          'eval_metric': 'auc',
                          'gamma': 1,
                          'min_child_weight': 1.5,
                          'max_depth': 5,
                          'lambda': 10,
                          'subsample': 0.7,
                          'colsample_bytree': 0.7,
                          'colsample_bylevel': 0.7,
                          'eta': 0.2,
                          'tree_method': 'exact',
                          'seed': 2020,
                          'nthread': 36,
                          "silent": True,
                          }
                
                watchlist = [(train_matrix, 'train'),(valid_matrix, 'eval')]
                
                model = clf.train(params, train_matrix, num_boost_round=50000, evals=watchlist, verbose_eval=3000, early_stopping_rounds=200)
                val_pred  = model.predict(valid_matrix, ntree_limit=model.best_ntree_limit)
                test_pred = model.predict(test_matrix , ntree_limit=model.best_ntree_limit)
                     
            if clf_name == "cat":
                params = {'learning_rate': 0.2, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type': 'Bernoulli',
                          'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False}
                
                model = clf(iterations=20000, **params)
                model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
                          cat_features=[], use_best_model=True, verbose=3000)
                
                val_pred  = model.predict(val_x)
                test_pred = model.predict(test_x)
                
            train[valid_index] = val_pred
            test = test_pred / kf.n_splits
            cv_scores.append(roc_auc_score(val_y, val_pred))
            
            print(cv_scores)
           
        print("%s_scotrainre_list:" % clf_name, cv_scores)
        print("%s_score_mean:" % clf_name, np.mean(cv_scores))
        print("%s_score_std:" % clf_name, np.std(cv_scores))
        return train, test
        
    def lgb_model(x_train, y_train, x_test):
        lgb_train, lgb_test = cv_model(lgb, x_train, y_train, x_test, "lgb")
        return lgb_train, lgb_test
    
    def xgb_model(x_train, y_train, x_test):
        xgb_train, xgb_test = cv_model(xgb, x_train, y_train, x_test, "xgb")
        return xgb_train, xgb_test
    
    def cat_model(x_train, y_train, x_test):
        cat_train, cat_test = cv_model(CatBoostRegressor, x_train, y_train, x_test, "cat") 
        return cat_train, cat_test
        
    lgb_train, lgb_test = lgb_model(x_train, y_train, x_test)

    提交结果

    test['是否流失'] = lgb_test
    test[['客户ID','是否流失']].to_csv('test_sub.csv', index=False)

    数据挖掘赛事交流群

    5eb753b7feff47ccdf43924d200be76b.png

    如果群满,关注Datawhale公众号,回复“数据挖掘”或“CV”或“NLP”可邀请加入各自战队群,除了经验交流,赛题也会在群内更新发布。

    一键三连,一起学习⬇️ 

    更多相关内容
  • 电信客户流失 重点客户保留计划 WA_Fn-UseC_-Telco-Customer-Churn.csv
  • 针对电信领域客户流失的问题,提出了改进聚类的客户流失预测模型。...通过实际电信客户数据集测试,与传统的预测算法比较,证明这种算法适合解决大数据集和不平衡数据,具有更高的精确度,能够取得较好的客户流失预测效果。
  • 特征字段:客户ID、地理区域、是否双频、是否翻新机、当前手机价格、手机网络功能、婚姻状况、家庭成人人数、信息库匹配、预计收入、信用卡指示器、当前设备使用天数、在职总月数、家庭中唯一订阅者的数量、家庭活跃...
  • 本文讨论了 2019 年第二季度电信行业的客户流失分析。 机器学习是数据挖掘的高级发展,用于从大量数据中提取特征。 论文讨论了监督机器学习模型。 通过支持向量机(SVM)分类步骤设计的监督模型,用于将流失客户和...
  • 数据挖掘视角下的电信客户流失预测研究.pdf
  • 基于分布式混合数据挖掘的电信客户流失分析.pdf
  • 针对电信客户流失数据集存在的数据维度过高及单一分类器预测效果较弱的问题,结合过滤式和封装式特征选择方法的优点及组合分类器的较高预测能力,提出了一种基于Fisher比率与预测风险准则的分步特征选择方法结合组合...
  • 基于深度学习的电信客户流失预测方法研究.pdf
  • 针对数据挖掘方法在电信客户流失预测中的局限性,提出将信息融合与数据挖掘相结合,分别从数据层、特征层、决策层构建客户流失预测模型。确定客户流失预测指标;根据客户样本在特征空间分布的差异性对客户进行划分,...
  • 许多公司经历了不同的技术,这些技术可以预测客户流失率并帮助设计有效的客户保留计划,因为获取新客户的成本远高于保留现有客户的成本。 在本文中,已使用三种机器学习算法通过两个基准数据集IBM Watson数据集来...
  • 电信客户流失分析

    千次阅读 2021-07-03 11:37:32
    分析目的 现如今,在电信行业蓬勃发展的同时,电信...因此,本文选取了Kaggle中一个现实世界里的电信公司的客户流失数据,用其来探究电信客户流失背后的主要原因,并建立客户流失预警模型,帮助电信公司为优化业务、

    1.分析目的

    现如今,在电信行业蓬勃发展的同时,电信市场也趋于饱和,获取一个新客户的难度要远远高于维系一个老客户的难度,而老客户的流失意味着收益的流失和市场占有率的下降。可以说,电信运营商的竞争就是针对客户资源的竞争。然而,客户流失从来都是无法避免的,客户流失的原因也不尽相同。若想做到客户流失前预防、流失后召回,就必须通过数据分析和建模来总结经验、预测未来。因此,本文选取了Kaggle中一个现实世界里的电信公司的客户流失数据,用其来探究电信客户流失背后的主要原因,并建立客户流失预警模型,帮助电信公司为优化业务、提高客户留存、减少流失制定策略。无论是互联网行业还是传统行业,所有产品都需要关注用户流失,用户流失原因的拆解思路也都大致相同。因此,本文的探究思路是比较具有现实的推广意义的。

    2.分析思路

    首先,将数据集中的标签属性划分为不同的维度。在每一个维度下,通过数据处理和数据可视化的手段,逐个探究各个因素与客户流失率之间是否具备相关性。

    其次,处理数据并建模。通过手工筛选方式对数据做特征工程处理,然后选取逻辑回归模型、随机森林模型建立电信客户流失预警机制。

    最后,结合以上分析结果,分别从业务角度和用户角度为电信公司加强客户黏性、减少客户流失提出一些可供落地的建议。

    3.探索数据集

    3.1去除重复值

    每条数据记录代表一位客户,使用字段customerID对数据进行去重

    #保证数据完整性先备份数据
    data_cleaner = data.copy()
    
    #重复值处理
    data_cleaner = data_cleaner.drop_duplicates(['customerID'])
    print('去重前的数据形状为:{},去重后的数据形状为{}'.format(data.shape,data_cleaner.shape))
    
    去重前的数据形状为:(7043, 21),去重后的数据形状为(7043, 21)
    

    可见在本数据集中,没有重复值

    3.2变量类型转换

    TotalCharges为总计缴费,应转变为数值变量

    TotalCharges存在11个空字段,无法直接转化为浮点数类型,将空字段填充为np.nan进行处理

    data_cleaner['TotalCharges'] = data_cleaner['TotalCharges'].replace(' ',np.nan).astype(float)
    

    3.3缺失值处理

    TotalCharges 字段是客户的总计缴费,总计缴费=留存月数(tenure)×月度缴费(MonthlyCharges)

    data_cleaner['TotalCharges'].fillna(data_cleaner['tenure']*data_cleaner['MonthlyCharges']
                                        ,inplace=True)
    

    3.4异常值处理

    本数据集的数据主要有两种类型,一种是以str类型为主的离散型数据,二是以int,float为主的连续型数值数据。

    # 离散型数据
    for i in data_cleaner.select_dtypes(include='object'):
        print('\n','-'*20,i,'的值有')
        print(data_cleaner[i].value_counts())
    

    观察离散数据的分布情况,没有明显的异常值,各值均正常。

    绘制连续型数值MonthlyChargesTotalCharges 的箱型图与分布图,观察是否有异常值

    月度收费年度收费

    由上图可知,字段MonthlyChargesTotalCharges不存在异常值情况。

    4.流失率的影响因素分析

    4.1客户个人特征属性

    客户的个人特征属性包括Gender(性别)、SeniorCitizen(是否为老年人)、Partner(是否有配偶)、Dependents(是否有子女)。

    4.1.1 性别维度

    总体样本中,女性与男性客户比例接近1:1,女性客户3488人,占比49.52%;男性客户3555人,占比50.48%

    男女人数比例男女流失人数占比
    性别gender_loss

    将客户以性别分群,从上述环形图可知,该电信业务的男性客户稍多一些,但男女占比基本为1:1;客户流失方面,男女比例也基本持平,男女客户流失情况大致相同

    男女流失率

    作图并计算流失率,男性客户流失率在26.2%,女性客户流失率在26.9%,二者相差无几。因此可以断定,客户的性别与其是否流失并无直接联系。

    4.1.2 年龄维度

    将客户以是否为老年人进行分群,从客户体量来说,客户群体偏向年轻化,老年客户只占到所有客户的16.21%,仅为1142人;非老年人占83.79%,有5901人。非老年人与老年人用户占比接近5.2:1。老年人是样本中的小群体,大部分客户是非老年人

    年龄维度

    再用流失率对两组客户进行细分,作图并计算流失率,发现老年客户的流失率在41.68%,而非老年客户的流失率在23.61%。可以认为,客户的年龄与其是否流失有直接联系,老年人群相对于非老年人群更容易流失。

    年龄维度流失占比年龄维度流失率
    老年人-流失

    探究造成老年客户高流失率的原因。根据现实经验,老年人比较容易在电子产品上出现使用困难,尤其是在使用网络服务的时候。因此提取出老年客户的主要业务开通情况数据(包括电话业务、互联网业务以及技术支持服务)

    Phone Service服务中的老年人订购情况流失率
    电话业务老年人比例电话业务中的老年人流失
    Internet Service服务中的老年人订购情况流失率
    电信服务老年人电信老年人流失

    由上图可知,90%以上的老年客户都开通了电话业务和互联网业务,其中互联网业务的覆盖率更是达到了95%以上。

    从电话业务上看,无论是否开通此项业务,老年客户的流失率都是比较高的,均达到了40%+,猜测可能是老年客户的需求还没有被完全满足,具体是什么需求可以通过问卷、走访等形式进行调研。此外也不排除是由于死亡等不可抗力因素导致的流失。

    然而,开通了互联网服务的老年客户的流失率却相当高,达到了43.21%,几乎是未开通这项服务的老年客户的流失率的5倍。这个数据在一定程度上验证了上文的猜想,老年客户确实存在上网困难的问题,这最终导致了他们的流失。另一方面,也可能是互联网业务本身存在一定的问题,比如网络不稳定或者价格与服务质量不相匹配等等。在后文会对此展开讨论。

    对于使用上存在的问题,可以通过开通技术支持服务这项附加服务来解决。观察老年客户开通技术支持服务的情况。

    Tech Support服务中的老年人订购情况流失率
    技术支持人数技术支持流失

    大多数老年客户都开通了技术支持服务。通过计算发现,没有开通技术支持服务的老年客户的流失率在48.19%,而开通了技术支持服务的老年客户的流失率仅在19.62%。可以认为,开通技术支持服务能够有效缓解老年客户的流失情况。

    4.1.3 亲属维度

    DependentsDep_lossDep_loss_rate
    有无子女image-20210703000333046有无子女流失率
    PartnerPar_lossPar_loss_rate
    已婚未婚已婚未婚流失人数已婚未婚流失率

    从图上可以发现,无配偶、无子女客户的流失率更高。

    根据现实经验,有配偶或子女的人群在日常生活中使用手机的时长更长,他们更倾向于长期固定使用一个电话号码,而不是频繁更换移动运营商,因此他们流失率也相对较低。

    将是否有配偶和是否有子女两列合并为是否有亲属列(有配偶或有子女),画出是否有亲属与在网时长、月租费、总费用之间的箱线图。

    xiangxiangtuqinshu

    上图呈现出的结果与经验相符,有亲属客户的在网时长集中在20-60个月之间,而大多数无亲属客户的在网时长不到40个月。两类客户的月租费分布比较相似,有亲属客户的月租费整体相对高一些。然而总费用就呈现出显著的差异。有亲属客户的总费用集中在800-5000元之间,而无亲属客户的总费用集中在200-2400元之间。

    从上图还可以观察到,一些客户虽然无配偶或子女,但他们已经成长为超级忠诚客户,在网时长、月租费和总费用都相当高。此外,上图也证明了,在网时长和总费用之间呈现出显著的正相关关系。

    4.1.4 小结

    • 性别与是否流失无关;年龄和亲属关系与是否流失有关。
    • 老年客户的流失率更高,造成这种高流失率的原因可能是老年客户存在上网困难问题,而开通技术支持服务可以在一定程度上减少流失,也可能是互联网业务本身存在一定问题,更有可能是两者的共同作用
    • 有亲属(即有配偶或子女)的客户流失率较低,相比无亲属的客户,他们的平均在网时长更长,月租费和总费用都要更高

    4.2电信业务服务属性

    包括PhoneService(电话业务)、InternetService(互联网业务)、MultipleLines(多线业务)、OnlineSecurity(在线安全业务)、OnlineBackup(在线备份业务)、DeviceProtection(设备保护业务)、TechSupport(技术支持业务)、StreamingTV(网络电视)、StreamingMovies(网络电影)

    4.2.1电话业务

    PhoneServicePh_lossPh_loss_rate
    image-20210703103202562image-20210703103139194image-20210703102958185

    90%以上的客户都开通了电话业务。开通了电话业务的客户流失率为26.71%,未开通电话业务的客户流失率为24.93%,二者差异不大。这说明从业务角度看,电话服务并不是导致流失的原因之一

    4.2.2互联网业务

    InternetServiceIn_lossIn_loss_rate
    image-20210703105256661image-20210703105244326image-20210703105203063

    约有78%的客户开通了互联网服务,其中使用Fiber optic的客户占比较高,但其流失率也相当高,超过40%,差不多是DSL客户流失率的两倍。

    根据现实经验,Fiber optic属于光纤,网速快且稳定,使用体验比DSL更好。 然而它却带来了相当惊人的客户流失率,因此可以断定Fiber optic存在一定的问题,必须尽快改进以提升这项业务的质量。

    image-20210703105502762

    画出DSL和Fiber optic与月租费之间的箱型图,不难看出,开通了Fiber optic的客户的月租费几乎是开通DSL客户的月租费的两倍。很明显,高费用的Fiber optic并没有给客户带来相匹配的使用体验,优化该项业务迫在眉睫。

    4.2.3附加业务

    Multiple Linesloss_numberloss_rate
    image-20210703105821848image-20210703105945514image-20210703110035518
    OnlineSecurityloss_numberloss_rate
    image-20210703110240748image-20210703110427613image-20210703110438667
    OnlineBackuploss_numberloss_rate
    image-20210703110630699image-20210703110614660image-20210703110543301
    DeviceProtectionloss_numberloss_rate
    image-20210703110748678image-20210703110803975image-20210703110820604
    TechSupportloss_numberloss_rate
    image-20210703110951186image-20210703110921666image-20210703110936880
    StreamingTVloss_numberloss_rate
    image-20210703111235602image-20210703111323654image-20210703111258261
    StreamingMoviesloss_numberloss_rate
    image-20210703111538868image-20210703111503800image-20210703111522810

    从环形图来看,基本上每一项附加业务的开通客户数都在总体的30%左右,多线业务的开通人数比较多,在40%左右。而开通了在线安全、在线备份、设备保护和技术支持这四项附加业务的客户的流失率都比较低。

    其中,未开通在线安全服务或技术支持服务的客户的流失率大概是已开通客户的流失率的两倍,说明这两项附加业务的开通确实可以显著地减少客户流失。

    此外,涉及互联网服务支持的网络电视、电影业务的流失率相对较高,又一次印证了公司的基础互联网服务的确存在着比较大的问题。

    4.2.4小结

    • 从公司的两项主要业务来看,目前基础的互联网服务存在着比较大的问题。这之中使用Fiber optic的客户最多,但其带来的流失率也高得惊人。这可能是因为高额月费并没有给客户带来高质量的体验,因此优化业务迫在眉睫。依托互联网服务的网络电视和网络电影业务上的高流失率也证明了这一点
    • 虽然公司的基础网络业务确实有一定问题,但是通过多项附加业务的补充,该问题带来的流失率可以被有效降低。其中,应当将在线安全服务和技术支持服务作为推荐给客户的附加业务的优先级

    4.3客户消费属性

    包括PaperlessBilling(账单形式)、PaymentMethod(支付方式)、Contract(合同签订方式)、tenure(在网时长)、MonthlyCharges(月租费)、TotalCharges(总费用)。

    4.3.1账单形式

    PaperlessBillingloss_numberloss_rate
    image-20210703152924939image-20210703153014023image-20210703152954208

    PaperlessBilling中,yes代表电子计费,No代表纸质化计费。从上图中可以得知,使用电子账单服务的客户流失率更高。这可能是因为,电子账单比较容易被忽视,带给客户的体验不佳

    4.3.2支付方式

    PaymentMethodloss_numberloss_rate
    image-20210703154101859image-20210703154139536image-20210703154118664

    使用电子支票支付的客户的流失率要远远高于其他方式,这可能是因为客户更习惯于传统的支票、银行卡支付方式,对电子支付的接纳度不高。

    对比之下,使用银行或信用卡自动转账的客户的流失率最低,推测是因为无需人工操作,对于客户来说更加便捷,自动支付带来的消费惯性也会使得客户不会轻易更换运营商。因此应当引导客户采用其他三种方式进行支付,尤其是自动转账方式

    4.3.3合同签订方式

    Contractloss_numberloss_rate
    image-20210703154453324image-20210703154535157image-20210703154514469

    从环形图上可以看出,目前大多数客户仍然采用按月签订的方式;通过柱形图可以明显看到,合同签订方式对客户的流失率有较大的影响,合同期限越长,客户的流失率越低,客户的黏性也越大。按月签订合同的客户流失率非常高,达到42.71%。 当合同期限为1年时,流失率陡降至11.27%,合同期限为2年时,流失率仅有2.83%

    image-20210703155007039

    画出合同签订期限与总费用之间的环形图。可以明显看到,签订长期合同(1年或2年)的用户贡献了60%以上的收入,也再一次表明,将月租客户逐渐发展为年租客户应当成为公司的一项重要发展策略

    4.3.4在网时长

    下载

    密度曲线表明,客户的流失率随着在网时长的增加而逐渐减少,在网时间越长,说明客户黏性越大,更不容易流失。此外,在网的第20个月是客户流失与否的分水岭。在20个月之前,尤其是前3个月,客户有着较高的流失率,因此应当在入网的前三个月尽可能让新客户感受到业务的价值所在。而在第20个月以后,客户的流失率越来越低,此时公司已经拥有了稳定的客户群。

    image-20210703162037965

    从以上箱型图也可以看出,在网时间越长的客户,流失的可能性越低。 大多数未流失客户的在网时长都在16个月以上。客户的在网时长超过30个月的,其流失的可能性则比较小

    还能观察到,有个别在网时间很长的客户也已经流失,这反映出公司对忠诚客户的关怀不足。

    这里将在网时长作为整个客户生命。根据描述性统计,所有客户的平均生命周期大约为32个月,大多数客户的生命周期集中在8-56个月之间。画出所有客户的生命周期直方图

    image-20210703162316946

    这是双峰趋势图。可以观察到,大量客户的生命周期集中在1-3个月。再一次证明,应当在前3个月尽量引导客户进行消费。此外,高质量的客户集中在67个月之后,他们属于忠诚用户。

    4.3.5月度与年度费用

    image-20210703162730120

    画出月租费的区间分布直方图。从图上可以直观地看到所有客户的月租费分布情况,以及每个费用区间的客户流失情况。显然,月租费在70-100元内的客户更容易流失。画出月租费在这一区间的客户流失率柱形图

    image-20210703163053498

    计算得到,月租费在70-80元的客户流失率为39.82%,80-90元的客户流失率为36.12%,90-100元的客户流失率为37.80%。对于这部分客户,可以通过调研形式询问流失原因,若是由于月费价格高昂,则可以通过优惠券或减免形式对这一区间的在网客户进行补贴,以降低流失率

    image-20210703163406996 image-20210703163259203

    同样画出总费用的区间分布直方图,可以看到,流失率随着总费用的增长而不断降低,低消费人群更容易流失,总费用在2000元以内的客户的流失率最高,达到31.98%。这再一次证明,引导客户提高消费额度、延长客户合同存续期是提高客户留存、减少流失的不二之选。

    4.3.6小结

    • 客户的消费行为背后隐藏着他们的消费偏好,通过数据发现,客户更习惯纸质的账单形式和自动转账的支付方式,而使用电子账单和电子支付的客户的流失率则比较高
    • 客户在网时间越长,总费用越高,流失率越低,客户黏性越大。月租费在70-100元间的客户、总费用低于2000元的客户更容易流失。易流失客户的生命周期通常为1-3个月,而生命周期达到67个月的则为高度忠诚用户,对这两种客户应当采取不同的留存和维系策略
    • 延长客户的合同期限,推动客户从月签转向年签,引导客户提高消费额度,是提高留存的重要策略

    5.流失预警模型构建

    5.1 特征工程

    根据上述分析,本文选取了以下17个与流失率较为相关的特征

    df = data.loc[:,['SeniorCitizen','Partner','Dependents','tenure','PaperlessBilling','Contract','PaymentMethod'
                     ,'InternetService','OnlineSecurity','OnlineBackup','DeviceProtection','StreamingTV','StreamingMovies'
                     ,'TechSupport','MonthlyCharges','TotalCharges','Churn']]
    

    创造新特征Families

    df['P_trans'] = df['Partner'].map({'Yes':1,'No':0})
    df['D_trans'] = df['Dependents'].map({'Yes':1,'No':0})
    df['F_trans'] = df['P_trans'] + df['D_trans']
    df['Families'] = df['F_trans'].apply(lambda x: 0 if x==0 else 1)
    

    对分类型变量进行0-1编码处理

    columns = ['PaperlessBilling','OnlineSecurity','OnlineBackup','DeviceProtection'
              ,'TechSupport','StreamingTV','StreamingMovies','']
    for i in columns:
        df[i] = df[i].apply(lambda x:1 if x=='Yes' else 0)
    

    对标签进行编码处理

    from sklearn.preprocessing import LabelEncoder
    df['Churn'] = LabelEncoder().fit_transform(df['Churn'])
    

    对连续性变量进行分箱处理

    from sklearn.preprocessing import OrdinalEncoder
    for i in ['tenure','MonthlyCharges','TotalCharges']:
        df[i] = pd.cut(df[i],bins=10,right=False)
        df[i] = df[i].astype(str)
        df[i] = OrdinalEncoder().fit_transform(df[i][:,np.newaxis])
    

    删除相关虚拟变量,减少多重共线性

    df = df.drop(columns=['Contract_Month-to-month','PaymentMethod_Mailed check','InternetService_No','Partner'
    		    ,'Dependents','P_trans','D_trans','F_trans'])
    

    5.2 模型构建

    划分测试集与训练集

    x = df.drop(columns=['Churn'])
    y = df['Churn']
    from sklearn.model_selection import train_test_split
    xtrain,xtest,ytrain,ytest = train_test_split(x,y,train_size=0.8,random_state=0)
    

    使用逻辑回归进行建模

    from sklearn.linear_model import LogisticRegression
    lr = LogisticRegression().fit(xtrain,ytrain)
    y_prod = lr.predict_proba(xtest)[:,1]
    y_predict = lr.predict(xtest)
    

    查看score与auc值

    from sklearn.metrics import roc_curve,auc,accuracy_score
    fpr_lr,tpr_lr,threshold_lr = roc_curve(ytest,y_prod)
    auc_lr = auc(fpr_lr,tpr_lr)
    score_lr = accuracy_score(ytest,y_predict)
    print('准确率为{0},AUC值为{1}'.format(score_lr,auc_lr))
    
    准确率为0.7856635911994322,AUC值为0.8160918640103579
    

    通过建立流失预警模型,可以提前预判哪些客户存在流失风险,并及时采取措施对这些客户进行挽留。相比流失后召回,流失预警的成本低、召回难度也相对小。

    6.结论与建议

    1.根据客户个人属性分群制定策略
    从数据来看,老年客户很可能因为在手机服务使用上存在一定困难而流失,而技术支持这项附加业务则可以有效改善这种状况,因此可以向老年客户推出包含电话、互联网和技术支持业务的温暖套餐,承诺定期线上回访。此外,还可以开通老年客户电信服务专线,将“送温暖”服务理念深入人心,进一步拉近与老年客户群之间的距离,提高留存率,也有利于吸纳更多的老年客户。

    对于有亲属的客户,可以推出包含主要业务和所有附加业务的家庭年费套餐,给予一定的折扣优惠或者免费赠送三个月的影视会员,针对网络电视和电影服务,承诺在合同期限内提供免费的技术升级。也可以开发仅限配偶和子女之间的“亲人圈”业务,合同以年计,同城的亲属之间通话免费,以此作为“亲人圈”的卖点,吸引客户的亲属入网。

    针对地点变动比较频繁的客户,推出“全国通”电话和流量套餐,力求减少这种不必要的流失。

    2.设立客户忠诚计划,引入积分和会员体系
    引入积分制度,建立积分商城。客户的任意电信消费都可以等额地兑换成积分,积累到地一定额度即可在积分商城内兑换虚拟或实物礼品,商城内也可以设置轮盘抽奖活动,增加趣味性、积分每年年底清零,公司也要定期向客户发送短信提醒其完成积分兑换。

    建立会员体系,进行差异化的运营和管理。客户连续使用本公司电信服务达到三个月即自动入会,成为普通会员。再根据总消费金额向上划分会员等级,例如铂金、黄金、钻石会员等等。同时要为不同等级的会员,配置不同层次的权益。比如专属客服、积分翻倍、赠送节日礼包、纪念品等等。

    3.吸纳新客户,维系老客户,召回流失客户

    • 新客策略
      根据图表分析,新客户在入网的前三个月最容易流失,而在短时间内留存客户的最有效手段就是让客户迅速感受并认同业务的价值。通过分析发现,电信附加业务可以在一定程度上弥补基础业务的不足。
      因此,对于新客户,除了入网即赠送消费券、给予折扣等常规促销手段,还可以开展附加业务首月免费体验活动。让新客户通过体验增值服务,感受到本公司电信业务的便捷、快捷与安全性。另外可以规定,新客户签订的合同期限越长,其获得的折扣和福利也会越多。因为比起说服老用户转变月签方式,培养新的长约客户显然来得更加高效。
      此外,开展老带新活动也是吸纳新客户的重要手段。经老客户推荐来的新客可以享受折上折,新客入网后老客即可获得积分,且拉新人数越多,积分越多。
    • 老客策略
      根据前文分析,在网时长超过20个月即可视为忠诚客户。对于忠诚客户,要定期维系和关怀,比如向客户发送生日短信、节日礼等等,忠诚客户的个性化需求也要满足。此外,也要定期对忠诚客户进行线上和线下回访,开展客户满意度调研,在优化当前业务的同时,发掘新的业务需求点。
    • 流失客户策略
      对于普通流失客户,可以通过批量发送短信或邮件的方式召回,内容中注明重新入网可享受的折扣优惠以及优惠的时限。
      对于高价值流失客户,可以通过电话回访、赠送伴手礼的方式进行召回。此外,也可以通过举办优惠活动大面积召回流失客户。

    4.提高业务服务质量,精准定位客户需求
    从客户的消费行为数据来看,多数客户尚未习惯使用电子支付,他们更倾向于传统的纸质账单形式和支票、银行卡的付款方式。因此要大力推荐并引导新老客户使用这两种方式,进而延长客户的存续期。

    从电信业务数据来看,目前互联网业务尤其是光纤方面仍然存在缺陷,对其进行技术改造和升级迫在眉睫。

    此外,开通在线安全、在线备份等附加业务对流失率的有效降低反映出客户对安全、便捷的业务的日益增长的需求。但在目前来说,开通附加业务的客户数量还比较少,因此要向客户大力宣传推广这些业务,唤醒他们的需求意识。

    展开全文
  • R语言_电信客户流失数据分析

    千次阅读 2021-12-15 14:36:35
    近年来,各行各业往往都会不可避免地面临用户流失的问题。研究表明,发展新用户所花费的宣传...因此,需要对电信客户进行流失分析与预测,发掘客户流失的原因,进而改善自身业务,提高用户的满意度,延长用户生命周期。

    1 引言

    近年来,各行各业往往都会不可避免地面临用户流失的问题。研究表明,发展新用户所花费的宣传、促销等成本显然高于维持老用户的成本,因此,做好"客户流失预警"可以有效降低营销成本,做到精准营销。
    如今,随着运营商的竞争不断加剧,电信运营商亟需提高用户留存率、增加用户黏性,减少客户流失。因此,需要对电信客户进行流失分析与预测,发掘客户流失的原因,进而改善自身业务,提高用户的满意度,延长用户生命周期。

    2 数据来源与数据概况

    2.1 数据来源

    数据来源kaggle电信用户流失数据集。
    https://www.kaggle.com/blastchar/telco-customer-churn

    2.2 数据概况

    电信客户流失数据集描述了电信用户是否流失以及其相关信息,共包含7043条记录,21个字段。 读入数据集后,了解数据集的基本信息。

    > telco.data <- read.csv("WA_Fn-UseC_-Telco-Customer-Churn.csv")
    > # 展示数据集的前六行数据
    > head(telco.data)
    
      customerID gender SeniorCitizen Partner Dependents tenure PhoneService
    1 7590-VHVEG Female             0     Yes         No      1           No
    2 5575-GNVDE   Male             0      No         No     34          Yes
    3 3668-QPYBK   Male             0      No         No      2          Yes
    4 7795-CFOCW   Male             0      No         No     45           No
    5 9237-HQITU Female             0      No         No      2          Yes
    6 9305-CDSKC Female             0      No         No      8          Yes
         MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection
    1 No phone service             DSL             No          Yes               No
    2               No             DSL            Yes           No              Yes
    3               No             DSL            Yes          Yes               No
    4 No phone service             DSL            Yes           No              Yes
    5               No     Fiber optic             No           No               No
    6              Yes     Fiber optic             No           No              Yes
      TechSupport StreamingTV StreamingMovies       Contract PaperlessBilling
    1          No          No              No Month-to-month              Yes
    2          No          No              No       One year               No
    3          No          No              No Month-to-month              Yes
    4         Yes          No              No       One year               No
    5          No          No              No Month-to-month              Yes
    6          No         Yes             Yes Month-to-month              Yes
                  PaymentMethod MonthlyCharges TotalCharges Churn
    1          Electronic check          29.85        29.85    No
    2              Mailed check          56.95      1889.50    No
    3              Mailed check          53.85       108.15   Yes
    4 Bank transfer (automatic)          42.30      1840.75    No
    5          Electronic check          70.70       151.65   Yes
    6          Electronic check          99.65       820.50   Yes
    
    > # 数据集的维度
    > dim(telco.data)
    
    [1] 7043   21
    

    每个字段的介绍如下表所示:

    字段名字段含义字段内容
    customerID客户ID
    gender性别Female & Male
    SeniorCitizen老年用户1表示是,0表示不是
    Partner伴侣用户Yes or No
    Dependents亲属用户Yes or No
    tenure在网时长0-72月
    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月租费18.85-118.35
    TotalCharges累计付费18.85-8684.8
    Churn该用户是否流失Yes or No

    3 研究问题

    1. 分析用户特征与流失的关系
    2. 流失客户普遍具有哪些特征?
    3. 尝试找到合适的模型预测流失客户。
    4. 针对性给出增加用户黏性、降低客户流失率的建议。

    4 数据预处理

    查看数据集中每个变量的类型。

    > str(telco.data)
    
    'data.frame':	7043 obs. of  21 variables:
     $ customerID      : chr  "7590-VHVEG" "5575-GNVDE" "3668-QPYBK" "7795-CFOCW" ...
     $ gender          : chr  "Female" "Male" "Male" "Male" ...
     $ SeniorCitizen   : int  0 0 0 0 0 0 0 0 0 0 ...
     $ Partner         : chr  "Yes" "No" "No" "No" ...
     $ Dependents      : chr  "No" "No" "No" "No" ...
     $ tenure          : int  1 34 2 45 2 8 22 10 28 62 ...
     $ PhoneService    : chr  "No" "Yes" "Yes" "No" ...
     $ MultipleLines   : chr  "No phone service" "No" "No" "No phone service" ...
     $ InternetService : chr  "DSL" "DSL" "DSL" "DSL" ...
     $ OnlineSecurity  : chr  "No" "Yes" "Yes" "Yes" ...
     $ OnlineBackup    : chr  "Yes" "No" "Yes" "No" ...
     $ DeviceProtection: chr  "No" "Yes" "No" "Yes" ...
     $ TechSupport     : chr  "No" "No" "No" "Yes" ...
     $ StreamingTV     : chr  "No" "No" "No" "No" ...
     $ StreamingMovies : chr  "No" "No" "No" "No" ...
     $ Contract        : chr  "Month-to-month" "One year" "Month-to-month" "One year" ...
     $ PaperlessBilling: chr  "Yes" "No" "Yes" "No" ...
     $ PaymentMethod   : chr  "Electronic check" "Mailed check" "Mailed check" "Bank transfer (automatic)" ...
     $ MonthlyCharges  : num  29.9 57 53.9 42.3 70.7 ...
     $ TotalCharges    : num  29.9 1889.5 108.2 1840.8 151.7 ...
     $ Churn           : chr  "No" "No" "Yes" "No" ...
    

    4.1 因子变量处理

    需要将该数据集中的部分变量转化为因子类型。

    > telco.data <- within(telco.data,{
    +   SeniorCitizen <- factor(SeniorCitizen, levels = c(0,1), labels = c("No", "Yes"))
    +   Partner <- factor(Partner)
    +   Dependents <- factor(Dependents)
    +                      })
    > Factors <- c("gender", "PhoneService", "MultipleLines", "InternetService", "OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies", "Contract", "PaperlessBilling", "PaymentMethod", "Churn")
    > telco.data[Factors] <- lapply(telco.data[Factors],factor)
    

    4.2 缺失值处理

    从图中可以看出,TotalCharges列有11个缺失值,占比大约0.16%。

    > colSums(is.na(telco.data))
    
          customerID           gender    SeniorCitizen          Partner 
                   0                0                0                0 
          Dependents           tenure     PhoneService    MultipleLines 
                   0                0                0                0 
     InternetService   OnlineSecurity     OnlineBackup DeviceProtection 
                   0                0                0                0 
         TechSupport      StreamingTV  StreamingMovies         Contract 
                   0                0                0                0 
    PaperlessBilling    PaymentMethod   MonthlyCharges     TotalCharges 
                   0                0                0               11 
               Churn 
                   0 
    
    > library(VIM)
    > par(cex = 0.72, font.axis = 3)
    > VIM::aggr(telco.data, prop = TRUE, numbers = TRUE)
    

    在这里插入图片描述

    处理缺失值数据的一种方法是插补均值、中位数或者众数。
    从直方图可知,TotalCharges数据呈偏态分布。根据正态分布选均值、中位数填充,偏态分布选中位数填充的原则,选择用TotalCharges列的中位数去填充这11个缺失值。

    > hist(telco.data$TotalCharges, breaks = 50, prob = TRUE, 
    +      main = "Histogram Of TotalCharges")
    

    在这里插入图片描述

    > library(Hmisc)
    > # 插补中位数
    > telco.data$TotalCharges <- as.numeric(Hmisc::impute(telco.data$TotalCharges, median))
    

    4.3 简化分类变量的属性值

    OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport、StreamingTV、StreamingMovies这六个变量的属性值有Yes、No、No internet serive 三种。

    通过分析这六个变量和Churn生成的二维列联表,不难发现"No internetserive"出现 的频数是一致的,可以认为该属性值不影响客户流失率,所以简化属性值,将其并入"No"这一属性值。

    > for(i in 10:15)
    + {
    +   print(xtabs(~ Churn + get(names(telco.data)[i]), data = telco.data))
    + }
    

    结果如下:

         get(names(telco.data)[i])
    Churn   No No internet service  Yes
      No  2037                1413 1724
      Yes 1461                 113  295
         get(names(telco.data)[i])
    Churn   No No internet service  Yes
      No  1855                1413 1906
      Yes 1233                 113  523
         get(names(telco.data)[i])
    Churn   No No internet service  Yes
      No  1884                1413 1877
      Yes 1211                 113  545
         get(names(telco.data)[i])
    Churn   No No internet service  Yes
      No  2027                1413 1734
      Yes 1446                 113  310
         get(names(telco.data)[i])
    Churn   No No internet service  Yes
      No  1868                1413 1893
      Yes  942                 113  814
         get(names(telco.data)[i])
    Churn   No No internet service  Yes
      No  1847                1413 1914
      Yes  938                 113  818
    
    > # 将“No internetserive”并入“No”这一属性值
    > levels(telco.data$OnlineSecurity)[2] <- "No"
    > levels(telco.data$OnlineBackup)[2] <- "No"
    > levels(telco.data$DeviceProtection)[2] <- "No"
    > levels(telco.data$TechSupport)[2] <- "No"
    > levels(telco.data$StreamingTV)[2] <- "No"
    > levels(telco.data$StreamingMovies)[2] <- "No"
    

    4.4 处理"量纲差异大"

    目前属于这类特征的变量有:MonthlyCharges和TotalCharges。我打算采用连续特征离散化的处理方式。原因是离散化后的特征对异常数据有更强的鲁棒性,降低过拟合的风险,模型会更稳定,预测的效果也会更好。

    数据离散化也称为分箱操作,其方法分为有监督分箱(卡方分箱、最小熵法分箱)和无监督分箱(等频分箱、等距分箱)。 本次为采用无监督分箱中的等频分箱进行操作。

    > library(Hmisc)
    > describe(telco.data[c("MonthlyCharges","TotalCharges")])
    
    telco.data[c("MonthlyCharges", "TotalCharges")] 
    
     2  Variables      7043  Observations
    -----------------------------------------------------------------------------------
    MonthlyCharges 
           n  missing distinct     Info     Mean      Gmd      .05      .10      .25 
        7043        0     1585        1    64.76    34.39    19.65    20.05    35.50 
         .50      .75      .90      .95 
       70.35    89.85   102.60   107.40 
    
    lowest :  18.25  18.40  18.55  18.70  18.75, highest: 118.20 118.35 118.60 118.65 118.75
    -----------------------------------------------------------------------------------
    TotalCharges 
           n  missing distinct     Info     Mean      Gmd      .05      .10      .25 
        7043        0     6531        1     2282     2447    49.65    84.61   402.23 
         .50      .75      .90      .95 
     1397.47  3786.60  5973.69  6921.02 
    
    lowest :   18.80   18.85   18.90   19.00   19.05, highest: 8564.75 8594.40 8670.10 8672.45 8684.80
    -----------------------------------------------------------------------------------
    
    > #根据描述性统计量将变量按0.25,0.5,0.75分位数分成4份
    > c_u_t <- function(x, n = 1) {
    +   result <- quantile(x, probs = seq(0, 1, 1/n))
    +   result[1] <- result[1]-0.001
    +   return(result)
    + }
    > telco.data <- transform(telco.data,
    +                         MonthlyCharges_c = cut(telco.data$MonthlyCharges,
    +                         breaks =c_u_t(telco.data$MonthlyCharges, n=4),
    +                         labels = c(1,2,3,4)),
    +                         TotalCharges_c = cut(telco.data$TotalCharges,
    +                         breaks = c_u_t(telco.data$TotalCharges, n=4),
    +                         labels = c(1,2,3,4)))
    > telco.data <- within(telco.data, {
    +   MonthlyCharges_c <- relevel(MonthlyCharges_c, ref = 1)
    +   TotalCharges_c <- relevel(TotalCharges_c, ref = 1)
    + })
    

    5 探索性数据分析

    查看流失客户的数量和占比,由图可知,客户流失率约为26.54%。

    > table(telco.data$Churn)
    
    
      No  Yes 
    5174 1869 
    
    > library(ggplot2)
    > options(digits=4)
    > ggplot(telco.data, aes(x = "" ,fill = Churn))+
    +   geom_bar(stat = "count", width = 0.5, position = 'stack')+
    +   coord_polar(theta = "y", start=0)+
    +   geom_text(stat="count", 
    +             aes(label = scales::percent(..count../nrow(telco.data), 0.01)), 
    +             size=4, position=position_stack(vjust = 0.5)) +
    +   theme(
    +     panel.background = element_blank(),
    +     axis.title = element_blank(),
    +     axis.text = element_blank(),
    +     axis.ticks = element_blank()
    +   )
    

    在这里插入图片描述

    5.1 用户特征与流失的关系

    根据数据集描述,将变量划分为用户属性、服务属性、合同属性,并从这三个维度进行分析。

    用户属性

    • 人口统计指标:‘gender’,‘SeniorCitizen’,‘Partner’,‘Dependents’
    • 用户活跃度:‘tenure’

    服务属性

    • 手机服务:‘PhoneService’, ‘MultipleLines’
    • 网络服务:‘InternetService’,‘OnlineSecurity’,‘OnlineBackup’, ‘DeviceProtection’,‘TechSupport’, ‘StreamingTV’,‘StreamingMovies’

    合同属性
    ‘MonthlyCharges’,‘TotalCharges’, ‘Contract’, ‘PaperlessBilling’, ‘PaymentMethod’

    5.1.1 用户属性分析

    用户属性包括:‘gender’,‘SeniorCitizen’,‘Partner’,‘Dependents’,‘tenure’。

    从图中可以得出以下结论:

    1. 性别对客户流失并无显著影响;
    2. 老年群体相较于其他群体,客户流失率较高;
    3. 无伴侣的客户流失率高于有伴侣的客户流失率;
    4. 无亲属的客户流失率高于有亲属的客户流失率。

    说明增加关联客户数量的产品有利于增加客户的忠诚度,减小客户流失。

    > library(cowplot)
    > p1 <- ggplot(telco.data, aes(x = gender, fill = Churn)) + 
    +   geom_bar(stat = 'count',position = "dodge")
    > p2 <- ggplot(telco.data, aes(x = SeniorCitizen, fill = Churn)) + 
    +   geom_bar(stat = 'count',position = "dodge")
    > p3 <- ggplot(telco.data, aes(x = Partner, fill = Churn)) + 
    +   geom_bar(stat = 'count',position = "dodge")
    > p4 <- ggplot(telco.data, aes(x = Dependents, fill = Churn)) + 
    +   geom_bar(stat = 'count',position = "dodge")
    > cowplot::plot_grid(p1, p2, p3, p4, nrow = 2, labels = LETTERS[1:4])
    

    在这里插入图片描述

    从用户活跃度即在网时长’tenure’来看,流失客户的在网时间较短,平均为10个月,且呈左偏分布;在网时间越长,客户流失率越低。

    > ggplot(telco.data, aes(x = Churn, y = tenure)) + geom_boxplot(aes(fill = Churn))
    

    在这里插入图片描述

    > ggplot(telco.data, aes(x = tenure)) +  geom_bar(fill = "lightblue") + facet_grid(Churn ~ .)
    

    在这里插入图片描述

    5.1.2 服务属性分析

    服务属性
    手机服务:‘PhoneService’, ‘MultipleLines’
    网络服务:‘InternetService’,‘OnlineSecurity’,‘OnlineBackup’, ‘DeviceProtection’,‘TechSupport’, ‘StreamingTV’,‘StreamingMovies’

    > p <- apply(telco.data, 2, function(R){
    +   ggplot(telco.data) + aes(x = R, fill = Churn) + geom_bar(stat = 'count',position = "fill")
    + })
    > p5 <- p['PhoneService']$PhoneService + labs(x = "PhoneService") 
    > p6 <- p['MultipleLines']$MultipleLines + labs(x = "MultipleLines")
    > p7 <- p['InternetService']$InternetService + labs(x = "InternetService")
    > p8 <- p['OnlineSecurity']$OnlineSecurity + labs(x = "OnlineSecurity")
    > p9 <- p['OnlineBackup']$OnlineBackup + labs(x = "OnlineBackup")
    > p10 <- p['DeviceProtection']$DeviceProtection + labs(x = "DeviceProtection")
    > p11 <- p['TechSupport']$TechSupport + labs(x = "TechSupport")
    > p12 <- p['StreamingTV']$StreamingTV + labs(x = "StreamingTV")
    > p13 <- p['StreamingMovies']$StreamingMovies + labs(x = "StreamingMovies")
    > cowplot::plot_grid(p5, p6, p7, nrow = 2)
    

    在这里插入图片描述

    > cowplot::plot_grid(p8, p9, p10, p11, p12, p13, nrow = 2)
    

    在这里插入图片描述

    由图可知:

    1. PhoneService电话服务对客户流失率影响不大。
    2. 开通多线通话服务的客户相比其他两类客户,流失率较高,可能是因为多条通话渠道导致的费用升高而且功能过剩。
    3. 开通Fiber optic光纤服务的客户流失率远高于开通DSL数字网络的客户流失率,这说明光纤服务是导致开通网络服务的客户流失的主要原因,需要进一步调查客户对光纤服务的反馈。总体而言,开通网络服务的客户流失率偏高。
    4. 在技术性服务(OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport)中,开通的客户流失率均比整体流失率26.54%低,而未开通的则高出整体流失率不少。
    5. 在娱乐性服务(StreamingTV、StreamingMovies)中,开通的客户流失率都比未开通的高。

    5.1.3 合同属性分析

    合同属性包括:
    ‘MonthlyCharges’,‘TotalCharges’, ‘Contract’, ‘PaperlessBilling’, ‘PaymentMethod’。

    > p14 <- p['Contract']$Contract + labs(x = 'Contract') 
    > p15 <- p['PaperlessBilling']$'PaperlessBilling' + labs(x = 'PaperlessBilling')
    > p16 <- p['PaymentMethod']$PaymentMethod + labs(x = 'PaymentMethod')
    > cowplot::plot_grid(p14, p15, p16, nrow = 3)
    

    在这里插入图片描述

    由图可知:

    1. 在签订合同上,按月签约的客户流失率最高,并且签约时间越长,客户流失率越低。这说明,按月签约的客户对产品的粘性不高。
    2. 在是否开通电子账单上,选择电子账单的客户流失率高于选择纸账单的客户流失率。
    3. 在支付方式上,选择Electronic check支付方式的客户流失率最高,其他三种流失率 差别不大。可以进一步调查选择Electronic check支付方式的客户,了解流失原因。
    > ggplot(telco.data, aes(x = Churn, y = MonthlyCharges)) + geom_boxplot(aes(fill = Churn))
    

    在这里插入图片描述

    > p17 <- ggplot(telco.data, aes(x = MonthlyCharges, fill= Churn, alpha = 0.5)) +
    +   geom_density()
    > p18 <- ggplot(telco.data, aes(x = TotalCharges, fill= Churn, alpha = 0.5)) +
    +   geom_density()
    > cowplot::plot_grid(p17, p18, nrow = 2)
    

    在这里插入图片描述

    由图可知,在月租费方面,流失客户的月租费整体水平要高于非流失客户; 月租费金额大约在70-100元的客户流失率较高。

    5.2 流失客户普遍具有的特征

    从用户属性来看,老年群体、无伴侣、无亲属、在网时间小于10个月的客户流失率较高。
    从服务属性来看,开通多线通话服务、开通Fiber optic光纤服务、未开通技术性服 务(OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport)、开通娱乐性服务(StreamingTV、StreamingMovies)的客户流失率较高。
    从合同属性来看,按月签约、开通电子账单、选择Electronic check支付方式、月租 费70-100元的客户流失率较高。

    6 电信客户流失预测模型

    6.1 特征选择

    经过上文的分析,目前认为与客户流失率关联较小的变量有:gender、PhoneService,而customerID是随机数,不影响建模,故可以筛选掉。

    观察变量之间的相关性。
    首先对分类变量进行相关性分析,一般使用卡方检验。
    在分类变量和目标变量Churn的卡方检验中,gender的P值为0.49,PhoneService的p值为 0.35,其他分类变量的P值都远小于0.01,所以不能拒绝gender、PhoneService和Churn相互独立的原假设,应被筛选掉。

    > sapply(telco.data[c(-1, -6, -19, -20, -21)], function(x){
    +   ch <- chisq.test(x, telco.data$Churn, simulate.p.value = T)
    +   list(chi_v=ch$statistic, p=ch$p.value)
    + })
    
          gender SeniorCitizen Partner   Dependents PhoneService MultipleLines
    chi_v 0.5224 160.4         159.4     189.9      1.004        11.33        
    p     0.4748 0.0004998     0.0004998 0.0004998  0.3388       0.005997     
          InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport
    chi_v 732.3           206.5          47.65        30.83            191        
    p     0.0004998       0.0004998      0.0004998    0.0004998        0.0004998  
          StreamingTV StreamingMovies Contract  PaperlessBilling PaymentMethod
    chi_v 28.16       26.54           1185      259.2            648.1        
    p     0.0004998   0.0004998       0.0004998 0.0004998        0.0004998    
          MonthlyCharges_c TotalCharges_c
    chi_v 359.8            400.1         
    p     0.0004998        0.0004998     
    

    其次,观察连续变量之间的相关性。
    tenure和TotalCharges的相关性为0.83,是强相关,去掉TotalCharges保留tenure方便计算。

    > # 计算相关矩阵
    > telco.cor <- cor(telco.data[,c('tenure','MonthlyCharges','TotalCharges')]) 
    > round(telco.cor, digits = 2)
    
                   tenure MonthlyCharges TotalCharges
    tenure           1.00           0.25         0.83
    MonthlyCharges   0.25           1.00         0.65
    TotalCharges     0.83           0.65         1.00
    

    最终,customerID、gender、PhoneService和TotalCharges被筛出掉。

    > telco <- telco.data[,c(-1, -2, -7, -19, -20, -23)]
    > head(telco)
    
      SeniorCitizen Partner Dependents tenure    MultipleLines InternetService
    1            No     Yes         No      1 No phone service             DSL
    2            No      No         No     34               No             DSL
    3            No      No         No      2               No             DSL
    4            No      No         No     45 No phone service             DSL
    5            No      No         No      2               No     Fiber optic
    6            No      No         No      8              Yes     Fiber optic
      OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV
    1             No          Yes               No          No          No
    2            Yes           No              Yes          No          No
    3            Yes          Yes               No          No          No
    4            Yes           No              Yes         Yes          No
    5             No           No               No          No          No
    6             No           No              Yes          No         Yes
      StreamingMovies       Contract PaperlessBilling             PaymentMethod Churn
    1              No Month-to-month              Yes          Electronic check    No
    2              No       One year               No              Mailed check    No
    3              No Month-to-month              Yes              Mailed check   Yes
    4              No       One year               No Bank transfer (automatic)    No
    5              No Month-to-month              Yes          Electronic check   Yes
    6             Yes Month-to-month              Yes          Electronic check   Yes
      MonthlyCharges_c
    1                1
    2                2
    3                2
    4                2
    5                3
    6                4
    

    6.2 划分数据集

    数据集中,流失客户有1869个样本,未流失客户有5174个样本。
    按照7:3划分训练集和测试集,用于模型的训练和有效性的评估。

    > set.seed(123)
    > train <- sample(nrow(telco), 0.7*nrow(telco))
    > telco.train <- telco[train,]
    > telco.test <- telco[-train,]
    > table(telco.train$Churn)
    
      No  Yes 
    3577 1353 
    
    > table(telco.test$Churn)
    
      No  Yes 
    1597  516 
    

    6.3 构建分类器并进行模型评估

    采用决策树、随机森林、支持向量机、逻辑回归四个模型。
    观察模型的准确度、精确度、召回率、特异度、f1值,绘制ROC曲线图,计算AOC值。

    > # 定义分类器性能标准
    > library(pROC)
    > telco_prediction <- function(algorithm, prob, test = telco.test, n = 2){
    +   pred <- predict(algorithm, telco.test, type = "class")
    +   table <- table(telco.test$Churn, pred)
    +   if(!all(dim(table)==c(2,2)))
    +     stop("Must be a 2 x 2 table")
    +   tn = table[1,1]
    +   fp = table[1,2]
    +   fn = table[2,1]
    +   tp = table[2,2]
    +   accuracy = round((tn+tp)/(tn+fp+fn+tp),n)
    +   precision = round(tp/(tp+fp),n)
    +   sensitivity = round(tp/(tp+fn),n)
    +   specificity = round(tn/(tn+fp),n)
    +   f1_score = round((2*precision*sensitivity)/(precision+sensitivity),n)
    +   # 绘制ROC
    +   modelroc <- roc(telco.test$Churn, prob[,2]) 
    +   plot(modelroc, print.auc=TRUE, auc.polygon=TRUE,legacy.axes=TRUE,
    +        grid=c(0.1, 0.2), 
    +        grid.col=c("green", "red"), max.auc.polygon=TRUE,
    +        auc.polygon.col="skyblue", print.thres=TRUE)
    +   auc <- auc(modelroc)
    +   # 输出指标
    +   data.frame(accuracy, precision, sensitivity, specificity, f1_score, auc)
    + }
    

    6.3.1 决策树

    在建立决策树之后,可以打印决策树的复杂性参数cp,观察决策树的误差等数据。 cp是参数复杂度(complexity parameter)作为控制树规模的惩罚因子,cp越大, 树分裂规模(nsplit)越小。
    输出参数(rel error)指示了当前分类模型树与空树之间的平均偏差比值。
    xerror为交叉验证误差,xstd为交叉验证误差的标准差。

    可以看到,当cp为0.01的时候,交叉误差最小。
    而决策树剪枝的目的就是为了得到更小交叉误差(xerror)的树。

    > library(rpart)
    > set.seed(123)
    > dtree <- rpart(Churn~., data = telco.train, method = "class",
    +                parms = list(split="information"))
    > dtree$cptable
    
           CP nsplit rel error xerror    xstd
    1 0.06245      0    1.0000 1.0000 0.02316
    2 0.01000      3    0.7871 0.8086 0.02156
    
    > # 按3次分割对应的复杂度参数0.01剪枝
    > dtree.pruned <- prune(dtree, cp=0.01)
    > # 绘制决策树
    > library(partykit)
    > plot(as.party(dtree.pruned), main = "Decision Tree")
    

    在这里插入图片描述

    > dtree.prob <- predict(dtree.pruned, telco.test, type="prob")
    > telco_prediction(dtree.pruned, dtree.prob, telco.test, 2)
    

    在这里插入图片描述

      accuracy precision sensitivity specificity f1_score    auc
    1      0.8      0.68        0.36        0.95     0.47 0.7972
    

    6.3.2 随机森林

    随机森林就是通过集成学习的思想将多棵树集成的一种算法,它的基本单元是决策树。随机森林是通过自助法重复抽样技术,从原始样本训练集中有放回地随机抽取k个样本生成新的训练集样本集合,然后根据自助样本集生成k个决策树组成的随机森林,最后根据所有决策树的预测结果来最终确定样本的预测结果。

    randomForest()函数中的两个重要参数为ntree和mtry,其中ntree为基分类器个数,默认为500;mtry为每个决策树包含的变量个数,默认为logN,数据量不大时可以循环选择最优参数值。

    > library(randomForest)
    > set.seed(123)
    > # 选择mtry
    > err <- as.numeric()
    > for(i in 1:(length(names(telco.train)))-1){
    +   mtry_test <- randomForest(Churn~., data = telco.train, mtry=i)
    +   err <- append(err, mean(mtry_test$err.rate))
    + }
    > print(err)
    
     [1] 0.3500 0.3479 0.2722 0.2769 0.2802 0.2837 0.2783 0.2784 0.2799 0.2817 0.2812
    [12] 0.2831 0.2829 0.2849 0.2843 0.2829 0.2876
    
    > mtry <- which.min(err)
    > mtry
    
    [1] 3
    
    > # 选择ntree
    > ntree_fit <- randomForest(Churn~., data = telco.train, mtry=mtry, ntree=1000)
    > plot(ntree_fit)
    

    在这里插入图片描述

    发现,mtry取3时err最小,ntree取500时误差趋于稳定。 因此,得到最终分类器,观察模型效果。

    > set.seed(123)
    > fit.rf <- randomForest(Churn~., data = telco.train, mtry = 3,
    +                            ntree= 500)
    > rf.prob <- predict(fit.rf, telco.test, type="prob")
    > telco_prediction(fit.rf, rf.prob, telco.test, 2)
    

    在这里插入图片描述

      accuracy precision sensitivity specificity f1_score    auc
    1      0.8      0.62         0.5         0.9     0.55 0.8269
    

    6.3.3 支持向量机

    支持向量机,一般简称SVM,通俗来讲,它是一种二类分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。

    kernel指定建模过程中使用的核函数,目的在于解决支持向量机线性不可分问题。有四类核函数可选,即线性核函数、多项式核函数、径向基核函数(高斯核函数)和神经网络核函数。研究人员发现,识别率最高,性能最好的是径向基核函数(默认的kernel值),其次是多项式核函数,最差的是神经网络核函数。

    > library(e1071)
    > set.seed(123)
    > fit.svm <- svm(Churn~., data =telco.train, probability=TRUE)
    > pred.svm <- predict(fit.svm, telco.test, probability=TRUE)
    > svm.prob <- attr(pred.svm, "probabilities")
    > telco_prediction(fit.svm, svm.prob, telco.test, 2)
    

    在这里插入图片描述

      accuracy precision sensitivity specificity f1_score    auc
    1     0.81      0.67        0.47        0.92     0.55 0.8295
    

    6.3.4 Logistic回归分析

    数据集中自变量较多,为了使构建的Logistic回归模型比较稳定和便于解释,应尽可能地剔除对回归模型贡献程度很小的变量。
    首先在训练集上使用所有自变量建立一个模型,然后使用逐步法构建一个模型,通过F检验对两个模型进行方差分析,评估两个模型是否有显著不同。

    > full.fit <- glm(Churn~.,data = telco.train, family = binomial())
    > summary(full.fit)
    > both.fit <- step(full.fit,direction = "both")
    > summary(both.fit)
    > anova(full.fit, both.fit, test = "Chisq")
    
    Analysis of Deviance Table
    
    Model 1: Churn ~ SeniorCitizen + Partner + Dependents + tenure + MultipleLines + 
        InternetService + OnlineSecurity + OnlineBackup + DeviceProtection + 
        TechSupport + StreamingTV + StreamingMovies + Contract + 
        PaperlessBilling + PaymentMethod + MonthlyCharges_c
    Model 2: Churn ~ SeniorCitizen + Dependents + tenure + MultipleLines + 
        InternetService + OnlineSecurity + OnlineBackup + TechSupport + 
        StreamingTV + StreamingMovies + Contract + PaperlessBilling + 
        PaymentMethod
      Resid. Df Resid. Dev Df Deviance Pr(>Chi)
    1      4906       4159                     
    2      4911       4162 -5    -2.92     0.71
    

    由结果可知,两者的差距并不显著。后续利用both.fit该模型进行分析。

    使用exp(coef())函数计算优势比,优势比可以解释为特征中1个单位的变化导致的结果发生比的变化。如果系数大于1,则说明当特征的值增加时,结果的发生比会增加。反之,系数小于1就说明,当特征的值增加时,结果的发生比会减小。

    > # 查看优势比
    > exp(coef(both.fit))
    
                             (Intercept)                     SeniorCitizenYes 
                                  0.5414                               1.1781 
                           DependentsYes                               tenure 
                                  0.7990                               0.9670 
           MultipleLinesNo phone service                     MultipleLinesYes 
                                  1.5816                               1.2939 
              InternetServiceFiber optic                    InternetServiceNo 
                                  2.3627                               0.4557 
                       OnlineSecurityYes                      OnlineBackupYes 
                                  0.6831                               0.8750 
                          TechSupportYes                       StreamingTVYes 
                                  0.7126                               1.3219 
                      StreamingMoviesYes                     ContractOne year 
                                  1.3802                               0.5219 
                        ContractTwo year                  PaperlessBillingYes 
                                  0.2259                               1.4270 
    PaymentMethodCredit card (automatic)        PaymentMethodElectronic check 
                                  0.9150                               1.3394 
               PaymentMethodMailed check 
                                  0.9616 
    

    both.fit模型输出的是Churn=Yes时的概率。
    可以看到,在保持其他变量不变的情况下:

    1. 开通Fiber optic互联网服务的客户流失风险是开通DSL服务的2.4倍,而未开通互联网服务的客户流失风险是开通DSL服务的0.5倍,所以,未开通互联网服务的客服流失风险最低;
    2. 按一年签订合同的客户流失风险是按月签订合同的0.5倍,按两年签订合同的客户流失风险是按月签订合同的0.2倍,所以签订合同的期限越长,客户流失的风险越低。

    之后利用构建的模型进行预测。

    > library(pROC)
    > glm.prob <- predict.glm(both.fit, telco.test, type = "response")
    > glm.class <- ifelse(glm.prob > 0.5, "Yes", "No")
    > telco.test$predict <- glm.class
    > true.value <- telco.test[,16]
    > predict.value <- telco.test[,18]
    > # 混淆矩阵
    > table <- table(true.value,predict.value)
    > tn = table[1,1]
    > fp = table[1,2]
    > fn = table[2,1]
    > tp = table[2,2]
    > accuracy = round((tn+tp)/(tn+fp+fn+tp),2)
    > precision = round(tp/(tp+fp),2)
    > sensitivity = round(tp/(tp+fn),2)
    > specificity = round(tn/(tn+fp),2)
    > f1_score = round((2*precision*sensitivity)/(precision+sensitivity),2)
    > # 绘制ROC
    > modelroc <- roc(true.value, glm.prob) 
    
    > plot(modelroc, print.auc=TRUE, auc.polygon=TRUE,legacy.axes=TRUE,
    +      grid=c(0.1, 0.2), 
    +      grid.col=c("green", "red"), max.auc.polygon=TRUE,
    +      auc.polygon.col="skyblue", print.thres=TRUE)  
    

    在这里插入图片描述

    > auc <- auc(modelroc)
    > # 输出指标
    > data.frame(accuracy, precision, sensitivity, specificity, f1_score, auc)
    
      accuracy precision sensitivity specificity f1_score   auc
    1     0.81      0.63        0.55         0.9     0.59 0.847
    

    6.4 输出特征重要性

    上述四个分类器的准确率都达到了80%以上,但由于正负样本比例不均衡,所以,accuracy不能客观评价算法的优劣。
    从结果来看,Logistic回归模型的sensitivity、f1值和auc值都较高,因此基于Logistic回归模型,输出特征重要性。

    ±---------------±---------±----------±------------±------------±---------±-------+
    | 模型 | accuracy | precision | sensitivity | specificity | f1_score | auc
    ±-----------------±---------±----------±------------±------------±---------±-------+
    | 决策树 | 0.8 | 0.68 | 0.36 | 0.95 | 0.47 | 0.7972 |
    ±-----------------±---------±----------±------------±------------±---------±-------+
    | 随机森林 | 0.8 | 0.62 | 0.5 | 0.9 | 0.55 | 0.8269 |
    ±-----------------±---------±----------±------------±------------±---------±-------+
    | SVM | 0.81 | 0.67 | 0.47 | 0.92 | 0.55 | 0.8295 |
    ±-----------------±---------±----------±------------±------------±---------±-------+
    | Logistic回归分析 | 0.81 | 0.63 | 0.55 | 0.9 | 0.59 | 0.847 |
    ±-----------------±---------±----------±------------±------------±---------±-------+

    > library(caret)
    > library(ggplot2)
    > library(dplyr)
    > importance <- caret::varImp(both.fit, scale = FALSE)
    > importance$var <- row.names(importance)
    > imp <- importance %>% 
    +   mutate(var = factor(var, levels = var[order(Overall)]))
    > ggplot(imp, aes(x = Overall, y=var)) + geom_bar(stat = "identity", fill = 'pink')+ theme_bw() + labs(x = "importance")
    

    在这里插入图片描述

    7 运营建议

    根据预测模型,构建一个潜在流失客户的列表。通过用户调研,详细了解客户对产品不满意的方面。

    7.1 用户层面

    1. 针对老年用户、无亲属、无伴侣用户,制定专属的个性化服务,如推出亲属套餐、温暖套餐等,提升用户的满意度。
    2. 可以为其提供签到积分换购、会员日充值优惠等活动。

    7.2 服务层面

    1. 针对新注册客户,降低第一年的月租费,以此渡过用户的流失高峰期。
    2. 重点改善“光纤网络”服务。
    3. 针对开通互联网服务、网络电视服务或电影服务的客户,提升网络体验,完善增值服务,例如对用户承诺免费提供网络升级的服务。
    4. 针对开通在线安全、在线备份、设备保护、技术支持等增值服务,对客户大力推广,可对其给予优惠,如首月免费体验、满减券等,鼓励客户使用增值服务。

    7.3 合同层面

    1. 针对单月合同用户,可推出“充值返钱”的活动,例如充值50返120,分6个月返,将月用户转化为半年用户,提高用户的在网时长,增加用户的黏性。
    2. 减少目前月租费在70-110元客户的部分费用,或采用赠送充值话费抵扣券的活动,以降低流失率。
    展开全文
  • 客户ID 地理区域 是否双频 是否翻新机 当前手机价格 手机网络功能 婚姻状况 家庭成人人数 信息库匹配 预计收入 ... 客户生命周期内平均月费用 客户生命周期内的平均每月使用分钟数 客户整个生命周期内...

    from google.colab import drive
    drive.mount('/content/drive')
    import os
    os.chdir('/content/drive/MyDrive/chinese task/讯飞-电信用户流失')
    
    Mounted at /content/drive
    

    参考:

    !pip install unzip
    !unzip '/content/drive/MyDrive/chinese task/讯飞-电信用户流失/电信客户流失预测挑战赛数据集.zip'n
    
    

    读取数据集:

    import pandas as pd
    train= pd.read_csv('./train.csv');
    test=pd.read_csv('./test.csv')
    train
    
    客户ID地理区域是否双频是否翻新机当前手机价格手机网络功能婚姻状况家庭成人人数信息库匹配预计收入...客户生命周期内平均月费用客户生命周期内的平均每月使用分钟数客户整个生命周期内的平均每月通话次数过去三个月的平均每月使用分钟数过去三个月的平均每月通话次数过去三个月的平均月费用过去六个月的平均每月使用分钟数过去六个月的平均每月通话次数过去六个月的平均月费用是否流失
    0070-118102003...242869135112123303101250
    111310139903000...4444719048319940488202441
    22141092702406...4818379271957120977540
    3310023203-11-1...4230316647322672446219651
    440-1069901203...361192488153510621371
    ..................................................................
    1499951499951010135003000...1564741602398074346122831
    14999614999661054203-11-1...529682081158257581307261570
    1499971499971510130001206...3950420554420345531205471
    149998149998121013990410-1...9168524923314094432236971
    1499991499991010104903-11-1...3717780147593516774340

    150000 rows × 69 columns

    一、查看各字段中分布情况

    train['是否流失'].value_counts()#查看正负样本数
    
    #查看是否有缺失值
    missing_counts = pd.DataFrame(train.isnull().sum())
    missing_counts.columns = ['count_null']
    missing_counts.describe()
    
    #查看各字段数据类型
    for col in train.columns:
      print(f'{col} \t {train.dtypes[col]} {train[col].nunique()}')
    
    import pandas as pd
    import numpy as np
    
    from sklearn.metrics import roc_auc_score
    from sklearn.metrics import accuracy_score
    from sklearn.model_selection import KFold
    import time
    from lightgbm import LGBMClassifier
    import lightgbm as lgb
    
    import matplotlib.pyplot as plt
    import matplotlib.gridspec as gridspec
    import seaborn as sns
    %matplotlib inline
    
    import warnings
    warnings.simplefilter('ignore', UserWarning)
    
    import gc
    gc.enable()
    import time
    

    1.2 使用pandas_profiling自动分析数据

    参考:

    conda install -c conda-forge pandas-profiling
    
    #!pip install -U pandas-profiling[notebook]
    #安装之后要重启kernal
    import pandas as pd
    import pandas_profiling
    data = pd.read_csv('./train.csv')
    profile = data.profile_report(title='Pandas Profiling Report')
    profile.to_file(output_file="telecom_customers_pandas_profiling.html")
    

    查看Pandas Profiling Report发现:

    • 类别特征:‘地理区域’,‘是否双频’,‘是否翻新机’,‘手机网络功能’,‘婚姻状况’,‘家庭成人人数’,‘信息库匹配’,‘信用卡指示器’,‘新手机用户’,‘账户消费限额’
    • 分箱特征有:‘预计收入’,
    • 异常值特征:‘家庭中唯一订阅者的数量’,‘家庭活跃用户数’,
    • 无用(数据不平衡)特征:‘平均呼叫转移呼叫数’,‘平均丢弃数据呼叫数’,[149797,148912]
    #区分数值特征和类别特征
    features=list(train.columns)
    categorical_features =['地理区域','是否双频','是否翻新机','手机网络功能','婚姻状况','预计收入',
                           '家庭成人人数','信息库匹配','信用卡指示器','新手机用户','账户消费限额']
    numeric_features =[item for item in features if item not in categorical_features]
    numeric_features=[i for i in numeric_features if i not in ['客户ID','是否流失']]
    #多类别和少类别
    categorical_features1 =['是否双频','是否翻新机','手机网络功能','信息库匹配','信用卡指示器','新手机用户','账户消费限额']
    categorical_features2 =['地理区域','婚姻状况','预计收入','家庭成人人数']
    
    #处理几个异常值
    #train[train['家庭中唯一订阅者的数量'].values > 13]=14
    
    #通过查看Pandas Profiling Report,发现以下列类别不平衡,打印出来看看情况
    #还有一些异常值暂时没处理
    cols=['家庭中唯一订阅者的数量','家庭活跃用户数','数据超载的平均费用','平均漫游呼叫数','平均丢弃数据呼叫数','平均占线数据调用次数',
         '未应答数据呼叫的平均次数','尝试数据调用的平均数','完成数据调用的平均数','平均三通电话数','平均峰值数据调用次数',
          '非高峰数据呼叫的平均数量','平均呼叫转移呼叫数']
    for i in cols:
        print(train[i].value_counts())
    
    • lr=0.2时roc=0.84479;0.3时0.8379,;lr=0.15时0.84578
    • ‘num_leaves’,30改为45时,0.8468

    这样调没啥用啊

    #以下特征99.5%都是一种数值,可以考虑删掉。[149797,149493,149218,148912]
    #lr=0.2时roc=0.84479
    null_clos=['平均呼叫转移呼叫数','平均占线数据调用次数','未应答数据呼叫的平均次数','平均丢弃数据呼叫数']
    
    for i in null_clos:
        del train[i]
        del test[i]
    train
    

    二、 使用baseline参数训练

    《科大讯飞:电信客户流失预测挑战赛baseline》

    1. 全部特征跑10931轮,valid_acc=0.84298
    2. null importance跑5000轮:
      1. 选取split_feats大于0的特征(43种)可跑14402轮,valid_acc=0.83887
      2. 选取feats大于0的特征(23种)可跑10946轮,valid_acc=0.8193
    3. null importance跑1000轮:
      1. 选取split_feats大于0的特征(66种)可跑11817轮,valid_acc=0.84417
      2. 选取feats大于0的特征(58种)可跑11725轮,valid_acc=0.84345
    from sklearn.model_selection import train_test_split
    # 划分训练集和测试集
    X_train,X_test,y_train,y_test=train_test_split(train.drop(labels=['客户ID','是否流失'],axis=1),train['是否流失'],random_state=10,test_size=0.2)
    
    imp_df = pd.DataFrame()
    lgb_train = lgb.Dataset(X_train, y_train,free_raw_data=False,silent=True)
    lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train,free_raw_data=False,
                           silent=True)
    
    lgb_params = {
          'boosting_type': 'gbdt',
          'objective': 'binary',
          'metric': 'auc',
          'min_child_weight': 5,
          'num_leaves': 2 ** 5,
          'lambda_l2': 10,
          'feature_fraction': 0.7,
          'bagging_fraction': 0.7,
          'bagging_freq': 10,
          'learning_rate': 0.2,
          'seed': 2022,
          'n_jobs':-1}
        
        # 训练5000轮,每300轮报告一次acc,200轮没有提升就停止训练
    clf = lgb.train(params=lgb_params,train_set=lgb_train,valid_sets=lgb_eval,
              num_boost_round=50000,verbose_eval=300,early_stopping_rounds=200)
    roc= roc_auc_score(y_test, clf.predict( X_test))
    y_pred=[1 if x >0.5 else 0 for x in clf.predict(X_test)]
    acc=accuracy_score(y_test,y_pred)
    
    Training until validation scores don't improve for 200 rounds.
    [300]	valid_0's auc: 0.733101
    [600]	valid_0's auc: 0.754127
    [900]	valid_0's auc: 0.766728
    [1200]	valid_0's auc: 0.777367
    [1500]	valid_0's auc: 0.78594
    [1800]	valid_0's auc: 0.792209
    [2100]	valid_0's auc: 0.798424
    [2400]	valid_0's auc: 0.80417
    [2700]	valid_0's auc: 0.808074
    [3000]	valid_0's auc: 0.811665
    [3300]	valid_0's auc: 0.814679
    [3600]	valid_0's auc: 0.817462
    [3900]	valid_0's auc: 0.820151
    [4200]	valid_0's auc: 0.822135
    [4500]	valid_0's auc: 0.824544
    [4800]	valid_0's auc: 0.825994
    Did not meet early stopping. Best iteration is:
    [4994]	valid_0's auc: 0.826797
    
    roc,acc
    
    (0.8267972007033084, 0.7533)
    

    三、Null Importances进行特征选择

    def get_feature_importances(X_train, X_test, y_train, y_test,shuffle, seed=None):
        # 获取特征
        train_features = list(X_train.columns)   
        # 判断是否shuffle TARGET
        y_train,y_test= y_train.copy(),y_test.copy()
        if shuffle:
            # Here you could as well use a binomial distribution
            y_train,y_test= y_train.copy().sample(frac=1.0),y_test.copy().sample(frac=1.0)
        
        
        lgb_train = lgb.Dataset(X_train, y_train,free_raw_data=False,silent=True)
        lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train,free_raw_data=False,silent=True)
        # 在 RF 模式下安装 LightGBM,它比 sklearn RandomForest 更快   
        lgb_params = {
          'boosting_type': 'gbdt',
          'objective': 'binary',
          'metric': 'auc',
          'min_child_weight': 5,
          'num_leaves': 2 ** 5,
          'lambda_l2': 10,
          'feature_fraction': 0.7,
          'bagging_fraction': 0.7,
          'bagging_freq': 10,
          'learning_rate': 0.2,
          'seed': 2022,
          'n_jobs':-1}
        
        # 训练模型
        clf = lgb.train(params=lgb_params,train_set=lgb_train,valid_sets=lgb_eval,
              num_boost_round=500,verbose_eval=50,early_stopping_rounds=30)
    
        #得到特征重要性
        imp_df = pd.DataFrame()
        imp_df["feature"] = list(train_features)
        imp_df["importance_gain"] = clf.feature_importance(importance_type='gain')
        imp_df["importance_split"] = clf.feature_importance(importance_type='split')
        imp_df['trn_score'] = roc_auc_score(y_test, clf.predict( X_test))
        
        return imp_df
    
    
    np.random.seed(123)
    # 获得市实际的特征重要性,即没有shuffletarget
    actual_imp_df = get_feature_importances(X_train, X_test, y_train, y_test, shuffle=False)
    actual_imp_df
    
    Training until validation scores don't improve for 20 rounds.
    [30]	valid_0's auc: 0.695549
    [60]	valid_0's auc: 0.704629
    [90]	valid_0's auc: 0.711638
    [120]	valid_0's auc: 0.715182
    [150]	valid_0's auc: 0.718961
    [180]	valid_0's auc: 0.722121
    [210]	valid_0's auc: 0.725615
    [240]	valid_0's auc: 0.728251
    [270]	valid_0's auc: 0.730962
    [300]	valid_0's auc: 0.733101
    [330]	valid_0's auc: 0.73578
    [360]	valid_0's auc: 0.73886
    [390]	valid_0's auc: 0.741238
    [420]	valid_0's auc: 0.742486
    [450]	valid_0's auc: 0.744295
    [480]	valid_0's auc: 0.746555
    Did not meet early stopping. Best iteration is:
    [495]	valid_0's auc: 0.747792
    
    featureimportance_gainimportance_splittrn_score
    0地理区域1956.6004223130.747792
    1是否双频442.401141620.747792
    2是否翻新机269.466828260.747792
    3当前手机价格3838.6961973650.747792
    4手机网络功能750.396258510.747792
    ...............
    62过去三个月的平均每月通话次数2540.7210273250.747792
    63过去三个月的平均月费用2098.8138673040.747792
    64过去六个月的平均每月使用分钟数2375.9257413370.747792
    65过去六个月的平均每月通话次数2541.7351723460.747792
    66过去六个月的平均月费用2103.0622073130.747792

    67 rows × 4 columns

    <svg xmlns=“http://www.w3.org/2000/svg” height="24px"viewBox=“0 0 24 24”
    width=“24px”>



    null_imp_df = pd.DataFrame()
    nb_runs = 10
    import time
    start = time.time()
    dsp = ''
    for i in range(nb_runs):
        # 获取当前的特征重要性
        imp_df = get_feature_importances(X_train, X_test, y_train, y_test, shuffle=True)
        imp_df['run'] = i + 1 
        # 将特征重要性连起来
        null_imp_df = pd.concat([null_imp_df, imp_df], axis=0)
        # 删除上一条信息
        for l in range(len(dsp)):
            print('\b', end='', flush=True)
        # Display current run and time used
        spent = (time.time() - start) / 60
        dsp = 'Done with %4d of %4d (Spent %5.1f min)' % (i + 1, nb_runs, spent)
        print(dsp, end='', flush=True)
    null_imp_df
    
    featureimportance_gainimportance_splittrn_scorerun
    0地理区域38.62273050.5053201
    1是否双频0.00000000.5053201
    2是否翻新机0.00000000.5053201
    3当前手机价格30.98030040.5053201
    4手机网络功能0.00000000.5053201
    ..................
    62过去三个月的平均每月通话次数109.945481140.50391110
    63过去三个月的平均月费用35.34462140.50391110
    64过去六个月的平均每月使用分钟数55.20038070.50391110
    65过去六个月的平均每月通话次数53.43908060.50391110
    66过去六个月的平均月费用47.45520060.50391110

    670 rows × 5 columns

    <svg xmlns=“http://www.w3.org/2000/svg” height="24px"viewBox=“0 0 24 24”
    width=“24px”>



    def display_distributions(actual_imp_df_, null_imp_df_, feature_):
        plt.figure(figsize=(13, 6))
        gs = gridspec.GridSpec(1, 2)
        # 画出 Split importances
        ax = plt.subplot(gs[0, 0])
        a = ax.hist(null_imp_df_.loc[null_imp_df_['feature'] == feature_, 'importance_split'].values, label='Null importances')
        ax.vlines(x=actual_imp_df_.loc[actual_imp_df_['feature'] == feature_, 'importance_split'].mean(), 
                   ymin=0, ymax=np.max(a[0]), color='r',linewidth=10, label='Real Target')
        ax.legend()
        ax.set_title('Split Importance of %s' % feature_.upper(), fontweight='bold')
        plt.xlabel('Null Importance (split) Distribution for %s ' % feature_.upper())
        # 画出 Gain importances
        ax = plt.subplot(gs[0, 1])
        a = ax.hist(null_imp_df_.loc[null_imp_df_['feature'] == feature_, 'importance_gain'].values, label='Null importances')
        ax.vlines(x=actual_imp_df_.loc[actual_imp_df_['feature'] == feature_, 'importance_gain'].mean(), 
                   ymin=0, ymax=np.max(a[0]), color='r',linewidth=10, label='Real Target')
        ax.legend()
        ax.set_title('Gain Importance of %s' % feature_.upper(), fontweight='bold')
        plt.xlabel('Null Importance (gain) Distribution for %s ' % feature_.upper())
    
    #画出“DESTINATION_AIRPORT”的特征重要性
    display_distributions(actual_imp_df_=actual_imp_df, null_imp_df_=null_imp_df, feature_='DESTINATION_AIRPORT')
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KNaPI0o5-1655391260403)(xunfei_files/xunfei_16_0.png)]

    plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体设置-黑体
    plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
    sns.set(font='SimHei')
    

    3.2 计算Score

    以未进行特征shuffle的特征重要性除以shuffle之后的0.75分位数作为我们的score

    
    feature_scores = []
    for _f in actual_imp_df['feature'].unique():
        f_null_imps_gain = null_imp_df.loc[null_imp_df['feature'] == _f, 'importance_gain'].values
        f_act_imps_gain = actual_imp_df.loc[actual_imp_df['feature'] == _f, 'importance_gain'].mean()
        gain_score = np.log(1e-10 + f_act_imps_gain / (1 + np.percentile(f_null_imps_gain, 75)))  # Avoid didvide by zero
        f_null_imps_split = null_imp_df.loc[null_imp_df['feature'] == _f, 'importance_split'].values
        f_act_imps_split = actual_imp_df.loc[actual_imp_df['feature'] == _f, 'importance_split'].mean()
        split_score = np.log(1e-10 + f_act_imps_split / (1 + np.percentile(f_null_imps_split, 75)))  # Avoid didvide by zero
        feature_scores.append((_f, split_score, gain_score))
    
    scores_df = pd.DataFrame(feature_scores, columns=['feature', 'split_score', 'gain_score'])
    
    plt.figure(figsize=(16, 16))
    gs = gridspec.GridSpec(1, 2)
    # Plot Split importances
    ax = plt.subplot(gs[0, 0])
    sns.barplot(x='split_score', y='feature', data=scores_df.sort_values('split_score', ascending=False).iloc[0:70], ax=ax)
    ax.set_title('Feature scores wrt split importances', fontweight='bold', fontsize=14)
    # Plot Gain importances
    ax = plt.subplot(gs[0, 1])
    sns.barplot(x='gain_score', y='feature', data=scores_df.sort_values('gain_score', ascending=False).iloc[0:70], ax=ax)
    ax.set_title('Feature scores wrt gain importances', fontweight='bold', fontsize=14)
    plt.tight_layout()
    
    null_imp_df.to_csv('null_importances_distribution_rf.csv')
    actual_imp_df.to_csv('actual_importances_ditribution_rf.csv')
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ObncLd9v-1655391260406)(xunfei_files/xunfei_19_1.png)]

    [('当前设备使用天数', 21885.414773210883), ('当月使用分钟数与前三个月平均值的百分比变化', 17307.072956457734), ('每月平均使用分钟数', 12217.853455409408), ('在职总月数', 11940.929380342364), ('客户生命周期内的平均每月使用分钟数', 11776.946275830269), ('客户整个生命周期内的平均每月通话次数', 11571.01933504641), ('已完成语音通话的平均使用分钟数', 10899.402202293277), ('客户生命周期内的总费用', 10882.543393820524), ('当前手机价格', 10766.242197856307), ('使用高峰语音通话的平均不完整分钟数', 10392.122741535306), ('计费调整后的总费用', 10233.600193202496), ('当月费用与前三个月平均值的百分比变化', 10154.000930830836), ('客户生命周期内的总使用分钟数', 9959.518506526947), ('计费调整后的总分钟数', 9880.493449807167), ('客户生命周期内平均月费用', 9879.557141974568), ('客户生命周期内的总通话次数', 9863.276128590107), ('过去六个月的平均每月使用分钟数', 9739.2590110749), ('过去六个月的平均每月通话次数', 9574.12247480452), ('过去三个月的平均每月使用分钟数', 9345.73676533997), ('计费调整后的呼叫总数', 9230.227682426572)]
    
    
    scores_df.sort_values(by="split_score",ascending=False,inplace=True)
    scores_df
    
    featuresplit_scoregain_score
    17每月平均使用分钟数4.1523974.571279
    60客户整个生命周期内的平均每月通话次数4.1163234.226021
    56计费调整后的总分钟数3.9928083.961585
    52客户生命周期内的总通话次数3.9325024.008059
    38一分钟内的平均呼入电话数3.8322583.505356
    ............
    35完成数据调用的平均数1.8787712.220746
    30未应答数据呼叫的平均次数1.7917593.040400
    28平均占线数据调用次数1.6094382.565711
    49平均呼叫转移呼叫数0.6931472.221640
    26平均丢弃数据呼叫数-23.025851-23.025851

    67 rows × 3 columns

    <svg xmlns=“http://www.w3.org/2000/svg” height="24px"viewBox=“0 0 24 24”
    width=“24px”>



      <script>
        const buttonEl =
          document.querySelector('#df-f361a60a-7ab8-44ef-b53e-41a69f129e6a button.colab-df-convert');
        buttonEl.style.display =
          google.colab.kernel.accessAllowed ? 'block' : 'none';
    
        async function convertToInteractive(key) {
          const element = document.querySelector('#df-f361a60a-7ab8-44ef-b53e-41a69f129e6a');
          const dataTable =
            await google.colab.kernel.invokeFunction('convertToInteractive',
                                                     [key], {});
          if (!dataTable) return;
    
          const docLinkHtml = 'Like what you see? Visit the ' +
            '<a target="_blank" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'
            + ' to learn more about interactive tables.';
          element.innerHTML = '';
          dataTable['output_type'] = 'display_data';
          await google.colab.output.renderOutput(dataTable, element);
          const docLink = document.createElement('div');
          docLink.innerHTML = docLinkHtml;
          element.appendChild(docLink);
        }
      </script>
    </div>
    
    #huffle target之后特征重要性低于实际target对应特征的重要性0.25分位数的次数百分比
    correlation_scores = []
    for _f in actual_imp_df['feature'].unique():
        f_null_imps = null_imp_df.loc[null_imp_df['feature'] == _f, 'importance_gain'].values
        f_act_imps = actual_imp_df.loc[actual_imp_df['feature'] == _f, 'importance_gain'].values
        gain_score = 100 * (f_null_imps < np.percentile(f_act_imps, 35)).sum() / f_null_imps.size
        f_null_imps = null_imp_df.loc[null_imp_df['feature'] == _f, 'importance_split'].values
        f_act_imps = actual_imp_df.loc[actual_imp_df['feature'] == _f, 'importance_split'].values
        split_score = 100 * (f_null_imps < np.percentile(f_act_imps, 35)).sum() / f_null_imps.size
        correlation_scores.append((_f, split_score, gain_score))
    
    corr_scores_df = pd.DataFrame(correlation_scores, columns=['feature', 'split_score', 'gain_score'])
    
    fig = plt.figure(figsize=(16, 16))
    gs = gridspec.GridSpec(1, 2)
    # Plot Split importances
    ax = plt.subplot(gs[0, 0])
    sns.barplot(x='split_score', y='feature', data=corr_scores_df.sort_values('split_score', ascending=False).iloc[0:70], ax=ax)
    ax.set_title('Feature scores wrt split importances', fontweight='bold', fontsize=14)
    # Plot Gain importances
    ax = plt.subplot(gs[0, 1])
    sns.barplot(x='gain_score', y='feature', data=corr_scores_df.sort_values('gain_score', ascending=False).iloc[0:70], ax=ax)
    ax.set_title('Feature scores wrt gain importances', fontweight='bold', fontsize=14)
    plt.tight_layout()
    plt.suptitle("Features' split and gain scores", fontweight='bold', fontsize=16)
    fig.subplots_adjust(top=0.93)
    
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c2T6Pes1-1655391260409)(xunfei_files/xunfei_22_1.png)]

    corr_scores_df.sort_values(by="split_score",ascending=False,inplace=True)
    corr_scores_df
    
    
    featuresplit_scoregain_score
    0地理区域100.0100.0
    50平均呼叫等待呼叫数100.0100.0
    36平均客户服务电话次数100.0100.0
    37使用客户服务电话的平均分钟数100.0100.0
    38一分钟内的平均呼入电话数100.0100.0
    ............
    29平均未接语音呼叫数100.0100.0
    30未应答数据呼叫的平均次数100.0100.0
    31尝试拨打的平均语音呼叫次数100.0100.0
    66过去六个月的平均月费用100.0100.0
    26平均丢弃数据呼叫数0.00.0

    67 rows × 3 columns

    <svg xmlns=“http://www.w3.org/2000/svg” height="24px"viewBox=“0 0 24 24”
    width=“24px”>



    3.3 筛选正确的特征

    通过corr_scores_df知道,平均丢弃数据呼叫数是没用的,可以去掉。去掉之后效果确实提升了

    X_train,X_test,y_train,y_test=train_test_split(train.drop(labels=
          ['客户ID','是否流失','平均丢弃数据呼叫数'],axis=1),train['是否流失'],random_state=10,test_size=0.2)
    
    imp_df = pd.DataFrame()
    lgb_train = lgb.Dataset(X_train,y_train,free_raw_data=False,silent=True)
    lgb_eval = lgb.Dataset(X_test,y_test,reference=lgb_train,free_raw_data=False,
                           silent=True)
    
    lgb_params = {
          'boosting_type': 'gbdt',
          'objective': 'binary',
          'metric': 'auc',
          'min_child_weight': 5,
          'num_leaves': 2 ** 5,
          'lambda_l2': 10,
          'feature_fraction': 0.7,
          'bagging_fraction': 0.7,
          'bagging_freq': 10,
          'learning_rate': 0.2,
          'seed': 2022,
          'n_jobs':-1}
        
        # 训练5000轮,每300轮报告一次acc,200轮没有提升就停止训练
    clf = lgb.train(params=lgb_params,train_set=lgb_train,valid_sets=lgb_eval,
              num_boost_round=50000,verbose_eval=300,early_stopping_rounds=200)
    roc= roc_auc_score(y_test, clf.predict( X_test))
    y_pred=[1 if x >0.5 else 0 for x in clf.predict(X_test)]
    acc=accuracy_score(y_test,y_pred)
    
    Training until validation scores don't improve for 200 rounds.
    [300]	valid_0's auc: 0.734833
    [600]	valid_0's auc: 0.753598
    [900]	valid_0's auc: 0.767934
    [1200]	valid_0's auc: 0.778701
    [1500]	valid_0's auc: 0.785552
    [1800]	valid_0's auc: 0.793379
    [2100]	valid_0's auc: 0.799713
    [2400]	valid_0's auc: 0.805404
    [2700]	valid_0's auc: 0.809381
    [3000]	valid_0's auc: 0.813516
    [3300]	valid_0's auc: 0.816289
    [3600]	valid_0's auc: 0.81927
    [3900]	valid_0's auc: 0.821682
    [4200]	valid_0's auc: 0.824342
    [4500]	valid_0's auc: 0.82676
    [4800]	valid_0's auc: 0.829004
    [5100]	valid_0's auc: 0.830592
    [5400]	valid_0's auc: 0.83205
    [5700]	valid_0's auc: 0.833626
    [6000]	valid_0's auc: 0.83478
    [6300]	valid_0's auc: 0.835981
    [6600]	valid_0's auc: 0.836975
    [6900]	valid_0's auc: 0.837994
    [7200]	valid_0's auc: 0.838715
    [7500]	valid_0's auc: 0.83963
    [7800]	valid_0's auc: 0.840372
    [8100]	valid_0's auc: 0.840644
    [8400]	valid_0's auc: 0.841068
    [8700]	valid_0's auc: 0.841685
    Early stopping, best iteration is:
    [8768]	valid_0's auc: 0.841806
    
    pred=clf.predict(X_test,num_iteration=clf.best_iteration)
    
    roc,acc
    
    (0.8418064634121478, 0.7683)
    

    四、跑通baseline

    baseline参考:https://mp.weixin.qq.com/s/nLgaGMJByOqRVWnm1UfB3g

    !pip install catboost
    
    import pandas as pd
    import os
    import gc
    import lightgbm as lgb
    import xgboost as xgb
    from catboost import CatBoostRegressor
    from sklearn.linear_model import SGDRegressor, LinearRegression, Ridge
    from sklearn.preprocessing import MinMaxScaler
    from gensim.models import Word2Vec
    import math
    import numpy as np
    from tqdm import tqdm
    from sklearn.model_selection import StratifiedKFold, KFold
    from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_loss
    import matplotlib.pyplot as plt
    import time
    import warnings
    warnings.filterwarnings('ignore')
    
    train = pd.read_csv('train.csv')
    test = pd.read_csv('test.csv')
    data = pd.concat([train, test], axis=0, ignore_index=True)
    
    features = [f for f in data.columns if f not in ['是否流失','客户ID','平均丢弃数据呼叫数']]
    
    train = data[data['是否流失'].notnull()].reset_index(drop=True)
    test = data[data['是否流失'].isnull()].reset_index(drop=True)
    
    x_train = train[features]
    x_test = test[features]
    
    y_train = train['是否流失']
    

    4.1使用lgb训练

    def cv_model(clf, train_x, train_y, test_x, clf_name):
        folds=5
        seed=2022
        kf=KFold(n_splits=folds,shuffle=True,random_state=seed)
    
        train=np.zeros(train_x.shape[0])
        test=np.zeros(test_x.shape[0])
    
        cv_scores = []
    
        for i, (train_index, valid_index) in enumerate(kf.split(train_x,train_y)):
            print('************************************ {} ************************************'.format(str(i+1)))
            trn_x,trn_y,val_x,val_y=train_x.iloc[train_index],train_y[train_index],train_x.iloc[valid_index],train_y[valid_index]
    
            if clf_name == "lgb":
                train_matrix=clf.Dataset(trn_x, label=trn_y)
                valid_matrix=clf.Dataset(val_x, label=val_y)
                #baseline参数
                params = {
                    'boosting_type': 'gbdt',
                    'objective': 'binary',
                    'metric': 'auc',
                    'num_leaves': 2 ** 5,
                    'lambda_l2': 10,
                    'feature_fraction': 0.7,
                    'bagging_fraction': 0.7,
                    'bagging_freq': 10,
                    'learning_rate': 0.2,
                    'seed': 2022,
                    'n_jobs':-1}
                #最优参数
                params2={'boosting_type': 'gbdt',
                    'objective': 'binary',
                    'metric': 'auc',
                    'bagging_fraction': 0.8864320989515848,
                    'bagging_freq': 10,
                    'feature_fraction': 0.7719195132945438,
                    'lambda_l1': 4.0642058550131175,
                    'lambda_l2': 0.7571744617226672,
                    'learning_rate': 0.33853400726057015,
                    'max_depth': 10,
                    'min_gain_to_split': 0.47988339149638315,
                    'num_leaves': 48,
                    'seed': 2022,
                    'n_jobs':-1}
                model = clf.train(params,train_matrix,50000,valid_sets=[train_matrix, valid_matrix], 
                                  categorical_feature=[],verbose_eval=3000, early_stopping_rounds=200)
                val_pred=model.predict(val_x,num_iteration=model.best_iteration)
                test_pred=model.predict(test_x,num_iteration=model.best_iteration)
                
                print(list(sorted(zip(features,model.feature_importance("gain")),key=lambda x: x[1], reverse=True))[:20])
                    
            if clf_name == "xgb":
                train_matrix=clf.DMatrix(trn_x,label=trn_y)
                valid_matrix=clf.DMatrix(val_x,label=val_y)
                test_matrix=clf.DMatrix(test_x)
                
                params={'booster': 'gbtree',
                          'objective': 'binary:logistic',
                          'eval_metric': 'auc',
                          'gamma': 1,
                          'min_child_weight': 1.5,
                          'max_depth': 5,
                          'lambda': 10,
                          'subsample': 0.7,
                          'colsample_bytree': 0.7,
                          'colsample_bylevel': 0.7,
                          'eta': 0.2,
                          'tree_method': 'exact',
                          'seed': 2020,
                          'nthread': 36,
                          "silent": True,
                          }
                
                watchlist=[(train_matrix, 'train'),(valid_matrix, 'eval')]
                
                model=clf.train(params, train_matrix, num_boost_round=50000, evals=watchlist, verbose_eval=3000, early_stopping_rounds=200)
                val_pred=model.predict(valid_matrix, ntree_limit=model.best_ntree_limit)
                test_pred=model.predict(test_matrix , ntree_limit=model.best_ntree_limit)
                     
            if clf_name=="cat":
                params={'learning_rate': 0.2, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type': 'Bernoulli',
                          'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False}
                
                model=clf(iterations=20000, **params)
                model.fit(trn_x,trn_y,eval_set=(val_x, val_y),
                          cat_features=[],use_best_model=True, verbose=3000)
                
                val_pred=model.predict(val_x)
                test_pred=model.predict(test_x)
                
            train[valid_index]=val_pred
            test=test_pred/kf.n_splits
            cv_scores.append(roc_auc_score(val_y,val_pred))
            
            print(cv_scores)
           
        print("%s_scotrainre_list:" % clf_name, cv_scores)
        print("%s_score_mean:" % clf_name, np.mean(cv_scores))
        print("%s_score_std:" % clf_name, np.std(cv_scores))
        return train, test
        
    def lgb_model(x_train,y_train,x_test):
        lgb_train,lgb_test=cv_model(lgb,x_train,y_train,x_test,"lgb")
        return lgb_train,lgb_test
    
    def xgb_model(x_train,y_train,x_test):
        xgb_train,xgb_test=cv_model(xgb,x_train,y_train,x_test,"xgb")
        return xgb_train, xgb_test
    
    def cat_model(x_train,y_train,x_test):
        cat_train,cat_test=cv_model(CatBoostRegressor,x_train,y_train,x_test,"cat") 
        return cat_train,cat_test
    
    lgb_train,lgb_test=lgb_model(x_train,y_train,x_test)#查看代码执行记录,耗时21min左右吧
    test['是否流失'] = lgb_test
    test[['客户ID','是否流失']].to_csv('lgb_base.csv',index=False)#提交成绩0.825
    
    ************************************ 1 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999488	valid_1's auc: 0.811334
    Early stopping, best iteration is:
    [5163]	training's auc: 0.999996	valid_1's auc: 0.8289
    [('当前设备使用天数', 21934.934124737978), ('当月使用分钟数与前三个月平均值的百分比变化', 17126.358324214816), ('在职总月数', 12409.957622632384), ('每月平均使用分钟数', 12073.125538095832), ('客户生命周期内的平均每月使用分钟数', 11994.06405813992), ('客户整个生命周期内的平均每月通话次数', 11518.068050682545), ('已完成语音通话的平均使用分钟数', 11292.594955265522), ('当前手机价格', 10964.187494635582), ('客户生命周期内的总费用', 10750.710047110915), ('使用高峰语音通话的平均不完整分钟数', 10274.193908914924), ('客户生命周期内的总使用分钟数', 10260.600554332137), ('当月费用与前三个月平均值的百分比变化', 10164.166730254889), ('计费调整后的总分钟数', 10095.02776375413), ('计费调整后的总费用', 10074.029564589262), ('客户生命周期内的总通话次数', 9900.794713005424), ('客户生命周期内平均月费用', 9874.11763061583), ('平均非高峰语音呼叫数', 9546.732098400593), ('过去六个月的平均每月通话次数', 9531.47578701377), ('过去六个月的平均每月使用分钟数', 9481.577100589871), ('计费调整后的呼叫总数', 9305.693744853139)]
    [0.8288996222651557]
    ************************************ 2 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999472	valid_1's auc: 0.811878
    Early stopping, best iteration is:
    [4772]	training's auc: 0.999971	valid_1's auc: 0.827608
    [('当前设备使用天数', 21505.16284123063), ('当月使用分钟数与前三个月平均值的百分比变化', 16946.651323199272), ('每月平均使用分钟数', 12132.766281962395), ('在职总月数', 11971.832910627127), ('客户生命周期内的平均每月使用分钟数', 11526.178689315915), ('客户整个生命周期内的平均每月通话次数', 11283.326876536012), ('当前手机价格', 11003.212880536914), ('客户生命周期内的总费用', 10808.01029574871), ('已完成语音通话的平均使用分钟数', 10684.196997240186), ('使用高峰语音通话的平均不完整分钟数', 10399.707967177033), ('当月费用与前三个月平均值的百分比变化', 10358.123901829123), ('客户生命周期内的总使用分钟数', 10162.593608289957), ('客户生命周期内的总通话次数', 10073.619953781366), ('计费调整后的总费用', 9978.180806919932), ('计费调整后的总分钟数', 9764.853373721242), ('过去三个月的平均每月通话次数', 9391.67290854454), ('过去六个月的平均每月通话次数', 9381.156281203032), ('客户生命周期内平均月费用', 9243.235832542181), ('过去六个月的平均每月使用分钟数', 9032.57935705781), ('计费调整后的呼叫总数', 8945.249050289392)]
    [0.8288996222651557, 0.8276084395403329]
    ************************************ 3 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999494	valid_1's auc: 0.811642
    Early stopping, best iteration is:
    [4663]	training's auc: 0.99999	valid_1's auc: 0.827114
    [('当前设备使用天数', 21289.608253866434), ('当月使用分钟数与前三个月平均值的百分比变化', 16997.806541010737), ('在职总月数', 12316.054881855845), ('客户生命周期内的平均每月使用分钟数', 11741.117707148194), ('每月平均使用分钟数', 11664.033028051257), ('已完成语音通话的平均使用分钟数', 11115.561656951904), ('客户整个生命周期内的平均每月通话次数', 10854.345216721296), ('当前手机价格', 10763.63857871294), ('客户生命周期内的总费用', 10621.98585870862), ('当月费用与前三个月平均值的百分比变化', 10375.685174629092), ('计费调整后的总费用', 10232.226524055004), ('客户生命周期内的总使用分钟数', 10052.964914098382), ('使用高峰语音通话的平均不完整分钟数', 9799.514198839664), ('计费调整后的总分钟数', 9735.032970786095), ('客户生命周期内平均月费用', 9637.621711835265), ('客户生命周期内的总通话次数', 9429.328524649143), ('过去六个月的平均每月使用分钟数', 9333.910300150514), ('计费调整后的呼叫总数', 9013.730677694082), ('过去六个月的平均每月通话次数', 8954.436415627599), ('过去六个月的平均月费用', 8829.167943418026)]
    [0.8288996222651557, 0.8276084395403329, 0.8271140081312421]
    ************************************ 4 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999532	valid_1's auc: 0.814214
    Early stopping, best iteration is:
    [5281]	training's auc: 0.999996	valid_1's auc: 0.830897
    [('当前设备使用天数', 21271.166813850403), ('当月使用分钟数与前三个月平均值的百分比变化', 17270.63153974712), ('每月平均使用分钟数', 12677.148315995932), ('在职总月数', 12486.456961512566), ('客户生命周期内的平均每月使用分钟数', 11930.549542114139), ('客户整个生命周期内的平均每月通话次数', 11403.163509890437), ('已完成语音通话的平均使用分钟数', 11126.607083335519), ('当前手机价格', 10973.327338501811), ('当月费用与前三个月平均值的百分比变化', 10719.836767598987), ('客户生命周期内的总费用', 10684.931542679667), ('计费调整后的总费用', 10567.041279122233), ('计费调整后的总分钟数', 10477.076363384724), ('客户生命周期内的总使用分钟数', 10404.941493198276), ('客户生命周期内平均月费用', 10015.077973127365), ('使用高峰语音通话的平均不完整分钟数', 9988.746752500534), ('过去六个月的平均每月使用分钟数', 9924.928602397442), ('客户生命周期内的总通话次数', 9658.558003604412), ('平均非高峰语音呼叫数', 9605.689363330603), ('过去六个月的平均每月通话次数', 9560.14350926876), ('计费调整后的呼叫总数', 9525.798342213035)]
    [0.8288996222651557, 0.8276084395403329, 0.8271140081312421, 0.8308971625977979]
    ************************************ 5 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999444	valid_1's auc: 0.8118
    Early stopping, best iteration is:
    [5148]	training's auc: 0.999994	valid_1's auc: 0.829686
    [('当前设备使用天数', 21662.356478646398), ('当月使用分钟数与前三个月平均值的百分比变化', 17710.528580009937), ('在职总月数', 12402.68640038371), ('每月平均使用分钟数', 11945.518620952964), ('客户生命周期内的平均每月使用分钟数', 11887.39459644258), ('已完成语音通话的平均使用分钟数', 11309.949122816324), ('客户整个生命周期内的平均每月通话次数', 11231.172733142972), ('客户生命周期内的总费用', 10822.351191923022), ('当前手机价格', 10691.375393077731), ('计费调整后的总费用', 10513.226110234857), ('当月费用与前三个月平均值的百分比变化', 10418.488398104906), ('客户生命周期内的总使用分钟数', 10276.142720848322), ('使用高峰语音通话的平均不完整分钟数', 10242.566086634994), ('计费调整后的总分钟数', 10193.664465650916), ('客户生命周期内的总通话次数', 10117.483586207032), ('客户生命周期内平均月费用', 9943.684495016932), ('过去六个月的平均每月通话次数', 9800.775234118104), ('过去三个月的平均每月通话次数', 9572.030710801482), ('过去六个月的平均每月使用分钟数', 9561.15305377543), ('平均非高峰语音呼叫数', 9292.315245553851)]
    [0.8288996222651557, 0.8276084395403329, 0.8271140081312421, 0.8308971625977979, 0.8296855557324957]
    lgb_scotrainre_list: [0.8288996222651557, 0.8276084395403329, 0.8271140081312421, 0.8308971625977979, 0.8296855557324957]
    lgb_score_mean: 0.8288409576534048
    lgb_score_std: 0.0013744978556818929
    
    
    
    
    
    "\n************************************ 1 ************************************\nTraining until validation scores don't improve for 200 rounds.\n[3000]\ttraining's auc: 0.999474\tvalid_1's auc: 0.811874\nEarly stopping, best iteration is:\n[4935]\ttraining's auc: 0.999996\tvalid_1's auc: 0.827972\n[('当前设备使用天数', 21885.414773210883), ('当月使用分钟数与前三个月平均值的百分比变化', 17307.072956457734), ('每月平均使用分钟数', 12217.853455409408), ('在职总月数', 11940.929380342364), ('客户生命周期内的平均每月使用分钟数', 11776.946275830269), ('客户整个生命周期内的平均每月通话次数', 11571.01933504641), ('已完成语音通话的平均使用分钟数', 10899.402202293277), ('客户生命周期内的总费用', 10882.543393820524), ('当前手机价格', 10766.242197856307), ('使用高峰语音通话的平均不完整分钟数', 10392.122741535306), ('计费调整后的总费用', 10233.600193202496), ('当月费用与前三个月平均值的百分比变化', 10154.000930830836), ('客户生命周期内的总使用分钟数', 9959.518506526947), ('计费调整后的总分钟数', 9880.493449807167), ('客户生命周期内平均月费用', 9879.557141974568), ('客户生命周期内的总通话次数', 9863.276128590107), ('过去六个月的平均每月使用分钟数', 9739.2590110749), ('过去六个月的平均每月通话次数', 9574.12247480452), ('过去三个月的平均每月使用分钟数', 9345.73676533997), ('计费调整后的呼叫总数', 9230.227682426572)]\n[0.8279715963308298]\n************************************ 2 ************************************\nTraining until validation scores don't improve for 200 rounds.\n[3000]\ttraining's auc: 0.999427\tvalid_1's auc: 0.810338\nEarly stopping, best iteration is:\n[4648]\ttraining's auc: 0.999965\tvalid_1's auc: 0.824151\n[('当前设备使用天数', 21631.878849938512), ('当月使用分钟数与前三个月平均值的百分比变化', 16730.961754366755), ('在职总月数', 12067.951921060681), ('每月平均使用分钟数', 12002.064660459757), ('客户生命周期内的平均每月使用分钟数', 11514.234459266067), ('客户整个生命周期内的平均每月通话次数', 11378.85348239541), ('已完成语音通话的平均使用分钟数', 10749.901214078069), ('当前手机价格', 10722.060040861368), ('客户生命周期内的总费用', 10603.264658093452), ('当月费用与前三个月平均值的百分比变化', 10405.526055783033), ('使用高峰语音通话的平均不完整分钟数', 10171.211520016193), ('客户生命周期内的总使用分钟数', 10006.355669140816), ('计费调整后的总分钟数', 9942.827439278364), ('客户生命周期内的总通话次数', 9937.020643949509), ('计费调整后的总费用', 9920.474541395903), ('过去六个月的平均每月使用分钟数', 9621.407806247473), ('客户生命周期内平均月费用', 9319.960188627243), ('过去三个月的平均每月通话次数', 9318.490131109953), ('平均月费用', 9294.081347599626), ('过去六个月的平均每月通话次数', 9203.844007015228)]\n[0.8279715963308298, 0.8241509252411403]\n************************************ 3 ************************************\nTraining until validation scores don't improve for 200 rounds.\n[3000]\ttraining's auc: 0.99949\tvalid_1's auc: 0.810687\nEarly stopping, best iteration is:\n[4731]\ttraining's auc: 0.999987\tvalid_1's auc: 0.825545\n[('当前设备使用天数', 21968.028517633677), ('当月使用分钟数与前三个月平均值的百分比变化', 16903.005848184228), ('在职总月数', 12133.818779706955), ('客户生命周期内的平均每月使用分钟数', 11976.253827899694), ('每月平均使用分钟数', 11948.46197539568), ('已完成语音通话的平均使用分钟数', 11421.855388239026), ('客户整个生命周期内的平均每月通话次数', 11262.173004433513), ('当前手机价格', 11005.929363071918), ('客户生命周期内的总费用', 10528.124375209212), ('客户生命周期内的总使用分钟数', 10390.872772306204), ('计费调整后的总费用', 10347.706698387861), ('当月费用与前三个月平均值的百分比变化', 10124.151285156608), ('计费调整后的总分钟数', 9813.354337349534), ('使用高峰语音通话的平均不完整分钟数', 9805.469536915421), ('客户生命周期内平均月费用', 9772.446165367961), ('过去六个月的平均每月使用分钟数', 9544.928655743599), ('计费调整后的呼叫总数', 9390.860902503133), ('过去六个月的平均每月通话次数', 9323.151294022799), ('客户生命周期内的总通话次数', 9320.212619245052), ('过去三个月的平均每月通话次数', 9084.183073118329)]\n[0.8279715963308298, 0.8241509252411403, 0.8255446840361296]\n************************************ 4 ************************************\nTraining until validation scores don't improve for 200 rounds.\n[3000]\ttraining's auc: 0.9995\tvalid_1's auc: 0.813234\nEarly stopping, best iteration is:\n[5599]\ttraining's auc: 0.999997\tvalid_1's auc: 0.831782\n[('当前设备使用天数', 21882.617314189672), ('当月使用分钟数与前三个月平均值的百分比变化', 17574.675364792347), ('每月平均使用分钟数', 12675.68729557097), ('在职总月数', 12567.960791677237), ('客户生命周期内的平均每月使用分钟数', 12466.111717522144), ('客户整个生命周期内的平均每月通话次数', 11556.674870744348), ('已完成语音通话的平均使用分钟数', 11522.147867411375), ('当前手机价格', 11065.775812849402), ('客户生命周期内的总使用分钟数', 10911.875026881695), ('客户生命周期内的总费用', 10715.607445791364), ('使用高峰语音通话的平均不完整分钟数', 10510.982212975621), ('当月费用与前三个月平均值的百分比变化', 10451.965088263154), ('计费调整后的总费用', 10446.603226020932), ('计费调整后的总分钟数', 10408.396666422486), ('过去六个月的平均每月使用分钟数', 10079.377708375454), ('客户生命周期内平均月费用', 10037.817246481776), ('客户生命周期内的总通话次数', 10017.892398029566), ('计费调整后的呼叫总数', 9739.093963235617), ('过去三个月的平均每月通话次数', 9609.546253487468), ('平均非高峰语音呼叫数', 9569.536746695638)]\n[0.8279715963308298, 0.8241509252411403, 0.8255446840361296, 0.8317817862344651]\n************************************ 5 ************************************\nTraining until validation scores don't improve for 200 rounds.\n[3000]\ttraining's auc: 0.999448\tvalid_1's auc: 0.810144\nEarly stopping, best iteration is:\n[5255]\ttraining's auc: 0.999999\tvalid_1's auc: 0.829245\n[('当前设备使用天数', 21498.932903170586), ('当月使用分钟数与前三个月平均值的百分比变化', 17680.002600044012), ('在职总月数', 12638.706078097224), ('客户生命周期内的平均每月使用分钟数', 12569.80523788929), ('每月平均使用分钟数', 12267.705941140652), ('当前手机价格', 11370.256973087788), ('已完成语音通话的平均使用分钟数', 11110.097302675247), ('客户整个生命周期内的平均每月通话次数', 11020.642103403807), ('客户生命周期内的总费用', 10986.333106696606), ('计费调整后的总费用', 10700.256485000253), ('当月费用与前三个月平均值的百分比变化', 10575.144608184695), ('计费调整后的总分钟数', 10401.467713326216), ('使用高峰语音通话的平均不完整分钟数', 10237.447989702225), ('客户生命周期内的总通话次数', 10139.773517146707), ('客户生命周期内平均月费用', 10076.59566681087), ('客户生命周期内的总使用分钟数', 9953.696122318506), ('过去六个月的平均每月使用分钟数', 9595.342250138521), ('平均非高峰语音呼叫数', 9504.704583987594), ('过去六个月的平均每月通话次数', 9500.140991523862), ('计费调整后的呼叫总数', 9425.357908219099)]\n[0.8279715963308298, 0.8241509252411403, 0.8255446840361296, 0.8317817862344651, 0.8292446287869105]\nlgb_scotrainre_list: [0.8279715963308298, 0.8241509252411403, 0.8255446840361296, 0.8317817862344651, 0.8292446287869105]\nlgb_score_mean: 0.827738724125895\nlgb_score_std: 0.002696458502502849\n"
    

    4.2 使用Xgb训练

    xgb_train,xgb_test=xgb_model(x_train,y_train,x_test)
    test['是否流失'] = xgb_test
    test[['客户ID','是否流失']].to_csv('xgb_base.csv',index=False)#2h50min,太慢了
    
    ************************************ 1 ************************************
    [0]	train-auc:0.635939	eval-auc:0.634176
    Multiple eval metrics have been passed: 'eval-auc' will be used for early stopping.
    
    Will train until eval-auc hasn't improved in 200 rounds.
    [3000]	train-auc:0.992932	eval-auc:0.788708
    [6000]	train-auc:0.999906	eval-auc:0.807173
    [9000]	train-auc:0.999997	eval-auc:0.812868
    Stopping. Best iteration:
    [9945]	train-auc:0.999999	eval-auc:0.814055
    
    [0.8140550495535315]
    ************************************ 2 ************************************
    [0]	train-auc:0.636635	eval-auc:0.633894
    Multiple eval metrics have been passed: 'eval-auc' will be used for early stopping.
    
    Will train until eval-auc hasn't improved in 200 rounds.
    [3000]	train-auc:0.992878	eval-auc:0.790387
    [6000]	train-auc:0.99988	eval-auc:0.807621
    Stopping. Best iteration:
    [8538]	train-auc:0.999991	eval-auc:0.812347
    
    [0.8140550495535315, 0.8123468873894992]
    ************************************ 3 ************************************
    [0]	train-auc:0.637058	eval-auc:0.630979
    Multiple eval metrics have been passed: 'eval-auc' will be used for early stopping.
    
    Will train until eval-auc hasn't improved in 200 rounds.
    [3000]	train-auc:0.992874	eval-auc:0.790023
    [6000]	train-auc:0.999898	eval-auc:0.80827
    [9000]	train-auc:0.999996	eval-auc:0.813291
    Stopping. Best iteration:
    [8933]	train-auc:0.999996	eval-auc:0.813342
    
    [0.8140550495535315, 0.8123468873894992, 0.8133415339513355]
    ************************************ 4 ************************************
    [0]	train-auc:0.635278	eval-auc:0.633351
    Multiple eval metrics have been passed: 'eval-auc' will be used for early stopping.
    
    Will train until eval-auc hasn't improved in 200 rounds.
    [3000]	train-auc:0.993107	eval-auc:0.78905
    [6000]	train-auc:0.999903	eval-auc:0.808401
    Stopping. Best iteration:
    [8343]	train-auc:0.999993	eval-auc:0.812439
    
    [0.8140550495535315, 0.8123468873894992, 0.8133415339513355, 0.8124389857259089]
    ************************************ 5 ************************************
    [0]	train-auc:0.635985	eval-auc:0.633911
    Multiple eval metrics have been passed: 'eval-auc' will be used for early stopping.
    
    Will train until eval-auc hasn't improved in 200 rounds.
    [3000]	train-auc:0.992892	eval-auc:0.788101
    [6000]	train-auc:0.999904	eval-auc:0.805732
    [9000]	train-auc:0.999997	eval-auc:0.810194
    Stopping. Best iteration:
    [10041]	train-auc:0.999999	eval-auc:0.811155
    
    [0.8140550495535315, 0.8123468873894992, 0.8133415339513355, 0.8124389857259089, 0.8111551410360852]
    xgb_scotrainre_list: [0.8140550495535315, 0.8123468873894992, 0.8133415339513355, 0.8124389857259089, 0.8111551410360852]
    xgb_score_mean: 0.8126675195312721
    xgb_score_std: 0.000982024071432044
    

    4.3 使用cat训练

    cat_train,cat_test=cat_model(x_train,y_train,x_test)#22min,和lgb差不多
    test['是否流失'] = cat_test
    test[['客户ID','是否流失']].to_csv('cat_base.csv',index=False)#
    
    ************************************ 1 ************************************
    0:	learn: 0.4955489	test: 0.4954619	best: 0.4954619 (0)	total: 233ms	remaining: 1h 17m 39s
    3000:	learn: 0.3769726	test: 0.4483572	best: 0.4483572 (3000)	total: 2m 5s	remaining: 11m 50s
    6000:	learn: 0.3209359	test: 0.4391546	best: 0.4391520 (5999)	total: 4m	remaining: 9m 21s
    Stopped by overfitting detector  (50 iterations wait)
    
    bestTest = 0.4360869428
    bestIteration = 7499
    
    Shrink model to first 7500 iterations.
    [0.78868229695141]
    ************************************ 2 ************************************
    0:	learn: 0.4953117	test: 0.4954092	best: 0.4954092 (0)	total: 39.5ms	remaining: 13m 10s
    3000:	learn: 0.3763302	test: 0.4490481	best: 0.4490378 (2981)	total: 1m 46s	remaining: 10m 2s
    6000:	learn: 0.3196365	test: 0.4402621	best: 0.4402621 (6000)	total: 3m 38s	remaining: 8m 30s
    Stopped by overfitting detector  (50 iterations wait)
    
    bestTest = 0.4361341716
    bestIteration = 8001
    
    Shrink model to first 8002 iterations.
    [0.78868229695141, 0.7897985044313038]
    ************************************ 3 ************************************
    0:	learn: 0.4954711	test: 0.4955905	best: 0.4955905 (0)	total: 38.5ms	remaining: 12m 49s
    3000:	learn: 0.3763265	test: 0.4477431	best: 0.4477431 (3000)	total: 1m 49s	remaining: 10m 21s
    Stopped by overfitting detector  (50 iterations wait)
    
    bestTest = 0.4406746798
    bestIteration = 5128
    
    Shrink model to first 5129 iterations.
    [0.78868229695141, 0.7897985044313038, 0.7788144016087264]
    ************************************ 4 ************************************
    0:	learn: 0.4955798	test: 0.4955669	best: 0.4955669 (0)	total: 46.1ms	remaining: 15m 21s
    3000:	learn: 0.3768704	test: 0.4486424	best: 0.4486421 (2997)	total: 1m 45s	remaining: 9m 59s
    Stopped by overfitting detector  (50 iterations wait)
    
    bestTest = 0.4426386429
    bestIteration = 4903
    
    Shrink model to first 4904 iterations.
    [0.78868229695141, 0.7897985044313038, 0.7788144016087264, 0.7744056829683829]
    ************************************ 5 ************************************
    0:	learn: 0.4955262	test: 0.4956471	best: 0.4956471 (0)	total: 38.9ms	remaining: 12m 57s
    3000:	learn: 0.3761659	test: 0.4494234	best: 0.4494234 (3000)	total: 1m 47s	remaining: 10m 11s
    6000:	learn: 0.3202277	test: 0.4407377	best: 0.4407330 (5999)	total: 3m 31s	remaining: 8m 12s
    9000:	learn: 0.2781913	test: 0.4347233	best: 0.4347168 (8998)	total: 5m 14s	remaining: 6m 24s
    Stopped by overfitting detector  (50 iterations wait)
    
    bestTest = 0.4323322625
    bestIteration = 10483
    
    Shrink model to first 10484 iterations.
    [0.78868229695141, 0.7897985044313038, 0.7788144016087264, 0.7744056829683829, 0.7982800693357867]
    cat_scotrainre_list: [0.78868229695141, 0.7897985044313038, 0.7788144016087264, 0.7744056829683829, 0.7982800693357867]
    cat_score_mean: 0.785996191059122
    cat_score_std: 0.0084674009574612
    

    4.4 另外去掉’平均丢弃数据呼叫数’特征

    效果变差了

    lgb_train,lgb_test=lgb_model(x_train,y_train,x_test)
    
    
    lgb_train,lgb_test=lgb_model(x_train,y_train,x_test)
    
    ************************************ 1 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999535	valid_1's auc: 0.811083
    Early stopping, best iteration is:
    [5495]	training's auc: 0.999999	valid_1's auc: 0.830252
    [('当前设备使用天数', 21646.43981860578), ('当月使用分钟数与前三个月平均值的百分比变化', 17622.58995847404), ('在职总月数', 12633.31053687632), ('每月平均使用分钟数', 12317.316355511546), ('客户整个生命周期内的平均每月通话次数', 12213.196875602007), ('客户生命周期内的平均每月使用分钟数', 11988.745236545801), ('已完成语音通话的平均使用分钟数', 11742.254607230425), ('客户生命周期内的总费用', 10961.734202891588), ('客户生命周期内的总使用分钟数', 10739.284949079156), ('当前手机价格', 10717.661178082228), ('使用高峰语音通话的平均不完整分钟数', 10648.361330911517), ('当月费用与前三个月平均值的百分比变化', 10563.12071943283), ('客户生命周期内平均月费用', 10260.813065826893), ('计费调整后的总费用', 10214.983077257872), ('客户生命周期内的总通话次数', 10042.090887442231), ('过去六个月的平均每月使用分钟数', 10030.256944060326), ('计费调整后的总分钟数', 9833.17426289618), ('过去六个月的平均每月通话次数', 9658.642087131739), ('平均非高峰语音呼叫数', 9604.195981651545), ('过去三个月的平均每月使用分钟数', 9474.32663051784)]
    [0.8302521387863329]
    ************************************ 2 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999484	valid_1's auc: 0.812252
    Early stopping, best iteration is:
    [4761]	training's auc: 0.999991	valid_1's auc: 0.827726
    [('当前设备使用天数', 20778.929791480303), ('当月使用分钟数与前三个月平均值的百分比变化', 17059.72723968327), ('在职总月数', 12247.527016088367), ('每月平均使用分钟数', 12162.8245485425), ('客户生命周期内的平均每月使用分钟数', 11649.190486937761), ('客户整个生命周期内的平均每月通话次数', 11235.27798551321), ('已完成语音通话的平均使用分钟数', 10887.697177901864), ('客户生命周期内的总使用分钟数', 10537.405863419175), ('客户生命周期内的总费用', 10427.963113591075), ('当前手机价格', 10388.50929298997), ('当月费用与前三个月平均值的百分比变化', 10345.741146698594), ('使用高峰语音通话的平均不完整分钟数', 10325.746990069747), ('计费调整后的总费用', 10308.259309798479), ('客户生命周期内的总通话次数', 9878.29905757308), ('过去六个月的平均每月使用分钟数', 9860.522675991058), ('计费调整后的总分钟数', 9831.829701200128), ('客户生命周期内平均月费用', 9413.955781325698), ('平均月费用', 9256.14368981123), ('过去三个月的平均每月通话次数', 9233.180386424065), ('过去六个月的平均每月通话次数', 9178.422535061836)]
    [0.8302521387863329, 0.8277260767493848]
    ************************************ 3 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999503	valid_1's auc: 0.812223
    Early stopping, best iteration is:
    [4737]	training's auc: 0.999988	valid_1's auc: 0.826507
    [('当前设备使用天数', 21187.639979198575), ('当月使用分钟数与前三个月平均值的百分比变化', 17066.7826115638), ('在职总月数', 12178.71656690538), ('每月平均使用分钟数', 11915.060246050358), ('客户生命周期内的平均每月使用分钟数', 11457.53249040246), ('已完成语音通话的平均使用分钟数', 11197.47149656713), ('客户整个生命周期内的平均每月通话次数', 11062.857962206006), ('当前手机价格', 10535.98642912507), ('计费调整后的总费用', 10396.114720955491), ('当月费用与前三个月平均值的百分比变化', 10280.928569793701), ('使用高峰语音通话的平均不完整分钟数', 10159.540036082268), ('过去六个月的平均每月使用分钟数', 10114.058793380857), ('客户生命周期内的总使用分钟数', 10109.089174315333), ('客户生命周期内的总费用', 10081.144412502646), ('计费调整后的总分钟数', 10064.824367910624), ('客户生命周期内的总通话次数', 9710.811524420977), ('过去六个月的平均每月通话次数', 9568.110130429268), ('客户生命周期内平均月费用', 9536.692147105932), ('计费调整后的呼叫总数', 9272.926451265812), ('过去三个月的平均每月通话次数', 9104.1763061136)]
    [0.8302521387863329, 0.8277260767493848, 0.8265070441159572]
    ************************************ 4 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999558	valid_1's auc: 0.812316
    Early stopping, best iteration is:
    [4955]	training's auc: 0.999985	valid_1's auc: 0.82816
    [('当前设备使用天数', 20919.606680095196), ('当月使用分钟数与前三个月平均值的百分比变化', 17050.523352131248), ('在职总月数', 12673.502319052815), ('每月平均使用分钟数', 12145.743713662028), ('客户生命周期内的平均每月使用分钟数', 12082.749334529042), ('已完成语音通话的平均使用分钟数', 11270.388913482428), ('客户整个生命周期内的平均每月通话次数', 11032.332806184888), ('客户生命周期内的总费用', 10647.951857417822), ('计费调整后的总费用', 10599.385332718492), ('客户生命周期内的总使用分钟数', 10490.505580991507), ('当前手机价格', 10461.154125005007), ('当月费用与前三个月平均值的百分比变化', 10269.522361278534), ('使用高峰语音通话的平均不完整分钟数', 10231.192073732615), ('客户生命周期内的总通话次数', 9965.85817475617), ('计费调整后的总分钟数', 9773.746473029256), ('客户生命周期内平均月费用', 9764.829889595509), ('过去六个月的平均每月使用分钟数', 9703.316017881036), ('过去六个月的平均每月通话次数', 9595.259186178446), ('平均非高峰语音呼叫数', 9585.856355905533), ('计费调整后的呼叫总数', 9195.526195570827)]
    [0.8302521387863329, 0.8277260767493848, 0.8265070441159572, 0.8281604518378232]
    ************************************ 5 ************************************
    Training until validation scores don't improve for 200 rounds.
    [3000]	training's auc: 0.999494	valid_1's auc: 0.809363
    Early stopping, best iteration is:
    [4829]	training's auc: 0.999983	valid_1's auc: 0.824736
    [('当前设备使用天数', 20857.728651717305), ('当月使用分钟数与前三个月平均值的百分比变化', 17141.65538044274), ('在职总月数', 12623.7158523947), ('每月平均使用分钟数', 12155.711411625147), ('客户生命周期内的平均每月使用分钟数', 11755.307457834482), ('客户整个生命周期内的平均每月通话次数', 11121.649592876434), ('客户生命周期内的总费用', 10800.35821519792), ('当前手机价格', 10647.860997959971), ('已完成语音通话的平均使用分钟数', 10567.15585295856), ('客户生命周期内的总使用分钟数', 10455.313509970903), ('计费调整后的总费用', 10241.350874692202), ('当月费用与前三个月平均值的百分比变化', 10177.092842921615), ('客户生命周期内的总通话次数', 10139.20638936758), ('使用高峰语音通话的平均不完整分钟数', 9981.980402067304), ('计费调整后的总分钟数', 9756.786857843399), ('过去六个月的平均每月使用分钟数', 9725.03030230105), ('客户生命周期内平均月费用', 9604.02791416645), ('计费调整后的呼叫总数', 9452.47144331038), ('平均非高峰语音呼叫数', 9228.985016450286), ('过去六个月的平均每月通话次数', 9228.196154907346)]
    [0.8302521387863329, 0.8277260767493848, 0.8265070441159572, 0.8281604518378232, 0.8247357260735417]
    lgb_scotrainre_list: [0.8302521387863329, 0.8277260767493848, 0.8265070441159572, 0.8281604518378232, 0.8247357260735417]
    lgb_score_mean: 0.8274762875126079
    lgb_score_std: 0.0018267969533472914
    

    五、贝叶斯调参

    #!pip install bayesian-optimization
    from bayes_opt import BayesianOptimization
    
    def LGB_bayesian(
        num_leaves,  # int
        bagging_freq,  # int
        learning_rate, 
        feature_fraction,
        bagging_fraction,
        lambda_l1,
        lambda_l2,
        min_gain_to_split,
        max_depth):
        
        # LightGBM expects next three parameters need to be integer. So we make them integer
        num_leaves = int(num_leaves)
        max_depth = int(max_depth)
    
        assert type(num_leaves) == int
        assert type(max_depth) == int
    
        param = {
            'num_leaves': num_leaves,
            'learning_rate': learning_rate,
            'bagging_fraction': bagging_fraction,
            'bagging_freq': bagging_freq,
            'feature_fraction': feature_fraction,
            'lambda_l1': lambda_l1,
            'lambda_l2': lambda_l2,
            'max_depth': max_depth,
            'objective': 'binary',
            'boosting_type': 'gbdt',
            'verbose': 1,
            'metric': 'auc',
            'seed': 2022,
            'feature_fraction_seed': 2022,
            'bagging_seed': 2022,
            'drop_seed': 2022,
            'data_random_seed': 2022,
            'is_unbalance': True,
            'boost_from_average': False,
            'save_binary': True,    
    
        }    
        lgb_train = lgb.Dataset(X_train,y_train,free_raw_data=False,silent=True)
        lgb_eval = lgb.Dataset(X_test,y_test,reference=lgb_train,free_raw_data=False,
                           silent=True)
        num_round=10000
        clf = lgb.train(param,lgb_train,num_round,valid_sets =lgb_eval,verbose_eval=500,early_stopping_rounds = 200)
        roc= roc_auc_score(y_test,clf.predict(X_test,num_iteration=clf.best_iteration))   
        return roc
    
    
    lgb_train = lgb.Dataset(X_train,y_train,free_raw_data=False,silent=True)
    lgb_eval = lgb.Dataset(X_test,y_test,reference=lgb_train,free_raw_data=False,
                           silent=True)
    
    bounds_LGB = {
        'num_leaves': (5,50),  
        'learning_rate': (0.03,0.5),    
        'feature_fraction': (0.1,1),
        'bagging_fraction': (0.1,1),
        'bagging_freq': (0,10),
        'lambda_l1': (0, 5.0), 
        'lambda_l2': (0, 10), 
        'min_gain_to_split': (0, 1.0),
        'max_depth':(5,15),
    }
    
    X_train,X_test,y_train,y_test=train_test_split(train.drop(labels=
          ['客户ID','是否流失','平均丢弃数据呼叫数'],axis=1),train['是否流失'],random_state=10,test_size=0.2)
    
    from bayes_opt import BayesianOptimization
    LGB_BO = BayesianOptimization(LGB_bayesian, bounds_LGB, random_state=13)
    init_points = 5
    n_iter = 15
    print('-' * 130)
    
    with warnings.catch_warnings():
        warnings.filterwarnings('ignore')
        LGB_BO.maximize(init_points=init_points, n_iter=n_iter, acq='ucb', xi=0.0)
    
    ----------------------------------------------------------------------------------------------------------------------------------
    |   iter    |  target   | baggin... | baggin... | featur... | lambda_l1 | lambda_l2 | learni... | max_depth | min_ga... | num_le... |
    -------------------------------------------------------------------------------------------------------------------------------------
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.756501
    [1000]	valid_0's auc: 0.779654
    [1500]	valid_0's auc: 0.795342
    [2000]	valid_0's auc: 0.804397
    [2500]	valid_0's auc: 0.812615
    [3000]	valid_0's auc: 0.818713
    [3500]	valid_0's auc: 0.82294
    [4000]	valid_0's auc: 0.826771
    [4500]	valid_0's auc: 0.82971
    [5000]	valid_0's auc: 0.832648
    Did not meet early stopping. Best iteration is:
    [5000]	valid_0's auc: 0.832648
    | [0m 1       [0m | [0m 0.8326  [0m | [0m 0.7999  [0m | [0m 2.375   [0m | [0m 0.8419  [0m | [0m 4.829   [0m | [0m 9.726   [0m | [0m 0.2431  [0m | [0m 11.09   [0m | [0m 0.7755  [0m | [0m 33.87   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.735036
    [1000]	valid_0's auc: 0.754152
    [1500]	valid_0's auc: 0.767662
    [2000]	valid_0's auc: 0.778614
    [2500]	valid_0's auc: 0.786152
    [3000]	valid_0's auc: 0.792418
    [3500]	valid_0's auc: 0.79872
    [4000]	valid_0's auc: 0.803314
    [4500]	valid_0's auc: 0.807683
    [5000]	valid_0's auc: 0.81121
    Did not meet early stopping. Best iteration is:
    [5000]	valid_0's auc: 0.81121
    | [0m 2       [0m | [0m 0.8112  [0m | [0m 0.7498  [0m | [0m 0.3504  [0m | [0m 0.3686  [0m | [0m 0.2926  [0m | [0m 8.571   [0m | [0m 0.2052  [0m | [0m 11.8    [0m | [0m 0.2563  [0m | [0m 20.64   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.749544
    [1000]	valid_0's auc: 0.774443
    [1500]	valid_0's auc: 0.78951
    [2000]	valid_0's auc: 0.800602
    [2500]	valid_0's auc: 0.80823
    [3000]	valid_0's auc: 0.814024
    [3500]	valid_0's auc: 0.81853
    [4000]	valid_0's auc: 0.821672
    [4500]	valid_0's auc: 0.823975
    [5000]	valid_0's auc: 0.826105
    Did not meet early stopping. Best iteration is:
    [5000]	valid_0's auc: 0.826105
    | [0m 3       [0m | [0m 0.8261  [0m | [0m 0.1085  [0m | [0m 3.583   [0m | [0m 0.9542  [0m | [0m 1.089   [0m | [0m 3.194   [0m | [0m 0.4614  [0m | [0m 5.319   [0m | [0m 0.06508 [0m | [0m 33.34   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.776662
    [1000]	valid_0's auc: 0.804675
    [1500]	valid_0's auc: 0.817655
    [2000]	valid_0's auc: 0.826085
    [2500]	valid_0's auc: 0.831839
    [3000]	valid_0's auc: 0.835281
    Early stopping, best iteration is:
    [3179]	valid_0's auc: 0.836292
    | [95m 4       [0m | [95m 0.8363  [0m | [95m 0.8864  [0m | [95m 0.08716 [0m | [95m 0.7719  [0m | [95m 4.064   [0m | [95m 0.7572  [0m | [95m 0.3385  [0m | [95m 10.09   [0m | [95m 0.4799  [0m | [95m 48.0    [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.751437
    [1000]	valid_0's auc: 0.777091
    [1500]	valid_0's auc: 0.793125
    [2000]	valid_0's auc: 0.805084
    [2500]	valid_0's auc: 0.812527
    [3000]	valid_0's auc: 0.81902
    [3500]	valid_0's auc: 0.823788
    [4000]	valid_0's auc: 0.827882
    [4500]	valid_0's auc: 0.831144
    [5000]	valid_0's auc: 0.834175
    Did not meet early stopping. Best iteration is:
    [5000]	valid_0's auc: 0.834175
    | [0m 5       [0m | [0m 0.8342  [0m | [0m 0.1     [0m | [0m 2.47    [0m | [0m 0.741   [0m | [0m 1.623   [0m | [0m 2.77    [0m | [0m 0.3569  [0m | [0m 14.19   [0m | [0m 0.2445  [0m | [0m 25.61   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.731224
    [1000]	valid_0's auc: 0.749423
    [1500]	valid_0's auc: 0.760884
    [2000]	valid_0's auc: 0.76975
    [2500]	valid_0's auc: 0.777677
    [3000]	valid_0's auc: 0.785282
    [3500]	valid_0's auc: 0.791667
    [4000]	valid_0's auc: 0.796303
    [4500]	valid_0's auc: 0.800412
    [5000]	valid_0's auc: 0.804301
    Did not meet early stopping. Best iteration is:
    [5000]	valid_0's auc: 0.804301
    | [0m 6       [0m | [0m 0.8043  [0m | [0m 0.6073  [0m | [0m 1.211   [0m | [0m 0.312   [0m | [0m 0.3293  [0m | [0m 7.263   [0m | [0m 0.2122  [0m | [0m 7.399   [0m | [0m 0.2959  [0m | [0m 19.23   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.758604
    [1000]	valid_0's auc: 0.783179
    [1500]	valid_0's auc: 0.798202
    [2000]	valid_0's auc: 0.808477
    [2500]	valid_0's auc: 0.816619
    [3000]	valid_0's auc: 0.821904
    [3500]	valid_0's auc: 0.825642
    [4000]	valid_0's auc: 0.828837
    [4500]	valid_0's auc: 0.83184
    [5000]	valid_0's auc: 0.833605
    Did not meet early stopping. Best iteration is:
    [4998]	valid_0's auc: 0.833608
    | [0m 7       [0m | [0m 0.8336  [0m | [0m 0.5285  [0m | [0m 0.4363  [0m | [0m 0.5314  [0m | [0m 4.917   [0m | [0m 0.0     [0m | [0m 0.2589  [0m | [0m 15.0    [0m | [0m 0.5876  [0m | [0m 36.33   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.775361
    [1000]	valid_0's auc: 0.802306
    [1500]	valid_0's auc: 0.816144
    [2000]	valid_0's auc: 0.823617
    [2500]	valid_0's auc: 0.828743
    [3000]	valid_0's auc: 0.831524
    Early stopping, best iteration is:
    [3085]	valid_0's auc: 0.831879
    | [0m 8       [0m | [0m 0.8319  [0m | [0m 1.0     [0m | [0m 8.511   [0m | [0m 1.0     [0m | [0m 4.544   [0m | [0m 7.213   [0m | [0m 0.4367  [0m | [0m 15.0    [0m | [0m 1.0     [0m | [0m 45.52   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.759645
    [1000]	valid_0's auc: 0.786561
    [1500]	valid_0's auc: 0.802323
    [2000]	valid_0's auc: 0.81118
    [2500]	valid_0's auc: 0.817364
    [3000]	valid_0's auc: 0.821898
    [3500]	valid_0's auc: 0.824679
    Early stopping, best iteration is:
    [3739]	valid_0's auc: 0.826167
    | [0m 9       [0m | [0m 0.8262  [0m | [0m 0.1     [0m | [0m 10.0    [0m | [0m 1.0     [0m | [0m 5.0     [0m | [0m 0.0     [0m | [0m 0.5     [0m | [0m 15.0    [0m | [0m 1.0     [0m | [0m 30.18   [0m |
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.708925
    [1000]	valid_0's auc: 0.721584
    [1500]	valid_0's auc: 0.729905
    [2000]	valid_0's auc: 0.736464
    [2500]	valid_0's auc: 0.741614
    [3000]	valid_0's auc: 0.746397
    [3500]	valid_0's auc: 0.750703
    [4000]	valid_0's auc: 0.754139
    [4500]	valid_0's auc: 0.757474
    [5000]	valid_0's auc: 0.760932
    Did not meet early stopping. Best iteration is:
    [5000]	valid_0's auc: 0.760932
    | [0m 10      [0m | [0m 0.7609  [0m | [0m 0.1     [0m | [0m 0.0     [0m | [0m 0.1     [0m | [0m 0.0     [0m | [0m 7.904   [0m | [0m 0.03    [0m | [0m 15.0    [0m | [0m 0.0     [0m | [0m 43.18   [0m |
    =====================================================================================================================================
    
    print(LGB_BO.max['target'])#优化完成后,让我们看看我们得到的最大值是多少。
    LGB_BO.max['params']#让我们看看参数:
    
    0.8362916622722081
    
    
    
    
    
    {'bagging_fraction': 0.8864320989515848,
     'bagging_freq': 0.08715732303784862,
     'feature_fraction': 0.7719195132945438,
     'lambda_l1': 4.0642058550131175,
     'lambda_l2': 0.7571744617226672,
     'learning_rate': 0.33853400726057015,
     'max_depth': 10.092622000835181,
     'min_gain_to_split': 0.47988339149638315,
     'num_leaves': 48.00083652189798}
    
    • BayesianOptimization库中还有一个很酷的选项。 你可以探测LGB_bayesian函数,如果你对最佳参数有所了解,或者您从其他kernel获取参数。 我将在此复制并粘贴其他内核中的参数。 你可以按照以下方式进行探测
    • 默认情况下这些将被懒惰地探索(lazy = True),这意味着只有在你下次调用maxime时才会评估这些点。 让我们对LGB_BO对象进行最大化调用。
    LGB_BO.probe(
        params={'bagging_fraction': 0.8864320989515848,
            'bagging_freq': 0.08715732303784862,
            'feature_fraction': 0.7719195132945438,
            'lambda_l1': 4.0642058550131175,
            'lambda_l2': 0.7571744617226672,
            'learning_rate': 0.33853400726057015,
            'max_depth': 10,
            'min_gain_to_split': 0.47988339149638315,
            'num_leaves': 48},
        lazy=True, # 
    )
    LGB_BO.maximize(init_points=0, n_iter=0) # remember no init_points or n_iter
    
    
    |   iter    |  target   | baggin... | baggin... | featur... | lambda_l1 | lambda_l2 | learni... | max_depth | min_ga... | num_le... |
    -------------------------------------------------------------------------------------------------------------------------------------
    Training until validation scores don't improve for 200 rounds.
    [500]	valid_0's auc: 0.776662
    [1000]	valid_0's auc: 0.804675
    [1500]	valid_0's auc: 0.817655
    [2000]	valid_0's auc: 0.826085
    [2500]	valid_0's auc: 0.831839
    [3000]	valid_0's auc: 0.835281
    Early stopping, best iteration is:
    [3179]	valid_0's auc: 0.836292
    | [0m 11      [0m | [0m 0.8363  [0m | [0m 0.8864  [0m | [0m 0.08716 [0m | [0m 0.7719  [0m | [0m 4.064   [0m | [0m 0.7572  [0m | [0m 0.3385  [0m | [0m 10.0    [0m | [0m 0.4799  [0m | [0m 48.0    [0m |
    =====================================================================================================================================
    

    通过属性LGB_BO.res可以获得探测的所有参数列表及其相应的目标值。

    for i, res in enumerate(LGB_BO.res):
        print("Iteration {}: \n\t{}".format(i, res))
    
    
    Iteration 0: 
    	{'target': 0.8326475014985013, 'params': {'bagging_fraction': 0.7999321695164382, 'bagging_freq': 2.375412200349123, 'feature_fraction': 0.8418506793952316, 'lambda_l1': 4.8287459902149985, 'lambda_l2': 9.726011139048934, 'learning_rate': 0.2431211462861367, 'max_depth': 11.09042462761278, 'min_gain_to_split': 0.7755265146048467, 'num_leaves': 33.87260051415811}}
    Iteration 1: 
    	{'target': 0.8112097384468528, 'params': {'bagging_fraction': 0.7498164065652524, 'bagging_freq': 0.35036524101437316, 'feature_fraction': 0.36860452380026143, 'lambda_l1': 0.29256245941037373, 'lambda_l2': 8.57060942587199, 'learning_rate': 0.2052413931011595, 'max_depth': 11.798479515780969, 'min_gain_to_split': 0.2562799493266301, 'num_leaves': 20.64115468186214}}
    Iteration 2: 
    	{'target': 0.8261050715459326, 'params': {'bagging_fraction': 0.10847149307287247, 'bagging_freq': 3.5833378270496974, 'feature_fraction': 0.9541847635103893, 'lambda_l1': 1.0894950456584445, 'lambda_l2': 3.193913663803646, 'learning_rate': 0.461353021420276, 'max_depth': 5.319036664398947, 'min_gain_to_split': 0.06508453704251449, 'num_leaves': 33.34230495985203}}
    Iteration 3: 
    	{'target': 0.8362916622722081, 'params': {'bagging_fraction': 0.8864320989515848, 'bagging_freq': 0.08715732303784862, 'feature_fraction': 0.7719195132945438, 'lambda_l1': 4.0642058550131175, 'lambda_l2': 0.7571744617226672, 'learning_rate': 0.33853400726057015, 'max_depth': 10.092622000835181, 'min_gain_to_split': 0.47988339149638315, 'num_leaves': 48.00083652189798}}
    Iteration 4: 
    	{'target': 0.8341753174329471, 'params': {'bagging_fraction': 0.1000108302125366, 'bagging_freq': 2.4697870099191634, 'feature_fraction': 0.7410094101204252, 'lambda_l1': 1.6229102489335656, 'lambda_l2': 2.769963563838095, 'learning_rate': 0.3568593627001331, 'max_depth': 14.185517481459488, 'min_gain_to_split': 0.2444757021979903, 'num_leaves': 25.613861777454364}}
    Iteration 5: 
    	{'target': 0.8043011941176942, 'params': {'bagging_fraction': 0.6072612766397498, 'bagging_freq': 1.2108187274978577, 'feature_fraction': 0.31196871185798225, 'lambda_l1': 0.32926780461649763, 'lambda_l2': 7.263184308560325, 'learning_rate': 0.2121743710976095, 'max_depth': 7.398509227738581, 'min_gain_to_split': 0.2958905376418717, 'num_leaves': 19.228154502442003}}
    Iteration 6: 
    	{'target': 0.833607682808766, 'params': {'bagging_fraction': 0.5284563589983934, 'bagging_freq': 0.4362596810480882, 'feature_fraction': 0.5314451681549669, 'lambda_l1': 4.917464527991873, 'lambda_l2': 0.0, 'learning_rate': 0.258904064526674, 'max_depth': 15.0, 'min_gain_to_split': 0.5875699451215919, 'num_leaves': 36.327854565302374}}
    Iteration 7: 
    	{'target': 0.8318794311203416, 'params': {'bagging_fraction': 1.0, 'bagging_freq': 8.511485033295257, 'feature_fraction': 1.0, 'lambda_l1': 4.543957420169538, 'lambda_l2': 7.21250815846057, 'learning_rate': 0.4367137368532056, 'max_depth': 15.0, 'min_gain_to_split': 1.0, 'num_leaves': 45.523690581294865}}
    Iteration 8: 
    	{'target': 0.826167096580232, 'params': {'bagging_fraction': 0.1, 'bagging_freq': 10.0, 'feature_fraction': 1.0, 'lambda_l1': 5.0, 'lambda_l2': 0.0, 'learning_rate': 0.5, 'max_depth': 15.0, 'min_gain_to_split': 1.0, 'num_leaves': 30.184337136649773}}
    Iteration 9: 
    	{'target': 0.7609321700847423, 'params': {'bagging_fraction': 0.1, 'bagging_freq': 0.0, 'feature_fraction': 0.1, 'lambda_l1': 0.0, 'lambda_l2': 7.904224578705337, 'learning_rate': 0.03, 'max_depth': 15.0, 'min_gain_to_split': 0.0, 'num_leaves': 43.17758999088051}}
    Iteration 10: 
    	{'target': 0.8362916622722081, 'params': {'bagging_fraction': 0.8864320989515848, 'bagging_freq': 0.08715732303784862, 'feature_fraction': 0.7719195132945438, 'lambda_l1': 4.0642058550131175, 'lambda_l2': 0.7571744617226672, 'learning_rate': 0.33853400726057015, 'max_depth': 10.0, 'min_gain_to_split': 0.47988339149638315, 'num_leaves': 48.0}}
    

    构建一个模型使用这些参数。

    params={'bagging_fraction': 0.8864320989515848,
            'bagging_freq': 0.08715732303784862,
            'feature_fraction': 0.7719195132945438,
            'lambda_l1': 4.0642058550131175,
            'lambda_l2': 0.7571744617226672,
            'learning_rate': 0.33853400726057015,
            'max_depth': 10,
            'min_gain_to_split': 0.47988339149638315,
            'num_leaves': 48,}
            
    param_lgb = {
            'num_leaves': int(LGB_BO.max['params']['num_leaves']), # remember to int here
            'max_bin': 63,
            'min_data_in_leaf': int(LGB_BO.max['params']['min_data_in_leaf']), # remember to int here
            'learning_rate': LGB_BO.max['params']['learning_rate'],
            'min_sum_hessian_in_leaf': LGB_BO.max['params']['min_sum_hessian_in_leaf'],
            'bagging_fraction': 1.0, 
            'bagging_freq': 5, 
            'feature_fraction': LGB_BO.max['params']['feature_fraction'],
            'lambda_l1': LGB_BO.max['params']['lambda_l1'],
            'lambda_l2': LGB_BO.max['params']['lambda_l2'],
            'min_gain_to_split': LGB_BO.max['params']['min_gain_to_split'],
            'max_depth': int(LGB_BO.max['params']['max_depth']), # remember to int here
            'save_binary': True,
            'seed': 1337,
            'feature_fraction_seed': 1337,
            'bagging_seed': 1337,
            'drop_seed': 1337,
            'data_random_seed': 1337,
            'objective': 'binary',
            'boosting_type': 'gbdt',
            'verbose': 1,
            'metric': 'auc',
            'is_unbalance': True,
            'boost_from_average': False,
        }
    
    
    nfold = 5
    gc.collect()
    skf = StratifiedKFold(n_splits=nfold, shuffle=True, random_state=2019)
    
    
    oof = np.zeros(len(train_df))
    predictions = np.zeros((len(test_df),nfold))
    
    i = 1
    for train_index, valid_index in skf.split(train_df, train_df.target.values):
        print("\nfold {}".format(i))
        xg_train = lgb.Dataset(train_df.iloc[train_index][predictors].values,
                               label=train_df.iloc[train_index][target].values,
                               feature_name=predictors,
                               free_raw_data = False
                               )
        xg_valid = lgb.Dataset(train_df.iloc[valid_index][predictors].values,
                               label=train_df.iloc[valid_index][target].values,
                               feature_name=predictors,
                               free_raw_data = False
                               )   
    
        
        clf = lgb.train(param_lgb, xg_train, 5000, valid_sets = [xg_valid], verbose_eval=250, early_stopping_rounds = 50)
        oof[valid_index] = clf.predict(train_df.iloc[valid_index][predictors].values, num_iteration=clf.best_iteration) 
        
        predictions[:,i-1] += clf.predict(test_df[predictors], num_iteration=clf.best_iteration)
        i = i + 1
    
    print("\n\nCV AUC: {:<0.2f}".format(metrics.roc_auc_score(train_df.target.values, oof)))
    
    

    Hyperopt入门指南《Insightful EDA + modeling LGBM hyperopt》

    test['是否流失'] = lgb_test
    test[['客户ID','是否流失']].to_csv('test_sub.csv',index=False)
    
    
    

    bda_l2’: 0.7571744617226672, ‘learning_rate’: 0.33853400726057015, ‘max_depth’: 10.0, ‘min_gain_to_split’: 0.47988339149638315, ‘num_leaves’: 48.0}}

    构建一个模型使用这些参数。

    params={'bagging_fraction': 0.8864320989515848,
            'bagging_freq': 0.08715732303784862,
            'feature_fraction': 0.7719195132945438,
            'lambda_l1': 4.0642058550131175,
            'lambda_l2': 0.7571744617226672,
            'learning_rate': 0.33853400726057015,
            'max_depth': 10,
            'min_gain_to_split': 0.47988339149638315,
            'num_leaves': 48,}
            
    param_lgb = {
            'num_leaves': int(LGB_BO.max['params']['num_leaves']), # remember to int here
            'max_bin': 63,
            'min_data_in_leaf': int(LGB_BO.max['params']['min_data_in_leaf']), # remember to int here
            'learning_rate': LGB_BO.max['params']['learning_rate'],
            'min_sum_hessian_in_leaf': LGB_BO.max['params']['min_sum_hessian_in_leaf'],
            'bagging_fraction': 1.0, 
            'bagging_freq': 5, 
            'feature_fraction': LGB_BO.max['params']['feature_fraction'],
            'lambda_l1': LGB_BO.max['params']['lambda_l1'],
            'lambda_l2': LGB_BO.max['params']['lambda_l2'],
            'min_gain_to_split': LGB_BO.max['params']['min_gain_to_split'],
            'max_depth': int(LGB_BO.max['params']['max_depth']), # remember to int here
            'save_binary': True,
            'seed': 1337,
            'feature_fraction_seed': 1337,
            'bagging_seed': 1337,
            'drop_seed': 1337,
            'data_random_seed': 1337,
            'objective': 'binary',
            'boosting_type': 'gbdt',
            'verbose': 1,
            'metric': 'auc',
            'is_unbalance': True,
            'boost_from_average': False,
        }
    
    
    nfold = 5
    gc.collect()
    skf = StratifiedKFold(n_splits=nfold, shuffle=True, random_state=2019)
    
    
    oof = np.zeros(len(train_df))
    predictions = np.zeros((len(test_df),nfold))
    
    i = 1
    for train_index, valid_index in skf.split(train_df, train_df.target.values):
        print("\nfold {}".format(i))
        xg_train = lgb.Dataset(train_df.iloc[train_index][predictors].values,
                               label=train_df.iloc[train_index][target].values,
                               feature_name=predictors,
                               free_raw_data = False
                               )
        xg_valid = lgb.Dataset(train_df.iloc[valid_index][predictors].values,
                               label=train_df.iloc[valid_index][target].values,
                               feature_name=predictors,
                               free_raw_data = False
                               )   
    
        
        clf = lgb.train(param_lgb, xg_train, 5000, valid_sets = [xg_valid], verbose_eval=250, early_stopping_rounds = 50)
        oof[valid_index] = clf.predict(train_df.iloc[valid_index][predictors].values, num_iteration=clf.best_iteration) 
        
        predictions[:,i-1] += clf.predict(test_df[predictors], num_iteration=clf.best_iteration)
        i = i + 1
    
    print("\n\nCV AUC: {:<0.2f}".format(metrics.roc_auc_score(train_df.target.values, oof)))
    
    

    Hyperopt入门指南《Insightful EDA + modeling LGBM hyperopt》

    test['是否流失'] = lgb_test
    test[['客户ID','是否流失']].to_csv('test_sub.csv',index=False)
    

    结果是:
    在这里插入图片描述
    调了几次提升不大就没再提交了

    展开全文
  • 记录第一次参加正式的数据挖掘竞赛,由科大讯飞xDatawhale举办的《电信客户流失预测挑战赛》
  • 电信客户流失预测。 关于该项目- 在这个项目中,我使用各种分类算法,使用数据集中的特征预测客户流失率。 使用的Python软件包-Pandas,Numpy,Scipy,scikit-learn,Seaborn和matplotlib。 关于数据集: 每行代表...
  • 使用的数据集来自开源的kaggle电信客户流失数据: ://www.kaggle.com/blastchar/telco-customer-churn 分类模型评估指标:精度,召回率,F1得分等。 分类中的错误类型:类型1错误:无法拒绝原假设。 误报。类型2...
  • sklearn复合评估器的构建(电信客户流失模型) 零、实验环境及目的 1. 实验环境 2. 实验目的 一、数据介绍 二、模型构建 1. 导入数据 2. 处理缺失数据 3. 构建流失模型 1)数据转换 2)特征选择 3)模型构建 三、...
  • 分析目标 ...f = pd.read_csv(r'D:\Data\电信用户流失' r'\WA_Fn-UseC_-Telco-Customer-Churn - Copy.csv') #检索数据 print(pd.isnull(f).sum(), f.info()) #按列检索数据 # for x in f.columns: #
  • 电信公司市场部为了预防用户流失,收集了已经打好流失标签的用户数据。现在要对流失用户情况进行分析,找出哪些用户可能会流失? 理解数据 采集数据 本数据集描述了电信用户是否流失以及其相关信息,共包含7043条...
  • 电信客户流失数据分析(一)

    万次阅读 多人点赞 2020-04-20 22:41:50
    目录来做个数据分析项目^-^任务1:探索数据集任务2:哪些输入特征与顾客流失具有关联性? 来做个数据分析项目- 背景:在kaggle网站上发现了这个数据集,就顺手拿来做个数据分析...电信客户流失数据集共7043条记录,...
  • 电信客户流失数据分析

    千次阅读 2020-08-21 14:24:32
    这里写自定义目录标题电信客户流失数据分析研究背景提出问题数据集描述特征工程1、数据预处理1.1、特征类型处理1.2、缺失值处理1.3、异常值处理1.4、分类变量标签整理2、特征选择2.1、方差过滤2.2、卡方检验过滤2.3...
  • 使用电信客户流失数据确定功能重要性 杰夫·斯帕格诺拉 笔记: 该自述文件正在进行中,将很快完成。 介绍 客户流失是任何业务的主要关注点。 在当今世界,客户比以往任何时候都更加了解信息,并且只需在移动设备...
  • 机器学习、数据挖掘、特征工程
  • 电信客户流失分析与预警

    千次阅读 2020-07-28 17:13:48
    文章目录一、分析背景与目的二、数据理解三、数据清洗1、读取并查看数据2、数据类型转换四、数据分析---流失用户画像分析1、总流失率2、流失用户画像(1)、用户基本信息1)、年龄(老年人、非老年人)2)、伴侣3)、...
  • 电信客户流失预测挑战赛
  • 科大讯飞电信客户流失预测赛打卡
  • 科大讯飞:电信客户流失预测挑战赛baseline

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,845
精华内容 1,538
关键字:

电信客户流失

友情链接: source_code_x85tp5.zip