Equivalent Speed: An improved average speed metric

Hey,

I found this metric thanks to strava sauce and I think it would be nice to integrate it in intervals.icu :eyes:

So what is this specific feature ?
Equivalent Speed can be viewed as taking the speed each metre (or suitably small distance interval) and taking the average.
The goal being to have a metric like VAM but for flat terrains. It allows people without a PM to compare different activities.
It will simulate a average speed without stops, wind nor hills?

More details here :
Equivalent Speed: An improved average speed metric

Equivalent Speed: A New Metric For Cycling Performance

1 Like

I like this metric, because I live in a hilly area :smiley:
I made it Public as a ā€œCustomā€ Field. You can search for it:
image

5 Likes

Where can I find it ? When on a activity ? I’m unsure

Activity Timeline tab - Custom - Looking glass icon

3 Likes

I revisited this because the resulting speed looks somewhat high with the math used. I don’t quit understand why sum of squared speeds divided by sum of speeds is used when it actually is easier to use sum of squared distances divided by sum of distances (on the condition that the recording interval is fairly stable at 1 sec, which it should). That will actually average speed over distance.
So I ended up with the following code (after stripping some unnecessary bits):

{
// Configuration
const DISTANCE_THRESHOLD = 0.55; // Distance threshold in meters
// Get Streams
const distances = icu.streams.distance; // Distance stream in meters
// Variables
let sumDistances = 0;
let sumDistancesSquared = 0;
// Data processing
for (let i = 0; i < distances.length; i++) {
const distance = distances[i];
if (i > 0) {
const distanceDiff = distances[i] - distances[i - 1];
// Apply thresholds
if (distanceDiff >= DISTANCE_THRESHOLD) {
sumDistances += distanceDiff;
sumDistancesSquared += distanceDiff * distanceDiff;
}
}
}
// Result calculation
const positionalAverageSpeed = sumDistancesSquared / sumDistances * 3.6;
// Display results
console.log(ā€œPositional average speed:ā€, positionalAverageSpeed, ā€œkm/hā€);
positionalAverageSpeed/3.6;
}

And this works perfectly well as Equivalent Pace for hikes/runs by just setting the conversion to ā€˜per km’.

Thanks, I removed some ā€œdebugā€ variables and cleaned it up a bit.

If I use distance, I get on some activities higher results. With my code I get the same results as in ā€œSauce for Stravaā€.
Didn’t understand the reasons exactly. Don’t know if distance is based on GPS, and because of this inaccuracy it’s a bit off, or some other reason. Additionally velocity_smooth is a smoothed value, so don’t have to mess around with eventually noisy distance data.

Nice to know, didn’t think of that :smiley:

That remark made me think it over again during my evening hike and I figured it out. On a sec by sec recording, point speed actually is the distance :wink: and both formula’s return basically the same.
So I’m back to using the original code which has less risk of turning to a mess when distance data is noisy.

1 Like

@R2Tom I was still not happy with the pace number returned for Walks/Hikes and a bit sceptical with the result for speed when cycling.
I found at why…
The velocity_smooth stream is a stream with integers. So every point value is rounded (or cut) to the closest smaller/greater integer value. And that seriously messes with the result for Walks.
1m/s is 3.6km/hour, and most of walk/hiking is somewhere in between 1 and 2m/sec.
For longer steady state cycling, a resolution of 3.6k/h also looks too small.

I modified the code once more for both and shared them as ā€˜Dist. based avg. speed’ and ā€˜Dist. based avg. pace’. I’m taking a 5sec moving average for velocity.
On my data, this is giving a much more meaningful result, especially on Walks/Hikes.

{
const speeds = icu.streams.velocity_smooth;
let sumSpeed = 0, sumSpeedSquared = 0, points = 0, movingpoints = 0
for (let i = 2; i < speeds.length-2; i++) {
smoothedspd = (speeds[i-1] + speeds[i] + speeds[i+1] + speeds[i-2] + speeds[i+2])/5
sumSpeed +=smoothedspd;
sumSpeedSquared += smoothedspd * smoothedspd;
points += 1;
if (smoothedspd > 0.85) {
movingpoints += 1;
}
}
const distanceAveragedSpeed = sumSpeedSquared / sumSpeed;
const elapsedAverage = sumSpeed/points;
const movingAverage = sumSpeed/movingpoints;
console.log(ā€œEquivalent speed (distance averaged):ā€, distanceAveragedSpeed * 3.6, ā€œkm/hā€);
console.log(ā€œElapsed Time Average speed:ā€, elapsedAverage * 3.6, ā€œkm/hā€);
console.log(ā€œMoving Time Average speed:ā€, movingAverage * 3.6, ā€œkm/hā€);
console.log(ā€œMoving Time:ā€, movingpoints, ā€œsecā€);
distanceAveragedSpeed;
}

1 Like

Ah gosh. Thanks for pointing this out. That’s why it sometimes got different results.