华为杯成绩出来的那几天,我的建模群消息基本没断过。晒奖的、查排名的、争论E题评阅尺度的,热闹程度堪比往年春节。今年是第二十二届中国研究生数学建模竞赛,作为一个连续跟了近三届研赛数据的老选手,我拿到官方公示的获奖名单之后,第一时间做了一轮完整的成绩数据分析——从赛题热度到获奖率,从一等奖高校分布到选题策略,尽量把公开信息榨干,看看数据背后到底藏了哪些值得琢磨的规律。
这篇文章就是这次分析的全过程记录。我会从数据怎么拿、怎么清洗,到核心分析维度怎么拆解,再到可视化怎么做,最后聊聊我踩过的坑和统计上容易犯的错。如果你准备参加下一届研赛,或者你是带队老师想评估指导方向,又或者你单纯想看看华为杯的获奖生态长什么样,这篇文章应该能给你一些别人不会写的参考。
1. 为什么要倒腾成绩数据:它能回答哪些实际问题
1.1 一份获奖名单不只是名单,它是竞赛生态的切片
很多人拿到获奖名单,扫一眼自己学校有没有名字就关掉了。但其实这份名单里每一行,都对应着一个真实参赛队伍四天三夜的选择:选哪道题、怎么配置队员、最后拿到什么奖。把这些行汇总起来,能看到的东西远超想象。
以赛题分布来说,获奖名单里每个赛题对应多少支队伍,能在一定程度上反映这道题的“人气”。虽然严格来说它只能代表“获奖队伍中的选题偏好”,不能完全代表所有参赛队伍,但结合你所在的建模群、知乎、公众号里的讨论声量,就能拼出一个比较完整的图景。今年我在几个大群里明显感觉到D题和E题的讨论热度高于其他题目,而获奖名单的统计结果也印证了这个体感。
另一个看得见的东西是奖项分布。一等奖、二等奖、三等奖各占多少,获奖面有多宽,直接决定你“拿奖的难度预期”。很多第一次参加研赛的同学对奖项没有概念,以为“只要认真做完就能拿奖”,这种想法非常危险。
1.2 三类核心读者,各自想从这里拿到什么
我做这次分析之前,给自己列了一个问题清单,后来发现这些问题基本覆盖了不同人群的需求。
对于打算参加下一届的学生来说,最关心的是“有没有更容易拿奖的题”。注意,我问的是“更容易”,而不是“简单”。因为数学建模赛题没有真正简单的,但不同赛题的竞争烈度、评阅尺度确实有差异。这个问题可以用数据给出一个概率参考。
对于指导老师和学院教务来说,关注点在于“哪些方向能出成绩”。如果某个学校在特定类型题目上屡屡拿一等奖,说明这个方向有师资积累、有传承、有训练套路,投入指导资源会更有针对性。
对于像我这样单纯对竞赛生态感兴趣的人,数据能回答的是更宏观的问题:参赛规模是不是一年比一年大?获奖率是变高还是变低?哪些学校是稳定的获奖大户,哪些是异军突起的黑马?
1.3 先说清楚这次统计的边界
在进入正文之前,我必须把口径交代清楚,否则后面所有的数字都可能被误解。本次分析基于官方公示的获奖名单,也就是获得一等奖、二等奖、三等奖以及专项奖的队伍。成功参赛奖不会被完整公布,落榜队伍更不可能出现在名单里。所以,这是一份“获奖者视角”的数据,无法精确计算每道题真正的落榜率。凡是涉及“选题总人数”的判断,我都只能做逻辑推断,而不是直接断言。
这一点非常重要,也是很多人做类似分析最容易犯的错。
2. 数据准备:名单从哪下载、怎么清洗、有哪些硬骨头
2.1 官方数据源和版本选择,别一上来就用PDF
华为杯的获奖名单一般会发布在赛事官方网站上,文件格式通常是xlsx和pdf两种。这里我强烈建议优先下载Excel版本,不要偷懒直接用PDF转表格。
原因很简单:PDF版本的获奖名单为了排版好看,经常出现合并单元格、换行断句、学校名称被拆成两行之类的情况。用tabula或者pdfplumber提取时,很容易出现错行,比如一个队伍的三名队员被拆到两行里,或者学校名和奖项错位。这类脏数据清洗起来极其痛苦。
另外要注意版本。每年公布成绩之后会有公示期,公示期内可能有异议、复核、调整。所以下载文件时,一定要看清文件名里有没有“最终版”“定稿”之类的字样。我遇到过拿着公示稿做统计、结果最后官方补录了几个一等奖名额的情况,数字白算。
2.2 清洗三关:编码、学校名、奖项字段
下载到Excel文件之后,真正的苦活才开始。获奖名单的字段一般包括队号(队伍编号)、学校名称、选题编号、奖项等级、队员姓名、指导教师等。大致看一下结构,但你永远会碰到意外。
第一关是中文乱码。如果你用pandas直接read_excel,一般不会乱码,因为有openpyxl兜底。但如果某些志愿者整理的名单是CSV格式,用read_csv读取时就容易遇到编码问题。Windows下可以尝试encoding="gbk",macOS或Linux下尝试encoding="utf-8"或"gb18030"。我个人的习惯是一律先用Excel打开另存为xlsx,让格式问题提前暴露,而不是在代码里硬猜编码。
第二关是学校名称规范化。这是最花时间的一步。官方名单里,同一个学校的名称可能有多种写法,比如“北京大学医学部”和“北京大学”会被分成两行,“哈尔滨工业大学(深圳)”和“哈尔滨工业大学”也会被分开。如果直接做groupby,北京大学的获奖数量会被拆成两半,排名自然失真。我的处理方法是建一个映射字典,把带有校区、研究院、医学部这类后缀的学校合并到主校名下。
第三关是奖项字段的统一。有些年份的名单里,获奖等级写的是“一等奖”,有些写“一等奖(华为专项奖)”,还有写“国家一等奖”的。这些如果不归一化,后面统计等级时会出现“假类别”。对于本文的分析,我把所有一等奖统一归为“一等奖”,然后单独把专项奖作为另一个标签保留。
2.3 一套直接能跑的清洗流程
下面是一段可以改用的Python代码,数据字段名请按照自己下载的Excel表头调整。我以“队号、学校、选题、奖项、队员1、队员2、队员3、指导老师”这个常见结构为例。
import pandas as pd # 读取数据,注意sheet_name按实际情况填写 df = pd.read_excel("华为杯2025获奖名单.xlsx", sheet_name="最终版") # 重命名字段,方便后续处理 df.columns = ["队号", "学校", "选题", "奖项", "队员1", "队员2", "队员3", "指导老师"] # 第一步:去空格、换行符 for col in ["学校", "选题", "奖项"]: df[col] = df[col].astype(str).str.replace("\n", "").str.replace(" ", "").str.strip() # 第二步:学校名称规范化映射 school_map = { "北京大学医学部": "北京大学", "清华大学深圳国际研究生院": "清华大学", "哈尔滨工业大学(深圳)": "哈尔滨工业大学", "山东大学(威海)": "山东大学", "浙江大学软件学院": "浙江大学", } df["学校"] = df["学校"].replace(school_map) # 第三步:奖项归一化 df["奖项"] = df["奖项"].replace({ "一等奖(华为专项奖)": "一等奖", "国家一等奖": "一等奖", "全国一等奖": "一等奖", # 二等奖、三等奖如果也有类似情况,按相同思路补充 }) # 第四步:去掉重复行 df = df.drop_duplicates(subset=["队号"]) # 看看清洗后的效果 print(df.head()) print("总记录数:", len(df))这段代码的思路很直白:先处理格式问题,再做名称合并,最后统一口径。每一步顺序不能乱,如果先合并且后做去空格,映射字典可能匹配不上,因为源数据里学校名后面可能带着空格。
2.4 用“总数核对法”验证数据质量
清洗完成后,一定不要直接上统计。我用一个笨办法做质量验证,叫“总数核对法”。
第一步,去官网新闻稿或者赛事总结里找参赛队伍总数。比如新闻报道提到“本届共有来自全国500多所高校的2万余支队伍参赛”,那你手里的获奖名单行数应该明显小于这个数,而且获奖比例通常在35%到45%之间。如果算出来获奖比例高达80%,说明名单范围搞错了,可能只下载了获奖比例特别高的某一类名单。
第二步,抽查自己熟悉的高校。随便选两三所你了解情况的高校,比如你本科母校或身边的学校,看名单里的队号、选题、奖项是否和你在学院通知里看到的一致。这一步不费时间但极其有效,能快速暴露合并单元格没被正确处理、字段错位之类的数据问题。
第三步,看学校唯一值的数量是否合理。如果清洗后的学校数量比往年多了几十个,大概率是同一学校的校区变体没有被合并干净。
3. 核心分析结果:赛题热度、获奖比例和高校格局
3.1 赛题热度:哪道题是“卷王之王”
清洗完数据之后,我做的第一个统计就是选题热度,即各赛题在获奖名单中出现的频率。本届共有A到E五个赛题,从统计结果看,E题和D题对应的获奖队伍数量明显偏高,这和赛期大家在群里吐槽“E题题面看了三遍没看懂”形成了有趣的对照——看不懂的人多,选的人也多。
凝练一下选题热度高的三个原因。一是题目背景贴近日常生活,比如交通调度、资源配置、数据分析这类,不需要额外补充大量专业知识就能开始建模型;二是参考资料丰富,往年论文和开源代码多,起步门槛低;三是题目自带“工程应用感”,用到的算法相对成熟,队伍容易在四天内拼出一个完整结果。
不过,热门题目是不是好选择,要拆开看。获奖名单中某题队伍多,只能说明“选择该题且获奖的人”多,不能直接说明“选这道题更容易获奖”。如果一个题目总参赛人数特别多,但获奖率偏低,它可能反而是一条红海赛道。这个问题我会在3.2小结里继续讲。
3.2 奖项分布:一等奖的竞争比想象中更激烈
接下来看奖项等级的整体分布。基于公开数据粗略估算,近几届华为杯的整体获奖率(三等奖及以上)稳定在35%到45%之间,其中一等奖比例通常不超过2%,二等奖约13%到15%,三等奖约20%到25%。下面这个表可以直观感受一下:
| 奖项等级 | 大致比例范围 | 难度感受 |
|---|---|---|
| 一等奖 | 约1.2% - 2% | 万里挑一,核心赛道的顶尖队伍 |
| 二等奖 | 约13% - 15% | 需要完整建模和高完成度论文 |
| 三等奖 | 约20% - 25% | 认真做完、格式规范就有机会 |
| 成功参赛奖 | 剩余大部分 | 绝大多数队伍的真实归宿 |
这个数据意味着什么?如果你目标只是拿三等奖保底,那么只要完整跑完比赛流程、模型合理、论文结构清楚,希望不小。但如果你想要二等奖以上,那就不是“做完”的问题了,而是要在模型创新度、算法实现精度、论文表达质量上都达到较高水平,同时还要有运气成分。一等奖就更不用说了,除了硬实力,还要求你们队伍的选题正好对上评阅专家的偏好。
每个赛题自己的获奖率也不一样。有些题目因为选的人多、竞争激烈,评阅尺度会被迫拉高,导致获奖率反而低于平均水平;另一些相对冷门的题,因为样本量小、参赛队伍平均水平波动大,有时候反而容易出现“超高获奖率”的年份。这里我特别提醒一句:单届数据里的获奖率浮动非常大,千万别拿一年的数据做决策。
3.3 高校一等奖排名:头部稳固,但黑马年年都有
按学校汇总一等奖数量,是大家最关心的榜单。我在自己的统计口径下做了排序,为了不影响官方数据权威性,这里不贴精确排名,只说说我看到的格局。
头部高校整体还是那几所:北京理工大学、武汉大学、中山大学、华中科技大学、西安电子科技大学、东南大学、南京理工大学、北京航空航天大学、电子科技大学、西北工业大学这些学校的一等奖数量经常排在榜单前列。这些学校的共同特点是数模竞赛传统深厚,校内培训体系完整,有稳定的高年级参赛梯队,很多队伍是从大四甚至研一就开始系统性训练的。
但我注意到一个容易被忽略的细节:绝对的一等奖数量受参赛基数影响很大。有些学校动辄几百支队伍参加校内选拔,获一等奖的数量自然多;而一些地方院校可能只有几十支队伍参赛,但质量极高,拿到两个一等奖,折算成“一等奖转化率”反而领先。如果你评价格局,建议算一下“一等奖数 / 该校获奖队伍总数”,这个比例更能反映学校的竞赛浓度。
黑马学校也年年存在。比如个别地方高校在E题这类应用性较强的题目上连续两届拿一等奖,背后往往是有比较强势的导师团队在支撑某个具体方向。这说明华为杯并不是头部985的专利,选题策略和训练方向对路,小体量队伍也有机会。
3.4 交叉分析:什么类型的学校更容易在什么题目上拿奖
只看总排名太过粗糙,我更关注“学校类型和赛题类型”之间的对应关系。我按学校主管部门背景和学科结构粗略分了几个类型,结合几年的获奖名单做了交叉统计,结论比较有意思。
理工科强校,尤其是通信、电子、计算机方向积累深厚的学校,在协议设计、信号处理、硬件优化类题目上优势明显。这个好理解,平时课程和科研训练就是这些内容。财经类院校则在数据分析、评价模型、经济调度类题目上表现出色,因为他们学生的统计学和经济学功底更扎实。师范类院校虽然参赛基数和获奖总量不大,但在模型推导和论文写作规范性上经常被评阅专家打高分。
放到实际操作层面,组队选题时可以参考这一点。如果你的队伍全部来自机械工程专业,硬选纯数据分析赛题,多少有点拿短板去拼别人长板的意思。反过来,如果你们学校在某个类型的赛题上有拿奖传承,优先考虑同类型题目,至少能借用上一届队伍的论文思路和训练资源。
4. 数据可视化:三步把获奖名单变成能直接发朋友圈的图
4.1 先想清楚“给谁看”,再选图表
统计做完之后,表达方式直接影响传播效果。我给图分成三个使用场景,不同场景对图表的精度要求不一样。
如果只是自己看数据规律,用matplotlib画最朴素的条形图、折线图就够了,不需要任何花哨的效果。如果想拿给导师看,在图上标注数据来源、统计口径,再补充关键结论的注释文字,让导师不用看代码也能快速抓取信息。如果你想发朋友圈或者做公众号素材,可以用pyecharts做交互式图表,地图热力、漏斗图、桑基图效果会更好。
整体原则是:图是辅助解释的,不是用来装饰的。如果你做完一个图还要用自然语言反复解释“这个颜色是什么意思”“这个数字代表什么”,那这个图就失败了。
4.2 一份可直接改用的Python可视化代码
我以“赛题热度”和“一等奖Top高校”这两个维度为例,给出一段可以直接跑的代码。
import pandas as pd import matplotlib.pyplot as plt # 解决中文乱码问题 plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "PingFang SC"] plt.rcParams["axes.unicode_minus"] = False # 假设df是清洗后的数据框 # df = pd.read_excel("清洗后的名单.xlsx") # 统计各赛题获奖队伍数量 topic_stats = df.groupby("选题")["队号"].count().sort_values(ascending=True) # 统计一等奖高校数量 df_first = df[df["奖项"] == "一等奖"] school_stats = df_first.groupby("学校")["队号"].count().sort_values(ascending=True).tail(10) # 并排画两个横向条形图 fig, axes = plt.subplots(1, 2, figsize=(14, 5)) topic_stats.plot(kind="barh", ax=axes[0], color="#4C72B0") axes[0].set_title("各赛题获奖队伍数量") school_stats.plot(kind="barh", ax=axes[1], color="#DD8452") axes[1].set_title("一等奖数量 Top 10 高校") plt.tight_layout() plt.savefig("华为杯2025成绩分析.png", dpi=300)代码很短,但有几个细节值得说。设置font.sans-serif时,最好把系统里确定存在的中文字体放在最前面,否则matplotlib会按顺序找,找不到时就会显示方块字。设置axes.unicode_minus为False,是为了让负号正常显示,否则坐标轴上的“-1”会变成一个横杠。保存图片用dpi=300,是为了在微信群里被压缩之后还能保持清晰。
4.3 配色和版式:别用默认的彩虹色,真的辣眼睛
如果你是给自己看的图,用matplotlib默认配色没问题。但如果你要把图发出去,默认配色是很多人的雷区。我建议至少用一组色盲友好配色,比如Tableau 10或者Matplotlib的tab10配色。我自己常用的是深蓝和橙色的组合,因为对比度高,打印出来或者屏幕上看都比较舒服。
再提一个容易被忽略的问题:横向条形图的数据,在plot之前先sort_values,并且用ascending=True让数据从小到大排列,这样画出来时最大的条在顶部。如果忘了排序,得到的图会非常凌乱,别人一眼看不出重点。另外,如果学校名称很长,注意调整figsize,或者用plt.tight_layout()避免标签被截断。
5. 统计陷阱与避坑实录:这些坑我替你踩过了
5.1 公示名单不等于最终名单
每年成绩公布后都有公示期,这期间并不是走走形式,真的会有队伍的奖项被调整。去年我就亲眼见过一份基于公示稿做的高校排名,后来官方发布最终稿时,某高校多了一等奖,名次直接跳过两档。所以,做分析之前一定还要做一件事:确认文件名里有没有“最终”“定稿”字样,并用最终数据重新核对一遍你的核心结论。
5.2 同一所学校多个校区和研究院,必须合并
这是最影响排名准确性的坑。官方名单里带校区后缀的学校相当多,比如“哈尔滨工业大学(深圳)”、“山东大学(威海)”、“北京大学医学部”等。如果统计时不做合并,排名表里同一个学校会被拆成好几条,高手的成绩被稀释。建议在清洗环节就建立完整的映射字典,并且每跑一次数据都检查“学校唯一值数量”,如果数量超过常规模,大概率有新的校区变体没被识别出来。
5.3 获奖率不等于题目难度
这个问题在很多场合都会被误读。某道题获奖率低,并不一定意味着它难,也可能是选这道题的队伍整体水平偏低(因为门槛看起来低,吸引了很多新手)。某道题获奖率高,也可能是题目的评阅标准比较宽松,或者题目偏“套路化”,容易踩到专家喜欢的模型套路。用单届数据判断“哪题更好拿奖”非常不可靠,至少要收集两到三届的跨年数据做横向对比,才能稍微减小噪声。
更隐蔽的问题是幸存者偏差。我们手里只有获奖队伍的数据,所有落榜队伍的选题、奖项、学校分布我们完全看不到。所以没有办法计算各赛题真实的落榜率。如果某道题获奖队伍数量多,绝不代表它的“投递性价比”高,因为它可能同时吸引了海量炮灰队伍。这种时候,群里反馈的“题目体验”反而比单看获奖名单更有效。
5.4 从PDF提取表格的惨痛教训
我早期偷懒下载过PDF格式名单,想用tabula直接解析成Excel,结果一页数据错位了将近三分之一。PDF的表格常常使用合并单元格,比如一个奖项占三行、队员姓名跨页,提取出来之后队号和学校根本对不上。从那以后,我的原则是能等官网出xlsx版就等,实在找不到,就把PDF转成文本,逐行人工核对关键字段。那不叫笨办法,那叫稳妥。
6. 一点个人体会:数据帮我做的几个参赛决策
分析做完之后,我对华为杯的选题策略有了更现实的认识。以前我和很多人一样,觉得选赛题就是要看题目难不难、资料多不多。现在我会多加一个维度:看看目标赛题的“竞争烈度”,也就是它在获奖名单里的队伍量、历届获奖率变化、以及本校在这个类型题目上的历史战绩。
去年帮一位师妹团队选备赛题目时,我们用类似的数据思路做了对比。当时大家都在讨论大数据分析类题目,热度极高,而另一道偏工程优化的题目选的人相对少,但学校往届在同类题上有过教练传承。权衡之后,师妹组选了后者,最后拿了二等奖。我不敢说这是数据的功劳,但至少数据帮她们避开了最拥挤的一条赛道。
最后再分享一个我在多次实战里反复提醒自己的认知:数据只能告诉你概率,不能替你坐在那张桌前。华为杯说到底拼的还是四天三夜里你们团队的推理速度、代码工程能力和论文写作耐力。获奖名单上的数字是抽象的,但每一个数字背后都是真实存在的熬夜、争吵和重写。用数据做决策,然后回到认真的训练里去,这才是成绩分析真正的意义。