项目概述:

本文从零开始,讲解如何解析骑行设备导出的 FIT 文件,并使用 Python(fitparse、pandas、matplotlib)对骑行数据进行清洗、分析与可视化。通过生成海拔、速度、卡路里等多维度图表,帮助骑手直观了解运动表现,从而科学地调整训练策略。文章提供了完整的可运行代码,适合运动科学爱好者、数据分析初学者参考。
本文导读

技术栈

本数据分析项目使用一下Python库:

  • fitparse:用于读取保存原始数据的骑行文件
  • pandas:用于读取csv文件
  • matplotlib:用于数据可视化
pip install fitparse pandas matplotlib

代码实现

一、数据读取与预处理

从fit文件中获取数据:

# 读取fit文件
fitfile = fitparse.FitFile("MAGENE_C506SE_2026-03-12_151516_1396807.fit")

# 存储列表
records = []
for record in fitfile.get_messages("record"):  # 筛选出类型为record的数据
    record_data = {}
    # 遍历record数据中的所有数据字段
    for data in record:
        # 如果该字段的值不为空
        if data.value is not None:
            # data.name: 字段名
            # data.value: 字段值
            record_data[data.name] = data.value
    # 添加到列表中
    records.append(record_data)

2.保存在csv文件中

# 转换为dataframe, 方便存储
df = pd.DataFrame(records)
df.to_csv('骑行数据.csv', index=False, encoding='utf-8-sig')
print("骑行数据.csv文件已保存")

二、数据可视化

导入必要的库

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import pandas as pd

matplotlib.use('TkAgg'):设置matplotlib使用TkAgg后端,在Tkinter界面显示图形

使用matplotlib的rcParams进行全局配置

①设置中文字体

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft Yahei', 'DejaVu Sans']

②解决负号显示问题

# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False

plt.rcParams['axes.unicode_minus']设置为False的时候,matplotlib会采取ASCII显示负号

读取文件、将时间转换为datetime类型

# 读取文件
data = pd.read_csv('骑行数据.csv', encoding='utf-8-sig')

# 将“时间”列转换为 datetime 类型
data['时间'] = pd.to_datetime(data['时间'], format='%Y/%m/%d %H:%M', errors='coerce')

errors中的参数设置为coerce,当无法显示时间时,会表示为NAT或者NaN

查看基本数据信息

print(data.head(5))
print(data.info())
print(data.shape)

data.head(5)查看前五行的字段和数据信息
data.info()查看数据的摘要信息
data.shape查看数据的形状(行、列)

图1 数据前5行展示,可见主要字段包括时间、距离、海拔等

创建画布、子图和总标题

# 创建画布和子图 - 3行2列的子图, subplots函数返回两个参数
fig, axes = plt.subplots(3, 2, figsize=(14, 12))

# 总标题
plt.suptitle('骑行数据分析', fontsize=20)

figsize(14, 12)设置图形的大小,14:宽, 12: 高

绘制折线图

# 1. 海拔vs距离
# 设置子图在布局中的位置 - 首行首列
ax1 = axes[0, 0]
# 绘制折线图
ax1.plot(data['距离'], data['海拔'], color="blue", linewidth=1)
# 设置x轴
ax1.set_xlabel('距离 (km)', fontsize=9)
# 设置y轴
ax1.set_ylabel('海拔 (m)', fontsize=9)
# 设置标题
ax1.set_title('海拔-距离')
# 添加网格线
ax1.grid(True, alpha=0.6, linestyle='--')

# 2. 速度vs距离
# 设置在布局中的位置
ax2 = axes[0, 1]
# 绘制折线图
ax2.plot(data['距离'], data['速度'], color="red", linewidth=1)
# 设置x轴
ax2.set_xlabel('距离 (km)', fontsize=9)
# 设置y轴
ax2.set_ylabel('速度 (km/h)', fontsize=9)
# 设置标题
ax2.set_title('速度-距离')
# 添加网格线
ax2.grid(True, alpha=0.6, linestyle='--')

# 3. 卡路里vs距离
ax3 = axes[1, 0]
ax3.plot(data['距离'], data['卡路里'], color="green", linewidth=1)
ax3.set_xlabel('距离 (km)', fontsize=9)
ax3.set_ylabel('卡路里 (kcal)', fontsize=9)
ax3.set_title('卡路里-距离')
ax3.grid(True, alpha=0.6, linestyle='--')

