Python数据可视化实战:2024数模竞赛C题种植策略全复盘
2026/9/20 21:14:34 网站建设 项目流程

简介:围绕2024年高教社杯全国大学生数学建模竞赛C题,本份资源提供一套完整的农作种植策略可视化代码,面向参赛学生、建模爱好者以及需要完成数据可视化分析的研究者。压缩包内含31个文件,整体大小约2MB,其中6个Python脚本覆盖数据预处理、描述性统计、异常值分析、柱状图与散点图绘制等关键步骤;20张PNG图片展示地块面积分布、销售单价与亩产量关系、相关性热图、箱线图、QQ图等核心分析结果;5个Excel文件包含原始附件及清洗整理后的数据,方便直接对照复现。已有1005人学习下载,适合备赛期快速上手。资源以“数据清洗—指标分析—可视化输出”为主线,帮助读者理解农作物种植策略中的产量、价格、成本与面积等多维指标关联,掌握用matplotlib等工具将模型结果转化为直观图表的方法,可直接用于论文配图或模型效果展示,有效节省从零编写代码的时间。 2024年高教社杯数模竞赛C题出来那天,我看群里不少队伍第一反应是“这题怎么这么长”,满屏的作物名称、地块编号、预期销量区间,光是读题就花了一个多小时。但等我自己把数据拉进Python、把几组关键图表画出来之后,才意识到这道题真正的难点不在题面长,而在于它把“不确定性”藏在了每一张表格里。这篇东西就是我做完C题可视化部分后的一份完整复盘,包含从数据结构梳理、图表选型,到核心绘图代码、以及从图里反推建模方向的全部思路,希望能帮到正在备赛或者打算用Python做竞赛可视化的同学。

C题的种植策略本质上是一个多地块、多作物、多年份的资源配置优化问题,题目给的数据不是传统意义上的“确定值”,更多是以预期区间形式出现的销量、成本和价格。这就意味着,你用表格逐行看数据时,很难直观感受到“哪种作物在哪种地块上更值得种”“市场波动对方案冲击有多大”,但一旦换成合适的图,很多结论几乎是自己跳出来的。接下来我按当时实际操作的顺序,把这套可视化方案从头到尾拆开讲。

1. C题到底在考什么:先给2024年的种植数据算一笔明白账

1.1 题面信息的基本盘

先捋一下C题给的信息形态。题目围绕某乡村的农作物种植展开,核心数据大概分几类:一是地块信息,包括不同地块的面积、所属村组、地块类型;二是作物信息,包括作物名称、种植成本、亩产量、销售价格、预期销售量;三是时间维度,覆盖2024到2030年,并且很多关键参数是以“区间”形式给出的。

这个“区间”是C题和我以前做过的很多优化题最不一样的地方。往年不少题目给出的价格、销量都是一个确定数,你按确定参数建模就行;但这道题里,预期销售量可能是一个波动范围,价格也未必是固定值。你在建模时首先要回答的问题就是:我到底按区间的哪个值来算收益?是保守取下界、乐观取上界,还是做情景加权?可视化此时的作用,就是把这种不确定性摊开在你们面前,而不是让它藏在表格深处。

1.2 从任务反推核心变量

C题通常会有多个问题,我当时是把任务拆成三层来理解的。

第一层,给定未来几年的基本数据,不考虑额外复杂约束,制定一个最优种植方案,目标基本是“收益最大化”,约束包括地块面积、作物适宜性、市场需求上限等。第二层,会加入轮作或重茬约束,比如豆类作物不能连作、某些作物需要间隔一定年份才能再次种植,这会让问题从“单年优化”升级成“多年份联动的排程问题”。第三层,引入智慧大棚等新条件,相当于把一部分地块的种植能力做了升级,成本变了、可种作物范围变了,方案也要随之调整。

因此这道题真正要你们回答的,其实是三件事:种什么、种在哪、种多少。所有可视化和建模工作都是围绕这三个问题展开的。你在做图之前,最好先把这三个问题写在一张纸上,不然很容易画出一些“好看但没用”的图。

1.3 为什么可视化在这道题里是明确的加分项

我见过不少队伍,论文里放了三张柱状图就交上去了。但在C题这种信息量很大的题目里,图表的角色远不止“证明你做了数据分析”,它其实是你的推理链条。评委看论文的时间非常有限,与其让他们在密密麻麻的表格里找你“为什么选这个方案”,不如直接用图把“我和方案之间的因果关系”呈现出来。

具体到C题,至少有三类图是值得做的:展示作物间经济效益差异的对比图、展示未来几年市场不确定性的区间图、展示最终种植方案在时间和地块两个维度上分布的图。如果这三类图做好了,整篇论文的骨架就立住了。

2. 可视化方案的设计思路:先想清楚每张图要替你说什么

2.1 图的本质是证据链,不是装饰

很多同学做可视化有个误区,先不管数据长什么样,打开matplotlib就是一顿炫技,最后画了一堆3D曲面图、桑基图,结果评委根本看不明白。我做竞赛图的第一个原则是:每张图都必须能回答一个具体问题。

C题里我觉得值得回答的问题包括:哪些作物亩均收益高?哪些作物虽然单价高但产量低?哪些地块适合种什么?未来销量区间变化对方案影响大吗?轮作约束下,同一块地在年份间的种植安排是否合理?这一串问题列出来,图的选型基本就定了,而不是反过来。

2.2 图表的四类角色分工

以我当时C题的可视化方案为例,图表大体分四类。

第一类是经济效益对比图,用来展示不同作物的亩产量、成本、价格、亩均利润。这类图解决的是“种什么”的问题,是所有后续建模的基础输入。第二类是市场预期区间图,把2024到2030年每种作物的预期销量和价格区间用区间带画出来。它解决的是“市场到底有多不确定”的问题。第三类是方案结果图,把模型算出来的最优种植方案投射到地块和年份的矩阵上,用热力图或者堆叠图呈现。第四类是约束检查图,专门画一个“轮作序列”,看同一块地上连续年份种了什么作物,一眼就能发现有没有违规连作。

这样划分之后,整个可视化的叙事逻辑就很清楚了:先看基础数据,再看市场环境,然后看方案生成,最后验证方案的可行性。你论文里的图表顺序也完全可以按这条线走。

2.3 配色、布局和标注的取舍原则

竞赛图表和平时做数据分析的图表有个重要区别:评审场景是纸质阅读和时间有限的观看。因此配色要尽量用同一色系或少量强调色,不要用彩虹色;字号要偏大,坐标轴标题要写完整,图例位置要固定且不遮挡数据。更关键的是,每一张图都要有一个“一眼能看出的结论”,如果一张图看了十秒还不知道想表达什么,那这张图就应该重画。

我一般会在图旁边直接加一个文本框,写上一句类似“2024-2030年豆类轮作约束下,地块A的种植序列无连作违规”的结论。别觉得这么做多余——对评委来说,这比让他在图里自己找结论要友好太多。

3. 核心可视化代码拆解:从数据表格到成图的完整路径

3.1 数据读取与口径统一

先说我当时处理数据的方式。C题附件往往是多张Excel表,包含作物成本、销售数据、地块信息等。为了方便可视化,我第一步是把所有表按“地块编号+年份+作物名称”作为主键,整理成一张长表,列结构大致如下。

import pandas as pd import numpy as np # 示意:假设已经将题目附件整理为长表 clean_data.csv # 列说明:year年份、plot_id地块编号、crop作物名称、area亩数、 # yield_per_mu亩产量(斤/亩)、cost_per_mu种植成本(元/亩)、 # price_per_kg销售单价(元/斤)、sales_low销量下界(斤)、sales_high销量上界(斤) df = pd.read_csv('clean_data.csv', encoding='gbk') print(df.head()) print(df.dtypes)

需要注意,题目里有的字段单位可能是“吨”“公斤”,有的可能是“斤”,这就涉及单位统一问题。我当时把产量和销量统一换算成“斤”,把价格统一成“元/斤”,成本统一成“元/亩”,避免后面算收益时出现数量级错误。数据量不大的情况下,用pandas做这种清洗很快,但单位坑一旦踩中,后面所有图都会跟着错。

3.2 作物经济效益对比图:用柱状图直接锁定赢家

在“种什么”这个问题上,我当时画的第一张核心图是“亩均利润对比”。计算逻辑很简单:亩均收入 = 亩产量 × 单价;亩均利润 = 亩均收入 - 种植成本。需要注意,这里的“亩均收入”用的是题目给的销售价格,不是市场实时价格。

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # 处理中文显示 plt.rcParams['axes.unicode_minus'] = False # 按作物汇总平均数据 crop_stats = df.groupby('crop').agg( yield_per_mu=('yield_per_mu', 'mean'), cost_per_mu=('cost_per_mu', 'mean'), price_per_kg=('price_per_kg', 'mean') ).reset_index() crop_stats['profit_per_mu'] = crop_stats['yield_per_mu'] * crop_stats['price_per_kg'] - crop_stats['cost_per_mu'] crop_stats = crop_stats.sort_values('profit_per_mu', ascending=True) # 横向条形图,方便看清作物名称 fig, ax = plt.subplots(figsize=(10, 8)) bars = ax.barh(crop_stats['crop'], crop_stats['profit_per_mu'], color='#4C72B0') ax.set_xlabel('亩均利润(元/亩)') ax.set_title('各作物亩均利润对比(2024-2030平均)') # 在条形末端标数值 for bar, val in zip(bars, crop_stats['profit_per_mu']): ax.text(val + 5, bar.get_y() + bar.get_height()/2, f'{val:.0f}', va='center', fontsize=9) plt.tight_layout() plt.savefig('profit_per_mu.png', dpi=300) plt.show()

这张图画出来之后,我第一反应是“果然,高价值蔬菜类作物的亩均利润明显高于普通粮食作物”。但这里要特别提醒一个问题:亩均利润高的作物,未必应该无限扩种,因为题目里还有预期销量上限。你画完图一定要再问自己一句:高利润作物的销量天花板在哪里?如果市场只需要那么多,多种出来的部分怎么处理?这个问题是后面建模的核心,也是C题最容易区分队伍层次的地方。

3.3 预期销量区间图:把不确定性“画”出来

C题给出的预期销量往往不是一个单点,而是一个区间。我当时把每种作物的销量下界和上界分别取出来,用折线加填充带的方式画出来。这样不仅能看到变化趋势,还能直观看到“不确定性有多大”。

# 示例:以某一作物为例,画2024-2030年预期销量区间带 crop_name = '西红柿' # 替换为目标作物 crop_df = df[df['crop'] == crop_name].groupby('year').agg( sales_low=('sales_low', 'mean'), sales_high=('sales_high', 'mean') ).reset_index() fig, ax = plt.subplots(figsize=(10, 6)) ax.plot(crop_df['year'], crop_df['sales_low'], label='销量下界', color='#C44E52') ax.plot(crop_df['year'], crop_df['sales_high'], label='销量上界', color='#55A868') ax.fill_between(crop_df['year'], crop_df['sales_low'], crop_df['sales_high'], color='#C44E52', alpha=0.2, label='预期区间') ax.set_xlabel('年份') ax.set_ylabel('预期销量(斤)') ax.set_title(f'{crop_name} 2024-2030年预期销量区间') ax.legend() plt.tight_layout() plt.savefig('sales_range.png', dpi=300) plt.show()

从这张图里能明显读出两件事。第一,某些作物的预期销量区间特别宽,说明市场不确定性大,建模时不能把销量当成一个固定常数,否则方案很容易被“过度乐观”或“过度保守”带偏。第二,不同作物的区间宽窄差异可以作为风险度量的一个参考:区间越宽,意味着最终收益的波动可能越大。

我当时在论文里就把“区间宽度”处理成了一个风险指标,在目标函数里加入了对波动性的惩罚,这样算出来的方案比单纯把销量取下界要合理得多。

