ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

航空客户价值分析:从RFM到LRFMC的K-Means聚类实现

航空客户价值分析:从RFM到LRFMC的K-Means聚类实现 简介面向数据挖掘课程设计的学生与从业者这份资料以航空公司客户价值分析为完整案例系统演示数据预处理、探索性分析、客户细分、预测建模与评估优化全流程。项目基于多维客户数据包含数据清洗、标准化、KMeans聚类、逻辑回归与随机森林等典型方法并注重数据隐私处理与结果业务化解读。压缩包共23个文件以Python脚本、Excel数据表、CSV数据集为主辅以XML配置和Word文档整体约20.78MB。脚本涵盖数据探索、清洗、尺度变换及聚类实现数据文件包含客户属性、飞行记录、消费金额等维度文档则记录课程设计的方法选择、评分标准与实验结论可帮助读者理解从原始数据到业务建议的完整路径。目前已有1254人学习下载适合需要完成课程设计、备赛或入门客户价值分析的读者。1. 从一份课程设计 zip 说起航空公司客户价值分析到底在分析什么如果你正在做数据挖掘课设或毕设搜“航空公司客户价值分析数据挖掘设计源代码”时刷到这份 zip大概率是被“源代码”三个字吸引进来的。这个题目的经典程度和它的名字一样老套但每年仍然有大量学生卡在同一个地方数据拿到了、代码能跑可老师一问“为什么用这个模型、聚类结果怎么解释”就答不上来。这份数据挖掘设计.zip 本质上是一个完整复现套件装的是数据、代码、报告和演示文件解决的是从原始客户数据到聚类分群再到业务解读的整条链路。我的建议是不要把它当成“交作业即用”的素材而是把它当一个可运行的样例工程来拆适合正在做航空客户价值分析、会员分群、RFM 变体落地或者想复现聚类算法完整流程的人。2. 源码与数据集结构先理清 zip 里的四种文件2.1 解压后的文件清单与各自用途拿到 zip 先别急着跑代码我习惯第一步把解压目录铺开看一遍。这种课设包的标准结构基本是四类东西数据集文件通常是 csv 或 xlsx、预处理和聚类的 Python 脚本或 Jupyter Notebook、实验报告文档、答辩 PPT。文件不多但每类文件的角色天差地别。文件/目录典型内容在复制时重点关注air_data.csv航空客户原始信息每行一个会员多条属性字段是否被改动过、数据量与你预期是否一致数据预处理.ipynb / preprocess.py缺失值清洗、剔除异常记录、LRFMC 指标计算观测窗口截止时间怎么取、标准化用哪种方式聚类分析.ipynb / cluster.pyK-Means 聚类、K 值选取、结果画像是否固定了随机种子、聚类中心怎么输出实验报告.doc/pdf背景、方法、结果与营销建议是否与附带代码跑出的图、表对应这里有个最常见的坑报告里的截图和代码跑出来的结果对不上。原因多半是作者改过代码参数但没更新截图或者你本地 pandas、sklearn 版本不同导致随机结果差异。拿到资源后第一件事是完整跑一遍再用自己的结果替换报告里的旧图。2.2 读懂字段字典17 个字段里哪些能用、哪些是坑航空客户数据的字段设计是有讲究的不是每列都能直接塞进聚类。这份资源里的原始表通常包含会员卡号、入会时间、首次乘机时间、最后一次乘机时间、飞行次数、飞行总里程、累计票价、平均折扣率、性别、年龄、工作城市等十几列。我一般拿到数据后先做个快速体检。import pandas as pd df pd.read_csv(air_data.csv, encodinggbk) print(df.shape) print(df.dtypes) print(df.isnull().sum()) # 看离散字段的取值分布例如性别、会员等级 for col in [GENDER, FFP_TIER]: if col in df.columns: print(df[col].value_counts())逻辑说明df.shape确认行列数df.dtypes看每个字段类型isnull().sum()统计缺失值这一套下来你就能判断数据质量。舱位等级、会员卡等级这类离散字段虽然可以用来讲故事但在 LRFMC 模型里通常不直接参与聚类因为它们的取值范围和含义跟 L、R、F、M、C 不在一个量纲上。参数说明编码方式用gbk是因为这类课程设计数据多半导出国产物料用utf-8读会直接报 UnicodeDecodeError。如果你的环境读不了改成encodingutf-8或encodinggb18030试试。性别、城市、等级这类字段先留着聚类时不进特征矩阵但后面做用户画像要用。3. 构建 LRFMC 指标预处理才是课设里最容易扣分的地方3.1 从 RFM 到 LRFMC为什么要多一个 L教科书上对客户价值分析的标准套路是 RFM最近一次消费时间Recency、消费频率Frequency、消费金额Monetary。这个模型放在零售、电商场景很好用但做航空公司客户分析时有个明显问题机票价格受季节、航线、提前购票时间影响太大直接用消费金额衡量客户价值会误伤低票价航线的常旅客。而且航空会员里有一类重要客户他们入会时间特别长、飞行频次高但最近半年飞得少RFM 会把这类人归为流失客户这显然不合理。所以航空场景的标准做法是把 RFM 改造成 LRFMC 五维指标指标含义对应原始字段的驱动方式L入会时间长度观测窗口结束时间减去入会时间R最近一次乘机时间间隔最后一次乘机日期距今的月数F飞行频率累计飞行次数M飞行总里程累计飞行公里数C平均折扣系数平均折扣率反映舱位等级偏好多出来的 L 用来识别“老会员”的价值C 用来识别“高舱位客户”。这两个维度是航空场景区别于普通电商 RFM 的核心也是答辩时最容易讲出彩的地方。把 RFM 到 LRFMC 的推导逻辑写在报告里评分通常会高于直接套库。3.2 用 pandas 完成清洗与 L/R/F/M/C 计算这一节是整套代码里最关键的一步。很多新手直接拿原始表跑聚类结果聚类中心解读出来完全没意义原因就是字段没处理成指标。下面代码是课设里最常用的实现顺序。import pandas as pd import numpy as np df pd.read_csv(air_data.csv, encodinggbk) # 1. 去掉完全重复的行 df df.drop_duplicates() # 2. 剔除异常记录票价为 0 或者折扣率为 0 df df[(df[SUM_YR_1] 0) (df[SUM_YR_2] 0)] df df[(df[avg_discount] 0) (df[avg_discount] 1)] # 3. 日期字段转成 datetime并构造观测窗口 df[FFP_DATE] pd.to_datetime(df[FFP_DATE]) df[LOAD_TIME] pd.to_datetime(df[LOAD_TIME]) end_date df[LOAD_TIME].max() # 4. 构造 LRFMC 五维特征 df[L] (end_date - df[FFP_DATE]).dt.days / 30 df[R] df[LAST_TO_END] df[F] df[FLIGHT_COUNT] df[M] df[SEG_KM_SUM] df[C] df[avg_discount] features [L, R, F, M, C]逻辑说明drop_duplicates()去重是为了防止同一会员有多个冗余行剔除票价为 0 和折扣率为 0 的记录是因为这些数据在真实业务里通常是赠票、员工票或录入脏数据留着会严重干扰聚类。end_date取数据里最大的LOAD_TIME也就是把所有客户放在同一个时间窗口内比较这是 L 和 R 计算公平性的前提。L 的单位是月所以用days/30。参数说明LAST_TO_END这个字段在经典案例里本身就是“最后一次乘机时间距观测窗口结束的月数”所以直接用不需要再减当前时间。如果你的数据里没有这个字段而是给了LAST_FLIGHT_DATE结算方式就变成(end_date - df[LAST_FLIGHT_DATE]).dt.days / 30。这套课设的评分重点之一就是你把原始字段转化为指标的过程是否讲得通所以清洗和构造逻辑一定要在报告里写清楚。3.3 为什么要用标准差标准化而不是归一化特征构造完之后直接聚类是有问题的。L 的量级可能是一两百个月R 可能是几个月F 是几十次M 是几万公里C 在 0 到 1 之间。量纲不一致会导致欧氏距离完全被 M 和 F 主导聚类结果基本等于只按“飞得多不多”分堆。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X scaler.fit_transform(df[features]) # 如果需要把标准化后的数据写回表用下面的方式 df_scaled pd.DataFrame(X, columnsfeatures, indexdf.index)逻辑说明StandardScaler做的是 Z-score 标准化每个特征减去均值、除以标准差变换后均值为 0、方差为 1。它比 MinMax 归一化更适合 K-Means原因是 K-Means 依赖欧氏距离而 Z-score 不会把数据压到固定区间导致离群点信息被削弱且对后续计算协方差、轮廓系数更友好。参数说明fit_transform只用一次把学到的均值和方差存进scaler对象里。后面如果有新客户数据要做预测必须用同一个scaler.transform()不能重新 fit否则训练和预测时的分布不一致聚类结果没法解释。4. K-Means 聚类的落地细节K 值怎么选、中心点怎么解读4.1 聚类的输入矩阵怎么组织标准化的五列特征拼成一个矩阵 X每一行是一个客户每一列是一个指标这是 K-Means 直接吃的输入格式。要注意的事情有两件一是特征顺序固定为 L、R、F、M、C后续聚类中心解读和画雷达图都依赖这个顺序二是索引要对齐如果你中间做过dropna()或筛选一定要用reset_index(dropTrue)否则后面把聚类标签并回原表时会出现错位。X df_scaled[features].values print(X.shape)逻辑说明df_scaled[features].values把 DataFrame 转成 numpy 二维数组sklearn 的KMeans只接受这种数值矩阵输入不接受带列名的 DataFrame。X.shape看一眼是 (行数, 5)确认没有把其他字段混进去。4.2 用 SSE 肘部法和轮廓系数选定 K 值K-Means 的 K 值没有公式可算课设里最稳妥的选法是先画 SSE 肘部图再用轮廓系数辅助验证。这两个指标在答辩时都要能讲出含义。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score sse [] sil [] k_range range(2, 9) for k in k_range: model KMeans(n_clustersk, n_init10, random_state0) labels model.fit_predict(X) sse.append(model.inertia_) sil.append(silhouette_score(X, labels)) # 查看每个 k 对应的指标 for k, s, si in zip(k_range, sse, sil): print(fk{k}, SSE{s:.2f}, Silhouette{si:.4f})逻辑说明SSE 是样本到其所属簇中心的距离平方和K 增大时 SSE 必然下降但下降速度会有一个明显的拐点拐点附近就是比较合理的 K 值。轮廓系数范围在 -1 到 1 之间越接近 1 说明簇内紧密、簇间分离越好0.5 以上都是可接受的结果。经典案例中 5 类客户通常对应 k5但我不建议直接写死先看你的数据拐点。参数说明n_init10表示 K-Means 用 10 个不同的初始质心各跑一遍取最优结果这是解决局部最优的标准参数新手最容易漏。random_state0固定随机种子保证每次跑出来的结果一致否则你换台电脑结果就变了报告里没法沉淀。4.3 用固定种子做最终聚类并读出中心点选定 K 值之后就是正式的最终聚类这一步和 4.2 的区别是把 k 固定下来并且把标签写回原始 DataFrame方便后续做用户画像。model KMeans(n_clusters5, n_init10, random_state42) df[cluster] model.fit_predict(X) # 查看聚类中心标准化后的均值 centers pd.DataFrame(model.cluster_centers_, columnsfeatures) print(centers) # 每个簇的样本数 print(df[cluster].value_counts().sort_index())逻辑说明model.cluster_centers_返回的是每个特征在标准化空间里的中心坐标因为输入是标准化后的 X所以这里的数值是 Z-score 的含义正数表示高于全体均值、负数表示低于全体均值。value_counts()看每个簇的人数占比如果某个簇人数特别少很可能是离群点问题回退到第 5 章的截断处理。random_state42是工程里常用的固定种子换成任意整数都行目的是结果可复现。需要注意的是聚类标签0-4本身没有业务含义我们是在解读完中心点之后才给每个簇赋予“重要保持客户”“一般客户”这类名字。4.4 五种客户画像怎么读从聚类中心到营销建议聚类中心读出的是标准化数值要把它翻译成业务语言才能写进报告。常见做法是把每个簇的 L、R、F、M、C 原值均值列出来和全体均值做对比然后给簇命名。簇LRFMC客户类型判断建议策略0高低高高高重要保持客户优先保障提供会员权益和专属服务1中低中中低重要发展客户推动升舱和联名合作提升折扣系数2高高中中高重要挽留客户定向召回发放优惠券唤醒3低中低低低一般客户维持基本服务低成本触达4低高低低低低价值客户不做额外投入这里有个很容易翻车的细节R 值越高代表“越久没坐飞机”所以解读时 R 的方向和其他四个指标相反。很多学生的报告把“R 高”解读成“最近刚飞完”直接被老师看出来没理解指标含义。写报告时图表和结论都建议围绕这张表把中心点数值还原成业务动作。5. 避坑与排错五个让新手翻车的常见问题5.1 问题一每次跑出来的聚类结果都不一样现象同一个代码跑两次value_counts()的分布对不上报告截图和实际结果不一致。原因K-Means 初始质心是随机选择的不同初始点会收敛到不同的局部最优你重跑一次相当于换了初始点。解决聚类和 K 值选择阶段统一固定random_state并在报告中写明参数值例如KMeans(n_clusters5, n_init10, random_state42)。n_init10会尝试 10 组初始点并保留最佳那个基本能消除随机性影响。从那以后我每次跑聚类都会把随机种子写在代码注释里。5.2 问题二M 字段里有超长尾巴聚类结果变成“一拖四”现象五个簇里有四个都长得很像剩下一个是极少数高里程用户中心点解读完全没有业务区分度。原因飞行里程 M 的分布极度右偏少数洲际航线用户把均值拉得很高标准化后这些点成为离群点K-Means 会专门为它们开辟一个簇。解决在标准化之前对异常值做分位数截断。这里有个常见误用很多人直接删掉“看起来不正常”的行但那是真实客户正确做法是压缩而不是删除。代码逻辑是在标准化前用clip()处理极值之后再重新做标准化和聚类。for col in features: q99 df[col].quantile(0.99) df[col] df[col].clip(upperq99) X scaler.fit_transform(df[features])逻辑说明quantile(0.99)算出第 99 分位数clip(upper...)把所有超过该值的样本压到这个分位数上保留样本数量但削减极值影响。这样既保住高价值客户还能避免聚类被极端值带走。5.3 问题三新数据导入后 predict 报错或结果全乱现象模型训练完了拿新一批客户数据进来跑model.predict()要么报特征数量不一致要么聚类结果全部归入同一类。原因训练时用到的是整个原始表的 17 列predict 时你只给了 5 列sklearn 按位置取特征列索引错位之后数据全乱或者新数据没有经过同一套清洗和标准化。解决把所有预处理逻辑封装成一个函数训练和预测时调用同一份代码避免两边逻辑漂移。def build_features(raw_df, end_dateNone): df raw_df.copy() if end_date is None: end_date pd.to_datetime(df[LOAD_TIME]).max() df[FFP_DATE] pd.to_datetime(df[FFP_DATE]) df[L] (end_date - df[FFP_DATE]).dt.days / 30 df[R] df[LAST_TO_END] df[F] df[FLIGHT_COUNT] df[M] df[SEG_KM_SUM] df[C] df[avg_discount] return df[[L, R, F, M, C]]逻辑说明这个函数接收原始 DataFrame 返回五列特征训练时先把原始表传进去拿特征再标准化和 fit预测时同样把新表传进去然后用保存好的scaler.transform()转成同分布数据段才能交给model.predict()。5.4 问题四报告中的雷达图坐标轴错乱现象客户画像用雷达图展示五个维度是 L、R、F、M、C但图里某个维度的数值明显不对整个图形被压扁。原因雷达图的每个维度坐标范围不一致R 和 C 范围小、M 范围大没有对展示数据做归一化图就被 M 拉变形或者横纵坐标写反导致五维变量显示不全。解决画雷达图时先用 MinMax 把五列缩放到 0-1 区间并手动固定每个维度坐标起始都在 0。这属于画图技巧上的坑写在实验报告里也能体现细节。5.5 问题五中文标签显示成方块现象seaborn 或 matplotlib 画图标题、图例里的中文全部变方块报告没法看。原因matplotlib 默认字体不包含中文字符。解决代码开头配置字体常见做法是选系统中的黑体或微软雅黑。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 常见 plt.rcParams[axes.unicode_minus] False参数说明font.sans-serif设置无衬线字体SimHei 是 Windows 自带黑体macOS 可以改成[Arial Unicode MS]。axes.unicode_minus要设为 False否则显示负号时会变方块。这个坑不大但每年都有人踩先写上能省十分钟调试时间。6. 进阶用客户价值得分验证聚类合理性聚类做完直接把结论写进报告逻辑上不够闭环因为缺少一个验证工作。我们的做法是用一个可解释的客户价值得分公式把五个簇排序看排序结果与聚类画像是否一致。这算是一个不错的加分项也是能体现梯度调优能力的地方。先定义权重L 波动小不作为强权重重点放在 R、F、M、C 四个维度。参考常见方案可定义value_score 0.2*L 0.15*R 0.3*F 0.25*M 0.1*C再用 MinMax 归一化到 0-100 分。from sklearn.preprocessing import MinMaxScaler # 每个簇内求指标均值做一次 0-1 归一化 cluster_profile df.groupby(cluster)[features].mean() mm MinMaxScaler() profile_norm pd.DataFrame( mm.fit_transform(cluster_profile), columnsfeatures, indexcluster_profile.index ) # 计算每个簇的价值得分 weights {L: 0.2, R: 0.15, F: 0.3, M: 0.25, C: 0.1} profile_norm[score] sum( profile_norm[col] * w for col, w in weights.items() ) print(profile_norm.sort_values(score, ascendingFalse))逻辑说明groupby(cluster)[features].mean()得到聚类中心MinMaxScaler把各维度缩到 0-1 后再做加权求和这样得分就聚成 0-1 的分数。如果排序结果里得分最高的簇确实对应 L 高、R 低、F 高、M 高、C 高的那类客户说明聚类分群与业务逻辑互相验证了。课堂上讲这一步时老师通常会认为你已经把聚类结果当作业务决策依据来用而不是停留在“聚类完了画个图”的阶段。这个小验证让答案更有说服力同时也帮你把整套流程从“算法跑通”推进到“业务能用”。稍微调整权重就可以对应不同公司对不同类型客户的侧重这也是课设答辩时最容易被提问的地方。权重取多少不是玄学而是要能解释清楚你所在公司更看重频繁出行还是高舱位消费。从那以后我做这类客户分群项目都会在聚类代码后面挂一个得分排序表再迭代一轮。拿到一份数据先跑一遍全流程再回头比对各簇得分与业务假设是否矛盾一旦矛盾就去检查预处理阶段是不是有字段理解偏差。这个流程走熟了你的课设或者毕设基本就不会被问倒。做聚类课设最怕的就是“代码跑通了但解释不了为什么分出来这些类”把得分验证写进报告等于给自己留了张底牌。希望这篇笔记能帮到你至少让你少踩几个我已经替你踩过的坑。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进