knitr::opts_chunk$set( collapse = TRUE, fig.width = 6.5, comment = "#>" )
Rationale and validation for this method of estimating anaerobic threshold is described by @Conconi1996-hb, but also disputed e.g. by @Jeukendrup1997-kn and @Hofmann1997-vd (see [References]).
Use a chest strap heart rate monitor if available.
At the end of the test, you can remain still for several minutes with the heart rate monitor still recording, to separately measure and compare rate of recovery. Analysis of heart rate recovery isn't currently handled by this package however.
Open the workout in the web version of Garmin Connect. Click the gearbox in the upper right-hand side:
{width="650"}
Then, export to TCX:
{width="183"}
Actually you don't need to import a TCX file, what matters for the
fitmodel()
function is that you provide a data.frame with columns time
, heart_rate
, and optionally speed
.
If you import a TCX file that is not from Garmin Connect, you may need to rename the column containing heart rate to heart_rate
and the column containing
time to time
. The time
column should be in seconds or a format that
can be coerced to seconds using as.numeric()
, such as the POSIXct/POSIXlt
formats that most services likely provide. If useDeviceSpeed
is FALSE, then
the speed column should be speed
.
Useful in case the run was started before the start of the actual step test, or ended after.
library(ConconiAnaerobicThresholdTest)
Note, files in this package are gzipped to save space. TCX files exported as
above will not have the .gz
extension and you should not use the gzfile()
adapter used below.
fname = system.file("extdata/2023-01-16.tcx.gz", package = "ConconiAnaerobicThresholdTest") tmp <- prepdata(gzfile(fname), startminutes = 0, endminutes = 100, useDeviceSpeed = TRUE) plot(tmp$minutes, tmp$speed) plot(tmp$minutes, tmp$cadence_running) plot(tmp$minutes, tmp$heart_rate)
By iteratively adjusting the startminutes
and endminutes
and replotting, or just replotting with adjusted axes, I found that the correct start time was at 0.15 minutes and correct end time at 15 minutes.
Import the data and show the same plots.
dat202301 <- prepdata(gzfile(fname), startminutes = 0.15, endminutes = 15, useDeviceSpeed = FALSE) (dat202301$date = substr(dat202301$time[1], 1, 10))
plot(dat202301$minutes, dat202301$speed) plot(dat202301$minutes, dat202301$cadence_running) plot(dat202301$minutes, dat202301$heart_rate)
This model uses all available data points:
fitmodel(dat202301, alldata = TRUE, title = "January 2023, using all HR data")
And this model uses only the final 5 measurements in each step:
fitmodel(dat202301, alldata = FALSE, title = "January 2023, using only last 5 HR measurements of each step")
fname = system.file("extdata/2023-09-15.tcx.gz", package = "ConconiAnaerobicThresholdTest") dat202309 <- prepdata(gzfile(fname), startminutes = 23.8, endminutes = 40.1, useDeviceSpeed = FALSE) dat202309$date = substr(dat202309$time[1], 1, 10) with(dat202309, plot(minutes, speed))
fitmodel(dat202309, alldata = TRUE, title = "September 2023, using all HR data") fitmodel(dat202309, alldata = FALSE, title = "September 2023, using only last 5 HR measurements of each step")
fname = system.file("extdata/2022-01-10.tcx.gz", package = "ConconiAnaerobicThresholdTest") dat202201 <- prepdata(gzfile(fname), startminutes = 26, endminutes = 38.99, useDeviceSpeed = FALSE) dat202201$date = substr(dat202201$time[1], 1, 10)
fitmodel(dat202201, alldata = TRUE, title = "January 2022, using all HR data") fitmodel(dat202201, alldata = FALSE, title = "January 2022, using only last 5 HR measurements of each step")
Some plots demonstrating comparison of two tests. First join the two data.frames, and convert date and speed to factors (to make plots appear the way I want them to).
xall <- full_join(x=dat202309, y=dat202301) |> full_join(y=dat202201) |> mutate(date = factor(date)) |> mutate(speed = factor(speed))
ggplot(xall, aes(x=speed, y=heart_rate, fill = date)) + geom_boxplot() + scale_y_continuous(breaks=seq(90, 200, by=10))
ggplot(xall, aes(x = minutes, y = heart_rate, color = date)) + geom_point(size = 0.5) + geom_smooth() + scale_y_continuous(breaks = seq(90, 200, by = 10), name = "Heart Rate (bpm)") + scale_x_continuous(breaks = seq(0, 16.5, by = 1.5), name = "Time (minutes)", sec.axis = sec_axis( ~ . / 1.5 + 6, name = "speed (km/h)", breaks = seq(6, 16, by = 1)))
filter(xall, date != "2022-01-10") |> # bad cadence data from 2022-01-10 ggplot(aes( x = as.numeric(as.character(speed)), y = 2 * cadence_running, color = date )) + geom_point(size = 0.5) + geom_smooth() + scale_y_continuous(breaks = seq(150, 200, by = 10), name = "Cadence (spm)") + scale_x_continuous( breaks = seq(0, 16.5, by = 1), name = "speed (km/h)", sec.axis = sec_axis(~ . / 1.5 + 6, name = "speed (km/h)", breaks = seq(6, 16, by = 1)) )
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.