# 4. 等级vs距离
ax4 = axes[1, 1]
ax4.plot(data['距离'], data['等级'], color="black", linewidth=1)
ax4.set_xlabel('距离 (km)', fontsize=9)
ax4.set_ylabel('等级', fontsize=9)
ax4.set_title('等级-距离')
ax4.grid(True, alpha=0.6, linestyle='--')

# 5. 体温vs距离
ax5 = axes[2, 0]
ax5.plot(data['距离'], data['体温'], color="purple", linewidth=1)
ax5.set_xlabel('距离 (km)', fontsize=9)
ax5.set_ylabel('体温 (°C)', fontsize=9)
ax5.set_title('体温-距离')
ax5.grid(True, alpha=0.6, linestyle='--')

axes[行, 列] 子图在整个图片中的位置设置,行和列都是从0开始

ax1.plot(data['距离'], data['海拔'], color="blue", linewidth=1)
x轴数据源:data[‘距离’]
y轴数据源:data[‘海拔’]
color=“blue”: 设置线条颜色为蓝色
linewidth: 设置线条宽度

ax1.set_xlabel('距离', fontsize=9) 设置x轴的标题
ax1.set_ylabel('海拔', fontsize=9) 设置y轴的标题
ax1.set_title('海拔-距离') 设置子图的标题

ax1.grid(True, alpha=0.6, linestyle='--') True:显示网格线 alpha:透明度 linestyle:线条样式

绘制散点图

# 6. 速度vs海拔散点图
ax6 = axes[2, 1]
# 散点图 - c:按强度等级着色
sc = ax6.scatter(data['速度'], data['海拔'], c=data['等级'], cmap='coolwarm', s=20, alpha=0.6)
# 设置x轴
ax6.set_xlabel('速度 (km/h)', fontsize=9)
# 设置y轴
ax6.set_ylabel('海拔', fontsize=9)
# 设置标题
ax6.set_title('速度-海拔散点图')
# 添加网格线
ax6.grid(True, alpha=0.6, linestyle='--')

scatter(x, y, c, cmap, s, alpha)
x: x轴的数据源
y: y轴的数据源
c: 按强度等级着色
cmap:散点的颜色
s:散点的大小
alpha:透明度

显示、保存图片

# 自动调节子参数
plt.tight_layout()

# 保存图片, 分辨率600
plt.savefig('骑行数据分析.png', dpi=600, bbox_inches="tight")

# 显示图片
plt.show()

dpi 图片的分辨率,分辨率越高,图片的线条清晰度越高
bbox_inches="tight": 使用tight进行保存,裁剪空白部分

可视化最终效果

图2 骑行数据分析结果:海拔、速度、卡路里等随距离变化趋势

图表解读:

  1. 海拔-距离图:在约15km处有一个明显的爬坡(海拔上升),随后下降。

  2. 速度-距离图:对应爬坡段,速度明显下降,说明爬坡对速度影响显著;下坡段速度回升。

  3. 卡路里-距离图:卡路里消耗随距离增加呈上升趋势,符合运动生理学规律。

  4. 等级-距离图:等级(强度)波动较大,爬坡段等级升高,下坡段等级降低。

  5. 体温-距离图:体温总体平稳,但在高强度区间略有上升。

  6. 散点图:高等级(暖色)的点集中在较低海拔区域,可能是平路冲刺阶段;低海拔区域也有部分高等级点,可能与加速有关。

加载数据的完整代码

import fitparse
import pandas as pd

# 读取fit文件
fitfile = fitparse.FitFile("MAGENE_C506SE_2026-03-12_151516_1396807.fit")

# 存储列表
records = []
for record in fitfile.get_messages("record"):  # 筛选出类型为record的数据
    record_data = {}
    # 遍历record数据中的所有数据字段
    for data in record:
        # 如果该字段的值不为空
        if data.value is not None:
            # data.name: 字段名
            # data.value: 字段值
            record_data[data.name] = data.value
    # 添加到列表中
    records.append(record_data)

# 转换为dataframe, 方便存储
df = pd.DataFrame(records)
df.to_csv('骑行数据.csv', index=False, encoding='utf-8-sig')
print("骑行数据.csv文件已保存")

数据可视化的完整代码

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import pandas as pd

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft Yahei', 'DejaVu Sans']
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False

# 读取文件
data = pd.read_csv('骑行数据.csv', encoding='utf-8-sig')

# 将“时间”列转换为 datetime 类型
data['时间'] = pd.to_datetime(data['时间'], format='%Y/%m/%d %H:%M', errors='coerce')

