一. 业务目标

  1. 借助航空公司的客户数据,对客户进行分类
  2. 对不同的客户类别进行特征分析,比较不同类客户的客户价值
  3. 对不同价值的客户类别提供个性化服务,制定相应的营销策略

二. 已有数据

  1. 客户基本信息:会员卡号、入会时间、第一次飞行日期、性别、年龄、会员卡级别、工作地城市、工作地所在省份、工作地所在国家
  2. 乘机信息:周期内的飞行次数、周期的结束时间、最后一次乘机时间到此周期结束的时长、平均折扣率、周期内的票价收入、周期内的总飞行公里数、末次飞行日期、平均乘机间隔时间、最大乘机间隔
  3. 积分信息:积分兑换次数,总精英积分、促销积分、合作伙伴积分、总累计积分、非乘机的积分变动次数、总基本积分

三. 分析思路

分析客户价值的标准模型师RFM模型,参见:http://baike.baidu.com/link?url=KZP2fpmmzOhzrGM_rHN2SSFBo4ilAbvLCgqS8P1QQQo5vlO7xIY0TedyRzLu7V4nCSXyeD4TodSAlqyFDypNTa 即:R:最近一次消费,F:消费频率,M:消费金额 考虑到航空公司的业务特征:

  1. 同样的消费金额,客户价值可能不同。比如说长航线经济舱的客户跟短航线头等舱的客户虽然金额一样,单后者显然更有价值。所以我们需要考虑客户的里程跟机票的折扣。
  2. 会员时间越长,越忠诚,价值越高。所以要考虑是不是会员以及入会时间。

所以最终设计从5个指标来评价客户价值:

  • L:会员入会时间距观测窗口结束时的月数
  • R:客户最后一次乘坐公司飞机距观测窗口结束的月数
  • F:客户在观测周期内乘坐公司飞机的次数
  • M:客户在观测窗口内累计的飞行里程
  • C:客户在观测窗口内乘坐舱位所对应的折扣系数均值

四. 算法选择

选择k-means聚类算法

五. 挖掘过程

1.数据抽取

选取一段时间内的数据,比如2012年到2014年两年的数据,作为分析样本。将客户基本信息,乘机信息,积分信息的44个属性形成一张大宽表,即air_data.csv。

2.数据探索

对原始数据进行初步的感知。识别出有没有缺失值和明显的异常值。

In [1]:

import pandas as pd

In [2]:

datafile = '/Users/frontc/book/ppdam/air_data.csv'

In [3]:

data = pd.read_csv(datafile,encoding='utf-8')

In [4]:

len(data)

Out[4]:

62988

In [5]:

data.describe().T

Out[5]:

