用Python实现200米成绩趋势分析与预测 📅 发布时间:2026/8/30 1:28:46 👁 浏览次数: “200米好像又行了”在部分观众眼里可能只是一句情绪表达近期200米短跑的成绩有回升趋势选手状态回暖。但如果把它当作一个技术问题来看这句话其实值得展开回升了多少谁贡献了主要变化整体趋势是均值提升还是极值前移预测下一次比赛成绩会落在什么区间这些问题的答案靠肉眼扫一组历史成绩很难说清。本文就以“200米成绩趋势分析”为背景用 Python 手把手演示一套完整的“获取数据 → 清洗 → 统计 → 可视化 → 趋势预测”流程。读者可以把它迁移到双 11 销量分析、接口耗时波动分析、股票收盘价变化等任意“某个指标到底行不行”的场景。1. 当“200米好像又行了”变成数据分析课题1.1 这其实是一道数据分析题“又行了”是一种非结构化描述。数据分析的第一步就是把它转化为可以计算的指标最近一年的平均成绩是否比前一年更快最好成绩是否刷新成绩的标准差是否变小标准差变小说明成绩更稳定而不仅仅是头部选手突出。如果对未来比赛成绩做简单预测置信区间是否收窄一旦把这些指标列出来分析框架就清晰了。我们可以用 Python 完成数据整理、计算、绘图和简单回归整个过程并不复杂熟悉 pandas 和 matplotlib 就足够了。1.2 为什么选 Python 而不是 ExcelExcel 当然可以算平均值、画折线图但在数据处理自动化上存在几个明显痛点数据量大或来源分散时手工粘贴容易出错。每次新增比赛数据要重复调整公式和图表范围。无法方便地做批量清洗、异常值检测和回归预测。Python 的优势在于脚本化、可复现、易扩展。哪怕只有一份 Excel 数据用 pandas 读入再处理也比手工拖拽更可控至少每次运行得到的结果完全一致。1.3 本文的阅读收益读完本文你将能够用 pandas 构造并清洗成绩数据表。计算一个指标的均值、中位数、标准差、四分位数。用 matplotlib 绘制成绩趋势图、直方图、箱线图。用 numpy 做一次一元线性回归预测后续成绩。掌握数据缺失、异常值、单位不一致等常见问题的排查思路。2. 环境准备与版本说明2.1 运行环境本文示例代码在以下环境中验证通过但版本不要求完全一致操作系统Windows 10 / 11、macOS、Ubuntu 均可Python3.9 及以上pandas1.5.x 或 2.xmatplotlib3.7.xnumpy1.24.x 或更高Jupyter Notebook / VS Code如果你的 Python 版本较旧建议先升级 Python再安装依赖。2.2 安装依赖建议先创建虚拟环境避免依赖冲突mkdir sprint200-analysis cd sprint200-analysis python -m venv venvWindows 激活虚拟环境venv\Scripts\activatemacOS / Linux 激活虚拟环境source venv/bin/activate然后安装依赖pip install pandas matplotlib numpy openpyxl如果安装速度较慢可以使用国内镜像pip install pandas matplotlib numpy openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 项目目录结构建议按下面的结构组织项目方便后续扩展sprint200-analysis/ ├── data/ # 存放原始数据 │ └── sprint200.csv ├── output/ # 存放图表和结果 ├── scripts/ # 代码脚本 │ ├── data_prepare.py │ ├── analysis.py │ └── visualize.py └── venv/ # 虚拟环境这不是强制要求但项目实战中保持目录清晰能减少很多“找不到文件、不知道数据在哪”的问题。3. 核心概念与关键库拆解3.1 pandas表格数据的“瑞士军刀”pandas 的核心数据结构是 DataFrame可以理解为一张带行索引和列名的大表。对于成绩分析我们需要关心的列通常包括athlete运动员姓名year比赛年份event比赛名称比如奥运会、世锦赛、钻石联赛time_seconds成绩单位是秒例如 19.31wind风速短跑成绩的合法性与风速有关pandas 最常用的操作包括import pandas as pd # 读取 CSV df pd.read_csv(data/sprint200.csv) # 查看前 5 行 print(df.head()) # 查看缺失值 print(df.isnull().sum()) # 分组统计 print(df.groupby(year)[time_seconds].mean())3.2 matplotlib一张图暴露趋势matplotlib 是最常用的绘图库。成绩分析中折线图适合展示年度变化趋势直方图适合展示成绩分布形态箱线图适合展示异常值和分散程度。一个最小绘图示例import matplotlib.pyplot as plt import pandas as pd df pd.read_csv(data/sprint200.csv) plt.figure(figsize(10, 6)) plt.plot(df[year], df[time_seconds], markero) plt.title(200m Sprint Time Trend) plt.xlabel(Year) plt.ylabel(Time (seconds)) plt.grid(True) plt.show()3.3 常见误区把模拟数据当成真实成绩要注意的是真实比赛数据来源包括世界田径官网、各大赛事官方成绩册数据获取涉及版权和授权问题。本文为了演示使用模拟数据构建示例不代表任何真实运动员或真实赛事成绩。如果你在自己的项目中接入真实数据需要先确认数据来源的合法性与授权边界。另外分析成绩时不能忽略风速。短跑项目中顺风超过一定标准通常为 2.0 m/s时成绩不会被认定为正式纪录。所以构造数据集时风速这一列不要省略。4. 完整实战200米成绩数据分析全流程下面进入完整实战。为了演示我们模拟近 12 年部分赛事 200 米成绩数据包含若干缺失值和一个可疑异常值方便展示数据清洗过程。4.1 构造模拟数据集先写一个脚本生成data/sprint200.csv文件路径建议放在项目根目录下。# 文件路径scripts/data_prepare.py import pandas as pd import numpy as np np.random.seed(42) years list(range(2013, 2025)) athletes [A, B, C, D, E, F, G, H, I, J] rows [] for year in years: # 每年取 8 到 12 个有效成绩 for _ in range(np.random.randint(8, 12)): athlete np.random.choice(athletes) # 成绩基准在 19.8 到 20.6 秒之间逐年轻微下降代表成绩提升 base_time 20.8 - (year - 2013) * 0.04 time_seconds round(np.random.normal(base_time, 0.3), 2) time_seconds max(19.0, time_seconds) wind round(np.random.normal(0.5, 0.8), 1) wind max(-2.0, min(3.0, wind)) rows.append({ year: year, athlete: athlete, event: np.random.choice([Olympic, WorldChampionship, DiamondLeague]), time_seconds: time_seconds, wind: wind }) df pd.DataFrame(rows) # 模拟两个缺失值 df.loc[5, time_seconds] np.nan df.loc[20, wind] np.nan # 模拟一个异常值明显慢于其他成绩 df.loc[30, time_seconds] 25.8 df.to_csv(data/sprint200.csv, indexFalse) print(df.shape)这段代码生成一张模拟成绩表。base_time逐年降低是为了模拟“成绩提升”的趋势随机噪声用来模拟运动员之间、比赛之间的波动。4.2 数据清洗与字段检查数据生成后先做一次体检。# 文件路径scripts/analysis.py import pandas as pd import numpy as np df pd.read_csv(data/sprint200.csv) print(数据形状, df.shape) print(缺失值统计) print(df.isnull().sum()) print(基础统计) print(df.describe())describe()会给出各数值列的均值、标准差、最小值、四分位数和最大值。注意观察time_seconds的最大值如果远大于正常范围就可能是异常值或者录入错误。接下来处理缺失值和异常值# 时间字段缺失行直接删除风速缺失行可填充为 0 df df.dropna(subset[time_seconds]) df[wind] df[wind].fillna(0.0) # 异常值识别超过 3 倍标准差视为可疑 mean_time df[time_seconds].mean() std_time df[time_seconds].std() lower_bound mean_time - 3 * std_time upper_bound mean_time 3 * std_time print(f正常成绩区间[{lower_bound:.2f}, {upper_bound:.2f}]) df df[(df[time_seconds] lower_bound) (df[time_seconds] upper_bound)] print(清洗后数据形状, df.shape)这里使用的是“3 倍标准差”原则是一种通用的异常值检测方法。实际项目中是否删除异常值需要结合业务背景判断不能一概而论。例如25.8 秒如果确认是录入错误可以删除如果是一场比赛的真实极端结果就要保留并单独分析。4.3 核心指标计算清洗完成后计算核心统计指标# 总体指标 print(200米成绩总体均值, round(df[time_seconds].mean(), 3)) print(200米成绩中位数, round(df[time_seconds].median(), 3)) print(200米成绩标准差, round(df[time_seconds].std(), 3)) print(200米成绩极差, round(df[time_seconds].max() - df[time_seconds].min(), 3)) # 按年份统计 year_stats df.groupby(year)[time_seconds].agg([mean, median, std, min, max]) print(year_stats.round(3))输出中值得关注的是如果某年mean明显下降说明整体水平提升。如果某个年份std很小说明该年成绩集中度高竞争更激烈。如果min创下新低说明最好成绩被刷新。也可以按赛事类型统计event_stats df.groupby(event)[time_seconds].agg([count, mean, min]) print(event_stats.round(3))4.4 可视化成绩趋势与分布可视化能更直观地回答问题“到底行不行”。先绘制年度成绩变化折线图# 文件路径scripts/visualize.py import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS, DejaVu Sans] plt.rcParams[axes.unicode_minus] False df pd.read_csv(data/sprint200.csv) df df.dropna(subset[time_seconds]) df[wind] df[wind].fillna(0.0) mean_time df[time_seconds].mean() std_time df[time_seconds].std() lower_bound mean_time - 3 * std_time upper_bound mean_time 3 * std_time df df[(df[time_seconds] lower_bound) (df[time_seconds] upper_bound)] year_mean df.groupby(year)[time_seconds].mean() year_min df.groupby(year)[time_seconds].min() year_std df.groupby(year)[time_seconds].std() fig, axes plt.subplots(2, 2, figsize(14, 10)) # 子图1逐年平均成绩 axes[0, 0].plot(year_mean.index, year_mean.values, markero, color#1f77b4) axes[0, 0].set_title(各年平均成绩趋势) axes[0, 0].set_xlabel(年份) axes[0, 0].set_ylabel(平均成绩秒) axes[0, 0].grid(True) # 子图2逐年最好成绩 axes[0, 1].plot(year_min.index, year_min.values, markero, color#ff7f0e) axes[0, 1].set_title(各年最好成绩趋势) axes[0, 1].set_xlabel(年份) axes[0, 1].set_ylabel(最好成绩秒) axes[0, 1].grid(True) # 子图3成绩分布直方图 axes[1, 0].hist(df[time_seconds], bins20, edgecolorblack, alpha0.7) axes[1, 0].set_title(成绩分布直方图) axes[1, 0].set_xlabel(成绩秒) axes[1, 0].set_ylabel(频数) # 子图4各年标准差 axes[1, 1].plot(year_std.index, year_std.values, markers, color#2ca02c) axes[1, 1].set_title(各年成绩标准差) axes[1, 1].set_xlabel(年份) axes[1, 1].set_ylabel(标准差秒) axes[1, 1].grid(True) plt.tight_layout() plt.savefig(output/200m_analysis.png, dpi150) plt.show()这里解释一下四个子图的作用平均成绩趋势反映整体水平的年度变化。最好成绩趋势反映头部成绩的突破情况。成绩分布直方图看成绩是否集中右侧拖尾越长说明存在明显较慢的成绩。标准差趋势反映成绩稳定度。绘图时要注意中文字体问题。Windows 下可以用SimHeimacOS 下可以用Arial Unicode MSLinux 服务器上可能需要额外安装中文字体。如果不想处理字体问题也可以把所有标题改为英文。再画一张箱线图观察异常值和四分位分布import matplotlib.pyplot as plt df_with_year df.copy() df_with_year.boxplot( columntime_seconds, byyear, figsize(14, 6), gridFalse, patch_artistTrue ) plt.title(200米成绩按年份箱线图) plt.suptitle() plt.xlabel(年份) plt.ylabel(成绩秒) plt.xticks(rotation45) plt.tight_layout() plt.savefig(output/200m_boxplot.png, dpi150) plt.show()箱线图可以同时展示中位数、四分位距和离群点是比平均值更稳健的分布视角。4.5 简单成绩趋势预测“好像又行了”往往还隐含一层意思接下来的比赛成绩会不会继续提升我们做一个简单的一元线性回归演示。import numpy as np import pandas as pd # 使用各年平均成绩做回归 year_mean df.groupby(year)[time_seconds].mean().reset_index() x year_mean[year].values y year_mean[time_seconds].values # 一元线性回归拟合 slope, intercept np.polyfit(x, y, 1) print(f拟合公式time {slope:.4f} * year {intercept:.4f}) # 预测未来两年 future_years np.array([2025, 2026]) future_times slope * future_years intercept for yr, t in zip(future_years, future_times): print(f{yr} 年预测平均成绩{t:.3f} 秒)np.polyfit(x, y, 1)返回的slope是斜率intercept是截距。如果斜率为负数说明平均成绩逐年下降也就是成绩在变好。但这里必须强调一个工程上的边界一元线性回归只适合做趋势参考不适合做严格预测。短跑成绩还受训练周期、伤病、风速、赛事密度等因素影响数据量很少时预测结果的外推风险很大。更严谨的做法是扩大数据范围纳入多年、多名运动员的数据。加入风速、年龄、赛事级别等特征。使用时间序列模型例如 ARIMA 或 Prophet。给出置信区间而不是一个点估计。4.6 运行与验证在项目根目录按顺序执行python scripts/data_prepare.py python scripts/analysis.py python scripts/visualize.py预期效果是data/sprint200.csv生成模拟数据。analysis.py打印缺失值、基础统计、分组统计和清洗前后的数据形状。visualize.py在output/目录下生成200m_analysis.png和200m_boxplot.png两张图。如果终端出现ModuleNotFoundError: No module named pandas说明虚拟环境没有正确激活或者依赖没有安装成功。检查pip list是否包含 pandas、matplotlib、numpy。5. 常见问题与排查思路下表汇总了这个项目里最容易遇到的问题也适用于其他 pandas 数据分析脚本。问题现象常见原因解决思路ModuleNotFoundError: No module named pandas虚拟环境未激活或依赖未安装激活虚拟环境后执行pip install pandas matplotlib numpyFileNotFoundError: data/sprint200.csv当前工作目录不对在项目根目录执行脚本或使用绝对路径中文图表显示为方框系统中文字体缺失或未设置字体设置plt.rcParams[font.sans-serif]并安装中文字体删除异常值后数据变化不明显异常值本身未超过 3 倍标准差改用 IQR 方法或结合业务判断wind列存在 NaN数据录入缺失用 0 填充或按平均风速填充预测结果可信度存疑样本量太少、特征过于简单扩大数据样本引入更多特征使用更复杂的时间序列模型df.loc[30, time_seconds]后数据没有变行索引在读取 CSV 后可能已变化先df.reset_index(dropTrue)确保索引连续关于异常值检测这里再补充一个 IQR四分位距方法。IQR 方法不需要假定数据服从正态分布更适合真实分布偏斜的场景q1 df[time_seconds].quantile(0.25) q3 df[time_seconds].quantile(0.75) iqr q3 - q1 lower_fence q1 - 1.5 * iqr upper_fence q3 1.5 * iqr print(fIQR 异常值边界[{lower_fence:.2f}, {upper_fence:.2f}]) df_iqr df[(df[time_seconds] lower_fence) (df[time_seconds] upper_fence)]3 倍标准差适合近似正态数据IQR 适合偏斜数据。两种方法可以都跑一遍对比差异后再决定保留哪些样本。6. 最佳实践与工程建议6.1 数据源管理原始数据必须只读。不要直接修改原始 CSV 或 Excel清洗后的数据另存为新的文件。在数据表里记录来源、抓取时间、生成脚本版本。重要数据的分析结论必须能追溯。如果使用真实数据务必确认数据提供方的协议是否允许二次分析和发布。涉及运动员个人信息时还应考虑隐私保护。6.2 代码组织上面演示中数据生成、清洗分析、可视化分成了三个脚本这个分层在真实项目中更明显data_prepare.py # 数据获取与落地 analysis.py # 数据清洗与指标计算 visualize.py # 可视化输出每个脚本只负责一个环节输出的中间结果写入文件避免一个脚本里塞几百行逻辑。数据量大时还可以把计算步骤封装成函数便于单元测试。6.3 结果可解释性分析最后一定要回答原始问题。“200米好像又行了”对应的结论应该是平均成绩从 20.5 秒附近下降到 19.9 秒附近。最好成绩从 19.6 秒突破到 19.3 秒。标准差从 0.4 缩小到 0.25成绩更稳定。如果只是画了一堆图却没有给出“到底行不行”的结论分析就没有完成。这一点在工作中尤其重要业务方要的是判断和依据而不是代码本身。6.4 安全与伦理边界成绩数据看似不敏感但在实际开发中任何数据分析项目都要考虑权限问题。读取数据库时遵循最小权限原则分析结果发布前确认是否有保密要求。不要因为“只是跑一下数据”就放宽数据访问控制。7. 总结与后续可学内容回到最初的那句“200米好像又行了”现在我们有了更严谨的表述方式过去 12 年模拟数据中 200 米平均成绩呈现出轻微下降趋势说明整体水平在提升同时标准差下降说明成绩分布更集中单点最好成绩的突破进一步拉高了“变强”的观感。这一套分析路径正好对应了业务场景里常见的“某个指标最近是不是变好了”问题。本文覆盖的核心内容包括pandas 中的数据读取、缺失值处理、异常值检测、分组统计。matplotlib 中折线图、直方图、箱线图的绘制。numpy 中polyfit做一元线性回归的基本用法。数据清洗和可视化项目的基本代码组织方式。如果继续深入可以学习seaborn让图表更精致学习statsmodels做带置信区间的时间序列分解或学习plotly/pyecharts做交互式图表。把这些技能组合起来你就能复用同一套流程分析接口响应时间、服务器 CPU 使用率、用户留存率等各种“行不行”的问题。建议你动手修改几个字段把年份区间拉长、把运动员数量增多、加入伤病或训练量等字段看看趋势预测会不会发生变化。只有亲手跑过一遍才能真正理解数据清洗为什么比画图更耗时间。