最近看到全国田径锦标赛女子100米决赛的消息,17岁小将陈妤颉跑出11秒08的成绩夺冠,赛后的那句“感谢大家来到现场观看比赛”也让人印象很深。看到成绩数据的时候,我第一反应不是单纯感叹“好快”,而是想到另一个问题:如果我们用数据分析的视角去看这个成绩,11秒08到底意味着什么?运动员的平均速度是多少?换算成普通人熟悉的配速又是什么概念?这类问题其实很适合用Python做一次实战分析。
所以这篇文章就不只聊体育新闻,而是把“陈妤颉11秒08夺冠”这个真实成绩作为案例,带大家从零搭建一个短跑成绩数据分析小工具,包含数据读取、速度与配速计算、可视化对比、常见报错排查等完整流程。无论你是刚开始学Python的数据爱好者,还是想给运动队做成绩管理的学生,这套流程都能直接参考。
1. 背景与核心概念
1.1 为什么关注100米短跑成绩
100米短跑是田径运动中衡量“绝对速度”的经典项目,比赛结果以秒为单位,小数点后两位的差距就可能决定排名。运动员的100米成绩不仅反映起跑反应、途中加速、最大速度维持等能力,也是评判短距离爆发力的重要指标。
从数据分析角度来看,100米成绩非常适合做量化分析,因为它的数据模型很简单:距离固定为100米,成绩就是时间,速度等于距离除以时间。
所以在运动训练和赛事复盘里,教练团队经常要处理大量成绩数据,比如历次比赛成绩、起跑反应时、分段计时、风速数据等。手动用Excel处理当然可以,但一旦数据量变大,或者需要批量生成报告、做图表对比时,Python的优势就体现出来了。
1.2 11秒08这个成绩意味着什么
“11秒08”不是随便一个数字。我们可以通过最基础的物理公式来做一次拆解。
100米11秒08的平均速度约为:
[ v = \frac{100}{11.08} \approx 9.03 \text{ m/s} ]
换算成更直观的单位:
[ 9.03 \times 3.6 \approx 32.49 \text{ km/h} ]
如果换算成跑步爱好者熟悉的“每公里配速”:
[ \frac{1000}{9.03} \approx 110.8 \text{ 秒/km} ]
也就是说,如果这个速度能按恒定速度维持一公里,大概1分51秒/公里。这和普通大众跑者的配速完全不是一个量级。
需要注意的是,上述计算只是“平均速度”。实际100米比赛中,运动员不可能全程匀速,起跑阶段速度较低,途中跑达到最大速度,最后阶段还有速度保持的问题。因此平均速度只能作为整体参考,要分析更精细的加速曲线,需要依靠高速计时设备或运动捕捉系统。
1.3 运动数据分析的常见应用场景
运动数据分析并不是什么高深概念,实际工作中经常用到,常见的场景包括:
- 成绩记录与排名管理:把运动员多次比赛成绩整理成结构化数据,方便查询和比较。
- 训练效果评估:对比不同阶段的成绩变化,判断训练计划是否有效。
- 比赛复盘:结合起跑反应、分段计时等数据分析胜负差距。
- 可视化汇报:用柱状图、折线图直观展示运动员表现,给教练或管理层看。
- 成绩预测:基于历史成绩建立简单回归模型,预测下一阶段目标成绩。
本文的核心目标,是先解决最基础的数据处理和分析问题,包括读取数据、计算指标、生成图表,把这些流程打通后,后续扩展到预测和建模就比较自然了。
2. 环境准备与数据设计
2.1 运行环境与依赖
本文示例以 Python 环境为例,主要依赖以下库:
- pandas:用于读取和处理CSV数据。
- matplotlib:用于绘制对比图表。
- os:Python标准库,处理文件路径。
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。建议使用 Python 3.8 及以上版本。
如果还没有安装依赖,可以在命令行中执行:
pip install pandas matplotlib如果你的网络环境下载较慢,可以使用国内镜像源,比如清华源:
pip install pandas matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,可以验证一下导入是否正常:
import pandas as pd import matplotlib.pyplot as plt print(pd.__version__)如果这段代码能正常输出pandas版本号,说明环境已经准备好。
2.2 数据文件设计
在分析成绩之前,需要把数据整理成结构化文件。这里选择CSV格式,因为它简单、轻量、用Excel可以打开,也方便pandas直接读取。
CSV文件的列设计建议:
| 字段名 | 含义 | 示例 |
|---|---|---|
| name | 运动员姓名 | 陈妤颉 |
| event | 赛事名称 | 全国田径锦标赛 |
| time_s | 100米成绩(秒) | 11.08 |
| wind | 风速(米/秒) | 0.0 |
注意,风速对短跑成绩有实际影响,顺风超过一定限制的成绩不会被认可为有效纪录。虽然本文示例不深入风速修正,但数据结构里保留这个字段是有必要的。
2.3 示例数据说明
为了演示分析流程,除陈妤颉的成绩使用新闻公开数据外,其他选手成绩均为模拟数据,仅用于代码演示,不代表任何真实比赛结果。
示例CSV文件内容如下:
name,event,time_s,wind 陈妤颉,全国田径锦标赛,11.08,0.0 选手A,模拟赛,11.25,0.4 选手B,模拟赛,11.42,-0.2 选手C,模拟赛,11.50,0.3为什么其他选手用“选手A、选手B、选手C”而不是真实姓名?因为本文只确定陈妤颉的成绩来自公开新闻,其余数据若使用真实姓名容易造成数据不准确或者误导。在真实业务中,你应当使用授权或公开可查的数据,并记录数据来源。
3. 成绩数据的核心计算方法
3.1 平均速度计算
平均速度是短跑成绩分析中最基础的指标。公式非常简单:
速度 = 距离 / 时间在100米项目中,距离固定为100米,时间就是成绩。
用Python实现:
def avg_speed(distance_m, time_s): """ 计算平均速度,单位:米/秒 """ return distance_m / time_s调用方式:
speed = avg_speed(100, 11.08) print(f"平均速度:{speed:.2f} 米/秒")输出:
平均速度:9.03 米/秒这里有个细节需要注意:11.08在Python中会被当作浮点数处理,不会出现整数除法导致结果为0的问题。但如果你从文本文件或Excel中读取时间字段,要确保列类型是数值型,否则后续计算会报错。
3.2 单位换算与配速
很多跑者习惯用“每公里配速”来衡量速度,这就需要对单位做换算。
米/秒转换为公里/小时,乘以3.6:
def speed_to_kmh(speed_ms): return speed_ms * 3.6计算每公里配速(秒/公里):
def speed_to_pace(speed_ms): return 1000 / speed_ms把秒数格式化成“分:秒”展示:
def format_pace(pace_sec): minutes = int(pace_sec // 60) seconds = int(pace_sec % 60) return f"{minutes}分{seconds}秒"以11.08秒为例:
speed = 100 / 11.08 # 约9.03 kmh = speed * 3.6 # 约32.49 pace = 1000 / speed # 约110.8 print(f"速度:{kmh:.2f} km/h") print(f"配速:{format_pace(pace)}")输出:
速度:32.49 km/h 配速:1分51秒配速的单位是“秒/公里”,数值越小速度越快。
3.3 分段速度分析思路
如果想分析运动员是起跑快还是冲刺快,单靠总成绩是不够的,需要分段计时数据。比如每10米一个计时点,然后计算各段的平均速度。
在只有总成绩的情况下,可以做一个理论上的匀速分段模型:
def calc_uniform_split(total_time, total_distance=100, split_distance=10): """ 按匀速模型计算每段用时,单位:秒 """ segment_count = total_distance // split_distance segment_time = total_time / segment_count return [segment_time] * segment_count调用:
splits = calc_uniform_split(11.08) print(splits)输出:
[1.108, 1.108, 1.108, 1.108, 1.108, 1.108, 1.108, 1.108, 1.108, 1.108]这个结果只是基于匀速假设的理论值。实际100米比赛的速度曲线通常是“起跑慢、途中快、冲刺略有下降”,所以分段分析必须依赖现场计时数据,不能直接用总成绩平均。
在项目落地时,建议先明确分析目标:如果你只有总成绩,就只做整体指标计算;如果你有分段数据,再考虑分段速度分析。不要用理论模型过度推测真实表现。
3.4 常见误区
先总结几个容易踩的坑,后文会在实战中进一步验证:
- 忘记处理空值和文本格式:CSV中成绩列可能混入“11’08”之类的文本,需要清洗。
- 直接使用原始列表计算:数据量大时性能差,也不便于筛选和分组计算。
- 图表中文乱码:matplotlib默认字体不支持中文,需要配置中文字体。
- 混淆“风”速字段:顺风可能提高成绩,需要在盘点数据源时注意是否已包含风速修正。
4. 完整实战案例:短跑成绩数据分析小工具
下面进入本文的核心部分。我们将从零搭建一个短跑成绩分析小工具,完成数据读取、指标计算、可视化对比的完整流程。
4.1 创建项目结构
先创建如下目录结构:
sprint-analysis/ ├── data/ │ └── sprint_data.csv ├── output/ └── sprint_analysis.pydata目录存放原始数据,output目录存放生成的图表或报告,sprint_analysis.py是主脚本。
在data/sprint_data.csv中写入上文的示例数据:
name,event,time_s,wind 陈妤颉,全国田径锦标赛,11.08,0.0 选手A,模拟赛,11.25,0.4 选手B,模拟赛,11.42,-0.2 选手C,模拟赛,11.50,0.3注意CSV的编码建议使用 UTF-8 或 UTF-8-SIG,避免中文乱码。Windows 环境下使用 Excel 打开时,UTF-8-SIG 兼容性更好。
4.2 编写数据读取与检查代码
在sprint_analysis.py中写入以下代码:
# -*- coding: utf-8 -*- """ 短跑成绩数据分析小工具 """ import os import pandas as pd def load_data(csv_path): """ 读取CSV文件,返回DataFrame """ df = pd.read_csv(csv_path, encoding='utf-8-sig') return df def main(): base_dir = os.path.dirname(__file__) csv_path = os.path.join(base_dir, 'data', 'sprint_data.csv') df = load_data(csv_path) print('原始数据:') print(df) print(f'\n数据形状:{df.shape}') print(f'成绩列类型:{df["time_s"].dtype}') if __name__ == '__main__': main()这段代码先定位CSV文件路径,然后读取数据,并打印基本信息和时间列类型。df.shape显示数据是几行几列,dtype用来确认成绩列是否为数值类型。
运行方式:
python sprint_analysis.py预期输出:
原始数据: name event time_s wind 0 陈妤颉 全国田径锦标赛 11.08 0.0 1 选手A 模拟赛 11.25 0.4 2 选手B 模拟赛 11.42 -0.2 3 选手C 模拟赛 11.50 0.3 数据形状:(4, 4) 成绩列类型:float64如果这一步正常,说明数据读取成功。
4.3 编写速度与配速计算函数
接下来增加计算逻辑。这里把每个功能封装成独立函数,方便复用和扩展。
def avg_speed(distance_m, time_s): """ 计算平均速度,单位:米/秒 """ return distance_m / time_s def speed_to_kmh(speed_ms): """ 米/秒 转 公里/小时 """ return speed_ms * 3.6 def speed_to_pace(speed_ms): """ 根据米/秒转换为每公里配速,单位:秒/公里 """ return 1000 / speed_ms def format_pace(pace_sec): """ 将配速格式化为 分:秒 """ minutes = int(pace_sec // 60) seconds = int(pace_sec % 60) return f"{minutes}分{seconds}秒" def add_metrics(df, distance_m=100): """ 在原DataFrame上新增速度、配速等指标列 """ df = df.copy() df['speed_ms'] = df['time_s'].apply(lambda t: avg_speed(distance_m, t)) df['speed_kmh'] = df['speed_ms'].apply(speed_to_kmh) df['pace_sec_per_km'] = df['speed_ms'].apply(speed_to_pace) df['pace_str'] = df['pace_sec_per_km'].apply(format_pace) return df这段代码新增了四列:
speed_ms:平均速度,单位米/秒。speed_kmh:平均速度,单位公里/小时。pace_sec_per_km:每公里配速,单位秒/公里。pace_str:配速的友好展示。
使用df.copy()是为了避免修改原始DataFrame,这是一种好的编码习惯。
4.4 多成绩对比与可视化
为了直观对比陈妤颉与其他模拟选手的成绩差异,使用matplotlib绘制柱状图。
完整代码:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC'] plt.rcParams['axes.unicode_minus'] = False def plot_speed(df, save_path=None): """ 绘制平均速度对比柱状图 """ fig, ax = plt.subplots(figsize=(8, 5)) bars = ax.bar(df['name'], df['speed_kmh'], color='#4C72B0') for bar, v in zip(bars, df['speed_kmh']): ax.text( bar.get_x() + bar.get_width() / 2, v + 0.15, f"{v:.2f}", ha='center', va='bottom' ) ax.set_ylabel('平均速度(km/h)') ax.set_title('女子100米短跑平均速度对比') ax.grid(axis='y', alpha=0.3) if save_path: plt.savefig(save_path, dpi=120, bbox_inches='tight') plt.show()这里有一个细节:执行plt.show()前先调用plt.savefig()保存图片。如果保存路径的目录不存在,需要先创建目录。
4.5 完整代码与运行结果
把上面所有函数汇总到一起,并增加输出表格的功能。
# -*- coding: utf-8 -*- """ 短跑成绩数据分析小工具 功能:读取短跑成绩数据,计算平均速度、配速,并生成对比图 """ import os import pandas as pd import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC'] plt.rcParams['axes.unicode_minus'] = False def load_data(csv_path): df = pd.read_csv(csv_path, encoding='utf-8-sig') return df def avg_speed(distance_m, time_s): return distance_m / time_s def speed_to_kmh(speed_ms): return speed_ms * 3.6 def speed_to_pace(speed_ms): return 1000 / speed_ms def format_pace(pace_sec): minutes = int(pace_sec // 60) seconds = int(pace_sec % 60) return f"{minutes}分{seconds}秒" def add_metrics(df, distance_m=100): df = df.copy() df['speed_ms'] = df['time_s'].apply(lambda t: avg_speed(distance_m, t)) df['speed_kmh'] = df['speed_ms'].apply(speed_to_kmh) df['pace_sec_per_km'] = df['speed_ms'].apply(speed_to_pace) df['pace_str'] = df['pace_sec_per_km'].apply(format_pace) return df def plot_speed(df, save_path=None): fig, ax = plt.subplots(figsize=(8, 5)) bars = ax.bar(df['name'], df['speed_kmh'], color='#4C72B0') for bar, v in zip(bars, df['speed_kmh']): ax.text( bar.get_x() + bar.get_width() / 2, v + 0.15, f"{v:.2f}", ha='center', va='bottom' ) ax.set_ylabel('平均速度(km/h)') ax.set_title('女子100米短跑平均速度对比') ax.grid(axis='y', alpha=0.3) if save_path: plt.savefig(save_path, dpi=120, bbox_inches='tight') plt.show() def main(): base_dir = os.path.dirname(__file__) csv_path = os.path.join(base_dir, 'data', 'sprint_data.csv') output_dir = os.path.join(base_dir, 'output') os.makedirs(output_dir, exist_ok=True) df = load_data(csv_path) print('原始数据:') print(df) df = add_metrics(df, distance_m=100) print('\n计算后的数据:') print(df[['name', 'time_s', 'speed_ms', 'speed_kmh', 'pace_str']]) chart_path = os.path.join(output_dir, 'speed_compare.png') plot_speed(df, save_path=chart_path) print(f'\n图表已保存到:{chart_path}') if __name__ == '__main__': main()运行:
python sprint_analysis.py预期控制台输出:
原始数据: name event time_s wind 0 陈妤颉 全国田径锦标赛 11.08 0.0 1 选手A 模拟赛 11.25 0.4 2 选手B 模拟赛 11.42 -0.2 3 选手C 模拟赛 11.50 0.3 计算后的数据: name time_s speed_ms speed_kmh pace_str 0 陈妤颉 11.08 9.025271 32.490974 1分51秒 1 选手A 11.25 8.888889 32.000000 1分53秒 2 选手B 11.42 8.756568 31.523645 1分54秒 3 选手C 11.50 8.695652 31.304348 1分55秒 图表已保存到:.../output/speed_compare.png这样,完整的分析流程就打通了。从数据读取到指标计算再到可视化输出,全程只需要一个Python脚本。
5. 常见问题与排查思路
5.1 常见问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 中文文字读取后出现乱码 | CSV编码不是UTF-8 | 使用encoding='utf-8-sig'读取,或统一保存为UTF-8 |
| matplotlib输出图表中文乱码 | 系统缺少中文字体或未配置字体 | 设置plt.rcParams['font.sans-serif'] |
读取CSV报FileNotFoundError | 路径拼接错误或文件不存在 | 检查目录结构,优先使用os.path.join构建路径 |
| 计算时报错:字符串不能减字符串 | 时间列是文本类型 | 使用pd.to_numeric()或清理特殊字符 |
| 图表保存后图片空白 | show()执行后图形被重置 | 先savefig再show |
5.2 详细排查:时间列类型问题
假设你从某系统导出的CSV中,成绩列的值长这样:11'08,也就是用了单引号代替小数点。此时pandas读取后该列可能是字符串类型,计算会直接报错。
推荐的处理方式:
df['time_s'] = df['time_s'].astype(str).str.replace("'", ".").astype(float)这里先强制转成字符串,再替换单引号为小数点,最后转成浮点数。实际清洗时,建议先打印df.dtypes确认各列类型。
5.3 详细排查:CSV路径找不到
路径报错很多时候是“当前工作目录”和“脚本所在目录”不一致导致的。最稳妥的方法是用os.path.dirname(__file__)获取脚本所在目录,再拼接相对路径。
base_dir = os.path.dirname(__file__) csv_path = os.path.join(base_dir, 'data', 'sprint_data.csv')这样不管你在哪个目录下运行脚本,都能正确定位文件。
5.4 详细排查:matplotlib中文乱码
Windows系统一般能用SimHei,macOS 可以用PingFang SC,Linux 建议安装WenQuanYi字体。如果所有字体都无效,可以先用字体管理器查看系统已安装字体:
import matplotlib.font_manager as fm fonts = [f.name for f in fm.fontManager.ttflist] print(fonts)然后从列表里选择一个合适的中文字体,再设置。
6. 最佳实践与工程建议
6.1 数据文件规范
成绩数据的CSV文件建议遵循以下规范:
- 文件名用英文小写加下划线,例如
sprint_data.csv,避免中文文件名在部分环境下出现编码问题。 - 表头统一使用英文小写,字段名见名知意。
- 每行代表一条独立记录,不要在一个单元格里写多个成绩。
- 原始数据单独保存,不要在清洗过程中覆盖原文件。
比如可以增加一列source记录数据来源,方便追溯:
name,event,time_s,wind,source 陈妤颉,全国田径锦标赛,11.08,0.0,公开新闻数据这在真实项目中几乎是最重要的习惯:数据来源可追溯,分析结果才能被信任。
6.2 代码封装与复用
不要把所有逻辑都堆在main()函数里。建议每个功能拆成独立函数,比如load_data、add_metrics、plot_speed。这样如果后续要增加“按赛事筛选”或“成绩预测”功能,只需要新增对应函数,不影响已有代码。
另外,距离distance_m这种参数可以设计成函数参数,默认值是100。如果将来自动化分析200米成绩,只需要调用时传入distance_m=200。
6.3 图表输出与报告生成
图表保存时,建议指定dpi和bbox_inches='tight':
plt.savefig(chart_path, dpi=120, bbox_inches='tight')dpi=120能保证图片清晰,bbox_inches='tight'可以避免坐标轴标签被截断。
如果分析结果要发给教练组,可以把整理后的DataFrame导出为CSV或Excel:
df.to_csv('analysis_result.csv', index=False, encoding='utf-8-sig')需要输出Excel时,可以安装openpyxl:
pip install openpyxl然后:
df.to_excel('analysis_result.xlsx', index=False)6.4 数据准确性与边界处理
短跑成绩分析虽然公式简单,但在真实场景中要注意几点:
- 风速字段不能忽略,国际田联对顺风风速超过2米/秒的成绩有特殊规定。
- 时间精度通常保留两位小数,计算中间量不要提前四舍五入,最后展示再格式化。
- 如果成绩列存在空值,要先决定是删除还是填充,不要带着NaN直接计算。
- 如果数据来自多人录入,要统一单位格式,避免出现
11.08和11.080混用的问题。
7. 后续可以继续学习的方向
文章篇幅有限,但顺着这个案例,你可以继续深入的方向其实很多。
第一个方向是“分段速度分析”。如果有一天你拿到了每10米的分段时间,就可以绘制速度曲线,分析运动员是否在某个阶段掉速,这对训练调整很有价值。
第二个方向是“成绩预测”。你可以收集同一运动员多次比赛成绩,使用线性回归或时间序列模型,预测下一场比赛的目标区间。当然,体育比赛受状态、天气、赛道等因素影响很大,预测结果只能作为参考。
第三个方向是“自动化报告”。把脚本接入定时任务,每次比赛结束后自动读取成绩、生成图表、发送邮件或钉钉消息,就是一套轻量级的运动数据工作流。
数据分析的真正价值,不在于把一个数字换成另一个数字,而在于把零散的记录转化为可理解、可对比、可决策的信息。如果你也有自己关心的运动成绩数据,建议从今天开始建一个简单的CSV文件,用这套流程跑一遍。
遇到问题不要急,按照“先检查数据,再检查代码,最后检查环境”的顺序排查,大部分情况都能定位到原因。希望这篇文章能帮你少走一点弯路。