尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

随机森林实战:零售库存预测完整数据挖掘链路解析

发布时间:2026/9/28 14:18:38

资讯中心
01
ARTICLE

随机森林实战:零售库存预测完整数据挖掘链路解析

随机森林实战:零售库存预测完整数据挖掘链路解析
简介这是一份面向数据挖掘初学者的零售店库存分析与预测实战资源围绕随机森林模型完成从数据探索、特征处理到模型构建与库存预测的完整流程并借助可视化呈现库存规律与预测结果。压缩包内共3个文件包含Jupyter Notebook代码、零售店库存CSV数据集以及渲染后的HTML分析报告整体仅2.31MB便于下载后快速开启实验。目前已有109人学习下载。资源提供可直接运行的完整代码与配套数据读者可对照Notebook逐步理解建模思路替换自有数据即可复用HTML报告集中展示关键图表与结论既方便复盘也可用作项目汇报素材。此外作者将数据、代码与结果呈现分开组织结构清晰既能帮助入门者掌握随机森林在库存场景中的实际应用也能为后续调优和扩展提供基础模板。1. 随机森林做零售库存预测这份资源里藏着一条完整的数据挖掘链路周五下午运营同事把一份门店库存表甩给我说再不预测下周的缺货风险仓库调配就要开天窗。这类活儿在零售数据里太常见了——库存数据躺着销量数据躺着可要回答“下个周期各门店该备多少货”就得把清洗、特征工程、建模、可视化完整跑一遍。这份基于随机森林模型的零售店库存可视化与预测资源正好就是一条能直接照抄的完整链路retail_store_inventory.csv提供原始数据分析.ipynb把从数据清洗到库存预测的每一步都摊开分析.html则是跑完之后的完整可视化结果。适合正在做数据挖掘课程设计、刚接触随机森林回归、或者想看看零售库存预测全流程的从业者。接下来我按自己拆这个项目的顺序把数据怎么处理、模型参数怎么定、哪些地方容易翻车逐段讲清楚。2. 数据准备与特征工程retail_store_inventory.csv 的字段拆解与清洗2.1 先看懂库存数据的字段结构拿到retail_store_inventory.csv第一件事不是建模而是把字段含义摸清楚。零售库存数据最常见的结构是每一行代表某个门店、某个 SKU、某一天的库存与销售快照。我一般先用info()和head()把数据形状和字段类型过一遍确认哪些是数值型、哪些是类别型、日期字段能不能直接解析。import pandas as pd import numpy as np df pd.read_csv(retail_store_inventory.csv, parse_dates[date]) print(df.shape) print(df.info()) print(df.head(10))parse_dates[date]这一步很关键如果漏掉后面做时间特征提取时dt.weekday这类操作会直接报错。看info()的输出重点确认三件事有没有空值列、类别字段是不是被误读成数值、日期有没有被解析成datetime64类型。这份数据里比较典型的字段包括store_id、category、stock_qty、sales_qty、inbound_qty、price、promotion_flag、holiday_flag其中store_id和category是类别型建模前要处理其余大多是数值型。注意一点库存表和销量表最常犯的错是把stock_qty当目标值。库存预测在零售场景里通常预测的是“下一个周期卖出多少”也就是sales_qty因为销量才是决定补货量的核心。库存量受盘点周期和进货策略影响直接预测它的意义远不如预测销量。2.2 缺失值与异常值库存为负不等于数据错误零售数据脏得很有规律。销量字段经常因为门店当天没上传数据而空缺库存字段偶尔会出现负数——这通常不是录入错误而是盘点差异实物盘亏或者系统冲销。我在这类项目里的处理习惯是销量缺失用同门店同品类的中位数回填库存负数用clip(lower0)截断而不是直接删行。# 先看缺失情况 print(df.isnull().sum()) # 销量缺失用同门店同品类的中位数回填避免整体均值把门店差异抹平 fill_map df.groupby([store_id, category])[sales_qty].transform(median) df[sales_qty] df[sales_qty].fillna(fill_map) # 库存为负按 0 处理比直接删除更稳妥删除会破坏门店-品类样本结构 df[stock_qty] df[stock_qty].clip(lower0) # 价格和进货量如果有极端空值直接删除该行因为它们不是主要特征 df df.dropna(subset[price, inbound_qty])回填用.transform(median)而不是fillna(df[sales_qty].median())区别在于前者是分组中位数保留了门店之间的销售水平差异。如果某家门店销量普遍是 50 件另一家是 200 件用全局中位数会把小店的数据拉偏。clip(lower0)处理负库存也有讲究负数不是有效训练样本强行保留会让模型学到“库存越负销量越高”这种假规律。2.3 日期与类别特征把时间维度拆成模型能吃的形状随机森林本身不吃日期对象必须把时间拆成数值特征。最常用的拆法是星期几、月份、是否周末、距数据起始日天数后面这个特征对捕捉长期趋势特别有用——库存数据往往有缓慢的爬坡或下滑模型如果没有时间位置概念纯靠周期性特征很难拟合趋势。# 时间特征周期性和趋势性要分开 df[weekday] df[date].dt.weekday df[month] df[date].dt.month df[day] df[date].dt.day df[is_weekend] (df[weekday] 5).astype(int) df[days_since_start] (df[date] - df[date].min()).dt.days # 类别字段随机森林用数值编码也能跑但 get_dummies 更稳 df pd.get_dummies(df, columns[store_id, category]) print(df.shape) print(df.columns.tolist())is_weekend看起来是weekday的重复信息但在零售场景里它单独存在是有价值的——周末的销售形态和周中完全不一样模型可以更直接地利用这个信号。days_since_start存在的意义是给模型一个“时间坐标”否则它对 1 月和 6 月的销量差异完全没有感知。get_dummies之后列数会膨胀如果门店数量特别多可以考虑用目标编码代替 one-hot但在这个数据集规模下 one-hot 完全够用。3. 随机森林建模从决策树集成到库存回归预测3.1 为什么选随机森林而不是线性回归或 XGBoost零售库存数据里销量和库存、促销、节假日之间几乎都是非线性关系促销日的销量可能比平时高三四倍节假日之前库存会异常波动这种拐点用线性回归很难刻画。随机森林的优势在于它对特征尺度不敏感不需要做归一化能自动捕捉特征间的交互而且对缺失值和异常值有一定的鲁棒性——这些特性正好匹配零售数据的脏乱差。和 XGBoost 比随机森林的调参压力小得多没有学习率、树深度和正则项之间的联动调优数据量在几万到几十万行这个级别时随机森林的训练速度和效果都足够用。我一般把它当作库存预测的基线模型先跑一版看效果如果精度不够再上梯度提升。随机森林和决策树的区别在于单棵决策树容易过拟合对训练数据的噪声极度敏感随机森林通过随机抽样样本和随机抽样特征训练多棵树再取平均把单棵树的方差压下来了泛化能力明显更强。3.2 训练集 / 测试集切分与基准模型建模第一步是确定特征和目标。特征用上一章处理好的数据目标是sales_qty。这里有个容易被忽视的细节store_id和category经过 one-hot 编码后会产生大量稀疏列随机森林对这种稀疏输入处理得不错但训练前还是要确认特征矩阵没有全零列否则会浪费计算资源。from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error feature_cols [c for c in df.columns if c not in [date, sales_qty]] X df[feature_cols].select_dtypes(include[np.number]) y df[sales_qty] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor( n_estimators200, max_depth12, min_samples_leaf3, random_state42, n_jobs-1 ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(MAE:, mean_absolute_error(y_test, y_pred)) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse))select_dtypes(include[np.number])是保险操作防止前面编码环节漏掉非数值列导致fit直接报错。MAE在库存预测里比RMSE更有解释性——它直接告诉你“平均每天每家门店的销量预测偏差是几件”这是采购和运营人员能听懂的指标。RMSE对大的偏差更敏感如果个别门店出现极端销量RMSE会被拉高这时候要看是不是特征里缺了什么信息。参数这块n_estimators200在这个数据规模上已经足够再多树收敛收益很小但训练时间线性增加。max_depth12是防止单棵树学得过细零售数据噪声大树太深容易把促销日的偶然波动当成规律。min_samples_leaf3强制叶子节点至少 3 个样本进一步平滑预测值。3.3 参数调优n_estimators 和 max_depth 的实际影响随机森林最值得调的三个参数是n_estimators、max_depth、min_samples_leaf另外max_features偶尔需要动但默认的1.0回归任务用全部特征在特征数不多时通常不用改。我习惯用网格搜索跑一遍重点看两个维度树的数量增加是否让验证集误差持续下降以及深度控制在多少时验证集误差不再改善。from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [8, 12, 16], min_samples_leaf: [2, 3, 5] } grid GridSearchCV( RandomForestRegressor(random_state42, n_jobs-1), param_grid, cv5, scoringneg_mean_absolute_error, verbose1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)网格搜索的scoring我用neg_mean_absolute_error而不是默认的R²原因和前面一样库存预测的业务指标是平均偏差几件不是拟合优度。cv5会做五折交叉验证每折 80% 训练 20% 验证对数据量中等的情况比较稳。跑完看best_params_通常会发现max_depth在 10 到 14 之间就够再深就是过拟合的甜蜜区——训练集误差一直降验证集误差反而回升。参数作用调优经验n_estimators树的数量越多越稳但收益递减200 够用超过 500 提升很小max_depth单棵树最大深度控制模型复杂度零售数据 8~16 之间过深必过拟合min_samples_leaf叶子节点最少样本数平滑预测2~5 合适太大则预测过于保守max_features每棵树随机抽几个特征特征少于 30 时默认即可n_jobs并行核数设为 -1 用满所有核4. 库存可视化分布洞察、相关性分析与预测结果呈现4.1 EDA 可视化先看数据长什么样再建模建模之前先画图这条习惯帮我避过很多坑。库存数据最常见的画像就是长尾分布大部分 SKU 备货不多少数爆款库存量巨大。如果一开始不看分布直接建模模型很容易被少数大库存样本带偏预测结果偏向高估。可视化这一步就是把异常分布暴露出来。import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(12, 4.5)) # 左图库存分布确认是否长尾 sns.histplot(df[stock_qty], bins50, axaxes[0]) axes[0].set_title(库存量分布) # 右图库存与销量的散点抽样画避免点太多糊成一团 sample df.sample(5000, random_state1) sns.scatterplot(datasample, xstock_qty, ysales_qty, alpha0.3, axaxes[1]) axes[1].set_title(库存与销量的关系) plt.tight_layout() plt.show()散点图如果呈现明显的正相关说明库存充足的门店销量确实更高但也可能是“销量高的门店备货多”这个业务逻辑造成的——因果关系要小心解读。长尾分布的处理方式通常是对库存量取对数或者做分箱但随机森林对单调变换不敏感所以这一步不会改变模型效果主要是让你对数据范围心里有数。4.2 特征重要性哪些字段在驱动预测随机森林有一个天然的诊断工具feature_importances_。它统计的是每个特征在所有决策树中被选作分裂节点时带来的不纯度下降总和归一化之后就是重要性占比。这个指标不能当绝对真理看但用来排查特征是否失效很有效。importance pd.Series(model.feature_importances_, indexX.columns) importance importance.sort_values(ascendingTrue) # 横向条形图方便读特征名 importance.tail(15).plot(kindbarh, figsize(10, 7)) plt.title(随机森林特征重要性 Top15) plt.tight_layout() plt.show()看重要性排序时重点观察三件事days_since_start是否靠前、promotion_flag有没有排进前十、one-hot 出来的门店特征是不是霸占了榜单。days_since_start靠前说明销量存在明显时间趋势这时候要考虑是不是有季节性因素没被拆出来promotion_flag不靠前则说明要么促销对销量影响确实小要么促销标记字段本身录得不准。如果门店 one-hot 特征占了前十名的大半通常意味着模型在“背门店 ID”而不是在学习业务规律这时候应该把门店特征换成门店规模、商圈类型等业务属性。4.3 预测 vs 实际折线图与 HTML 导出模型训练完、指标也看了最后一步是把预测结果画出来。库存预测最好画时间序列对比图横轴是日期纵轴是销量实际值和预测值两条折线叠在一起哪里偏了、偏移方向是什么一眼就能看出来。比只看 MAE 数字直观得多。# 预测测试集 y_pred model.predict(X_test) # 把预测结果拼回原始 DataFrame按日期排序后画折线 result X_test.copy() result[actual] y_test.values result[pred] y_pred result[date] df.loc[result.index, date].values result result.sort_values(date) # 只画一部分全量画太密看不清 sample_result result.iloc[:200] plt.figure(figsize(14, 5)) plt.plot(sample_result[date], sample_result[actual], label实际销量, linewidth2) plt.plot(sample_result[date], sample_result[pred], label预测销量, linewidth2, alpha0.8) plt.xlabel(日期) plt.ylabel(销量) plt.legend() plt.title(随机森林库存预测实际与预测对比) plt.xticks(rotation45) plt.tight_layout() plt.show() # 导出 HTML 报告方便分享给不看代码的同事 result[[date, actual, pred]].to_html(分析.html, indexFalse)折线图的读数方法如果预测值整体滞后于实际值——实际涨了预测还平着通常是时间特征不足模型没有把最近几天的动量带进来可以考虑加上“前 7 天销量均值”作为滞后特征。如果预测值整体偏低多半是训练集里促销日样本太少模型没学够。to_html导出的文件就是资源里那个分析.html的生成方式把表格和图表都嵌进去运营同事直接浏览器打开就能看不用装 Python 环境。5. 避坑与常见问题零售库存预测里的五个翻车现场5.1 随机切分导致数据泄漏预测结果虚高现象MAE 只有个位数模型表现好得不像话但一上线预测下周销量就全偏了。原因零售数据本质是时间序列同一天的门店样本之间存在强相关性train_test_split随机切分时测试集里混进了训练集同一天的数据模型等于“见过”正确答案。解决按时间切分用前 80% 的时间段做训练后 20% 做验证模拟真实的预测场景。# 按时间切分代替随机切分 split_date df[date].quantile(0.8) mask df[date] split_date X_train, X_test X[mask], X[~mask] y_train, y_test y[mask], y[~mask]5.2 缺失值直接 dropna 导致门店样本失衡现象训练出来的模型对某些门店的预测误差特别大。原因dropna把销量缺失的行整体删掉如果某家小店恰好有三分之一的天数没上报数据删完之后它的样本量比其他店少一截模型对这家店的规律学不到位。解决缺失列如果是销量用同门店同品类中位数回填如果是价格这类辅助特征直接删除该行对整体影响不大。5.3 促销日预测偏低模型把活动当成噪声现象平时误差还行一到促销日预测值明显低于实际销量。原因促销日在全量数据里占比只有 5% 左右树模型在分裂时优先用能区分大部分样本的特征促销信号被淹没了。解决除了promotion_flag布尔值再加一个“距离上次促销天数”的数值特征让模型能感知促销的累积效应。另外可以尝试对促销样本做加权或者单独给促销日建一个子模型。5.4 特征重要性被高基数门店特征带偏现象特征重要性 Top10 里七八个是store_id_xxx业务特征全部靠后。原因one-hot 之后门店数量几十个每棵树的每次分裂都在几百个候选特征里挑门店特征虽然不能泛化到新门店但它确实能精确区分训练样本。解决把 one-hot 门店特征换成门店聚合特征比如门店平均销量、门店品类数、门店所在区域这些特征在新门店上仍然可用。5.5 预测值出现负数库存预测直接失真现象model.predict的输出出现 -5、-3 这种负数销售预测物理上不成立。原因随机森林回归的预测值是叶子节点样本均值如果叶子节点里混入了销量为 0 的样本和销量很小的样本均值可能被压到接近 0但不会到负数——负数通常是因为特征里包含负值模型在树分裂时捕捉到了“某个特征越负销量越差”的规律。解决库存和销量特征统一做非负处理预测结果用np.clip(y_pred, 0, None)截断不要放任负数进入报表。6. 预测结果验证残差分析、滚动回测与一个让结果更可信的习惯模型不是跑完就结束验证这一步才是决定能不能上线发布的关键。我最常用的验证手段是残差分析画一张预测值 vs 残差的散点图横轴是预测值纵轴是实际值减预测值。如果残差围绕 0 均匀分布说明模型的误差在各销量区间都比较稳定如果残差呈现明显的喇叭形——销量越高残差越大说明模型对高销量门店的系统性低估这时候需要考虑增加特征或者做分层建模。residual y_test - y_pred plt.figure(figsize(8, 5)) plt.scatter(y_pred, residual, alpha0.3) plt.axhline(y0, colorred, linestyle--) plt.xlabel(预测销量) plt.ylabel(残差实际 - 预测) plt.title(残差分布检查) plt.show()除了残差图滚动回测是更贴近实际预测姿势的验证方式。真实场景里你是用前 N 周的数据预测下一周不是用随机抽样的 80% 数据预测另外 20%。滚动回测的做法是固定窗口长度比如用 12 周数据训练预测第 13 周然后窗口整体后移一周循环到数据末尾把每一周的预测拼起来和真实值对比。这个流程比单次切分更接近运营节奏也更早暴露数据漂移问题——如果模型在回测最后几周的误差越来越大说明业务规律已经变了需要重新训练。# 滚动回测每次用过去 12 周数据预测下一周 df_sorted df.sort_values(date) weeks df_sorted[date].dt.to_period(W).unique() for i in range(12, len(weeks)): train_mask df_sorted[date].dt.to_period(W).isin(weeks[i-12:i]) test_mask df_sorted[date].dt.to_period(W) weeks[i] # 训练 预测 记录误差 # 这一步可以循环收集所有周次的 MAE从那时起我每次跑库存预测都强制自己先走一遍时间切分和残差检查再做结论。调参调得再好验证方式是错的模型上线就是给自己埋雷。随机森林在零售场景里的上限取决于特征工程而不是树木数量——把业务里那些“经验之谈”翻译成特征列才是数据挖掘真正值钱的部分。这份资源把从数据清洗到可视化的完整链路都摊开了照着跑一遍以上这些坑基本都能踩到并弄明白。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。