[Python数据挖掘]第7章、航空公司客户价值分析
一、背景和挖掘目标
二、分析方法与过程
客户价值识别最常用的是RFM模型(最近消费时间间隔Recency,消费频率Frequency,消费金额Monetary)
1、EDA(探索性数据分析)
#对数据进行基本的探索 import pandas as pd data = pd.read_csv('data/air_data.csv', encoding = 'utf-8') #读取原始数据,指定UTF-8编码(需要用文本编辑器将数据装换为UTF-8编码) 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('tmp/explore.xls') #导出结果
2、数据预处理
1.数据清洗
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] #该规则是“或”
票价为空表示该值缺失,票价为0表示飞这一趟没花钱,二者概念不同
2.属性规约
原始数据属性太多,根据之前提出的LRFMC模型,只保留6个与之相关的属性
3.数据变换
方法1:EXCEL手动操作(方便简单)
data_select.to_excel('tmp/data_select.xls', index = False) #数据写入
方法2:代码操作(方便新增信息的抽取)
from datetime import datetime #使用匿名函数将LOAD_TIME数据转换成datetime格式,然后才能进行日期加减(匿名函数比for循环效率高) data_select['LOAD_TIME_convert'] = data_select['LOAD_TIME'].apply(lambda x: datetime.strptime(x, '%Y/%m/%d')) data_select['FFP_DATE_convert'] = data_select['FFP_DATE'].apply(lambda x: datetime.strptime(x, '%Y/%m/%d')) #构造一个Series序列接收 (LOAD_TIME-FFP_DATE) data_select['L']=pd.Series() #(LOAD_TIME-FFP_DATE)得到两个日期之间的天数间隔,然后除以30得到月份间隔 这一步相当费时 for i in range(len(data_select)): data_select['L'][i] =(data_select['LOAD_TIME_convert'][i]-data_select['FFP_DATE_convert'][i]).days/30 data_select = data_select.rename(columns = {'LAST_TO_END': 'R','FLIGHT_COUNT':'F','SEG_KM_SUM':'M','avg_discount':'C'}) data_selected=data_select[['L','R','F','M','C']] data_selected
接下来进行数据标准化
#标准差标准化 import pandas as pd data = pd.read_excel('data/zscoredata.xls', index = False) data = (data - data.mean(axis = 0))/(data.std(axis = 0)) #简洁的语句实现了标准化变换,类似地可以实现任何想要的变换。 data.columns=['Z'+i for i in data.columns] #表头重命名。 data.to_excel('tmp/zscoreddata.xls', index = False) #数据写入
3、模型构建
1.客户聚类
#K-Means聚类算法 import pandas as pd from sklearn.cluster import KMeans #导入K均值聚类算法 k = 5 #需要进行的聚类类别数 #读取数据并进行聚类分析 data = pd.read_excel('data/zscoreddata.xls') #调用k-means算法,进行聚类分析 kmodel = KMeans(n_clusters = k, n_jobs = 4) #n_jobs是并行数,一般等于CPU数较好 kmodel.fit(data) #训练模型 # kmodel.cluster_centers_ #查看聚类中心 # kmodel.labels_ #查看各样本对应的类别 #简单打印结果 s = pd.Series(['客户群1','客户群2','客户群3','客户群4','客户群5'], index=[0,1,2,3,4]) #创建一个序列s r1 = pd.Series(kmodel.labels_).value_counts() #统计各个类别的数目 r2 = pd.DataFrame(kmodel.cluster_centers_) #找出聚类中心 r = pd.concat([s,r1,r2], axis = 1) #横向连接(0是纵向),得到聚类中心对应的类别下的数目 r.columns =[u'聚类名称'] +[u'聚类个数'] + list(data.columns) #重命名表头 print(r)
2.客户价值分析
#雷达图代码摘自 https://blog.csdn.net/Just_youHG/article/details/83904618 def plot_radar(data): ''' the first column of the data is the cluster name; the second column is the number of each cluster; the last are those to describe the center of each cluster. ''' kinds = data.iloc[:, 0] labels = data.iloc[:, 2:].columns centers = pd.concat([data.iloc[:, 2:], data.iloc[:,2]], axis=1) centers = np.array(centers) n = len(labels) angles = np.linspace(0, 2*np.pi, n, endpoint=False) angles = np.concatenate((angles, [angles[0]])) fig = plt.figure() ax = fig.add_subplot(111, polar=True) # 设置坐标为极坐标 # 画若干个五边形 floor = np.floor(centers.min()) # 大于最小值的最大整数 ceil = np.ceil(centers.max()) # 小于最大值的最小整数 for i in np.arange(floor, ceil + 0.5, 0.5): ax.plot(angles, [i] * (n + 1), '--', lw=0.5 , color='black') # 画不同客户群的分割线 for i in range(n): ax.plot([angles[i], angles[i]], [floor, ceil], '--', lw=0.5, color='black') # 画不同的客户群所占的大小 for i in range(len(kinds)): ax.plot(angles, centers[i], lw=2, label=kinds[i]) #ax.fill(angles, centers[i]) ax.set_thetagrids(angles * 180 / np.pi, labels) # 设置显示的角度,将弧度转换为角度 plt.legend(loc='lower right', bbox_to_anchor=(1.5, 0.0)) # 设置图例的位置,在画布外 ax.set_theta_zero_location('N') # 设置极坐标的起点(即0°)在正北方向,即相当于坐标轴逆时针旋转90° ax.spines['polar'].set_visible(False) # 不显示极坐标最外圈的圆 ax.grid(False) # 不显示默认的分割线 ax.set_yticks([]) # 不显示坐标间隔 plt.show() plot_radar(r) #调用雷达图作图函数
4、决策支持
三、【拓展思考】客户流失分析
1、目标
2、数据预处理
参考自https://blog.csdn.net/zhouchen1998/article/details/85113535
import pandas as pd from datetime import datetime def clean(data): ''' 数据清洗,去除空记录 ''' 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 = data[['LOAD_TIME', 'FFP_DATE', 'LAST_TO_END', 'FLIGHT_COUNT', 'avg_discount', 'SEG_KM_SUM', 'LAST_TO_END', 'P1Y_Flight_Count', 'L1Y_Flight_Count']] return data def LRFMCK(data): ''' 经过计算得到我的指标数据 ''' # 其中K为标签标示用户类型 data2 = pd.DataFrame(columns=['L', 'R', 'F', 'M', 'C', 'K']) time_list = [] for i in range(len(data['LOAD_TIME'])): str1 = data['LOAD_TIME'][i].split('/') str2 = data['FFP_DATE'][i].split('/') temp = datetime(int(str1[0]), int(str1[1]), int(str1[2])) - 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('data/data_changed.csv', encoding='utf-8') def standard(): ''' 标准差标准化 ''' data = pd.read_csv('data/data_changed.csv', encoding='utf-8').iloc[:, 1:6] # 简洁的语句实现了标准化变换,类似地可以实现任何想要的变换 data = (data - data.mean(axis=0)) / (data.std(axis=0)) data.columns = ['Z' + i for i in data.columns] data2 = pd.read_csv('data/data_changed.csv', encoding='utf-8') data['K'] = data2['K'] data.to_csv('data/data_standard.csv', index=False) if __name__ == '__main__': data = pd.read_csv('data/air.csv', encoding='utf-8', engine='python') data=clean(data) data.to_csv('data/data_filter.csv', index = False, encoding='utf-8') data = pd.read_csv('data/data_filter.csv', encoding='utf-8') #不重新读取的话,调用LRFMCK会报错,我也不知道为什么 LRFMCK(data) standard()
不知道为什么,总是要反复写入文件和读取文件,不然会莫名其妙的报错。猜测可能是csv文件与xls文件不同导致
3、模型构建
import pandas as pd from sklearn import tree from sklearn.model_selection import train_test_split from sklearn.metrics import confusion_matrix #导入混淆矩阵函数 import pydotplus # 读取数据 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 cm_plot(y, yp): cm = confusion_matrix(y, yp) #混淆矩阵 plt.matshow(cm, cmap=plt.cm.Greens) #画混淆矩阵图,配色风格使用cm.Greens,更多风格请参考官网。 plt.colorbar() #颜色标签 for x in range(len(cm)): #数据标签 for y in range(len(cm)): plt.annotate(cm[x,y], xy=(x, y), horizontalalignment='center', verticalalignment='center') plt.ylabel('True label') #坐标轴标签 plt.xlabel('Predicted label') #坐标轴标签 return plt data, label = getDataSet('data/data_standard.csv') train_data, test_data, train_label, test_label = train_test_split(data, label, test_size=0.2) #使用决策树 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("tree.pdf") cm_plot(test_label, clf.predict(test_data)).show()