count mean std min 25% 50% 75% max
MEMBER_NO 62988.0 31494.500000 18183.213715 1.00 15747.750000 31494.500000 47241.250000 62988.000000
FFP_TIER 62988.0 4.102162 0.373856 4.00 4.000000 4.000000 4.000000 6.000000
AGE 62568.0 42.476346 9.885915 6.00 NaN NaN NaN 110.000000
FLIGHT_COUNT 62988.0 11.839414 14.049471 2.00 3.000000 7.000000 15.000000 213.000000
BP_SUM 62988.0 10925.081254 16339.486151 0.00 2518.000000 5700.000000 12831.000000 505308.000000
EP_SUM_YR_1 62988.0 0.000000 0.000000 0.00 0.000000 0.000000 0.000000 0.000000
EP_SUM_YR_2 62988.0 265.689623 1645.702854 0.00 0.000000 0.000000 0.000000 74460.000000
SUM_YR_1 62437.0 5355.376064 8109.450147 0.00 NaN NaN NaN 239560.000000
SUM_YR_2 62850.0 5604.026014 8703.364247 0.00 NaN NaN NaN 234188.000000
SEG_KM_SUM 62988.0 17123.878691 20960.844623 368.00 4747.000000 9994.000000 21271.250000 580717.000000
WEIGHTED_SEG_KM 62988.0 12777.152439 17578.586695 0.00 3219.045000 6978.255000 15299.632500 558440.140000
AVG_FLIGHT_COUNT 62988.0 1.542154 1.786996 0.25 0.428571 0.875000 1.875000 26.625000
AVG_BP_SUM 62988.0 1421.440249 2083.121324 0.00 336.000000 752.375000 1690.270833 63163.500000
BEGIN_TO_FIRST 62988.0 120.145488 159.572867 0.00 9.000000 50.000000 166.000000 729.000000
LAST_TO_END 62988.0 176.120102 183.822223 1.00 29.000000 108.000000 268.000000 731.000000
AVG_INTERVAL 62988.0 67.749788 77.517866 0.00 23.370370 44.666667 82.000000 728.000000
MAX_INTERVAL 62988.0 166.033895 123.397180 0.00 79.000000 143.000000 228.000000 728.000000
ADD_POINTS_SUM_YR_1 62988.0 540.316965 3956.083455 0.00 0.000000 0.000000 0.000000 600000.000000
ADD_POINTS_SUM_YR_2 62988.0 814.689258 5121.796929 0.00 0.000000 0.000000 0.000000 728282.000000
EXCHANGE_COUNT 62988.0 0.319775 1.136004 0.00 0.000000 0.000000 0.000000 46.000000
avg_discount 62988.0 0.721558 0.185427 0.00 0.611997 0.711856 0.809476 1.500000
P1Y_Flight_Count 62988.0 5.766257 7.210922 0.00 2.000000 3.000000 7.000000 118.000000
L1Y_Flight_Count 62988.0 6.073157 8.175127 0.00 1.000000 3.000000 8.000000 111.000000
P1Y_BP_SUM 62988.0 5366.720550 8537.773021 0.00 946.000000 2692.000000 6485.250000 246197.000000
L1Y_BP_SUM 62988.0 5558.360704 9351.956952 0.00 545.000000 2547.000000 6619.250000 259111.000000
EP_SUM 62988.0 265.689623 1645.702854 0.00 0.000000 0.000000 0.000000 74460.000000
ADD_Point_SUM 62988.0 1355.006223 7868.477000 0.00 0.000000 0.000000 0.000000 984938.000000
Eli_Add_Point_Sum 62988.0 1620.695847 8294.398955 0.00 0.000000 0.000000 345.000000 984938.000000
L1Y_ELi_Add_Points 62988.0 1080.378882 5639.857254 0.00 0.000000 0.000000 0.000000 728282.000000
Points_Sum 62988.0 12545.777100 20507.816700 0.00 2775.000000 6328.500000 14302.500000 985572.000000
L1Y_Points_Sum 62988.0 6638.739585 12601.819863 0.00 700.000000 2860.500000 7500.000000 728282.000000
Ration_L1Y_Flight_Count 62988.0 0.486419 0.319105 0.00 0.250000 0.500000 0.711111 1.000000
Ration_P1Y_Flight_Count 62988.0 0.513581 0.319105 0.00 0.288889 0.500000 0.750000 1.000000
Ration_P1Y_BPS 62988.0 0.522293 0.339632 0.00 0.258150 0.514252 0.815091 0.999989
Ration_L1Y_BPS 62988.0 0.468422 0.338956 0.00 0.167954 0.476747 0.728375 0.999993
Point_NotFlight 62988.0 2.728155 7.364164 0.00 0.000000 0.000000 1.000000 140.000000

从上面可以看到,总样本数为62988,但是部分字段的count值小于62988,说明此字段存在缺失值。看到字段的最小值和最大值,比如票价的最小值、折扣率、里程的最小值存在为0的。 所以接下来的数据清洗阶段需要处理这些异常值和缺失值,以免干扰到正常结果。

