
数学建模国赛只有三天时间但真正让人崩溃的往往不是模型本身而是思路还没理顺、数据一团乱麻、图表画出来自己都不想看。很多队伍不是不会建模而是被“问题分析不聚焦、数据清洗太耗时、图表表达不专业”这三件事拖垮了。本文分享一套我在备赛和实战中反复打磨的“模块求解 skill”三件套——问题分析、数据处理、图表绘制把每个环节变成可复用的标准化流程配合完整代码示例和避坑清单适合零基础参赛队伍也适合想在数据处理和可视化上提速的老手。1. 为什么需要一套“模块求解 skill”1.1 数学建模竞赛的本质是一场限时科研数学建模国赛CUMCM的题目通常给出一段真实背景、若干数据表和明确问题要求参赛队在 72 小时内完成从读题、建模、求解到论文写作的完整闭环。这个过程本质上和一次小型科研项目非常像先理解需求再处理数据然后构建模型最后用图表和文字表达结论。但很多队伍在实战中容易陷入三个误区拿到题目立刻开始套模型忽略了题目中的约束条件和隐含假设导致模型与数据脱节数据文件一打开就手动复制粘贴耗时且容易出错更不要说处理缺失值和异常值图表直接用 Excel 默认样式或者用 Matplotlib 画出来就塞进论文字号、标注、配色都不符合论文规范。这三个误区叠加在一起就导致一个常见结果模型看起来很高端但数据支撑不足图表表达混乱最终论文整体说服力不够。1.2 “skill”在数学建模中的含义“skill”这个词如果放在 AI Agent、Codex 的语境下通常指“把一类任务的处理流程脚本化、模板化”。放到数学建模里我们可以把它理解成一套可复用的解题流程对某一类问题把分析步骤、数据处理代码、图表模板沉淀下来下次遇到相似题目可以直接调用而不是每次从零开始。本文要分享的三模块 skill 大致分工如下模块定位解决的核心问题问题分析 skill前端思路层把一道赛题拆解成可执行的研究问题、约束条件和假设清单数据处理 skill中端数据层统一读取、清洗、转换、特征构造让数据可信可用图表绘制 skill后端表达层绘制符合国赛规范、直接可插入论文的图表这三个模块是递进关系问题分析决定数据处理的方向数据处理决定图表能画什么图表决定论文最终呈现效果。2. 环境准备与 Python 工具链2.1 开发环境说明本文所有代码基于 Python 3.9 编写在 Windows / macOS / Linux 下均可运行。推荐使用 Anaconda 创建独立虚拟环境避免包版本互相干扰。如果你已经有项目环境也可以直接用 pip 安装依赖。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。建议创建虚拟环境conda create -n mathmodel python3.9 -y conda activate mathmodel2.2 需要安装的 Python 库在实战中我常用的库如下统一写入requirements.txtpandas1.5.0 numpy1.23.0 matplotlib3.6.0 seaborn0.12.0 scikit-learn1.1.0 statsmodels0.13.0 scipy1.9.0 openpyxl3.0.0安装命令pip install -r requirements.txt简单说明一下每个库的用途pandas 负责数据读取、清洗、分组统计和透视numpy 提供高效的数值计算和数组操作matplotlib 是基础绘图库负责几乎所有图表的绘制seaborn 在 matplotlib 基础上封装了统计图表绘制热力图、分布图很方便scikit-learn 用于数据标准化、编码和基础机器学习模型statsmodels 常用于回归分析和时间序列分解scipy 提供优化、积分、插值等科学计算能力openpyxl 让 pandas 可以读写 .xlsx 文件。2.3 项目目录结构建议每个参赛队建一个统一的目录模板这样找文件、备份和写作都很方便competition/ │ ├── data/ │ ├── raw/ # 原始赛题数据只读不改 │ └── processed/ # 清洗后的数据 │ ├── code/ │ ├── analysis/ # 问题分析笔记 │ ├── data_process/ # 数据处理脚本 │ └── plot/ # 绘图脚本 │ ├── paper/ │ ├── figures/ # 论文用图 │ └── draft/ # 论文草稿 │ └── README.md这个结构看起来简单但能让你在比赛最后半天不用花时间找“那张画好的图到底存到哪里去了”。3. Skill 一问题分析模块3.1 拿到题目后先做什么很多队伍拿到赛题后第一件事是查资料、下文献结果资料查了两三个小时题目还没吃透。我建议把问题分析做成一个“固定流程”前 30 分钟到 1 小时只做一件事把题目逆向拆解。所谓逆向拆解就是从“题目要我们回答什么”出发倒推出需要哪些数据、哪些模型、哪些假设。拆解时对照下面这张表拆解维度要回答的问题示例研究对象题目关注的核心对象是什么城市共享单车的潮汐调度显式目标题目明确要求我们求解什么优化调度方案使车辆利用率最高隐式约束题目背景中隐藏的限制条件车辆数量有限、调度车容量限制、时间窗口数据需求需要哪些字段才能支撑求解订单数据、站点位置、天气、时间可量化指标怎么评价结果好坏调度成本、用户等待时间、车辆空驶率实际操作时可以三个人同时读题各自写一份拆解笔记再碰头合并。这个做法的好处是能避免“一个人带偏全队思路”的情况。3.2 建立问题的 WBS 分解清单拆解完成后把大问题分解成可执行的小任务形成工作分解结构WBS。下面是用注释形式展示的一份通用 checklist可以直接复制到项目 README 中# code/analysis/problem_checklist.py # 目标把赛题变成可执行任务清单 # 使用方式填空后按顺序执行 TASK_LIST { 业务理解: [ 用 3 句话向队友复述题目背景, 列出 5 个关键词研究对象、时间段、地域范围、目标、限制, 画出业务逻辑草图文字版, ], 数据探查: [ 每个数据表有哪些字段含义是什么, 数据粒度是什么按天/按小时/按订单/按站点, 是否有缺失值、异常值、重复记录, 数据时间范围是否覆盖题目要求的周期, ], 模型选择: [ 题目是否需要预测如果是优先考虑时间序列或回归, 题目是否需要评价如果是考虑层次分析法、熵权法、TOPSIS, 题目是否需要优化如果是考虑线性规划、遗传算法、模拟退火, 如果问题耦合先拆成子问题再考虑是否联立, ], 结果输出: [ 每个问题最终要输出哪些图表, 每个图表要支撑哪个结论, 是否需要灵敏度分析或误差分析, ], }3.3 问题分析阶段的常见误区问题分析最常见的误区是“过早进入建模”。比如题目要求分析“交通拥堵的影响因素”有队伍上来就写了一个多元线性回归但连数据里有哪些指标都没看最后发现关键字段缺失只能返工。正确做法是先明确“题目限定的研究边界”再确定“数据能不能支撑这个边界”如果数据支撑不了就需要合理假设或转换研究角度。数学建模允许做假设但这意味着模型结果的可信度会受到影响所以假设不要乱写每一项假设都要能自圆其说。4. Skill 二数据处理模块4.1 数据处理的标准流程数据处理在竞赛中占比极大甚至可以说“数据清洗的质量决定了模型上限”。我推荐把数据处理固定成下面这个流水线读取原始数据 → 统一列名与格式 → 缺失值处理 → 异常值处理 → 去重 → 特征构造 → 合并与透视 → 输出清洗后数据每一步都做检查防止错误在后续模型阶段才暴露。下面是一个通用数据清洗函数可以直接复制后按赛题修改字段名# code/data_process/clean_data.py import pandas as pd import numpy as np def load_data(file_path, sheet_nameNone): 读取 csv 或 excel 文件 if file_path.endswith(.csv): return pd.read_csv(file_path, encodingutf-8) elif file_path.endswith((.xlsx, .xls)): return pd.read_excel(file_path, sheet_namesheet_name) else: raise ValueError(不支持的文件格式请使用 csv 或 excel) def clean_data(df, datetime_colNone, target_colNone): 通用数据清洗流程 # 1. 删除完全重复的行 df df.drop_duplicates().copy() # 2. 统一列名去除首尾空格、转小写 df.columns [str(col).strip().lower().replace( , _) for col in df.columns] # 3. 缺失值处理数值列用中位数填充类别列用众数填充 for col in df.columns: if df[col].dtype in [int64, float64]: df[col] df[col].fillna(df[col].median()) else: df[col] df[col].fillna(df[col].mode().iloc[0] if not df[col].mode().empty else unknown) # 4. 异常值处理使用 IQR 方法超过上下边缘的值替换为边界值 numeric_cols df.select_dtypes(include[np.number]).columns for col in numeric_cols: Q1 df[col].quantile(0.25) Q3 df[col].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR df.loc[df[col] lower, col] lower df.loc[df[col] upper, col] upper # 5. 时间列处理提取年、月、日、小时等特征 if datetime_col and datetime_col in df.columns: df[datetime_col] pd.to_datetime(df[datetime_col]) df[year] df[datetime_col].dt.year df[month] df[datetime_col].dt.month df[day] df[datetime_col].dt.day df[hour] df[datetime_col].dt.hour df[weekday] df[datetime_col].dt.weekday return df4.2 数据探查Data Profiling清洗之前要先对数据做一次“体检”。建议在脚本中统一输出以下信息# 数据体检脚本通常比赛开始 30 分钟内运行 def profile_data(df): print(数据形状, df.shape) print(\n字段列表) print(df.dtypes) print(\n缺失值统计) print(df.isnull().sum()) print(\n数值字段描述) print(df.describe()) print(\n类别字段唯一值数量) print(df.select_dtypes(include[object]).nunique())运行后会得到一份“数据体检报告”通过这份报告你可以快速判断数据量是否满足建模需求是否存在字段缺失严重的列需要考虑删除或补全是否有明显离群值类别字段是否太多或太杂。在国赛实战中我见过不少队伍忽略了这一步把包含大量缺失值的列直接放进模型结果模型跑出来效果极差还找不到原因。所以数据体检不是可选步骤而是必选步骤。4.3 特征构造与数据转换原始数据往往不能直接建模需要构造新的特征。常见做法包括时间特征从时间戳中提取月份、星期、小时、是否节假日统计特征按某个分组计算均值、方差、最大值、最小值再拼回原表比例特征如“订单量 / 可用车辆数”“故障数 / 总运行时长”编码特征对类别型变量使用 one-hot 编码或标签编码。下面是一个特征工程的示例片段# code/data_process/feature_engineering.py import pandas as pd from sklearn.preprocessing import StandardScaler, OneHotEncoder def build_features(df): 假设 df 已经完成缺失值清洗 # 1. 按站点构造统计特征 station_stats df.groupby(station_id)[rent_count].agg( station_meanmean, station_stdstd, station_maxmax ).reset_index() df df.merge(station_stats, onstation_id, howleft) # 2. 构造比例特征 df[rent_per_bike] df[rent_count] / (df[bike_count] 1e-6) # 3. 类别编码对站点类型做 one-hot ohe OneHotEncoder(sparse_outputFalse) encoded ohe.fit_transform(df[[station_type]]) encoded_df pd.DataFrame(encoded, columnsohe.get_feature_names_out([station_type])) df pd.concat([df, encoded_df], axis1) # 4. 数值列标准化留到建模前再做避免信息泄露 return df这里有一个容易被忽略的注意点标准化应该在划分训练集和测试集之后再做用训练集的均值和标准差去转换测试集。竞赛中很多队伍先标准化再划分虽然大多数时候影响不大但这属于“数据泄漏”的隐患在严谨的建模流程中应该避免。4.4 大数据量下的处理技巧国赛有时会给出几百万行的订单数据直接用 pandas 处理可能很慢甚至内存溢出。这时候有几个实用办法读取时指定数据类型比如用pd.read_csv(path, dtype{station_id: int32})用chunksize分块读取先做必要的筛选再合并提前删除不必要的列减少内存占用如果数据实在太大先按题目要求聚合到“站点-小时”或“站点-天”粒度再进入建模流程。切记不要一开始就把原始全量数据 load 进内存再做所有操作容易导致电脑卡死。5. Skill 三图表绘制模块5.1 国赛论文图表的基本要求论文图表不只是“展示数据”更是“辅助论证”。评阅老师看一篇论文通常时间有限图表的直观程度直接影响对模型的判断。我个人总结了三条硬性要求图片清晰dpi 至少 300插入 Word 后不模糊每一张图都有标题、坐标轴标签、单位和图例图表的结论与论文文字一致图要能单独看懂。5.2 统一绘图配置Matplotlib 默认样式偏学术风但直接使用会出现中文字体方框、坐标轴重叠、配色不够美观等问题。建议在绘图脚本开头设置统一配置# config/plot_config.py import matplotlib.pyplot as plt import matplotlib as mpl # 中文字体设置Windows 使用 SimHeimacOS 可使用 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False # 解决负号显示问题 # 全局绘图风格 plt.style.use(seaborn-v0_8-whitegrid) # 图片尺寸和清晰度 mpl.rcParams[figure.dpi] 100 mpl.rcParams[savefig.dpi] 300 mpl.rcParams[font.size] 12 mpl.rcParams[axes.labelsize] 12 mpl.rcParams[axes.titlesize] 14 mpl.rcParams[legend.fontsize] 10 # 统一保存参数 def save_fig(fig, path): fig.savefig(path, bbox_inchestight, facecolorwhite) print(f图片已保存{path})这段配置基本可以做到“一次配置全局复用”。把这里的save_fig函数封装好所有图都用它保存保证输出风格一致。5.3 常用图表类型与适用场景图表类型适用场景关键代码折线图时间序列变化趋势plt.plot(x, y)散点图两个变量之间的相关性plt.scatter(x, y)柱状图类别之间的数值对比plt.bar(cat, val)堆叠柱状图总量构成随时间变化plt.bar(x, a, labelA)箱线图数据分布与异常值plt.boxplot(data)热力图变量之间相关性矩阵seaborn.heatmap(corr)雷达图多指标评价对比plt.polar(theta, values)3D 散点图三维变量关系ax.scatter3D(x, y, z)5.4 论文级图表示例下面是一个比较完整的示例包含相关性热力图和预测效果对比图。相关性热力图# code/plot/correlation_heatmap.py import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from config.plot_config import save_fig df pd.read_csv(data/processed/clean_data.csv) numeric_df df.select_dtypes(include[number]) fig, ax plt.subplots(figsize(10, 8)) corr numeric_df.corr() sns.heatmap(corr, annotTrue, fmt.2f, cmapRdBu_r, squareTrue, linewidths0.5, axax) ax.set_title(变量相关性热力图) save_fig(fig, paper/figures/corr_heatmap.png) plt.close(fig)这里要注意热力图的annotTrue会在格子里显示相关系数当变量很多时格子会非常拥挤建议只选择与目标变量相关性较强的 8-10 个变量来画。预测效果对比图# code/plot/prediction_compare.py import matplotlib.pyplot as plt import numpy as np import pandas as pd from config.plot_config import save_fig # 假设 df 中包含真实值和预测值 df pd.read_csv(data/processed/prediction_result.csv) fig, ax plt.subplots(figsize(12, 5)) ax.plot(df[time], df[actual], label真实值, linewidth2, colorblack) ax.plot(df[time], df[predict], label预测值, linewidth2, colorred, linestyle--) ax.fill_between(df[time], df[lower], df[upper], alpha0.2, colorgray, label95% 置信区间) ax.set_xlabel(时间) ax.set_ylabel(订单量) ax.set_title(模型预测效果对比) ax.legend() ax.grid(True, linestyle--, alpha0.6) plt.xticks(rotation45) save_fig(fig, paper/figures/prediction_compare.png) plt.close(fig)5.5 图表绘制的进阶细节配色方面尽量使用同一色系或低饱和色不要每张图都换个花哨配色坐标轴范围要合理不要因为个别异常值把整体趋势压扁如果有多子图注意子图之间标题、图例、轴标签的对齐保存格式推荐 PNG 或 PDFPDF 插入 Word 更清晰但文件较大建议最终提交 PDF 版本论文时用 PDF 图片汉字字体设置是个高频坑如果绘图时出现方框优先检查plt.rcParams[font.sans-serif]是否设置成功。6. 完整实战案例城市共享单车潮汐调度问题下面用一个模拟赛题串联三个 skill展示从问题分析到图表输出的完整链路。赛题背景示例数据非真实赛题某市共享单车系统记录了站点的借还车订单包含站点编号、时间戳、借出车辆数、归还车辆数、可用车辆数等字段。请分析站点租还需求的时空规律构建调度优化模型并给出早晚高峰的调度建议。6.1 问题分析阶段首先按问题分析 skill 拆解研究对象共享单车站点的租还需求显式目标分析时空规律构建调度优化模型给出建议隐式约束调度车辆数量有限、站点容量有限、调度耗时等数据需求订单数据、站点容量、天气、节假日信息可量化指标站点车辆溢出次数、车辆空缺时长、调度成本。这个阶段输出的是一份一页纸的分析笔记写清楚“我们要解决什么问题、用什么数据、用什么方法、输出什么结果”。6.2 数据处理阶段用前面写的clean_data函数处理订单数据并按“站点-小时”粒度聚合# 主流程示意main_process.py import pandas as pd from data_process.clean_data import load_data, clean_data from data_process.feature_engineering import build_features # 1. 读取原始数据 df load_data(data/raw/order_data.xlsx, sheet_nameorder) # 2. 清洗 df clean_data(df, datetime_coltime, target_colrent_count) # 3. 按站点-小时聚合 df[time_hour] df[time].dt.floor(h) hourly df.groupby([station_id, time_hour]).agg( rent_sum(rent_count, sum), return_sum(return_count, sum), avg_available(available_bikes, mean) ).reset_index() # 4. 构造特征 hourly build_features(hourly) # 5. 保存 hourly.to_csv(data/processed/hourly_station.csv, indexFalse, encodingutf-8-sig)6.3 图表绘制阶段分析早晚高峰需求时先看全站点的需求量时序曲线# 全站点小时汇总 daily_hour hourly.groupby(hourly[time_hour].dt.hour)[rent_sum].sum().reset_index() daily_hour.columns [hour, total_rent] fig, ax plt.subplots(figsize(12, 5)) ax.plot(daily_hour[hour], daily_hour[total_rent], markero, colorsteelblue) ax.set_xlabel(小时) ax.set_ylabel(总借车量) ax.set_title(全站点各小时借车需求分布) ax.set_xticks(range(0, 24)) ax.grid(True, linestyle--, alpha0.6) save_fig(fig, paper/figures/hourly_demand.png) plt.close(fig)再按站点类型分面绘制箱线图观察不同区域站点的需求差异fig, ax plt.subplots(figsize(12, 6)) sns.boxplot(datahourly, xstation_type, yrent_sum, axax) ax.set_title(不同站点类型的借车需求分布) ax.set_xlabel(站点类型) ax.set_ylabel(每小时借车量) save_fig(fig, paper/figures/station_type_box.png) plt.close(fig)6.4 运行与结果说明运行上述脚本后paper/figures/目录下会生成需求分布图、相关性热力图、预测对比图等。把这几个图和问题分析中的结论对应起来论文写作阶段可以直接引用不需要再临时补图。整个链路跑通后你会发现从拿到数据到输出第一批图表可能只需要一个下午。剩下的时间可以集中火力优化模型和打磨论文而不是在数据清洗和画图上反复挣扎。7. 常见问题与排查思路问题现象常见原因解决思路绘图时中文显示为方框未设置中文字体设置plt.rcParams[font.sans-serif]并axes.unicode_minusFalsepandas 读取 excel 报错缺少 openpyxl 或 xlrd安装openpyxl或另存为 csv 再读取数据量太大内存崩溃DataFrame 载入全量数据只读必要列、指定dtype、分块读取或先聚合再建模drop 列时出现警告链式赋值问题复制 DataFrame 再操作避免连续使用df[col][mask]缺失值过多模型效果差补全策略不合理或删除太多行先看缺失占比超过 50% 的列考虑删除其余用中位数或模型插补标准化后数值范围很奇怪对类别变量也做了标准化只用数值列做标准化类别变量使用编码保存图片后论文中模糊dpi 设置太低或图片被拉伸设置savefig.dpi300插入 Word 时保持原图比例预测图和真实值对不上时间对齐问题排序后检查索引确保真实值和预测值按同一时间轴对齐如果遇到上面没列出的问题排查顺序建议是先看数据打印 shape 和 head再看类型dtypes然后看是否有 NaN最后看绘图代码的坐标轴和颜色参数。大多数问题都能靠这个顺序定位到。8. 团队分工与工程化建议8.1 三人三角色的建议分工数学建模竞赛通常是三人一组我见过比较稳定的分工模式是建模手负责问题分析、模型选择、公式推导、灵敏度分析代码手负责数据清洗、模型实现、图表绘制、代码管理写作手负责论文结构、图表排版、文字润色、摘要打磨。但分工并不绝对代码手要懂建模写作手也要能看懂图表含义。最理想的状态是三人对问题分析 skill 达成共识由代码手统一实现数据处理和绘图流程写作手在拿到图表后立即开始撰写对应章节。8.2 代码与文件命名规范数据文件按01_原始数据、02_清洗数据、03_特征数据的顺序编号图片按01_问题分析、02_数据探索、03_模型结果分类存放每个代码文件开头写清楚用途、输入、输出代码中不要出现绝对路径使用相对路径方便三台电脑同步运行每完成一个阶段手动备份一次关键脚本和数据。8.3 安全与合规边界数学建模竞赛使用的数据是赛题官方提供的公开数据原则上可以自由使用。但在实际项目或科研中处理数据时需要注意几个边界涉及用户隐私、商业敏感信息的数据必须做脱敏处理不要在论文中编造数据或结果模型输出与论文图表必须一致如果使用第三方代码或开源项目注意保留声明避免学术不端风险对数据库执行删除、更新操作前必须备份并在测试环境验证。8.4 避免“过度加工”数据数据处理的目标是让数据更干净而不是刻意制造“好看的结果”。我见过有队伍为了追求相关性很高反复筛选样本、删掉“不好看”的数据点这种做法的本质是数据造假在国赛评阅中是高风险行为。正确做法是记录每一步处理逻辑让数据处理过程可复现、可解释这样即使结果不理想也能通过分析过程找到问题。9. 总结与学习路线围绕这篇文章你至少可以掌握三条可迁移的技能第一问题分析要“逆向拆解”把一道赛题拆成研究对象、显式目标、隐式约束、数据需求、可量化指标五要素再用 WBS 清单把任务落地第二数据处理要走标准流水线从读取、体检、清洗到特征构造每一步都输出检查信息避免脏数据进入模型第三图表绘制要建立统一配置从字体、配色、分辨率到保存格式都规范化让图表直接达到论文可用的标准。下一步建议你找一道往年国赛真题用本文的流程走一遍“读题 → 数据体检 → 画三张图 → 写第一个简单模型”的完整练习。不用追求一次做到完美重点是熟悉这套 skill 的操作节奏同时把踩过的坑记在团队文档里。真正到了赛场上时间紧张是常态能让你稳住阵脚的就是赛场前反复演练过的那套标准化流程。