简介:这份资源面向参加2024年高教社杯全国大学生数学建模竞赛C题的学生与指导教师,聚焦农作物种植策略的可视化实现。包内共31个文件,以20个png图表、6个Python脚本和5个xlsx数据表为主,压缩包约2MB,涵盖数据预处理、描述性统计、异常值分析、价格与占地面积可视化等模块,图表类型包括热力图、箱线图、散点图、饼图与QQ图等。已有1010人学习下载,说明其在赛题复现与可视化参考方面具有较高认可度。读者可借助现成脚本快速完成附件数据清洗与图形输出,对照图表理解作物亩产量、销售单价、种植成本与地块面积之间的关系,并参考异常值检测与相关性分析思路,为自身建模论文的图表呈现与结果验证提供可复用的代码模板和排错方向。
1. 从赛题到可交互图表:这套种植策略可视化代码到底能省多少事
每年数学建模竞赛的 C 题都是数据密集型题目,2024 年高教社杯 C 题「农作物的种植策略」也不例外。题目给了一张包含地块类型、作物种类、种植季次、产量与成本参数的表格,要求参赛队在耕地资源约束、轮作要求、市场需求波动等条件下,给出 2024 到 2030 年的最优种植方案。很多队伍在建模和求解上花了两三天,最后卡在「怎么把结果讲清楚」这一步——论文里全是表格和数字,评委翻三页就疲了。
这套可视化代码解决的就是这个环节。它把求解输出的种植方案(通常是各年份、各地块、各作物的种植面积矩阵)转成堆叠面积图、热力图、桑基图、雷达图等图表,让「哪块地种了什么、几年间结构怎么变、方案在哪些指标上占优」一眼可见。适合两类人:一类是正在准备竞赛、需要快速出图的队伍;另一类是已经拿到求解结果、但不知道怎么把数据组织成可绘图格式的选手。代码本身不包含求解器,它只负责「结果 → 图表」这一段,所以你得先有自己的种植方案数据。
2. 数据接口与目录结构:先把输入格式对齐再谈画图
2.1 输入数据的三种常见形态
不同队伍的求解器输出格式差异很大,常见的有三种:一是长表格式,每行是一条「年份-地块-作物-面积」记录;二是宽表格式,行是地块、列是年份或作物,单元格是面积;三是嵌套字典,Python 求解脚本里直接以 dict 形式保存。可视化代码默认吃长表,因为长表最容易做分组聚合,也最接近 pandas 的 groupby 习惯。
如果你手上是宽表,需要先 melt 成长表。这一步看起来简单,但坑在于列名里可能混了年份和作物名,比如「2024_小麦」这种复合列名,直接 melt 会得到一列无意义的 variable。常见做法是用正则把年份和作物名拆成两列,再传给绘图函数。
2.2 目录结构与依赖安装
代码包一般包含以下几个部分:data/放原始数据和求解结果,src/放绘图脚本,config/放作物颜色映射和地块标签,output/放生成的图片。依赖主要是 pandas、matplotlib、seaborn、plotly 和 networkx(桑基图用)。安装命令如下:
pip install pandas matplotlib seaborn plotly networkx kaleidokaleido 是 plotly 导出静态图片的后端,很多人在本地能显示图但导出 PNG 时报错,就是缺这个包。如果你只在 Jupyter 里看图,可以不装,但论文排版通常需要高清 PNG 或 PDF,建议装上。
2.3 配置文件里的三个关键参数
config/plot_config.yaml里有三个参数最常改:year_range控制横轴年份范围,默认 2024-2030;crop_order控制堆叠图的图例顺序,按作物类别排还是按面积排;color_map是作物到颜色的映射,如果不改,小麦和玉米可能都是蓝色系,堆叠图会糊成一片。我一般会把主粮作物用暖色、经济作物用冷色、蔬菜用绿色系,这样评委一眼能区分结构。
提示:配置文件里的中文作物名要和数据表里的名称完全一致,包括空格和括号。曾经有队伍数据里写「小麦(冬)」,配置里写「小麦(冬)」,全角半角不一致导致颜色映射失效,图例全是灰色。
3. 四类核心图表的绘制流程与参数调优
3.1 堆叠面积图:看种植结构年际变化
堆叠面积图最适合展示「每年各类作物总种植面积占比」。核心逻辑是按年份和作物分组求和,然后 pivot 成宽表,再用 matplotlib 的 stackplot 或 pandas 的 area plot。
import pandas as pd import matplotlib.pyplot as plt # 读取长表格式的种植方案 df = pd.read_csv('data/planting_result.csv') # 按年份和作物聚合面积 pivot_df = df.groupby(['year', 'crop'])['area'].sum().unstack(fill_value=0) # 按 config 中的作物顺序重排列 crop_order = ['小麦', '玉米', '水稻', '大豆', '蔬菜'] pivot_df = pivot_df[crop_order] fig, ax = plt.subplots(figsize=(10, 6)) ax.stackplot(pivot_df.index, pivot_df.T.values, labels=pivot_df.columns, colors=['#D4A017', '#8B4513', '#2E8B57', '#4682B4', '#90EE90'], alpha=0.85) ax.set_xlabel('年份') ax.set_ylabel('种植面积(亩)') ax.legend(loc='upper left', ncol=3) plt.tight_layout() plt.savefig('output/stacked_area.png', dpi=300)逻辑说明:groupby后unstack把长表转成「年份 × 作物」的宽表,fill_value=0保证缺失组合填零而不是 NaN。stackplot的values需要转置,因为它的输入是「序列的序列」,每一行是一个作物的面积序列。alpha设 0.85 是为了让网格线透出来,方便读数值。dpi 设 300 是论文投稿的常见要求,低于 200 打印出来会糊。
参数调优:如果年份多、作物多,图例会挤,可以把ncol改成 2 或 3,或者把图例放到图外。colors建议手动指定,不要用默认色环,因为默认色环在堆叠图里相邻颜色对比度不够。
3.2 热力图:地块-作物匹配关系一目了然
热力图适合展示「某一年份各地块种了什么、面积多大」。行是地块,列是作物,单元格颜色深浅代表面积。这种图在答辩时特别好用,评委能快速看出哪些地块被充分利用、哪些地块有空闲。
import seaborn as sns # 筛选特定年份 year_df = df[df['year'] == 2024] heatmap_data = year_df.pivot_table(index='plot', columns='crop', values='area', aggfunc='sum', fill_value=0) plt.figure(figsize=(12, 8)) sns.heatmap(heatmap_data, annot=True, fmt='.0f', cmap='YlGnBu', linewidths=0.5, cbar_kws={'label': '面积(亩)'}) plt.title('2024 年地块-作物种植面积热力图') plt.tight_layout() plt.savefig('output/heatmap_2024.png', dpi=300)逻辑说明:pivot_table比pivot更稳,因为如果同一地块同一作物有多条记录(比如不同季次),aggfunc='sum'会自动合并。annot=True把数值标在格子里,fmt='.0f'控制不显示小数。cmap='YlGnBu'是浅黄到深蓝的渐变,面积越大颜色越深,符合直觉。
注意:如果地块数量超过 20 个,行标签会重叠,这时候要么把图拉长,要么只画重点地块。我一般会按总面积排序,取前 15 个地块画图,剩下的在附录里用表格呈现。
3.3 桑基图:追踪作物从地块到市场的流向
桑基图在种植策略题里是个加分项,因为它能同时展示「地块 → 作物 → 销售渠道」的多级流向。plotly 的go.Sankey需要节点和链接两个数据结构,节点是所有出现过的地块名、作物名、渠道名,链接是它们之间的面积流量。
import plotly.graph_objects as go # 构建节点列表 plots = df['plot'].unique().tolist() crops = df['crop'].unique().tolist() channels = ['自用', '市场销售', '库存'] labels = plots + crops + channels # 构建链接:地块到作物 source, target, value = [], [], [] for _, row in df.iterrows(): source.append(labels.index(row['plot'])) target.append(labels.index(row['crop'])) value.append(row['area']) fig = go.Figure(go.Sankey( node=dict(label=labels, pad=15, thickness=20), link=dict(source=source, target=target, value=value) )) fig.write_image('output/sankey.png', width=1200, height=800, scale=2)逻辑说明:labels.index()把名称转成索引,这是 plotly Sankey 的硬性要求。scale=2让导出图片分辨率翻倍,适合论文。如果流向太密,可以按作物类别聚合,比如把「小麦」「玉米」合并成「粮食作物」,减少节点数。
3.4 雷达图:多方案综合对比
如果你有多个求解方案(比如线性规划、遗传算法、启发式各出一个结果),雷达图能把「总收益、土地利用率、轮作合规度、市场满足率、成本」五个维度放在一张图上对比。
import numpy as np categories = ['总收益', '土地利用率', '轮作合规度', '市场满足率', '成本控制'] schemes = { '方案A': [0.85, 0.92, 0.78, 0.88, 0.70], '方案B': [0.90, 0.85, 0.82, 0.80, 0.75], '方案C': [0.78, 0.95, 0.90, 0.85, 0.68] } angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False).tolist() angles += angles[:1] # 闭合 fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True)) for name, values in schemes.items(): vals = values + values[:1] ax.plot(angles, vals, label=name, linewidth=2) ax.fill(angles, vals, alpha=0.15) ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1)) plt.tight_layout() plt.savefig('output/radar.png', dpi=300)逻辑说明:雷达图的关键是闭合,所以angles和vals都要在末尾追加第一个元素。fill的alpha设 0.15 是为了让多个方案叠加时还能看清各自的轮廓。bbox_to_anchor把图例挪到图外,避免遮挡数据。
4. 避坑与排查:那些让图表翻车的细节
4.1 中文显示成方块
现象:图表里所有中文变成方框,英文和数字正常。原因:matplotlib 默认字体不含中文字形。解决:在绘图前设置plt.rcParams['font.sans-serif'] = ['SimHei']和plt.rcParams['axes.unicode_minus'] = False。如果系统没有 SimHei,换成「Microsoft YaHei」或「Noto Sans CJK」。Linux 服务器上通常没有这些字体,需要手动安装或把字体文件放到代码目录并用font_manager加载。
4.2 堆叠图面积总和对不上
现象:堆叠图某年的总面积和表格里算出来的不一致。原因:数据里同一地块同一作物同一季次有多条记录,groupby时没去重或没合并。解决:先检查原始数据的唯一键,确认「年份+地块+作物+季次」是否唯一。如果不唯一,要么在求解阶段就合并,要么在绘图前用groupby加sum聚合。
4.3 plotly 导出图片报错
现象:fig.write_image报「Kaleido 未安装」或「版本不兼容」。原因:plotly 导出静态图依赖 kaleido,且版本要匹配。解决:pip install kaleido==0.2.1,如果还不行,降级 plotly 到 5.x 版本。另一个常见原因是缺少 Chrome 内核,kaleido 需要它来渲染,服务器上要装chromium或google-chrome。
4.4 热力图颜色区分度不够
现象:面积差异大的地块和面积小的地块颜色看起来差不多。原因:默认的线性颜色映射对极端值不敏感。解决:把cmap换成对数刻度,或者用LogNorm归一化。另一个办法是手动设置vmin和vmax,把最大值截断到 95 分位数,避免一个超大值把其他颜色压扁。
4.5 桑基图节点顺序混乱
现象:桑基图的节点排列没有逻辑,地块和作物交叉出现。原因:labels列表的顺序决定了节点位置。解决:按「地块 → 作物 → 渠道」的顺序拼接labels,并且把同类节点按面积或名称排序。如果还是乱,可以用node的x和y参数手动指定位置,但工作量较大,一般排序就够了。
5. 进阶技巧:把静态图变成可交互的答辩工具
5.1 用 plotly 做年份滑块
论文里的图是静态的,但答辩时如果能拖动滑块看不同年份的种植结构变化,效果会好很多。plotly 的frames机制可以实现这个效果,核心思路是把每年的数据做成一个 frame,然后用updatemenus加滑块。
import plotly.express as px fig = px.bar(df, x='crop', y='area', color='crop', animation_frame='year', range_y=[0, df['area'].max() * 1.1]) fig.update_layout(title='各年份种植面积动态变化') fig.write_html('output/animated_bar.html')逻辑说明:animation_frame='year'自动生成滑块,range_y固定纵轴范围,避免每年刻度跳动。导出 HTML 后可以直接用浏览器打开,答辩时全屏展示。如果数据量大,可以只保留面积前 10 的作物,减少渲染压力。
5.2 图表组合与论文排版
论文里通常需要把多张图拼成一张大图,用plt.subplots或matplotlib.gridspec都行。我一般会把堆叠面积图放上面,热力图和雷达图放下面,桑基图单独一页。导出时统一用 300 dpi 的 PNG,如果期刊要求矢量图就导 PDF。注意 PDF 里中文字体要嵌入,否则编辑那边打开可能乱码。
5.3 一个我踩过的坑
有一次帮队伍调图,堆叠面积图怎么画都是空白,查了两个小时才发现数据里的year列是字符串类型,groupby后索引排序变成「2024, 2025, 2026...」没问题,但stackplot的 x 轴不接受字符串,需要转成整数。从那以后我每次拿到数据第一件事就是df.info()看列类型,年份、面积、数量这些列必须是数值型,分类列必须是 category 或 object,混了就先转再画。
希望这套可视化代码能帮你把建模结果讲得更清楚,少在出图这一步耗时间。
本文还有配套的精品资源,点击获取