Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running index should be graphed over time #1074

Closed
thomasturrell opened this issue Nov 30, 2021 · 18 comments
Closed

Running index should be graphed over time #1074

thomasturrell opened this issue Nov 30, 2021 · 18 comments
Labels

Comments

@thomasturrell
Copy link

Personally I find my running index over time one of my most useful metrics when assessing my long term progression.

An individual running index is of little value to me but the moving average is tremendously useful.

This is an example of a running index report in Polar flow.

Screenshot 2021-11-30 at 14 33 40

Incidentally, I believe that running index in elevate should be renamed. It is not a the same algorithm as Polar's running index and it would be a shame if people got confused because the two algorithms have the same name.

@thomaschampagne
Copy link
Owner

Hi @thomasturrell, could you provide me (by a private way) an export of your files (option 2 on this strava link)?

I'm currently tackling with @tsourbier the following issue: #871

In version 7, it should be renamed as "Running Performance Index", "Running Scale" or "Running Ratio" or better idea?

Thanks for your help,

Thomas

@thomasturrell
Copy link
Author

Glad to be of help. Elevate is a very useful training tool.

Some naming suggestions:

  • Running Rating or Run Rating
  • Running Score or Run Score

I like the literation in Run Rating, so that would be my preference. Elevate should use a name that isn’t used by anyone else.

I will gladly share my runs with you. I will message you privately.

@tsourbier
Copy link

tsourbier commented Dec 1, 2021

I'll side with "Running Rating" it is perfect imho to encompass the fact it is the fruit of some calculations and allows to compare runs & runners.

@thomasturrell
Copy link
Author

@thomaschampagne I have sent you DM in twitter.

@thomaschampagne
Copy link
Owner

@thomaschampagne I have sent you DM in twitter.

I replied you ;)

@thomaschampagne
Copy link
Owner

Let's go for "Running Rating" then. I have a first result that compares both source. @thomasturrell Let my know if I can post it here.

@thomasturrell
Copy link
Author

thomasturrell commented Dec 2, 2021

@thomaschampagne no problem, please post the comparisons here. I am expecting my running rating to have less spread than my running index.

I am looking forward to seeing the results.

@thomaschampagne
Copy link
Owner

thomaschampagne commented Dec 2, 2021

Here you go on your 2021 activities:

  • Matplotlib + scipy

Figure_1

desmos-graph

@thomaschampagne
Copy link
Owner

The CSV polar-vs-elevate-RI.zip

@thomasturrell
Copy link
Author

thomasturrell commented Dec 2, 2021

That is really interesting. Elevate does a really good job.

As I suspected, Elevate results do not have as much spread (which is a good thing).

@thomaschampagne there is one surprising result, data point 94 is higher but I can not see an obvious reason why.

@tsourbier
Copy link

@thomasturrell as we discovered with my data 2 issues could be at play:

  • bad HR data that Polar was able to filter out but not Elevate.
  • bad Elevation data that Strava corrects when you look at it online but the error can still be present in the export.

may be looking into that training deeper will reveal some other oddities 😂

T.

@thomasturrell
Copy link
Author

thomasturrell commented Dec 2, 2021

  • bad Elevation data that Strava corrects when you look at it online but the error can still be present in the export.

@thomaschampagne I have seen this in your results. I did a 10K race with significant elevation gain, Strava does a good job of correcting it. However most watches struggle to give accurate elevation data from GPS.

I also suspect you are right about the 'bad' heart rate data. But filtering that out would be very hard. I notice that even Strava doesn't have an algorithm for that. Stava does not spot people who accidentally mark a bike ride as a run (trivial for a human to spot, difficult for an algorithm).

I think that people should be able to remove a running rating for certain runs in Elevate. I don't want my anomalous readings to affect my moving average but I also do not want to delete a run with bad heart rate data (or bad GPS). Having said that, with enough activities it will average out.

@thomaschampagne
Copy link
Owner

thomaschampagne commented Dec 2, 2021

@thomasturrell @tsourbier I implemented a Flag mecanism able to detect flaws in speed, power, heartrate... For example if activity is flagged with a strange HRSS then his score will not count for Fitness Trend (we use another valid one or none). I can do the same for running index/rating...