3.4 方案结果热力图:让评委一眼看懂你种了什么

模型跑完之后,最需要展示的是结果方案。C题的结果是一个高维组合,常见做法是画“地块-年份”热力图,颜色代表不同作物。这样一张图就能说清楚:每一块地在每一年到底种了什么,整体轮作关系是否清晰。

# 假设 solution_df 是模型输出的方案表 # 列:plot_id、year、crop、area solution_df = pd.read_csv('solution.csv', encoding='gbk') # 将作物名转为类别编码 solution_df['crop_code'] = solution_df['crop'].astype('category').cat.codes pivot = solution_df.pivot_table(index='plot_id', columns='year', values='crop_code', aggfunc='sum') # 用imshow画热力图 fig, ax = plt.subplots(figsize=(12, 8)) im = ax.imshow(pivot.values, cmap='tab20', aspect='auto') ax.set_xticks(range(len(pivot.columns))) ax.set_xticklabels(pivot.columns) ax.set_yticks(range(len(pivot.index))) ax.set_yticklabels(pivot.index) ax.set_xlabel('年份') ax.set_ylabel('地块编号') ax.set_title('最优种植方案:地块×年份 作物分布热力图') # 图例显示作物类别 crop_names = solution_df[['crop_code', 'crop']].drop_duplicates().sort_values('crop_code')['crop'].tolist() cbar = plt.colorbar(im, ticks=range(len(crop_names))) cbar.ax.set_yticklabels(crop_names) plt.tight_layout() plt.savefig('planting_heatmap.png', dpi=300) plt.show()

这里有个细节我踩过坑:当方案里作物数量比较多时,直接用作物名作为色块标签,图例会非常拥挤,颜色也容易混淆。我的解决办法是先按作物大类分组,比如“粮食作物”“豆类作物”“蔬菜类作物”“果类作物”,然后每个大类给一个主色调,同一大类下的不同作物用深浅区分。这样整张图既不会信息过载,又能看出轮作大类之间的交替关系。

3.5 轮作约束检查图:把连作风险标出来

C题的轮作约束是第二问以后的重头戏。比如豆类作物不能连续在同一地块种植,或者某类作物种植后需要间隔几年才能再种。手工检查这种约束非常痛苦,尤其是几十个地块、七年时间,手算基本不可能。我当时的方法是直接写一个“违规标记图”:对每个地块,逐年扫描种植序列,如果发现同一作物连续种植超过允许年份,就在对应位置打一个醒目的红色标记。

# 示例:检查每一地块上“豆类作物是否连续种植两年以上” legume_set = {'大豆', '绿豆', '红豆', '花生'} # 按题目要求调整 # 构建地块-年份-作物的透视表 crop_pivot = solution_df.pivot_table(index='plot_id', columns='year', values='crop', aggfunc='last') violations = [] for plot_id in crop_pivot.index: series = crop_pivot.loc[plot_id] for year in series.index[:-1]: if series[year] in legume_set and series[year + 1] in legume_set: violations.append((plot_id, year, year + 1)) print('连作违规记录:', violations) # 如果违规过多,可以在热力图上叠加红色网格标记

图上标红之后,你不需要再去论文里用大段文字描述“我们验证了轮作约束满足”,直接把这张图放上去,结论一目了然。哪怕模型结果确实违规了,这张图也能帮你在交卷前尽早发现逻辑漏洞,比模型跑完就万事大吉要稳妥得多。

4. 图表背后的建模启示:怎么从图中找到优化方向

4.1 高效益≠大面积扩种,市场天花板才是隐形约束

把作物经济效益对比图画出来之后,不少队伍会掉进一个陷阱:既然西红柿、草莓这些作物亩均利润高,那就大面积种它们。但你一旦叠加销量区间图就会发现,高效益作物的预期销量区间往往很小,市场根本消化不了那么多产能。这时候你会意识到,C题的约束重点不是“地块不够”,而是“市场吃不下”。

