MFO computes, from indirect-calorimetry data, the maximal fat oxidation rate (MFO), the exercise intensity at which it occurs (Fatmax), the Fatmax zone, the crossover point, and the SIN model of fat oxidation kinetics (Chenevière et al., 2009). It is aimed at sport and health scientists who run graded exercise tests and want a reproducible, documented alternative to hand-made spreadsheets.
The development version (0.2.x, recommended) is installed from GitHub:
# install.packages("pak")
pak::pak("JorgeDelro/MFO")The CRAN release is still 0.1.0 (2022) and lacks most of the features described below:
install.packages("MFO")One participant is described by three recordings, exported from the
gas analyser at a fixed sampling interval (step_time, in
seconds) with columns VO2 and VCO2 (mL/min),
RER and HR (bpm):
| Recording | Argument | Used for |
|---|---|---|
| Resting metabolism (15–30 min) | db_basal |
Most stable 5-min window → resting VO2, RER, CHO, FAT, kcal |
| Incremental MFO test | db_MFO |
Steady-state average of each step → fat oxidation curve |
| Maximal graded test | db_graded |
VO2max = highest mean over vo2max_window s (default
30), to express intensity as %VO2max (or pass VO2max
directly) |
The package ships one anonymised example participant
(basal_df, MFO_df,
VO2max_df).
library(MFO)
data(list = c("basal_df", "MFO_df", "VO2max_df"), package = "MFO")
result <- MFO(
step_time = 20, # one record every 20 s
db_MFO = MFO_df,
db_basal = basal_df,
db_graded = VO2max_df,
cv_var = "RER", # stability criterion for the resting window
author = "Frayn", # stoichiometric equations
method = "P3", # curve-fitting method
step_duration = 180, # 3-min steps
steady_state_duration = 60, # average the last minute of each step
body_mass = 75,
HRmax = 190
)
#> Warning in MFO(step_time = 20, db_MFO = MFO_df, db_basal = basal_df, db_graded
#> = VO2max_df, : 1 step(s) with negative fat oxidation detected (RER > 1.0
#> artifact).
result$MFO # g/min
#> [1] 0.316712
result$FAT_MAX # %VO2max
#> [1] 36.17038
result$VO2max # ml/min, highest 40-s mean (30 s rounded up to 2 records)
#> [1] 3315
c(result$FatMax_zone_lower, result$FatMax_zone_upper)
#> [1] 24.19065 48.34069result$MFO_plot
The kinetics of the curve are described with the SIN model:
kin <- MFO_kinetics(result$MFO_db)
unlist(kin[c("d", "s", "t", "FatMax_SIN", "Fatmin", "R_squared")])
#> d s t FatMax_SIN Fatmin R_squared
#> -0.3848798 0.9096423 0.3551958 36.1821171 76.4426627 0.9654434kin$MFO_kinetics_plot
To process a whole folder of participants and write a summary table,
see ?MFOs.
Fat (FAT) and carbohydrate (CHO) oxidation in g/min are computed from
VO2 and VCO2 in L/min, assuming negligible protein oxidation during
exercise. Choose the equation set with author:
author |
CHO (g/min) | FAT (g/min) | Source |
|---|---|---|---|
"Frayn" |
4.55 VCO2 − 3.21 VO2 | 1.67 VO2 − 1.67 VCO2 | Frayn (1983) |
"Peronnet" |
4.58519 VCO2 − 3.22597 VO2 | 1.69469 VO2 − 1.70130 VCO2 | Péronnet & Massicotte (1991), derived from Table 1 |
"Jeukendrup" |
4.344 VCO2 − 3.061 VO2 (40–50 %VO2max) 4.210 VCO2 − 2.962 VO2 (50–75 %VO2max) |
1.695 VO2 − 1.701 VCO2 | Jeukendrup & Wallis (2005) |
"Jeukendrup" needs VO2max to pick the CHO equation for
each step. Energy expenditure (Kcal) is CHO × 4 + FAT × 9
kcal/min with energy_equivalents = "classic", the default
in 0.2.x; "source" uses the values of each equation’s
source (Péronnet & Massicotte 3.8683/9.7460 kcal/g, also for Frayn,
who gives none; Jeukendrup & Wallis 3.95 or 4.07/9.75 kcal/g). The
default will change to "source" in 0.3.0. Always report
which equation you used: differences are small for FAT but grow for CHO
and the crossover point.
Each step contributes one point (FAT, %VO2max). method
chooses how the maximum is located:
"P3" (default): third-degree
polynomial \(\mathrm{FAT}(x) = a + bx + cx^2 +
dx^3\), with \(x\) = %VO2max.
Fatmax is the root of \(b + 2cx + 3dx^2 =
0\) that lies within the measured range and gives the highest
FAT. Falls back to P2 with a warning if the cubic cannot be fitted."P2": parabola, Fatmax \(= -b / 2c\). More stable with few steps,
but forces a symmetric curve."MV" (measured values): the step with
the highest observed FAT (author equations). No curve is
fitted: R_squared and RMSE are NA
and the Fatmax zone uses linear interpolation between steps."MRER" (Chenevière et al., 2009):
\(1 - \mathrm{RER} = a_1 + b_1x\) and
\(\mathrm{VO_2} = a_2 + b_2x\) are
fitted as straight lines, so \(\mathrm{FAT} =
1.7\,(1 - \mathrm{RER})\,\mathrm{VO_2}\) is a parabola with
Fatmax at its vertex, \(x = -(a_1b_2 +
a_2b_1)/(2b_1b_2)\). Falls back to MV with a warning if the
vertex is not a maximum inside the measured range. Up to v0.2.0
"MRER" returned the highest observed step, which is now
"MV".For P3, P2 and MRER the fit’s R_squared and
RMSE are returned. The Fatmax zone is the
range of intensities where the fitted FAT is ≥ 90 % of MFO (Achten et
al., 2002). The crossover point is the intensity at
which CHO exceeds FAT, by linear interpolation between steps.
MFO_kinetics() normalises fat oxidation to %MFO (highest
observed step = 100 %) and fits the three-parameter sine model of
Chenevière et al. (2009, eq. 14) directly to the observed steps by
non-linear least squares (minpack.lm::nlsLM), with no prior
smoothing:
\[ \%\mathrm{MFO}(x) = \sin\left\{\left[\frac{\pi^{1/s}}{\pi + 2d}\left(Kx + d + t\right)\right]^{s}\right\}, \qquad K = \frac{\pi}{100} \]
With d = 0, s = 1 and t = 0 the curve is a half sine wave peaking at 50 %VO2max.
Fatmax and Fatmin follow in closed form from the equation: Fatmax =
[(π + 2d)·2^(−1/s) − d − t]/K and Fatmin, the second zero of the curve,
= (π + d − t)/K (fatmin_threshold gives a threshold
instead, e.g. 0.05 of MFO). Chenevière et al. fitted only exercise steps
with RER ≤ 1.0: RER_cutoff = 1 reproduces that selection,
and include_basal = TRUE departs from it. The SIN is
sensitive to how the last step is handled (negative_FAT);
see ?MFO_kinetics.
At high intensity, lactate buffering pushes RER above 1 and the equations return negative FAT. Two arguments control this:
RER_cutoff: steps with RER above this value are removed
before fitting. A number, or by name following Amaro-Gahete et
al. (2019): "classic" (1.00), "safe" (0.95,
the end-of-test RER they recommend), "sedentary" (0.93) or
"trained" (0.90), the mean RER at MFO in those groups. In
the example data they leave 7, 6, 5 and 4 of the 8 steps.negative_FAT: "truncate" negative values
to 0 (default), "exclude" those steps, or
"keep" them (inspection only).| Element | Unit | Meaning |
|---|---|---|
MFO |
g/min | Maximal fat oxidation |
MFO_relative |
mg/kg/min | MFO / body mass (if body_mass given) |
MFO_FFM |
mg/kgFFM/min | MFO / fat-free mass (if fat_free_mass given) |
FAT_MAX |
%VO2max | Intensity eliciting MFO |
FAT_MAX_HR |
bpm | Heart rate at Fatmax (linear interpolation) |
FAT_MAX_porcHRmax |
%HRmax | Fatmax as %HRmax (if HRmax given) |
FatMax_zone_lower, FatMax_zone_upper |
%VO2max | Fatmax zone |
crossover_point |
%VO2max | Intensity where CHO > FAT |
R_squared, RMSE |
– | Goodness of fit |
x_CHO, x_FAT, x_Kcal |
g/min, kcal/min | Resting values |
MFO_db, MFO_plot |
– | Step table and ggplot |
These were found while verifying the code against the primary sources
(see docs/SPEC.md, section 19) and are scheduled for
v0.2.1. The SIN and MRER issues do not affect MFO and Fatmax obtained
with P3 or P2.
Fatmin. Fixed in the development version.method = "MRER" in v0.2.0 took the
observed maximum of 1.7 (1 − RER) VO2, a measured-values method. The
development version implements the two-regression MRER of Chenevière et
al. (2009) and adds method = "MV".include_basal = TRUE (development version).Load column of MFO_db is a step label,
not the measured external load.