简介:这份资源是一套面向数据挖掘初学者与电商分析方向学习者的实战案例包,围绕随机森林算法展开,用于探究电商网站推销商品效果的影响因素。案例以真实电商数据为切入点,帮助读者理解从数据预处理、特征选择到模型训练与结果解读的完整分析链路,适合作为课程设计、竞赛练手或数据分析入门的参考素材。压缩包共2个文件,包含1个xlsx数据表与1个py脚本,前者提供建模所需的原始电商数据,后者承载随机森林建模与分析的实现代码,整体约45KB,体量轻便,便于快速上手复现。目前已有104人学习下载,具备一定的参考热度。读者可借助该案例掌握随机森林在电商场景中的落地思路,理解特征重要性评估与推销因素归因方法,并可直接运行脚本对照数据验证结果,为后续独立完成类似数据挖掘项目积累可复用的代码框架与分析经验。
1. 电商推销转化率上不去,随机森林能帮你找到那根最敏感的神经
电商网站每天产生海量行为数据,曝光、点击、加购、下单,每个环节都在说话。但真正让运营和算法同学头疼的是:推销商品的影响因素到底哪个最重要?是价格敏感度、页面停留时长,还是用户历史购买频次?传统AB实验只能验证单一变量,而基于随机森林算法的特征重要性排序,能一次性把几十个维度的贡献度量化出来。这个方案适合有Python基础、手头有用户行为日志的数据分析师或算法工程师,不需要深度学习环境,一台16G内存的笔记本就能跑通全流程。核心价值在于:用集成学习替代拍脑袋,把“我觉得”变成“模型认为”,并且随机森林对缺失值和异常值天然鲁棒,省去大量清洗玄学。接下来我会从数据准备、特征工程、模型调参到结果解读,把这条链路完整拆开,让你能直接复现并迁移到自己的业务场景。
2. 从原始日志到模型输入:电商行为数据的特征工程流水线
2.1 数据字段的取舍与构造逻辑
电商网站的用户行为日志通常包含用户ID、商品ID、时间戳、事件类型(曝光/点击/加购/下单)、页面停留时长、商品价格、品类、用户历史购买次数等。直接把这些字段扔进模型效果往往很差,因为原始日志是长格式,一行一个事件,而随机森林需要的是宽格式,一行一个样本。我一般会以“用户-商品对”为粒度做聚合,构造如下特征:
- 用户侧:历史购买总次数、历史点击率、平均客单价、最近7天活跃天数
- 商品侧:价格、折扣率、品类热度、上架天数
- 交互侧:该用户对该商品的曝光次数、点击次数、加购次数、总停留时长、是否收藏
- 上下文:曝光时段(小时)、是否周末、推荐位类型
目标变量定义为“该用户-商品对是否在曝光后24小时内下单”,二分类问题。注意不要用未来信息,比如用下单后的行为反推,会造成标签泄漏。
2.2 用pandas做聚合与缺失值处理
下面这段代码展示了从原始事件表构造宽表的核心步骤。假设原始表名为events,字段包括user_id, item_id, event_type, timestamp, duration, price, category。
import pandas as pd import numpy as np # 读取原始日志,假设已按时间排序 events = pd.read_csv('ecommerce_events.csv', parse_dates=['timestamp']) # 构造用户-商品对的基础聚合 agg_dict = { 'event_type': [ ('exposure_count', lambda x: (x == 'exposure').sum()), ('click_count', lambda x: (x == 'click').sum()), ('cart_count', lambda x: (x == 'cart').sum()), ('order_count', lambda x: (x == 'order').sum()) ], 'duration': [('total_duration', 'sum'), ('avg_duration', 'mean')], 'price': [('price_mean', 'mean')] } # 分组聚合,注意多级列名处理 grouped = events.groupby(['user_id', 'item_id']).agg(agg_dict) grouped.columns = ['_'.join(col).strip() for col in grouped.columns] grouped = grouped.reset_index() # 补充用户侧历史特征(以天为窗口) user_history = events[events['event_type'] == 'order'].groupby('user_id').agg( user_total_orders=('item_id', 'count'), user_avg_price=('price', 'mean') ).reset_index() # 合并用户特征 df = grouped.merge(user_history, on='user_id', how='left') # 缺失值填充:数值型用中位数,类别型单独处理 num_cols = df.select_dtypes(include=[np.number]).columns df[num_cols] = df[num_cols].fillna(df[num_cols].median()) # 构造标签:该用户-商品对是否有下单记录 df['label'] = (df['order_count'] > 0).astype(int) print(df.shape) print(df['label'].value_counts())这段代码的逻辑说明:先按用户-商品对聚合事件计数和时长统计,再关联用户历史订单特征,最后用中位数填充数值缺失。参数方面,agg_dict里的lambda函数可以替换成自定义函数,比如计算点击率时用click_count / exposure_count,但要注意分母为0的情况,我一般加一个平滑项+1。fillna用中位数而不是均值,是因为电商行为数据长尾分布严重,均值容易被极端值拉偏。标签构造时,如果正样本比例低于5%,需要考虑负采样或调整类别权重,否则模型会偏向预测负类。
2.3 特征缩放与类别编码的取舍
随机森林基于树分裂,不需要对数值特征做标准化,但类别特征必须编码。对于品类、推荐位类型这种低基数类别(少于10个),直接用One-Hot编码;对于高基数类别(如商品ID),我一般用目标编码(Target Encoding)或者频率编码,避免维度爆炸。下面是一个目标编码的示例,注意要用交叉验证防止泄漏。
from sklearn.model_selection import KFold def target_encode(train_df, test_df, col, target, n_splits=5, smoothing=10): """目标编码,带平滑和交叉验证""" kf = KFold(n_splits=n_splits, shuffle=True, random_state=42) train_encoded = np.zeros(len(train_df)) for train_idx, val_idx in kf.split(train_df): X_tr, X_val = train_df.iloc[train_idx], train_df.iloc[val_idx] # 计算训练折的统计量 agg = X_tr.groupby(col)[target].agg(['mean', 'count']) # 平滑:全局均值与类别均值加权 global_mean = X_tr[target].mean() smooth_mean = (agg['mean'] * agg['count'] + global_mean * smoothing) / (agg['count'] + smoothing) train_encoded[val_idx] = X_val[col].map(smooth_mean).fillna(global_mean) # 测试集用全量训练数据统计 agg_full = train_df.groupby(col)[target].agg(['mean', 'count']) global_mean_full = train_df[target].mean() smooth_full = (agg_full['mean'] * agg_full['count'] + global_mean_full * smoothing) / (agg_full['count'] + smoothing) test_encoded = test_df[col].map(smooth_full).fillna(global_mean_full) return train_encoded, test_encoded # 对高基数类别做目标编码 df['category_te'], _ = target_encode(df, df, 'category', 'label')参数说明:smoothing控制平滑强度,值越大越偏向全局均值,适合类别样本少的情况;n_splits一般取5,数据量特别大时取3。注意目标编码必须在训练集内部做交叉验证,测试集用全量训练统计,否则会引入标签泄漏导致线下AUC虚高。如果品类特征本身就有业务含义,也可以保留One-Hot,随机森林对冗余特征不敏感,但维度太高会拖慢训练速度。
3. 随机森林模型训练与调参:从默认参数到业务可解释
3.1 为什么选随机森林而不是XGBoost或逻辑回归
逻辑回归可解释性强但拟合非线性交互能力弱,电商场景里“价格低+停留长+历史购买多”这种组合效应很难用线性模型捕捉。XGBoost效果通常更好,但调参复杂、训练时间长,而且特征重要性输出不如随机森林直观。随机森林的袋外估计(OOB)可以免费做验证,特征重要性基于不纯度减少或排列重要性,对业务方解释友好。我一般先用随机森林快速拿到基线特征重要性,再决定是否上XGBoost做提分。如果数据量在百万级以下,随机森林的性价比最高。
3.2 关键参数的含义与调参顺序
随机森林的主要参数有:
| 参数 | 含义 | 推荐范围 | 调参优先级 |
|---|---|---|---|
| n_estimators | 树的数量 | 100-500 | 高 |
| max_depth | 树最大深度 | 8-20 | 高 |
| min_samples_split | 节点分裂最小样本数 | 2-20 | 中 |
| min_samples_leaf | 叶子节点最小样本数 | 1-10 | 中 |
| max_features | 每次分裂考虑的特征数 | sqrt/0.3-0.5 | 中 |
| class_weight | 类别权重 | balanced | 高(不平衡时) |
调参顺序:先固定n_estimators=200,用网格搜索调max_depth和min_samples_leaf,再调max_features,最后增加n_estimators到300-500。如果正负样本比例超过1:10,设置class_weight='balanced'。不要一上来就调min_samples_split,它和min_samples_leaf有耦合,先调叶子节点更稳定。
3.3 训练代码与OOB验证
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.metrics import roc_auc_score, classification_report # 准备特征和标签 feature_cols = [c for c in df.columns if c not in ['user_id', 'item_id', 'label']] X = df[feature_cols] y = df['label'] # 划分训练集和测试集,stratify保证类别比例 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 基础模型,开启OOB rf = RandomForestClassifier( n_estimators=300, max_depth=12, min_samples_leaf=3, max_features='sqrt', class_weight='balanced', oob_score=True, n_jobs=-1, random_state=42 ) rf.fit(X_train, y_train) # OOB分数 print(f'OOB Score: {rf.oob_score_:.4f}') # 测试集评估 y_pred_proba = rf.predict_proba(X_test)[:, 1] print(f'Test AUC: {roc_auc_score(y_test, y_pred_proba):.4f}') print(classification_report(y_test, rf.predict(X_test)))逻辑说明:oob_score=True让模型在训练时自动用袋外样本评估,省去额外验证集。class_weight='balanced'自动调整权重,适合正样本少的情况。n_jobs=-1用满CPU核心。输出AUC和分类报告后,如果AUC低于0.7,需要回头检查特征工程或增加特征。注意max_features='sqrt'是分类任务默认值,特征数很多时可以试0.3或0.5。
3.4 用排列重要性替代不纯度重要性
随机森林自带的feature_importances_基于不纯度减少,对高基数类别特征有偏。更可靠的是排列重要性(Permutation Importance),它打乱某个特征的值看模型性能下降多少。下面代码展示计算和可视化。
from sklearn.inspection import permutation_importance import matplotlib.pyplot as plt # 在测试集上计算排列重要性,重复10次 perm_imp = permutation_importance( rf, X_test, y_test, n_repeats=10, random_state=42, n_jobs=-1 ) # 整理结果 imp_df = pd.DataFrame({ 'feature': feature_cols, 'importance_mean': perm_imp.importances_mean, 'importance_std': perm_imp.importances_std }).sort_values('importance_mean', ascending=False) # 画前15个特征 top_n = 15 plt.figure(figsize=(10, 6)) plt.barh(imp_df['feature'][:top_n], imp_df['importance_mean'][:top_n], xerr=imp_df['importance_std'][:top_n]) plt.xlabel('Permutation Importance') plt.gca().invert_yaxis() plt.tight_layout() plt.savefig('feature_importance.png', dpi=150)参数说明:n_repeats=10表示每个特征打乱10次取平均,值越大越稳定但越慢。importance_std反映稳定性,如果某特征标准差很大,说明重要性不可靠。排列重要性在测试集上计算,比训练集更真实。如果发现price_mean重要性最高,说明价格是推销转化的核心杠杆,运营可以针对性做折扣策略。
4. 避坑指南:电商随机森林建模的五个血泪教训
4.1 标签泄漏:用未来行为预测过去
现象:线下AUC高达0.95,上线后效果暴跌到0.6。
原因:构造特征时用了下单后的行为,比如“下单后是否评价”被误当成特征,或者聚合时间窗口包含了标签发生后的数据。
解决:严格按时间切分训练集和测试集,特征计算只使用标签发生时间之前的数据。我一般会加一个feature_cutoff_time,所有聚合操作都限制在这个时间点之前。
4.2 类别特征目标编码泄漏
现象:训练集AUC比测试集高0.1以上,模型过拟合。
原因:目标编码时用了全量数据统计,没有做交叉验证,导致训练集里每个类别的编码值包含了自身标签信息。
解决:用K折交叉验证计算训练集编码,测试集用全量训练统计。上面2.3节的代码已经处理了这个问题,但要注意smoothing参数不能太小,否则类别样本少时编码值波动大。
4.3 正负样本极度不平衡导致模型“偷懒”
现象:模型把所有样本预测为负类,准确率99%但召回率为0。
原因:电商下单转化率通常只有1%-5%,随机森林默认的基尼系数对多数类友好。
解决:设置class_weight='balanced',或者用imblearn做SMOTE过采样。但SMOTE对高维稀疏数据效果一般,我更推荐调整类别权重加阈值移动。预测时不要用0.5作为阈值,用验证集上的F1最大化来确定最佳阈值。
4.4 特征重要性被高基数类别带偏
现象:item_id或user_id这类高基数特征排在重要性第一位,但业务上无法解释。
原因:基于不纯度的重要性对高基数特征有偏好,因为分裂点多,容易过拟合。
解决:不要直接把ID类特征放入模型,用目标编码或频率编码替代。如果已经放入,改用排列重要性评估,并且检查高基数特征在排列重要性中是否仍然靠前。我一般会把ID类特征单独做嵌入,不参与树分裂。
4.5 忽略特征之间的交互效应
现象:单看特征重要性,价格和停留时长都不高,但业务上明明很重要。
原因:随机森林能捕捉交互,但特征重要性是边际贡献,如果两个特征强相关,重要性会被稀释。
解决:用SHAP值做局部解释,看每个样本的特征贡献。或者手动构造交互特征,比如price * duration、click_count / exposure_count,再重新训练。SHAP的TreeExplainer对随机森林支持很好,计算速度也快。
5. 把模型结论翻译成运营动作:特征重要性排序后的落地技巧
模型跑完只是第一步,真正值钱的是把特征重要性翻译成可执行的运营策略。假设排列重要性显示前五名是:历史购买次数、折扣率、页面停留时长、曝光次数、品类热度。那么运营动作可以这样拆:
- 历史购买次数高但未下单的用户,推送高折扣券,因为这类用户对价格敏感且信任平台。
- 折扣率对转化影响大,但边际效应递减,可以画部分依赖图(PDP)找到折扣率的最优区间,比如满减20%比满减30%的转化提升更明显。
- 页面停留时长超过30秒但未点击的用户,可能是详情页信息不足,优化商品描述和评价展示。
下面是一个用PDP找折扣率最优区间的代码片段。
from sklearn.inspection import PartialDependenceDisplay # 假设折扣率特征名为'discount_rate' fig, ax = plt.subplots(figsize=(8, 5)) PartialDependenceDisplay.from_estimator( rf, X_train, features=['discount_rate'], kind='average', ax=ax, grid_resolution=50 ) ax.set_title('折扣率对下单概率的部分依赖') plt.tight_layout() plt.savefig('pdp_discount.png', dpi=150)参数说明:grid_resolution=50表示在折扣率范围内取50个点计算平均预测概率。如果曲线在某个点后走平,说明折扣再加大也带不来转化,运营可以省下预算。注意PDP假设特征之间独立,如果折扣率和价格强相关,需要改用ICE图看个体差异。
另一个技巧是用SHAP值做用户分群。对每个用户计算SHAP值,然后聚类,把“价格敏感型”“冲动型”“忠诚型”分开,针对不同群体设计推销策略。SHAP的summary_plot能直接画出全局特征贡献,比排列重要性更细粒度。
最后说一个我自己的习惯:每次模型上线前,我会挑100个样本人工核对特征和预测结果,看看有没有反常识的案例。有一次发现模型给一个从不购物的用户推高价商品,查特征发现是“历史购买次数”缺失被填充成了中位数,而中位数偏高。后来改成用0填充并加一个缺失指示特征,AUC提升了0.02。这个教训告诉我,缺失值填充不是小事,尤其是当缺失本身有业务含义时。希望帮到你。
本文还有配套的精品资源,点击获取