☰
2025美赛C题:从时序面板到负二项回归的奥运会奖牌预测
2026/10/14 4:36:46 网站建设 项目流程

简介:2025年美赛C题建模与数据分析代码合集,面向参赛学生、数据分析爱好者与教练团队,围绕奥运奖牌预测与相关因素分析提供完整可运行的Python解决方案。资源覆盖数据清洗、特征工程、奖牌预测、进步退步分析、零奖牌统计、奖牌突破概率、东道主效应及项目设置影响等模块,采用线性回归、随机森林等模型,并配有matplotlib/seaborn可视化输出,可支撑从数据预处理到结果解读的完整流程。整套资源共91个文件,以28个csv数据、21个py脚本、12个docx文档、10个ipynb分析笔记本为主,另有pdf赛题原文、markdown说明与汇报PPT等,压缩包约25MB,目录按原始数据、清洗数据、预测脚本、论文报告等划分,便于快速定位。已有167人学习下载。随包提供中英文赛题、可复现代码、建模思路文档与配套报告,并附有数据字典、分组中间结果等辅助产物,适合需要系统备赛或复现美赛C题完整方案的同学。

1. 2025年美赛C题不是一道数据挖掘题,而是一道“时序结构”题

2025年美赛C题的核心任务是:基于一百二十多年的奥运会历史数据,预测下一届各代表团(NOC)的奖牌数,并对奖牌变化背后的相关因素做分析。很多队伍拿到题的第一反应是把它当成回归竞赛:划分训练测试集、调XGBoost、用RMSE排名次。这个思路在第一轮就会翻车,因为这批数据的训练集和测试集分布严重不一致——每届奥运会的参赛代表团范围不同、比赛小项数量不同、主办国效应年年有但强弱差很多,模型很容易学到“年份和总奖牌数的伪关系”,而不是国家实力。你调参调得再漂亮,预测下一届前几名照样全错。这道题真正考的是:你能不能把历史数据整理成一个能支撑时序推理的面板结构,再在这个结构上谈预测。本篇文章讲的就是这样一套完整做法:从数据清洗到特征工程,从奖牌预测到进步退步分析,再到可视化呈现,每一节都有能直接落地的代码和参数说明。适合准备参赛的建模队,也适合想系统做一次奥运数据项目的从业者。

2. 数据清洗与特征工程:把120年奖牌表整理成能建模的“国家-届次”面板

2.1 数据清洗:先确定观测单元,再谈特征

美赛C题原始数据通常是“行级明细”,每一行代表一枚奖牌,包含年份、代表团代码、运动项目、小项名称、奖牌类型。建模的第一步不是跑模型,而是把行级明细转换成面板数据。我给这个步骤定的原则非常简单:一行一个国家、一届奥运会、一个统计口径。这里最容易犯的错误是低估了“唯一键”的重要性——如果单纯按年份加总,你会得到每届的总奖牌数,但那只是一个数字,无法建模国家粒度;如果按“年份-代表团”聚合,又会遇到代表团改名、国家合并、解体继承等历史问题。我的做法是先建立一个代表团映射表,把历史上出现过的所有NOC代码统一到一个“延续代码”上,再去做GroupBy。

import pandas as pd # medals.csv 至少包含 year, noc, sport, event, medal 五列 raw = pd.read_csv("medals.csv") # 映射表:把历史NOC代码统一到延续代码 mapping = { "URS": "RUS", # 举例:历史代码 -> 延续代码 "TCH": "CZE", # 实际比赛中根据官方NOC变更说明维护这张表 } raw["noc_clean"] = raw["noc"].replace(mapping) # 按 年份-延续代码 聚合出金/银/铜/总数 agg = ( raw.groupby(["year", "noc_clean"]) .agg( gold=("medal", lambda s: (s == "Gold").sum()), silver=("medal", lambda s: (s == "Silver").sum()), bronze=("medal", lambda s: (s == "Bronze").sum()), ) .reset_index() ) agg["total"] = agg["gold"] + agg["silver"] + agg["bronze"]