However most watches struggle to give accurate elevation data from GPS.

If you have an altimeter that's OK. But indeed it's complicated for Elevate without. It's fixable using a Kalman filter but I have to know the measurement errors of the altitude signal first (get the standard deviation on a 100m flat section ?!).

The "alone point" is the below activity. And indeed altitude is broken:

image

@thomasturrell Do you want a dev build including the current "Running Rating"?

@thomasturrell
Copy link
Author

@thomaschampagne A dev build would be great. I have a Mac (if that makes any difference).

@thomaschampagne
Copy link
Owner

@thomasturrell I sent you a dm on twitter

@thomaschampagne
Copy link
Owner

thomaschampagne commented Dec 4, 2021

Results with new assertions before computing Running Rating:

  /**
   * Computes equivalent Polar running index
   * https://support.polar.com/en/support/tips/Running_Index_feature
   * https://www.polar.com/en/smart_coaching/features/running_index/chart
   */
  public static runningRating(
    athleteSnapshot: AthleteSnapshot,
    stats: ActivityStats,
    timeArray: number[],
    distanceArray: number[],
    velocityArray: number[]
  ): number {
    if (
      !stats.movingTime ||
      !stats?.heartRate?.avg ||
      !stats?.pace?.gapAvg ||
      !athleteSnapshot?.athleteSettings?.maxHr ||
      _.isEmpty(timeArray) ||
      _.isEmpty(distanceArray) ||
      _.isEmpty(velocityArray)
    ) {
      return null;
    }

    // Verify running rating requirements
    const COOPER_KPH_THRESHOLD = 6;

    // Verify legit ascent speed to get proper grade adjusted distance value
    if (Number.isFinite(stats?.elevation?.ascentSpeed) && stats?.elevation?.ascentSpeed > this.MAX_ASCENT_SPEED) {
      return null;
    }

    // Verify avg heart-rate against athlete current max hr. Should not be higher!
    if (Number.isFinite(stats?.heartRate?.avg) && stats?.heartRate?.avg > athleteSnapshot.athleteSettings.maxHr) {
      return null;
    }

    // Verify standard deviation. Missing variation means heart rate problem
    if (Number.isFinite(stats?.heartRate?.stdDev) && stats?.heartRate?.stdDev === 0) {
      return null;
    }

    // Ensure to have legit hr effort using HRR%
    if (
      (Number.isFinite(stats?.heartRate?.avgReserve) && stats.heartRate.avgReserve < 52) ||
      stats.heartRate.avgReserve > 98
    ) {
      return null;
    }


    // Verify that athlete ran at least 12 minutes over 6 kph. If not, no running performance index
    const bestCooperSpeed = ActivityComputer.computeSplit(velocityArray, timeArray, 60 * 12) * Constant.MPS_KPH_FACTOR;
    if (bestCooperSpeed < COOPER_KPH_THRESHOLD) {
      return null;
    }

    // Compute the grade adjusted speed in meters per seconds
    const gradeAdjustedMetersPerSec = Movement.paceToSpeed(stats.pace.gapAvg) / Constant.MPS_KPH_FACTOR;

    // Keep the greatest adjusted distance
    const gradeAdjustedDistance = Math.max(gradeAdjustedMetersPerSec * stats.movingTime, _.last(distanceArray));

    // Now compute the running performance index
    const distanceRate: number = (213.9 / (stats.movingTime / 60)) * (gradeAdjustedDistance / 1000) ** 1.06 + 3.5;
    const intensity: number = Math.min((stats.heartRate.avg / athleteSnapshot.athleteSettings.maxHr) * 1.45 - 0.3, 1);
    return _.round(distanceRate / intensity, ActivityComputer.RND) || null;
  }

image

@stale
Copy link

stale bot commented Apr 16, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Apr 16, 2022
@stale
Copy link

stale bot commented Apr 28, 2022

This issue has been automatically closed because it didn't had recent activity.

@stale stale bot closed this as completed Apr 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants