2026年数学建模国赛备赛期已经可以开始了。不管是第一次参赛还是拿过奖的老队伍,竞赛里最耗时的永远不是模型推导本身,而是问题分析、数据处理、图表绘制这三个环节来回返工。很多队伍两天下来,真正用于建模和论文的时间不到三分之一,剩下的时间都耗在数据清洗、变量定义不清、图表改了一遍又一遍上。这次我们把这套流程整理成模块化求解 skill,让每个环节都有标准流程、可复用脚本和验收清单。
这里说的 skill,不是简单的一句提示词,而是一套由说明文件、Python 脚本、检查清单组成的技能包。拿到赛题后,先跑问题分析 skill 完成题目拆解,再通过数据处理 skill 把原始数据清洗成标准化表格,最后由图表绘制 skill 直接产出论文级图表。整个过程人可以介入,AI 也可以按同一套流程执行,既适合备赛阶段训练,也适合比赛现场快速出结果。下面直接给核心能力和落地步骤。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 解决目标 | 将数模国赛中的问题分析、数据处理、图表绘制三个环节标准化、模块化 |
| 模块划分 | 问题分析 skill、数据处理 skill、图表绘制 skill |
| 输出产物 | 赛题拆解文档、标准化数据表格、论文级图表、批量处理结果 |
| 运行环境 | Python 3.8 及以上,依赖 pandas、numpy、matplotlib、seaborn、openpyxl |
| 是否需要 GPU | 不需要,这套 skill 以数据处理为主,CPU 即可运行 |
| 启动方式 | 命令行逐模块执行,或封装为 HTTP API 服务 |
| 是否支持批量任务 | 支持,通过批量脚本遍历输入目录自动处理 |
| 是否支持接口 API | 支持,可封装为 FastAPI 服务供团队共享 |
| 适用对象 | 数模国赛参赛队伍、建模实验课程、需要快速出图出表的科研团队 |
| 扩展方向 | 接入 AI 助手后,可让 AI 按同一套 SOP 自动执行各模块 |
从功能上看,这套方案不依赖昂贵的硬件,也不依赖某个特定的在线平台,所有脚本都可以在本地测试通过后直接用于比赛场景。核心价值是把“凭感觉做”变成“按流程做”,把重复劳动压缩到最小。
2. 适用场景与使用边界
这套 skill 最适合下面几类人:
- 第一次参加数模国赛的队伍。队伍里如果有人熟悉 Python,按照本文的脚本和 SOP 把三个模块跑通,比赛时就不用手忙脚乱。
- 已经参赛过但总在数据环节返工的队伍。数据清洗和图表绘制统一成脚本后,换一套数据也能快速产出结果。
- 需要批量复现实验结果的场景。比如需要同时处理多份实验数据、多组调查问卷、多张表格,手动操作容易出错,脚本批量处理更稳定。
它能解决的问题也很明确:问题分析阶段不会拆题,拿到题目不知道先做什么;数据处理阶段反复清洗同一份数据,代码写一次丢一次;图表绘制阶段风格不统一,论文插图很难看。
但这套 skill 不能替代真正的建模能力。它负责流程标准化,不负责替你决定用哪个模型、怎么写一篇有竞争力的论文。如果队伍本身对微分方程、线性规划、统计分析这些基础模型不熟悉,先补数学基础,再谈 skill 提效。另外,竞赛对 AI 辅助参赛通常有明确规则,使用任何自动化工具或 AI 生成内容前,务必阅读当年赛区发布的参赛规则,按要求完成必要的声明和合规操作。
数据使用也有边界。清洗和绘图的数据必须来自合法渠道,涉及个人信息的要先脱敏,涉及企业或单位数据要确认授权。图表导出后如果用于论文或公开发布,需要确保数据来源和引用说明完整。
3. 环境准备与项目目录规划
3.1 本地环境检查
这套 skill 只需要一个相对干净的 Python 环境。推荐使用 3.8 及以上版本,安装依赖时用虚拟环境隔离,避免和系统 Python 冲突。如果你的机器上已经装了 Anaconda,可以直接创建一个独立环境:
# 创建并激活虚拟环境,命令在 Windows、Linux、macOS 上通用 python -m venv skill_env source skill_env/bin/activate # Windows 使用 skill_env\Scripts\activate然后安装基础依赖:
pip install pandas numpy matplotlib seaborn openpyxlpandas 负责表格读取与清洗,numpy 负责数值计算,matplotlib 和 seaborn 负责图表绘制,openpyxl 用来读写 Excel 文件。如果后面要封装 HTTP API,再额外安装:
pip install fastapi uvicorn python-multipart3.2 推荐目录结构
比赛环境文件多且乱,强烈建议在拿到赛题的第一时间就建立统一目录。下面是一个可以直接套用的结构:
project/ ├── data/ │ ├── inputs/ # 原始数据放这里 │ ├── outputs/ # 清洗后的数据 │ └── raw.csv # 单份数据也可以直接放在 data 下 ├── outputs/ │ └── figs/ # 图表输出目录 ├── skills/ │ ├── problem_analysis/ # 问题分析 skill │ ├── data_process/ # 数据处理 skill │ └── plot/ # 图表绘制 skill ├── scripts/ │ ├── data_clean.py │ ├── plot_charts.py │ └── batch_run.py └── README.md把原始数据、清洗数据、图表输出分开存放,最大的好处是比赛最后写论文时能快速找到素材,不会出现“图在哪、数据是哪一版”的混乱。每一份数据修改后另存为新文件,不要覆盖原始文件。
4. skill 模块设计与配置文件
skill 模块化设计的核心是一份说明文件加若干可执行脚本。说明文件让 AI 或队友理解这个模块是做什么的、输入输出是什么、验收标准是什么;脚本负责把流程跑起来。下面给出一个通用 skill 配置模板,路径和参数需要按实际项目调整。
4.1 skill 文件结构
skills/ ├── problem_analysis/ │ ├── SKILL.md │ └── analysis_template.md ├── data_process/ │ ├── SKILL.md │ └── data_clean.py └── plot/ ├── SKILL.md └── plot_charts.py4.2 以数据处理 skill 为例的 SKILL.md
# 数模国赛-数据处理Skill ## 功能定位 将竞赛中的原始数据清洗、转换、特征工程标准化,输出可直接用于建模的表格。 ## 输入 - 原始数据文件(CSV / Excel) - 数据说明文档(可选) ## 处理流程 1. 加载数据并检查字段类型 2. 处理缺失值:数值列用均值或中位数填充,类别列用众数填充 3. 去重、去全空列 4. 异常值检测:使用 3σ 原则或 IQR 方法 5. 输出标准化编码数据 ## 调用方式 bash python scripts/data_clean.py --input data/raw.csv --output data/processed.csv ## 验收标准 - 无全空列、无重复行 - 数值列无字符串混入 - 输出文件编码为 UTF-8有了这份 SKILL.md,无论是队友手动执行还是 AI 调用,都能按同一套标准完成数据处理,不会出现两个人洗出两份完全不同的表的尴尬情况。
5. 问题分析 skill:赛题拆解与验收测试
5.1 为什么需要单独做一个问题分析 skill
很多队伍拿到赛题后直接把题目丢给 AI,让 AI 给一个模型就开写。这样做的结果是论文读起来“没有灵魂”,题目其实没有被真正拆透。问题分析 skill 要做的是把拆题变成一个可重复、可验收的动作链:先把题目背景压缩成三句话,再列出关键变量、约束条件和需要假设的点,最后给出至少两种建模路线并对复杂度做预判。
5.2 问题分析流程模板
下面是一个可以直接复制到团队的 analysis_template.md 的模板:
# 赛题拆解模板 ## 1. 问题背景 用 3 句话概括题目场景,确保队伍所有人对题目理解一致。 ## 2. 问题重述 用自己的语言复述要解决什么问题,禁止直接抄题目原文。 ## 3. 变量定义表 | 变量名 | 含义 | 类型 | 单位 | | --- | --- | --- | --- | ## 4. 约束条件 - 硬约束:题目中不可违反的条件 - 软约束:可以通过目标函数权衡的条件 ## 5. 假设清单 每个假设都写明对结果的影响,方便后期在论文中说明局限性。 ## 6. 建模路线对比 | 路线 | 模型 | 输入 | 输出 | 复杂度 | 风险点 | | --- | --- | --- | --- | --- | --- |5.3 验证问题分析是否达标
判断问题分析 skill 是否跑通的标志很简单:队伍里任何一个人不看题目原文,只看拆解文档,也能复述出这道题要做什么、数据从哪里来、输出结果应该是什么样。如果拆解文档里变量表是空的、约束条件只有一条、假设清单混乱,说明还没有完成。
建议在拿到赛题后 2 到 3 小时内完成问题分析,不要在这个阶段纠结具体模型公式。拆题越清楚,后面的数据清洗和建模越顺利。这个阶段产出的变量定义表要保留到论文写作阶段,避免前后不一致。
6. 数据处理 skill:清洗脚本与效果验证
6.1 功能说明
数据处理 skill 是整个流程里最容易标准化、也最容易出问题的一环。竞赛中拿到的数据普遍存在列名不规范、单位混用、缺失值、重复记录、文本数字混在一起等问题。下面提供一个通用清洗脚本,它会把 CSV 或 Excel 读进来,去掉全空行和全空列,去重,然后把数值类型的文本列自动转成数字。
# scripts/data_clean.py # 通用数据清洗脚本:读取原始数据,输出标准化表格 import pandas as pd from pathlib import Path def load_data(path: str) -> pd.DataFrame: path = Path(path) if path.suffix == ".csv": return pd.read_csv(path, encoding="utf-8-sig") if path.suffix in (".xlsx", ".xls"): return pd.read_excel(path) raise ValueError(f"不支持的文件格式: {path.suffix}") def clean(df: pd.DataFrame, drop_duplicates: bool = True) -> pd.DataFrame: # 去除全空列和全空行 df = df.dropna(axis=1, how="all").dropna(axis=0, how="all") if drop_duplicates: df = df.drop_duplicates() # 数值列统一转为 float,非数值列保留为对象 for col in df.columns: if df[col].dtype == object: numeric = pd.to_numeric(df[col], errors="coerce") if numeric.notna().sum() >= 0.8 * df[col].notna().sum(): df[col] = numeric return df def fill_missing(df: pd.DataFrame) -> pd.DataFrame: for col in df.columns: if df[col].dtype in ("float64", "int64", "float32", "int32"): df[col] = df[col].fillna(df[col].median()) else: df[col] = df[col].fillna(df[col].mode()[0] if not df[col].mode().empty else "未知") return df if __name__ == "__main__": df = load_data("data/raw.csv") df_clean = fill_missing(clean(df)) df_clean.to_csv("data/processed.csv", index=False, encoding="utf-8-sig") print(f"清洗完成:共 {len(df_clean)} 行,{len(df_clean.columns)} 列")这个脚本不是银弹。业务含义明显的数据不能盲目填充中位数,比如某列是“是否发生故障”,0 和 1 的缺失值用中位数填充就没有意义。使用时要根据题目说明文档判断每个字段应该怎么处理。
6.2 效果验证方法
假设你有一份 data/raw.csv,里面混了空行、重复行和字符串形式的数字,运行:
python scripts/data_clean.py如果输出:
清洗完成:共 120 行,5 列并且用 pandas 重新读取 processed.csv 后没有报类型错误,说明清洗通过。更严格的验证方式是打印每一列的数据类型和缺失值比例:
import pandas as pd df = pd.read_csv("data/processed.csv", encoding="utf-8-sig") print(df.info()) print(df.isna().sum())正常情况下 processed.csv 应该没有全空列,数值列的类型是 float64 或 int64,缺失值数量为 0。如果输出仍有缺失值,说明 fill_missing 阶段没有覆盖所有列,需要回去检查填充逻辑。
6.3 常见失败场景
清洗脚本最常见的问题是编码。很多比赛数据会用 GBK 编码保存,直接用 utf-8-sig 读取会报 UnicodeDecodeError。这时可以把读取参数改成:
pd.read_csv(path, encoding="gbk")或者使用 pandas 的自动编码识别,在读取前用 chardet 检测文件编码。另一个问题是数字列里混着“暂无”“-”这类占位符,to_numeric 会把这些转成 NaN,后续填充逻辑要能容忍这种情况。
7. 图表绘制 skill:论文级图表与验证
7.1 论文图表的基本要求
数模论文里的图表不只是展示结果,更是论文评审的重要依据。图表绘制 skill 要解决两个问题:一是风格统一,二是输出尺寸满足论文排版需求。一套统一风格包含:同一字体、同一种配色、统一分辨率、坐标轴标签完整。下面给出一个通用绘图配置脚本,包含折线图和相关性热力图两个最常用的图表类型。
# scripts/plot_charts.py # 配置化图表绘制:每个图表由一组配置驱动 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False def plot_line(df: pd.DataFrame, x: str, y: list, output: str = "line.png"): fig, ax = plt.subplots(figsize=(10, 5)) for col in y: ax.plot(df[x], df[col], label=col, marker="o") ax.set_xlabel(x) ax.set_ylabel("数值") ax.set_title(f"{x} 与 {', '.join(y)} 的关系") ax.legend() ax.grid(True, alpha=0.3) fig.tight_layout() fig.savefig(output, dpi=150) plt.close(fig) def plot_corr(df: pd.DataFrame, output: str = "corr.png"): numeric = df.select_dtypes(include="number") fig, ax = plt.subplots(figsize=(8, 6)) if len(numeric.columns) > 1: sns.heatmap(numeric.corr(), annot=True, cmap="coolwarm", ax=ax) fig.tight_layout() fig.savefig(output, dpi=150) plt.close(fig) if __name__ == "__main__": data = pd.read_csv("data/processed.csv", encoding="utf-8-sig") numeric_cols = data.select_dtypes(include="number").columns.tolist() if len(numeric_cols) >= 1: plot_line(data, data.columns[0], numeric_cols[:3], "outputs/figs/line.png") if len(numeric_cols) >= 2: plot_corr(data, "outputs/figs/corr.png") print("图表绘制完成,结果输出到 outputs/figs 目录")7.2 图表绘制测试
运行:
python scripts/plot_charts.py然后检查 outputs/figs 目录是否生成 line.png 和 corr.png。打开图片,重点看三处:中文字体是否乱码、坐标轴标签是否完整、图例是否遮挡数据。中文字体问题最常见,如果系统上没有 SimHei,可以换成“Microsoft YaHei”或者系统自带的其他中文字体,或者直接把字体文件路径写进配置:
import matplotlib.font_manager as fm font = fm.FontProperties(fname="/path/to/font.ttf") plt.rcParams["font.family"] = font.get_name()7.3 图表验收清单
判断图表是否达到论文标准,可以对照这份清单:
- 图片分辨率是否达到 150dpi 以上;
- 图中所有文字是否清晰可读;
- 坐标轴是否有单位和标签;
- 图例是否说明每个系列的含义;
- 颜色方案是否统一(整篇论文不超过 2 到 3 个配色);
- 图片文件名是否与论文中的图编号对应。
比赛时时间紧张,不建议临时手写绘图代码。把常用图表模板提前准备好,比赛时只改数据和标签,是最稳妥的做法。
8. 批量任务、接口 API 与运行效率
8.1 批量处理多个数据文件
竞赛中经常出现多张数据表需要相同处理流程的情况。比如题目给了 5 年的数据、每个年份一个文件,或者多个省份的观测数据,这时手工逐个跑一遍效率太低。批量脚本可以遍历输入目录,自动处理所有文件并输出对应结果。
# scripts/batch_run.py # 批量处理:遍历输入目录,逐文件执行数据清洗和图表绘制 from pathlib import Path import pandas as pd from data_clean import load_data, clean, fill_missing from plot_charts import plot_line, plot_corr def process(path: Path): print(f"开始处理:{path.name}") df = fill_missing(clean(load_data(str(path)))) out_sub = Path("outputs") / path.stem out_sub.mkdir(parents=True, exist_ok=True) df.to_csv(out_sub / "processed.csv", index=False, encoding="utf-8-sig") numeric_cols = df.select_dtypes(include="number").columns.tolist() if len(numeric_cols) >= 2: plot_corr(df, str(out_sub / "corr.png")) if len(numeric_cols) >= 1: plot_line(df, df.columns[0], numeric_cols[:3], str(out_sub / "line.png")) print(f"完成:{path.name}") if __name__ == "__main__": inputs = Path("data/inputs") for f in sorted(inputs.glob("*.csv")) + sorted(inputs.glob("*.xlsx")): process(f)批量任务要注意两个问题。第一,单个文件处理失败不能中断整个流程,建议在 process 函数里加 try/except,失败时打印错误信息并跳过。第二,输出目录要按文件名区分,避免文件覆盖。上面的脚本已经把每个文件的输出放在以文件名命名的子目录里。
8.2 封装为 HTTP API 服务
如果队伍人数多,需要多人共用一套数据处理和绘图能力,可以把脚本封装成 FastAPI 服务。这样团队里不熟悉 Python 的同学也能通过网页或接口直接上传数据、获取结果。
# scripts/api_service.py # 示例:将数据清洗和绘图封装为 HTTP API,需要按实际项目调整 from fastapi import FastAPI, UploadFile import pandas as pd from data_clean import clean, fill_missing from plot_charts import plot_corr app = FastAPI() @app.post("/clean") async def clean_file(file: UploadFile): df = pd.read_csv(file.file, encoding="utf-8-sig") cleaned = fill_missing(clean(df)) return { "rows": len(cleaned), "columns": len(cleaned.columns), "missing": int(cleaned.isna().sum().sum()), "sample": cleaned.head(3).to_dict(orient="records") } @app.post("/plot") async def create_plot(file: UploadFile): df = pd.read_csv(file.file, encoding="utf-8-sig") cleaned = fill_missing(clean(df)) plot_corr(cleaned, "outputs/api_corr.png") return {"status": "ok", "output": "outputs/api_corr.png"}启动服务:
uvicorn scripts.api_service:app --host 127.0.0.1 --port 8000然后可以用 curl 验证接口:
curl -X POST http://127.0.0.1:8000/clean \ -F "file=@data/raw.csv"接口返回 JSON 格式的行数、列数、缺失值数量和样例数据,说明接口已经正常工作。封装 API 时要注意只监听 127.0.0.1,不要暴露到公网,避免别人直接往你服务器上传数据。
8.3 运行效率与资源占用观察
数据处理和图表绘制的资源占用不像深度学习模型那么夸张,但也不是完全不需要关注。当数据量达到几十万行、列数有几十列时,内存占用会明显上升。观察方法有两种:一是在命令行用 time 命令统计脚本总耗时,二是用 psutil 或系统任务管理器观察内存变化。
time python scripts/data_clean.py降低内存占用的常用手段:只读取需要的列,不要全表读入;把不用的列在清洗后立刻删除;用 pd.to_numeric 压缩数据类型;数据量过大时用分块读取:
chunks = pd.read_csv("data/large.csv", chunksize=10000) result = [] for chunk in chunks: result.append(clean(chunk)) df = pd.concat(result, ignore_index=True)比赛期间如果遇到数据文件特别大,先处理成小文件再建模,不要一上来就全量加载。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 读取 CSV 报 UnicodeDecodeError | 文件编码不是 UTF-8 | 用记事本或 vim 查看文件编码 | 将读取参数改为 gbk,或先用文本编辑器另存为 UTF-8 |
| 清洗后列数变少 | dropna(axis=1) 把部分缺失列删掉了 | 打印删除前的列名 | 确认全空列定义是否符合预期,必要时改为手动删除指定列 |
| 数值列变成 object 类型 | 列中混入文本占位符 | 打印该列取值分布 | 先替换占位符为 NaN,再做 to_numeric 转换 |
| 中文字体显示为方块 | 系统缺少对应字体 | 查看 matplotlib 字体警告 | 指定系统中文字体文件路径 |
| 图例遮挡数据 | 图例位置不合理 | 打开图片观察 | 设置 loc="upper left" 或调整 figsize |
| 批量任务一个文件报错中断 | 没有 try/except 保护 | 查看错误堆栈 | 在循环内部捕获异常并跳过 |
| 接口返回 500 错误 | 上传文件格式不支持 | 查看服务日志 | 检查接口是否只支持 CSV,扩展支持 Excel |
| 文件被覆盖 | 输出路径重复 | 检查目录结构 | 按输入文件名建立子目录 |
| 清洗后数据量骤减 | 去重逻辑太激进 | 比较去重前后行数 | 把 drop_duplicates 限定在关键列上 |
| 比赛现场找不到输出文件 | 目录结构混乱 | 检查工作目录 | 使用绝对路径并统一 outputs 目录 |
上面这些问题,前三个在数据处理中最常见,中文字体问题在图表绘制中最常见。建议比赛前一天把脚本在比赛环境完整跑一遍,确认没有路径或编码问题后再封箱。
10. 最佳实践与使用建议
10.1 第一天先完整跑通一条流程
拿到赛题后,先不要急着读论文或套模型。按照问题分析 skill 完成拆题,然后把题目数据跑一遍数据处理和图表绘制的默认脚本,确认输出正常。这样即使后面换模型、换思路,你手里已经有一份干净的标准化数据和一组可用的基础图表。
10.2 版本管理比想象中重要
比赛三天,代码和数据会改很多次。建议用 Git 管理项目,至少做到“当天结束前提交一次”。如果不熟悉 Git,也可以用最简单的办法:每次修改前把文件复制一份,文件名带上日期和版本号。这个习惯能避免比赛最后一天出现“改完数据处理脚本后原图消失”的情况。
10.3 把 skill 文档写进团队协作流程
SKILL.md 不只是给 AI 看的,也是给队友看的。建议队伍里负责写作的同学也读一遍数据处理和图表绘制两个 skill 的说明,这样他在写论文时知道手上的表是哪一步生成、图表是怎么画的,写出来的分析才有依据。问题分析 skill 产出的变量定义表,要一直沿用到最后论文的模型假设和符号说明章节。
10.4 合规使用工具和数据
如果要在比赛中借助 AI 工具,一定要确认竞赛规则是否允许、需要不需要申报。数据处理脚本生成的图表如果引用外部数据,要在论文中注明数据来源。涉及未公开数据的,不要擅自上传到在线平台,优先使用本地脚本处理。
10.5 下一步扩展方向
这套 skill 目前覆盖了数据竞赛中最高频的三个模块。后续可以继续扩展:把常用统计检验做成单独的 skill 模块,把论文排版规范固化到 LaTeX 模板里,把数据标准化与归一化封装成可配置参数,甚至把整套流程接入团队自己的 API 服务,参赛时所有成员统一调用。先把基础流程跑顺,再逐步叠加新模块,是最务实推进方式。建议把这套方案在模拟赛上完整跑一遍,把踩到的坑提前排掉。