3.数据预处理
  • 数据清洗 在数据探索过程中发现了,有缺失值和票价为0、折扣率为0、总飞行公里数为0的记录,考虑到样本总数比较多,这类数据占比比较小,所以决定对他们进行丢弃处理,具体处理策略:丢弃票价为空的记录,丢弃票价为0但是折扣率不为0总飞行里程数也不为0的记录。

In [6]:

data = data[data['SUM_YR_1'].notnull()&data['SUM_YR_2'].notnull()]  # 丢弃票价为空的记录

In [7]:

index1 = data['SUM_YR_1']!=0

In [8]:

index2 = data['SUM_YR_2']!=0

In [9]:

index3 = (data['avg_discount']==0) & (data['SEG_KM_SUM']==0)

In [10]:

data = data[index1 | index2 | index3 ]  # 其实这里更符合自然语义的是做差集。暂时没找到dataframe做差集的方式

此时数据已经是经过清洗的了。接下来是做属性规约,也就是去除不相关的、弱相关的或冗余的列.

  • 属性规约 根据前期确定的lrfmc五个指标,我们保留有用的字段:MEMBER_NO是主键,ffp_date入会时间,load_time观测窗口的结束时间,last_to_end最后一次乘机时间至观测窗口结束的时长,FLIGHT_COUNT观测窗口内的飞行次数,SEG_KM_SUM观测窗口内的总里程数,avg_discount折扣系数

In [11]:

data.columns

Out[11]:

Index([u'MEMBER_NO', u'FFP_DATE', u'FIRST_FLIGHT_DATE', u'GENDER', u'FFP_TIER',
u'WORK_CITY', u'WORK_PROVINCE', u'WORK_COUNTRY', u'AGE', u'LOAD_TIME',
u'FLIGHT_COUNT', u'BP_SUM', u'EP_SUM_YR_1', u'EP_SUM_YR_2', u'SUM_YR_1',
u'SUM_YR_2', u'SEG_KM_SUM', u'WEIGHTED_SEG_KM', u'LAST_FLIGHT_DATE',
u'AVG_FLIGHT_COUNT', u'AVG_BP_SUM', u'BEGIN_TO_FIRST', u'LAST_TO_END',
u'AVG_INTERVAL', u'MAX_INTERVAL', u'ADD_POINTS_SUM_YR_1',
u'ADD_POINTS_SUM_YR_2', u'EXCHANGE_COUNT', u'avg_discount',
u'P1Y_Flight_Count', u'L1Y_Flight_Count', u'P1Y_BP_SUM', u'L1Y_BP_SUM',
u'EP_SUM', u'ADD_Point_SUM', u'Eli_Add_Point_Sum',
u'L1Y_ELi_Add_Points', u'Points_Sum', u'L1Y_Points_Sum',
u'Ration_L1Y_Flight_Count', u'Ration_P1Y_Flight_Count',
u'Ration_P1Y_BPS', u'Ration_L1Y_BPS', u'Point_NotFlight'],
dtype='object')

In [12]:

data = data.loc[:,['MEMBER_NO','FFP_DATE','LOAD_TIME','LAST_TO_END','FLIGHT_COUNT','SEG_KM_SUM','avg_discount']]

In [13]:

data[:10]

Out[13]:

MEMBER_NO FFP_DATE LOAD_TIME LAST_TO_END FLIGHT_COUNT SEG_KM_SUM avg_discount
0 54993 2006/11/02 2014/03/31 1 210 580717 0.961639
1 28065 2007/02/19 2014/03/31 7 140 293678 1.252314
2 55106 2007/02/01 2014/03/31 11 135 283712 1.254676
3 21189 2008/08/22 2014/03/31 97 23 281336 1.090870
4 39546 2009/04/10 2014/03/31 5 152 309928 0.970658
5 56972 2008/02/10 2014/03/31 79 92 294585 0.967692
6 44924 2006/03/22 2014/03/31 1 101 287042 0.965347
7 22631 2010/04/09 2014/03/31 3 73 287230 0.962070
8 32197 2011/06/07 2014/03/31 6 56 321489 0.828478
9 31645 2010/07/05 2014/03/31 15 64 375074 0.708010
  • 数据变换 将数据加工为制定的五个指标,L,R,F,M,C

