简介这份资源面向参加数学建模竞赛、准备毕业设计或课程设计的高校学生聚焦2023年高教社杯全国大学生数学建模竞赛C题「蔬菜类商品的自动定价与补货决策」提供完整源码与配套论文资料帮助读者快速理解赛题思路并复现建模流程。压缩包共110个文件约72.74MB包含8个Python脚本、16个xlsx数据表、12个h5与12个pkl模型文件、12个joblib中间结果、36张png图表以及pdf、doc、md、m、rmd等说明文档覆盖数据处理、神经网络建模、结果可视化与论文撰写各环节。目前已有852人学习下载。资源中代码注释较为完整新手也能对照理解模型按辣椒类、食用菌、茄类、花菜类、花叶类、水生根茎类等品类分别训练可直接用于毕设、期末大作业或课程设计具备较高的参考与复用价值。1. 蔬菜定价补货这道题为什么每年都有人卡在“算得出、卖不动”2023年高教社杯全国大学生数学建模竞赛C题题目叫“蔬菜类商品的自动定价与补货决策”。很多人第一次看觉得就是个优化问题真上手才发现数据里藏着损耗、时段、品类替代、价格弹性还有“今天不卖完明天就烂”的硬约束。它要解决的不是单纯算个最优解而是让超市在每天凌晨决定每个蔬菜单品定什么价、补多少货才能在损耗和利润之间找到平衡。适合谁适合正在做数学建模、想拿这道题练手或者做零售定价、生鲜补货算法落地的同学和工程师。这道题的核心矛盾很直接——定价高了卖不动补货多了烂在架上补货少了又断货。下面我按“数据怎么读、模型怎么搭、代码怎么跑、坑在哪”的顺序把这道题从论文到源码的落地路径拆开讲。2. 先看清数据蔬菜品类、时段与损耗的三角关系2.1 附件数据到底给了什么这道题通常会给几个附件销售流水、批发价、品类信息、损耗率。销售流水里一般包含日期、单品编码、销量、销售单价、是否打折批发价按日期和单品给品类信息告诉你这是叶菜、根茎还是花菜。很多人一上来就急着建优化模型结果连“销量是当天卖完的量还是上架量”都没分清后面全错。我一般会先做三件事第一把单品编码和品类对齐确认每个单品属于哪个蔬菜大类第二把销售日期和批发日期对齐注意批发价可能是前一天或当天凌晨的第三把损耗率单独拉出来因为损耗直接决定“补货量 销量 / (1 - 损耗率)”这个换算关系。如果损耗率是按品类给的就不要硬套到单品上否则叶菜和根茎的差异会被抹平。import pandas as pd # 读取销售流水、批发价、品类信息 sales pd.read_csv(sales.csv) wholesale pd.read_csv(wholesale.csv) category pd.read_csv(category.csv) # 对齐单品编码与品类 sales sales.merge(category, on单品编码, howleft) # 按日期和单品聚合销量与均价 daily sales.groupby([日期, 单品编码]).agg( 销量(销量, sum), 均价(销售单价, mean) ).reset_index() # 合并批发价 daily daily.merge(wholesale, on[日期, 单品编码], howleft) print(daily.head())这段代码的逻辑是先把分散的附件拼成一张“日期-单品-销量-均价-批发价”的主表。参数上注意销量用sum是因为同一天同一单品可能有多次销售记录均价用mean是为了后续算毛利。如果批发价有缺失不要直接删先看是不是非交易日用前向填充更稳。2.2 损耗率怎么进模型损耗率是这道题最容易被低估的参数。常见做法是补货量 预测销量 / (1 - 损耗率)。但这里有个坑——损耗率如果是按“上架量”算的那这个公式成立如果是按“销售量”算的公式要反过来。我一般会先拿几天数据反推用实际补货量、实际销量和损耗率对一遍确认口径。另外损耗率不是常数。叶菜夏天损耗能到30%以上冬天可能只有10%。如果题目给了按品类和月份的平均损耗率就按品类和月份取如果只给了品类平均那就在模型里加一个季节因子用历史数据拟合。别小看这一步损耗率差5个点最优补货量能差出10%以上。2.3 价格弹性的粗略估计定价决策离不开价格弹性。题目通常不会直接给弹性系数需要自己估。最简单的方法对每个品类用历史数据做销量对价格的回归取对数后斜率就是弹性。注意要控制日期固定效应否则会把季节趋势误当成价格影响。import numpy as np import statsmodels.api as sm # 取某个品类的数据 cat_data daily[daily[品类] 叶菜].copy() cat_data[log销量] np.log(cat_data[销量] 1) cat_data[log价格] np.log(cat_data[均价] 1) # 加日期虚拟变量控制季节 date_dummies pd.get_dummies(cat_data[日期], prefixd, drop_firstTrue) X pd.concat([cat_data[[log价格]], date_dummies], axis1) X sm.add_constant(X) y cat_data[log销量] model sm.OLS(y, X).fit() print(model.params[log价格])这里log价格的系数就是弹性估计。如果系数是 -1.2说明价格涨1%销量降1.2%。参数上注意加1是为了避免零销量取对数出错日期虚拟变量是为了吸收季节和促销影响。如果数据量不够可以按品类合并估计不要硬拆到单品。3. 定价与补货的联合优化从目标函数到求解器3.1 目标函数怎么写才不翻车这道题的目标通常是“利润最大化”但利润 销售收入 - 批发成本 - 损耗成本。损耗成本怎么算如果损耗的菜没有残值那就是批发价乘以损耗量。所以目标函数可以写成利润 Σ(售价 × 销量) - Σ(批发价 × 补货量)其中补货量 销量 / (1 - 损耗率)。这样写的好处是把损耗直接折进补货量里不用单独再减一次。但要注意如果题目允许打折清仓那损耗率会变目标函数要分段。我一般会先写一个简化版假设当天必须清完售价和销量满足线性需求函数然后求最优售价。再逐步加约束补货量上限、品类最小陈列量、价格不能低于成本等。别一上来就写全约束否则求解器容易不收敛。3.2 用 scipy 做单品类最优定价先拿一个品类练手。假设需求函数是线性的销量 a - b × 售价。用历史数据拟合出 a 和 b然后代入利润函数求极值。from scipy.optimize import minimize_scalar # 拟合线性需求销量 a - b * 售价 X cat_data[均价] y cat_data[销量] b, a np.polyfit(X, y, 1) # 利润函数售价 * 销量 - 批发价 * 补货量 批发价 cat_data[批发价].mean() 损耗率 0.2 def profit(price): 销量 a - b * price if 销量 0: return 1e6 # 惩罚无效解 补货量 销量 / (1 - 损耗率) return -(price * 销量 - 批发价 * 补货量) # 取负求最小 res minimize_scalar(profit, bounds(批发价, 批发价*3), methodbounded) print(最优售价:, res.x, 最大利润:, -res.fun)这段代码用minimize_scalar做一维搜索。参数上bounds设成批发价到三倍批发价是防止价格低到亏本或高到没人买损耗率先用品类平均后面可以换成动态值。注意profit返回负值是因为 scipy 默认求最小。如果销量算出来是负的直接给个大惩罚值让求解器避开。3.3 多品类补货把线性规划用起来单品类定价只是热身真题要求的是多品类联合决策。这时候可以用线性规划或整数规划。决策变量是每个品类每天的补货量和售价约束包括总补货量不超过仓库容量、每个品类补货量不低于最小陈列、售价在合理区间。目标还是利润最大化。from scipy.optimize import linprog # 假设有3个品类决策变量为补货量 x1,x2,x3 # 目标最大化利润 - 最小化负利润 # 利润系数 售价*销量 - 批发价*补货量这里简化为线性 c [-10, -12, -8] # 负的单位利润 # 约束总补货量 100 A_ub [[1, 1, 1]] b_ub [100] # 每个品类补货量 5 bounds [(5, None), (5, None), (5, None)] res linprog(c, A_ubA_ub, b_ubb_ub, boundsbounds, methodhighs) print(最优补货量:, res.x)这里用的是linprog参数c是负的单位利润因为要最小化。A_ub和b_ub是总补货量约束bounds是每个品类的最小补货量。实际题目里单位利润不是常数它跟售价和销量有关所以更准确的做法是用非线性规划或分段线性化。但作为起步先用线性规划跑通流程再逐步替换目标函数。3.4 动态规划处理“今天不卖完明天就烂”如果题目强调损耗和时段比如当天没卖完的菜第二天只能打折或扔掉那就得用动态规划。状态是“当前库存”决策是“定价和补货”转移是“明天库存 今天补货 - 今天销量”。我一般会按天滚动用后向归纳求最优策略。# 简化动态规划假设需求是随机的用期望利润 import numpy as np def dp_optimal(T, max_inventory, demand_samples): # V[t][i] 表示第t天库存为i时的最大期望利润 V np.zeros((T1, max_inventory1)) policy np.zeros((T, max_inventory1)) for t in range(T-1, -1, -1): for i in range(max_inventory1): best_profit -1e9 best_order 0 for order in range(max_inventory - i 1): expected 0 for d in demand_samples: sold min(i order, d) revenue sold * 10 # 假设售价10 cost order * 5 # 假设批发价5 leftover i order - sold expected revenue - cost - leftover * 2 # 损耗惩罚 expected / len(demand_samples) if expected V[t1][max(0, iorder-sold)] best_profit: best_profit expected best_order order V[t][i] best_profit policy[t][i] best_order return V, policy这段代码是动态规划的骨架。V[t][i]是第 t 天库存 i 时的最大期望利润policy存最优补货量。参数上demand_samples是历史需求样本用来算期望leftover * 2是损耗惩罚可以换成实际损耗成本。注意状态空间别开太大否则跑不动。实际用的时候可以把品类拆开分别做再在总库存约束下协调。4. 避坑与排查这道题最容易翻车的五个地方4.1 销量和补货量口径搞反现象算出来的补货量比销量还小利润高得离谱。原因把“销量”当成“补货量”直接用了忽略了损耗。解决先确认损耗率是按上架量还是销售量算的补货量 销量 / (1 - 损耗率) 只在上架量口径下成立。拿几天实际数据反推验证。4.2 价格弹性估成正数现象回归出来价格系数是正的价格越高销量越高。原因没控制季节和促销或者把打折价和原价混在一起。解决加日期虚拟变量把打折记录单独标记或者只用非打折日数据估弹性。如果还是正检查是不是把“售价”和“销量”的因果搞反了。4.3 损耗率用成常数现象模型在夏天预测的补货量偏低冬天偏高。原因损耗率随季节变化用年平均损耗率会失真。解决按品类和月份分组算损耗率或者加季节因子。如果数据不够至少按季度分。4.4 优化求解器不收敛现象linprog或minimize报错或者结果全是边界值。原因约束矛盾比如最小补货量之和超过仓库容量或者目标函数非线性太强。解决先检查约束可行性把硬约束改成软约束加惩罚项非线性强就分段线性化或者用启发式算法先找可行解。4.5 忽略品类替代现象叶菜降价后根茎销量也降了模型没捕捉到。原因只做了单品类优化没考虑品类之间的替代关系。解决在需求函数里加交叉价格项或者用向量自回归估计品类间影响。如果时间紧至少把替代性强的品类放一起联合优化。5. 从论文到源码怎么把结果讲清楚、跑出来5.1 论文里必须有的三张表第一张是数据清洗前后的对比表说明你处理了多少异常值、缺失值。第二张是价格弹性估计表按品类列出弹性系数和显著性。第三张是最优定价与补货结果表按天或按品类给出建议售价、补货量和预期利润。这三张表能把你的逻辑链完整呈现评审一看就知道你不是拍脑袋。5.2 源码怎么组织才方便复现我一般会分四个文件data_preprocess.py负责读数据和清洗demand_estimate.py负责估弹性和需求函数optimize.py负责定价和补货求解main.py串流程。每个文件里把参数写在开头比如损耗率、价格区间、仓库容量方便别人改。不要把所有代码塞一个文件否则调参时容易乱。# main.py 示例 from data_preprocess import load_and_clean from demand_estimate import estimate_elasticity from optimize import optimize_price_order data load_and_clean(sales.csv, wholesale.csv, category.csv) elasticity estimate_elasticity(data) result optimize_price_order(data, elasticity, loss_rate0.2, capacity100) print(result)这样组织的好处是每一步可单独测试。参数loss_rate和capacity放在调用处改起来不用翻代码。如果题目要求输出每天每个品类的决策就在optimize里循环日期把结果存成 CSV。5.3 验证方法用历史数据回测别只跑一天就下结论。把历史数据分成训练集和测试集用训练集估参数在测试集上模拟定价补货算实际利润。如果测试集利润比实际低太多说明模型过拟合。我一般会看两个指标预测销量和实际销量的 MAPE以及模拟利润和实际利润的偏差。MAPE 超过30%就得回头检查需求函数。5.4 一个具体技巧把损耗率做成动态惩罚项如果题目允许可以在目标函数里加一个动态损耗惩罚损耗率越高补货量惩罚越大。这样模型会自动偏向少补货避免烂市。具体做法是在利润函数里减一项lambda * 损耗率 * 补货量lambda用历史数据调。这个技巧在叶菜类特别管用因为叶菜损耗波动大固定损耗率容易翻车。我做完这道题最大的教训是别急着上复杂模型先把数据口径和损耗率搞对再谈优化。很多队伍卡住不是因为算法不行而是因为销量和补货量没对齐。希望帮到你。本文还有配套的精品资源点击获取