第7章【拓展思考】航空公司客户流失分析

发布时间 2023-03-22 21:12:59作者: 乘着风·轻舟过

一、实战背景

  • 如今的营销已经远远不同于过去了,那个一张传单一则广告的时代结束了,这是个大数据的时代。
  • 一个时代有一个时代的生存法则,那些没有适应时代的企业,即使曾经是个庞然大物,今天也几乎不见踪影。
  • 在航空公司这个特殊的领域,国内竞争还是很严峻的,一个顾客的流失造成的损失是4-5个新顾客的流入所不能弥补的,分析用户(特别是会员用户)相关信息,建立模型,发现流失用户特征,制定针对性营销策略,挽留用户是企业生存的重要一环。
  • 本项目基于大数据样本进行挖掘建模,根据用户特征建立模型发现用户流失特征及其原因分析。

二、分析

  • 其实,作为航空公司,客户流失是不可避免的,因为有些用户乘机频率非常低,这部分用户是偶然用户,他们的流失没有太大的挽留价值。
  • 再者,那些会员用户才是研究的重点,愿意入会的,一定是有频繁乘机体验的,这部分人要么是商务精英要么是比较富裕的人,这几类人都是颇具头脑的。但是,即使这样还是流失了,那就是航空公司给出的服务问题了,这类人是重点研究对象。

三、处理过程

  当前,有一个比较尴尬的问题,什么样的顾客可以定义为流失呢?样本数据中根本没有这一项。我们不妨这样定义:

客户类型  定义方式
已流失客户 第二年飞行次数与第一年飞行次数比例小于50%
准流失客户 第二年飞行次数与第一年飞行次数比例在[50%,90%)内
未流失客户 第二年飞行次数与第一年飞行次数比例大于90%
  • 通过上面的定义,解决了这个问题,以一种相对合理的方式定义了用户类型。
  • 当然,实际处理时还需要选取客户的关键属性建立模型,如会员卡级别、客户类型(如上定义)、平均乘机时间间隔、平均折扣率、积分兑换次数、非乘机积分总和、单位里程票价和单位里程积分等。
  • 任然采用数据挖掘领域常用的82开进行训练测试样本分布。

四、数据获取

  • 数据给出(特征过多)

五、数据探索

  • 首先声明,表头均没有使用中文,均为航空公司报表专业术语观测窗口使用两年(06年4月到08年3月),请了解后再阅读本博客,这里不做赘述了。
  • 经过初步的数据探索,我得到了一个分析表,记录数目,各列的平均值、四分位数、标准差、极值等;
  • 为数据预处理定下了基本的基调,首先,有几列数据几乎所有记录都为0,这是没有意义的。(在数据挖掘领域中没有对比的数据是没有任何意义的,因为所有数据都是为了建模服务的)
import pandas as pd

if __name__ == '__main__':
    data = pd.read_csv('../extra/data.csv')
    data.drop(['MEMBER_NO'], axis=1, inplace=True)
    data.describe().to_csv('../extra/data_explored.csv')
    data.to_excel('../extra/data.xls')

六、数据预处理

  • 在数据预处理的第一步就出现了一个不算问题的问题,就是由于Excel解析csv以及编码的一些问题,我看到的表格是错位的,我数据处理第一步就直接数据流导出去成为一个新的xls文件,对于xls文件Excel的解析没有问题,现在,最基本的,我们可视化判断没有了阻碍。(保留最开始的数据是数据分析的基本原则之一)
  • 然后,发现第一列用户编号是用户的唯一标识,然而这对于建模是没有意义的,必须剔除第一列。
  • 选取合适属性构造了需要的六个属性LRFMCK模型,并进行标准化数据。
"""
进行了数据清洗,属性规约,数据变换等基本预处理操作
"""
import pandas as pd


def clean(fileName):
    '''
    数据清洗,去除空记录
    :param fileName:
    :return:
    '''
    cleanedFile = './extra/data_cleaned.csv'
    data = pd.read_excel(fileName)
    # 非空保留
    data = data[data['SUM_YR_1'].notnull() & data['SUM_YR_2'].notnull()]
    # 只保留票价非零的,或者平均折扣率与总飞行公里数同时为0的记录。
    index1 = data['SUM_YR_1'] != 0
    index2 = data['SUM_YR_2'] != 0
    index3 = (data['SEG_KM_SUM'] == 0) & (data['avg_discount'] == 0)
    data = data[index1 | index2 | index3]
    data.to_csv(cleanedFile)


def change(fileName):
    '''
    取出需要的属性列
    :param fileName:
    :return:
    '''
    changedFile = './extra/data_changed.csv'
    data = pd.read_csv(fileName, encoding='utf-8')
    data = data[['LOAD_TIME', 'FFP_DATE', 'LAST_TO_END', 'FLIGHT_COUNT', 'avg_discount', 'SEG_KM_SUM', 'LAST_TO_END',
                 'P1Y_Flight_Count', 'L1Y_Flight_Count']]
    data.to_csv(changedFile, encoding='utf-8')