这段代码的逻辑是:先对NOC做一次“口径统一”,再用groupby同时统计金、银、铜数量。拆开统计比直接计数更有用,因为后面可以做加权奖牌数,也可以分别建模“金牌预测”和“总奖牌预测”。参数说明里有两个细节:一是lambda计数只统计medal字段等于指定值的行,聚合前最好过滤掉medal为空的脏数据;二是在真实比赛中,你要确认奖牌明细里“没有获得奖牌但参赛”的代表团是否出现在别的表里——那一部分才是真正的0奖牌样本,不能让非参赛国混进面板。

2.2 特征工程的三个必做项:东道主效应、项目数变化、历史实力

面板生成后,下一步是特征。我做过几轮奥运会数据项目,最重要的经验是:不要一上来想着造几百个高阶交叉特征,先把三个结构性特征做扎实,模型效果立马不一样。第一个是东道主效应。每届主办国在当届的奖牌数普遍有明显提升,但提升幅度不固定,而且下一届往往会回落到正常水平附近。所以要做的不是“主办国=1”这种单变量,而是三列:host_now(当届主场)、host_prev(上届主场)、host_next(下届主场)。第二个是项目数变化。奥运会每届的小项总数不一样,新增项目往往对东道主和传统优势国更有利,直接用“当届总项目数”作为整体暴露量即可,如果数据更细,可以做成“该国优势小项的数量”。第三个是历史实力。代表队实力有惯性,但距离现在越久参考价值越低,用指数衰减加权来处理比简单用上一届奖牌数更平滑。

# 在 panel 基础上继续做特征 panel = agg.sort_values(["noc_clean", "year"]).reset_index(drop=True) # 上届奖牌数 / 上上届奖牌数 panel["last_total"] = panel.groupby("noc_clean")["total"].shift(1) panel["last2_total"] = panel.groupby("noc_clean")["total"].shift(2) # 东道主三列:需要提前准备好 host_map,键是年份,值是主办NOC panel["host_now"] = panel["year"].map(host_map).eq(panel["noc_clean"]).astype(int) panel["host_prev"] = panel.groupby("noc_clean")["host_now"].shift(1).fillna(0) panel["host_next"] = panel.groupby("noc_clean")["host_now"].shift(-1).fillna(0) # 指数衰减加权历史实力:最近三年权重最高 import numpy as np def weighted_hist(x, decay=0.7): if len(x) == 0: return np.nan w = decay ** np.arange(len(x) - 1, -1, -1) return float(np.dot(x, w) / w.sum()) panel["hist_str"] = ( panel.groupby("noc_clean")["total"] .transform(lambda s: s.rolling(3, min_periods=1).apply(weighted_hist, raw=True)) )

这段代码里的shift是按国家分组后的滞后操作,不会串到别的国家。rolling(3)表示最多看前三届,decay=0.7表示越早的一届权重越低:三届的权重比大约是1:0.7:0.49,近期成绩主导但历史稳定性也在。decay的调参方向是:如果模型预测波动过大,说明对近期过度敏感,可以提高到0.8甚至0.9;如果模型反应迟钝,调低到0.5。host_next这一列用于训练是没问题的,因为训练时“下一届”是已知的历史届次,但最终预测2028年时这一列必须置零,否则会用到不存在的未来信息,这一点务必在预测阶段单独处理。

2.3 探索性可视化的第一张图:先看东道主效应再决定建模策略

特征做完之后不要急着建模,先用可视化确认假设。我的固定动作是画两张图:第一张是东道主奖牌数与其前后两届的对比折线,用来确认东道主效应是否显著;第二张是全体代表团的奖牌数分布直方图,确认数据是零膨胀计数分布还是近似正态。第二张图往往会让队伍意识到,奖牌数分布右侧长尾非常严重,“奖牌数为0”的参赛代表团占了一半以上,这种情况下直接用线性回归是危险的。

