
简介本资源是2023年全国大学生数学建模竞赛C题《蔬菜类商品自动定价与补货决策优化》的完整参赛成果面向计算机、统计、运筹与管理类专业的本科生及毕业设计需求者聚焦生鲜零售场景下的动态定价、损耗建模与多目标补货决策等实际问题。压缩包共46个文件11.9MB含24个Excel数据表如单品销售量、平均损耗率、GRU预测结果等、9个Jupyter Notebook覆盖聚类分析、ARIMA/GRU销量预测、箱线图可视化、混合整数规划求解等核心环节、3个Python脚本含BONMIN调用接口及1篇定稿论文PDF和详细README说明文档。已有103人学习下载内容经过实测可直接运行代码注释清晰、模块分工明确涵盖数据清洗→特征工程→多模型预测→优化求解→结果可视化全流程特别适合毕设选题参考、建模能力进阶与供应链算法实践拓展。1. 项目概述从数学建模到商业决策的实战跨越拿到“蔬菜类商品自动定价与补货决策优化研究”这个题目很多人的第一反应可能是这不就是个数学建模竞赛题吗确实它源自2023年全国大学生数学建模竞赛的C题。但如果你只把它看作一份需要求解的试卷那就大大低估了它的价值。在我这个经历过多次竞赛、也参与过实际供应链系统开发的“老手”看来这个题目本质上是一个高度浓缩的、真实的商业智能BI与运营优化Ops的实战沙盘。它精准地戳中了生鲜零售行业尤其是社区超市、线上生鲜平台最核心、也最头疼的两个痛点定价与补货。蔬菜类商品具有典型的短生命周期、高损耗率、需求波动大且受多重因素影响的特征。今天白菜卖一块五一斤可能被抢光明天同样的价格可能就无人问津补货多了晚上就是一堆损耗直接变成利润黑洞补货少了又意味着销售机会的流失和顾客满意度的下降。这道赛题就是要求我们构建一个数学模型像一位经验丰富的店长或精明的供应链经理一样根据历史销售数据、品类关联、损耗情况自动地、动态地决定每种蔬菜该卖多少钱、该进多少货。这不仅仅是数学这是用数学语言对商业逻辑进行的一次精密“翻译”和“求解”。本文将带你彻底拆解这个项目不仅复现论文中的核心模型与代码更会深入探讨在实际业务场景中如何将模型落地、会遇到哪些“坑”、以及如何调整优化。无论你是参赛学生希望深化理解还是行业从业者寻求解决方案都能从中获得可直接参考的“干货”。2. 核心问题拆解与建模思路总览面对“定价”与“补货”这两个耦合的决策问题首要任务是进行清晰的问题拆解。我们不能眉毛胡子一把抓必须理清其中的逻辑链条和约束条件。2.1 问题一销量与定价的量化关系构建这是所有决策的基石。题目通常会提供一段时间内比如过去4周多种蔬菜的每日销售流水数据包括单品编码、名称、售价、销量、成本等。我们的第一个目标就是建立销量对售价的响应模型即需求函数。注意这里切忌直接使用简单的线性回归。蔬菜需求受自身价格影响外还强烈受到替代品和互补品价格的影响。例如菠菜涨价了消费者可能转而购买小青菜西红柿涨价了鸡蛋的销量可能也会受影响因为西红柿炒鸡蛋的需求下降了。因此一个更合理的模型是引入交叉价格弹性的需求函数。对于第i种蔬菜其第t天的销量 (Q_{it}) 可以建模为[ \ln(Q_{it}) \alpha_i \beta_i \cdot \ln(P_{it}) \sum_{j \neq i} \gamma_{ij} \cdot \ln(P_{jt}) \epsilon_{it} ]其中(P_{it}) 是商品i在t日的售价。(\beta_i) 是自身价格弹性预期为负值价格越高销量越低。(\gamma_{ij}) 是交叉价格弹性表示商品j的价格对商品i销量的影响。若 (\gamma_{ij} 0)则j是i的替代品j涨价i销量增若 (\gamma_{ij} 0)则j是i的互补品。(\alpha_i) 是截距项(\epsilon_{it}) 是误差项。实操要点数据预处理必须处理缺失值、异常值如销量为0或极高。对于缺失的日销量可以考虑用前后均值或品类均值填充但需记录。品类筛选并非所有蔬菜都存在强关联。可以先通过计算销量相关系数或基于业务知识如叶菜类、茄果类、根茎类预先分组只在组内或强相关的品类间计算交叉弹性以减少模型复杂度和过拟合风险。模型估计由于方程右侧包含其他商品的价格这构成了一个联立方程系统。直接使用OLS普通最小二乘法估计会有偏误。可以采用似不相关回归SUR或三阶段最小二乘法3SLS进行系统估计这在Python的statsmodels库或R语言中都有现成实现。2.2 问题二单日定价与补货联合优化模型在得到需求函数后我们就可以在给定成本、损耗率的约束下构建以最大化当日预期毛利为目标的优化模型。这是整个项目的核心。决策变量(P_i)商品i的当日售价。(O_i)商品i的当日补货订货量晨间到货量。(S_i)商品i的当日预期销量由需求函数决定即 (S_i f(P_i, P_j, ...))。目标函数最大化总毛利 [ \text{Maximize } Z \sum_{i} [ (P_i - C_i) \cdot S_i - C_i \cdot \theta_i \cdot (O_i - S_i)^ ] ] 其中(C_i) 是商品i的进货成本单位成本。(\theta_i) 是商品i的损耗率未能售出部分的成本损失比例。((O_i - S_i)^ \max(0, O_i - S_i))表示未售出的库存量。约束条件需求函数约束(S_i Q_i(P_1, P_2, ..., P_n))即销量由定价模型决定。库存平衡约束预期销量不能大于可用库存即 (S_i \leq I_{i0} O_i)其中 (I_{i0}) 是前日结余库存。业务逻辑约束售价约束(P_i^{min} \leq P_i \leq P_i^{max})如不能低于成本不能高于市场承受范围。补货量约束(0 \leq O_i \leq O_i^{max})基于货架容量、供应商能力等。非负约束所有变量非负。模型求解的挑战与技巧 这是一个典型的非线性规划NLP问题因为目标函数和需求函数约束都是非线性的。直接求解全局最优解比较困难。常用的方法有梯度下降/上升法对于变量不多的情况可以尝试。序列二次规划SQPSciPy库中的minimize函数设置methodSLSQP可以处理这类带约束的非线性优化问题非常实用。启发式算法如遗传算法GA、模拟退火SA适用于变量较多、解空间复杂的情况。Python的DEAP或PyGAD库是不错的选择。实操心得在实际编程中将“最大化”问题转化为“最小化”问题即minimize -Z更方便使用现有的优化库。另外初始解的设置非常关键一个好的初始解如用历史平均价和销量能极大提高求解速度和找到更优解的可能性。2.3 问题三多日动态规划与库存策略现实中的决策不是孤立的今天的补货会影响明天的库存进而影响明天的决策。问题三要求我们考虑一个多日如一周的规划周期这需要引入动态规划或模型预测控制MPC的思想。我们可以建立一个以多日总毛利最大为目标的模型。其状态变量是每日开始的库存水平 (I_t)决策变量是每日的定价 (P_t) 和补货量 (O_t)状态转移方程是库存的动态变化(I_{t1} (I_t O_t - S_t)^)这里简化处理假设未售完的库存全部进入次日实际上可能有折损。直接求解多日动态规划可能面临“维数灾难”。一个实用且高效的近似方法是滚动时域优化RHO也就是MPC的核心在每一天开始时基于当前库存 (I_t)对未来N天例如3天进行优化求解得到未来N天的决策序列。只执行第一天的定价和补货决策。到第二天根据新的实际销量或预测更新和库存状态重复步骤1重新优化未来N天。这种方法既能考虑未来影响又具有应对不确定性的鲁棒性。3. 数据处理与特征工程实战详解“垃圾进垃圾出”在数据建模中永不过时。竞赛提供的数据往往粗糙直接建模效果会很差。这部分是拉开差距的关键。3.1 原始数据清洗与整合假设我们拥有以下原始表格sales.csv销售流水字段包括date日期code商品编码name商品名sale_price售价sale_quantity销量cost成本需确认。loss.csv损耗记录字段可能包括datecodeloss_quantity。category.csv商品分类信息。清洗步骤格式统一确保日期为datetime格式商品编码为字符串数值字段为float。异常值处理负值或零值销量、价格为0或负值明显错误。需结合前后数据或品类均值进行合理插补或直接剔除如果极少。极大值某天某个单品销量暴增可能是团购。需要设定阈值如3倍标准差之外将其平滑处理或视为特殊事件单独分析。缺失值处理对于少量缺失的日销量使用前向填充ffill或线性插值。对于整天数据缺失考虑用该商品在星期几的历史均值填充因为生鲜销售有强烈的周周期性。数据聚合将销售流水按date和code聚合为日度数据得到每个单品每日的总销量和平均售价或最后售价。3.2 关键特征构造这是提升模型预测能力的核心。时间特征day_of_week星期几0-6反映周末效应。is_weekend是否为周末。day_of_month月初、月末可能影响采购预算。is_holiday是否为节假日需外部导入节假日日历。历史特征lag_1,lag_2,lag_7前1天、2天、7天的销量/价格。反映短期趋势和周期性。rolling_mean_7过去7天的平均销量/价格。反映近期水平。rolling_std_7过去7天销量的标准差。反映需求波动性。品类关联特征计算同一大类下其他商品当日的平均价格作为“品类价格指数”。利用问题一中估计出的交叉弹性系数 (\gamma_{ij})构造一个“竞争品加权价格”特征(\sum_{j \neq i} \gamma_{ij} \cdot P_j)只加总 (\gamma_{ij}) 显著不为0的商品。库存与损耗特征starting_inventory每日期初库存前日结余当日补货。这需要从销售和损耗数据中反向推算是一个难点。loss_rate_hist该商品历史平均损耗率。推算期初库存的示例代码逻辑# 假设 df 是按 date 和 code 排序的日度销售数据包含‘sale_quantity’和‘loss_quantity’ df[daily_outflow] df[sale_quantity] df[loss_quantity] # 假设我们知道第一天的期初库存 init_inv或将其作为一个待估计参数 init_inv 100 inventory [init_inv] for i in range(1, len(df)): prev_inv inventory[-1] # 假设补货量 replenish 是未知的在优化模型中它是决策变量。 # 但在数据预处理时我们可以根据“销量损耗期末库存期初库存补货”的平衡关系 # 如果我们有期末库存的盘点记录就能倒推出补货量。如果没有则需要先估计一个补货策略如定期定量这是一个迭代或联合估计的过程。 # 这里展示一个简化思路假设每日补货量等于前7天平均销量一个简单的基准策略。 avg_sale_last_7 df[sale_quantity].iloc[max(0, i-7):i].mean() replenish avg_sale_last_7 current_inv prev_inv replenish - df[daily_outflow].iloc[i] inventory.append(max(0, current_inv)) # 库存非负 df[estimated_inventory] inventory注意库存推算是非常关键且容易出错的一环。竞赛数据可能不直接提供补货量需要根据业务逻辑进行合理假设和估计并在模型中对假设的敏感性进行分析。4. 定价与补货联合优化模型的Python实现这里我们聚焦于问题二的单日优化模型给出一个使用SciPy库的完整实现示例。我们假设有3种关联商品菠菜、小青菜、西红柿。4.1 定义需求函数以对数线性模型为例首先我们需要基于历史数据估计出需求函数的参数。这里假设我们已经用SUR方法估计好了参数。import numpy as np from scipy.optimize import minimize # 假设估计出的参数 (示例值实际应从数据估计) # 商品索引: 0-菠菜1-小青菜2-西红柿 alpha np.array([3.0, 2.8, 3.2]) # 截距 beta np.array([-1.2, -1.1, -1.3]) # 自身价格弹性 # 交叉价格弹性矩阵 gamma[i,j] 表示商品j价格对商品i需求的影响 gamma np.array([ [0, 0.3, 0.0], # 菠菜受小青菜价格正影响替代品 [0.2, 0, 0.0], # 小青菜受菠菜价格正影响 [0.0, 0.0, 0] # 西红柿假设与其他两者无关实际可能受黄瓜等影响 ]) # 成本、损耗率、初始库存、最大补货量 cost np.array([1.0, 0.8, 1.5]) loss_rate np.array([0.2, 0.15, 0.1]) # 20% 15% 10%的损耗 initial_inv np.array([10, 15, 20]) max_order np.array([50, 50, 50]) price_min cost * 1.1 # 最低售价为成本的1.1倍 price_max cost * 3.0 # 最高售价为成本的3倍 def demand_function(prices): 计算给定价格向量下的预期销量对数线性模型 log_p np.log(prices) log_q alpha beta * log_p for i in range(len(prices)): for j in range(len(prices)): if i ! j: log_q[i] gamma[i, j] * log_p[j] return np.exp(log_q) # 返回实际销量预测值4.2 定义优化目标与约束def objective(x): 目标函数负的每日预期毛利因为scipy.minimize是求最小化 x: 决策变量向量 [P0, P1, P2, O0, O1, O2] prices x[:3] orders x[3:] # 预测销量 predicted_sales demand_function(prices) # 销量不能超过可用库存 available_inv initial_inv orders actual_sales np.minimum(predicted_sales, available_inv) # 计算剩余库存可能损耗的部分 leftover np.maximum(available_inv - actual_sales, 0) # 计算毛利销售收入 - 销售成本 - 损耗成本 revenue np.sum(actual_sales * prices) goods_cost np.sum(actual_sales * cost) loss_cost np.sum(leftover * cost * loss_rate) daily_gross_profit revenue - goods_cost - loss_cost return -daily_gross_profit # 取负以求最小化 def constraint_inventory_sales(x): 约束预测销量 期初库存 补货量 prices x[:3] orders x[3:] predicted_sales demand_function(prices) available_inv initial_inv orders return available_inv - predicted_sales # 需要 0 # 定义变量边界 bounds [ (price_min[0], price_max[0]), (price_min[1], price_max[1]), (price_min[2], price_max[2]), # 价格 (0, max_order[0]), (0, max_order[1]), (0, max_order[2]) # 补货量 ] # 定义约束字典 cons [ {type: ineq, fun: constraint_inventory_sales} # 不等式约束 0 ] # 设置初始解例如历史平均价和基于历史销量的补货量 x0 np.array([2.0, 1.5, 2.8, 20, 25, 30])4.3 求解并分析结果# 调用优化器求解 result minimize(objective, x0, methodSLSQP, boundsbounds, constraintscons, options{maxiter: 1000, ftol: 1e-9}) if result.success: optimal_solution result.x optimal_prices optimal_solution[:3] optimal_orders optimal_solution[3:] max_profit -result.fun # 记得取回正值 print(优化成功) print(f最优定价菠菜 {optimal_prices[0]:.2f}元 小青菜 {optimal_prices[1]:.2f}元 西红柿 {optimal_prices[2]:.2f}元) print(f最优补货菠菜 {optimal_orders[0]:.1f}kg 小青菜 {optimal_orders[1]:.1f}kg 西红柿 {optimal_orders[2]:.1f}kg) print(f预期单日最大毛利{max_profit:.2f}元) # 计算并打印预期销量和损耗 pred_sales demand_function(optimal_prices) avail_inv initial_inv optimal_orders actual_sales np.minimum(pred_sales, avail_inv) leftover np.maximum(avail_inv - actual_sales, 0) print(f预期销量{actual_sales}) print(f预期损耗量{leftover}) else: print(优化失败:, result.message)代码解读与注意事项methodSLSQP适用于具有边界和约束的非线性优化问题。初始点x0很重要。如果优化失败或不理想可以尝试多组不同的初始点如随机生成。约束constraint_inventory_sales返回的是一个向量scipy会理解每个元素都需要0。实际应用中需求函数可能更复杂如加入时间特征优化变量也更多求解时间会变长。可能需要使用更高效的商业求解器如Gurobi,CPLEX或启发式算法。5. 模型评估、验证与业务落地思考构建出模型并得到一组“最优解”只是第一步。在真实业务中我们必须回答这个模型靠谱吗怎么用5.1 模型评估与回溯测试我们不能只相信模型在训练集上的表现。需要进行严格的回溯测试。样本外测试将数据按时间划分为训练集如前3周和测试集最后1周。用训练集估计模型参数在测试集上固定每天用模型做出决策并与该日实际发生的“最优决策”通常用事后诸葛亮的视角以实际可实现的最佳利润为基准进行对比。关键评估指标利润提升率模型策略下的累计模拟利润 vs. 基准策略如固定价格、固定补货量的累计利润。库存周转率模型是否在提升利润的同时加快了库存周转。损耗率模型是否有效控制了损耗。服务水平断货率是否在可接受范围内。敏感性分析参数敏感性需求弹性系数估计有误差怎么办在弹性值上下浮动10%观察利润变化的幅度。如果变化剧烈说明模型对参数很敏感需要更稳健的估计方法或更保守的决策。数据敏感性剔除某些异常日期或商品模型结论是否稳定5.2 从模型到系统落地实施的挑战与策略即使模型在回溯测试中表现优异直接用于指导每日经营仍面临挑战数据实时性模型需要前一天的库存、当天的成本等输入。需要建立可靠的数据采集和同步流程如POS系统与库存管理系统对接。决策可解释性店长可能无法理解为什么今天菠菜突然要涨价5毛。系统需要提供决策依据的简要说明例如“因小青菜进货价上涨预计部分需求将转移至菠菜故建议小幅提价以提升毛利”。人工干预接口必须允许店长基于本地知识如天气预报、社区活动对系统建议进行覆盖或调整。系统应记录每次干预及后续结果用于迭代优化模型。渐进式上线不要在所有门店、所有品类一次性上线。可以选择一个门店、几个核心品类进行A/B测试对比模型组和人工控制组的业绩用数据证明价值后再推广。5.3 常见问题与排查技巧实录在实际编码和调试中你肯定会遇到以下问题问题1优化求解器不收敛或找不到可行解。可能原因1约束条件相互冲突或过于严格。例如价格下限设得太高导致预测销量永远大于最大可供应量初始库存最大补货。排查放松约束边界或检查需求函数预测值是否在合理范围。打印出约束函数在初始点的值看是否已有违反。可能原因2目标函数或约束函数存在数值问题如除以零、取对数负数。排查在函数内部添加assert语句或try-except块确保所有输入都在定义域内。例如价格必须大于0。可能原因3初始点选择太差。排查尝试多组不同的初始点或者先用一个简化模型如忽略交叉弹性求出一个解再用这个解作为复杂模型的初始点。问题2需求函数的预测误差很大。可能原因1遗漏了重要特征如节假日、天气温度、降雨。解决引入外部数据。天气数据可以从公开API获取节假日信息可以内置日历。可能原因2存在未观测到的促销或竞争对手行为。解决在数据中标注已知的促销日期。对于竞争对手如果无法获取其价格数据可以尝试用时间趋势或行业指数作为代理变量。可能原因3模型形式不合适。对数线性模型可能无法捕捉某些非线性关系。解决尝试其他模型如线性模型Q a b*P、或机器学习模型梯度提升树、神经网络。但要注意机器学习模型可能缺乏可解释性且优化时需要调用模型预测计算量更大。问题3模型给出的补货量波动剧烈今天50kg明天5kg。可能原因模型只追求单日利润最大化忽略了补货的固定成本如物流成本、操作稳定性以及供应商的最小起订量。解决在目标函数中增加“补货量平滑性”惩罚项。例如在目标函数中加入- λ * Σ(O_t - O_{t-1})^2其中λ是平滑系数惩罚补货量的大幅变动。或者增加约束|O_t - O_{t-1}| ≤ Δ限制每日补货变化幅度。问题4如何处理新品或历史数据很少的商品解决采用“冷启动”策略。品类类比将其归入一个品类使用该品类的平均价格弹性。保守策略初期采用“低毛利率、高周转”的策略定价接近成本补货量小批量多次快速收集市场反馈数据。贝叶斯更新设定一个先验分布基于品类每销售一天就用新数据更新后验分布动态调整模型参数。这个项目从一道赛题出发其内涵却覆盖了数据分析、计量经济学、运筹优化和系统思维的多个层面。真正的价值不在于求出那一个数学上的最优解而在于构建一套数据驱动、持续迭代的决策框架。当你理解了销量如何随价格变化懂得了库存、损耗与利润之间的微妙平衡并能用代码将这份理解自动化时你就已经从一个解题者成长为一名能够解决真实商业问题的分析师或工程师了。在实际操作中我最大的体会是永远对数据保持怀疑对模型的输出保持审慎。模型是强大的辅助但最终决策需要融合算法的智能与人的经验与智慧。本文还有配套的精品资源点击获取