# 查看数据基本信息 - 前5行数据、摘要信息、形状
print(data.head(5))
print(data.info())
print(data.shape)

# 创建画布和子图 - 3行2列的子图, subplots函数返回两个参数
fig, axes = plt.subplots(3, 2, figsize=(14, 12))

# 总标题
plt.suptitle('骑行数据分析', fontsize=20)

# 1. 海拔vs距离
# 设置子图在布局中的位置 - 首行首列
ax1 = axes[0, 0]
# 绘制折线图
ax1.plot(data['距离'], data['海拔'], color="blue", linewidth=1)
# 设置x轴
ax1.set_xlabel('距离 (km)', fontsize=9)
# 设置y轴
ax1.set_ylabel('海拔 (m)', fontsize=9)
# 设置标题
ax1.set_title('海拔-距离')
# 添加网格线
ax1.grid(True, alpha=0.6, linestyle='--')

# 2. 速度vs距离
# 设置在布局中的位置
ax2 = axes[0, 1]
# 绘制折线图
ax2.plot(data['距离'], data['速度'], color="red", linewidth=1)
# 设置x轴
ax2.set_xlabel('距离 (km)', fontsize=9)
# 设置y轴
ax2.set_ylabel('速度 (km/h)', fontsize=9)
# 设置标题
ax2.set_title('速度-距离')
# 添加网格线
ax2.grid(True, alpha=0.6, linestyle='--')

# 3. 卡路里vs距离
ax3 = axes[1, 0]
ax3.plot(data['距离'], data['卡路里'], color="green", linewidth=1)
ax3.set_xlabel('距离 (km)', fontsize=9)
ax3.set_ylabel('卡路里 (kcal)', fontsize=9)
ax3.set_title('卡路里-距离')
ax3.grid(True, alpha=0.6, linestyle='--')

# 4. 等级vs距离
ax4 = axes[1, 1]
ax4.plot(data['距离'], data['等级'], color="black", linewidth=1)
ax4.set_xlabel('距离 (km)', fontsize=9)
ax4.set_ylabel('等级', fontsize=9)
ax4.set_title('等级-距离')
ax4.grid(True, alpha=0.6, linestyle='--')

# 5. 体温vs距离
ax5 = axes[2, 0]
ax5.plot(data['距离'], data['体温'], color="purple", linewidth=1)
ax5.set_xlabel('距离 (km)', fontsize=9)
ax5.set_ylabel('体温 (°C)', fontsize=9)
ax5.set_title('体温-距离')
ax5.grid(True, alpha=0.6, linestyle='--')

# 6. 速度vs海拔散点图
ax6 = axes[2, 1]
# 散点图 - c:按强度等级着色
sc = ax6.scatter(data['速度'], data['海拔'], c=data['等级'], cmap='coolwarm', s=20, alpha=0.6)
# 设置x轴
ax6.set_xlabel('速度 (km/h)', fontsize=9)
# 设置y轴
ax6.set_ylabel('海拔', fontsize=9)
# 设置标题
ax6.set_title('速度-海拔散点图')
# 添加网格线
ax6.grid(True, alpha=0.6, linestyle='--')

# 自动调节子参数
plt.tight_layout()

# 保存图片, 分辨率600
plt.savefig('骑行数据分析.png', dpi=600, bbox_inches="tight")

# 显示图片
plt.show()

常见问题与注意事项

  • 文件路径错误
    请确保FIT文件存在于当前工作目录,或使用绝对路径。建议将文件放在与脚本相同的文件夹中。

  • 中文显示乱码
    如果图表中的中文显示为方框,说明系统中没有代码中指定的字体(如SimHei)。可以安装相应字体,或更换为系统已有的中文字体(如 ‘Arial Unicode MS’ on macOS)。

  • 时间转换失败
    如果CSV中的时间格式与代码中的 format 不匹配,errors=‘coerce’ 会将无效时间设为 NaT。请检查原始数据的时间格式,必要时调整 format 参数。

  • 图片保存为空白
    如果在 plt.show() 之后调用 plt.savefig(),可能保存空白图片。请确保 savefig 在 show 之前,或使用 plt.savefig 后不调用 show。

总结与拓展

本文从零开始,演示了如何解析FIT文件、保存为CSV,并使用Matplotlib进行多维度数据可视化。最终生成包含6个子图的骑行分析图表,帮助骑手直观了解运动表现。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