import matplotlib.pyplot as plt # 第一张图:东道主轨迹抽样 host_nocs = panel[panel["host_now"] == 1]["noc_clean"].unique()[:5] fig, ax = plt.subplots(figsize=(8, 4)) for noc in host_nocs: sub = panel[panel["noc_clean"] == noc].set_index("year")["total"] sub.plot(ax=ax, marker="o", label=str(noc)) ax.set_title("Host Effect: medal trend around hosting year") ax.legend() plt.tight_layout() plt.show() # 第二张图:奖牌数分布 panel["total"].hist(bins=50) plt.title("Distribution of total medals per NOC per Games") plt.show()

这两张图的价值不是“好看”,而是给模型选型提供依据。如果发现东道主效应在不同国家间差异很大,比如有些国家主办年翻倍、有些国家只是小幅上升,那么建模时就要考虑“主办国 × 历史实力”的交互项,而不是只放一个哑变量。如果第二张图确认了零膨胀和长尾,那么第3章的负二项回归就是比线性回归更合理的选择。可视化的输出不需要精致到论文级别,此时它更像是你手里的证据,帮你决定下一步往哪个方向使劲。

3. 奖牌预测:从负二项回归基线到机器学习校准

3.1 第一版模型必须是可解释基线,不能是调包

奖牌数预测这件事,评委和客户关心的往往不只是“数字是多少”,更关心“为什么是这个数”。所以第一版模型我会选择可解释的计数模型,而不是直接把数据丢给随机森林。奖牌数是一个典型计数变量:取值是非负整数,方差往往大于均值,存在大量小国低奖牌样本。泊松回归要求均值等于方差,在奥运数据上基本不满足;负二项回归多了一个离差参数alpha,允许方差大于均值,更适合这种过离散数据。建模时用“国家固定效应 + 年份连续趋势 + 东道主效应 + 上届奖牌 + 历史实力”,这一套组合的意义在于:每一项都有明确业务含义,输出系数可以直接写进论文的“因素分析”章节。

import statsmodels.formula.api as smf # 训练集:用截至上届的数据;测试集:留最后一届 train = panel[panel["year"] < 2024].dropna(subset=["total"]) test = panel[panel["year"] == 2024].copy() formula = "total ~ C(noc_clean) + year + host_now + host_prev + last_total + hist_str" model = smf.negativebinomial(formula, data=train).fit() print(model.summary()) # 预测并保留区间 test["pred_total"] = model.predict(test)

负二项回归的输出里先看alpha系数的置信区间。如果区间包含0,说明数据不过离散,退回泊松更简约;如果alpha显著大于0,负二项就是必要的。C(noc_clean)会为每个代表团生成一个哑变量,对于参赛历史长的大国来说没问题,但小国样本少、系数不稳定,我一般会在拟合前过滤掉“参赛次数低于5届”的代表团,避免固定效应把小国的历史噪声当成实力。year系数代表全球奖牌总数随时间缓慢上升的趋势,如果这一项不显著,说明总奖牌量主要由项目数变化决定,此时应该把year换成“当届项目总数”作为显式暴露量,负二项回归完整写法是model.fit(exposure=np.log(events)),但我个人更建议直接用“当届项目数”做成特征,这样解释起来更直观。

3.2 模型诊断与参数调整:用什么指标判断预测好坏

对于计数预测,RMSE可以被用到,但它对长尾大国的误差过于敏感。我习惯同时看三个指标:R方(回归视角)、平均绝对误差MAE、以及一个更贴近题目诉求的指标——Top10命中率,即预测奖牌数前10名中有几个真的出现在实际前10名里。这三个指标视角不同,R方看整体拟合,MAE看平均偏差,Top10命中率看排序能力。比赛论文里最容易被挑战的恰恰是最后一项:评委不会关心你预测的第45名误差多小,他们更关心前几名是否靠谱。

