尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

数学建模竞赛实战:基于需求弹性与库存策略的商品定价与补货决策

数学建模竞赛实战:基于需求弹性与库存策略的商品定价与补货决策 1. 从“思路更新中”到“完整解题方案”一次C题实战复盘每年九月的那个周末对于全国几十万大学生来说都是一个不眠之夜。全国大学生数学建模竞赛国赛的题目一发布各大论坛、社群瞬间被“求思路”、“求代码”的帖子淹没。标题里“[C题]2023 年全国大学生数学建模比赛思路、代码更新中.....”这种状态我太熟悉了——它精准地捕捉了比赛初期那种信息饥渴、时间紧迫、团队在黑暗中摸索的集体焦虑。作为一个从参赛者到指导者在这个领域摸爬滚打了十多年的“老建模人”我想抛开那些泛泛而谈的备赛指南直接切入2023年国赛C题的核心进行一次深度的、可复现的解题实战复盘。这不是一份简单的“思路”而是一个完整的、从问题拆解到模型构建再到代码实现与论文写作的闭环过程。我会重点分享我们团队当时是如何一步步拨开迷雾将“思路更新中”的状态最终转化为一份结构严谨、逻辑自洽的参赛论文的其中包含大量常规教程里不会写的决策细节、工具使用的“骚操作”以及临场应对的心得。2023年C题的主题是关于“蔬菜类商品的自动定价与补货决策”这是一个典型的运筹学与数据分析交叉的问题背景贴近生活但内涵极其丰富。它要求参赛者基于超市的销售流水、商品信息以及外部批发价格数据为蔬菜商品设计自动定价模型和补货决策模型。这道题的魅力在于它没有标准答案却有一条清晰的评价逻辑你的模型是否比人工决策更赚钱、更高效下面我就把我们团队72小时的作战过程拆解给你看。2. 破题第一步超越数据清洗构建“业务理解”数据框架拿到题目和数据新手团队的第一反应往往是立刻打开Excel或Python开始清洗数据查看缺失值和异常值。这没错但远远不够。对于C题这种业务导向的问题在动第一行代码之前必须建立深刻的“业务理解”。这决定了你后续所有模型的方向和高度。2.1 数据背后的业务逻辑链梳理我们拿到的数据通常包括销售流水表包含单品编码、销售日期、销量、售价、成本价等。商品信息表包含单品编码、分类编码、单品名称等。批发价格表包含日期、商品分类、批发价格等。很多团队会止步于对每个表进行独立分析。我们的做法是在编程清洗之前先用纸笔或思维导图画出这些数据之间的业务逻辑关系图核心驱动链外部批发价波动 → 影响单品成本 → 影响定价策略 → 影响销量与损耗 → 最终决定利润。分类层级商品有“大类”如蔬菜、“中类”如叶菜类、“小类”如菠菜批发价通常在中类或小类层面。定价和补货模型必须在合适的粒度上操作。时间维度销售有日周期性周末效应、周周期性、季节性不同蔬菜的上市季节还有促销等事件影响。批发价也有其波动规律。注意题目中“可售时间”是一个关键约束。蔬菜是生鲜品今天进货如果未来几天卖不完就会损耗。这意味着补货决策不是一个简单的“库存低于阈值就补”而是一个带有时间窗和损耗风险的动态规划问题。很多初期思路忽略了这一点导致模型过于理想化。2.2 数据清洗中的“业务化”处理基于上述理解我们的数据清洗就不仅仅是处理缺失值了而是为后续模型构建特征关联与整合将销售流水、商品信息、批发价表通过“单品编码”和“日期分类编码”关联起来形成一张包含每条销售记录对应成本通过日期和分类映射到批发价的宽表。构造衍生变量毛利率(售价 - 成本价) / 售价。这是定价模型的核心优化目标之一。损耗率估算对于没有直接损耗数据的情况我们通过“进货量可通过前后库存与销量反推 - 销量”来估算潜在损耗并结合商品保质期可售时间赋予权重。需求弹性特征计算每个单品历史销量对价格变化的敏感度。我们采用了简化的点弹性计算方法弹性 ≈ (销量变化百分比) / (价格变化百分比)选取促销前后或价格明显波动的时段进行计算作为定价模型的输入特征。时间特征不仅仅是“星期几”我们还构造了“是否节假日”、“是否促销日”、“月度周期”等。异常值处理的业务判断销量为0或极高价格异常如成本价高于售价这些不能简单删除或均值填充。我们需要结合业务判断销量为0可能是缺货也可能是确实没卖动价格异常可能是数据录入错误也可能是临时的清仓处理。我们的原则是对于明显且无法解释的录入错误如价格小数点错位予以修正或剔除对于可能的业务现象将其作为一个特殊状态标志保留在模型中予以考虑。# 示例构建核心数据宽表与特征Python pandas import pandas as pd import numpy as np # 假设已加载数据 sales_df pd.read_csv(sales.csv) # 销售流水 goods_df pd.read_csv(goods.csv) # 商品信息 wholesale_df pd.read_csv(wholesale.csv) # 批发价 # 1. 关联商品信息 merged_df pd.merge(sales_df, goods_df, onproduct_id, howleft) # 2. 关联批发价需要日期和分类匹配 # 假设批发价表有date, category_mid, wholesale_price # 商品信息表有category_mid merged_df pd.merge(merged_df, wholesale_df, left_on[sale_date, category_mid], right_on[date, category_mid], howleft) merged_df.rename(columns{wholesale_price: cost_price}, inplaceTrue) # 3. 计算毛利率 merged_df[gross_margin] (merged_df[sale_price] - merged_df[cost_price]) / merged_df[sale_price] # 4. 计算简化需求弹性示例使用前后两天数据 # 注意这里仅为演示逻辑实际需更严谨的样本选择和计算 merged_df[price_change_pct] merged_df.groupby(product_id)[sale_price].pct_change() merged_df[sales_change_pct] merged_df.groupby(product_id)[sales_volume].pct_change() merged_df[price_elasticity] merged_df[sales_change_pct] / merged_df[price_change_pct] # 处理无穷大和缺失值 merged_df[price_elasticity].replace([np.inf, -np.inf], np.nan, inplaceTrue) # 5. 构造时间特征 merged_df[sale_date] pd.to_datetime(merged_df[sale_date]) merged_df[day_of_week] merged_df[sale_date].dt.dayofweek merged_df[is_weekend] merged_df[day_of_week].isin([5, 6]).astype(int) # 假设有一个节假日列表 holiday_list [2023-01-01, 2023-05-01, ...] merged_df[is_holiday] merged_df[sale_date].dt.strftime(%Y-%m-%d).isin(holiday_list).astype(int)3. 定价模型从经济学原理到可求解的数学规划定价是C题的核心。题目要求“自动定价”意味着模型需要根据成本、需求、库存、竞争题目中未明确给出但可隐含考虑等因素动态输出建议售价。我们放弃了复杂的机器学习黑箱模型如神经网络因为其可解释性差且在有限时间内难以调优。最终选择了基于需求价格弹性的非线性规划模型因为它有坚实的经济学基础模型透明且能直接与利润最大化目标挂钩。3.1 模型建立利润最大化视角设对于某个单品在某天p为售价决策变量c为成本价已知来自批发价Q(p)为需求函数表示价格p下的预测销量I为当前库存T为剩余可售天数目标最大化当日预期利润π (p - c) * min(Q(p), I)。但这里有个关键点如果预测销量Q(p)大于库存I则最多只能卖出I件利润为(p-c)*I如果Q(p)小于I则全部按预测销量卖出利润为(p-c)*Q(p)剩余库存可能产生后续利润或损耗。考虑到蔬菜的损耗我们必须引入损耗成本。假设当日未售出的单位商品在未来每天有一个固定的损耗风险概率或价值折损。为了简化我们将多期问题转化为单期问题通过一个损耗惩罚项来体现如果定价过高导致销量Q(p)远小于库存I则剩余库存I - Q(p)面临损耗风险在目标函数中应减去一个与剩余库存成正比的惩罚成本。因此单日单品的定价模型可以抽象为Maximize: (p - c) * Sales(p) - θ * Max(0, I - Sales(p)) Subject to: p_lower p p_upper Sales(p) min(Q(p), I) (或更精确的需求函数)其中Sales(p)是价格p下的实际销量θ是单位库存的损耗惩罚系数p_lower和p_upper是价格上下限可由市场承受度和毛利率要求设定。3.2 需求函数Q(p)的估计弹性是关键需求函数是模型的核心。我们采用最经典的线性需求函数Q(p) a - b * p。其中a是潜在市场规模b是价格敏感系数。b可以直接与我们之前计算的需求价格弹性E关联起来。在某个价格点p0和销量Q0点弹性公式为E (dQ/dp) * (p0/Q0) -b * (p0/Q0)。因此如果我们能从历史数据中估算出弹性E在价格p0附近并且知道该点的Q0就可以反推出b -E * (Q0 / p0)。进而a Q0 b * p0。# 示例估算线性需求函数参数 def estimate_demand_function(product_data, price_colsale_price, sales_colsales_volume): 根据单个商品的历史数据估算线性需求函数 Q a - b * p 的参数。 使用弹性反推法。 # product_data 是某个单品的历史DataFrame # 计算历史平均价格和销量作为基准点 (p0, Q0) p0 product_data[price_col].mean() Q0 product_data[sales_col].mean() # 计算历史价格弹性简化用价格和销量的相关系数乘以标准差比近似 # 更严谨的做法是用回归或选取特定时段计算 corr product_data[[price_col, sales_col]].corr().iloc[0,1] std_price product_data[price_col].std() std_sales product_data[sales_col].std() if std_price 0: E_approx corr * (std_sales / std_price) * (p0 / Q0) else: E_approx -0.5 # 默认弹性实际情况需谨慎 # 反推参数 b 和 a b -E_approx * (Q0 / p0) a Q0 b * p0 # 确保参数合理需求随价格上升而下降b应为正a通常为正 if b 0: b 0.1 # 设置一个小的正数作为下限 if a 0: a Q0 # 用历史平均销量作为截距下限 return a, b, E_approx # 对每个单品应用此函数 demand_params {} for pid, group in merged_df.groupby(product_id): a, b, E estimate_demand_function(group) demand_params[pid] {a: a, b: b, elasticity: E}3.3 模型求解解析解与数值方法对于线性需求函数Q(p) a - b*p并且假设Sales(p) Q(p)即库存充足利润函数π(p) (p-c)*(a-b*p)是一个关于p的二次函数。求导令其为零可得最优价格的解析解p* (a b*c) / (2*b)前提是p*在[p_lower, p_upper]区间内且对应的Q(p*)非负。然而我们引入了库存约束I和损耗惩罚。此时问题变为一个分段函数优化问题如果最优解p*对应的Q(p*) I则库存充足解析解p*仍然有效实际销量为Q(p*)。如果Q(p*) I则库存成为瓶颈。此时利润函数变为π(p) (p-c)*I - θ*(I - min(Q(p), I))但注意当Q(p) I时第二项为0。实际上当库存是硬约束时最优策略往往是提价以减少需求使其恰好等于或略低于库存同时享受更高的单价利润。此时问题简化为在约束Q(p) I下最大化(p-c)*Q(p)。代入Q(p)a-b*p I得到p (a-I)/b。结合价格上限最优价格可能是p_upper也可能是使Q(p)I的那个价格p_I (a-I)/b需要比较两者利润。更一般地当考虑损耗惩罚时问题没有简单的解析解。我们采用数值搜索法如黄金分割搜索、梯度下降在价格区间[p_lower, p_upper]内寻找使目标函数最大的p。这对于计算机来说非常高效。# 示例考虑库存和损耗惩罚的定价优化数值解法 from scipy.optimize import minimize_scalar def optimize_price(cost, a, b, inventory, penalty_theta, price_low, price_high): 优化目标函数利润 (p - cost) * sales - penalty_theta * max(0, inventory - sales) 其中 sales min(max(0, a - b * p), inventory) # 确保销量非负且不超过库存 def objective(p): demand max(0, a - b * p) # 预测需求 sales min(demand, inventory) # 实际销量 revenue (p - cost) * sales leftover max(0, inventory - sales) penalty penalty_theta * leftover profit revenue - penalty return -profit # 因为我们要最大化利润所以取负值用于最小化函数 # 使用有界优化算法 result minimize_scalar(objective, bounds(price_low, price_high), methodbounded) optimal_price result.x max_profit -result.fun # 转回正利润 return optimal_price, max_profit # 调用示例 cost 2.5 a 100 b 8 inventory 15 penalty_theta 0.5 # 假设每剩余一件库存产生0.5元的损耗风险成本 price_low cost * 1.1 # 最低售价为成本价上浮10% price_high cost * 3.0 # 最高售价为成本价3倍 opt_price, opt_profit optimize_price(cost, a, b, inventory, penalty_theta, price_low, price_high) print(f最优价格: {opt_price:.2f}, 预期利润: {opt_profit:.2f})4. 补货模型动态规划与s, S策略的融合定价决定了“怎么卖”补货则决定了“进多少”。蔬菜补货的特殊性在于今日补货影响未来多日的销售与损耗。这是一个典型的动态库存问题尤其适合用动态规划DP或随机动态规划来建模。但在72小时的竞赛中实现一个完整的多物品、多期随机DP挑战巨大。我们采用了近似策略将多期问题分解为一系列单期决策并用s, S策略进行优化。4.1 核心思想基于需求预测的安全库存与目标库存s, S策略是库存管理的经典策略当库存水平低于再订货点s时触发补货将库存补充至目标水平S。我们的任务就是为每个单品或分类确定合适的s和S。需求预测首先我们需要预测未来L天补货提前期可售时间窗的需求。我们使用了时间序列模型如 Holt-Winters 季节性模型对每个单品的日销量进行预测。考虑到比赛时间我们采用了更轻量的移动平均季节性因子的方法。# 示例简单的需求预测移动平均法 def forecast_demand(historical_sales, lead_time1, seasonality7): historical_sales: 历史销量序列列表或Series lead_time: 需要预测的未来天数 seasonality: 季节性周期例如7天 # 计算最近一个周期的平均值 recent_avg np.mean(historical_sales[-seasonality:]) # 可以考虑加入趋势或季节性调整因子这里简化处理 forecast [recent_avg] * lead_time return forecast确定安全库存s安全库存用于应对需求的不确定性。我们使用历史预测误差如均方根误差 RMSE 或平均绝对偏差 MAD来度量不确定性。设定一个服务水平如95%的订单满足率安全库存safety_stock z * σ * sqrt(L)其中z是服务水平对应的标准正态分布分位数如95%对应1.65σ是日需求的标准差L是提前期。再订货点s 提前期内的预测需求 安全库存。确定目标库存水平S目标库存S需要平衡持有成本、缺货成本和补货成本。一个常用的启发式方法是S s Q其中Q是经济订货批量EOQ。但EOQ假设需求恒定在生鲜场景下不适用。我们将其简化为S 预测的未来T天例如可售时间的总需求 安全库存。这里的T需要根据商品的保质期可售时间和补货频率来调整。4.2 模型整合补货决策流程每日补货决策流程如下检查当前库存I。预测未来LT天的需求D_forecastL为提前期T为计划覆盖期。计算再订货点s sum(D_forecast[0:L]) safety_stock。计算目标库存水平S sum(D_forecast[0:LT]) safety_stock。决策如果I s则触发补货补货量order_quantity S - I。否则不补货。约束处理补货量需考虑供应商最小起订量、车辆装载容量、仓库容量等。题目中可能隐含了这些约束需要在模型中作为边界条件加入。实操心得在比赛中我们并没有为每个单品单独计算复杂的s和S。而是先按商品分类中类进行聚类因为同类商品的需求模式和批发价波动相似。为每个分类计算一套(s, S)参数然后应用到该分类下的所有单品大大减少了计算量且结果更稳健。对于特别重要或销量大的单品如西红柿、黄瓜再单独微调参数。5. 模型联调与策略评估搭建模拟器是关键定价模型和补货模型不是孤立的。定价影响销量销量影响库存和补货决策补货决策影响库存水平库存水平又通过损耗惩罚影响定价。因此必须将两个模型耦合起来进行评估。5.1 构建历史数据模拟器我们搭建了一个离散事件模拟器以历史数据为输入模拟运行我们设计的定价与补货策略并计算关键绩效指标KPIs。输入历史销售流水、批发价序列、初始库存。模拟逻辑按时间步长天推进。每天对于每个有库存的单品根据当天的成本、库存、预测需求运行定价模型得到建议售价我们假设超市完全按此定价。然后根据这个售价和需求函数模拟当天的销量可加入随机扰动模拟不确定性。接着更新库存。最后运行补货模型决定是否补货及补货量假设补货立即到货或考虑固定提前期。输出整个模拟周期内的总销售额、总成本、总利润、平均毛利率、库存周转率、损耗率等。# 示例简化模拟器框架 class SupermarketSimulator: def __init__(self, historical_data, demand_params_dict, pricing_func, replenishment_func): self.data historical_data.sort_values(sale_date) self.demand_params demand_params_dict self.pricing_func pricing_func # 定价函数 self.replenish_func replenishment_func # 补货函数 self.inventory {} # 当前库存 {product_id: quantity} self.results [] def run_simulation(self, start_date, end_date, initial_inventory): self.inventory initial_inventory.copy() current_date start_date while current_date end_date: daily_data self.data[self.data[sale_date] current_date] if daily_data.empty: # 没有销售记录的日子只进行补货决策 self._make_replenishment(current_date) current_date pd.Timedelta(days1) continue daily_profit 0 daily_sales_volume 0 for _, row in daily_data.iterrows(): pid row[product_id] cost row[cost_price] if pid not in self.inventory or self.inventory[pid] 0: continue # 无库存可售 # 1. 定价决策 a, b self.demand_params[pid][a], self.demand_params[pid][b] opt_price, _ self.pricing_func(cost, a, b, self.inventory[pid], ...) # 2. 模拟销售加入随机噪声 predicted_demand max(0, a - b * opt_price) # 简化实际销量为预测需求与库存的较小值并加10%的随机波动 actual_sales int(min(predicted_demand, self.inventory[pid]) * np.random.uniform(0.9, 1.1)) actual_sales max(0, actual_sales) # 3. 更新库存和利润 self.inventory[pid] - actual_sales profit (opt_price - cost) * actual_sales daily_profit profit daily_sales_volume actual_sales # 每日结束后进行补货决策 self._make_replenishment(current_date) self.results.append({ date: current_date, daily_profit: daily_profit, daily_sales: daily_sales_volume, total_inventory: sum(self.inventory.values()) }) current_date pd.Timedelta(days1) return pd.DataFrame(self.results) def _make_replenishment(self, date): # 调用补货函数更新 self.inventory # 这里需要根据补货模型计算补货量 pass5.2 策略对比与参数调优模拟器的最大价值在于可以进行对比实验和参数调优。基准策略可以设定一个简单的基准策略如“成本加成定价”售价成本*固定系数和“定期定量补货”。用模拟器计算其历史表现。我们的策略运行我们设计的定价补货模型得到模拟结果。对比指标比较总利润、利润率、库存周转率、服务水平不缺货率等。我们的目标不仅是总利润更高还要保证策略的稳健性利润波动小和可行性补货频率和批量合理。通过模拟器我们可以对模型中的关键参数进行调优例如定价模型中的损耗惩罚系数θ补货模型中的服务水平z和计划覆盖期T。可以采用网格搜索或启发式方法寻找使模拟总利润最大化的参数组合。6. 论文写作与可视化讲好一个逻辑闭环的故事数学建模竞赛模型和代码只占一半分数另一半在于论文——你如何清晰、严谨、有说服力地展示你的工作。6.1 论文结构骨架摘要重中之重用一段话概括问题、你的方法、模型、算法和主要结论。必须包含关键指标的具体数值提升如“相比基准策略总利润提升XX%”。问题重述与分析不要照抄题目。用自己的话梳理问题的背景、目标、约束条件和难点。画出逻辑关系图。模型假设与符号说明列出所有重要假设并说明其合理性。制作清晰的符号说明表。数据分析与预处理展示你对数据的深刻理解。包括数据概览、分布可视化、异常值处理、特征工程如弹性计算的过程和结果图表。模型建立定价模型详细推导目标函数、约束条件解释需求函数估计方法给出解析解或数值解法流程。补货模型阐述s, S策略在此问题上的应用说明需求预测、安全库存、目标库存的计算方法。模型耦合说明两个模型如何协同工作可以用一个流程图展示每日决策流程。模型求解与模拟介绍模拟器的设计、参数设置、基准策略。展示参数调优的过程如θ和z对利润的影响曲面图。结果分析这是展示你工作价值的核心部分。整体效果用表格和对比柱状图展示你的策略 vs 基准策略在总利润、平均毛利率等核心指标上的表现。典型商品分析选取几个有代表性的商品如高销量、高损耗、价格敏感展示在你的策略下其价格、销量、库存的动态变化曲线图。敏感性分析改变关键假设如需求预测误差增大、批发价波动加剧测试模型的鲁棒性。策略建议基于模型结果给超市管理者几条具体、可操作的建议例如“对于菠菜这类损耗快的叶菜建议采用更高的损耗惩罚系数实行‘少进快销’策略”。模型评价与推广客观评价模型的优点如考虑因素全面、可解释性强和局限性如对历史数据质量依赖高、未考虑竞争对手定价并提出可能的改进方向如引入机器学习预测、考虑多商品联合补货。参考文献与附录规范引用。附录可放置核心代码片段、大量中间结果表格。6.2 可视化技巧一图胜千言多用组合图。例如用双Y轴图表同时展示某个商品的价格曲线和销量曲线直观显示价格弹性。热力图展示不同商品分类在不同参数下的利润表现用于参数调优分析。流程图清晰地展示你的整体建模思路和决策流程。表格要精炼只放最重要的汇总结果。详细数据放在附录。踩坑实录我们第一次模拟结果总利润反而比基准策略低。排查后发现是定价模型中的损耗惩罚系数θ设置过高导致系统过于保守宁愿低价倾销也不愿承受任何库存风险虽然损耗降低了但整体毛利大幅下滑。通过模拟器进行参数扫描我们找到了一个平衡点使利润最大化。这个过程及其分析成为了我们论文结果分析部分的一个亮点体现了模型调优的价值。从看到“[C题]思路更新中....”的茫然到建立起包含数据分析、定价模型、补货模型、模拟评估的完整框架这72小时是对团队知识储备、编程能力和应变能力的极限考验。回顾这次实战最深的体会是数学建模竞赛不是比谁的模型最高深而是比谁对问题的理解更透彻谁的解决方案更完整、更自洽、更能用数据和逻辑说服人。不要沉迷于炫技般的复杂算法把基础模型如线性规划、时间序列、s, S策略用扎实、用透彻并巧妙地组合起来解决实际问题往往能取得更好的效果。最后一定要留足时间给论文写作和检查一篇逻辑清晰、图表美观的论文是赢得评委青睐的关键。
返回列表