def LRFMCK(fileName):
    '''
    经过计算得到我的指标数据
    :param fileName:
    :return:
    '''
    data = pd.read_csv(fileName)
    # 其中K为标签标示用户类型
    data2 = pd.DataFrame(columns=['L', 'R', 'F', 'M', 'C', 'K'])
    time_list = []
    import datetime
    for i in range(len(data['LOAD_TIME'])):
        str1 = data['LOAD_TIME'][i].split('/')
        str2 = data['FFP_DATE'][i].split('/')
        temp = datetime.datetime(int(str1[0]), int(str1[1]), int(str1[2])) - datetime.datetime(int(str2[0]), int(str2[1]), int(str2[2]))
        time_list.append(temp.days)
    data2['L'] = pd.Series(time_list)
    data2['R'] = data['LAST_TO_END']
    data2['F'] = data['FLIGHT_COUNT']
    data2['M'] = data['SEG_KM_SUM']
    data2['C'] = data['avg_discount']
    temp = data['L1Y_Flight_Count'] / data['P1Y_Flight_Count']
    for i in range(len(temp)):
        if temp[i] >=0.9:
            # 未流失客户
            temp[i] = 'A'
        elif 0.5 < temp[i] < 0.9:
            # 已经流失客户
            temp[i] = 'B'
        else:
            temp[i] = 'C'
    data2['K'] = temp
    data2.to_csv('./extra/data_changed2.csv', encoding='utf-8')


def standard():
    '''
    标准差标准化
    :return:
    '''
    data = pd.read_csv('./extra/data_changed2.csv', encoding='utf-8').iloc[:, 1:6]
    zscoredfile = './extra/data_standard.csv'
    # 简洁的语句实现了标准化变换,类似地可以实现任何想要的变换
    data = (data - data.mean(axis=0)) / (data.std(axis=0))
    data.columns = ['Z' + i for i in data.columns]
    data2 = pd.read_csv('./extra/data_changed2.csv', encoding='utf-8')
    data['K'] = data2['K']
    data.to_csv(zscoredfile, index=False)


if __name__ == '__main__':
    # dataFile = './extra/data.xls'
    # clean(dataFile)
    # change('./extra/data_cleaned.csv')
    # LRFMCK('./extra/data_changed.csv')
    standard()
View Code

七、数据挖掘建模

  • 流失用户的分析,并且判断用户会不会流失,这是一个分类问题,我们使用决策树建模。

1、安装库

  • 安装以下库:

  • 还需要下载:pydotplus

  • 背景说明:
    通过pip安装了GraphViz模块后,在对着sklearn的决策树文档操作输出决策树模型结果时,报错了:InvocationException: GraphViz’s executables not found

    解决流程:
    1、下载安装GraphViz(这是一个独立软件)
    https://graphviz.gitlab.io/_pages/Download/Download_windows.html

 

 2、下载完后解压缩后,复制bin文件夹的路径。将GraphViz安装目录的bin目录放到环境变量的path路径中

  windows下:

  运行下边这个语句

import os
os.environ["PATH"] += os.pathsep + 'D:/BiShe_Graduate/bin'  #注意修改你的路径

    3、验证是否安装并配置成功。

       win+R输入cmd进入windows命令行界面,输入dot -version,如果显示graphviz的相关版本信息,则安装配置成功

2、开始建模

  step1:写重要的功能函数,便于主函数调用(复用)

import pandas as pd
from sklearn import tree
from sklearn.model_selection import train_test_split
import pydotplus
# from sklearn.externals.six import StringIO
from six import StringIO

 
 
def getDataSet(fileName):
    # 读取数据
    data = pd.read_csv(fileName)
    dataSet = []
    for item in data.values:
        dataSet.append(list(item[:5]))
    label = list(data['K'])
    return dataSet, label
 
 
def divide(dataSet, labels):
    '''
    分类数据,按比例拆开
    :param dataSet:
    :param labels:
    :return:
    '''
    train_data, test_data, train_label, test_label = train_test_split(dataSet, labels, test_size=0.2)
    return train_data, test_data, train_label, test_label
View Code

  step2:导入画图工具GraphViz

import os
os.environ["PATH"] += os.pathsep + 'D:/BiShe_Graduate/bin'  #注意修改你的路径

  step3:主函数

  观察data_standard.csv

  

  • 原数据表的第一列用户编号是用户的唯一标识,然而这对于建模是没有意义的,第一列被剔除。
  • 再选取合适属性构造了需要的六个属性LRFMCK模型,并进行标准化数据,得到data_standard.csv。
if __name__ == '__main__':
    data, label = getDataSet('../extra/data_standard.csv')
    train_data, test_data, train_label, test_label = divide(data, label)
    clf = tree.DecisionTreeClassifier(max_depth=5)
    clf = clf.fit(train_data, train_label)
    # 可视化
    dataLabels = ['ZL', 'ZR', 'ZF', 'ZM', 'ZC', ]
    data_list = []
    data_dict = {}
    for each_label in dataLabels:
        for each in data:
            data_list.append(each[dataLabels.index(each_label)])
        data_dict[each_label] = data_list
        data_list = []
    lenses_pd = pd.DataFrame(data_dict)
    print(lenses_pd.keys())
    dot_data = StringIO()
    tree.export_graphviz(clf, out_file=dot_data, feature_names=lenses_pd.keys(),
                         class_names=clf.classes_, filled=True, rounded=True, special_characters=True)
    graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
    graph.write_pdf("treeRX3035.pdf")
    from cm_plot import *
    cm_plot(test_label, clf.predict(test_data)).show()
    print("混淆矩阵__number3035任")

并生成

 

八、后续处理

  • 通过分类预测现有用户是否流失倾向是无比重要的,发现流失可能性进行相应营销比较必要,这里决策树预测准确率还算可以,在深度合适(发现4-5)预测准确率达到85%左右,对于营销需求是达到了。

九、补充说明

  • 参考书《Python数据分析与挖掘》
  • 本案例使用决策树建模