from sklearn.metrics import mean_absolute_error mae = mean_absolute_error(test["total"], test["pred_total"]) print("MAE:", mae) # Top10命中率:预测前10 vs 实际前10 pred_top10 = set(test.nlargest(10, "pred_total")["noc_clean"]) true_top10 = set(test.nlargest(10, "total")["noc_clean"]) hit_rate = len(pred_top10 & true_top10) / 10 print("Top10 Hit Rate:", hit_rate)

这段代码是评估口径,不在建模复杂度上做文章。MAE如果超过10枚,说明模型对大国(单届几十枚奖牌)的预测系统偏差较大,要去查是不是某个主办国的大年没被模型捕捉;Top10命中率如果低于50%,优先怀疑特征缺口而不是模型复杂度。负二项回归本身没有超参数可调,真正可调的是特征,所以“参数怎么改”要落回特征构造:host_prev的方向和显著性能提示你东道主效应是“持续两届”还是“一届游”;last_total的系数接近1还是大于1,决定你是否需要对动量强的队伍做额外校准。

3.3 用LightGBM做残差校准:让非线性信号补位

负二项回归的可解释性很好,但表达不了非线性交互。比如“某国既是东道主,又在近两届新增了好几个优势小项”这种组合效应,线性模型很难拟合。我的做法是分两步:第一步用负二项回归得到基线预测,计算训练集残差;第二步用LightGBM在残差上做回归,学习线性模型剩下的非线性信号。最终预测等于基线预测加残差预测。为什么不在第一步直接上GBDT?因为那样会丢失“主办国系数是多少、历史实力弹性是多少”这些论文里最值钱的解释材料。残差校准是折中方案:主体可解释,局部补非线性。

import lightgbm as lgb train["base_pred"] = model.predict(train) train["resid"] = train["total"] - train["base_pred"] feat_cols = ["year", "host_now", "host_prev", "last_total", "hist_str", "gold_last"] # 还可以加收入、人口等外部特征,但需要确保2028年也能取到 dtrain = lgb.Dataset(train[feat_cols], label=train["resid"]) dval = lgb.Dataset(test[feat_cols], label=test["total"] - model.predict(test)) params = { "objective": "regression", "learning_rate": 0.02, "num_leaves": 15, "min_data_in_leaf": 20, "verbosity": -1, } model_gbdt = lgb.train( params, dtrain, num_boost_round=500, valid_sets=[dval], callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)], ) test["pred_final"] = test["pred_total"] + model_gbdt.predict(test[feat_cols])

这里有几个关键参数值得解释。learning_rate设置为0.02是为了让模型学得慢一点,配合early_stopping防止在残差上学过头。num_leaves=15限制了树的复杂度,因为残差里能学到的真实信号本来就有限;如果你的训练集有几十个代表团和十几届数据,树太深一定会过拟合。min_data_in_leaf=20保证每个叶子至少有20个样本,进一步压过拟合。最容易被忽略的是:GBDT训练时用的特征是“基线模型也用过的”,这意味着它没有办法创造出全新信息,只是把已有特征的非线性组合找出来。如果你想让校准带来明显提升,应该在这个阶段加入基线没用过的外部特征,比如该代表团在上届各项目中的“银牌转金牌率”,这类特征代表的是实力上升的微观信号。用LightGBM校准后的最终预测,我建议不要再把区间做复杂推导,直接用训练集残差的标准差乘以0.7作为预测区间的半宽,简洁且诚实。

4. 进步退步分析:把“某国这届涨了很多”拆成真实崛起与周期波动

4.1 量化定义:不要用“较上届增长百分比”评价小国

进步退步分析最直观的想法是“这届奖牌数减上届奖牌数”,但直接算绝对值或百分比都有缺陷。奖牌基数为1的国家,下届变3枚就是增长200%,从统计学上根本无法区分这是真实进步还是随机波动。所以我在做这道题时会先把绝对奖牌数转换成两个更稳的口径:一是奖牌占比,即该国奖牌数除以当届总奖牌数;二是奖牌数在所有参赛代表团中的百分位排名。用这两个口径计算进步退步,才不会让小数点级别的变化被无限放大。尤其是当题目要求“分析进步与退步的背后因素”时,评委关心的是哪些代表团属于结构性上升,哪些只是东道主一年游。

# 奖牌占比 panel["share"] = panel["total"] / panel.groupby("year")["total"].transform("sum") # 连续两届占比变化 panel = panel.sort_values(["noc_clean", "year"]).reset_index(drop=True) panel["share_chg"] = panel.groupby("noc_clean")["share"].diff() # 三届趋势斜率:用最简单的线性回归斜率做动量方向 def trend_slope(y): if len(y) < 3: return 0.0 x = np.arange(len(y), dtype=float) return float(np.polyfit(x, y, 1)[0]) panel["trend"] = ( panel.groupby("noc_clean")["share"] .transform(lambda s: s.rolling(3, min_periods=3).apply(trend_slope, raw=True)) )

share_chg是短期变化,trend是中期趋势。两者方向一致时进步/退步的判断可靠性最高;方向矛盾时,比如短期上涨但三届斜率向下,说明只是波动而不是趋势。使用这两个特征时,要注意rolling(3)会把早期的值变成NaN,后续处理时要么填充0,要么直接丢弃早期样本,我倾向于丢弃,因为“进步退步”本来就需要至少三届数据才有意义。

4.2 判定算法:阈值划分与形状聚类

有了share_chg和trend之后,下一步是划分类别。最简单的做法是设双阈值:趋势大于0.01且短期变化大于0.005标为“强势进步”;趋势小于负0.01标为“明显退步”;中间地带叫“波动调整”。阈值怎么定?我的经验是不要拍脑袋,看数据的分布,把trend和share_chg做散点图,找出自然的分类边界。更稳健的替代方案是用KMeans对“该国连续几届的奖牌占比时间序列”做形状聚类,把时间序列归一化后聚类成三类:持续上升型、波动型、持续退步型。这种方法的优点是省去手工设阈值,缺点是需要你手动解释每个簇的业务含义,工作量反而更大。我用KMeans做出来的类别常常与直觉不符,因为这届大题的数据覆盖了一百多年,不同时期奥运会规模差异巨大,归一化虽然保留了形状,却丢掉了量级,所以最后我还是选择“阈值划分+人工校准”的方法写进论文。

# 阈值划分示例,阈值为示例值,需结合数据分布调整 def classify_progress(row): if pd.isna(row["trend"]): return "数据不足" if row["trend"] > 0.01 and row["share_chg"] > 0.005: return "持续进步" if row["trend"] < -0.01: return "明显退步" if row["share_chg"] > 0.005: return "短期上升" return "波动调整" panel["progress_type"] = panel.apply(classify_progress, axis=1)

这段代码的价值在于把主观判断变成一个可复现的规则,方便在论文里写清楚“进步退步的定义”。注意阈值不是通用的,2012年之后的数据分布和前一百年完全不同,所以我实际应用时会按“近期四届”重新计算阈值。这里有一个关键约束:进步退步分析只能对“连续参赛的代表团”做,中间缺了一届的代表团(无论是战争停办还是主动缺席)会导致diff和trend断崖,处理方法是把缺届视为NaN而不是0,否则会把一届普通成绩错判成退步。

4.3 进步退步分析如何反哺预测模型

进步退步分析不只是为了凑一个分析章节,它应该直接服务预测。我在做最终预测时会构造一个“动量虚拟变量”:如果某代表团被分到“持续进步”,在GBDT残差校准阶段额外加权;如果被分到“明显退步”,则在最后一位预测时把基线预测下调一定幅度,幅度与退步斜率成正比。这个思路的本质是:奖牌惯性不是均匀的,线性模型给每个国家同样的惯性系数,但进步退步分析告诉你,有些国家的惯性是负的。除此之外,进步退步分类还有一些“非预测用途”:它可以帮助你在因素分析中回答“哪些因素和进步/退步相关”,比如计算进步类代表团的平均新增项目数、是否东道主、上届参与小项数等统计量。这比在论文里堆一堆相关系数更有说服力。

5. 避坑与常见问题:奖牌预测翻车的5个高发位置

5.1 时间序列数据用随机K折交叉验证,导致预测结果虚高

现象:模型在验证集上MAE很小,Top10命中率高得离谱,但一旦预测下一届真实未来数据,排名完全错乱。

原因:随机K折把不同届次的样本打散进训练集和测试集,模型在训练时已经见过“未来届次”的数据分布,尤其是东道主效应和历史实力特征会泄露信息。

解决:只使用前向验证。具体做法是以时间排序,前几届训练、后一届验证,依次滚动。例如训练到2020届预测2024届,再用到2024届预测2028届。在负二项回归和LightGBM校准中,都严格按这个时间窗划分数据,代码里的train/test就是按“year < 2024”和“year == 2024”切出来的,这就属于前向验证。

提示:前向验证的测试集只有一届,样本量少、评估波动大,这是奥运数据题目绕不开的现实:历史届次总共二十多次,不可能变出更多的“未来”。接受这一点,不要通过随机K折制造虚假的信心。

5.2 东道主效应被无限外推:预测下届主办国奖牌爆炸

现象:模型预测下一届主办国奖牌数比其历史峰值还高出一倍,看起来就不可信。

原因:负二项回归的host_now系数很大,预测时将host_now=1直接套用,模型认为该系数对所有国家一致,没有考虑“东道主红利”会随该国原有实力、上届是否已经透支而衰减。

解决:给东道主效应乘以一个收缩系数,通常取0.5~0.8。更精细的做法是构造“东道主 × 上届奖牌占比”的交互项,让模型自己去学:原来实力弱但占便宜大的国家收缩更大,还是原来实力强的国家加成才多。我在实际项目中一般分两步走:先不加交互项看主效果,再加交互项看系数变化,如果交互项显著说明收缩逻辑成立,最终预测时用交互后的预测值。

5.3 聚合后的奖牌总数和原始明细不一致,被评委抓到口径错误

现象:论文里写的总奖牌数和官方历史数据集对不上,或者某国某届的金牌数比实际少了三枚。

原因:聚合时丢失了NOC改名、奖牌类型大小写不一致、以及部分行在读取时被当作空值过滤掉。这些错误在行级明细几十万行的数据里很容易被忽略。

解决:每次聚合后做全链路校验。第一层校验:聚合后的gold+silver+bronze必须等于原始明细中medal非空的行数,注意一枚奖牌对应一行,所以是计数不是数值求和。第二层校验:任意挑三个大国、两届三届的数据,和公开的官方奖牌表手动比对,确保NOC映射表没搞错。

assert agg["total"].sum() == raw["medal"].notna().sum(), "奖牌总数不一致" assert (agg[["gold", "silver", "bronze"]].sum(axis=1) == agg["total"]).all(), "分项与总数不一致"

这两行断言写在清洗脚本的最后,一旦不通过立即中断,不要带着脏数据往下走。我踩过最大的坑是映射表把某个历史代码映射到了两个不同的延续代码,导致该国的数据被切成了两半,断言一查就暴露了。

5.4 进步退步分析把小样本波动当成了趋势

现象:某个只拿过一两枚奖牌的小代表团被标成“显著进步500%”,在论文里显得很滑稽。

原因:基数太小,百分比变化被放大到失真。这是比例类指标在小样本下的通病。

