Python 实战:骑行数据可视化分析(Pandas+Matplotlib)
文章目录
- 项目概述:
- 技术栈
- 代码实现
- 从fit文件中获取数据:
- 导入必要的库
- 使用matplotlib的rcParams进行全局配置
- 读取文件、将时间转换为datetime类型
- 查看基本数据信息
- 创建画布、子图和总标题
- 绘制折线图
- 绘制散点图
- 显示、保存图片
- 可视化最终效果
- 加载数据的完整代码
- 数据可视化的完整代码
- 常见问题与注意事项
- 总结与拓展
项目概述:
本文从零开始,讲解如何解析骑行设备导出的 FIT 文件,并使用 Python(fitparse、pandas、matplotlib)对骑行数据进行清洗、分析与可视化。通过生成海拔、速度、卡路里等多维度图表,帮助骑手直观了解运动表现,从而科学地调整训练策略。文章提供了完整的可运行代码,适合运动科学爱好者、数据分析初学者参考。
本文导读
技术栈
本数据分析项目使用一下Python库:
- fitparse:用于读取保存原始数据的骑行文件
- pandas:用于读取csv文件
- matplotlib:用于数据可视化
pipinstallfitparse pandas matplotlib代码实现
一、数据读取与预处理
从fit文件中获取数据:
# 读取fit文件fitfile=fitparse.FitFile("MAGENE_C506SE_2026-03-12_151516_1396807.fit")# 存储列表records=[]forrecordinfitfile.get_messages("record"):# 筛选出类型为record的数据record_data={}# 遍历record数据中的所有数据字段fordatainrecord:# 如果该字段的值不为空ifdata.valueisnotNone:# 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文件已保存")二、数据可视化
导入必要的库
importmatplotlib matplotlib.use('TkAgg')importmatplotlib.pyplotaspltimportpandasaspd
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查看数据的形状(行、列)
创建画布、子图和总标题
# 创建画布和子图 - 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()# 保存图片, 分辨率600plt.savefig('骑行数据分析.png',dpi=600,bbox_inches="tight")# 显示图片plt.show()
dpi图片的分辨率,分辨率越高,图片的线条清晰度越高bbox_inches="tight": 使用tight进行保存,裁剪空白部分
可视化最终效果
图表解读:
海拔-距离图:在约15km处有一个明显的爬坡(海拔上升),随后下降。
速度-距离图:对应爬坡段,速度明显下降,说明爬坡对速度影响显著;下坡段速度回升。
卡路里-距离图:卡路里消耗随距离增加呈上升趋势,符合运动生理学规律。
等级-距离图:等级(强度)波动较大,爬坡段等级升高,下坡段等级降低。
体温-距离图:体温总体平稳,但在高强度区间略有上升。
散点图:高等级(暖色)的点集中在较低海拔区域,可能是平路冲刺阶段;低海拔区域也有部分高等级点,可能与加速有关。
加载数据的完整代码
importfitparseimportpandasaspd# 读取fit文件fitfile=fitparse.FitFile("MAGENE_C506SE_2026-03-12_151516_1396807.fit")# 存储列表records=[]forrecordinfitfile.get_messages("record"):# 筛选出类型为record的数据record_data={}# 遍历record数据中的所有数据字段fordatainrecord:# 如果该字段的值不为空ifdata.valueisnotNone:# 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文件已保存")数据可视化的完整代码
importmatplotlib matplotlib.use('TkAgg')importmatplotlib.pyplotaspltimportpandasaspd# 设置中文字体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()# 保存图片, 分辨率600plt.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个子图的骑行分析图表,帮助骑手直观了解运动表现。
