Package {lactater}


Title: Tools for Analyzing Lactate Thresholds
Version: 1.0.0
Description: Set of tools for analyzing lactate thresholds from a step incremental test to exhaustion. Estimate Log-log, Onset of Blood Lactate Accumulation (OBLA), Baseline plus (Bsln+), the Dmax family, Lactate Turn Points (LTP), the Lactate / Intensity ratio (LTratio), and the Individual Anaerobic Thresholds of Dickhuth (IAT) and Stegmann in cycling, running, swimming, or rowing, with bootstrap confidence intervals, data-quality checks, training zones, test comparisons, training load, and the maximal lactate accumulation rate (VLamax). Beaver WL, Wasserman K, Whipp BJ (1985) <doi:10.1152/jappl.1985.59.6.1936>. Heck H, Mader A, Hess G, Mücke S, Müller R, Hollmann W (1985) <doi:10.1055/s-2008-1025824>. Kindermann W, Simon G, Keul J (1979) <doi:10.1007/BF00421101>. Skinner JS, Mclellan TH (1980) <doi:10.1080/02701367.1980.10609285>. Berg A, Jakob E, Lehmann M, Dickhuth HH, Huber G, Keul J (1990) PMID 2408033. Zoladz JA, Rademaker AC, Sargeant AJ (1995) <doi:10.1113/jphysiol.1995.sp020959>. Cheng B, Kuipers H, Snyder A, Keizer H, Jeukendrup A, Hesselink M (1992) <doi:10.1055/s-2007-1021309>. Bishop D, Jenkins DG, Mackinnon LT (1998) <doi:10.1097/00005768-199808000-00014>. Hughson RL, Weisiger KH, Swanson GD (1987) <doi:10.1152/jappl.1987.62.5.1975>. Jamnick NA, Botella J, Pyne DB, Bishop DJ (2018) <doi:10.1371/journal.pone.0199794>. Hofmann P, Tschakert G (2017) <doi:10.3389/fphys.2017.00337>. Hofmann P, Pokan R, von Duvillard SP, Seibert FJ, Zweiker R, Schmid P (1997) <doi:10.1097/00005768-199706000-00005>. Pokan R, Hofmann P, Von Duvillard SP, et al. (1997) <doi:10.1097/00005768-199708000-00009>. Dickhuth H-H, Yin L, Niess A, et al. (1999) <doi:10.1055/s-2007-971105>. Stegmann H, Kindermann W, Schnabel A (1981) <doi:10.1055/s-2008-1034604>. Heuberger JAAC, Gal P, Stuurman FE, et al. (2018) <doi:10.1371/journal.pone.0206846>. Meixner B, Nusser V, Koehler K, et al. (2024) <doi:10.14814/phy2.16086>.
License: MIT + file LICENSE
Copyright: see file COPYRIGHTS
URL: https://fmmattioni.github.io/lactater/, https://github.com/fmmattioni/lactater
BugReports: https://github.com/fmmattioni/lactater/issues
Encoding: UTF-8
LazyData: true
VignetteBuilder: knitr
Suggests: covr, dplyr, grDevices, knitr, lintr, pagedown, readxl, rmarkdown, gt, htmltools, spelling, systemfonts, testthat (≥ 3.0.0)
Imports: ggplot2 (≥ 4.0.0), ggtext (≥ 0.2.0), grid, patchwork, pillar, rlang, splines, stats, tibble, tools, utils, vctrs
Depends: R (≥ 4.1.0)
Language: en-US
Config/testthat/edition: 3
Config/Needs/website: purrr, tidyr
Config/roxygen2/version: 8.1.1
NeedsCompilation: no
Packaged: 2026-11-12 21:07:02 UTC; felipe
Author: Felipe Mattioni Maturana ORCID iD [aut, cre]
Maintainer: Felipe Mattioni Maturana <felipe.mattioni@med.uni-tuebingen.de>
Repository: CRAN
Date/Publication: 2026-11-13 09:10:24 UTC

lactater: Tools for Analyzing Lactate Thresholds

Description

logo

Set of tools for analyzing lactate thresholds from a step incremental test to exhaustion. Estimate Log-log, Onset of Blood Lactate Accumulation (OBLA), Baseline plus (Bsln+), the Dmax family, Lactate Turn Points (LTP), the Lactate / Intensity ratio (LTratio), and the Individual Anaerobic Thresholds of Dickhuth (IAT) and Stegmann in cycling, running, swimming, or rowing, with bootstrap confidence intervals, data-quality checks, training zones, test comparisons, training load, and the maximal lactate accumulation rate (VLamax). Beaver WL, Wasserman K, Whipp BJ (1985) doi:10.1152/jappl.1985.59.6.1936. Heck H, Mader A, Hess G, Mücke S, Müller R, Hollmann W (1985) doi:10.1055/s-2008-1025824. Kindermann W, Simon G, Keul J (1979) doi:10.1007/BF00421101. Skinner JS, Mclellan TH (1980) doi:10.1080/02701367.1980.10609285. Berg A, Jakob E, Lehmann M, Dickhuth HH, Huber G, Keul J (1990) PMID 2408033. Zoladz JA, Rademaker AC, Sargeant AJ (1995) doi:10.1113/jphysiol.1995.sp020959. Cheng B, Kuipers H, Snyder A, Keizer H, Jeukendrup A, Hesselink M (1992) doi:10.1055/s-2007-1021309. Bishop D, Jenkins DG, Mackinnon LT (1998) doi:10.1097/00005768-199808000-00014. Hughson RL, Weisiger KH, Swanson GD (1987) doi:10.1152/jappl.1987.62.5.1975. Jamnick NA, Botella J, Pyne DB, Bishop DJ (2018) doi:10.1371/journal.pone.0199794. Hofmann P, Tschakert G (2017) doi:10.3389/fphys.2017.00337. Hofmann P, Pokan R, von Duvillard SP, Seibert FJ, Zweiker R, Schmid P (1997) doi:10.1097/00005768-199706000-00005. Pokan R, Hofmann P, Von Duvillard SP, et al. (1997) doi:10.1097/00005768-199708000-00009. Dickhuth H-H, Yin L, Niess A, et al. (1999) doi:10.1055/s-2007-971105. Stegmann H, Kindermann W, Schnabel A (1981) doi:10.1055/s-2008-1034604. Heuberger JAAC, Gal P, Stuurman FE, et al. (2018) doi:10.1371/journal.pone.0206846. Meixner B, Nusser V, Koehler K, et al. (2024) doi:10.14814/phy2.16086.

Author(s)

Maintainer: Felipe Mattioni Maturana felipe.mattioni@med.uni-tuebingen.de (ORCID)

Authors:

See Also

Useful links:


Plot lactate thresholds

Description

Plots the result of lactate_threshold(). Without method, an overview: the fitted lactate curve on top and every threshold below, grouped by what the method aims at, on the same intensity axis, with a shaded band for how far the methods aimed at LT1 and at LT2 lie apart, so you can see at a glance how much the methods agree; the two that mark LT1 and LT2 (lt1, lt2, as chosen in lactate_threshold()) are drawn in green. With method, the plot of that one method: what the method fitted, the measured stages, and the threshold with its intensity, lactate and heart rate.

Usage

## S3 method for class 'lactate_thresholds'
autoplot(object, method = NULL, lt1 = NULL, lt2 = NULL, ...)

## S3 method for class 'lactate_thresholds'
plot(x, method = NULL, ...)

Arguments

object, x

The result of lactate_threshold().

method

A method to plot (one of object$method), or NULL for the overview.

lt1, lt2

The thresholds that mark LT1 and LT2, drawn in green in the overview. Default to those chosen in lactate_threshold().

...

Not used.

Details

The plots are ggplot2 objects: add layers, scales or a theme() to change them. See theme_lactater() for the dark style (options(lactater.style = "night")) and the font.

Value

a ggplot2 (one method) or patchwork (overview) object.

Examples

results <- lactate_threshold(
  demo_data,
  intensity_column = "intensity",
  lactate_column = "lactate",
  heart_rate_column = "heart_rate",
  plot = FALSE
)
autoplot(results)
autoplot(results, method = "Dmax")

Bootstrap intervals

Description

Wild bootstrap around the user-selected curve: each replicate keeps the fitted values and adds to each stage its own residual, scaled by 1 / sqrt(1 - leverage) and widened by qt(level) / qnorm(level) for the n - p degrees of freedom of the curve, with a random sign (Rademacher weights; Wu, 1986; Davidson & Flachaire, 2008). Residuals never move between stages, because the error of a lactate sample grows with its concentration. The widening brought the coverage of 95% intervals from 75-93% to 84-97% in data-raw/bootstrap_coverage.R. Davidson & Flachaire (2008) used residuals divided by 1 - leverage (HC3), which they found made little difference; in that simulation it gave intervals two to three times too wide for Bsln+ (coverage 95-99%). A resting sample outside the fit gets the residual size of the first stage. Every method is then run again on the replicate. The interval is the percentile interval of the replicate intensities. For internal use only.

Usage

bootstrap_intervals(
  data,
  settings,
  method,
  curve,
  n_rows,
  n_boot,
  level,
  seed = NULL
)

Arguments

data

The data retrieved from prepare_data().

settings

The analysis settings.

method

The method categories to run.

curve

The user-selected curve fitted to the original data.

n_rows

Number of thresholds the methods return.

n_boot

Number of replicates.

level

Confidence level.

seed

A seed for the replicates, or NULL to draw from the current random stream.

Value

a matrix with one row per threshold and the columns low and high.


Layers of a breakpoint method

Description

The fitted straight lines (solid where they were fitted, dashed beyond), and for LTP the other turn point. For internal use only.

Usage

breakpoint_layers(fit, method, intensity, log_axes, colours, sport)

Arguments

fit

A fit from fit_breakpoints() or fit_two_lines().

method

The method label.

log_axes

Whether the fit is in log-log space.

colours

The plot colours.

sport

The sport, for the unit of the other turn point.

Value

a list with the layers, the lactate y where the threshold sits, and the top.


The straight lines of a breakpoint fit

Description

The straight lines of a breakpoint fit

Usage

breakpoint_lines(fit)

Arguments

fit

A fit from fit_breakpoints() or fit_two_lines().

Value

a list of functions, one line per segment, each defined over all x.


Calibrate your own performance equation

Description

Fits the relationship between a threshold and race performance on your own athletes, for any threshold, sport and test protocol, so that predict_performance() can use it for new tests. This is the most valid way to predict performance from a step test: the published equations only apply to tests run like those of their studies, while yours applies to your protocol, your analyzer and your athletes.

Usage

calibrate_performance(
  data,
  threshold,
  performance,
  method,
  sport = "running",
  distance = NULL,
  event = NULL,
  protocol = NULL
)

## S3 method for class 'lactate_calibration'
autoplot(object, ...)

Arguments

data

A data frame with one row per athlete.

threshold

The column of data with the threshold, in the intensity unit of the sport.

performance

The column of data with the race time (s) when distance is given, or the mean race intensity otherwise.

method

The lactater method the thresholds come from (e.g. "LTP2"), read from new tests in predict_performance().

sport

"cycling", "running", "rowing" or "swimming", as in lactate_threshold().

distance

The race distance in meters, when performance is a time. Not for cycling.

event

A name for the race. Defaults to the distance (e.g. "10 km"), or "Performance".

protocol

A short description of your test protocol and population, printed with every prediction.

object

The result of calibrate_performance().

...

Not used.

Details

Give one row per athlete: the threshold from lactate_threshold() and the result of a race run soon after the test (e.g. within 2 months, as in Roecker et al., 1998). With distance, performance is the race time in seconds and is converted to the mean race intensity of the sport (speed in km/h for running, m/s for swimming, and power in W for rowing, with the Concept2 relation watts = 2.80 / pace^3, pace in s/m); without it, performance is already a mean intensity (e.g. time-trial power in W). The equation is a straight line, intensity = intercept + slope * threshold, as in the published equations. Use the same protocol for the new tests as for the athletes it was fitted on, and more athletes make a narrower, more stable range.

Value

a lactate_calibration tibble with one row: the equation (intercept, slope), its residual SD (sigma), n, the correlation r, the leave-one-out prediction error (loo_error), and what predict_performance() needs. Plot it with autoplot().

Examples

## thresholds and 10 km times of 12 runners from your lab (simulated here)
set.seed(1)
lab <- data.frame(ltp2 = round(rnorm(12, 15, 1.5), 1))
lab$time_10km <- round(10000 / ((0.5 + 1.02 * lab$ltp2 + rnorm(12, 0, 0.4)) / 3.6))
own <- calibrate_performance(
  lab, "ltp2", "time_10km", method = "LTP2", sport = "running", distance = 10000,
  protocol = "3-min stages, +1 km/h, 1% grade, earlobe"
)
own
autoplot(own)

The thresholds that mark LT1 and LT2

Description

What the caller asked for, otherwise what lactate_threshold() was given, otherwise the package defaults (a result saved before the choice was kept). For internal use only.

Usage

chosen_pair(info, lt1 = NULL, lt2 = NULL)

pair_text(info)

Arguments

info

The lactater attribute of a result.

lt1, lt2

A method, an intensity, or NULL.

Value

a list with lt1 and lt2.

pair_text(): "83 W (Bsln + 0.5)" for each of the two that was found, named LT1 and LT2.


Column name from a bare name, a string, or a variable that holds a string

Description

Column name from a bare name, a string, or a variable that holds a string

Usage

column_name(expr, .data, env)

Arguments

expr

The captured argument.

.data

The data.

env

Where to look up a variable.

Value

the column name, or NULL.


Compare two lactate tests

Description

Compares two tests of the same athlete (e.g. before and after a training block): the change of each threshold, whether it is larger than the noise of the method, the heart rate at each threshold, and notes on what could make the comparison unfair.

Usage

compare_tests(before, after, cv = NULL, swc = 0, level = 0.9)

## S3 method for class 'lactate_comparison'
autoplot(object, ...)

Arguments

before, after

The results of lactate_threshold() for the two tests, with the same sport.

cv

The test-retest coefficient of variation (%) of each method: one number for all methods, or a named vector (e.g. c("LTP2" = 3)) that adds to or replaces the defaults, or the result of lactate_reliability() for your own tests.

swc

The smallest worthwhile change, in % of the intensity before. Defaults to 0.

level

The confidence level of the interval. Defaults to 0.9.

object

The result of compare_tests().

...

Not used.

Details

A threshold moves between two tests even without any change in fitness, by the day-to-day variation of the athlete and the error of the analyzer and the method. Each change is therefore given with an interval, change +/- z * sqrt(2) * typical error, where the typical error is the coefficient of variation (cv) times the intensity before (Hopkins, 2000; Swinton et al., 2018). This is the model behind the repeatability of Bland & Altman (1996), for whom two measurements of the same person differ by less than 1.96 * sqrt(2) within-subject standard deviations in 95% of pairs, a size also known as the minimal detectable change (Sainani, 2017). With a cv from lactate_reliability(), z is the t quantile for its degrees of freedom, because a typical error from few athletes is itself uncertain (Swinton et al., 2018): with 10 athletes tested twice, the normal quantile would call 13% of unchanged pairs "beyond the noise" at the 90% level instead of 10%.

Three columns say how the change stands against that noise, without a probability attached to them:

Three things decide how often a verdict is wrong, so settle them before looking at the result:

The default cv of each method is lactater's own calculation: lactate_reliability() on the five step tests of each of 47 well-trained male cyclists in the open data of Heuberger et al. (2018; 204 tests, 5-min stages, venous blood, about two weeks apart), so that it is the noise of each method as lactater computes it, on a step test. The output gives it in the cv column.

Method Default cv (%) Published (%)
OBLA 2.0, 2.5, 3.0, 3.5 7.7, 5.2, 4.6, 4.8
OBLA 4.0 4.4 3.8 (Heuberger et al., 2018)
Bsln + 0.5, 1.0, 1.5 8.2, 5.0, 4.5 3.7, 3.4, 3.4 (Pallares et al., 2016; ramp test)
Dmax 5.9 3.4 (Heuberger et al., 2018)
ModDmax 5.1 4.3 (Heuberger et al., 2018)
Exp-Dmax, Log-Poly-ModDmax, Log-Exp-ModDmax 4.3, 5.5, 4.9
IAT 4.4 3.6 (Heuberger et al., 2018)
LTP1, LTP2 10.6, 6.0
Log-log, LTratio 7.8, 9.4

The defaults are on the careful side. Heuberger et al. computed their values on the riders who received placebo, with their own implementation of each method; the open data do not say which riders received erythropoietin, so the defaults use every rider and are, if anything, too high (pairs of consecutive tests gave values of the same size, so a drift between the two groups does not explain them). Those tests have no resting sample, so Bsln+ rose from the first stage. To use the published values instead, give them: cv = c("IAT" = 3.6, "Dmax" = 3.4). Stegmann, Keul, Simon and your own fixed or baseline_plus values have no default: their verdict is NA unless you give one.

The defaults come from cycling and are used for every sport, as a percentage of the intensity; the notes say so for other sports. No open test-retest data of running step tests were found to compute running values the same way. Published running values are of a similar size in trained runners: typical errors of about 0.3 to 0.8 km/h (2% to 6%) for 22 trained men on a treadmill with 1-min stages, from the SDs of their test-retest differences (the CVs they report, 1.2% to 3.5%, are smaller; Cerezuela-Espejo et al., 2018), and 0.4 to 1.4 km/h for 19 healthy, untrained people with 4-min and 8-min stages (Gavin et al., 2014). The best cv is the one of your own athletes, measured with your protocol and analyzer. A cv assumes that the noise grows with the intensity, which is common for such measures but should be checked where the data allow (Atkinson & Nevill, 1998).

Value

a lactate_comparison tibble with one row per method found in both tests: method, before, after, change, change_percent, the interval of the change (change_low, change_high), the cv (%) it was computed with, the noise, the ratio of the change to it, the verdict, and the heart rate at each threshold in both tests (when recorded). The notes attribute lists what differs between the two tests (stages, step, resting sample, sample type) and a fall in peak lactate. Plot both curves with autoplot().

References

Atkinson G, Nevill AM. Statistical methods for assessing measurement error (reliability) in variables relevant to sports medicine. Sports Medicine. 1998;26(4):217-238.

Bland JM, Altman DG. Measurement error. BMJ. 1996;312(7047):1654. doi:10.1136/bmj.312.7047.1654

Cerezuela-Espejo V, Courel-Ibáñez J, Morán-Navarro R, Martínez-Cava A, Pallarés JG. The relationship between lactate and ventilatory thresholds in runners: validity and reliability of exercise test performance parameters. Frontiers in Physiology. 2018;9:1320. doi:10.3389/fphys.2018.01320

Gavin JP, Willems MET, Myers SD. Reproducibility of lactate markers during 4 and 8 min stage incremental running: a pilot study. Journal of Science and Medicine in Sport. 2014;17(6):635-639. doi:10.1016/j.jsams.2013.08.006

Heuberger JAAC, Gal P, Stuurman FE, de Muinck Keizer WAS, Mejia Miranda Y, Cohen AF. Repeatability and predictive value of lactate threshold concepts in endurance sports. PLOS ONE. 2018;13(11):e0206846.

Hopkins WG. Measures of reliability in sports medicine and science. Sports Medicine. 2000;30(1):1-15.

Lohse KR, Sainani KL, Taylor JA, Butson ML, Knight EJ, Vickers AJ. Systematic review of the use of "magnitude-based inference" in sports science and medicine. PLOS ONE. 2020;15(6):e0235318.

Pallares JG, Moran-Navarro R, Ortega JF, Fernandez-Elias VE, Mora-Rodriguez R. Validity and reliability of ventilatory and blood lactate thresholds in well-trained cyclists. PLOS ONE. 2016;11(9):e0163389.

Sainani KL. Reliability statistics. PM&R. 2017;9(6):622-628. doi:10.1016/j.pmrj.2017.05.001

Sainani KL. The problem with "magnitude-based inference". Medicine & Science in Sports & Exercise. 2018;50(10):2166-2176. doi:10.1249/MSS.0000000000001645

Swinton PA, Hemingway BS, Saunders B, Gualano B, Dolan E. A statistical framework to interpret individual response to intervention: paving the way for personalized nutrition and exercise prescription. Frontiers in Nutrition. 2018;5:41.

Examples

series <- subset(lactate_tests, athlete == "cyclist_series")
before <- lactate_threshold(subset(series, day == 0), intensity, lactate, heart_rate, plot = FALSE)
after <- lactate_threshold(subset(series, day == 58), intensity, lactate, heart_rate, plot = FALSE)
compare_tests(before, after)

What could make two tests hard to compare

Description

What could make two tests hard to compare

Usage

comparison_notes(before, after)

Arguments

before, after

The lactater attributes of the two tests.

Value

a character vector of notes (empty when nothing stands out).


Compute thresholds

Description

Fits the user-selected curve and runs the requested methods. For internal use only.

Usage

compute_thresholds(data, settings, method)

Arguments

data

The data retrieved from prepare_data().

settings

The analysis settings.

method

The method categories to run.

Value

a list with the user curve, the result rows (with method_category) and the plot_data for each row.


Convert intensity to pace

Description

Usage

convert_to_pace(intensity, sport = "swimming")

Arguments

intensity

The intensity to convert.

sport

The sport.

Value

the pace, rounded to 0.1 s.


Layers of a method drawn on the fitted curve

Description

The curve, plus the Dmax line and the gap to it, the Bsln+ baseline and rise, or the IAT rise from LTratio. For internal use only.

Usage

curve_layers(plot_data, intensity, lactate, colours, sport)

Arguments

plot_data

What the method drew on: the fitted curve (with a Dmax line, a Bsln+ baseline, or the IAT from point), a breakpoints fit, or a ratio curve, and the fitting label.

colours

The plot colours.

sport

The sport, for the unit of the LTratio label.

Value

a list with the layers, the lactate y where the threshold sits, and the top of the drawing.


Points along a fitted curve

Description

Points along a fitted curve

Usage

curve_points(curve, from = curve$domain[1], n = 200)

Arguments

curve

A curve from fit_curve().

from