In [14]:

import datetime

In [15]:

data['FFP_DATE']=data['FFP_DATE'].map(lambda x:datetime.datetime.strptime(x,'%Y/%m/%d'))

In [16]:

data['LOAD_TIME']=data['LOAD_TIME'].map(lambda x:datetime.datetime.strptime(x,'%Y/%m/%d'))

In [17]:

data['L'] = data['LOAD_TIME']-data['FFP_DATE']

In [18]:

data['L'] = data['L'].map(lambda x: x.days/30.0 ) # 会员加入的时长,单位是月

In [19]:

data['R'] = data['LAST_TO_END']/30.0  # 最近一次乘坐飞机,转换到月

In [20]:

data['F'] = data['FLIGHT_COUNT']

In [21]:

data['M'] = data['SEG_KM_SUM']

In [22]:

data['C'] = data['avg_discount']

In [23]:

data = data.loc[:,['MEMBER_NO','L','R','F','M','C']]

In [24]:

data.describe().T

Out[24]:

count mean std min 25% 50% 75% max
MEMBER_NO 62044.0 31485.237928 18188.650537 1.000000 15715.750000 31476.500000 47247.250000 62988.000000
L 62044.0 49.623036 28.262697 12.166667 24.500000 42.600000 72.733333 114.566667
R 62044.0 5.751090 6.050872 0.033333 0.966667 3.500000 8.666667 24.366667
F 62044.0 11.971359 14.110619 2.000000 3.000000 7.000000 15.000000 213.000000
M 62044.0 17321.694749 21052.728111 368.000000 4874.000000 10200.000000 21522.500000 580717.000000
C 62044.0 0.722180 0.184833 0.136017 0.613085 0.712162 0.809293 1.500000

In [28]:

outfile = '/Users/frontc/book/ppdam/air_data2.xls'
data.to_excel(outfile,index=False)

可以看见这5个指标的量级不在一个量级,为了消除影响,需要对数据进行标准化处理

In [38]:

data = pd.read_excel(outfile,index_col='MEMBER_NO')

In [39]:

data[:3]

Out[39]:

L R F M C
MEMBER_NO
54993 90.200000 0.033333 210 580717 0.961639
28065 86.566667 0.233333 140 293678 1.252314
55106 87.166667 0.366667 135 283712 1.254676

In [40]:

data = (data - data.mean(axis=0)) / (data.std(axis=0))  # 零均值规范化:即均值为0,标准差为1,axis=0是指定操作的轴

In [41]:

data[:3]

Out[41]:

L R F M C
MEMBER_NO
54993 1.435707 -0.944948 14.034016 26.761154 1.295540
28065 1.307152 -0.911894 9.073213 13.126864 2.868176
55106 1.328381 -0.889859 8.718869 12.653481 2.880950

完成了数据预处理之后,接下来进入模型构建阶段

4. 模型构建
  • 使用kmeans聚类算法对客户数据进行客户分群

In [42]:

from sklearn.cluster import KMeans

In [43]:

kmodel = KMeans(n_clusters=5,n_jobs=4)  # 构建模型

In [44]:

kmodel.fit(data)  # 训练

Out[44]:

KMeans(copy_x=True, init='k-means++', max_iter=300, n_clusters=5, n_init=10,
n_jobs=4, precompute_distances='auto', random_state=None, tol=0.0001,
verbose=0)

In [45]:

kmodel.cluster_centers_  # 查看聚类的中心

Out[45]:

