
简介一套针对2024年高教社杯全国大学生数学建模竞赛C题“农作物的种植策略”的可视化代码资源面向正在备赛的建模选手帮助解决从原始Excel数据到图表呈现之间繁琐的数据处理与绘图问题。压缩包内共三十一个文件包括六个Python脚本、五个Excel数据文件和二十张PNG图表整体大小约2MB其中脚本覆盖数据导入清洗、描述性统计分析、异常值检测、占地面积与价格分布、多变量关系探索等环节Excel文件则包含导入整理后的中间数据便于复现和检验。已有1005人学习下载适合参赛者快速借鉴可视化方案也可作为赛前练手的数据分析样例。借助这些代码读者可以学习如何利用Python绘制相关性热图、柱状图、箱线图、QQ图、散点图等常用图表并围绕农作物种植策略形成一套清晰的数据表达逻辑节省大量编码和调试图时间更好聚焦模型构建与结果解读。此外脚本按功能拆分、结构清晰读者可替换自身数据快速调整形成适合自己团队的分析流程。 2024年高教社杯全国大学生数学建模竞赛C题我拿到题的第一反应是这道题本质上是把一个村庄几十块地、十几种作物、两季种植、还有智慧大棚的复杂场景压缩成一个多约束的优化问题。表面是“种地”实际是线性规划加不确定性的组合拳。更关键的是很多队伍能建模求解但最后交上去的图又小又糊方案结果没有可视化支撑评委看得费劲自然拿不到高分。这篇我就把C题的建模思路、稳健优化处理和可视化代码完整梳理一遍重点说曲线救国过程中踩过的坑以及怎么用三张图把整个种植策略讲透适合正在备赛数模、尤其是打算走优化路线的同学参考。1. 先看明白C题这不是种地是解一个大规模优化问题1.1 题目到底在问什么把“种地”翻译成数学语言2024年C题“农作物的种植策略”核心场景我是这样理解的有若干地块分为普通地块和智慧大棚两类有若干种农作物分为粮食作物和蔬菜每年分两季种植。题目要求制定种植方案使得总收益最大同时要满足轮作、豆类占比、大棚作物基本需求等限制。第一问基本是确定性优化第二问加入销售量、价格、成本的波动第三问要做更极端的不确定性分析。把这段描述翻译成数学语言核心就三个部分。决策变量设为(x_{ijk})表示第i个地块在第j季种第k种作物的面积。目标函数是最大化总利润利润等于销售收入减去种植成本而销售收入又和亩产量、销售单价、实际销售量有关。约束条件就多了单块地面积不能超过总面积每种作物的总种植面积不能超过预期销售量对应的面积上限同一种作物不能在同一个地块连作豆类作物种植面积占比通常有最低要求大棚作物因为有温度和湿度优势亩产和成本都和露地不同需要单独处理。为什么这题适合用线性规划而不是启发式算法因为目标函数和大部分约束都是线性的决策变量是连续面积不是0-1选择规模也就是几百个变量、几百个约束CBC或GLPK求解器几秒钟就能出结果完全没必要上遗传算法。我记得当时看到有队伍用遗传算法去解结果调参调了一整天最后解的质量还不如线性规划好属于典型的杀鸡用牛刀。1.2 四个容易被忽略的题目细节新手很容易只盯着“利润最大化”看而忽略了一些在评分标准里占分很多的隐性约束。第一个是同作物连作限制。题目背景里明确提到某些作物不能在同一地块连续种植。实现上可以增加一个跨季约束若某地块第一季种了作物k第二季就不能再种k。这个约束在建模时用“第二季该作物的面积 第一季该作物面积的反向标记”实现或者更简单地提前规定哪些作物不能连作在数据准备阶段就排除。第二个是豆类固氮作用。政策上通常要求豆类种植面积占总种植面积的一定比例我记得C题常见要求是大于等于某个阈值比如10%到15%之间。这一点很容易被忘记但恰恰是题目想考察的“农业可持续性”思想。要是你的方案里豆类面积为零后面稳健性做得再好这一条也会扣分。第三个是大棚作物的特殊处理。智慧大棚通常种植番茄、黄瓜、茄子等蔬菜亩产高于露地但建设成本和维护成本也高。题目会给出大棚条件下不同作物的亩产和成本数据建模时不能把所有地块一律看待需要把地块类型作为索引维度来处理。第四个是销售量的上限约束。每种作物的预期销售量是有限的按亩产换算成最大种植面积后不能超过这个上限。否则算出来的最优方案可能让粮食堆满仓库现实中根本卖不出去评委一眼就能看出模型有问题。这四个细节如果在数据预处理阶段就内化到约束里后面求解会顺利很多如果等求解报错再回去改很容易在约束之间制造冲突排查起来相当痛苦。2. 建模两种思路确定性模型和稳健优化模型2.1 基础模型最大化利润的线性规划先给一个能直接跑通的最小示例帮新手把pulp库的建模流程过一遍。假设有5个地块、3种作物不考虑轮作时模型长这样from pulp import LpProblem, LpMaximize, LpVariable, LpStatus, value plots [A1, A2, A3, A4, B1] crops [小麦, 玉米, 大豆] areas {A1: 12, A2: 10, A3: 8, A4: 15, B1: 6} profit {小麦: 520, 玉米: 430, 大豆: 380} limit {小麦: 30, 玉米: 35, 大豆: 18} prob LpProblem(crop_planning, LpMaximize) x {(i, j): LpVariable(fx_{i}_{j}, 0) for i in plots for j in crops} # 目标函数总利润最大化 prob sum(profit[j] * x[i, j] for i in plots for j in crops) # 约束1每个地块的种植面积不超过可用面积 for i in plots: prob sum(x[i, j] for j in crops) areas[i] # 约束2每种作物的总种植面积不超过销量上限 for j in crops: prob sum(x[i, j] for i in plots) limit[j] prob.solve() print(LpStatus[prob.status]) for (i, j), var in x.items(): if var.varValue and var.varValue 1e-6: print(i, j, round(var.varValue, 2))这段代码跑出来就是每一块地上种什么、种多少。实际赛题只要把plots、areas、profit、limit替换成题目给的地块表和作物表再把轮作约束、豆类比例约束加进去就是一个能用的第一问模型。pulp的语法很直观变量是LpVariable约束用“prob 表达式 不等式 0”的写法初学者半小时就能上手。2.2 面对波动选min-max稳健优化还是随机规划第二问开始加入不确定性这才是C题的真正分水岭。销售量会变、价格会变、成本也会变如果只用确定性模型算出来的“最优方案”在波动面前可能非常脆弱。主流做法有两种随机规划和稳健优化。随机规划需要对不确定性参数假设概率分布比如假设价格服从正态分布然后用蒙特卡洛抽样生成多个情景在每个情景下分别计算目标最后优化期望利润。这种方法理论完备但分布假设一旦不靠谱结果就失真。很多队伍不管三七二十一直接假设正态分布评委问一句“依据是什么”就答不上来。我更推荐用min-max稳健优化思路特别简单把所有可能的情景列出来算出每个情景下的总利润取其中最小的那个把这个“最坏利润”当作目标去最大化。换句话说我不赌哪个情景会发生我确保在最差的情况下利润依然高。公式写出来就是[ \max_{X} \min_{s} Z_s(X) ]这里(Z_s(X))是第s个情景下的总利润。这个max-min问题可以通过引入一个辅助变量W来线性化令目标函数变成最大化W然后添加约束(W \le Z_s(X))对所有情景s成立。这样仍然是线性规划求解难度没有本质变化。在pulp里实现时核心就是上面这段代码的多情景版本。每个情景单独算一遍利润表达式然后全部作为W的上界约束。实际操作时情景生成可以这样做以题目给的预期销售量为基准上下浮动10%、20%、30%加上价格上下浮动组合出十几个情景。不需要做几千次蒙特卡洛十几个代表性情景就足够说明问题了。2.3 如何在论文里解释稳健性结果建模完成后评委更关心的是你的方案凭什么比别人稳。强烈建议做一张小幅对比表列出三列确定性最优方案、稳健最优方案、以及两者在极端情景下的利润。表格大概长这样方案类型基准利润万元最坏情景利润万元最坏情景相对基准下降确定性最优118086027.1%稳健最优109010156.9%这张表放在论文里比写三页文字都管用。它清晰地说明确定性方案在正常情况下更赚钱但遇到波动就会崩稳健方案虽然基准利润低一些但最坏情况下依然能守住千万级利润。而可视化代码要做的就是把这张表背后的东西用图讲出来。3. 可视化代码实战三张图讲清楚一个策略3.1 环境准备与中文字体坑可视化我用的是Python的matplotlib搭配pandas和numpy做数据处理。第一件事处理中文字体matplotlib默认字体不支持中文直接画图会出现小方框。代码开头先固定设置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False不同操作系统能用哪个字体不一样。Windows下SimHei基本都能用Linux服务器上通常要装fonts-noto-cjkmacOS用Arial Unicode MS最稳。axes.unicode_minus设置为False是为了让负号正常显示否则坐标轴上的负号也会变方块。3.2 第一张图种植策略地块热力图我花了最多心思的图就是这张。它把题目给定的村庄地块布局图按真实坐标画成矩形色块每个颜色代表一种作物。这样评委一眼就能看出整个村庄的地块被分配成了什么结构比几十行数字表格直观得多。import matplotlib.pyplot as plt from matplotlib.patches import Rectangle # 示例数据地块名、类型、左下角坐标、宽高 plots [ {地块: A-01, 类型: 普通, x: 0, y: 0, w: 4, h: 3}, {地块: A-02, 类型: 普通, x: 4, y: 0, w: 4, h: 3}, {地块: A-03, 类型: 普通, x: 0, y: 3, w: 4, h: 3}, {地块: A-04, 类型: 普通, x: 4, y: 3, w: 4, h: 3}, {地块: B-01, 类型: 智慧大棚, x: 8, y: 0, w: 3, h: 4}, {地块: B-02, 类型: 智慧大棚, x: 8, y: 4, w: 3, h: 4}, ] # 第一季的种植方案由求解结果得到 strategy { A-01: 小麦, A-02: 小麦, A-03: 大豆, A-04: 玉米, B-01: 番茄, B-02: 黄瓜, } colors { 小麦: #d4a373, 大豆: #a3b18a, 玉米: #ffd166, 番茄: #ef476f, 黄瓜: #06d6a0, 休耕: #e0e0e0, } fig, ax plt.subplots(figsize(10, 6)) for p in plots: crop strategy[p[地块]] rect Rectangle((p[x], p[y]), p[w], p[h], facecolorcolors[crop], edgecolorwhite, linewidth2) ax.add_patch(rect) ax.text(p[x] p[w]/2, p[y] p[h]/2, f{p[地块]}\n{crop}, hacenter, vacenter, fontsize10) ax.set_xlim(-1, 12) ax.set_ylim(-1, 9) ax.set_aspect(equal) ax.set_title(第一季种植策略地块分布, fontsize14) plt.show()实际比赛时只需要把plots列表换成题目给出的地块坐标信息再把strategy换成由求解结果生成的字典就能完整复现。这里有一个关键经验地块坐标要从题目给的布局图里提取好我当年是先把村庄图放大把每个矩形的左下角坐标和宽高记到Excel里再读入代码。这一步看似机械其实特别容易出错少记一个地块、搞错宽高都会导致热力图错位。建议提取完坐标后先画一个纯灰底图人工对照题目图核对一次再往下走。3.3 第二张图两季种植面积堆叠图热力图展示的是空间分布但评委还想看结构变化第一季种了什么第二季种了什么豆类占比有没有达标。这时用堆叠柱状图最直观。import matplotlib.pyplot as plt seasons [第一季, 第二季] wheat [260, 0] corn [80, 240] soy [100, 0] veg [40, 30] fig, ax plt.subplots(figsize(8, 5)) ax.bar(seasons, wheat, label小麦, color#d4a373) ax.bar(seasons, corn, bottomwheat, label玉米, color#ffd166) bottom_soy [wheat[i] corn[i] for i in range(2)] ax.bar(seasons, soy, bottombottom_soy, label大豆, color#a3b18a) bottom_veg [bottom_soy[i] soy[i] for i in range(2)] ax.bar(seasons, veg, bottombottom_veg, label蔬菜, color#06d6a0) ax.set_ylabel(种植面积亩) ax.set_title(两季作物种植面积结构) ax.legend() plt.show()这段代码把两个季度的面积数据分别堆叠起来。绘图时我把面积数据按季分组每一季的作物面积加起来等于该季所有地块可用面积之和。这样能顺带检查求解结果有没有违反面积约束如果堆出来的总高度和可用面积对不上那多半是前面数据处理时就漏了地块。豆类占比是否达标也可以在这张图上加一条水平虚线表示目标比例或者在上方注释里写出实际占比。比如第一季大豆种了100亩总种植面积480亩占比约20.8%超过10%的要求那这个结构就是合规的。写论文时这张图直接对应“方案可行性”的论证。3.4 第三张图利润分布直方图第三张图专门服务稳健性分析。用蒙特卡洛模拟价格波动假设价格在基准值附近随机浮动计算五千次总利润画成直方图再标出基准利润和最坏情况分位数。这张图能让评委直观看到方案的抗风险能力。import numpy as np import matplotlib.pyplot as plt np.random.seed(42) base_profit 1090 # 稳健方案基准利润单位万元 price_factor np.random.normal(1.0, 0.08, 5000) profits base_profit * price_factor fig, ax plt.subplots(figsize(9, 5)) ax.hist(profits, bins40, color#457b9d, edgecolorwhite) ax.axvline(base_profit, colorred, linestyle--, linewidth2, label基准利润) ax.axvline(np.percentile(profits, 5), colororange, linestyle--, linewidth2, label5%分位数) ax.legend() ax.set_xlabel(总利润万元) ax.set_ylabel(频数) ax.set_title(价格波动下的总利润分布) plt.show()这里用price_factor乘以基准利润模拟所有作物价格同比例波动的简化情形。实际操作中可以细化到每种作物单独抽样然后按情景汇总。我在跑这张图的时候发现一个很有意思的现象确定性最优方案在蒙特卡洛模拟下的5%分位数利润往往比稳健方案低不少而且左尾很长说明极端差情景出现的概率虽然低但一旦发生损失很大。这个信息在直方图上非常明显直接截图放论文里比任何文字都有说服力。如果还想更进一步可以画并排的双直方图一张是确定性方案一张是稳健方案用透明色叠加。评委一眼就能看出稳健方案的分布更“胖”、更“聚拢”这就是方案稳健性的视觉化证明。4. 实战中的常见报错与排查技巧整个建模和可视化过程中我踩过不少坑也帮同学排查过很多次整理成表格给后来人避雷。症状常见原因解决方案matplotlib中文全变方块系统缺少对应中文字体安装Noto Sans CJK或用英文图例pulp求解结果是Infeasible多个约束互相冲突先关闭豆类占比或轮作约束逐步放开定位冲突热力图矩形重叠地块坐标或宽高数据录入错误先画灰色底图和题目原图人工比对热力图整体变形坐标系纵横比没设置调用ax.set_aspect(equal)堆叠图各季总高度不一致求解结果包含休耕地未计入面积检查每个地块面积之和是否等于总面积保存图片模糊dpi设置过低保存时加dpi300bbox_inchestight利润分布全是负数成本数据单位没有统一核对元/亩和万元/亩的换算还要特别提醒一点pulp求解出Infeasible时不要急着删约束找一个小规模数据测试是最高效的定位方式。我常用的做法是先把所有约束去掉只保留目标函数确认能解然后一条一条把约束加回去加到哪一步报错了问题就锁定在哪一步。这个方法尤其适合面积限制、轮作限制、豆类限制三者同时存在的情况。绘图方面我也有一条个人原则竞赛图一定不要用seaborn默认的深色网格背景。评委打印论文时深色背景既费墨又看不清白色背景加浅色网格才是稳妥选择。另外图例字体不要小于8号坐标轴标签不要小于10号每张图的标题要直接点出想表达的核心结论比如“稳健方案在最坏情景下利润仍保持1000万元以上”而不是写“利润分布图”。5. 从出图到成文可视化怎么帮你把分数拿稳5.1 好图的四个标准信息、美观、自解释、可引用竞赛评阅速度快评委没有时间仔细读每一段代码和公式。一张好的图必须满足四个标准信息完整、排版美观、不依赖正文就能看懂、在论文里可以被明确引用。热力图属于空间信息型堆叠图属于结构信息型直方图属于不确定性信息型。三张图分别回答了三个评委最关心的问题方案是什么、方案是否合理、方案靠不靠谱。如果你的可视化只做出一张图或者做的全是长方形折线图那信息密度就太低了。5.2 三张图如何对应论文的三个核心结论我建议论文的正文逻辑和图的顺序完全对应。第一问写完模型后立刻放热力图说明“各地块种植结构如下”第二问分析结构合理性时放堆叠图说明“两季作物面积分配符合轮作和豆类占比约束”第三问做稳健性分析时放直方图说明“在最坏情景下方案利润仍然可观”。这样做的好处是每个核心论点都有图作为证据论文不会变成干巴巴的公式堆砌。图表要有编号正文里用“如图1所示”来引用而不是把图直接丢到正文后面不管。5.3 一点真实经验最后说一点真实经验画图这件事一定要排在论文写作之前先让方案能够被三张图完整讲故事再动笔写文字。我会先把模型的求解结果全部导出成CSV然后用matplotlib把热力图、堆叠图、直方图跑通确认结果符合预期后再根据图的结论反推论文段落结构。这样写的时候逻辑紧凑不会写到一半发现图和文字对不上。另外画图控制在固定时间内很重要。我第一次做的时候在颜色选择和字体调优上花了整整一天后来发现图的配色只要统一、清晰、不扎眼就够了没必要追求审美突破。把省下来的时间拿去多测几个情景、多调几轮约束对分数的提升更大。我的建议是建模花两到三天求解和稳健性分析花一到两天可视化半天到一天留出至少半天统一排版和改图时间就不会失控。这个安排不一定适用所有人但方向是对的重心永远在模型本身可视化只是让模型价值被看见的手段。本文还有配套的精品资源点击获取