Where to start (default: the start of the curve's domain).

n

Number of points.

Value

a data frame with intensity and lactate.


Did the lactate curve shift, or change its shape?

Description

Tests whether two tests of the same athlete differ only by a horizontal shift of the lactate curve, or also in its shape. Both tests are fitted with the exponential plus constant, lactate = a + b * exp(c * intensity) (Hughson et al., 1987), in which a pure horizontal shift by d changes only b:

Usage

curve_shape(before, after, level = 0.95)

## S3 method for class 'lactate_shape'
autoplot(object, ...)

Arguments

before, after

The results of lactate_threshold() for the two tests, with the same sport and at least five stages each.

level

The confidence level of the shift. Defaults to 0.95.

object

The result of curve_shape().

...

Not used.

Details

The two fits are compared with an extra-sum-of-squares F test (Motulsky & Ransnas, 1987) and with AICc, the Akaike criterion corrected for small samples (Hurvich & Tsai, 1989); the verdict is "shape changed" only when both prefer the free shape. The shift d comes with a profile confidence interval.

Read the result with care:

Value

a lactate_shape list with shift (intensity units, positive when the curve moved to the right), shift_low and shift_high, f, df1, df2, p_value, aicc_shift and aicc_free, verdict ("shape changed" when the F test and AICc both prefer the free shape, "shift" otherwise: none was detected), the peak lactate of both tests and the two exponential curves. Print it for a summary, and plot it with autoplot(): the two fitted curves, and the first one moved by the shift (dashed).

References

Faude O, Kindermann W, Meyer T. Lactate threshold concepts: how valid are they? Sports Medicine. 2009;39(6):469-490.

Hurvich CM, Tsai CL. Regression and time series model selection in small samples. Biometrika. 1989;76(2):297-307. doi:10.1093/biomet/76.2.297

Hughson RL, Weisiger KH, Swanson GD. Blood lactate concentration increases as a continuous function in progressive exercise. Journal of Applied Physiology. 1987;62(5):1975-1981.

Maassen N, Busse MW. The relationship between lactic acid and work load: a measure for endurance capacity or an indicator of carbohydrate deficiency? European Journal of Applied Physiology and Occupational Physiology. 1989;58(7):728-737.

Motulsky HJ, Ransnas LA. Fitting curves to data using nonlinear regression: a practical and nonmathematical review. FASEB Journal. 1987;1(5):365-374.

Wackerhage H, Kabasakalis A, Seiler S, Heck H. Is the vLamax for glycolysis what the VO2max is for oxidative phosphorylation? Sports Medicine. 2025;55(8):1853-1866.

Examples

series <- subset(lactate_tests, athlete == "cyclist_series")
before <- lactate_threshold(subset(series, day == 0), intensity, lactate, plot = FALSE)
after <- lactate_threshold(subset(series, day == 58), intensity, lactate, plot = FALSE)
curve_shape(before, after)

Shift of the whole lactate curve between two tests

Description

How far the fitted lactate curve moved between two tests: the intensity gained at each lactate value, and, when intensity is given, the lactate saved at each intensity. A rightward shift (more intensity for the same lactate) is the classic sign of better endurance; check the peak lactate too (see compare_tests()).

Usage

curve_shift(before, after, lactate = seq(1.5, 4, by = 0.5), intensity = NULL)

Arguments

before, after

The results of lactate_threshold() for the two tests, with the same sport.

lactate

Lactate values (mmol/L) at which to compare the intensity. Values outside either curve are NA. Defaults to seq(1.5, 4, by = 0.5).

intensity

Intensities at which to compare the lactate, or NULL.

Value

a tibble with lactate, intensity_before, intensity_after and shift; with intensity, rows with intensity, lactate_before, lactate_after and lactate_change are added.

Examples

series <- subset(lactate_tests, athlete == "cyclist_series")
before <- lactate_threshold(subset(series, day == 0), intensity, lactate, plot = FALSE)
after <- lactate_threshold(subset(series, day == 58), intensity, lactate, plot = FALSE)
curve_shift(before, after)

Demo data

Description

A dataset containing the lactate and heart rate data collected from a step-incremental test as an example, with a simulated rating of perceived exertion

Usage

demo_data

Format

A data frame with 8 rows and 6 variables:

step

the number of the step, starting at zero for the baseline.

length

the length of each step.

intensity

the intensity performed on each step (in this case in watts).

lactate

the blood lactate concentration.

heart_rate

the heart rate associated with that step.

rpe

the rating of perceived exertion (Borg 6-20). Simulated: about 11 at 2 mmol/L and 14 at 4 mmol/L, close to the means of 2,560 adults at the first rise in lactate (10.8) and at 4 mmol/L (14.1; Scherr et al., 2013), and 19 at the last stage.


Dmax point

Description

Finds where the curve lies furthest below the straight line from ⁠(x0, y0)⁠ to ⁠(x1, y1)⁠. The perpendicular distance is the vertical distance times a constant, so both peak at the same intensity. For internal use only.

Usage

dmax_point(curve, x0, y0, x1, y1)

Arguments

curve

A function returning the fitted lactate at given intensities.

x0, y0

The start of the line.

x1, y1

The end of the line.

Value

the intensity, or NA when the curve never dips below the line.


The position text of the zones, once per domain

Description

Zones 1 and 2 of the five-zone model are both "below LT1": the text is drawn once, centered over the two.

Usage

domain_spans(bands, span)

Arguments

bands

The zone bands of a plot: from, to, description.

span

The width of the plot, in intensity.

Value

a data frame with from, to and description (empty where there is no room for it).


The two splits inside the moderate and the severe domain (five-zone model)

Description

The two splits inside the moderate and the severe domain (five-zone model)

Usage

domain_splits(split, lt, info)

Arguments

split

"relative", "seiler", or two intensities.

lt

LT1 and LT2.

info

The lactater attribute of the results.

Value

the zone 1|2 and zone 4|5 intensities.


Fit a continuous piecewise-linear model by exhaustive breakpoint search

Description

For fixed breakpoints the model ⁠y = b0 + b1 x + sum(c_j max(x - psi_j, 0))⁠ is linear, so the residual sum of squares can be computed for every candidate. All breakpoint positions on a grid are tried and the best one is refined locally. Unlike iterative fits (e.g. segmented), the result is the global least-squares optimum (up to the grid) and is deterministic. For internal use only.

Usage

fit_breakpoints(x, y, n_breakpoints = 2, flat_first = FALSE, min_points = 2)

breakpoint_design(x, psi, flat_first = FALSE)

predict_breakpoints(fit, newx)

Arguments

x, y

The measured points.

n_breakpoints

Number of breakpoints (1 or 2).

flat_first

Whether the first segment is flat (slope fixed at 0).

min_points

Minimum number of measured points in each segment.

psi

The breakpoints.

fit

A fit from fit_breakpoints() or fit_two_lines().

newx

Where to evaluate the fitted lines.

Value

a list with the breakpoints psi, the coefficients, x, y, and flat_first.


Fit a lactate curve

Description

Fits the lactate curve used by the threshold methods. For internal use only.

Usage

fit_curve(
  points,
  fit = c("3rd degree polynomial", "4th degree polynomial", "B-spline", "Exponential")
)

Arguments

points

A data frame with intensity and lactate.

fit

The curve type.

Details

Value

a list with the fit type, the points, the domain (intensity range of the points), the coefficients, predict, a function returning the fitted lactate at given intensities, and, for the fits that are linear in their parameters, the leverage of each point.


Fit two independent straight lines that share a division point

Description

Each measured point in turn is the division point, which belongs to both segments (Beaver et al., 1985). Each segment gets its own least-squares line, and the division with the lowest total residual sum of squares wins (Jamnick et al., 2018). The breakpoint psi is where the two lines cross. For internal use only.

Usage

fit_two_lines(x, y, min_points = 3)

Arguments

x, y

The measured points, sorted by x.

min_points

Minimum number of points per line, the division point included. A line through two points has no residuals, so fewer than three would favor the shortest segments.

Value

a list with the breakpoint psi (NA when the lines cross outside the range of x), the intercept and slope of each line (lines, one row per line), x, and y.


Format an intensity with the decimals of its sport

Description

Format an intensity with the decimals of its sport

Usage

format_intensity(x, sport)

Arguments

x

Intensities.

sport

The sport.


Format a pace as minutes and seconds

Description

Format a pace as minutes and seconds

Usage

format_pace(seconds, sport)

Arguments

seconds

The pace in seconds.

sport

The sport, which sets the distance unit.

Value

e.g. "4:30 /km".


The look of lactater's tables

Description

Gives a gt table the look of the tables in lactater's reports and articles, so your own tables can match them: IBM Plex Sans, the full width of the page, bold column labels over one dark rule, light rules between rows, bold row groups, and no fills or colors. Needs the gt package. options(lactater.web_font = FALSE) leaves out the link to the web font (IBM Plex Sans is then used when installed), which keeps a self-contained page small.

Usage

gt_lactater(table, size = 14)

Arguments

table

A gt table.

size

The font size, in pixels.

Value

the gt table.

Examples


gt::gt(head(demo_data)) |>
  gt_lactater()


The heart rate at a threshold, labeled beside its point

Description

Its value over "at LT1", right of the point and under the line, where no stage sits, or left of it and above near the right edge or the bottom of the panel. The offsets are in points, so the label hugs its point at any plot size. For internal use only.

Usage

hr_point_label(x, y, lt, xlim, ylim, colours)

Arguments

x, y

The point: the threshold and its heart rate.

lt

"LT1" or "LT2".

xlim, ylim

The limits of the panel.

colours

The plot colours.

Value

an annotation_custom() layer.


A table of the report

Description

A table of the report

Usage

html_table(table, size = 13, compact = FALSE)

Arguments

table

A gt table.

size

The font size, in pixels.

compact

Whether to set the rows closer, for the tables kept for the record.

Value

its HTML.


Decimals shown for the intensity of each sport

Description

Whole watts, km/h to 0.1 and m/s to 0.01: finer than that is below what a step test can resolve.

Usage

intensity_digits(sport)

Arguments

sport

The sport.


Training intensity distribution label

Description

Training intensity distribution label

Usage

intensity_distribution(share)

Arguments

share

The fractions of time in zones 1, 2 and 3.

Value

"polarized", "pyramidal", "threshold", "high intensity", or "other".


Intensity axis label

Description

Intensity axis label

Usage

intensity_label(sport)

Arguments

sport

The sport.


An intensity with its unit

Description

An intensity with its unit

Usage

intensity_text(x, sport)

Arguments

x

An intensity.

sport

The sport.


Intensity unit of each sport

Description

Intensity unit of each sport

Usage

intensity_unit(sport)

Arguments

sport

The sport.


Lactate axis breaks

Description

Whole numbers: every 1, 2 or 4 mmol/L depending on the range.

Usage

lactate_breaks(top)

Arguments

top

The top of the axis.


Check a lactate threshold analysis

Description

Runs a set of checks on the test and the results of lactate_threshold(), so problems that are easy to miss in a table of numbers are flagged: too few stages, no resting sample, thresholds that were not reached or sit at the edge of the tested range, an early lactate dip or a final plateau, a test that may have stopped before the second lactate turn point, and methods that rely on fixed blood lactate concentrations used on another sample type.

Usage

lactate_checks(x)

Arguments

x

The result of lactate_threshold().

Value

a tibble with one row per check: check, status ("ok", "note", or "warning"), and details. It prints as a checklist, with a tick for each check that passed.

References

Hofmann P, Pokan R, von Duvillard SP, Seibert FJ, Zweiker R, Schmid P. Heart rate performance curve during incremental cycle ergometer exercise in healthy young male subjects. Medicine & Science in Sports & Exercise. 1997;29(6):762-768.

Bonaventura JM, Sharpe K, Knight E, Fuller KL, Tanner RK, Gore CJ. Reliability and accuracy of six hand-held blood lactate analysers. Journal of Sports Science and Medicine. 2015;14:203-214.

Foxdal P, Sjodin B, Rudstam H, Ostman C, Ostman B, Hedenstierna GC. Lactate concentration differences in plasma, whole blood, capillary finger blood and erythrocytes during submaximal graded exercise in humans. European Journal of Applied Physiology. 1990;61(3-4):218-222.

Examples

results <- lactate_threshold(demo_data, intensity, lactate, heart_rate, plot = FALSE)
lactate_checks(results)

Lactate curve

Description

It retrieves the lactate curve for plotting purposes.

Usage

lactate_curve(
  .data,
  intensity_column,
  lactate_column,
  heart_rate_column,
  fit = c("3rd degree polynomial", "4th degree polynomial", "B-spline"),
  include_baseline = FALSE,
  sport = c("cycling", "running", "swimming", "rowing")
)

Arguments

.data

The raw data.

intensity_column

The name of the intensity column.

lactate_column

The name of the lactate column.

heart_rate_column

The name of the heart rate column, if applicable.

fit

The fit you would like to use for plotting the lactate curve. Options are ⁠3rd degree polynomial⁠, ⁠4th degree polynomial⁠, or B-spline.

include_baseline

A boolean to indicate whether to include the resting (baseline) sample in the fit. The resting sample is the row with intensity 0; it is placed one median step before the first stage. Data without a resting sample is fine. Rows with a missing intensity or lactate are dropped with a warning.

sport

The sport at which the incremental test was performed. One of cycling, running, or swimming.

Value

a list with the following elements:

data

a tibble containing the raw data with the columns intensity, lactate, and heart_rate.

lactate_curve

a tibble containing the data with the columns intensity and lactate for plotting the lactate curve according to the fit method chosen.

heart_rate_response

a tibble with the columns intensity and heart_rate at each exercise stage. A line through these points is the interpolation used for the heart rate at each threshold.

Examples

lactate_curve(demo_data, intensity, lactate, heart_rate)

Check the lactate curve and how much each threshold depends on single stages

Description

Regression diagnostics for the fitted lactate curve, adapted to tests of 6 to 10 stages, and a leave-one-out check of every threshold. Print it for a short summary, and plot it with autoplot() for six panels, each with a line on what to look for; they need a tall plot, about 9 x 10 inches, and the article Checking the analysis shows them panel by panel.

Usage

lactate_diagnostics(x)

## S3 method for class 'lactate_diagnostics'
autoplot(object, ...)

Arguments

x

The result of lactate_threshold().

object

The result of lactate_diagnostics().

...

Not used.

Details

Value

a lactate_diagnostics object: a list with stages (one row per stage used in the fit: intensity, lactate, fitted, residual, standardized, leverage, cooks_distance, influential), leave_one_out (method, stage left out, intensity without it, change), jackknife (method, intensity, lowest, highest, jackknife_se), and fits (lactate_fit_quality(), best prediction error first).

References

Bonaventura JM, Sharpe K, Knight E, Fuller KL, Tanner RK, Gore CJ. Reliability and accuracy of six hand-held blood lactate analysers. Journal of Sports Science and Medicine. 2015;14:203-214.

Cook RD. Detection of influential observation in linear regression. Technometrics. 1977;19(1):15-18.

Efron B. The jackknife, the bootstrap and other resampling plans. Philadelphia: SIAM; 1982.

Examples

female <- subset(lactate_tests, test == "cycling_female")
results <- lactate_threshold(female, intensity, lactate, heart_rate, plot = FALSE)
diagnostics <- lactate_diagnostics(results)
diagnostics
## six panels: draw them in a tall window, about 9 x 10 inches
if(interactive()) autoplot(diagnostics)

Goodness of fit of the lactate curves

Description

Fits every available curve to the same points and reports how well each follows the measured lactate, to help choose fit in lactate_threshold(). The statistics are computed on the measured stages (plus the resting sample when include_baseline = TRUE).

Usage

lactate_fit_quality(x)

Arguments

x

The result of lactate_threshold().

Details

A curve with more parameters always follows the points more closely, so the RMSE favors it. The prediction error does not: each stage is predicted by the curve fitted without it, which punishes a curve that bends to follow single points (leave-one-out cross-validation).

Value

a tibble with one row per curve: fit, used (the curve chosen in lactate_threshold()), n_points, parameters, r_squared, rmse and see (standard error of the estimate) in mmol/L, and max_abs_residual and the leave-one-out prediction_error (root-mean-square) in mmol/L.

Examples

results <- lactate_threshold(demo_data, intensity, lactate, plot = FALSE)
lactate_fit_quality(results)

Maximal lactate steady state from constant-load bouts

Description

Finds the maximal lactate steady state (MLSS) from two or more constant-load bouts of at least 30 min on separate days: a bout is steady when blood lactate rises by no more than 1.0 mmol/L from minute 10 to minute 30, and the MLSS is the highest steady bout. Heck et al. (1985), who introduced the test and the term, allowed a rise of 1 mmol/L over the last 20 min of 3 + 25-min runs; the minute 10 to 30 window is that of Beneke (2003). Against the highest steady oxygen uptake, a window from minute 15 or 20 to minute 30 was more accurate (Iannetta et al., 2022); set it with from. With the result of lactate_threshold() for a step test of the same athlete, it also gives how far each threshold lies from the MLSS, your own check of which threshold estimates it.

Usage

lactate_mlss(bouts, x = NULL, rise = 1, from = 10)

## S3 method for class 'lactate_mlss'
autoplot(object, ...)

Arguments

bouts

A data frame with one row per sample: intensity (the load of the bout, in the unit of the step test), time (minutes from the start of the bout) and lactate (mmol/L).

x

The result of lactate_threshold() for a step test of the same athlete, or NULL.

rise

The largest rise in lactate (mmol/L) from minute from to minute 30 of a steady bout. Defaults to 1.

from

The minute the rise is measured from, to minute 30. Defaults to 10 (Beneke, 2003). In 10 men cycling, the MLSS from minute 10 lay 8 W below the highest steady oxygen uptake on average, and from minute 15 or 20 2 W above it, where oxygen uptake was steady in 7 of the 10 rather than 9 (Iannetta et al., 2022).

object

The result of lactate_mlss().

...

Not used.

Details

The lactate at minute from and at minute 30 is interpolated linearly between the samples, so sample at or around both. A bout that ended before minute 30 (exhaustion) is not steady. The true MLSS lies between the highest steady bout and the next bout up, so bouts closer together pin it down better. Thresholds against a measured MLSS land from about 40 W below to 40 W above it on average, depending on the method and the protocol (Hauser et al., 2014; Ploszczyca et al., 2020). Whether the MLSS or the critical power marks the boundary between the heavy and severe domains is debated (Keir et al., 2015; Mattioni Maturana et al., 2016; Jones et al., 2019).

Value

a lactate_mlss list with mlss (the highest steady bout, NA when none was steady), next_bout (the lowest bout above it, which was not steady, or NA), bouts (a tibble with one row per bout: intensity, lactate_from (at minute from), lactate_30, rise, minutes (the last sample), and steady), the samples, rise and from, and, with x, thresholds (a tibble with method, intensity, and the gap to the MLSS in intensity units and in %, positive when the threshold lies above it). Plot it with autoplot(); the article Beyond the step test shows it.

References

Beneke R. Methodological aspects of maximal lactate steady state - implications for performance testing. European Journal of Applied Physiology. 2003;89(1):95-99. doi:10.1007/s00421-002-0783-1

Hauser T, Adam J, Schulz H. Comparison of selected lactate threshold parameters with maximal lactate steady state in cycling. International Journal of Sports Medicine. 2014;35(6):517-521.

Heck H, Mader A, Hess G, Mücke S, Müller R, Hollmann W. Justification of the 4-mmol/l lactate threshold. International Journal of Sports Medicine. 1985;6(3):117-130. doi:10.1055/s-2008-1025824

Iannetta D, Ingram CP, Keir DA, Murias JM. Methodological reconciliation of CP and MLSS and their agreement with the maximal metabolic steady state. Medicine & Science in Sports & Exercise. 2022;54(4):622-632. doi:10.1249/MSS.0000000000002831

Jones AM, Burnley M, Black MI, Poole DC, Vanhatalo A. The maximal metabolic steady state: redefining the 'gold standard'. Physiological Reports. 2019;7(10):e14098.

Keir DA, Fontana FY, Robertson TC, Murias JM, Paterson DH, Kowalchuk JM, Pogliaghi S. Exercise intensity thresholds: identifying the boundaries of sustainable performance. Medicine & Science in Sports & Exercise. 2015;47(9):1932-1940. doi:10.1249/MSS.0000000000000613

Mattioni Maturana F, Keir DA, McLay KM, Murias JM. Can measures of critical power precisely estimate the maximal metabolic steady-state? Applied Physiology, Nutrition, and Metabolism. 2016;41(11):1197-1203. doi:10.1139/apnm-2016-0248

Ploszczyca K, Jazic D, Piotrowicz Z, Chalimoniuk M, Langfort J, Czuba M. Comparison of maximal lactate steady state with anaerobic threshold determined by various methods based on graded exercise test with 3-minute stages in elite cyclists. BMC Sports Science, Medicine and Rehabilitation. 2020;12:70.

Examples

bouts <- data.frame(
  intensity = rep(c(130, 140, 150), each = 4),
  time = rep(c(5, 10, 20, 30), 3),
  lactate = c(2.4, 2.6, 2.7, 2.8, 3.1, 3.4, 3.8, 4.1, 3.9, 4.5, 5.4, 6.3)
)
results <- lactate_threshold(demo_data, intensity, lactate, plot = FALSE)
mlss <- lactate_mlss(bouts, results)
mlss
mlss$thresholds
## the plot is short and wide: see it in the article Beyond the step test
if(interactive()) autoplot(mlss)

Test-retest reliability of each threshold, from your own repeated tests

Description

The typical error of each threshold, from athletes who did the same test two or more times while their fitness did not change (e.g. a few days apart): the residual standard deviation of a two-way analysis of athletes and tests, which leaves out the mean change from one test to the next (Hopkins, 2000). It is given in intensity units and as a coefficient of variation, 100 * (exp(s) - 1), with s the typical error of the log intensities. Pass the result to compare_tests(cv = ) so that its verdicts use the noise of your own protocol, analyzer and athletes rather than the default values, from trained male cyclists.

Usage

lactate_reliability(x, athlete, test)

Arguments

x

The result of lactate_threshold() with .by, one group per athlete and test (e.g. .by = c("athlete", "test")).

athlete, test

The columns of x that name the athlete and the test (e.g. the test number or date). Tests are matched by their order within each athlete: first, second, and so on.

Details

A typical error from few athletes is itself uncertain: with 10 athletes and 2 tests (9 degrees of freedom) its 90% confidence interval spans 0.73 to 1.65 times its value (from the chi-square distribution of a variance). The result therefore gives the degrees of freedom (df) and that interval for the cv (cv_low, cv_high), as reliability statistics should always come with one (Sainani, 2017), and compare_tests() widens its intervals for a small df. Atkinson & Nevill (1998) recommend more than 40 athletes for a reliability study; with fewer, read the interval.

The bias is the mean change from each test to the next, which the typical error leaves out. Report it beside the random error (Atkinson & Nevill, 1998): a bias that is not near zero is a learning or order effect, or a change in fitness between the tests. The cv assumes that the error grows with the intensity, as it usually does for such measures; with athletes of a similar level the two cannot be told apart, and typical_error is the same noise in intensity units.

Value

a tibble with one row per method: method, n_athletes (with two or more tests), n_tests, bias (the mean change from one test to the next, intensity units), typical_error (intensity units), cv (%) with its 90% confidence interval (cv_low, cv_high), and the degrees of freedom of the typical error (df). Methods with fewer than two such athletes get NA.

References

Atkinson G, Nevill AM. Statistical methods for assessing measurement error (reliability) in variables relevant to sports medicine. Sports Medicine. 1998;26(4):217-238.

Hopkins WG. Measures of reliability in sports medicine and science. Sports Medicine. 2000;30(1):1-15.

Sainani KL. Reliability statistics. PM&R. 2017;9(6):622-628. doi:10.1016/j.pmrj.2017.05.001

Examples

## the same athlete tested twice, a few days apart (simulated here as small changes in lactate)
set.seed(1)
repeats <- do.call(rbind, lapply(1:4, function(a) do.call(rbind, lapply(1:2, function(t) {
  transform(demo_data, athlete = a, test = t, intensity = intensity * (0.9 + 0.05 * a),
            lactate = lactate * exp(rnorm(nrow(demo_data), 0, 0.05)))
}))))
results <- lactate_threshold(repeats, intensity, lactate, method = c("OBLA", "Dmax"), plot = FALSE,
                             .by = c("athlete", "test"))
reliability <- lactate_reliability(results, "athlete", "test")
reliability

Lactate threshold report table

Description

Formats the result of lactate_threshold() as a publication-ready table: thresholds grouped by method family, with the confidence interval when available, and the checks that need attention as notes. Needs the gt package.

Usage

lactate_report(x, title = "Lactate thresholds")

Arguments

x

The result of lactate_threshold().

title

The table title, or NULL for none.

Value

a gt_tbl object (print it, or save it with gt::gtsave()).

Examples


results <- lactate_threshold(demo_data, intensity, lactate, heart_rate, plot = FALSE)
lactate_report(results)


Lactate step tests from different sports, athletes and scenarios

Description

Sixteen incremental step tests in long format, one row per stage, to try every part of lactater on real data: cycling, running, rowing and swimming; elite, trained and untrained athletes; one athlete tested six times over five years; and the cases that need care (an early lactate dip, no resting sample, 5-min steps, a submaximal test). Analyze one test with subset(), or several tests of one sport with .by = test.

Usage

lactate_tests

Format

A tibble with one row per stage and 13 columns:

test

the test: cycling_elite, cycling_female, cycling_early_dip, cycling_series_1 to cycling_series_6, running, rowing, cycling_5min_no_rest, swimming, untrained, submaximal, running_recreational.

athlete

the athlete; the six cycling_series tests share cyclist_series.

sport

cycling, running, rowing, or swimming, as in lactate_threshold(sport = ).

sex

M or F.

body_mass

body mass (kg) on the test day, NA when not reported.

day

days since the athlete's first test (0 for single tests).

description

who was tested, and how.

source

⁠Trinity College Dublin (CC BY 4.0)⁠, ⁠Heuberger et al. 2018 (CC BY 4.0)⁠, or simulated.

step

the stage, 0 for the resting sample.

intensity

power (W) for cycling and rowing, speed (km/h) for running, speed (m/s) for swimming; 0 for the resting sample.

lactate

blood lactate (mmol/L).

heart_rate

heart rate (bpm), NA when not recorded.

rpe

rating of perceived exertion (Borg 6-20), simulated tests only; NA for the real tests.

Details

Twelve tests are real: eleven from the Trinity College Dublin graded exercise test data (3-min stages, lactate sampled at 2 min, YSI analyzer; running on a treadmill at 0% grade; rowing with 1 min of rest between stages) and one from Heuberger et al. (2018) (5-min steps from 175 W). Names, birth dates and test dates were removed. Four tests are simulated, where no open data exists: a swimmer's 7 x 200 m step test (+0.05 m/s per step; Pelarigo et al., 2017), an untrained client tested to exhaustion and stopped early (25 W steps; untrained men peaked near 250 W in Messonnier et al., 2013), and a recreational runner on the protocol of Roecker et al. (1998) (treadmill at 2% grade, +2 km/h every 3 min from 6 km/h), so that their race-time equations apply as published. Their lactate rises exponentially through about 2.5 mmol/L near the first threshold, with analyzer scatter of 3% from 2 mmol/L and 5% below (Bonaventura et al., 2015). Their simulated RPE rises with lactate, from about 11 at 2 mmol/L to 14 at 4 mmol/L (close to the cohort means at the first rise in lactate and at 4 mmol/L of Scherr et al., 2013) and 19 to 20 at the last stage. See data-raw/lactate_tests.R.

Source

Trinity College Dublin graded exercise test data. Zenodo. doi:10.5281/zenodo.10841412. License CC BY 4.0.

Heuberger JAAC, Gal P, Stuurman FE, de Muinck Keizer WAS, Mejia Miranda Y, Cohen AF. Repeatability and predictive value of lactate threshold concepts in endurance sports. PLOS ONE. 2018;13(11):e0206846. Data: figshare, doi:10.6084/m9.figshare.7240571. License CC BY 4.0.

References

Bonaventura JM, Sharpe K, Knight E, Fuller KL, Tanner RK, Gore CJ. Reliability and accuracy of six hand-held blood lactate analysers. Journal of Sports Science and Medicine. 2015;14:203-214.

Messonnier LA, Emhoff CAW, Fattor JA, Horning MA, Carlson TJ, Brooks GA. Lactate kinetics at the lactate threshold in trained and untrained men. Journal of Applied Physiology. 2013;114(11):1593-1602.

Pelarigo JG, Machado L, Fernandes RJ, Greco CC, Vilas-Boas JP. Oxygen uptake kinetics and energy system's contribution around maximal lactate steady state swimming intensity. PLOS ONE. 2017;12(2):e0167263.

Roecker K, Schotte O, Niess AM, Horstmann T, Dickhuth HH. Predicting competition performance in long-distance running by means of a treadmill test. Medicine & Science in Sports & Exercise. 1998;30(10):1552-1557.

Scherr J, Wolfarth B, Christle JW, Pressler A, Wagenpfeil S, Halle M. Associations between Borg's rating of perceived exertion and physiological measures of exercise intensity. European Journal of Applied Physiology. 2013;113(1):147-155.

Examples

unique(lactate_tests[c("test", "sport", "description")])
rowing <- subset(lactate_tests, test == "rowing")
lactate_threshold(rowing, intensity, lactate, heart_rate, sport = "rowing", plot = FALSE)

Lactate thresholds from a step test

Description

Estimates the lactate thresholds of a step incremental test with every method at once, or the methods you choose, and returns them with the lactate, heart rate and RPE at each one. How each method is defined, how lactater computes it and what the evidence says is in the article The methods and the evidence; the essentials are under Details.

Usage

lactate_threshold(
  .data,
  intensity_column,
  lactate_column,
  heart_rate_column = NULL,
  rpe_column = NULL,
  rpe_scale = c("6-20", "CR10"),
  method = c("Log-log", "OBLA", "Bsln+", "Dmax", "LTP", "LTratio", "IAT", "Keul",
    "Simon", "Stegmann"),
  fit = c("3rd degree polynomial", "4th degree polynomial", "B-spline"),
  include_baseline = FALSE,
  sport = c("cycling", "running", "swimming", "rowing"),
  loglog_restrainer = 1,
  plot = TRUE,
  dmax_anchor = c("original", "fitted", "observed"),
  moddmax_rise = 0.4,
  ltp_fix_first_slope = FALSE,
  dmax_methods = c("Dmax", "ModDmax", "Exp-Dmax", "Log-Poly-ModDmax", "Log-Exp-ModDmax"),
  fixed = c(2, 2.5, 3, 3.5, 4),
  baseline_plus = c(0.5, 1, 1.5),
  iat_rise = 1.5,
  ltp_breakpoints = 2,
  recovery = NULL,
  stage_duration = NULL,
  last_stage_duration = NULL,
  site = NULL,
  grade = NULL,
  max_intensity = NULL,
  sample = c("blood", "plasma", "sweat", "interstitial fluid"),
  ci = FALSE,
  n_boot = 500,
  ci_level = 0.95,
  seed = NULL,
  lt1 = "Bsln + 0.5",
  lt2 = "Log-Poly-ModDmax",
  .by = NULL
)

Arguments

.data

One row per stage: the intensity, the lactate at the end of the stage and, if recorded, the heart rate and the rating of perceived exertion (RPE). The resting sample, if any, is the row with intensity 0. Rows with a missing intensity or lactate are dropped with a warning.

intensity_column

The name of the intensity column: bare, as a string, or a variable that holds the name.

lactate_column

The name of the lactate column.

heart_rate_column

The name of the heart rate column, if recorded. The heart rate at each threshold is interpolated linearly between the measured stages, and is NA outside them: a threshold there is doubtful anyway (see lactate_checks()). A zone boundary below the first stage, which is common for zone 1|2, is instead extrapolated (see lactate_zones()).

rpe_column

The name of the RPE column, if recorded. The RPE at each threshold is interpolated like the heart rate. A heart rate or RPE column with no values, as when none was written on the recording form, is left out.

rpe_scale

The RPE scale: "6-20" (Borg's RPE scale, default) or "CR10" (Borg's category-ratio scale, 0 to 10, with ratings above 10 allowed for an absolute maximum).

method

The method families to estimate, one or more of "Log-log", "OBLA", "Bsln+", "Dmax", "LTP", "LTratio", "IAT", "Keul", "Simon" and "Stegmann". Defaults to all of them (Keul and Simon only for running, Stegmann only when recovery is given).

fit

The curve fitted to the lactate values: "3rd degree polynomial" (default), "4th degree polynomial" or "B-spline" (a natural cubic regression spline with 4 degrees of freedom; with 6 stages it nearly passes through every point). OBLA, Bsln+ and Stegmann read their thresholds off this curve; the other methods fit their own, as their papers did (see Details). lactate_fit_quality() compares the curves on your data.

include_baseline

Whether the resting sample is part of the curve fitted with fit (used by OBLA, Bsln+, LTratio, IAT and Stegmann). In the fit it is placed one median step before the first stage. Log-log, LTP and the Dmax family fit the exercise stages only. Defaults to FALSE.

sport

One of "cycling" (intensity in W), "running" (km/h), "swimming" (m/s) or "rowing" (W). For running, swimming and rowing a pace column is added: s/km, s/100 m, and the Concept2 split in s/500 m (watts = 2.80 / pace^3, pace in s/m).

loglog_restrainer

Deprecated. Log-log uses all stages, as Beaver et al. (1985) did. A value below 1 still keeps only that share of the first stages, with a warning; it will be removed in a future version.

plot

Whether to draw the plot of each method into a plot column. Defaults to TRUE.

dmax_anchor

Where the ends of the Dmax lines sit: "original" (default) as in each variant's paper, "fitted" on the fitted curve, "observed" at the measured lactate of the start and end stages. See Details.

moddmax_rise

The rise in lactate (mmol/L) between two consecutive stages that marks the first rise in ModDmax. Defaults to 0.4 (Bishop et al., 1998, who measured fingertip plasma lactate); change it for other sample types such as sweat.

ltp_fix_first_slope

Whether the first LTP segment (before LTP1) is flat instead of estimated. Defaults to FALSE.

dmax_methods

Which Dmax variants to estimate when method includes "Dmax": one or more of "Dmax", "ModDmax", "Exp-Dmax", "LTP1-Poly-ModDmax", "Log-Poly-ModDmax" and "Log-Exp-ModDmax". Defaults to all but "LTP1-Poly-ModDmax" (a line from LTP1 instead of Log-log; not part of Jamnick et al., 2018).

fixed

The fixed lactate concentrations (mmol/L) for OBLA. Defaults to c(2, 2.5, 3, 3.5, 4).

baseline_plus

The rises above baseline (mmol/L) for Bsln+. Defaults to c(0.5, 1, 1.5).

iat_rise

The rise (mmol/L) above the lactate at LTratio that defines IAT. Defaults to 1.5 (Dickhuth et al., 1999).

ltp_breakpoints

The number of lactate turn points to fit: 2 (default) for a test to exhaustion, or 1 for a submaximal test that stopped before the second turn point (LTP1 only; LTP2 is NA).

recovery

The lactate samples taken after the end of the test, for Stegmann: a data frame with the columns time (minutes after the end of exercise) and lactate. Sample until lactate has fallen back below its end-of-exercise value; Stegmann et al. (1981) sampled at 1, 2, 5 and 10 min.

stage_duration

The duration of each stage, in minutes, or NULL. Stegmann needs it to put the recovery minutes on the intensity axis, and predict_performance(), reference_percentiles() and compare_tests() check it against their protocols (a different stage length moves the thresholds).

last_stage_duration

The minutes completed in the last stage when the test ended before the stage did, or NULL (the last stage was completed). The intensity of the last stage is then time-weighted: the stage before it plus the completed share of the increment (Kuipers et al., 2003), so 2 of 3 min at 200 W after 175 W count as 191.7 W. Needs stage_duration. The lactate of a short last stage had less time to rise; max_intensity leaves the stage out instead.

site

Where the blood was sampled: "earlobe", "fingertip", "venous", "other", or NULL (not recorded). It does not change the calculations; predict_performance(), reference_percentiles() and compare_tests() check it against their protocols.

grade

The treadmill grade (%) of a running test, or NULL. Checked like site.

max_intensity

Stages above this intensity are left out of every method (an incomplete last stage, or one where lactate plateaus). Defaults to NULL (keep all stages).

sample

The sample the lactate was measured in: "blood" (default), "plasma", "sweat" or "interstitial fluid". It does not change the calculations; lactate_checks() warns when a method relies on fixed blood lactate concentrations that do not transfer to the sample.

ci

Whether to add bootstrap confidence intervals for the intensities (intensity_low, intensity_high). See Details. Defaults to FALSE.

n_boot

Number of bootstrap replicates when ci = TRUE. Defaults to 500.

ci_level

Confidence level when ci = TRUE. Defaults to 0.95.

seed

A seed for the bootstrap replicates, so that the intervals are reproducible, or NULL to draw them from the current random stream (then call set.seed() first for reproducibility).

lt1, lt2

The thresholds that mark LT1 and LT2 for this athlete: a method (e.g. "Log-log", "LTP2") or an intensity. Chosen once here, they are what lactate_zones(), test_report(), the overview plot and the header of compare_tests() and predict_performance() use. Default to "Bsln + 0.5" and "Log-Poly-ModDmax"; see lactate_zones() for why.

.by

Names of columns that identify separate tests ("athlete" or athlete, or c("athlete", "date")). Each test is analyzed on its own and the results are bound together, with the .by columns first.

Details

Log-log

Two straight lines through log(lactate) against log(intensity), split at a measured stage that belongs to both lines, at the split with "the lowest residuals sum of squares" (Beaver et al., 1985; Jamnick et al., 2018), with at least three stages per line, so at least five stages. The threshold is where the lines cross, and the lactate is read off the lines; it is NA when they cross outside the measured stages. Beaver et al. plotted lactate against oxygen uptake; lactater uses the intensity.

OBLA

The intensity at which the fitted curve reaches each concentration in fixed, 2.0 to 4.0 mmol/L by default (Heck et al., 1985; Kindermann et al., 1979; Skinner & McLellan, 1980), solved exactly. When the curve crosses a target more than once (an early lactate dip), the last crossing where lactate rises through it is used: from there on, lactate stays above the target. NA when the curve never reaches it.

Bsln+

The intensity at which the fitted curve is each rise in baseline_plus above the baseline (Berg et al., 1990; Zoladz et al., 1995), solved like OBLA. The baseline is one sample: the resting sample when there is one, otherwise the first stage. Studies differ here: Heuberger et al. (2018) used the mean of the samples before lactate rose, and Jamnick et al. (2018) wrote only "baseline value(s)". One sample carries the analyzer error, about 0.1 mmol/L at low concentrations (Bonaventura et al., 2015): on demo_data, a baseline 0.1 mmol/L lower or higher moves Bsln + 0.5 from 83 W to 77 or 88 W. lactate_diagnostics() shows how much each threshold rests on the baseline sample, and lactate_checks() says when the LT1 methods disagree.

The Dmax family

Every variant takes the point of its curve that lies furthest below a straight line ending at the last stage (the perpendicular distance peaks at the same point as the vertical one). The variants differ in the curve, which fit does not change, and in where the line starts:

With dmax_anchor = "original" (default) the ends of the line sit where each paper put them: on the fitted curve for Dmax and Exp-Dmax ("the two end points of the curve", Cheng et al., 1992), and at the measured lactate for ModDmax ("the LT and the final lactate point", Bishop et al., 1998) and for the end of the Log-log variants ("the final lactate point", Jamnick et al., 2018). "fitted" puts every end on the curve and "observed" every end at the measured values (Heuberger et al., 2018); a line starting at the Log-log threshold or LTP1 always starts on the curve. NA when the curve never dips below the line. The resting sample is never used.

LTP

Three connected straight segments through the exercise stages, with the two breakpoints found by exhaustive search for the lowest residual sum of squares, at least two stages per segment (so at least six stages; four with ltp_breakpoints = 1, which fits LTP1 only). ltp_fix_first_slope = TRUE keeps the first segment flat. LTP1 is the first rise of lactate above resting levels and LTP2 the second, accelerated rise (Hofmann et al., 1997; Pokan et al., 1997; Hofmann & Tschakert, 2017). The original papers fitted each turn point in its own window, on tests with 1-min or 90-s stages; with the 6 to 10 stages of a step test the windows hold too few stages, so lactater fits the three segments together. The fit has 6 parameters, so with 6 or 7 stages a single stage can move the turn points a lot: lactate_checks() says so, and lactate_diagnostics() shows by how much.

LTratio and IAT

LTratio is the intensity where lactate / intensity is lowest on a B-spline through the stages (Dickhuth et al., 1999). IAT, the individual anaerobic threshold of Dickhuth et al. (1999), is the intensity iat_rise (1.5 mmol/L) above the lactate at LTratio, on the same spline (as described by Heuberger et al., 2018). Like OBLA and Bsln+, IAT uses an absolute lactate step, so it assumes blood lactate.

Keul and Simon

The individual anaerobic thresholds of Keul et al. (1979) and Simon et al. (1981), for running only: the speed at which a 3rd degree polynomial through the exercise stages rises by 1.26 mmol/L per km/h (Keul; a tangent at 51°34') or by 1 mmol/L per km/h (Simon; 45°). Keul et al. took the mean slope at 4 mmol/L of 60 tests of 20 cross-country skiers; Simon et al. took 45° as the point of the largest change in slope. Both tested on a treadmill at a 5% grade with 3-min stages of 2 km/h, sampling earlobe blood, and set the slope on that protocol. A slope in watts or m/s has no counterpart in these papers, so the methods run only with sport = "running". When the slope reaches the value more than once, the last crossing where it rises through it is used; NA when it never does within the stages.

Stegmann

The individual anaerobic threshold of Stegmann et al. (1981). After the test, blood lactate first rises and then falls. From the point in recovery where it is back at its end-of-exercise value, a tangent is drawn to the lactate curve of the test; the point of contact is the threshold, and the slope of the tangent the maximal rate of lactate elimination. Stegmann et al. plotted lactate against time, so lactater puts the recovery minutes on the intensity axis at the rate the test rose (the median increment between stages divided by stage_duration), interpolates the return to the end-of-exercise value between the recovery samples after the post-exercise peak, and draws the tangent to the curve fitted with fit. When lactate never falls back to its end value, or never rises after the test, the threshold is NA.

Bootstrap intervals

With ci = TRUE, each threshold gets an approximate percentile bootstrap interval for the noise of the samples. Each replicate keeps the curve fitted to the data and gives every stage its own residual with a random sign (a wild bootstrap; Wu, 1986; Davidson & Flachaire, 2008), because the error of a lactate sample grows with its concentration; the residuals are scaled for their leverage and widened for the few degrees of freedom a step test leaves. Every method is then run again on each replicate, and the bounds are rounded outward. Replicates on which a method fails are left out, with a warning. The interval does not include the bias of the fitted curve, errors that are the same in every sample (analyzer bias, sampling site), or the day-to-day variation of the athlete, which is larger (about 3.5-4% of the intensity; Heuberger et al., 2018; see compare_tests()). In simulated tests, 95% intervals held the true threshold in 84% to 97% of tests, depending on the method and the analyzer error; how they were calibrated is in Checking the analysis.

Value

a lactate_thresholds tibble (plot it with autoplot() or plot()) with one row per threshold:

method_category

the method family.

method

the method.

fitting

the curve the method used.

intensity

the intensity at the threshold, in whole watts, km/h to 0.1 or m/s to 0.01; NA when the method found no threshold in the tested range.

intensity_low, intensity_high

with ci = TRUE, the confidence interval of the intensity.

pace

for running, swimming and rowing, the pace at the threshold.

lactate

the lactate at the threshold.

heart_rate

the heart rate at the threshold, when heart_rate_column is given.

rpe

the RPE at the threshold, when rpe_column is given.

plot

the plot of the method, when plot = TRUE.

With .by, the columns that identify each test come first.

References

Beaver WL, Wasserman K, Whipp BJ. Improved detection of lactate threshold during exercise using a log-log transformation. Journal of Applied Physiology. 1985;59(6):1936–40.

Heck H, Mader A, Hess G, Mücke S, Müller R, Hollmann W. Justification of the 4-mmol/l Lactate Threshold. International Journal of Sports Medicine. 1985;06(03):117–30.

Kindermann W, Simon G, Keul J. The significance of the aerobic-anaerobic transition for the determination of work load intensities during endurance training. European Journal of Applied Physiology and Occupational Physiology. 1979;42(1):25–34.

Skinner JS, Mclellan TH. The Transition from Aerobic to Anaerobic Metabolism. Research Quarterly for Exercise and Sport. 1980;51(1):234–48.

Bonaventura JM, Sharpe K, Knight E, Fuller KL, Tanner RK, Gore CJ. Reliability and accuracy of six hand-held blood lactate analysers. Journal of Sports Science and Medicine. 2015;14:203-214.

Berg A, Jakob E, Lehmann M, Dickhuth HH, Huber G, Keul J. Current aspects of modern ergometry. Pneumologie. 1990;44(1):2–13.

Zoladz JA, Rademaker AC, Sargeant AJ. Non-linear relationship between O2 uptake and power output at high intensities of exercise in humans. The Journal of Physiology. 1995;488(1):211–7.

Cheng B, Kuipers H, Snyder A, Keizer H, Jeukendrup A, Hesselink M. A New Approach for the Determination of Ventilatory and Lactate Thresholds. International Journal of Sports Medicine. 1992;13(07):518–22.

Bishop D, Jenkins DG, Mackinnon LT. The relationship between plasma lactate parameters, Wpeak and 1-h cycling performance in women. Med Sci Sports Exerc. 1998;30(8):1270–5.

Hughson RL, Weisiger KH, Swanson GD. Blood lactate concentration increases as a continuous function in progressive exercise. Journal of Applied Physiology. 1987;62(5):1975–81.

Jamnick NA, Botella J, Pyne DB, Bishop DJ. Manipulating graded exercise test variables affects the validity of the lactate threshold and VO2peak. PLOS ONE. 2018;13(7):e0199794.

Davidson R, Flachaire E. The wild bootstrap, tamed at last. Journal of Econometrics. 2008;146(1):162-169. doi:10.1016/j.jeconom.2008.08.003

Wu CFJ. Jackknife, bootstrap and other resampling methods in regression analysis. The Annals of Statistics. 1986;14(4):1261-1295. doi:10.1214/aos/1176350142

Heuberger JAAC, Gal P, Stuurman FE, de Muinck Keizer WAS, Mejia Miranda Y, Cohen AF. Repeatability and predictive value of lactate threshold concepts in endurance sports. PLOS ONE. 2018;13(11):e0206846.

Hofmann P, Tschakert G. Intensity- and Duration-Based Options to Regulate Endurance Training. Front Physiol. 2017;8:337.

Hofmann P, Pokan R, von Duvillard SP, Seibert FJ, Zweiker R, Schmid P. Heart rate performance curve during incremental cycle ergometer exercise in healthy young male subjects. Med Sci Sports Exerc. 1997;29(6):762–8.

Pokan R, Hofmann P, Von Duvillard SP, et al. Left ventricular function in response to the transition from aerobic to anaerobic metabolism. Med Sci Sports Exerc. 1997;29(8):1040–7.

Dickhuth H-H, Yin L, Niess A, et al. Ventilatory, Lactate-Derived and Catecholamine Thresholds During Incremental Treadmill Running: Relationship and Reproducibility. International Journal of Sports Medicine. 1999;20(02):122–7.

Kuipers H, Rietjens G, Verstappen F, Schoenmakers H, Hofman G. Effects of stage duration in incremental running tests on physiological variables. International Journal of Sports Medicine. 2003;24(7):486–91. doi:10.1055/s-2003-42020

Stegmann H, Kindermann W, Schnabel A. Lactate kinetics and individual anaerobic threshold. International Journal of Sports Medicine. 1981;2(3):160–5. doi:10.1055/s-2008-1034604

Keul J, Simon G, Berg A, Dickhuth HH, Goerttler I, Kübel R. Bestimmung der individuellen anaeroben Schwelle zur Leistungsbewertung und Trainingsgestaltung. Deutsche Zeitschrift für Sportmedizin. 1979;30(7):212–8.

Simon G, Berg A, Dickhuth HH, Simon-Alt A, Keul J. Bestimmung der anaeroben Schwelle in Abhängigkeit vom Alter und von der Leistungsfähigkeit. Deutsche Zeitschrift für Sportmedizin. 1981;32(1):7–14.

Examples

results <- lactate_threshold(demo_data, intensity, lactate, heart_rate, plot = FALSE)
results
lactate_checks(results)
autoplot(results, method = "Dmax")

Lactate thresholds by group

Description

Runs lactate_threshold() once per group and binds the results, with the group columns first. For internal use only.

Usage

lactate_threshold_by(.data, .by, arguments)

Arguments

.data

The raw data.

.by

The grouping column names.

arguments

The other lactate_threshold() arguments.


Thresholds over a series of tests

Description

Fits a straight line through each threshold over three or more tests of the same athlete, to show the trend with its uncertainty instead of reading noise from one test to the next.

Usage

lactate_trend(..., day, level = 0.9)

## S3 method for class 'lactate_trend'
autoplot(
  object,
  method = NULL,
  view = NULL,
  cv = NULL,
  lt1 = NULL,
  lt2 = NULL,
  ...
)

Arguments

...

The results of lactate_threshold(), in any order. A list of results can be spliced in with ⁠!!!⁠.

day

The date of each test, in the same order as ...: dates, or days since the first test.

level

The confidence level of the interval of the trend, and of the noise band in the "thresholds" view. Defaults to 0.9, as in compare_tests().

object

The result of lactate_trend().

method

The methods to plot in the "thresholds" view. Defaults to ModDmax, IAT, OBLA 4.0 and Dmax, which have a published test-retest variation (see compare_tests()), when present.

view

"curves", the default for up to five tests: the lactate curves of all tests (the latest in blue, the first in dark gray, the tests between in light gray), to see where along the intensity range the curve moved, then their heart rate with LT1 and LT2 as filled points (the latest test labeled), above the five training zones of each test from lt1 and lt2. "thresholds", the default for more tests: one panel per method, each test as a dot (with its own bootstrap interval when computed, at the ci_level of lactate_threshold()) and a gray band for the noise of the method around the first test, at the level of lactate_trend(), so that dots outside the band are changes larger than the noise. Every test is one more look: with nothing changed, at least one dot falls outside a 90% band in 24% of series with three later tests, 33% with five and 43% with eight, so one dark dot in a long series is weak evidence, and the trend line of lactate_trend() is the answer for the series.

cv

The test-retest coefficient of variation (%) for the noise band, as in compare_tests().

lt1, lt2

The thresholds that mark LT1 and LT2 in the "curves" view. Default to those each test chose in lactate_threshold().

Value

a lactate_trend tibble with one row per method found in every test: method, tests, the change per 30 days (per_30_days) with its confidence interval at level (low, high), and the first and last fitted values. Plot it with autoplot(): up to five tests as their lactate curves and training zones, more as each threshold over time.

References

Hopkins WG. Measures of reliability in sports medicine and science. Sports Medicine. 2000;30(1):1-15.

Examples

series <- subset(lactate_tests, athlete == "cyclist_series")
days <- c(0, 163, 1107, 1865, 2046)
tests <- lapply(days, function(d) {
  lactate_threshold(subset(series, day == d), intensity, lactate, plot = FALSE)
})
## the test dates are not in the data: an example start date
trend <- lactate_trend(!!!tests, day = as.Date("2019-01-14") + days)
trend
autoplot(trend, lt1 = "Bsln + 0.5", lt2 = "ModDmax")
autoplot(trend, view = "thresholds")

Training zones from two lactate thresholds

Description

Builds three or five training zones from a first (LT1) and a second (LT2) lactate threshold, with the zone boundaries placed at the boundaries of the exercise intensity domains: below LT1 is the moderate domain, from LT1 to LT2 the heavy domain, and above LT2 the severe domain (Jamnick et al., 2020). You choose which thresholds mark LT1 and LT2.

Usage

lactate_zones(x, lt1 = NULL, lt2 = NULL, model = 3, split = "relative")

## S3 method for class 'lactate_zones'
autoplot(
  object,
  heart_rate = getOption("lactater.heart_rate", "panel"),
  view = c("curve", "ruler"),
  ...
)

Arguments

x

The result of lactate_threshold().

lt1, lt2

The thresholds that mark LT1 and LT2: a method in x$method, or an intensity. Default to those chosen in lactate_threshold(), which are "Bsln + 0.5" and "Log-Poly-ModDmax" unless you set them; see Details.

model

The number of zones: 3 (default) or 5.

split

How to split the moderate and severe domains in the five-zone model: "relative" (default), "seiler", or two intensities. Ignored with three zones.

object

The result of lactate_zones().

heart_rate

How the plot shows the heart rate of the stages, when the test has it: "panel" (default), in a panel under the curve on the same intensity axis, read at LT1 and LT2; "axis", on the lactate plot against a second axis, as many lab reports do; or "none". options(lactater.heart_rate = "axis") sets it for every plot and report.

view

"curve" (default), the zones on the lactate curve; or "ruler", one bar of the zones over the intensity, pace, heart rate and RPE at each boundary, to hand to the athlete.

...

Not used.

Details

LT1 is usually a "first rise" threshold ("Log-log", "Bsln + 0.5", "Bsln + 1.0", "LTratio", "LTP1"), and LT2 a threshold aimed at the maximal lactate steady state (MLSS) or the second, abrupt rise (the Dmax family, "LTP2", "IAT", "Stegmann", "Keul" and "Simon" (running), and "Bsln + 1.5", which estimated the MLSS in two studies; Jamnick et al., 2020). These are two of the three categories of threshold concepts of Faude et al. (2009); fixed values such as "OBLA 4.0" are the third. Against a measured MLSS these landed from about 40 W below to 40 W above it on average, by method, protocol and athletes (Hauser et al., 2014; Ploszczyca et al., 2020), and whether the MLSS or the critical power marks the boundary between the heavy and severe domains is debated (Jones et al., 2019). Different choices give different zones, so report which ones you used.

The default pair has the best evidence for each boundary and moves least with a single stage. Bsln + 0.5 is among the most defensible markers of the moderate-heavy boundary (Jamnick et al., 2020), and Log-Poly-ModDmax estimated the MLSS with 4-min stages (Jamnick et al., 2018). Left out one stage at a time on the cycling tests of lactate_tests, Bsln + 0.5 moved by a median 1.7% (jackknife standard error) and Log-Poly-ModDmax by 8.6%, against 23% for LTP1, whose three-segment fit has few stages to spare in a step test. Bsln + 0.5 rests on a single baseline sample, so check that sample (see lactate_diagnostics()).

Value

a lactate_zones tibble with one row per zone: zone, domain, description, intensity_from, intensity_to, the matching pace range (running, swimming, rowing), the lactate range on the fitted curve (NA at a boundary outside the stages, where the curve is not extrapolated), and the heart_rate and rpe ranges (when available; a boundary below the first stage is extrapolated from the first three stages). The first zone has no lower bound and the last no upper bound. Plot it with autoplot().

References

Faude O, Kindermann W, Meyer T. Lactate threshold concepts: how valid are they? Sports Medicine. 2009;39(6):469-490.

Hofmann P, Tschakert G. Intensity- and duration-based options to regulate endurance training. Frontiers in Physiology. 2017;8:337.

Bishop DJ, Beck B, Biddle SJH, et al. Physical activity and exercise intensity terminology: a joint American College of Sports Medicine (ACSM) expert statement and Exercise and Sport Science Australia (ESSA) consensus statement. Medicine & Science in Sports & Exercise. 2025;57(11):2599-2613. doi:10.1249/MSS.0000000000003795

Glass S, Dwyer GB, editors. ACSM's metabolic calculations handbook. Baltimore: Lippincott Williams & Wilkins; 2007.

Hauser T, Adam J, Schulz H. Comparison of selected lactate threshold parameters with maximal lactate steady state in cycling. International Journal of Sports Medicine. 2014;35(6):517-521.

Jamnick NA, Botella J, Pyne DB, Bishop DJ. Manipulating graded exercise test variables affects the validity of the lactate threshold and VO2peak. PLOS ONE. 2018;13(7):e0199794.

Jamnick NA, Pettitt RW, Granata C, Pyne DB, Bishop DJ. An examination and critique of current methods to determine exercise intensity. Sports Medicine. 2020;50(10):1729-1756.

Jones AM, Burnley M, Black MI, Poole DC, Vanhatalo A. The maximal metabolic steady state: redefining the 'gold standard'. Physiological Reports. 2019;7(10):e14098.

Ploszczyca K, Jazic D, Piotrowicz Z, Chalimoniuk M, Langfort J, Czuba M. Comparison of maximal lactate steady state with anaerobic threshold determined by various methods based on graded exercise test with 3-minute stages in elite cyclists. BMC Sports Science, Medicine and Rehabilitation. 2020;12:70.

Seiler S. What is best practice for training intensity and duration distribution in endurance athletes? International Journal of Sports Physiology and Performance. 2010;5(3):276-291.

Sitko S, Artetxe X, Bonnevie-Svendsen M, et al. What is "zone 2 training"?: experts' viewpoint on definition, training methods, and expected adaptations. International Journal of Sports Physiology and Performance. 2025;20(11):1614-1617. doi:10.1123/ijspp.2024-0303

Tonnessen E, Sylta O, Haugen TA, Hem E, Svendsen IS, Seiler S. The road to gold: training and peaking characteristics in the year prior to a gold medal endurance performance. PLOS ONE. 2014;9(7):e101796.

Examples

results <- lactate_threshold(demo_data, intensity, lactate, heart_rate, plot = FALSE)
lactate_zones(results)
lactate_zones(results, model = 5)
lactate_zones(results, lt1 = "Log-log", lt2 = "ModDmax", model = 5, split = "seiler")

Colors of the current plot style

Description

Colors of the current plot style

Usage

lactater_colours(style = getOption("lactater.style", "light"))

Arguments

style

"light", "night" or "journal".

Value

a named vector of colors.


The leave-one-stage-out grid

Description

One row per method and one column per stage left out, red where the threshold rises without that stage and blue where it falls, darker the further it moves, with the move written in the cells where it is at least 3% of the threshold. For internal use only.

Usage

leave_one_out_panel(object, colours, sport)

Arguments

object

The result of lactate_diagnostics().

colours

The plot colours.

sport

The sport.


A minimal result object from a stored lactater attribute, for helpers that take results

Description

A minimal result object from a stored lactater attribute, for helpers that take results

Usage

list_to_thresholds(info)

Arguments

info

A lactater attribute.


Log-log breakpoint

Description

Fits two straight lines to log(lactate) vs log(intensity) of the measured points and returns the unrounded intensity where they cross. For internal use only.

Usage

loglog_breakpoint(points, loglog_restrainer = 1)

Arguments

points

The measured points (intensity, lactate) used for the fit.

loglog_restrainer

The share of the points to use (see lactate_threshold()).

Value

a list with the breakpoint intensity and the fit from fit_two_lines() (in log-log space).


LTratio minimum

Description

LTratio minimum

Usage

ltratio_minimum(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the B-spline spline, the intensity of the minimum ratio, its value ratio_min, and the ratio curve over the exercise stages.


The threshold: dot, dotted guides to both axes, and the label joined by a curved arrow

Description

The label sits above the threshold, to the left when there is room (above a rising curve the plot is empty) and to the right otherwise; with below, below the curve and to the right, when the threshold leaves room under it. A label steps over (or, below the curve, under) the lines of avoid, the Dmax line and the drawn curve, when one would cross it. For internal use only.

Usage

marker_layers(
  x,
  y,
  label,
  x_range,
  y_range,
  log_axes,
  colours,
  below = FALSE,
  avoid = list()
)

Arguments

x, y

Where the threshold sits.

label

A list with the value and the detail lines.

x_range, y_range

The ranges of the plot.

log_axes

Whether both axes are logarithmic.

colours

The plot colours.

below

Whether to put the label below the curve.

avoid

The lines (intensity, lactate) the label must not cross: the Dmax line and the drawn curve.


Bsln+

Description

It applies the ⁠Bsln+⁠ methods: the intensity at which the fitted curve reaches each rise in settings$baseline_plus (0.5, 1.0, and 1.5 mmol/L by default) above the baseline. The baseline is the first sample: the resting sample when there is one, otherwise the first stage.

Usage

method_bsln_plus(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


Dmax

Description

It applies the Dmax methods: "Dmax", "ModDmax", "Exp-Dmax", "Log-Poly-ModDmax", and "Log-Exp-ModDmax".

Usage

method_dmax(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Details

Every variant looks for the point on the fitted curve that lies furthest below a straight line between two anchors. The variants differ in the fitted curve and in where the line starts:

The line always ends at the last stage. The resting sample is never used. With settings$dmax_anchor = "observed" the line's ends sit at the measured lactate of the start and end stages instead of on the fitted curve; the Log-log and LTP1 starts have no measured value, so they always sit on the curve. "original" follows each paper: on the curve for Dmax and Exp-Dmax, at the measured values for the ModDmax variants.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


Individual Anaerobic Threshold (IAT)

Description

It applies the IAT method: the intensity at which the B-spline curve is settings$iat_rise (1.5 mmol/L by default) above the lactate at LTratio (Dickhuth et al., 1999).

Usage

method_iat(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


Log-log

Description

It applies the Log-log method. Two straight lines are fitted to log(lactate) vs log(intensity), split at the measured stage with the lowest total residual sum of squares, and the threshold is where they cross (see fit_two_lines()). The lactate at the threshold is read off the lines. The resting sample is never used: it has no exercise intensity.

Usage

method_loglog(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


Lactate turn points (LTP)

Description

It applies the LTP methods: LTP1, and LTP2. A three-segment continuous linear regression is fitted to the measured points; the two breakpoints with the lowest residual sum of squares are found by exhaustive search (see fit_breakpoints()). With ltp_fix_first_slope the first segment is flat. Hofmann et al. (1997) and Pokan et al. (1997) fitted each turn point with its own two-line regression inside a window, on tests with 1-min or 90-s stages; with the 6 to 10 stages of a typical step test those windows hold too few stages, so the three segments are fitted together. With settings$ltp_breakpoints = 1 (a submaximal test) two segments are fitted, the breakpoint is LTP1 and LTP2 is NA. The lactate at each turn point is read off the fitted segments. The resting sample is never used: it has no exercise intensity.

Usage

method_ltp(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


Minimum Lactate-Intensity Ratio (LTratio)

Description

It applies the LTratio method: the intensity where lactate / intensity, on a B-spline fit, is lowest. The search runs over the exercise stages only.

Usage

method_ltratio(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


What each method does, in one line

Description

What each method does, in one line

Usage

method_note(method, settings)

Arguments

method

The method label.

settings

The analysis settings.

Value

the plot subtitle.


Onset of Blood Lactate Accumulation (OBLA)

Description

It applies the OBLA methods: the intensity at which the fitted curve reaches each fixed concentration in settings$fixed (2.0, 2.5, 3.0, 3.5, and 4.0 mmol/L by default).

Usage

method_obla(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data for each row.


Method names with their subscripts

Description

Plotmath, so that "ModDmax" is drawn as ModD with a subscript max and "LTratio" as LT with a subscript ratio. Plotmath keeps the labels plain element_text(), which users can restyle. method_plotmath() gives the text to parse (for label_parsed and parse = TRUE), method_label() the parsed expressions, and method_html() the same names for HTML.

Usage

method_plotmath(x)

method_html(x)

method_label(x, bold = FALSE)

Arguments

x

Method names.

bold

Whether to draw them in bold, for titles.

Value

a character vector, or an expression vector.


Method result

Description

The common shape of every ⁠method_*()⁠ result. For internal use only.

Usage

method_result(method, fitting, intensity, lactate, plot_data)

Arguments

method, fitting, intensity, lactate

The result columns.

plot_data

What the plot needs, either one list shared by all rows or a list per row.


Keul and Simon

Description

The individual anaerobic thresholds of Keul et al. (1979) and Simon et al. (1981): the speed at which a 3rd degree polynomial through the exercise stages rises by 1.26 mmol/L per km/h (an angle of 51°34') or by 1 mmol/L per km/h (45°). Both fitted lactate against running speed in km/h, on treadmill tests at a 5% grade with 3-min stages of 2 km/h; a slope in other units has no meaning, so these methods run on running tests only. When the slope reaches the value more than once, the last crossing where it rises through it is used. For internal use only.

Usage

method_slope(data, method, slope)

Arguments

data

The data retrieved from prepare_data().

method

The method name.

slope

The slope (mmol/L per km/h).

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data.


Individual anaerobic threshold of Stegmann et al. (1981)

Description

It applies the Stegmann method: blood lactate is followed into recovery until it falls back to its end-of-exercise value, and from that point a tangent is drawn to the lactate curve of the test; the point of contact is the threshold, and the slope of the tangent the maximal rate of lactate elimination (Stegmann et al., 1981). Lactate is followed over time, so recovery minutes are put on the intensity axis at the rate the test rose (the median increment between stages divided by settings$stage_duration). The time lactate is back at its end-of-exercise value is interpolated linearly between the recovery samples after the post-exercise peak.

Usage

method_stegmann(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the result rows (method, fitting, intensity, lactate) and the plot_data.


Data used for modeling

Description

The exercise stages, plus the positioned resting sample when include_baseline = TRUE. The prepared data keeps the resting sample at intensity 0; only the fit places it one step before stage 1 (see position_rest()).

Usage

modeling_data(.data, include_baseline)

Arguments

.data

The data retrieved from prepare_data().

include_baseline

Whether to include the resting sample.


Build a lactate threshold result

Description

Adds the class and keeps what plotting needs (data, curve, settings, unrounded intensities). For internal use only.

Usage

new_lactate_thresholds(
  out,
  data,
  curve,
  settings,
  plot_data,
  intensity,
  intervals = NULL
)

Arguments

out

The result tibble.

data

The data retrieved from prepare_data().

curve

The user-selected curve.

settings

The analysis settings.

plot_data

What each row's plot needs.

intensity

The unrounded threshold intensities.

intervals

The unrounded confidence intervals (a low/high matrix), or NULL.


A secondary point with a small label

Description

A secondary point with a small label

Usage

other_point(x, y, label, colours)

Arguments

x, y

Where the point sits.

label

The label below it, as plotmath (a quoted string, or e.g. "LT"[ratio]).

colours

The plot colours.


The table beside the overview plot

Description

The table beside the overview plot

Usage

overview_table(rows, colours, sport, intervals)

Arguments

rows

The thresholds (one row each, with row).

colours

The plot colours.

sport

The sport.

intervals

Whether the thresholds have confidence intervals.


The change of LT1 and LT2 in one line each

Description

The change of LT1 and LT2 in one line each

Usage

pair_change(x, info)

Arguments

x

The comparison.

info

The lactater attribute of the first test, whose LT1 and LT2 are followed.

Value

a character vector named LT1 and LT2 (only those chosen by method and found in both tests).


Combine lactate threshold methods into one plot

Description

The one-line note under each title is dropped: it would crowd a grid of small panels.

Usage

plot_methods(plots, ...)

Arguments

plots

The ggplot2 objects to be combined.

...

Additional arguments passed onto patchwork::wrap_plots().

Value

a patchwork object


Overview plot

Description

Every threshold of the test on one intensity axis: the fitted curve on top, and below it one row per method, grouped by what it aims at (the first rise, the second rise, or a fixed lactate), with the threshold as a dot, its value beside it, and its confidence interval as a line (when computed). Two shaded bands, through the curve and the rows, show how far the methods aimed at LT1 and at LT2 lie apart. The title gives LT1 and LT2 when both chosen methods were computed. A table on the right prints the lactate, heart rate, RPE, percentage of the peak intensity and of the highest heart rate at each threshold, with the lactate shaded by its value.

Usage

plot_overview(object, info, chosen = character())

Arguments

object

The result of lactate_threshold().

info

Its lactater attribute.

chosen

The methods that mark LT1 and LT2, drawn in green.


The lactate curves and training zones of a few tests

Description

plot_series() draws every lactate curve (the latest test in blue, the first in dark gray, the tests between in light gray), then the heart rate of the stages in the same colors with LT1 and LT2 as filled points (when the tests have it), above the five zones of each test as one bar that ends at its highest stage, on one intensity axis. series_zones() gives the five zones of each test, and names the test when a threshold is missing. date_labels() names tests by date ("3 Nov 2026") or by day ("Day 58"). For internal use only.

Usage

plot_series(tests, labels, lt1, lt2)

series_zones(tests, labels, lt1, lt2)

date_labels(day)

Arguments

tests

Results of lactate_threshold(), oldest first.

labels

The name of each test, from date_labels().

lt1, lt2

The thresholds that mark LT1 and LT2, as in lactate_zones().

day

Dates, or days since the first test.


Plot one lactate threshold

Description

Draws what the method fitted, the measured stages, and the threshold with a two-line label joined by an arrow: the intensity, then the pace, lactate and heart rate. For internal use only.

Usage

plot_threshold(
  method,
  intensity,
  lactate,
  heart_rate,
  plot_data,
  data,
  settings,
  interval = NULL,
  rpe = NA
)

Arguments

method

The method label.

intensity, lactate, heart_rate, rpe

The threshold values.

plot_data

What the method drew on: the fitted curve (with a Dmax line, a Bsln+ baseline, or the IAT from point), a breakpoints fit, or a ratio curve, and the fitting label.

data

The data retrieved from prepare_data().

settings

The analysis settings (sport).

interval

The confidence interval of the intensity (low, high), or NULL.

Value

a ggplot2 object, or NA when the threshold could not be estimated.


The zones as a ruler

Description

One bar of the zones on the intensity axis, and under each boundary the intensity, pace, heart rate and RPE where one zone ends and the next begins (those the zones have). For internal use only.

Usage

plot_zone_ruler(object, zones, limits, colours)

Arguments

object

The result of lactate_zones().

zones

Its lactater_zones attribute.

limits

The intensity range of the plot.

colours

The plot colours.


A curved arrow that ends on the edge of a dot

Description

The arrow is laid out in inches when the plot is drawn, so it stops at the edge of the dot and points at its center whatever the size of the plot. For internal use only.

Usage

pointer_layer(from, to, bend, colour, gap = 2.6)

Arguments

from, to

The label end and the dot center, in data units (ggplot2 applies the scale transformation).

bend

How far the middle of the arrow bows out, as a share of its length; the sign sets the side.

colour

The arrow color.

gap

Where the arrow stops, in mm from the dot center.


Polarization index (Treff et al., 2019)

Description

Polarization index (Treff et al., 2019)

Usage

polarization_index(share)

Arguments

share

The fractions of time in zones 1, 2 and 3.

Value

the index, 0 when zone 3 is empty, and NA when zone 3 exceeds zone 1.


Position the resting sample

Description

The resting sample (intensity 0) is placed one step before the first stage, so it can be plotted next to the curve and, with include_baseline = TRUE, fitted. The step is the median step between stages, so a missed stage or a short last stage does not shift it.

Usage

position_rest(.data)

Arguments

.data

The data retrieved from prepare_data().

Value

.data with the resting intensity replaced; unchanged when there is no resting sample.


Predict performance from a step test

Description

Estimates race times in running and time-trial power in cycling from the thresholds of a step test, with a 95% prediction range. Each estimate comes from an equation fitted on one threshold, in one population, with one test protocol, so it only applies to tests run the same way:

Usage

predict_performance(x, method = NULL, equation = NULL)

## S3 method for class 'lactate_performance'
autoplot(object, ...)

Arguments

x

The result of lactate_threshold().

method

The thresholds to predict from. Defaults to every threshold of x that has an equation.

equation

Your own equation(s) from calibrate_performance(), one or a list of them, used in place of the published ones.

object

The result of predict_performance().

...

Not used.

Details

Other thresholds are not offered with the published equations: an equation fitted on one threshold gives wrong numbers for another.

The 95% range is the prediction interval of the regression: the predicted intensity +/- t x the standard error of the estimate x sqrt(1 + 1/n + (threshold - mean)^2 / ((n - 1) * sd^2)). Roecker et al. (1998) reported neither that error nor the mean of the thresholds per distance, so the error is derived from their tables as ⁠SD x sqrt(1 - r^2)⁠, with the SD of race speed computed from the SD of race time, and the last term is left out.

Expect an individual error of about +/- 10% of race speed in running and +/- 25 W in cycling with the published equations. A warning is given when the steps of the test differ from those of the source study (a different step changes the threshold: Coen et al., 2001; Kuipers et al., 2003), and when a threshold lies outside the range of the source sample. Stage duration, sampling site and treadmill grade are checked too when the test was analyzed with stage_duration, site and grade (see lactate_threshold()); otherwise compare them with the protocol in the printed header. A recent race is a better predictor of the next one than any step test (Vickers & Vertosick, 2016).

Value

a lactate_performance tibble with one row per estimate: event, method, threshold, the predicted mean race intensity (speed or power, in the unit of the sport) with its 95% range (intensity_low, intensity_high), for races over a distance the time with its range (time_low, time_high), and the source. Plot it with autoplot(); the article Predicting performance shows it.

References

Coen B, Urhausen A, Kindermann W. Individual anaerobic threshold: methodological aspects of its assessment in running. International Journal of Sports Medicine. 2001;22(1):8-16.

Heuberger JAAC, Gal P, Stuurman FE, de Muinck Keizer WAS, Mejia Miranda Y, Cohen AF. Repeatability and predictive value of lactate threshold concepts in endurance sports. PLOS ONE. 2018;13(11):e0206846.

Kuipers H, Rietjens G, Verstappen F, Schoenmakers H, Hofman G. Effects of stage duration in incremental running tests on physiological variables. International Journal of Sports Medicine. 2003;24(7):486-491.

Roecker K, Schotte O, Niess AM, Horstmann T, Dickhuth HH. Predicting competition performance in long-distance running by means of a treadmill test. Medicine & Science in Sports & Exercise. 1998;30(10):1552-1557.

Sjödin B, Jacobs I. Onset of blood lactate accumulation and marathon running performance. International Journal of Sports Medicine. 1981;2(1):23-26.

Vickers AJ, Vertosick EA. An empirical study of race times in recreational endurance runners. BMC Sports Science, Medicine and Rehabilitation. 2016;8:26.

Examples

runner <- data.frame(
  intensity = c(0, 8, 10, 12, 14, 16, 18),
  lactate = c(1.0, 1.1, 1.2, 1.6, 2.6, 4.6, 8.0)
)
results <- lactate_threshold(runner, intensity, lactate, sport = "running", plot = FALSE)
prediction <- predict_performance(results)
prediction
## the plot is short and wide: see it in the article Predicting performance
if(interactive()) autoplot(prediction)

Prepare raw data

Description

It organizes and renames the raw data.

Usage

prepare_data(
  .data,
  intensity_column,
  lactate_column,
  heart_rate_column = NULL,
  rpe_column = NULL,
  rpe_scale = "6-20"
)

Arguments

.data

The raw data.

intensity_column

The name of the intensity column, as a string.

lactate_column

The name of the lactate column, as a string.

heart_rate_column

The name of the heart rate column, as a string, or NULL.

rpe_column

The name of the rating of perceived exertion column, as a string, or NULL.

rpe_scale

The RPE scale, "6-20" or "CR10", used to check the values.

Value

a tibble with the following columns:

intensity

The intensity column.

lactate

The lactate column.

heart_rate

The heart rate column, if applicable.

rpe

The rating of perceived exertion column, if applicable.


Check and tidy the recovery samples

Description

Check and tidy the recovery samples

Usage

prepare_recovery(recovery, stage_duration)

Arguments

recovery

The recovery argument of lactate_threshold().

stage_duration

The stage_duration argument of lactate_threshold().

Value

a data frame with time and lactate, ordered by time, without missing samples.


Read a recorded step test

Description

Reads a test recorded with the web form of recording_sheet() (an .xlsx with the sheets stages, recovery and test), or a CSV in the columns of the template, and returns what lactate_threshold() and test_report() need. Needs the readxl package for an .xlsx.

Usage

read_recording(file)

Arguments

file

The .xlsx downloaded from the form, or a .csv.

Value

a list with

See Also

recording_sheet() for the form.

Examples

## a test recorded with the CSV template
file <- tempfile(fileext = ".csv")
write.csv(demo_data[c("intensity", "lactate", "heart_rate", "rpe")], file, row.names = FALSE)
test <- read_recording(file)
results <- lactate_threshold(
  test$stages, intensity, lactate, heart_rate, rpe_column = rpe, plot = FALSE
)

The protocol of a recorded test in one line

Description

The protocol of a recorded test in one line

Usage

recording_protocol(fields, sport)

Arguments

fields

The fields of the recording form.

sport

The sport.

Value

a string such as "Lode Excalibur, 3-min stages of +25 W from 50 W, earlobe whole blood, Biosen C-Line", or NULL when nothing was recorded.


A sheet to record a step test

Description

Copies a printable recording sheet, a web form and a CSV template into a folder, so every test is recorded the same way and goes straight into lactate_threshold(). The sheet is an A4 web page (open it in a browser and print it) with the athlete, the 24 to 48 h before the test, the protocol, one row per stage for intensity, lactate, heart rate and RPE, and how the test ended, as Best practices for a step test suggests. The form has the same fields to fill in during the test, saves itself in the browser, and downloads an .xlsx that read_recording() reads, or a CSV of the stages; it works offline and is also on the website. The template has the columns lactater reads, with the resting sample as the first row.

Usage

recording_sheet(dir = ".", open = interactive())

Arguments

dir

The folder to copy them into.

open

Whether to open the form in the browser.

Value

the paths of the sheet, the form and the template, invisibly.

See Also

read_recording() to read a test recorded with the form.

Examples

files <- recording_sheet(tempfile(), open = FALSE)
read.csv(files[["template"]])

Objects exported from other packages

Description

These objects are imported from other packages. Follow the links below to see their documentation.

ggplot2

autoplot()


The percentile of one value among the reference values

Description

Interpolates the nine published percentiles to the age, then the value between them on the normal scale.

Usage

reference_percentile(value, method, unit, sex, age)

Arguments

value

The power (W) or power per kg.

method

"OBLA 3.0" or "Bsln + 1.0".

unit

"W" or "W/kg".

sex

"female" or "male".

age

Age in years.

Value

a list with the percentile (NA beyond the published ones) and the standing.


Thresholds against the population

Description

Places the power of a cycling test at 3 mmol/L ("OBLA 3.0") and at 1 mmol/L above the resting lactate ("Bsln + 1.0") among people of the same sex and age, on the reference percentiles of the German population aged 14 to 64 (Fiedler et al., 2025): in watts and, with body_mass, in watts per kg.

Usage

reference_percentiles(x, age, sex, body_mass = NULL)

Arguments

x

The result of lactate_threshold() for a cycling test.

age

Age in years, from 14 to 64.

sex

"female" or "male" (or "F", "M").

body_mass

Body mass (kg), for the percentiles of power per kg, or NULL.

Details

The percentiles come from 2,389 women and 2,801 men of two national health surveys (KiGGS wave 2 and DEGS1), tested on a cycle ergometer from 25 W with 25 W more every 2 min, at 60-80 rpm, with earlobe blood analyzed on a Biosen C-Line, until 85% of the age-predicted maximal heart rate. Their thresholds were computed with lactater "on a 3rd degree polynomial with the baseline (step 0) data" included, so the two thresholds are computed here in the same way, whatever fit and include_baseline the test was analyzed with: they can differ by a few watts from its table. Read the result with three limits in mind:

Value

a lactate_reference tibble with one row per threshold and unit: method, value, unit, the percentile, and the standing in plain words, from the published percentiles it falls between: "top 5%", "top 10%", "top 25%", "upper half", "lower half", "bottom 25%", "bottom 10%", "bottom 5%", or beyond the 2.5th and 97.5th, "top 2.5%" and "bottom 2.5%".

References

Fiedler J, Thron M, Altmann S, Haertel S, Barsch F, Schlenker L, Krug S, Boes K, Woll A. Reference standards for power at the lactate threshold 2 for cycle ergometry throughout the lifespan in Germany. Journal of Sports Sciences. 2025;43(16):1654-1664. doi:10.1080/02640414.2025.2517913

Bentley DJ, Newell J, Bishop D. Incremental exercise test design and analysis: implications for performance diagnostics in endurance athletes. Sports Medicine. 2007;37(7):575-586.

Examples

results <- lactate_threshold(demo_data, intensity, lactate, heart_rate, plot = FALSE)
reference_percentiles(results, age = 31, sex = "male", body_mass = 72)

Refine the extremum of a function found on a grid

Description

The grid finds the basin of the extremum; optimize() between the neighbours of the best grid point makes it exact. For internal use only.

Usage

refine_extremum(f, grid, values = f(grid), maximum = FALSE, tol = 1e-08)

Arguments

f

The function of one variable.

grid

Where it was evaluated.

values

f(grid), when already computed.

maximum

Whether to look for a maximum.

tol

The tolerance of optimize().

Value

the result of optimize(): minimum or maximum, and objective.


Thresholds relative to body mass, peak intensity and peak heart rate

Description

Expresses each threshold so that different athletes can be compared: per kg of body mass (power only), as a percentage of the highest intensity of the test, and the heart rate as a percentage of the highest heart rate of the test. A fixed lactate concentration falls at a similar percentage of peak power in trained and untrained people (Maassen & Busse, 1989), so percentages of peak do not rank fitness by themselves.

Usage

relative_thresholds(x, body_mass = NULL)

Arguments

x

The result of lactate_threshold().

body_mass

Body mass (kg), or NULL.

Value

x with intensity_per_kg (with body_mass, cycling and rowing), percent_peak and, with heart rate, percent_peak_heart_rate.

References

Maassen N, Busse MW. The relationship between lactic acid and work load: a measure for endurance capacity or an indicator of carbohydrate deficiency? European Journal of Applied Physiology and Occupational Physiology. 1989;58(7):728-737.

Examples

elite <- subset(lactate_tests, test == "cycling_elite")
results <- lactate_threshold(elite, intensity, lactate, heart_rate, plot = FALSE)
relative_thresholds(results, body_mass = elite$body_mass[1])

Report internals

Description

check_report() stops when report is not one; add_section() adds a section that is drawn when the report is saved, so the header can come in any order; ⁠html_*()⁠ draw the parts. For internal use only.

Usage

prepare_series(tests, dates)

check_report(report)

add_section(report, part, draw)

html_page(report)

html_header(report)

html_method(zones, boundary)

html_lede(r)

html_stats(r)

html_figure(zones)

html_plot(plot, caption, height = 3.5)

html_progress(r, cv)

html_reference(r)

html_zones(zones, sport)

html_prediction(r)

html_checks(x)

html_stages(info)

html_methods(x, info)

Arguments

tests

A list of results of lactate_threshold().

dates

Their dates, or days since the first test.

report, r

A report from report_start().

part

The name of the section, for printing.

draw

A function of the report that returns the section's HTML.

zones

The result of lactate_zones().

boundary

1 for LT1, 2 for LT2.

plot

A ggplot or a patchwork, drawn without its title and subtitle.

caption

The text under it.

height

Its height, in inches; it is 7 inches wide.

sport

The sport.

x

The result of lactate_threshold().

info

The lactater attribute of the results.


Build a test report step by step

Description

Start a report with report_start(), then add the parts you want, in the order you want them, and write it with report_save(). Printing a report previews it in the viewer. test_report() builds the default report in one call.

Usage

report_start(x, lt1 = NULL, lt2 = NULL, dates = NULL)

report_header(report, name = NULL, date = NULL, ..., lab = NULL, logo = NULL)

report_summary(report)

report_zones(report, model = 5, chart = TRUE)

report_progress(report, cv = NULL)

report_reference(report)

report_prediction(report)

report_checks(report)

report_test(report, protocol = NULL)

report_methods(report)

report_text(report, title, text)

report_page_break(report)

report_save(
  report,
  file = "lactate-test-report.html",
  pdf = FALSE,
  open = interactive()
)

Arguments

x

The result of lactate_threshold(), or a list of two to five of them for a series of tests of one athlete.

lt1, lt2

The thresholds that mark LT1 and LT2, as in lactate_zones().

dates

For a series, the date of each test: dates, or days since the first test. Without them, the names of x are read as dates.

report

A report from report_start().

name

The athlete's name, the heading of the report.

date

The date of the test, as you want it printed.

...

Other details to list under the name, as label = value, e.g. sex = "female", age = 27, club = "Tuebingen RC". body_mass (kg, a number) also gives the thresholds per kg.

lab

Your lab, institution or business, printed at the top.

The path to a logo image (PNG or JPEG), printed at the top right.

model

3 or 5 zones.

chart

Whether to draw the zones chart above the table.

cv

The typical error of the methods, as in compare_tests(); the defaults cover only some methods.

protocol

One line on how the test was done: ergometer, stages, sampling site, analyzer.

title

The heading of a section of your own.

text

Its text: one string per paragraph.

file

Where to write the report, an .html file.

pdf

Whether to print a PDF next to the HTML file as well. Needs the pagedown package and Google Chrome or Chromium.

open

Whether to open the report in the browser.

Details

To report a series of tests of one athlete, give report_start() a list of tests and their dates: the sections above then describe the latest test, and report_progress() shows the progress over all of them.

Value

a lactate_test_report to add to, and for report_save() the path of the report, invisibly.

Examples


results <- lactate_threshold(
  demo_data, intensity, lactate, heart_rate, rpe_column = rpe, plot = FALSE
)
report <- report_start(results) |>
  report_header(
    name = "Demo athlete", date = "3 November 2026", sex = "male", age = 31, body_mass = 72,
    lab = "Exercise physiology lab"
  ) |>
  report_summary() |>
  report_zones(model = 3) |>
  report_text("Training", "Two sessions a week in zone 2, one in zone 3.")
report_save(report, tempfile(fileext = ".html"), open = FALSE)


Retrieve heart rate

Description

Heart rate at given intensities, interpolated linearly between the measured stages. A straight line through the whole test misses the bend of the heart-rate curve at high intensities (2-6 bpm on demo_data). Outside the stages (e.g. below the first one) it is NA.

Usage

retrieve_heart_rate(raw_data, intensity_value)

Arguments

raw_data

The raw data, with a heart_rate column.

intensity_value

The intensity values.

Value

the heart rate (bpm, rounded) at each intensity.


Retrieve intensity

Description

Retrieves the intensity at which the fitted lactate curve reaches a given lactate value, by solving the fitted curve. When the curve crosses the value more than once (an early lactate dip, or a plateau at the end), the last crossing where lactate rises through the value is used: from there on, lactate stays above it.

Usage

retrieve_intensity(curve, lactate_value)

Arguments

curve

A curve from fit_curve().

lactate_value

The lactate value.

Value

the intensity associated with the estimated lactate threshold, or NA when the curve never rises through lactate_value within the tested range.


Retrieve lactate

Description

Retrieves the fitted lactate at given intensities, rounded to 0.1 mmol/L.

Usage

retrieve_lactate(curve, intensity_value)

Arguments

curve

A curve from fit_curve().

intensity_value

The intensity values.

Value

the lactate associated with the estimated lactate threshold


Retrieve RPE

Description

The rating of perceived exertion at given intensities, interpolated linearly between the measured stages like the heart rate, to 0.1. For internal use only.

Usage

retrieve_rpe(raw_data, intensity_value)

Arguments

raw_data

The raw data, with a heart_rate column.

intensity_value

The intensity values.

Value

the RPE at each intensity.


Measured stages and the resting sample

Description

The stages as open dots; the resting sample, placed one step before the first stage, fainter and labeled. For internal use only.

Usage

stage_layers(points, data, colours, label_rest = TRUE, rest_below = FALSE)

Arguments

points

The stages to draw (intensity, lactate), with the resting sample positioned.

data

The data retrieved from prepare_data(), to tell the resting sample apart.

colours

The plot colours.

label_rest

Whether to label the resting sample.

rest_below

Whether that label goes under the dot instead of over it.


Measured stages layer

Description

The measured stages as open dots.

Usage

stage_points(points, colours, outline = colours[["curve"]])

Arguments

points

The stages to draw (intensity, lactate).

colours

The plot colours.

outline

The outline color.


Layers of the Stegmann method

Description

The fitted curve, the recovery samples placed after the last stage (labeled with their minutes), the point where lactate is back at its end-of-exercise value, and the tangent from there to the curve. For internal use only.

Usage

stegmann_layers(plot_data, colours)

Arguments

plot_data

The method's plot data, with the curve and the tangent.

colours

The plot colours.

Value

a list with the layers, the lactate y where the threshold sits, the top and the x_range.


The Stegmann tangent

Description

The Stegmann tangent

Usage

stegmann_tangent(data, curve, settings)

Arguments

data

The data retrieved from prepare_data().

curve

The user-selected curve from fit_curve().

settings

The analysis settings.

Value

a list with the contact intensity (or NA when recovery lactate never fell back to its end-of-exercise value after a rise), the back time (minutes after exercise), the point from which the tangent is drawn, the recovery samples placed on the intensity axis, and the maximal elimination rate (mmol/L/min).


Target label

Description

A lactate value as it appears in a method name, with at least one decimal (4 gives "4.0", 2.25 gives "2.25").

Usage

target_label(x)

Arguments

x

Lactate values.


A report of a step test, as a web page

Description

Writes the default report of a step test in one call: the header with the athlete's details, LT1 and LT2 with what they mean, the training zones, the predicted performance, what to double-check, the test, and every method. Open it in a browser, or print it to PDF from there, or set pdf = TRUE: two A4 pages, the answer and the zones on the first, the record on the second (a section moves to the next page whole). To choose the sections, their order, or to add your lab's name and logo, build the report step by step with report_start(). Needs the gt package.

Usage

test_report(
  x,
  file = "lactate-test-report.html",
  athlete = list(),
  lt1 = NULL,
  lt2 = NULL,
  protocol = NULL,
  notes = NULL,
  pdf = FALSE,
  open = interactive(),
  dates = NULL
)

Arguments

x

The result of lactate_threshold(), or a list of two to five of them with their dates.

file

Where to write the report, an .html file.

athlete

A named list for the header, passed to report_header(), e.g. list(name = "Ana Silva", date = "3 November 2026", sex = "female", age = 27, body_mass = 61).

lt1, lt2

The thresholds that mark LT1 and LT2, as in lactate_zones().

protocol

One line on how the test was done: ergometer, stages, sampling site, analyzer. Not shown for a series.

notes

Free text for the end of the report.

pdf

Whether to print a PDF next to the HTML file as well. Needs the pagedown package and Google Chrome or Chromium.

open

Whether to open the report in the browser.

dates

With a list of two to five tests in x, the date of each test (dates, or days since the first test). The report then starts with the progress over the tests, and describes the latest one.

Value

the path of the report, invisibly.

See Also

report_start() to build a report step by step.

Examples


results <- lactate_threshold(
  demo_data, intensity, lactate, heart_rate, rpe_column = rpe, plot = FALSE
)
report <- test_report(
  results,
  file = tempfile(fileext = ".html"),
  athlete = list(name = "Demo athlete", date = "3 November 2026", body_mass = 72),
  protocol = "Cycle ergometer, 3-min stages of +25 W from 50 W, earlobe capillary blood",
  open = FALSE
)


lactater ggplot2 theme

Description

The theme used by every lactater plot: axis lines, no grid, and quiet text, so the lactate curve and the threshold carry the color. Use it to make your own plots match.

Usage

theme_lactater(
  base_size = 11,
  base_family = getOption("lactater.font", ""),
  style = getOption("lactater.style", "light")
)

Arguments

base_size

Base font size, in points.

base_family

Base font family.

style

"light", "night" or "journal".

Details

Two options change every lactater plot:

The subtitle and the caption are text boxes from ggtext, so they wrap by themselves to the width of the plot. Restyle them with ggtext::element_textbox_simple() rather than ggplot2::element_text(), which cannot replace them.

Plots are ggplot2 objects, so anything else can be changed by adding layers, scales or a theme().

Value

a ggplot2 theme.

Examples

library(ggplot2)
ggplot(demo_data, aes(intensity, lactate)) +
  geom_point() +
  theme_lactater()

Threshold label

Description

Threshold label

Usage

threshold_text(
  intensity,
  lactate,
  heart_rate,
  sport,
  interval = NULL,
  rpe = NA
)

Arguments

intensity, lactate, heart_rate, rpe

The threshold values.

sport

The sport, which sets the intensity unit.

interval

The confidence interval of the intensity (low, high), or NULL.

Value

a list with the value (the intensity, e.g. "140 W") and the detail line (the interval, pace, lactate and heart rate, joined with middle dots).


Training load and intensity distribution from the lactate zones

Description

Sorts the samples of training sessions (heart rate, or power or speed) into the zones of lactate_zones(), and returns the time in each zone and the training intensity distribution with its polarization index. It prints a short summary and can be plotted with autoplot().

Usage

training_load(
  data,
  zones,
  column = measure,
  measure = c("heart_rate", "intensity"),
  interval = 1,
  .by = NULL
)

## S3 method for class 'lactate_load'
autoplot(object, ...)

Arguments

data

A data frame with one row per sample, or a numeric vector of samples.

zones

The result of lactate_zones(), with three or five zones.

column

The column of data with the samples. Defaults to the name of measure.

measure

What the samples are: "heart_rate" (bpm; uses the heart rate at the zone boundaries) or "intensity" (power or speed in the unit of the test).

interval

The time between two samples, in seconds. Defaults to 1.

.by

Names of columns that group the samples (e.g. "session" or "week"), kept in the order they appear.

object

The result of training_load().

...

Not used.

Details

The distribution describes a training period, so give it the samples of all the sessions of that period (e.g. .by = "week").

Value

a lactate_load tibble with one row per group: minutes, the minutes in each zone (zone_1, ...), the percentage of time in each zone (zone_1_percent, ...), with five zones the percentage in each domain (moderate_percent, heavy_percent, severe_percent), polarization_index, and distribution.

References

Seiler S. What is best practice for training intensity and duration distribution in endurance athletes? International Journal of Sports Physiology and Performance. 2010;5(3):276-291.

Treff G, Winkert K, Sareban M, Steinacker JM, Sperlich B. The polarization-index: a simple calculation to distinguish polarized from non-polarized training intensity distributions. Frontiers in Physiology. 2019;10:707.

Examples

results <- lactate_threshold(demo_data, intensity, lactate, heart_rate, plot = FALSE)
zones <- lactate_zones(results, lt1 = "Log-log", lt2 = "ModDmax")
## two sessions of heart rate sampled every 5 s
sessions <- data.frame(
  session = rep(c("easy", "intervals"), each = 720),
  heart_rate = c(rep(140, 720), rep(c(150, 185), times = 360))
)
load <- training_load(sessions, zones, interval = 5, .by = "session")
load
autoplot(load)

Label of the user-selected fit

Description

Label of the user-selected fit

Usage

user_fitting(settings)

Arguments

settings

The analysis settings.


Maximal lactate accumulation rate (VLamax)

Description

Estimates the maximal lactate accumulation rate (VLamax) from a short all-out sprint test (typically 15 s), not from the incremental test:

Usage

vlamax(lactate_pre, lactate_post, test_duration = 15, alactic_time = 3.5)

Arguments

lactate_pre

Blood lactate before the sprint (mmol/L).

lactate_post

Blood lactate samples after the sprint (mmol/L); the highest is used.

test_duration

Sprint duration in seconds. Defaults to 15.

alactic_time

Alactic time in seconds. Defaults to 3.5 (Meixner et al., 2024).

Details

VLamax = (La_{peak} - La_{pre}) / (t_{test} - t_{alactic})

VLamax needs its own protocol: a resting lactate sample before the sprint, an all-out sprint, and lactate sampled every minute for several minutes after it (Meixner et al. (2024) used up to 8 min). The highest post-sprint value is the peak.

The alactic time (alactic_time) is the part of the sprint assumed to be covered by phosphocreatine. Fixing it at 3.5 s gave the most reliable VLamax in 50 cyclists (ICC 0.91, CV 3.1%); estimating it from the power curve was much less reliable (CV 12-26%), and a 1-s change in it can change VLamax by up to 26% (Meixner et al., 2024). Protocols vary a lot between studies (Langley et al., 2025), and whether VLamax measures the maximal glycolytic rate is still debated: Clark & Macdermid (2025) found it adds little beyond the sprint power itself. Compare values only within the same protocol, ergometer and lactate analyzer.

The default test_duration of 15 s is the sprint of Meixner et al. (2024) and of most cycling studies. A review of 60 studies recommends a sport-specific all-out sprint of 10-12 s instead, with lactate sampled every minute for 10 min, and calls the rate from 15 s or longer a peak rather than a maximal rate; it also found the use of VLamax to predict performances of 1 min or longer inconclusive (Quittmann, 2026). Set test_duration to your sprint.

vlapeak is the same rise divided by the whole sprint, with no alactic time, vLa_{peak} = (La_{peak} - La_{pre}) / t_{test}, so it is lower than vlamax. Wackerhage et al. (2025) proposed it partly because the alactic-time correction is error-prone. In 182 cyclists and triathletes, both gave similar associations with the lactate thresholds (Röhrs et al., 2026).

Value

a tibble with vlamax and vlapeak (mmol/L/s), lactate_peak and delta_lactate (mmol/L), and glycolytic_time (s).

References

Meixner B, Nusser V, Koehler K, Sablain M, Boone J, Sperlich B. Reliability of power output, maximal rate of capillary blood lactate accumulation, and phosphagen contribution time following 15-s sprint cycling in amateur cyclists. Physiological Reports. 2024;12(10):e16086. doi:10.14814/phy2.16086

Langley J, Haase R, Nitzsche N, Porter M. Methodological approaches in testing maximal lactate accumulation rate - vLamax: a systematic review. Journal of Science and Cycling. 2025;14(1):9. doi:10.28985/1425.jsc.09

Clark B, Macdermid PW. VLamax correlates strongly with glycolytic performance. Research Quarterly for Exercise and Sport. 2025:1-8.

Quittmann OJ. Maximal lactate accumulation rate (cLamax): current evidence and future directions for exercise testing and training. European Journal of Applied Physiology. 2026;126(1):1-36. doi:10.1007/s00421-025-06022-7

Röhrs D, Wagner CM, Reer R, Keiner M. Lactate accumulation rate is associated with lactate turnpoints in cyclists and triathletes: a comparison of vLapeak and vLamaxblood. European Journal of Applied Physiology. 2026. doi:10.1007/s00421-026-06444-x

Wackerhage H, Kabasakalis A, Seiler S, Heck H. Is the vLamax for glycolysis what the VO2max is for oxidative phosphorylation? Sports Medicine. 2025;55(8):1853-1866. doi:10.1007/s40279-025-02259-6

Examples

vlamax(lactate_pre = 1.1, lactate_post = c(8.9, 10.4, 10.9, 10.6, 10.1))

Time-weight the intensity of an incomplete last stage

Description

The stage before it plus the completed share of the increment: completed + t / T * increment (Kuipers et al., 2003).

Usage

weight_last_stage(intensity, share)

Arguments

intensity

The intensities, in increasing order.

share

The share of the last stage that was completed (t / T).

Value

intensity with its last value replaced.


Time in zone and intensity distribution from the minutes in each zone

Description

Time in zone and intensity distribution from the minutes in each zone

Usage

zone_summary(minutes, domain)

Arguments

minutes

The minutes in each zone.

domain

The domain of each zone ("moderate", "heavy", "severe").

Value

a one-row data frame.


Heart rate or RPE at zone boundaries

Description

Interpolated between the stages, like the thresholds; a boundary below the first stage (e.g. the zone 1|2 split) is extrapolated from a straight line through the first three stages, where heart rate and RPE rise about linearly with intensity. For internal use only.

Usage

zone_value(data, column, x, digits, lowest = 0)

Arguments

data

The data retrieved from prepare_data().

column

"heart_rate" or "rpe".

x

The boundary intensities.

digits

Decimals.

lowest

The lowest value the measure can take.