某航空公司的客户价值分析
根据2/8定律,80%的收益来自于20%的高价值客户,客户价值分析的目的是为了对客户进行分群,然后采取精细化和针对性的经营,将有限的资源运营在高价值客户或者有机会成为高价值客户的客户。
数据集为某航空公司的若干客户信息,数据集包含用户相关信息、用户消费信息以及折扣积分等相关信息,利用这些信息对客户进行分群,并对不同价值的客户进行分析,提出精细化和针对性的运营策略。
原数据链接:https://pan.baidu.com/s/1HHbsYw8DYcNg9RYUTJQNdA 提取码:3hpm
#-*- coding:utf8 -*-
import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
from sklearn import preprocessing
import matplotlib.pyplot as plt
加载相关包
datafile='air_data.csv'
data=pd.read_csv(datafile,encoding='utf-8')
print(data.info())
print('+'*30)
print(data.describe())
print('+'*30)
print(data.head())
print('+'*30)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 62988 entries, 0 to 62987
Data columns (total 44 columns):
MEMBER_NO 62988 non-null int64
FFP_DATE 62988 non-null object
FIRST_FLIGHT_DATE 62988 non-null object
GENDER 62985 non-null object
FFP_TIER 62988 non-null int64
WORK_CITY 60719 non-null object
WORK_PROVINCE 59740 non-null object
WORK_COUNTRY 62962 non-null object
AGE 62568 non-null float64
LOAD_TIME 62988 non-null object
FLIGHT_COUNT 62988 non-null int64
BP_SUM 62988 non-null int64
EP_SUM_YR_1 62988 non-null int64
EP_SUM_YR_2 62988 non-null int64
SUM_YR_1 62437 non-null float64
SUM_YR_2 62850 non-null float64
SEG_KM_SUM 62988 non-null int64
WEIGHTED_SEG_KM 62988 non-null float64
LAST_FLIGHT_DATE 62988 non-null object
AVG_FLIGHT_COUNT 62988 non-null float64
AVG_BP_SUM 62988 non-null float64
BEGIN_TO_FIRST 62988 non-null int64
LAST_TO_END 62988 non-null int64
AVG_INTERVAL 62988 non-null float64
MAX_INTERVAL 62988 non-null int64
ADD_POINTS_SUM_YR_1 62988 non-null int64
ADD_POINTS_SUM_YR_2 62988 non-null int64
EXCHANGE_COUNT 62988 non-null int64
avg_discount 62988 non-null float64
P1Y_Flight_Count 62988 non-null int64
L1Y_Flight_Count 62988 non-null int64
P1Y_BP_SUM 62988 non-null int64
L1Y_BP_SUM 62988 non-null int64
EP_SUM 62988 non-null int64
ADD_Point_SUM 62988 non-null int64
Eli_Add_Point_Sum 62988 non-null int64
L1Y_ELi_Add_Points 62988 non-null int64
Points_Sum 62988 non-null int64
L1Y_Points_Sum 62988 non-null int64
Ration_L1Y_Flight_Count 62988 non-null float64
Ration_P1Y_Flight_Count 62988 non-null float64
Ration_P1Y_BPS 62988 non-null float64
Ration_L1Y_BPS 62988 non-null float64
Point_NotFlight 62988 non-null int64
dtypes: float64(12), int64(24), object(8)
memory usage: 21.1+ MB
None
++++++++++++++++++++++++++++++
MEMBER_NO FFP_TIER AGE FLIGHT_COUNT BP_SUM \
count 62988.000000 62988.000000 62568.000000 62988.000000 62988.000000
mean 31494.500000 4.102162 42.476346 11.839414 10925.081254
std 18183.213715 0.373856 9.885915 14.049471 16339.486151
min 1.000000 4.000000 6.000000 2.000000 0.000000
25% 15747.750000 4.000000 35.000000 3.000000 2518.000000
50% 31494.500000 4.000000 41.000000 7.000000 5700.000000
75% 47241.250000 4.000000 48.000000 15.000000 12831.000000
max 62988.000000 6.000000 110.000000 213.000000 505308.000000
EP_SUM_YR_1 EP_SUM_YR_2 SUM_YR_1 SUM_YR_2 SEG_KM_SUM \
count 62988.0 62988.000000 62437.000000 62850.000000 62988.000000
mean 0.0 265.689623 5355.376064 5604.026014 17123.878691
std 0.0 1645.702854 8109.450147 8703.364247 20960.844623
min 0.0 0.000000 0.000000 0.000000 368.000000
25% 0.0 0.000000 1003.000000 780.000000 4747.000000
50% 0.0 0.000000 2800.000000 2773.000000 9994.000000
75% 0.0 0.000000 6574.000000 6845.750000 21271.250000
max 0.0 74460.000000 239560.000000 234188.000000 580717.000000
... ADD_Point_SUM Eli_Add_Point_Sum L1Y_ELi_Add_Points \
count ... 62988.000000 62988.000000 62988.000000
mean ... 1355.006223 1620.695847 1080.378882
std ... 7868.477000 8294.398955 5639.857254
min ... 0.000000 0.000000 0.000000
25% ... 0.000000 0.000000 0.000000
50% ... 0.000000 0.000000 0.000000
75% ... 0.000000 345.000000 0.000000
max ... 984938.000000 984938.000000 728282.000000
Points_Sum L1Y_Points_Sum Ration_L1Y_Flight_Count \
count 62988.0000 62988.000000 62988.000000
mean 12545.7771 6638.739585 0.486419
std 20507.8167 12601.819863 0.319105
min 0.0000 0.000000 0.000000
25% 2775.0000 700.000000 0.250000
50% 6328.5000 2860.500000 0.500000
75% 14302.5000 7500.000000 0.711111
max 985572.0000 728282.000000 1.000000
Ration_P1Y_Flight_Count Ration_P1Y_BPS Ration_L1Y_BPS \
count 62988.000000 62988.000000 62988.000000
mean 0.513581 0.522293 0.468422
std 0.319105 0.339632 0.338956
min 0.000000 0.000000 0.000000
25% 0.288889 0.258150 0.167954
50% 0.500000 0.514252 0.476747
75% 0.750000 0.815091 0.728375
max 1.000000 0.999989 0.999993
Point_NotFlight
count 62988.000000
mean 2.728155
std 7.364164
min 0.000000
25% 0.000000
50% 0.000000
75% 1.000000
max 140.000000
[8 rows x 36 columns]
++++++++++++++++++++++++++++++
MEMBER_NO FFP_DATE FIRST_FLIGHT_DATE GENDER FFP_TIER WORK_CITY \
0 54993 2006/11/02 2008/12/24 男 6 .
1 28065 2007/02/19 2007/08/03 男 6 NaN
2 55106 2007/02/01 2007/08/30 男 6 .
3 21189 2008/08/22 2008/08/23 男 5 Los Angeles
4 39546 2009/04/10 2009/04/15 男 6 贵阳
WORK_PROVINCE WORK_COUNTRY AGE LOAD_TIME ... \
0 北京 CN 31.0 2014/03/31 ...
1 北京 CN 42.0 2014/03/31 ...
2 北京 CN 40.0 2014/03/31 ...
3 CA US 64.0 2014/03/31 ...
4 贵州 CN 48.0 2014/03/31 ...
ADD_Point_SUM Eli_Add_Point_Sum L1Y_ELi_Add_Points Points_Sum \
0 39992 114452 111100 619760
1 12000 53288 53288 415768
2 15491 55202 51711 406361
3 0 34890 34890 372204
4 22704 64969 64969 338813
L1Y_Points_Sum Ration_L1Y_Flight_Count Ration_P1Y_Flight_Count \
0 370211 0.509524 0.490476
1 238410 0.514286 0.485714
2 233798 0.518519 0.481481
3 186100 0.434783 0.565217
4 210365 0.532895 0.467105
Ration_P1Y_BPS Ration_L1Y_BPS Point_NotFlight
0 0.487221 0.512777 50
1 0.489289 0.510708 33
2 0.481467 0.518530 26
3 0.551722 0.448275 12
4 0.469054 0.530943 39
[5 rows x 44 columns]
++++++++++++++++++++++++++++++
加载数据,观察数据,可发现数据包含很多类型,有类别属性、数值属性、时间属性,有些属性是唯一识别属性,各属性观测值的范围也不相同,差别很大,有些属性还存在缺失值。
explore=data.describe().T
explore['null']=len(data)-explore['count']
explore=explore[['null','max','min']]
explore.rename(columns={i:j for i,j in zip(['null','max','min'],[u'空值数',u'最大值',u'最小值'])},inplace=True)
print(explore)
空值数 最大值 最小值
MEMBER_NO 0.0 62988.000000 1.00
FFP_TIER 0.0 6.000000 4.00
AGE 420.0 110.000000 6.00
FLIGHT_COUNT 0.0 213.000000 2.00
BP_SUM 0.0 505308.000000 0.00
EP_SUM_YR_1 0.0 0.000000 0.00
EP_SUM_YR_2 0.0 74460.000000 0.00
SUM_YR_1 551.0 239560.000000 0.00
SUM_YR_2 138.0 234188.000000 0.00
SEG_KM_SUM 0.0 580717.000000 368.00
WEIGHTED_SEG_KM 0.0 558440.140000 0.00
AVG_FLIGHT_COUNT 0.0 26.625000 0.25
AVG_BP_SUM 0.0 63163.500000 0.00
BEGIN_TO_FIRST 0.0 729.000000 0.00
LAST_TO_END 0.0 731.000000 1.00
AVG_INTERVAL 0.0 728.000000 0.00
MAX_INTERVAL 0.0 728.000000 0.00
ADD_POINTS_SUM_YR_1 0.0 600000.000000 0.00
ADD_POINTS_SUM_YR_2 0.0 728282.000000 0.00
EXCHANGE_COUNT 0.0 46.000000 0.00
avg_discount 0.0 1.500000 0.00
P1Y_Flight_Count 0.0 118.000000 0.00
L1Y_Flight_Count 0.0 111.000000 0.00
P1Y_BP_SUM 0.0 246197.000000 0.00
L1Y_BP_SUM 0.0 259111.000000 0.00
EP_SUM 0.0 74460.000000 0.00
ADD_Point_SUM 0.0 984938.000000 0.00
Eli_Add_Point_Sum 0.0 984938.000000 0.00
L1Y_ELi_Add_Points 0.0 728282.000000 0.00
Points_Sum 0.0 985572.000000 0.00
L1Y_Points_Sum 0.0 728282.000000 0.00
Ration_L1Y_Flight_Count 0.0 1.000000 0.00
Ration_P1Y_Flight_Count 0.0 1.000000 0.00
Ration_P1Y_BPS 0.0 0.999989 0.00
Ration_L1Y_BPS 0.0 0.999993 0.00
Point_NotFlight 0.0 140.000000 0.00
统计所有属性观测值的空值数、最大值和最小值,可以更详细的观测各个属性,可发现共有三个属性存在缺失值,这些缺失值个数占总体观测值比率很小。
data=data[data['SUM_YR_1'].notnull()&data['SUM_YR_2'].notnull()]
data['SUM_YR']=data['SUM_YR_1']+data['SUM_YR_2']
index1=data['SUM_YR'] !=0
index2=(data['SEG_KM_SUM']==0)&(data['avg_discount']==0)
data=data[index1|index2]
数据清理:票价为空或者票价为0&折扣率为0&总飞行里程数大于0的记录都是一些异常记录,会对后面的分析产生不利影响,这里需要删除。
features=['LOAD_TIME','FFP_DATE','LAST_TO_END','FLIGHT_COUNT','SEG_KM_SUM','avg_discount']
data_choiced=data[features]
print(data_choiced.head())
LOAD_TIME FFP_DATE LAST_TO_END FLIGHT_COUNT SEG_KM_SUM avg_discount
0 2014/03/31 2006/11/02 1 210 580717 0.961639
1 2014/03/31 2007/02/19 7 140 293678 1.252314
2 2014/03/31 2007/02/01 11 135 283712 1.254676
3 2014/03/31 2008/08/22 97 23 281336 1.090870
4 2014/03/31 2009/04/10 5 152 309928 0.970658
这一步是特征选择,根据RFM模型和航空公司用户指标的特殊性,这里选择用户飞行总时间、最近一次飞行距观测窗口结束的时间、总飞行次数、总飞行里程数、平均折扣率这些属性作为指标进行后面的数据挖掘和分析,这里因为已有的属性作为指标已经足够进行后面的分析,这里就不再构建指标了。
train_x=pd.DataFrame()
train_x['总时间']=(pd.to_datetime(data_choiced.iloc[:,0])-pd.to_datetime(data_choiced.iloc[:,1]))/np.timedelta64(1,'D')
train_x['最近一次']=data_choiced.iloc[:,2]
train_x['次数']=data_choiced.iloc[:,3]
train_x['里程数']=data_choiced.iloc[:,4]
train_x['折扣率']=data_choiced.iloc[:,5]
print(train_x.head())
def train_x_show(raw_data):
fig=plt.figure(figsize=(25,25))
for i in range(5):
ax=fig.add_subplot(3,2,i+1)
ax.hist(raw_data.iloc[:,i],bins=100)
ax.set_xlabel(raw_data.columns[i],fontsize=30)
ax.set_ylabel(u'频率',fontsize=30)
plt.xticks(fontsize=30)
plt.yticks(fontsize=30)
ax=fig.add_subplot(3,2,6)
ax.hist(raw_data['里程数']/raw_data['次数'],bins=100)
ax.set_xlabel('平均里程数',fontsize=30)
ax.set_ylabel(u'频率',fontsize=30)
plt.xticks(fontsize=30)
plt.yticks(fontsize=30)
plt.rcParams['font.sans-serif']=['Simhei']
plt.show()
train_x_show(train_x)
总时间 最近一次 次数 里程数 折扣率
0 2706.0 1 210 580717 0.961639
1 2597.0 7 140 293678 1.252314
2 2615.0 11 135 283712 1.254676
3 2047.0 97 23 281336 1.090870
4 1816.0 5 152 309928 0.970658
对数据进行处理,这里可以看出大部分客户的飞行次数、飞行里程数都是比较小的,这也符合了2/8定律。
std_scaler=preprocessing.StandardScaler()
train_x=std_scaler.fit_transform(train_x)
print(train_x)
plt.hist(train_x)
plt.show()
[[ 1.43571897 -0.94495516 14.03412875 26.76136996 1.29555058]
[ 1.30716214 -0.9119018 9.07328567 13.1269701 2.86819902]
[ 1.32839171 -0.88986623 8.71893974 12.65358345 2.88097321]
...
[-0.14942206 -0.73561725 -0.70666211 -0.77233818 -2.68990622]
[-1.20618274 1.6056619 -0.70666211 -0.77984321 -2.55464809]
[-0.47965977 0.60304353 -0.70666211 -0.78668323 -2.39233833]]
对数据进行标准化处理,这里选择标准差标准化。
def model_test(data_,start=2,stop=10):
SSE_table=pd.DataFrame()
numb,SSE=[],[]
for i in range(start,stop):
kmeans_model=KMeans(n_clusters=i,n_jobs=4)
kmeans_model.fit(data_)
predict_y=kmeans_model.predict(data_)
predict_vector=[]
for j in predict_y:
predict_vector.append(kmeans_model.cluster_centers_[j])
sse=np.sum(np.power(data_-predict_vector,2))
numb.append(i)
SSE.append(sse)
SSE_table['簇数']=numb
SSE_table['SSE']=SSE
return SSE_table
SSE_table=model_test(train_x,2,10)
这里用K-Means进行聚类分析,一般客户分群k值选取为4,5个左右,不易过多,这里采用计算SSE的方法,尝试找到最好的K数值。
print(SSE_table)
SSE_table.plot(x='簇数',y='SSE')
簇数 SSE
0 2 229348.669720
1 3 184346.806281
2 4 151013.114090
3 5 133362.301139
4 6 118069.047171
5 7 107635.877614
6 8 100707.980570
7 9 94069.592981
<matplotlib.axes._subplots.AxesSubplot at 0x1b5b16a3a58>
可以看出当簇数为4时出现了拐点,由于k值选择不易过大,下面选k为4和5进行分析。
kmeans_model=KMeans(n_clusters=4,n_jobs=4)
kmeans_model.fit(train_x)
predict_y=kmeans_model.predict(train_x)
kmeans_model2=KMeans(n_clusters=5,n_jobs=4)
kmeans_model2.fit(train_x)
predict_y2=kmeans_model2.predict(train_x)
训练模型,得出各用户所属群的类别。
labels=np.array([u'总时间',u'最近一次',u'次数',u'里程数',u'折扣率'])
def spide(x,n,num):
fig=plt.figure(figsize=(5,5))
ax=fig.add_subplot(1,1,1,polar=True)
for i in range(n):
angles=np.linspace(0,2*np.pi,len(labels),endpoint=False)
stats=x.iloc[i,:]
stats=np.concatenate((stats,[stats[0]]))
angles=np.concatenate((angles,[angles[0]]))
ax.plot(angles,stats,'o-',linewidth=2, label = '类%d:%d'%(i,num[i]))
ax.fill(angles,stats,alpha=0.25)
ax.set_thetagrids(angles * 180/np.pi,labels, fontsize=15)
ax.set_xlabel('%d类特征分析图'%n, fontsize=20)
plt.legend(loc='upper right', bbox_to_anchor=(1.4,1.0),ncol=1,fancybox=True,shadow=True)
plt.show()
r2=pd.DataFrame(kmeans_model.cluster_centers_)
num2=pd.Series(kmeans_model.labels_).value_counts()
r2_new=pd.concat([r2,num2],axis=1)
r2_new.columns=list(labels)+[u'类别数量']
print(r2_new)
print('+'*30)
r3=pd.DataFrame(kmeans_model2.cluster_centers_)
num3=pd.Series(kmeans_model2.labels_).value_counts()
r3_new=pd.concat([r3,num3],axis=1)
r3_new.columns=list(labels)+[u'类别数量']
print(r3_new)
print('+'*30)
spide(r2,4,num2)
spide(r3,5,num3)
总时间 最近一次 次数 里程数 折扣率 类别数量
0 -0.696202 -0.408644 -0.169954 -0.170097 -0.133801 26277
1 -0.311455 1.659763 -0.572081 -0.535889 -0.020545 12925
2 1.138629 -0.364145 -0.098711 -0.107696 0.095677 17258
3 0.478723 -0.795991 2.430013 2.374625 0.381567 5584
++++++++++++++++++++++++++++++
总时间 最近一次 次数 里程数 折扣率 类别数量
0 -0.700212 -0.414892 -0.161144 -0.160959 -0.255134 24659
1 0.483332 -0.799390 2.483222 2.424743 0.308633 5336
2 -0.313681 1.686272 -0.574021 -0.536825 -0.173328 12125
3 0.051843 -0.002668 -0.226805 -0.231256 2.191365 4184
4 1.160676 -0.377224 -0.086919 -0.094845 -0.155906 15740
++++++++++++++++++++++++++++++
这里可以看出,
当k值取4时,可以看出各群的特征还是比较明显的,类2次数和里程数都较大,数量较少,显然是高价值用户群,类1显然是低价值用户群,数目也较大,类3是老客户,可以看出这一部分客户正在流失,需要采取手段降低流失率,类0是飞行里程和次数都很少的顾客,且最近一次乘机时间距观察窗口末也较长,为低价值客户。
当k值取5时,可以看出它包括k取4值的用户群而且它又多出来一个群类3,这个群可以看做是由类0分出来的群,可以发现这个群享受的折扣率较高,是个挺有意思的发现,有些用户可能在有折扣的时候才会偶尔消费。
综上,k值取5比较合理。其中:
类1里程数和乘机次数都较高,为重要保留客户,应该把最主要的精力和资源放在和他们保持关系上。
类4为总乘机时间长,但是观测窗口内的里程数和乘机次数都较少,可能是正在流失的老客户,可以对他们进行一些调查,找出流失的原因,这些原因可能和航空公司某些的不足和失误有关,都蕴含着巨大价值,对这些客户采取一些手段,增加他们的生命周期。
类0是可能是最近有过乘机的新客户,而且这一人数很大,是重要的发展客户,也是运营团队未来要加大努力的方向。
类3也可能是最近有过乘机的新客户,但是他们的折扣率都较大,可以考虑在淡季时增加对其的折扣促进其消费。
类2是低价值客户。