从建模角度,我的处理方法是把目标函数拆成“种植收益 - 未售出损耗 - 风险惩罚”。对于销量上限,不再简单写成“产量≤销量”这个硬约束,而是引入一个可调参数,允许在特定情况下超产,但超产部分按低价或废弃处理。这样模型更有弹性,也更能体现题目里“预期”二字的含义。

4.2 区间宽度给敏感性分析提供了天然坐标

销量区间带图画完之后,还可以进一步做“分作物敏感性分析”。我把每种作物按区间宽度排序,区间最宽的几类作物挑出来,单独测试价格上下浮动10%、销量上下浮动20%时,最优方案和最终收益的变化幅度。结果发现,方案对某些高价蔬菜的销量波动非常敏感,而对粮食作物相对不敏感。

这个结论放在论文里很加分,因为它证明了你们不只是“跑出了一个方案”,还想清楚了方案在什么情况下会失效、失效的程度有多大。这些分析全都可以从区间带图上延伸出来,不需要额外找数据。

4.3 热力图还能帮你发现“组合拳”式的种植规律

种植方案热力图除了展示结果,还有一个隐含价值:它可以帮你做模式识别。比如你看图发现,某一类地块连续几年都在“粮食作物-豆类作物”之间交替,说明模型倾向于用豆类来养地;另一个地块则长期种蔬菜,说明那块地的自然条件和灌溉条件更适合高附加值作物。这种规律在表格里很难发现,但在热力图上几乎是自动呈现的,你可以把它作为论文里“方案合理性的进一步讨论”素材。

5. 做图过程中踩过的坑与经验总结

5.1 中文字体问题:一套组合拳解决

matplotlib默认字体是不支持中文的,直接用plt.title写中文,输出就是一个个方框。解决方法是设置中文字体,比如SimHei、Microsoft YaHei,同时要关闭unicode负号,否则坐标轴上的负号会显示异常。如果你在服务器或者云端环境跑,可能还需要手动指定字体文件路径。

from matplotlib import font_manager import matplotlib.pyplot as plt # 方式一:直接指定系统已安装的中文字体 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 方式二:如果上面不生效,可以直接加载字体文件 # font_path = '/usr/share/fonts/msyh.ttf' # font_prop = font_manager.FontProperties(fname=font_path) # 然后在每个标题里统一传 fontproperties=font_prop

这个问题越早解决越好,不然画到中途才发现所有图的标题都是乱码,重新生成一遍非常浪费时间。

5.2 区间数据画图时容易把上下界搞反

画销量区间带时,我一开始直接用sales_low和sales_high画了两条线,结果fill_between的上界传成了sales_low、下界传成了sales_high,整张图看起来像一个倒扣的碗,差点把结论带偏。建议在画任何带区间的图时,先打印一下最大值和最小值,确认数据范围合理后再绘图。

5.3 堆叠面积图看比例很爽,看总量很坑

有段时间我想用堆叠面积图展示每年各类作物的种植面积结构变化,一开始觉得挺好看。但后来发现堆叠图的“中间层的绝对高度变化”非常有误导性——某类作物面积没变,但因为它下面那层面积变了,它在图上就会看起来忽高忽低。后面我果断放弃堆叠图,改用100%百分比堆叠图或者分面的小多图,因果更清晰。

5.4 不要为画图而画图,先有结论再选图

这是我每次带队都反复强调的一点。C题信息量大,能画的图太多了,但你最终放上论文的图应该控制在8到12张之间。每张图必须有明确的定位:要么是输入数据的洞察,要么是模型结果的展示,要么是约束满足的验证。画图之前先问自己一句:“这张图我要让评委看到什么结论?”如果回答不上来,就不要画。

最后再分享一个个人经验:可视化从来不是建模竞赛的附属品,它其实是建模思路的探照灯。2024年C题这套数据,我可以说一大半的建模决策都是从图里看出来的——哪几种作物是利润引擎,哪些市场区间宽得吓人,轮作约束切在哪个年份会让方案结构变化最大。与其对着表格冥思苦想,不如先把该画的图都画出来,让数据自己开口说话。这也正是我写这篇复盘最想传达的东西。

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

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

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

立即咨询