Good point. I agree with you, this plot probably comes from a series in which the same visualization was used for different variables (including some with a strong relationship), hence the potential confusion. Appreciate your feedback!
Marco - could you please elaborate a little bit more on SWC/normal values. Is this calculated from all rMSSD in the entire time series or only from the 7 days making up the moving average for a given day?
Edit: just noticed in the chart that your normal values are for 30 days. Is this correct? So you take the standard deviation for the last 30 days? And construct the green band around the mean for the 30days for each day?
Edit2: and the 0.5 or 0.75 std is only to one side of the 30d mean or is it 0.5/0.75 for both sides, e.g. 0.25/0.375 for each side to the mean?
Hi! We currently use 2 months, so 60 days as I think this is a better trade-off between short and long term stressors (acute/chronic). The baseline is then 7 days.
Thanks.
And the 0.5 or 0.75 std is only to one side of the 60d mean or is it 0.5/0.75 for both sides?
I’d say 0.75 on each side off the top of my head (this was configurable in the past and might come back), I’d stress as usual to focus on the big picture, this is a simple way to spot deviations from what is expected to be your normal considering your past data, different numbers will do, then over the years we found the current ones to be working rather well. Hope this helps!
Thanks, have this put together quickly in Golden Cheetah. I use the HRV4Training app on Android.
I guess I should stop doing business trips. Have to tell my boss that it interfers with my training.
Would be interesting to see last year. Haven’t used the app yet. Similar volume but this year I’m a little bit “wiser” and take more care of recovery. E.g. do only hard stuff when feeling right.
one last point and you can take us out of business haha: I would use ln rMSSD as data is not normally distributed, similarly to what you see in research when they show the SWC and HRV (as ln rMSSD you get something that is pretty much half the Recovery Points in HRV4T, you could just use those).
Do you mind pushing that to the user charts database for GC or sharing it otherwise?
Sorry, but I use my own build of GC. The HRV chart is based on completely different set up, e.g. I’ve put my data on a cloud and programmed the access myself.
Today race day. And as usual my HRV has plummeted, the app telling me to “limit intensity” today. Don’t know
Now with ln(rmssd):
now if I’m honest, sorry, in the end it does not really tell me anything new to how I simply feel. And since I base my training on how I feel, this is probably the reason why I don’t cross the bottom line.
One more question about this (I threw something like this together in an R Chart for GC, which I am happy to share once I’ve made it a little prettier): Did you use 0.75 std of the moving 60 day averages to construct the band or of the raw values of the past 60 days?
I’ve added both to the latest version +/- 0.7 std and +/- 0.5 std. I’m using a 30d average for the “normal-mean”.
Have to move this out of GC at some point. Would be easy to do. Grab the csv from HRV4Training via dropbox and construct the chart. GC is super slow and cumbersome on my computers. If only I had the time …
Race went super well on the week-end despite being told by the app to limit intensity. Podium
Must say, looking at my HRV it’s really mainly work and pre-race worries which drive my (daily) HRV down.
and a zoom in
I might be a bit dense (I am in the humanities!) but what I am doing right now is I get a dataframe from GC with two columns: date and RecoveryScore (from HRV4Training), then I add the following columns:
60DayMean, StandardDeviation (calculated for the last 60 values of the 60DayMean), and the 7DayBaseLine.
So I have a different sd value for every day.
Does that sound about right?
I’ve never really understood what this recovery score is exactly. I’m sure there is a detailed explanation on the website but I would appreciate an “executive’s summary”
I use RMSSD.
- Grab date and RMSSD
- calculate ln(RMSSD)
3a) calculate rolling means for 7 and 30 (or in your case 60)d
3b) calculate rolling std for 30d from ln(RMSSD)-series [so here’s a discrepancy, do we take std of the means or of the raw series, e.g. ln(RMSSD) ???] - plot
This is at least my understanding.
Actually, I misread my own code. I did do a rolling std from the raw values rather than the means. Thanks for your help anyway! I might migrate to ln(RMSSD) after I finished this, I just decided to skip that step because of @marco_alt’s comments and because it’s in the dataset anyway.
Python code snippet for charting it outside of GC. This access the HRV4Training export *.csv file. In my case I export it to Dropbox. This path would need changing.
7d rolling mean and 60d-mean +/- 60d-std
import pandas as pd
import matplotlib.pyplot as plt
import datetime
import numpy as np
df = pd.read_csv(‘C:\Users\xy\Dropbox\Apps\HRV4Training\MyMeasurements_Android.csv’, index_col=False)
dates = [datetime.datetime.strptime(x, ‘%Y-%d-%m’) for x in df.date]
rmssd = df.rMSSD.replace(0, np.nan).apply(np.log)
rmssd7 = rmssd.rolling(7, min_periods=3).mean()
rmssd60 = rmssd.rolling(60, min_periods=20).mean()
rmssd60std = rmssd.rolling(60, min_periods=20).std()
plt.plot_date(x=dates, y=rmssd7, fmt=“r-”)
plt.plot_date(x=dates, y=rmssd60.add(rmssd60std.mul(0.7)), fmt=“b-.”)
plt.plot_date(x=dates, y=rmssd60.sub(rmssd60std.mul(0.7)), fmt=“b-.”)
plt.ylabel(“ln(rMSSD)”)
plt.grid(True)
plt.show()
Actually, In GC do you have it as a python chart or as custom metrics/formulas? I tried to go the formulas route at first but found that it wouldn’t give me measures for non-activity dates.
In GC as python chart with plotly charting library. However, as alluded to above, I store my data differently in GC. I don’t use the GC Python Api to access my workouts.
Going through the GC API would look similar to this I guess:
import pandas as pd
hrv = GC.seasonMeasures(group=“Hrv”)
pdhrv = pd.DataFrame(hrv)
pdhrv looks then likes this
AVNN | HF | HR | LF | PNN50 | RMSSD | RecoveryPoints | SDNN | date | |
---|---|---|---|---|---|---|---|---|---|
177 | 1280.05698 | 0.0470521 | 46.8729134 | 0.02104777 | 55.2631579 | 71.0614217 | 8.36117889 | 73.7212174 | 30-04-19 |
178 | 1244.44444 | 0.06947699 | 48.2142857 | 0.02558803 | 68.9655172 | 92.4509499 | 8.80397241 | 60.2293285 | 01-05-19 |
179 | 1282.01754 | 0.09751767 | 46.8012316 | 0.04303748 | 45.9459459 | 71.4464508 | 8.37013374 | 50.7166672 | 02-05-19 |
180 | 1284.33048 | 0.04807177 | 46.7169476 | 0.0365463 | 28.9473684 | 56.9641254 | 8.00034183 | 39.8611033 | 03-05-19 |
181 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 04-05-19 |
Yeah. You’d want to pass all=True so you get the full history. But other than that, that’s how I do it.
UPDATE: This is now available as a GC chart in the GC chart database as “HRV Trends and Stress.” The range of days for the normal “band” and the type of Stress (BikeStress, TSS, Duration) can be configured via variables in the beginning of the code.