fitanalysis is a Python library for analysis of ANT/Garmin .fit
files.
It's geared toward cycling and allows for easy extraction of data such as the
following from a .fit
file:
- elapsed time
- moving time
- average heart rate
- average power
- normalized power (based on information publicly available about TrainingPeaks' NP®)
- intensity (based on information publicly available about TrainingPeaks' IF®)
- training stress (based on information publicly available about TrainingPeaks' TSS®)
My impetus for this project was to better understand how platforms like TrainingPeaks analyze power and heart rate data to arrive at an estimation of training stress. As such, this project attempts to match those platforms' calculations as closely as possible.
Pandas, NumPy, and fitparse are required.
python setup.py install
(or python setup.py install --user
) to install.
fitanalysis provides the Activity
class.
import fitanalysis
activity = fitanalysis.Activity('my_activity.fit')
print activity.elapsed_time
print activity.moving_time
# Also available for heart rate and cadence
print activity.mean_power
print activity.norm_power
# Intensity and training stress calculations require
# a functional threshold power value (in Watts)
print activity.intensity(310)
print activity.training_stress(310)
Construction of an Activity
parses the .fit
file and detects periods of
inactivity, as such periods must be removed from the data for heart rate-,
cadence-, and power-based calculations.
Here is a comparison for a few of my rides of varying profiles across the various platforms.
fitanalysis | TrainingPeaks | Garmin Connect | Strava | ||
---|---|---|---|---|---|
Ride 1: epic ride 126.5 mi 15207 ft climbing |
|||||
Elapsed time | 12:19:40 | * | 12:19:20 | 12:19:40 | |
Moving time | 9:07:14 | - | 9:06:12 | 9:09:26 | |
Mean power | 182 W | 183 W | 183 W | 183 W | |
Norm. power | 232 W | 232 W | 232 W | - | |
Intensity | 0.74 | 0.74 | 0.74 | - | |
Training stress | 504.0 | 505.1 | 503.2 | - | |
Ride 2: interval workout 11.9 mi 1352 ft climbing |
|||||
Elapsed time | 1:32:34 | * | 1:32:34 | 1:32:34 | |
Moving time | 57:17 | - | 57:11 | 57:51 | |
Mean power | 172 W | 168 W | 168 W | 172 W | |
Norm. power | 289 W | 286 W | 287 W | - | |
Intensity | 0.93 | 0.92 | 0.92 | - | |
Training stress | 81.7 | 82.3 | 83.1 | - | |
Ride 3: tempo 25.4 mi 2451 ft climbing |
|||||
Elapsed time | 2:09:02 | 2:08:58 | 2:08:58 | 2:09:02 | |
Moving time | 1:32:39 | - | 1:32:23 | 1:32:43 | |
Mean power | 201 W | 201 W | 201 W | 202 W | |
Norm. power | 270 W | 269 W | 270 W | - | |
Intensity | 0.86 | 0.86 | 0.87 | - | |
Training stress | 115.3 | 114.1 | 115.1 | - | |
Ride 4: "coffee pace" 13.4 mi 902 ft climbing |
|||||
Elapsed time | 1:41:24 | 1:41:23 | 1:41:23 | 1:41:24 | |
Moving time | 57:15 | - | 57:02 | 57:23 | |
Mean power | 138 W | 139 W | 139 W | 139 W | |
Norm. power | 251 W | 252 W | 252 W | - | |
Intensity | 0.80 | 0.81 | 0.81 | - | |
Training stress | 61.6 | 61.6 | 61.2 | - |
- Data not available on this platform
* Didn't calculate. TrainingPeaks doesn't directly report elapsed time so it has to be manually summed from lap durations, and these rides have lots of laps.
- Garmin Connect is the most aggressive when calculating moving time, Strava is the most lenient, and fitanalysis falls in between.
- Mean power calculated by fitanalysis is at most 1 W different than mean power calculated by another platform.
- Normalized power calculated by fitanalysis is at most 2 W different than normalized power calculated by another platform.
- Training stress calculated by fitanalysis corresponds well to other platforms across a large range.
All of the activities in the table above were recorded with autopause enabled, so they don't highlight any differences in how each platform handles long periods of inactivity. To test this I recorded a ride with autopause disabled, and then used fitanalysis to analyze it in two ways: detecting and removing periods of inactivity (the default for fitanalysis), and leaving the data as-is. This activity includes a 2-minute period of inactivity, in addition to shorter stops e.g. at stop lights.
fitanalysis (inactivity removed) |
fitanalysis (inactivity not removed) |
TrainingPeaks | Garmin Connect | Strava | |
---|---|---|---|---|---|
Elapsed time | 34:54 | 34:54 | 34:54 | 34:54 | 34:54 |
Moving time | 30:48 | 34:54 | - | 30:57 | 31:12 |
Mean power | 247 W | 219 W | 220 W | 220 W | 248 W |
Norm. power | 279 W | 271 W | 272 W | 272 W | - |
Intensity | 0.89 | 0.87 | 0.87 | 0.87 | - |
Training stress | 41.1 | 43.8 | 43.6 | 43.7 | - |
Average power with periods of inactivity removed matches Strava's average power, but not TrainingPeaks or Garmin Connect. They calculate average power from the raw data.
TrainingPeaks and Garmin Connect also calculate normalized power from the raw data.
Garmin Connect does calculate moving time but it appears not to use it for the power calculations. If inactivity isn't removed from the power data then elapsed time should indeed be used for consistency, but the choice to remove the inactivity for the purpose of moving time calculation and not do so for power is puzzling.
Because Strava removes inactivity for power calculations, both approaches seem to be accepted. It's my opinion that removing inactivity is the correct approach because, depending on the length of inactivity, not doing so can lead to an inflated or deflated estimation of the effort during periods of activity. One counter-argument I can see is for structured workouts: it may be desirable to include the rest periods in calculations of intensity and training stress because in this case the length of the rest is deliberately chosen as part of the workout. Perhaps this is the reason for TrainingPeaks' implementation?
This is only one data point, so looking at some more rides would be interesting, but one takeaway from this example is this: want to inflate your TSS? Try disabling autopause (and don't take really long breaks, but apparently moderately long breaks are fine).
Coggan, Andrew. (2012, June 20). Calculate Normalised Power for an Interval. [Forum comment]. Retrieved June 14, 2017, from http://www.timetriallingforum.co.uk/index.php?/topic/69738-calculate-normalised-power-for-an-interval/&do=findComment&comment=978386
Coggan, Andrew. (2016, February 10). Normalized Power, Intensity Factor and Training Stress Score. Retrieved June 14, 2017, from https://www.trainingpeaks.com/blog/normalized-power-intensity-factor-training-stress/
Coggan, Andrew. (2003, March 13). TSS and IF - at last! Retrieved June 14, 2017, from http://lists.topica.com/lists/wattage/read/message.html?mid=907028398&sort=d&start=9353
Eckner, Andreas. (2017, April 3). Algorithms for Unevenly Spaced Time Series: Moving Averages and Other Rolling Operators. Retrieved June 14, 2017, from http://eckner.com/papers/Algorithms%20for%20Unevenly%20Spaced%20Time%20Series.pdf
Friel, Joe. (2009, Sept 21). Estimating Training Stress Score (TSS). Retrieved June 22, 2017, from https://www.trainingpeaks.com/blog/estimating-training-stress-score-tss/
This project is licensed under the MIT License. See LICENSE file for details.