用Python进行航空公司客户价值分析(数据分析)
学习资料:
参考图书:《Python数据分析与挖掘实战》(机械工业出版社)第7章
参考博文:https://blog.csdn.net/a857553315/article/details/79177524
https://www.kesci.com/home/project/5a71818f94eaea7410491462
目标
书中介绍了背景,要求根据背景通过数据挖掘实现如下目标:
- 借助航空公司客户数据,对客户进行分类
- 对不同的客户类别进行特征分析,比较不同客户的客户价值
- 对不同价值的客户类别提供个性化服务,制定相应的营销策略
分析方法和过程
FRM模型(Frequency Recency Monetary)
F:消费频率
R:最近消费时间间隔
M:消费金额
传统的RFM模型分析的属性分箱方法,如图所示。它是依据属性的平均值进行划分,其中大于平均值得表示为向上的箭头(↑),小于平均值的表示为向下的箭头(↓),虽然也能够识别出最具有价值的客户,但是细分的客户群太多,提高了针对性营销的成本。
书中采用了LRFMC模型的五个指标进行K-Means聚类,识别出最有价值的客户。
L:客户关系长度
R:消费时间间隔
F:消费频率
M:飞行里程
C:平均值
为什么选择这五个指标呢?
- 选择客户在一定时间内积累的飞行里程M和客户在一定时间内乘坐舱位所对应的折扣系数的平均值C两个指标代替了消费金额。
- 考虑航空公司会员入会时间的长短在一定程度上能够影响客户价值,所以在模型中增加客户关系长度L,作为区分客户的另一指标。
指标含义:
模型 | L | R | F | M | C |
---|---|---|---|---|---|
LRFMC模型 | 会员入会时间距观测窗口结束的月数 | 客户最近一次乘坐公司飞机距观测窗口结束的月数 | 客户在观测窗口内乘坐公司飞机的次数 | 客户在观测窗口内累计的飞行里程 | 客户在观测窗口内乘坐舱位所对应的折扣系统的平均值 |
分析步骤:
(1)从航空公司的数据源中进行选择性抽取与新增数据抽取分别形成历史数据和增量数据;
(2)对步骤(1)中形成的两个数据集进行数据探索分析(EDA)与预处理,包括数据缺失值与异常值的探索分析,数据的属性规约、清洗和变换。
(3)对步骤(2)中形成的已完成数据预处理的建模数据,基于旅客价值LRFMC模型进行客户分群,对各个客户群进行特征分析,识别出有价值的客户;
(4)针对模型结果得到不同价值的客户,采用不同的营销手段,提供定制化的服务。
数据探索分析(EDA)实施
省略一些代码……
explore = data.describe(percentiles = [], include = 'all').T #包括对数据的基本描述,percentiles参数是指定计算多少的分位数表(如1/4分位数、中位数等);T是转置,转置后更方便查阅
explore['null'] = len(data)-explore['count'] #describe()函数自动计算非空值数,需要手动计算空值数
explore = explore[['null', 'max', 'min']]
explore.columns = [u'空值数', u'最大值', u'最小值'] #表头重命名
'''这里只选取部分探索结果。
describe()函数自动计算的字段有count(非空值数)、unique(唯一值数)、top(频数最高者)、freq(最高频数)、mean(平均值)、std(方差)、min(最小值)、50%(中位数)、max(最大值)'''
explore.to_excel(resultfile) #导出结果
接下来进行数据预处理。
数据预处理
采用数据清洗、属性规约与数据变换等预处理方法。
通过EDA分析,发现数据中存在缺失值,票价最小值为0,折扣率最小值为0,总飞行公里数大于0的记录。
由于原始数据量大,这类数据所占据比例较小,对于问题影响不大,因此对其进行丢弃处理。
具体方法如下:
- 丢弃票价为空的记录
- 丢弃票价为0、平均折扣率不为0、总飞行公里数大于0的记录
通过观测可知,数据集中存在票价为零但是飞行公里大于零的不合理值,但是所占比例较小,这里直接删去
只保留票价非零的,或者平均折扣率与总飞行公里数同时为0的记录。
删除后剩余的样本值是62044个,可见异常样本的比例不足1.5%,因此不会对分析结果产生较大的影响。
属性规约
选择与LRFMC指标相关的6个属性。删除与其不相关、弱相关或者冗余的属性。
原始数据集的特征属性太多,而且各属性不具有降维的特征,故这里选取几个对航空公司来说比较有价值的几个特征进行分析,这里并没有完全按照书中的做法选取特征,最终选取的特征是第一年总票价、第二年总票价、观测窗口总飞行公里数、飞行次数、平均乘机时间间隔、观察窗口内最大乘机间隔、入会时间、观测窗口的结束时间、平均折扣率这八个特征。下面说明这么选的理由:
- 选取的特征是第一年总票价、第二年总票价、观测窗口总飞行公里数是要计算平均飞行每公里的票价,因为对于航空公司来说并不是票价越高,飞行公里数越长越能创造利润,相反而是那些近距离的高等舱的客户创造更大的利益。
- 当然总飞行公里数、飞行次数也都是评价一个客户价值的重要的指标
- 入会时间可以看出客户是不是老用户及忠诚度
- 通过平均乘机时间间隔、观察窗口内最大乘机间隔可以判断客户的乘机频率是不是固定
- 平均折扣率可以反映出客户给公里带来的利益,毕竟来说越是高价值的客户享用的折扣率越高
对特征进行变换:
由于不同的属性相差范围较大,这里进行标准化处理
对于K-Means方法,k的取值是一个难点,因为是无监督的聚类分析问题,所以不寻在绝对正确的值,需要进行研究试探。这里采用计算SSE的方法,尝试找到最好的K数值。编写函数如下:
def distEclud(vecA, vecB):
"""
计算两个向量的欧式距离的平方,并返回
"""
return np.sum(np.power(vecA - vecB, 2))
def test_Kmeans_nclusters(data_train):
"""
计算不同的k值时,SSE的大小变化
"""
data_train = data_train.values
nums=range(2,10)
SSE = []
for num in nums:
sse = 0
kmodel = KMeans(n_clusters=num, n_jobs=4)
kmodel.fit(data_train)
# 簇中心
cluster_ceter_list = kmodel.cluster_centers_
# 个样本属于的簇序号列表
cluster_list = kmodel.labels_.tolist()
for index in range(len(data)):
cluster_num = cluster_list[index]
sse += distEclud(data_train[index, :], cluster_ceter_list[cluster_num])
print("簇数是",num , "时; SSE是", sse)
SSE.append(sse)
return nums, SSE
nums, SSE = test_Kmeans_nclusters(filter_zscore_data)
画图
#画图,通过观察SSE与k的取值尝试找出合适的k值
# 中文和负号的正常显示
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['font.size'] = 12.0
plt.rcParams['axes.unicode_minus'] = False
# 使用ggplot的绘图风格
plt.style.use('ggplot')
## 绘图观测SSE与簇个数的关系
fig=plt.figure(figsize=(10, 8))
ax=fig.add_subplot(1,1,1)
ax.plot(nums,SSE,marker="+")
ax.set_xlabel("n_clusters", fontsize=18)
ax.set_ylabel("SSE", fontsize=18)
fig.suptitle("KMeans", fontsize=20)
plt.show()
观察图像,并没有的所谓的“肘”点出现,是随k值的增大逐渐减小的,这里选取当k分别取4, 5, 6时进行,看能不能通过分析结果来反向选取更合适的值,k取值4时的代码如下:
kmodel = KMeans(n_clusters=4, n_jobs=4)
kmodel.fit(filter_zscore_data)
# 简单打印结果
r1 = pd.Series(kmodel.labels_).value_counts() #统计各个类别的数目
r2 = pd.DataFrame(kmodel.cluster_centers_) #找出聚类中心
# 所有簇中心座标值中最大值和最小值
max = r2.values.max()
min = r2.values.min()
r = pd.concat([r2, r1], axis = 1) #横向连接(0是纵向),得到聚类中心对应的类别下的数目
r.columns = list(filter_zscore_data.columns) + [u'类别数目'] #重命名表头
# 绘图
fig=plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, polar=True)
center_num = r.values
feature = ["入会时间", "飞行次数", "平均每公里票价", "总里程", "时间间隔差值", "平均折扣率"]
N =len(feature)
for i, v in enumerate(center_num):
# 设置雷达图的角度,用于平分切开一个圆面
angles=np.linspace(0, 2*np.pi, N, endpoint=False)
# 为了使雷达图一圈封闭起来,需要下面的步骤
center = np.concatenate((v[:-1],[v[0]]))
angles=np.concatenate((angles,[angles[0]]))
# 绘制折线图
ax.plot(angles, center, 'o-', linewidth=2, label = "第%d簇人群,%d人"% (i+1,v[-1]))
# 填充颜色
ax.fill(angles, center, alpha=0.25)
# 添加每个特征的标签
ax.set_thetagrids(angles * 180/np.pi, feature, fontsize=15)
# 设置雷达图的范围
ax.set_ylim(min-0.1, max+0.1)
# 添加标题
plt.title('客户群特征分析图', fontsize=20)
# 添加网格线
ax.grid(True)
# 设置图例
plt.legend(loc='upper right', bbox_to_anchor=(1.3,1.0),ncol=1,fancybox=True,shadow=True)
# 显示图形
plt.show()
分别取 k=5;k=6时,做出雷达图进行分析。
结论
通过观察可知:
- 当k取值4时,每个人群包含的信息比较复杂,且特征不明显
- 当k取值5时,分析的结果比较合理,分出的五种类型人群都有自己的特点又不相互重复
- 当k取值6时,各种人群也都有自己的特点,但是第4簇人群完全在第5簇人群特征中包含了,有点冗余的意思
综上,当k取值为5时,得到最好的聚类效果,将所有的客户分成5个人群,再进一步分析可以得到以下结论:
- 第一簇人群,10957人,最大的特点是时间间隔差值最大,分析可能是“季节型客户”,一年中在某个时间段需要多次乘坐飞机进行旅行,其他的时间则出行的不多,这类客户我们需要在保持的前提下,进行一定的发展;
- 第二簇人群,14732人,最大的特点就是入会的时间较长,属于老客户按理说平均折扣率应该较高才对,但是观察窗口的平均折扣率较低,而且总里程和总次数都不高,分析可能是流失的客户,需要在争取一下,尽量让他们“回心转意”;
- 第三簇人群,22188人,各方面的数据都是比较低的,属于一般或低价值用户
- 第三簇人群,8724人,最大的特点就是平均每公里票价和平均折扣率都是最高的,应该是属于乘坐高等舱的商务人员,应该重点保持的对象,也是需要重点发展的对象,另外应该积极采取相关的优惠政策是他们的乘坐次数增加
- 第五簇人群,5443人, 总里程和飞行次数都是最多的,而且平均每公里票价也较高,是重点保持对象
- 分析完毕,结果暗合市场的二八法则的,价值不大的第二三簇的客户数最多,而价值较大的第四五簇的人数较少。
持续更新