☰
Python实现小超市供销存优化:数据清洗到补货策略全解析
2026/10/4 7:40:48 网站建设 项目流程

简介:这是2017年BDCI“小超市供销存管理优化”赛题的Python源代码资源,面向希望掌握机器学习在库存与销售预测场景落地方法的开发者、数据竞赛选手与相关专业学生。资源包为zip压缩格式,共20个文件,包含11个csv数据文件、5个Python脚本(model1.py、model2.py、model3.py及output.py等)、2个docx项目文档、1个pptx汇报材料和1个txt运行顺序说明,整体大小仅1.22MB,轻量但内容完整。已有308人学习下载。从赛题描述看,资源覆盖了特征提取、pandas数据清洗、scikit-learn模型训练与评估、可视化模型解释、预测补货优化等完整流程;目录将数据与程序分离,配套运行顺序说明,便于对照项目报告一步步复盘。无论是备战数据挖掘竞赛,还是学习真实业务中的预测建模,都能从中获得从原始数据到最终结论的完整示范,是一套兼具教学与参考价值的实战资源。 2017年BDCI赛题里有一个特别接地气的题目——小超市供销存管理优化。第一次看到这个题,我第一反应是:这哪是比赛,这不就是把我楼下小卖部老板每天挠头的问题搬上考场了吗?一家小超市,几十种商品,每天都有销售流水,供应商给的供货价格和到货周期还不一样,怎么订货、订多少、多久订一次,直接决定月底是赚钱还是压了一堆卖不动的货。

这道题赛方给了大量历史销售数据和供应商信息,要求选手设计一套补货、库存管理方案,让超市在满足顾客需求的前提下尽可能提高利润。我这次写的是一个基于Python的完整复现版本,从数据清洗、库存模拟到策略求值全部在一个工程里完成,纯Python源码,不依赖额外的商用求解器。文章适合正在学Python数据分析、对供应链优化感兴趣的人参考,也适合竞赛党直接拿来当baseline跑通流程。

这里先总结一下这套源码的整体能力:它能读入销售记录和供应商表,自动聚合出每日销量,模拟不同补货策略下的库存变化,最终输出一份按商品、按日期的订货计划,并给出对应的利润、缺货天数、期末库存等指标。下面我把整个项目的思路、建模过程和代码实现完整拆开讲。

1. 赛题复盘:先搞清楚“供销存”到底要算什么

1.1 赛题背景与目标重述

小超市的供销存问题,核心是三件事:供应、销售和库存。“供应”指从哪家供应商、以什么价格、多长时间到一批货;“销售”指每天每个商品卖出去多少;“库存”则是这两者之间的缓冲,既能留住顾客,又不能压太多资金。赛题给的正是这三块原始数据,但原始数据不会告诉你什么叫“优化”,这个定义需要参赛者自己完成。

我复现时把目标定义成“最大化经营净利润”,这是最贴近门店老板感受的指标。净利润由四部分组成:销售收入减去进货成本,再减去缺货损失和库存持有成本。缺货损失不是真实支出,但它代表顾客想买却没货、流失掉的那部分利润,必须扣;库存持有成本则包含资金占用和可能的商品损耗,同样要扣。这个口径一旦定下来,后面所有算法都围绕它转,否则算出来的“最优”方案根本没有可比性。

1.2 核心约束与优化方向拆解

决定优化方案之前,不能忽略约束。我总结了几类在赛题和真实门店中都会出现的限制:

  • 库存不能为负,缺货时只能接受损失;
  • 每个供应商都有自己的供货能力与到货周期,不是想订多少就能立刻到;
  • 单次补货通常有最小起订量,或者为了分摊配送成本,要限制补货次数;
  • 商品之间有明显的销售差异,畅销品和滞销品不能共用一套补货规则。

这些约束让问题从“多订点就行”变成了带限制的离散优化问题。再加上时间维度,完整决策空间就是“每个时间点要不要下单、下多少”。这属于典型的有限时域库存优化问题,也是很多供应链管理系统底层的核心逻辑。赛题数据规模不算大,但决策空间组合很多,直接穷举不现实,需要设计合理的近似策略。

1.3 为什么选用Python而不是其他工具

选择Python不是因为“网上教程多”,而是这个赛题的数据量和运算量正好在Python的舒适区里。几十种商品、几百天销售流水,这个规模用纯循环都能跑完,更不用说用pandas做聚合后速度非常快。Python的另一个优势是生态完整,从数据读取、分析、模拟到可视化一站式解决,写出来的代码可读性也高,方便赛后复盘和调参数。

如果换到更大的场景,比如上千万条订单流水,那我会考虑用C++或者分布式框架。但对门店级的供销存优化,Python就是最合适的工具,能让你把80%的精力放在策略设计而不是工程细节上。

2. 数据预处理与指标口径:决定成败的第一道关

2.1 销售流水清洗与日销量聚合

赛题给的销售流水通常是一行一条购买记录,包含商品编号、销售日期、销售数量、销售金额等信息。但原始数据直接用不了,常见问题有三个:同一天同一商品有多条记录;日期字段格式不统一;部分日期可能完全没有销售记录。

我的处理方式是先用pandas把“商品编号+日期”作为分组键,对销售数量做sum聚合,得到一个标准的长表。然后对每个商品生成完整日期序列,从销售开始日到结束日逐天补齐,没有销售的日子销量记为0。这一步非常关键,因为后续库存模拟是按天滚动的,如果某天缺失记录被当成“不存在”而不是“没卖出去”,补货决策就会错位。

2.2 供应商信息整理与到货周期映射

供应商数据相对简单,通常包含供应商编号、商品编号、供货单价、供货能力、到货周期等信息。这里最需要小心的是“到货周期”。它指的是从下单到商品实际入库的天数,比如到货周期为2天,那么第1天下的单,第3天才能进入可用库存。

我在代码里用字典把“商品编号-供应商”映射成到货周期和供货价,再根据商品从哪家供应商进货来决定模拟时的补货提前期。一个小坑是,同一商品可能对应多家供应商,报价和周期都不一样。复现时我默认选择到货周期短的那家,因为对门店来说,缺货损失往往比单价差异更敏感,这个取舍在后面的参数敏感性测试里也能看出来。

2.3 成本参数设定与口径统一

建模前需要把利润计算要用到的参数全部定义清楚。下面是我在复现中使用的一组示例参数,实际跑代码时可以按业务情况调整:

参数含义示例取值备注
成本价商品进货单价3.20元来自供应商报价
零售价商品销售单价5.00元来自销售流水
持货成本每件商品每天持有成本0.10元按货值百分比估算
缺货损失缺货一件对应毛利损失1.80元按零售价减成本价
最小起订量单次最少采购量10件可来自供应商规则
到货周期下单到入库天数2天不同供应商不同

参数口径统一非常重要。比如“持货成本”,如果按货值的0.5%计算,低价商品和高价商品每天占用成本完全不同,不能用一个固定数字糊弄过去。我在代码里使用“持货成本=成本价×持货费率”的方式计算,这样更贴近实际情况。

3. 建模思路:从“经验判断”到“可计算的决策规则”

3.1 三类成本如何平衡

供销存优化的本质是平衡三类成本:订货成本、持有成本、缺货成本。订货成本指的是每次下单产生的固定费用,包括人力、配送等。如果订货次数太多,固定费用会拖累利润;如果订货次数太少,单次订货量会很大,库存持有成本上升;如果订得太少太频繁,缺货风险降低但成本高;如果完全不控制,又会频繁缺货流失顾客。这三者的平衡点就是最优策略所在。

一个直观的类比是家里的冰箱采购:每天都去菜市场买最新鲜的菜,消耗时间成本;一次买一周的菜,有的菜放到坏掉就是持有成本;周末想做饭却发现缺菜,那就是缺货成本。超市补货和这个逻辑完全一样,只是需要把“感觉”变成“公式”。

3.2 库存滚动模拟与利润计算

我采用按天滚动模拟的方式评估任何一套补货策略。基本逻辑是:每天先接收“按到货周期应该到达的订单量”,然后计算当天库存;把当天销量和库存比较,库存够就正常销售,库存不够就记一次缺货并损失对应毛利;最后扣除当天所有库存的持有成本。模拟结束后,把所有天的收入和成本累加,得到总利润。

这个过程的代码逻辑可以表示成下面这样:

def simulate_inventory(sales, order_plan, init_stock=20, lead_time=2, price=5.0, cost=3.2, holding_fee=0.1, shortage_loss=1.8): stock = init_stock shortage_days = 0 total_profit = 0.0 total_sales = 0 for day in range(len(sales)): # 当天到货 stock += order_plan.get(day - lead_time, 0) demand = sales[day] # 实际销售量由库存上限决定 sold = min(stock, demand) shortage = demand - sold if shortage > 0: shortage_days += 1 total_profit -= shortage * shortage_loss stock -= sold total_sales += sold total_profit += sold * (price - cost) - stock * holding_fee return total_profit, shortage_days, total_sales, stock

这段函数是整个项目的地基。后续不管用启发式算法、网格搜索还是机器学习,最终都要落到这个评估函数上,用利润数字检验策略好坏。

3.3 用启发式搜索逼近最优解

确定了评估函数之后,下一步是找一套好的订货计划。我没有直接用商用求解器,因为赛题规模不算大,用启发式搜索反而更容易理解和调试。我选择的是“定期补货+安全库存”策略,再对两个关键参数做网格搜索:

  • 补货周期:每隔多少天检查一次库存并下单;
  • 目标库存系数:按未来多少天的预测销量来设定目标库存。

补货计划的生成逻辑是:每到补货日,统计当前库存水平,预测未来一个到货周期加补货周期内的需求,用预测值减去当前库存,再向上取整到最小订货批量的整数倍。如果计算出的订货量大于零,就生成一条补货记录。

import math def build_order_plan(sales, check_interval=7, horizon=9, min_order=10, pack_size=10, init_stock=20, lead_time=2): plan = {} stock = init_stock for day in range(0, len(sales), check_interval): # 预测未来 horizon 天需求 future_demand = sum(sales[day: day + horizon]) target = max(future_demand, min_order) order_qty = max(0, math.ceil((target - stock) / pack_size) * pack_size) if order_qty > 0: plan[day] = order_qty stock += order_qty for d in range(day, min(day + check_interval, len(sales))): stock = max(stock - sales[d], 0) return plan

注意这段代码是简化版,实际运行时需要在每一天都更新库存,特别是到货周期内不能重复下单。但思路很清晰:策略由两个数字决定,然后用网格搜索找到利润最高的参数组合。用这种办法,几个小时内就能把参数空间扫一遍,在赛题数据规模下完全可行。

4. 代码架构与核心功能实现

4.1 模块划分与工程结构

整个项目源码保持了模块化,方便把“数据处理”“策略搜索”“结果分析”三个环节分开。我的工程目录大致是这样的:

模块文件职责
数据层data_loader.py读入销售数据与供应商数据
预处理preprocess.py日期补齐、销量聚合、参数配置
模拟引擎simulator.py库存滚动模拟与利润计算
策略搜索optimizer.py网格搜索/枚举补货参数
结果输出report.py输出订货计划表与指标统计

这样拆分的好处是,改参数不会污染数据处理逻辑,换策略也只是新增一个函数,不必重写模拟引擎。如果你想把项目扩展成自己的仓库,按这个结构维护最省心。

4.2 核心代码片段解读

数据加载部分用pandas最顺手,读入后统一转成标准列名。比如销售数据统一成“item_id”“date”“qty”,供应商数据统一成“item_id”“supplier_id”“unit_cost”“lead_time”。之后所有逻辑都只认这些列名,减少后续出错的概率。

模拟引擎的代码在上一节已经给出,是全项目最核心的函数。我在实际使用中给它加了一个“debug模式”,可以打印每一天的库存变化和缺货记录,非常好用。比如某个商品总在每周三缺货,打开debug打印几行就能看到规律,比盯着最终利润数字猜原因高效得多。

4.3 结果输出与可视化

优化结束之后,代码会把最优补货计划保存成CSV,包括“商品编号、订货日期、订货量、预计到货日期、订货时库存”。同时按商品维度汇总出利润、缺货天数、期末库存、订货次数等核心指标。这个输出格式也是答辩和写报告时最常需要的形式。

可视化方面我用matplotlib画了每个商品的库存-销量时序图,把补货时点和缺货时点都标出来。图上能直观看到库存是否总是压得很高、缺货是否集中在某些促销日附近。不要小看这几张图,它经常能发现建模时忽略的异常规律,比如某个商品销量在月初特别高,普通数据分析根本看不出来。

5. 常见问题与排查技巧实录

5.1 数据对齐出错的坑

这个项目里我踩过最大的坑是日期对齐。销售数据本身不是连续每一天都有记录,有些商品在某个时间段内可能就是0销售。如果直接拿原始记录做模拟,库存会被误判为“一直没动”,导致补货策略完全失效。解决办法是先按商品生成完整日期索引,再左连接销量,缺失值用0填充。

另一个相关问题是补货到货日期的边界。比如到货周期是2天,第1天订货,第3天到货。如果代码里用的是“当天订货,当天就到”,最优参数会明显偏向减少订货量,因为缺货风险被低估了。这个边界在代码里极其容易忽略,但影响非常大。

5.2 缺货损失参数太灵敏

缺货损失的设定对最优策略影响非常大。如果缺货损失设得很高,算法会倾向于多订货、高库存;如果设得很低,算法会频繁缺货。实际操作中我的做法是,先按“零售价-成本价”算出毛利作为缺货损失的基准,再扫一个上下浮动50%的范围,观察最优参数是否剧烈变化。如果变化不大,说明策略稳定;如果变化很大,说明模型对这个参数比较敏感,需要业务方给出明确口径。

我在复现赛题时发现,很多队伍的模型本质差别不大,最终分数差距往往来自成本参数的口径选择。所以建议你在跑代码前,先把参数定义和业务对齐,不要默认一个数字就开跑。

5.3 网格搜索太慢怎么办

如果商品种类多、补货周期范围大,网格搜索的组合数量会迅速膨胀。我的优化办法分两步:先用大步长粗扫,找到利润较高的区域;再在小范围内细扫,找到更精确的参数组合。这个方法虽然简单,但能把搜索时间从几小时缩短到几分钟。

如果商品数量特别多,还可以把“所有商品共用一套参数”改成“每个商品单独搜索参数”,再按利润贡献排序,只对贡献最大的前几个商品做细粒度调参。这样既避免过度拟合,又能聚焦真正影响利润的SKU。

最后再分享一个实用技巧:我在跑代码时把所有商品的最优参数保存成一个JSON文件,每次调参都保留历史版本。这样反复实验后能快速对比不同版本之间的利润差异,也方便在报告里展示“参数调优过程”,比只给一个最终结果有说服力得多。这套代码里我已经把JSON输出的逻辑写好了,直接用就行。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询