ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于LRFMC的航空公司客户价值分析Python实战指南

基于LRFMC的航空公司客户价值分析Python实战指南 简介航空公司客户价值分析Python源码与配套数据文件构成一套完整的数据挖掘实践项目面向具备一定Python基础、希望掌握商业分析或机器学习流程的学习者目标是基于航空公司的会员信息与飞行记录通过特征工程和聚类方法识别不同类型的客户价值。压缩包共10个文件包括5个Excel数据表存储原始数据与中间结果、3个CSV数据文件、1个Python主程序及1个导入模块说明文档总体积17.13MB文件覆盖数据清洗、z-score标准化、特征构造、聚类建模与结果导出等关键环节。目前已有891人学习下载读者可通过源码完整走通从原始数据探索、缺失值处理、RFM指标计算到K-means客户分群及结果可视化的全流程并结合案例理解特征选择、模型评估及业务应用思路。代码结构清晰、注释完整覆盖常见数据挖掘竞赛或实训中客户价值分析的核心步骤适合作为课程设计、毕业设计或企业客户分析项目的参考实现便于二次开发与扩展。1. 把客户价值分析从零售 RFM 平移过来哪里会先翻车拿到 air_data.csv 做航空公司客户价值分析最常见的错误是直接照搬零售场景的 RFM 模型用消费间隔、消费频率、消费金额去套 K-means结果把买全价票的商务客和刷折扣票的里程客归成同一类。LRFMC 是航空场景的替代方案用入会时长 L、最近乘机间隔 R、飞行次数 F、总里程 M、平均折扣系数 C 描述会员价值。这套 Python 源码从 air_data.csv 一路处理到 zscoreddata.xls中间经过 data_cleaned.csv 的清洗和 z-score 标准化再交给 K-means 分群全程只用 pandas 和 sklearn不依赖任何深度学习框架。适合有半年以上 Python 基础、想完整复现客户价值分析链路的数据分析师。下载包里的 explore.xls 和属性说明文件可以直接作为探索阶段的输出参考。2. 数据读取与探索先摸清 air_data.csv 有哪些列可用2.1 导入模块与运行前的环境检查导入模块说明.txt 里按顺序列了 pandas、numpy、matplotlib、seaborn 和 sklearn 的 preprocessing 模块。聚类只用到 sklearn.cluster.KMeans标准化用到 preprocessing.StandardScaler整个项目不依赖深度学习框架几个类就能完成。跑之前先确认环境里装了 openpyxl因为 to_excel 写 .xls 文件时默认走 openpyxl 引擎缺了它会在落盘那一步报 ImportError。pip install pandas numpy matplotlib seaborn scikit-learn openpyxl装完依赖后开始读数据import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.preprocessing import StandardScaler air_data pd.read_csv(air_data.csv, encodinggb18030) print(air_data.shape) print(air_data.dtypes) print(air_data.isnull().sum())逻辑说明read_csv 的 encoding 参数是第一道坎。航司业务系统导出的 CSV 大多是 GB18030 编码直接用默认 utf-8 打开会在第一行就抛 UnicodeDecodeError。air_data.shape 输出类似 (62892, 44) 的行列数44 列里真正参与 LRFMC 建模的只有五个字段其余像 FFP_TIER、WORK_CITY 这些保留到探索阶段做参考特征工程阶段再剔除。isnull().sum() 按列统计缺失值如果某列缺失数超过行数的一半这列基本可以直接丢不需要再花时间填充。参数说明encoding 参数只在读取时生效。如果列名能读出来但内容乱码八成是文件实际编码和指定编码不一致可以依次试 gb18030、gbk、utf-8-sig 这几种常见编码。读进来的 DataFrame 先不要急着改列名等确认编码正确后再处理否则排查问题时会混入两个变量。2.2 explore.xls 里应该看到什么explore.xls 是第一步探索性分析的落盘产物里面装的是描述统计表和分布直方图。它回答三个问题哪些字段有缺失、哪些字段有业务异常值、哪些字段分布是长尾。explore_cols [FFP_DATE, LAST_TO_END, FLIGHT_COUNT, SEG_KM_SUM, avg_discount, SUM_YR_1, SUM_YR_2] explore_data air_data[explore_cols].describe().T explore_data.to_excel(explore.xls)参数说明describe() 只统计数值列FFP_DATE 是 datetime 类型会被跳过想看日期分布需要单独再画直方图。转置后的表每行一个字段列依次是 count、mean、std、min、25%、50%、75%、max。对比 mean 和 50% 就能看出长尾程度SEG_KM_SUM 的均值如果远大于中位数说明少数高频会员贡献了大部分里程这类分布直接喂给 K-means 会让普通会员被挤成一团所以标准化环节不能省略。提示explore.xls 和数据清洗文件是两条线的产物。不要把 explore.xls 当成清洗后的数据用它只是中间检查点真正的建模输入是后面生成的标准文件。2.3 核心字段的数据质量检查表在写清洗代码之前我习惯先画一张字段处理决策表把哪些字段留、哪些删、哪些要特殊处理写清楚避免在代码里随手 dropna字段业务含义常见问题处理策略FFP_DATE入会日期部分缺失转 datetime缺失则剔除LAST_TO_END最近乘机距今天数少量缺失中位数回填FLIGHT_COUNT乘机次数出现 0 值保留0 是有效业务值SEG_KM_SUM飞行总里程缺失、为 0缺失剔除0 保留avg_discount平均折扣系数为 0 表示免票过滤为 0 的行SUM_YR_1/SUM_YR_2两年累计票价同时为空直接剔除代码层面把这几个规则合并成布尔索引filter_rule ( air_data[SEG_KM_SUM].notnull() air_data[SUM_YR_1].notnull() air_data[avg_discount].notnull() (air_data[avg_discount] ! 0) ) raw_valid air_data[filter_rule] print(raw_valid.shape)代码说明filter_rule 用括号包住每个条件再执行与运算pandas 里不加括号会抛“DataFrame 真值不明确”的异常。notnull 判断先于数值判断执行避免 NaN 参与比较产生意外结果。raw_valid 是初步过滤后的数据后面清洗还会做第二次取舍。这张表也是交付给同事时最好的注释比代码开头的 docstring 直观得多。3. 从 data_cleaned 到 zscoreddata清洗与标准化链路3.1 两个 data_cleaned 文件的分工压缩包里同时有 data_cleaned.csv 和 data_cleaned1.csv名字很接近但不是重复文件。前者是清洗后的原始字段快照后者是构造完 LRFMC 特征后的样本专门用来做标准化和聚类。四份数据的分工可以这样看文件内容下游用途data_cleaned.csv过滤异常后的全部字段特征工程输入data_cleaned1.csv清洗后加 LRFMC 新列标准化的输入zscoreddata.xls五维标准化结果K-means 实际输入explore.xls描述统计与分布探索性分析报告清洗代码按函数组织便于换数据源时复用def clean_airline_data(df): df df.copy() df df[df[SUM_YR_1].notnull()] df df[df[SUM_YR_1] ! 0] df df[df[avg_discount] ! 0] return df data_cleaned clean_airline_data(air_data) data_cleaned.to_csv(data_cleaned.csv, indexFalse, encodingutf-8-sig)逻辑说明函数内先 copy避免 pandas 的 SettingWithCopyWarning 污染外部变量。SUM_YR_1 先判非空再判非 0空值和 0 值是两个不同的脏数据类型空值说明记录不完整0 值可能是免票或未出票。avg_discount 为 0 的免票记录虽然真实存在但放进聚类会把折扣维度拖出一整个异常簇分析价值很低。参数说明to_csv 的 encodingutf-8-sig 是给 Excel 用户准备的。不带 BOM 的 UTF-8 在 Excel 里打开中文列名会乱码加上 sig 后 Excel 能正确识别。如果你平时用 pandas 处理完数据直接丢给别人这个参数值得养成习惯。3.2 StandardScaler 为什么在这里是标配航空客户价值分析的五个维度量纲差异非常大L 是几百到几千天F 是个位数到几百次M 是几千到几十万公里C 是 0 到 1.5 的小数。K-means 基于欧氏距离量纲大的维度会主导距离计算不标准化的结果就是聚类主要由 L 和 M 决定C 和 R 几乎不起作用。feature_cols [L, R, F, M, C] scaler StandardScaler() zscored_array scaler.fit_transform(data_cleaned1[feature_cols]) zscored_df pd.DataFrame(zscored_array, columnsfeature_cols) zscored_df.to_excel(zscoreddata.xls, indexFalse)逻辑说明StandardScaler 对每个维度独立计算均值和标准差输出 (x - mean) / std使五个维度都落在均值 0、标准差 1 的统一尺度上。zscoreddata.xls 里每列的中心都在 0 附近K-means 的欧氏距离不再被某个字段的绝对值带偏。参数说明fit_transform 在同一份数据上同时学习参数并转换只能用于训练阶段。如果后续有新客户数据要做预测必须用同一个 scaler 的 transform 方法不能重新 fit_transform否则新数据的均值和标准差变了老模型和新数据不在同一空间。我一般会把 scaler 用 joblib 保存到 tmp 目录下次直接加载。3.3 标准化前先做断言行数和缺失值都要查进入 K-means 之前标准化这块最好做两道门卫检查避免聚类跑出一个莫名其妙的结果、debug 半天才发现是前面某一步丢了数据assert zscored_df.isnull().sum().sum() 0, zscored data contains NaN assert zscored_df.shape[0] data_cleaned1.shape[0], row count changed after scaling代码说明assert 在条件为假时抛 AssertionError 并打印后面的字符串。第一行把整个 DataFrame 的缺失值加起来判断是否为 0第二个 assert 判断标准化过程有没有改变行数。这两道门卫平时是沉默的一旦触发就知道问题出在清洗和标准化之间而不是聚类参数本身。import joblib joblib.dump(scaler, tmp/scaler.pkl)顺便说一句tmp 目录在源码里不是临时垃圾区。这里通常会放 scaler.pkl、聚类模型或 np.save 出来的簇中心目的是让预测脚本不用重新训练。只要数据分布没有大偏移线上打分直接用这一套保存物就够了。4. LRFMC 特征构造与 K-means 聚类实现4.1 五维特征里最容易被算错的 L 和 RLRFMC 特征从原始字段构造的核心就是把日期转成天数差。data_cleaned[FFP_DATE] pd.to_datetime(data_cleaned[FFP_DATE]) anchor_date data_cleaned[FFP_DATE].max() pd.Timedelta(days1) data_cleaned[L] (anchor_date - data_cleaned[FFP_DATE]).dt.days data_cleaned[R] data_cleaned[LAST_TO_END] data_cleaned[F] data_cleaned[FLIGHT_COUNT] data_cleaned[M] data_cleaned[SEG_KM_SUM] data_cleaned[C] data_cleaned[avg_discount] data_cleaned1 data_cleaned[[FFP_DATE, L, R, F, M, C]].copy() data_cleaned1.to_csv(data_cleaned1.csv, indexFalse, encodingutf-8-sig)代码说明anchor_date 取数据集中最大入会日期的下一天这样所有会员的 L 最小是 1不会出现 0 天会员。之所以不用系统当前日期做锚点是因为同一份 CSV 在 3 月跑和 10 月跑L 会差出七个月分析结果不可复现。R 直接取 LAST_TO_END这个字段在源表里已经是“最近一次乘机距今的天数”不需要再转换。F 和 M 看着简单但要注意 FLIGHT_COUNT 为 0 的会员是否应该保留——如果一张票都没飞但里程非 0多半是里程兑换活动这类样本在聚类里会成为噪声。参数说明pd.to_datetime 要放在构造 L 之前否则字符串相减直接报 TypeError。copy() 是为了避免 data_cleaned1 和 data_cleaned 共享同一块内存后面给 data_cleaned1 加 cluster 列时不会污染前面的表。4.2 用肘部法则配合 n_init 选 K 值K-means 的 K 值选择在航空客户场景里通常落在 4 到 6 之间。代码上先把肘部曲线画出来from sklearn.cluster import KMeans import matplotlib.pyplot as plt sse [] for k in range(2, 9): km KMeans(n_clustersk, n_init10, max_iter300, random_state42) km.fit(zscored_df) sse.append(km.inertia_) plt.plot(range(2, 9), sse, markero) plt.xlabel(k) plt.ylabel(SSE) plt.savefig(elbow.png, dpi120)逻辑说明inertia_ 是簇内误差平方和K 越大该值必然越小但下降速度会变缓曲线出现肘形拐点的位置就是收益递减的分界。航空客户价值分析一般选 K5对应高价值核心、高潜成长、普通休闲、沉睡新客、流失风险五类。如果业务要求更细可以试 K6但超过 7 个簇之后群体边界很难向管理层解释。样本量小的时候也可以试层次聚类的 dendrogram 做交叉判断但几万条会员记录下 linkage 矩阵的内存压力太大工程上还是 K-means 更实用。参数说明n_init10 指定 K-means 从 10 组不同初始中心出发选择 inertia 最小的结果作为最终模型。sklearn 1.4 之后 n_init 默认值从 10 改成了 auto不显式指定的话不同 scikit-learn 版本跑出来的结果可能在细节上对不上。random_state42 固定随机种子保证每次复现结果一致。max_iter300 是单个初始中心的迭代上限几万条样本一般几十轮就收敛如果出现不收敛警告再调大。K 值也可以用轮廓系数做交叉验证from sklearn.metrics import silhouette_score score silhouette_score(zscored_df, km.labels_) print(silhouette score:, score)轮廓系数大于 0.5 表示聚类结构明显0.3 到 0.5 之间属于可接受范围。航空公司会员数据长尾严重一般不会出现特别高的分值0.35 左右就可以进入业务解读不要为了凑一个高分把用户群拆成碎片。4.3 聚类画像表与业务命名聚类跑完标签本身没有任何业务含义必须按维度均值给每个簇做画像。data_cleaned1[cluster] km.labels_ profile data_cleaned1.groupby(cluster)[[L, R, F, M, C]].mean().round(2) profile.to_excel(cluster_profile.xls)groupby 按 cluster 分组求五维均值输出的 5 行 5 列表就是分群画像。解读时要记住四个方向R 越小表示最近刚飞过活跃度高L 长表示入会久F 高表示频率高M 大表示里程贡献大C 高表示常买全价票价格不敏感。常见的画像分布可以参考下表但数字一定以你自己跑出来的数据为准clusterLRFMC业务命名0高低高高高高价值核心客群1中中中中中普通经济舱客群2低高低低中新入会沉默客群3高高低中中历史高频流失风险4低低高高低里程多但折扣低客群第 4 簇是最典型的“里程大户但贡献不匹配”群体飞得多、里程大但平均折扣系数低说明大量购买特价舱位。这类客户适合用里程促销刺激消费频次不适合给最高等级会员权益。给运营同事输出画像时用业务命名而不是簇编号因为 cluster 编号每次重跑都可能改变业务名却是稳定的。5. 模型上线前要补上的落盘与稳定性校验5.1 把标准化参数和聚类模型一起持久化线下跑通聚类只是第一步真正要每月给运营输出客户群需要把训练好的标准化容器和聚类模型保存下来而不是每次都重新训练。import joblib joblib.dump(scaler, tmp/scaler.pkl) joblib.dump(km, tmp/km.pkl) new_z scaler.transform(new_data[feature_cols]) new_cluster km.predict(new_z)predict 返回每个新样本最近的簇中心索引。注意 new_data 的字段顺序必须和 feature_cols 一致这是容易踩的隐性坑——列名相同但顺序不同predict 的结果会完全错位。建议在预测脚本里对新数据做一次 reindex确保顺序一致后再进模型。5.2 用簇占比波动检查聚类稳定性每次重新聚类后检查每个簇的样本占比。这个占比直接对应运营预算分配比例如果某次跑出来高价值客群从 12% 跳到了 25%大概率不是客户突然变优质了而是数据清洗条件发生了变化。cluster_share data_cleaned1[cluster].value_counts(normalizeTrue).sort_index() print(cluster_share.round(4))value_counts(normalizeTrue) 输出每个簇的样本占比sort_index 保证显示顺序按簇编号排列而不是按占比降序。对比历史两次运行的占比如果某个簇偏移超过两个百分点优先检查 SUM_YR_1、avg_discount 的过滤条件有没有变动再看是否新增了采购渠道或其他外部数据源。最终落到运营侧的动作可以参照这个映射客户群运营动作高价值核心客群专属客户经理、优先升舱、会员等级保护普通经济舱客群维持现状不做高成本营销新入会沉默客群首飞券、会员权益提醒、唤醒邮件历史高频流失风险里程到期提醒、保级进度提示里程多但折扣低客群定向里程促销控制成本型权益输出这张表时把 cluster_profile.xls 里的均值画像一起带上不要只发簇编号。运营侧看到的是“高价值核心客群 R 均值 15 天、F 均值 24 次”才知道这批人最近一次乘机是半个月前决策才有依据。每次新一期数据跑完后把 cluster_share 和 cluster_profile 与上一期对比任一簇占比变动超过两个百分点时先检查 scaler.pkl 和清洗条件是否与上期一致再谈业务变化。本文还有配套的精品资源点击获取
返回列表