array([[-0.0059132 ,  0.00754052, -0.25161715, -0.26149989,  2.06062997],
[-0.70046128, -0.41708668, -0.15794723, -0.15693023, -0.2727363 ],
[ 1.16410406, -0.37863743, -0.08493981, -0.09262803, -0.16076185],
[ 0.48514132, -0.79994039, 2.4835996 , 2.42466864, 0.31484279],
[-0.31010163, 1.69251936, -0.57467543, -0.53661368, -0.18792669]])

In [53]:

r=pd.concat([data,pd.Series(kmodel.labels_,index=data.index)],axis=1)  # 将原始数据与聚类训练得到的类别拼接,用index连接

In [54]:

r.columns=list(data.columns)+[u'聚类类别']  # 给新增加的聚类类别列加个列名

In [55]:

r[:10]

Out[55]:

L R F M C 聚类类别
MEMBER_NO
54993 1.435707 -0.944948 14.034016 26.761154 1.295540 3
28065 1.307152 -0.911894 9.073213 13.126864 2.868176 3
55106 1.328381 -0.889859 8.718869 12.653481 2.880950 3
21189 0.658476 -0.416098 0.781585 12.540622 1.994714 3
39546 0.386032 -0.922912 9.923636 13.898736 1.344335 3
56972 0.887281 -0.515257 5.671519 13.169947 1.328291 3
44924 1.701075 -0.944948 6.309337 12.811656 1.315599 3
22631 -0.043274 -0.933930 4.325015 12.820586 1.297873 3
32197 -0.543344 -0.917403 3.120249 14.447881 0.575103 3
31645 -0.145883 -0.867824 3.687198 16.993157 -0.076664 3

In [56]:

r.to_excel('/Users/frontc/book/ppdam/air_data3.xls')  # 输出到文件中

  • 客户特征分析

In [68]:

r.groupby([u'聚类类别'])['L','R','F','M','C'].mean()

Out[68]:

L R F M C
聚类类别
0 -0.001236 0.007194 -0.250158 -0.260104 2.071453
1 -0.700570 -0.416815 -0.158244 -0.157254 -0.271261
2 1.163792 -0.378502 -0.085133 -0.092826 -0.160045
3 0.484903 -0.799925 2.483400 2.424450 0.314712
4 -0.310388 1.691860 -0.574608 -0.536513 -0.186893

通过每个类别的均值去分析一下每个类别的特点。 第一类优势是在c,说明消费价格不敏感。但是入会时间短,里程和频率低,但是很长时间没坐过了。所以属于高潜力,需挽留的客户。定位:重要挽留客户 第二类无优势项目,定位:低价值客户 第三类入会时间最长,但贡献较低,定位:一般客户 第四类入会时间较长,里程数,频率,折扣系数均较好,而且最近还乘坐过。定位:重要保持客户 第五类刚刚入会,乘坐有一段时间了,频率笑历程笑,系数也很低,说明这是打折机票才会去乘坐的客户。定位:低价值客户 下面通过雷达图的形式展示特征。其中雷达图代码借鉴:http://www.cnblogs.com/hhh5460/p/4361610.html

In [109]:

import numpy as np
import matplotlib.pyplot as plt
def radar_plot(data):
#=======自己设置开始============
#标签
labels = np.array(['L','R','F','M','C'])
#数据个数
dataLenth = 5
#========自己设置结束============
angles = np.linspace(0, 2*np.pi, dataLenth, endpoint=False)
data = np.concatenate((data, [data[0]])) # 闭合
angles = np.concatenate((angles, [angles[0]])) # 闭合
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)# polar参数!!
ax.plot(angles, data, 'bo-', linewidth=2)# 画线
ax.fill(angles, data, facecolor='r', alpha=0.25)# 填充
ax.set_thetagrids(angles * 180/np.pi, labels, fontproperties="SimHei")
ax.set_title('radar', va='bottom', fontproperties="SimHei")
ax.set_rlim(-3,3)
ax.grid(True)
plt.show()

In [110]:

for i in r.groupby([u'聚类类别'])['L','R','F','M','C'].mean().as_matrix():
radar_plot(i)

img

img

img

img

img

img