解决:给进步退步分析加“最小奖牌量门槛”,比如近三届平均奖牌数大于等于5才参与分类;低于门槛的全部并入“数据不足”。如果不想丢弃小国,就用奖牌占比的变化来判断,占比每届变化0.005在排名上是可比的,但在一个奖牌总数3000枚的奥运会上,0.005占比只对应15枚奖牌,仍然会高估小国。所以我看重的是“门槛 + 占比双条件”,两者都过才算有效信号。

5.5 可视化把金银铜用颜色区分,导致数量维度被误读

现象:论文里的堆叠柱状图,每个国家的金牌区、银牌区、铜牌区用不同颜色区分,评委看不出总数差异,只看到颜色块的高度,实际上最长的颜色块对应的可能是铜牌最多,而不是总奖牌第一。

原因:金银铜是分类信息,不适合直接对颜色做读者解释;正确的映射是“数量”用高度或长度,“类别”用颜色或图例。

解决:画“总奖牌数排序柱状图 + 金/银/铜占比堆叠条”,或者把金银铜映射到同一个柱状图的内部堆叠区,并在图例上明确说明数字为主、颜色为辅。如果目标是突出金牌,直接画“加权奖牌数”,权重按金3银2铜1,颜色只标显著进步/退步类别。可视化的原则是“数量维永远优先于类别维”,这句话写进论文不会错。

6. 进阶用法:稳健性检验与情景分析,让预测结论经得起追问

6.1 稳健性检验:换口径、删届次、调参数

评委最常问的一句话是“你的结论对数据选择敏感吗”。为了回答这个问题,我会跑三组稳健性检验:一是换奖牌口径,把“总奖牌”换成“加权奖牌数”重跑整套流程,如果Top10预测结果变化不大,说明结论不依赖口径选择;二是剔除特殊届次,一战二战期间的奥运会、以及早期参赛规模极小的几届,从训练集中拿掉后重新拟合,看东道主系数和趋势系数是否仍然成立;三是调整历史实力特征的衰减系数,从0.7改成0.5和0.9,看排名稳定性。这三组结果整理成一张参数对照表放进论文附录,比任何文字解释都有力。

6.2 情景分析:把点预测升级成决策区间

点预测只是一个数字,没有决策参考价值。我给最终预测输出三个情景:基准情景,即模型原始预测;强主办国情景,将东道主效应收缩系数调低(相当于更信任主办国红利),同时假设该国上届已存在的优势项目全部保留;保守情景,把全体预测值向历史均值收缩10%。三个情景共享同一个特征工程和建模流程,只是参数不同,因此可以写成两个函数复跑。这样做的好处是:论文里可以写“我们的模型预测东道主代表团将获得35~45枚奖牌,中位数38枚”,而不是写一个孤零零的38。评审对区间的接受度远高于点估计,因为区间本质上是诚实地承认了模型不确定性。

6.3 一套代码组织方式,让整套方案可复现

这道题的工作流可以稳定分成四个脚本:clean.py负责数据清洗与口径校验,features.py负责面板构建与特征生成,model.py负责负二项回归和LightGBM校准,analyze.py负责进步退步分析、可视化和稳健性检验。四个脚本按顺序执行,中间产物分别是panel.csv、features.csv、prediction.csv、analysis_results.csv。我在做这类题时一直保留这套结构,换一个数据集、换一届年份,只需要改clean.py里的映射表和features.py里的host_map,后面的脚本一行都不用动。“开箱即用”说的就是这种结构:数据进去,论文图表和预测表格出来。多年做这类题的习惯让我养成了一个偏好:把所有“口径定义”写成一个单独的字典放在代码最前面,比如金牌权重、东道主收缩系数、最小奖牌量门槛,这样每次跑完看到结果时,第一个动作不是怀疑模型,而是检查口径字典是否还符合这次比赛的要求。希望帮到你。

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

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

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

立即咨询