
去年帮一个电商团队做用户数据分析时老板丢给我一份300万行的订单表问了我四个问题哪些商品适合放一起推荐、哪些用户未来会复购、复购用户大概能贡献多少销售额、以及用户能不能分群运营。这四个问题恰好对应了数据分析里最常用的四类算法关联规则、分类、回归、聚类。很多刚接触大数据分析的同学把这四类算法当成四个独立的知识点去学但实际业务里它们经常出现在同一条分析链路里结果要互相验证、互相补充。这篇文章我就用这份订单数据做例子把关联规则、分类、回归、聚类的完整流程串起来讲一遍包括参数怎么调、代码怎么写、结果怎么解释、坑在哪里。适合正在做数据分析项目、准备面试、或者刚入门机器学习想要落地业务场景的读者。1. 业务问题与数据准备一份订单表能挖出四种价值1.1 四个业务问题对应四种算法先把业务问题翻译成算法问题这一步很多人会跳过但恰恰是整个项目最关键的环节。老板问的四个问题本质上对应的是四种不同的目标变量和学习方式。哪些商品适合捆绑推荐没有标签纯粹从订单明细里挖掘商品之间的共现关系这是典型的无监督关联规则问题用Apriori或FP-Growth。哪些用户未来会复购有明确的二分类标签复购/不复购过去的行为特征做自变量属于有监督分类问题可以用逻辑回归、随机森林、XGBoost。复购用户大概消费多少钱标签是连续型金额变量属于回归问题可以用线性回归、岭回归、随机森林回归。用户能不能分群运营同样没有标签基于用户行为特征做无监督聚类最常用K-Means和层次聚类。这四类方法不是孤立的。关联规则可以告诉你什么商品组合值得推广聚类可以告诉你哪一类用户最容易接受这种组合分类和回归可以帮你把每个用户的复购概率和消费金额算出来最后形成一个从选品到人群再到预估收益的完整闭环。所以我在做项目时从来不会只跑一个模型交差而是把这四类方法的结果放在一起看。1.2 数据清洗与字段工程原始订单表字段大致包括order_id、user_id、item_id、category、quantity、price、order_amount、order_date以及用户维度信息user_age、user_gender、user_register_days、visit_count_last_90d、purchase_count_last_90d。拿到数据的第一步不是建模而是做数据清洗。我一般按这个顺序处理缺失值user_age缺失率大概12%我选择用中位数填充而不是均值。因为年龄往往有偏态分布均值会被少数高龄用户拉高中位数更稳健。异常值把order_amount小于等于0的订单剔除这些通常是退款、测试订单或者系统异常quantity小于等于0的记录同理。时间字段把order_date解析成datetime类型提取出月份、星期几、是否节假日等维度特征。电商数据的季节性很强这些时间特征在回归模型里往往比很多用户属性更重要。类别字段中文商品类别不能直接喂给模型要做one-hot编码或者标签编码。如果类别数量太多就先做频次筛选只保留TOP 50的类别。金额字段order_amount做log1p变换。这一点放在回归部分详细讲但在数据准备阶段就要先把变换列生成好。1.3 为什么先按不同粒度拆成多张分析表一份订单表不能直接喂给所有算法因为四种算法对数据粒度的要求完全不同。关联规则需要的是订单-商品的交易明细通常每一行是一个用户购物篮里的一个商品分类和回归需要的是用户级别的特征每个用户一行聚类也需要用户级别的行为特征矩阵。所以我在数据准备阶段会拆出三张表order_item_table字段是order_id、item_id、category用于关联规则。user_feature_table字段是user_id、各种行为统计特征、人口属性、标签列用于分类和回归。user_behavior_matrix只有特征、没有标签标准化后用于聚类。同样一份原始数据因为目标不同组织方式完全不同。很多人拿到数据就急着建模结果用订单粒度做用户分类导致同一个用户出现多次训练集和测试集之间信息泄漏。先拆表再建模能规避掉一大批这类问题。2. 关联规则实战从购物篮里挖出一起出现的商品组合2.1 支持度、置信度、提升度到底在算什么关联规则最核心的三个指标是支持度、置信度和提升度。很多教程只是把公式列出来我这里用一个具体例子说明它们到底在算什么。假设有100笔订单其中牛奶出现在40笔里面包出现在30笔里牛奶和面包同时出现在10笔里。支持度P(牛奶∩面包) 10 / 100 0.1。支持度衡量的是这个组合在全部订单里占多大比例解决的是这个规则有没有足够的样本支撑。支持度太低规则就是小样本噪声没有推广价值。置信度P(面包|牛奶) 10 / 40 0.25。置信度衡量的是买了牛奶的订单里有多少比例也买了面包解决的是这个规则在A发生的条件下有多大概率成立。提升度P(面包|牛奶) / P(面包) 0.25 / 0.3 ≈ 0.83。提升度小于1说明买了牛奶反而降低了买面包的概率牛奶和面包是负相关这条规则没有用。提升度大于1才有正向关联的意义而且越大说明关联越强。所以看规则时不要只看置信度。置信度0.8的牛奶→面包可能只是因为面包本身销量极高P(面包)就是0.8那这条规则纯属废话。必须把提升度大于1作为硬性筛选条件。2.2 最小支持度和置信度阈值怎么定参数设置是关联规则最容易纠结的地方。我的经验是不要一上来就追求最优参数而是从小到大逐步试探。最小支持度先从0.05开始跑如果频繁项集数量爆炸比如上万条就抬高到0.1如果结果太少就降低到0.02或0.01。具体阈值取决于数据量300万笔订单里支持度0.01已经对应3万笔订单完全是可靠的样本量。最小置信度通常从0.5起步。置信度太低规则没有业务价值太高比如0.9又会漏掉很多有意义的组合。提升度强制大于1是底线实际项目中我通常只保留提升度大于1.5的规则因为提升度接近1的规则往往经不起时间维度的验证。另外建议对频繁项集的长度做限制max_len3即可。2项集和3项集最容易做业务解释4项以上组合往往是噪音。2.3 Apriori实现与结果解读以Python为例用mlxtend库可以非常简洁地实现Apriori。首先要将订单数据转换成事务列表每一行是一个订单内购买的商品集合。import pandas as pd from mlxtend.preprocessing import TransactionEncoder from mlxtend.frequent_patterns import apriori, association_rules # transactions: list of lists每个子列表是一个订单的商品集合 te TransactionEncoder() te_ary te.fit(transactions).transform(transactions) df pd.DataFrame(te_ary, columnste.columns_) # 挖掘频繁项集 frequent_itemsets apriori(df, min_support0.02, use_colnamesTrue, max_len3) # 生成关联规则按提升度筛选 rules association_rules(frequent_itemsets, metriclift, min_threshold1.2) rules rules.sort_values(lift, ascendingFalse) print(rules.head(20))结果表里每行是一条规则关键列是antecedents、consequents、support、confidence、lift。我拿到结果后的第一件事不是看lift最高的而是看support相对较高的那些规则。lift极高但support只有0.001的规则往往只是极少数订单的巧合业务上没法落地。我会在代码里加一层筛选比如support大于0.02、confidence大于0.3、lift大于1.5然后再人工看一遍。结果解读举例假设发现手机壳→钢化膜的提升度是2.8支持度0.04、置信度0.42意味着在所有订单中4%的订单同时买了这两样买手机壳的订单里有42%也买了钢化膜。这种规则可以直接给运营做捆绑套餐参考。2.4 关联规则的坑商品粒度和季节干扰我用关联规则踩过最多的坑有三个。第一个坑是商品粒度问题。直接用item_id挖会挖出一堆赠品A→电池B这种完全没有运营意义的规则。我的做法是先在category粒度上挖一轮找到大类目之间的强关联再从中选取重点类目下钻到子类甚至单品。这样既控制了频繁项集规模又保证了业务可解释性。第二个坑是季节性干扰。夏天挖出的防晒霜→冰袖提升度很高看起来很漂亮但到了秋冬季节这条规则完全不成立。解决方法是按月份或者按季节分别建模然后对比同一规则在不同时间段的表现。如果一条规则只在某个月显著就不能作为全年运营策略。第三个坑是因果误读。关联规则只能说明一起出现不能说明谁导致谁。比如婴儿纸尿裤→啤酒这种野史级规则即使数据上支撑也没有任何因果关系。给业务方出报告时必须把相关和因果的边界讲清楚否则很容易做出错误决策。3. 分类实战预测用户是否再次购买3.1 先定义复购这个标签分类建模的第一步是定义标签这一步决定了整个模型的上限。我的定义是以当前时间点为基准用户在之前90天的行为特征做自变量未来30天内是否下单作为标签y。这里要特别注意时间窗口的划分防止数据泄漏。比如用户A在1月1日到3月31日之间有活跃行为那么在4月1日到4月30日之间是否下单这个标签才能用。如果我把用户是否领券并核销这种未来信息放进特征模型AUC会虚高得离谱但上了线就会原形毕露。数据里正负样本比例大约1比5也就是大约17%的用户在观察窗口内复购。这个比例不算特别失衡但也不能直接用准确率来评估。3.2 逻辑回归先打底可解释性强、上线快做分类我几乎不会跳过逻辑回归这个基线模型。原因很简单逻辑回归可解释性强、训练快、稳定而且线上部署成本极低。在大数据项目里很多时候业务方要的不是最高的AUC而是这个模型为什么判定这个用户会复购。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import roc_auc_score, classification_report X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) clf LogisticRegression(max_iter1000, class_weightbalanced) clf.fit(X_train, y_train) y_pred_proba clf.predict_proba(X_test)[:, 1] print(AUC:, roc_auc_score(y_test, y_pred_proba)) print(classification_report(y_test, clf.predict(X_test)))class_weightbalanced是处理正负样本比例不均最简单的方式相当于给少数类更高的惩罚权重。逻辑回归自带正则化默认L2对于特征之间存在多重共线性的情况L2正则能压住系数方差这也是我选择逻辑回归而不是朴素线性分类器的原因之一。跑完逻辑回归后我习惯去看每个特征的系数。比如visit_count_last_90d系数为正purchase_count_last_90d系数为正说明最近活跃度越高复购概率越大user_age系数接近0说明年龄对复购的线性贡献不大。这些信息可以直接用来跟业务方沟通。3.3 随机森林和XGBoost什么时候换复杂模型逻辑回归AUC跑出来0.72作为一个基线已经不错。但业务方希望再压一压这时我才会考虑树模型。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, max_depth8, min_samples_leaf50, class_weightbalanced, n_jobs-1, random_state42 ) rf.fit(X_train, y_train) y_pred_proba_rf rf.predict_proba(X_test)[:, 1] print(Random Forest AUC:, roc_auc_score(y_test, y_pred_proba_rf))随机森林的AUC到了0.75比逻辑回归高3个点。这3个点值得换模型吗要看业务场景。如果复购预测是给运营发优惠券用的每多召回一批复购用户都对应真金白银的成本那3个点的AUC提升可能带来明显的ROI变化。如果只是做BI看板上的一个参考指标逻辑回归完全够用。XGBoost在样本量大、特征交互复杂的情况下往往还能再提一两个点但代价是调参成本和过拟合风险都要上来。我用XGBoost时重点盯三个参数learning_rate调到0.01-0.05之间防止过拟合max_depth不要超过6用early_stopping_rounds控制迭代次数。线性模型、随机森林、XGBoost三者要横向对比而不是默认复杂模型一定最好。3.4 分类评估指标准确率在这个场景里会骗人正负样本1比5的问题直接看准确率会得到很有误导性的结论。假如模型把所有用户都预测成不复购准确率也有83%看起来很高但复购用户一个都没找出来这个模型对业务毫无价值。所以分类评估我主要看三个指标AUC不依赖阈值衡量模型把正样本排到负样本前面的概率。0.75意味着75%的概率能把复购用户和未复购用户区分开。精确率和召回率这是一对矛盾。精确率高说明看中的用户多数真的会复购召回率高说明大部分复购用户被找出来了。业务成本决定阈值我不直接用默认的0.5阈值而是基于业务成本去卡。比如给预测概率Top 20%的用户发优惠券假设优惠券成本每张5元即使转化率只有10%只要平均客单价利润够高就能回本那这个成本结构下就可以在这个阈值上下手。3.5 踩过的坑特征泄漏和时间漂移这一节分享两个我在分类项目里真实踩过的坑都是那种看AUC很漂亮上线就翻车的典型。第一个是特征泄漏。早期做复购预测时我把是否领取优惠券并核销放进特征矩阵AUC直接到了0.91。后来仔细检查才发现核销行为本身发生在标签窗口内等于是用未来信息预测未来当然准。这种问题在代码里极难发现因为特征名看起来很合理。我的解决方法是建立一份特征清单字段后面标注数据产生的日期凡是产生时间晚于标签定义时间点的字段一律剔除。第二个是时间漂移。用去年3月到5月的数据训练测试集也是同期数据AUC稳定在0.75。但用同样的模型预测今年同期数据AUC掉到0.65。原因很简单用户的消费习惯、平台的活动节奏都在变。对这类问题没有一劳永逸的办法最实用的就是建立定时重训机制每月或每季度用最新数据重新拟合一次模型同时监控特征分布漂移。4. 回归实战预测用户未来90天消费金额4.1 回归和分类的差异标签形态不同建模逻辑也不同复购预测只能回答用户会不会买业务方接下来一定还会问如果买大概买多少。这时候就要上回归模型。两者最大的区别是标签的形态分类的y是离散的0/1回归的y是连续值而且消费金额这类标签往往严重右偏少数高消费用户拖出长长的尾巴。我见过很多人不处理这个分布直接把原始金额丢给线性回归结果模型预测值全部向均值附近压缩高价值用户被严重低估。解决办法是对标签做log1p变换。import numpy as np df[amount_log] np.log1p(df[order_amount_sum])log1p就是log(1x)好处是x等于0时结果也是0不会出现log0的报错。训练完成后做预测再用expm1还原成真实金额。这个技巧在消费金额、收入、房价这类长尾目标的回归任务里几乎是必备操作。4.2 特征工程用RFM思路构造行为特征回归模型的特征和分类模型有重合但不能完全一样。我做消费金额预测时会重点构造RFM体系里的特征Recency用户最后一次购买距今天数反映新鲜度。Frequency过去90天购买次数反映活跃度。Monetary过去90天消费总额、平均客单价、最大单笔金额。品类广度过去90天购买了多少个二级品类反映用户需求宽度。访问行为过去90天访问次数、加购次数、收藏次数。人口属性注册天数、年龄、性别。代码示意user_feature df.groupby(user_id).agg( last_purchase_dayslambda x: (pd.Timestamp(2024-04-01) - x.max()).days, purchase_count(order_id, count), amount_sum(order_amount, sum), amount_mean(order_amount, mean), category_count(category, nunique) ).reset_index()这些特征在做聚合时要小心groupby之后的count、sum、mean这些统计量对缺失值非常敏感需要在清洗阶段处理好。4.3 岭回归和随机森林回归的对比取舍回归模型我通常同时跑几个做对比。线性回归的一个问题是特征之间如果有多重共线性系数估计会很不稳定。岭回归通过加L2正则化把系数往0方向压缩牺牲一点偏差换来更小的方差尤其在特征数量多、相关性高的时候表现稳定。from sklearn.linear_model import Ridge from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 岭回归 ridge Ridge(alpha1.0) ridge.fit(X_train, y_train) y_pred_ridge ridge.predict(X_test) # 随机森林回归 rfr RandomForestRegressor( n_estimators200, max_depth10, min_samples_leaf20, n_jobs-1, random_state42 ) rfr.fit(X_train, y_train) y_pred_rfr rfr.predict(X_test)评估回归模型我不只看R²还会看MAE和RMSE。MAE是每个预测值和真实值差的绝对值的平均RMSE会放大偏差大的样本所以RMSE大于MAE越多说明存在少数预测偏差极大的样本。在消费金额预测里RMSE高往往是那几个高消费用户造成的模型学不到他们的规律这很正常。实际跑下来岭回归的R²大约0.35随机森林回归R²约0.52。如果你只看R²会觉得模型很差但在用户消费行为预测里R²超过0.3就已经有实用价值了因为人的消费行为本身就包含大量随机性。重要的是看排序能力也就是预测值能不能把高消费用户排到前面给运营圈定Top 20%高潜人群。4.4 回归的坑预测值被压缩和过拟合回归任务里我最常遇到的问题是预测值被压缩。原始金额经过log变换后模型在log空间里做均方误差最小化预测值天然会向均值回归。还原到原始金额后高消费用户的预测值普遍偏低低消费用户反而偏高。如果业务需要的是精准金额这个问题无解只能靠更多特征和更复杂的模型去逼近。但大多数场景其实只需要排序比如给未来90天预计消费最高的前20%用户发高价值权益这时候压缩不影响排序直接按预测值从高到低截断即可。我一般会明确告诉业务方这个模型适合做排序而不是做财务预算避免后续扯皮。过拟合在回归里也有体现。随机森林如果不限制max_depth和min_samples_leaf训练集R²能到0.95测试集只有0.4。我习惯从max_depth6、min_samples_leaf30开始调宁可在训练集上舍弃一点表现也要保住测试集的稳定性。5. 聚类实战用户分群与标签画像5.1 聚类前必须做的预处理标准化和降维聚类和前面三类方法最大的不同在于它没有标签可以用来验证对错所以每一步选择都要更加谨慎。K-Means基于欧氏距离计算样本相似度如果特征量纲不一致比如visit_count的数值是几百、user_age是几十、amount是几千距离计算会被量纲大的特征主导。所以聚类前必须做标准化。我习惯用StandardScaler把每个特征转成均值为0、方差为1的分布。标准化之后还有一个问题如果特征维度太多比如做了one-hot编码后维度到了200以上K-Means在高维空间里距离区分度会下降这就是常说的维度灾难。我的做法是先做PCA降维到20-50个主成分保留90%以上的方差再去做K-Means。PCA降维不光是减少计算量更重要的是把噪声特征过滤掉让聚类结果更稳定。5.2 K-Means的K值选择肘部法则加轮廓系数K-Means最让人头疼的是K怎么选。我用的方法有两种结合起来判断。第一种是肘部法则。对不同的K值分别计算SSE簇内平方误差和然后画折线图。随着K增大SSE必然下降但下降速率会越来越慢。找到那个拐点也就是下降速度突然变得平缓的K值就是合理的聚类数。第二种是轮廓系数。轮廓系数衡量样本与自己簇内样本的相似度以及与其他簇样本的差异度取值在-1到1之间越大说明聚类效果越好。轮廓系数不会只跟着K单调变化更适合做参考。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score sse [] silhouette_scores [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, random_state42, n_init10) labels km.fit_predict(X_pca) sse.append(km.inertia_) silhouette_scores.append(silhouette_score(X_pca, labels))从我的项目经验看业务场景下K取3到5个簇最合适。K太大每个簇的人群特征趋于碎片化运营没法为每个簇单独设计策略K太小簇内差异太大画像标签做出来没有区分度。最终我选了K4。5.3 层次聚类Python实现树状图辅助定KK-Means依赖初始质心的选择同一份数据跑几次结果可能有波动。如果对K值拿不准我会用层次聚类辅助判断。层次聚类不要求预先指定K而是生成一棵树状图你可以在任意高度切一刀得到对应数量的聚类。from scipy.cluster.hierarchy import dendrogram, linkage, fcluster from scipy.spatial.distance import pdist import matplotlib.pyplot as plt # 先计算距离矩阵再用Ward方法做层次聚类 Z linkage(X_pca, methodward) # 画树状图 dendrogram(Z, truncate_modelevel, p5) plt.show() # 在树状图上按距离阈值切分成4个簇 labels fcluster(Z, t4, criterionmaxclust)层次聚类的时间复杂度比较高样本量几万以内还能接受到了百万级就很吃力。所以我的用法是在随机抽样的2万条数据上跑层次聚类用树状图确定K然后用K-Means在全量数据上跑。这样既利用了层次聚类直观的优点又避开了它的性能瓶颈。5.4 聚类结果解读给每个簇起个能听懂的名字聚类做完必须回到业务解释否则就是一堆没有意义的编号。我会对每个簇的标准化中心向量做还原统计每个簇在各特征上的均值然后给它们起名字。在这个项目里四个簇的特征非常清晰簇0购买次数多、金额高、访问频繁、注册时间长命名为高活跃高价值用户占比约12%。簇1购买次数中等、客单价中低、品类数较多、注册时间短命名为成长型新客占比约35%。簇2购买次数少、最近购买距今很久、访问次数少命名为沉睡用户占比约28%。簇3购买频次低但单笔金额高、品类窄、复购不稳定命名为高客单低频用户占比约25%。这四类用户对应的运营策略完全不同高活跃高价值用户要维护忠诚度、给专属权益成长型新客要做品类交叉推荐沉睡用户要靠优惠券唤醒高客单低频用户要提升复购频次。聚类结果还可以反过来帮前面的模型做特征工程比如把所属簇作为一个特征喂给分类和回归模型。簇标签相当于把无监督学习的结果变成了有监督模型的输入变量实践中通常能带来一到两个点的AUC提升。6. 全流程串联从数据到业务的最后一公里6.1 三种算法结果如何互相印证分开跑完四种算法只是第一步真正的价值在于让它们互相验证。我在最终交付报告时是这样组织的关联规则挖出了高端手机保护壳快充头的高提升度组合。通过聚类发现这种组合主要集中在簇3高客单低频用户里那么运营策略就不是面向全量用户推送而是精准针对簇3用户设计套餐。分类模型筛选出复购概率Top 20%的用户回归模型在这些人里再叠加一个未来90天消费金额排序最终选出来的用户群从业务上和簇0高活跃高价值用户高度重合说明两类模型从不同角度捕捉到了同一批高价值人群。这就是全流程联动的意义。单看关联规则你只知道商品组合单看聚类你只知道人群差异单看分类和回归你知道谁会买、买多少但不知道怎么去推广。四者联合起来才能在正确的时间、用正确的商品、触达正确的人。6.2 工程化落地的几点经验代码层面数据分析和建模如果用Jupyter Notebook一张张跑到了交付阶段会很痛苦。我现在的习惯是把数据清洗、特征工程、模型训练分别封装成Python函数用统一的配置文件管理参数。模型训练完用joblib保存每个模型附带一份特征列表和当时的评估指标方便回溯。import joblib # 保存模型 joblib.dump(rfr, models/amount_rfr.pkl) joblib.dump(scaler, models/scaler.pkl) joblib.dump(pca, models/pca.pkl)参数实验一定要留记录。我在项目里用了一个简单的CSV文件记录每次实验的模型名称、特征版本、参数组合、AUC或R²这样过两周回来还能知道这个结果是怎么跑出来的。这个习惯帮我避免过很多次重复调参。6.3 最后再分享两句大实话这套流程做完我最深的体会是算法不是越复杂越好业务解释比精确数字重要。逻辑回归AUC比XGBoost低两个点但业务方拿着系数表能自己讲清楚活跃度影响最大这就是好模型。回归R²只有0.4但只要能把高价值用户排到前面业务目标就能达成。另外一定要盯着上线后的数据。模型不是交完报告就结束的用户行为会漂移同一套规则过半年可能完全失效。定期监控特征分布、模型效果该重训就重训这是一套完整流程里真正能长期产生价值的环节。