It’s a good question. GPS is pretty noising for GAP and its annoying when my best efforts seem to be mostly fake from GPS jumping around where I look at GAP.
But I personally would really love to see the following:
GAP based efficiency / hr/pace zone 2
No 1 above but trended over multiple weeks
GAP decoupling interesting but I personally don’t look at it much in cycling. Think I’d be more interested in 1 and 2 above because I’m just looking to see improvement overtime.
As I am adding miles I expect decoupling to remain or get worse. It also would skew 1 and 2… Would want really to trend my pre-decoupled data to see that I’m getting better when fresh and to trend where decoupling starts to see that my fatigue point is moving after more minutes running.
I don’t have any power for running but would love trending Joe Friels suggestion:
Aerobic Threshold 2 “If you are also using a GPS device, when the workout is over, divide the normalized graded pace (NGP) for the AeT [aerobic threshold] portion by your average heart rate for the same portion to find your efficiency factor (EF) for this session. An increasing EF over time indicates that your aerobic fitness is improving.”
This is super clunky to figure out, but with the decoupling feature you can set the warm up and cool down so possibly have a number automatically calculated for the EF for that activity and then trend it on the fitness page.
That is Efficiency and decoupling in the same way as you do it for power.
EF is Speed/HRavg. But I think it is better to take Avg speed iso Normalized because normalized accentuates high peaks and if you have GPS troubles, Avg will much more reflect what you’re looking for. Normalized speed will be inflated when GPS is less good.
Decoupling based on speed is then 1 - EF second half/ EF first half. Multiply by 100 to get %.
One way to use it is to determine how long you can go before exceeding 5%. Then work same HR to increase time before decoupling. Speed and time before decoupling should go up if you train consistently. HR remains the same, preferably close to but lower then AeT.
I agree! I think you would be suprised by how many people compare their speed for Z2 rides or runs. Basically everyone without a powermeter I reckon. I live in the flattest country in Europe, so a speed / Hr in z2 metric would do the trick. I’ve got power indoors, so I wouldn’t use it for cycling, but for running it would be great!
Hi, in TP they use “Aerobic Decoupling pa:hr”. Could it be possible use in non power related activities like running and Nordic skiing? And why not calculated for all activities?
Tried to make a custom chart with duration or speed vs Decoupling but could not. It could be useful in investigating aerobic base .
Br Steve
Activity chart page - Custom (bottom of page) - click on the ‘looking glass’ field button and scroll down to @Matheus_Silva 's shared field to select it.
Thanks for this. I would also love (like the above comment) pace:hr decoupling as a percetage from first half to second half of the activity. I’m not super proficient with Javascript but if anyone else has more experience I think the implementation should be possible (correct me if I’m wrong).
Calculate pace / hr for the first half of the activity (split data index in half) like @Matheus_Silva but keep all zones not just zone 2, then do the same for the second half to find the percent decoupling via pace.
Basically I’d love a feature like training peaks has for aerobic pace decoupling, I’ll have a play around with some Javascript but if anyone else has thought of this / done this I’d love to hear about it. Thanks.
I’ve tried to modify the pace at zone script as you suggested and created the Pace Decoupling field.
There is still some issues with numbers precision, so the result is not precise for all activities
If someone with more knowledge of javascript can help to solve this issue it would be great. Below is the current implementation we have
// only considers run activities
if(activity.type=='Run')
{
// get data streams
hrStr = streams.get("fixed_heartrate").data
velStr = streams.get("velocity_smooth").data
// initializes counters
count1 = 0
velMed1 = 0
hrMed1 = 0
count2 = 0
velMed2 = 0
hrMed2 = 0
// iterate over first half of data stream
for (let i=0; i<velStr.length/2; i++){
hr=hrStr[i]
vel=velStr[i]
// count number of points
count1++
// acumulates velocity and hr points
velMed1 = velMed1 + vel
hrMed1 = hrMed1 + hr
}
// iterate over second half of data stream
for (let i=((velStr.length/2)+1); i<velStr.length; i++){
hr=hrStr[i]
vel=velStr[i]
// count number of points
count2++
// acumulates velocity and hr points
velMed2 = velMed2 + vel
hrMed2 = hrMed2 + hr
}
// calculates averages
velMed1 = velMed1/count1
hrMed1 = hrMed1/count1
velMed2 = velMed2/count2
hrMed2 = hrMed2/count2
// calculates pace/hr for each half of the run
pchr1 = (velMed1/hrMed1)*100
pchr2 = (velMed2/hrMed2)*100
// calculates aerobic pace decoupling
// issue with rounding in the calculation below, this number is too small and apparently precision is lost in the operation
paceDec = ((pchr1-pchr2)/pchr1)
}```
I also wrote some Javascript using normalised GAP smoothed over a chosen period, im using 60s but you could use whatever you like. This is some edited code from another custom field. Let me know what you think.
EDIT: I also made this public so you should be able to get it on the interval field explorer.
EDIT2: I also made this public for the activity and intervals, so its there for both now. Let me know if anyone has any ideas to make this better / more accurate.
if ((activity.type == "Run" || activity.type == "VirtualRun" || activity.type == "Ride" || activity.type == "Walk" || activity.type == "Hike") && !activity.icu_ignore_hr)
{
smoothBy = 60 //seconds
gaStr = streams.get("ga_velocity").data
hrStr = streams.get("fixed_heartrate").data
hrSum = 0
xgap1 = 0
xgap2 = 0
gaSmooth1 = 0
gaSmooth2 = 0
count = interval.end_index - interval.start_index
// Number of points in first half of activity
count1 = Math.floor(count / 2)
// Number of points in second half of activity
count2 = count - count1
// Find EF for first half of activity
for (let i=0; i<count1; i++)
{
hrSum = hrSum + hrStr[interval.start_index + i]
gaSmooth1 = gaSmooth1 + gaStr[interval.start_index + i]
if(i%smoothBy == 0 || i+1 == count1)
{
xgap1 = xgap1 + ((gaSmooth1/smoothBy ) ** 4)
gaSmooth1 = 0
}
}
// Calculates average HR for first half
avgHR1 = hrSum / count1
//calculates normalized average
xgap1 = (xgap1 / Math.ceil(count1/smoothBy))**(1/4)
eff1 = (xgap1 * 60) / (avgHR1)
eff1 = +eff1.toFixed(3)
// Reset Hr sum to zero
hrSum = 0
// Find EF for second half of activity
for (let i=0; i<count2; i++)
{
hrSum = hrSum + hrStr[interval.start_index + count1 + i]
gaSmooth2 = gaSmooth2 + gaStr[interval.start_index + count1 + i]
if(i%smoothBy == 0 || i+1 == count2)
{
xgap2 = xgap2 + ((gaSmooth2/smoothBy ) ** 4)
gaSmooth2 = 0
}
}
// Calculates average HR for first half
avgHR2 = hrSum / count2
//calculates normalized average
xgap2 = (xgap2 / Math.ceil(count2/smoothBy))**(1/4)
eff2 = (xgap2 * 60) / (avgHR2)
eff2 = +eff2.toFixed(3)
decoupling = (((eff1 - eff2) / eff1) * 100)
decoupling = +decoupling.toFixed(3)
}
I found that this only worked with about half of my workouts, took a look at the code and found a problem in how it is parsing the second half of the data streams.
When velStr.lenght (the number of datapoints in the data) is an odd number, halving it will not give an integer, e.g: if velStr.length === 2409, then velStr.length/2 === 1204.5.
The easiest way to correct it is to change 1 line
// iterate over second half of data stream
for (let i=((velStr.length/2)+1); i<velStr.length; i++) {
with
// iterate over second half of data stream
for (let i=(Math.floor(velStr.length/2)+1); i<velStr.length; i++) {
The Math.floor() function will round down the result of (velStr.lenght/2) to an integer, which can then be used as an index in the loop.
Hope it helps!
I couldn’t find a way to use this script in a way that also calculates the data for intervals and not only the whole activity, but I haven’t read any documentation yet.
Best regards and hope it helps! It worked like a charm for me
In JavaScript, the index of a for loop must be an integer. If velStr.length equals 4221, then velStr.length / 2 will result in 2110.5, which is a floating-point number. Therefore, the loop won’t work as intended.
To make the loop work correctly, you’ll need to convert velStr.length / 2 to an integer. You can use Math.floor() for this, which rounds down to the nearest integer:
Hello Adam. I am using your script but noticed it doesn’t compute decoupling on my treadmill runs. I guess the problem is that in these runs the GAP is (in my opinion wrongly) calculated as zero. The best solution would be to correct the GAP computation to be = to pace on the treadmill, but while that happens (@david ) would it be possible to get in your script the pace when the GAP is reported to be zero? Thank you!
I attempted to add this, I’ve got it working for the overall activity using the activity velocity, however, it is good to remember that this is an estimation from your watch and will not be very accurate in general, since there is no GPS data. Remember the data is only useful if it is accurate.
I added it to the interval as well, but currently it does not work. It seems it still processes all of the data, rather than only the data in the given interval. I am using the velocity_smoothed stream as my data point for this. If anyone is aware of a way to get to work, please let me know and I will add it.
EDIT:
I have refactored and updated the script, I found a few bugs in it that prevented it from working in Intervals, it actually never worked correctly for them.
Changes:
Now working for intervals correctly
Not using GAP anymore, just velocity. GAP is very noisy and does not produce appropriate values for almost any run. I have compared this new version with Power:HR decoupling and it is very close for most activities on flat-ish ground. Obviously this will not work for runs that have variable elevtation gain, rolling runs may work fine, but not for intervals on hilly terrain.
Uses the Warmup and Cooldown times specified in the settings to remove the warmup and cooldown from the calculation.
Works for Treadmill runs as well.
This will not be appropriate for trail running / hill running. Use a power monitor, like RunPoweModel for Garmin or a pod on your shoe for these activities. This is a limitation of pace data and our current GAP models.
Just one comment. My speed data on the treadmill is very accurate, more than outside because it doesn’t come from my watch. I have a Technogym that transmits its speed data via Bluetooth so it is the equipment I use for all my tests. I can have very accurate control of speed, temperature, etc.
I’m glad you find it useful, let me know if you notice any more issues or bugs Oh right, that is good to be able to connect your treadmill to your devices, makes that data much more accurate.