| Title: | Reference Values and Interpretation for Pulmonary Function Tests |
|---|---|
| Description: | Computes predicted values and lower/upper limits of normal for pulmonary function tests according to American Thoracic Society (ATS) and European Respiratory Society (ERS) reference standards. Supports spirometry (Global Lung Function Initiative GLI 2012, Quanjer et al. (2012) <doi:10.1183/09031936.00080312>; and the race-neutral GLI 2022 / GLI Global equations, Bowerman et al. (2023) <doi:10.1164/rccm.202205-0963OC>), static lung volumes (GLI 2021, Hall et al. (2021) <doi:10.1183/13993003.00289-2020>), and the carbon monoxide transfer factor / diffusion capacity (GLI 2017, Stanojevic et al. (2017) <doi:10.1183/13993003.00010-2017>, including the 2020 author correction <doi:10.1183/13993003.50010-2017>). Also assigns interpretive pattern labels (Normal, Non-specific, Obstructed, Restricted, Mixed) from spirometry and lung-volume measurements following the ERS/ATS 2022 interpretation algorithm, Stanojevic et al. (2022) <doi:10.1183/13993003.01499-2021>. |
| Authors: | Pat Johnson [aut, cre] (ORCID: <https://orcid.org/0000-0001-8365-1375>), Scott Helgeson [aut] (ORCID: <https://orcid.org/0000-0001-7590-2293>), Zach Quicksall [ctb], Jairo Pena [ctb] |
| Maintainer: | Pat Johnson <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.0.4 |
| Built: | 2026-11-01 03:02:40 UTC |
| Source: | https://github.com/overdodactyl/pft |
Classifies bronchodilator response (BDR) by the percent change in the measured value relative to the patient's predicted value, as recommended by Stanojevic et al. ERJ 2022. Significant BDR is defined as a post-bronchodilator increase of more than 10% of the predicted value in either FEV1 or FVC. This replaces the 2005 standard, which used a >=12% AND >=200 mL change from baseline.
pft_bdr(pre, post, predicted, threshold = BDR_THRESHOLD_PCT_PRED)pft_bdr(pre, post, predicted, threshold = BDR_THRESHOLD_PCT_PRED)
pre |
Numeric vector of baseline measurements obtained before bronchodilator administration. |
post |
Numeric vector of the corresponding measurements obtained
after bronchodilator administration, in the same units and of the
same length as |
predicted |
Numeric vector of predicted (median) values for the
same measure and units, typically the |
threshold |
Percent-of-predicted change considered significant. Defaults to 10 (the Stanojevic 2022 criterion). |
A data frame with one row per input observation and columns:
pct_pred_change: (post - pre) / predicted * 100.
is_significant: logical, TRUE when pct_pred_change > threshold.
NA is propagated wherever any of pre, post, predicted is NA.
pre is the baseline measurement obtained before bronchodilator
administration; post is the corresponding measurement obtained
after bronchodilator administration under the laboratory's
testing protocol. Each pre/post pair must refer to the same
physiologic measure in the same units and belong to the same paired
assessment. The caller identifies which values are pre and post;
pft does not infer test phase from timestamps, values, medication
records, or curve shape, and it interprets the supplied labels
literally.
This function's pct_pred_change column is percent-of-predicted
change (the 2022 criterion). The predecessor pft_bdr_2005() emits a
similarly-named but different column, pct_change, which is
percent-of-baseline change ((post - pre) / pre * 100, the 2005
criterion). The two functions deliberately use distinct column names
so a result frame can carry both without ambiguity.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. See the "Bronchodilator responsiveness testing" section.
pft_spirometry() to obtain the predicted FEV1 / FVC values
used as the denominator. pft_interpret() runs BDR automatically
when <measure>_pre and <measure>_post columns are present.
pft_bdr(pre = 2.5, post = 3.0, predicted = 4.0) # -> 12.5% of predicted change, is_significant = TRUEpft_bdr(pre = 2.5, post = 3.0, predicted = 4.0) # -> 12.5% of predicted change, is_significant = TRUE
Classifies bronchodilator response (BDR) by the Pellegrino et al.
ERJ 2005 dual criterion: significant when the relative change from
baseline is strictly greater than 12% AND the absolute change is
strictly greater than 200 mL. Replaced in 2022 by pft_bdr()'s
simpler "> 10% of predicted" rule.
Both inequalities are strict, matching the paper's disambiguating wording on p. 959: "(>12% of control and >200 mL)". A change that hits either boundary exactly (e.g. exactly 12% or exactly 200 mL) is not significant under this criterion.
pft_bdr_2005(pre, post)pft_bdr_2005(pre, post)
pre, post
|
Numeric vectors of baseline (pre-bronchodilator)
and corresponding post-bronchodilator measurements, in litres,
same length. As with |
A data frame with one row per input observation and three
columns: pct_change (i.e. (post - pre) / pre * 100),
abs_change (i.e. post - pre in litres), and is_significant
(logical, TRUE when pct_change > 12 AND abs_change > 0.2).
NA propagates wherever either of pre / post is NA.
This function's pct_change column is percent-of-baseline
change (the 2005 criterion). The 2022 pft_bdr() emits a
similarly-named but different column, pct_pred_change, which is
percent-of-predicted change ((post - pre) / predicted * 100,
the 2022 criterion). The two functions deliberately use distinct
column names so a result frame can carry both without ambiguity.
>)The Pellegrino et al. ERJ 2005 paper is internally inconsistent on
operator symbols: Table 6 uses inclusive symbols (>=12%,
>=200 mL), while the running text on p. 959 disambiguates the
rule as "(>12% of control and >200 mL)" – strict > on both
criteria. This implementation follows the running-text convention,
matching the more specific of the two source formulations. Callers
who prefer the inclusive Table 6 convention can trivially test
equality themselves against the pct_change and abs_change
columns; a mixed-convention wrapper is out of scope for this
package.
Pellegrino R, Viegi G, Brusasco V, et al. Interpretative strategies for lung function tests. Eur Respir J. 2005;26(5):948-968. doi:10.1183/09031936.05.00035205. Criterion stated in the "Bronchodilator response" section (p. 958) and disambiguated on p. 959; Table 6 uses inclusive symbols.
pft_bdr() for the current Stanojevic 2022 criterion
(>10% of predicted). Unlike the 2022 form, the 2005 version does
not need the patient's predicted FEV1 / FVC – only the pre and
post measurements.
# +25% relative AND +500 mL absolute -> both strict bounds cleared, # SIGNIFICANT under the 2005 criterion. pft_bdr_2005(pre = 2.0, post = 2.5) # +12% relative exactly and +300 mL absolute: NOT significant, because # the 2005 criterion requires strictly >12% (not >=12%). pft_bdr_2005(pre = 2.5, post = 2.8) # +5% relative and +100 mL absolute: neither bound cleared, NOT # significant. pft_bdr_2005(pre = 2.0, post = 2.1)# +25% relative AND +500 mL absolute -> both strict bounds cleared, # SIGNIFICANT under the 2005 criterion. pft_bdr_2005(pre = 2.0, post = 2.5) # +12% relative exactly and +300 mL absolute: NOT significant, because # the 2005 criterion requires strictly >12% (not >=12%). pft_bdr_2005(pre = 2.5, post = 2.8) # +5% relative and +100 mL absolute: neither bound cleared, NOT # significant. pft_bdr_2005(pre = 2.0, post = 2.1)
pft_change() computes the conditional change score (CCS) defined
in Box 2 of the Stanojevic et al. ERS/ATS 2022 interpretation
standard. The CCS evaluates whether the change between two FEV1
z-scores is larger than would be expected from within-subject
variability and regression to the mean alone.
Formula (paper Box 2 p. 12):
Where the autocorrelation r is itself a function of the time
interval between measurements and the patient's age at the first
time point:
Changes within +/- 1.96 change scores are considered within the
normal limits per the paper.
This formula was derived from a children/young-people cohort
(Stanojevic 2022 references the underlying study and notes the
approach has "yet to be validated, extended to adults" but
permits its use as "a reasonable tool to facilitate
interpretation"). For adults the 2022 standard alternatively
recommends FEV1Q (Box 3); see pft_fev1q().
pft_change(z1, z2, age_t1 = NULL, time_years = NULL, r = NULL)pft_change(z1, z2, age_t1 = NULL, time_years = NULL, r = NULL)
z1, z2
|
Numeric vectors of FEV1 z-scores at time 1 and time 2. |
age_t1 |
Numeric. Patient age (in years) at the first measurement. |
time_years |
Numeric. Elapsed time between measurements in years (e.g. 0.25 for 3 months, 4 for 4 years). |
r |
Optional. Numeric in |
A data frame with columns:
ccs: the conditional change score.
r_used: the autocorrelation actually used in the calculation
(returned so callers can audit the value chosen).
is_significant: logical, TRUE when |ccs| > 1.96
(i.e. outside the paper's normal-limits range).
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. Box 2 (p. 12).
pft_spirometry() to produce the FEV1 z-scores at each
time point.
# Stanojevic 2022 Box 2 worked example: a 14-year-old male whose # FEV1 z-score dropped from -0.78 to -1.60 over 3 months. pft_change(z1 = -0.78, z2 = -1.60, age_t1 = 14, time_years = 0.25) # -> r_used = 0.912, ccs ~= -2.17, is_significant = TRUE # Same drop spread over 4 years pft_change(z1 = -0.78, z2 = -1.60, age_t1 = 14, time_years = 4) # -> r_used = 0.762, ccs ~= -1.55, is_significant = FALSE# Stanojevic 2022 Box 2 worked example: a 14-year-old male whose # FEV1 z-score dropped from -0.78 to -1.60 over 3 months. pft_change(z1 = -0.78, z2 = -1.60, age_t1 = 14, time_years = 0.25) # -> r_used = 0.912, ccs ~= -2.17, is_significant = TRUE # Same drop spread over 4 years pft_change(z1 = -0.78, z2 = -1.60, age_t1 = 14, time_years = 4) # -> r_used = 0.762, ccs ~= -1.55, is_significant = FALSE
pft_classify() assigns ATS patterns using spirometry and lung volume data.
By default it applies the Stanojevic et al. ERS/ATS 2022 algorithm
(Figure 8); pass standard = "2005" to apply the predecessor
Pellegrino et al. ERJ 2005 algorithm.
Typically called via pft_interpret() as part of the one-call
workflow; exported for callers who want to apply the classifier to
pre-computed columns directly.
pft_classify( data, standard = c("2022", "2005"), year = 2022, fev1 = fev1, fev1_lln = NULL, fvc = fvc, fvc_lln = NULL, fev1fvc = fev1fvc, fev1fvc_lln = NULL, tlc = tlc, tlc_lln = tlc_lln )pft_classify( data, standard = c("2022", "2005"), year = 2022, fev1 = fev1, fev1_lln = NULL, fvc = fvc, fvc_lln = NULL, fev1fvc = fev1fvc, fev1fvc_lln = NULL, tlc = tlc, tlc_lln = tlc_lln )
data |
A data frame containing the six spirometry input columns
( |
standard |
Which interpretive standard's classifier to apply.
|
year |
GLI year suffix to use when looking up the spirometry
LLN columns ( |
fev1, fev1_lln, fvc, fvc_lln, fev1fvc, fev1fvc_lln, tlc, tlc_lln
|
Column references for the eight inputs. Defaults are the canonical
names ( |
The original data frame with two appended columns:
ats_classification: pattern label. Values depend on the
selected standard; see above.
ats_pattern_combination: a 4-character string in fixed column
order FEV1, FVC, FEV1/FVC, TLC, with "A" denoting the value
is below its LLN, "N" denoting it is at or above, and "?"
denoting the value (and its LLN) was missing. So "NNAN" means
only FEV1/FVC is below its LLN (pure airway obstruction);
"AANA" means FEV1, FVC, and TLC are all low while FEV1/FVC is
preserved (restriction); "NNA?" means FEV1/FVC is below LLN
and TLC is unknown. The pattern-combination string is
independent of the standard selected.
Under the default ERS/ATS 2022 path, a restrictive label is never assigned from spirometry alone, and a low FVC by itself is never labelled restriction:
"Restricted" requires a preserved FEV1/FVC ( LLN)
and TLC below its LLN. A low TLC is the defining criterion
(Stanojevic 2022 Figure 10). FVC is typically, but not
necessarily, also low; rows with a normal FVC and a low TLC (the
NNNA / ANNA combinations, which Figure 8 does not reach) are
labelled "Restricted" on the basis of the low TLC.
"Mixed" requires FEV1/FVC below its LLN and TLC below its
LLN.
A low FVC with a preserved FEV1/FVC and a normal TLC is
"Non-specific", not "Restricted".
When TLC is unavailable, neither "Restricted" nor "Mixed" is
assigned: rows with a preserved ratio are returned as NA, and
rows with a low ratio are labelled "Obstructed" (see "Missing
TLC" below). The spirometry-only PRISm screen is reported
separately by pft_prism().
The "Obstructed" label is a physiologic pattern label (FEV1/FVC
below its LLN); like the other labels, it describes the test result
and is not a disease diagnosis.
Each column-reference argument accepts three forms:
a bare column name – fev1 = my_fev1
a string – fev1 = "my_fev1"
an injected value – fev1 = !!my_var where my_var <- "my_fev1"
Defaults are the canonical pft column names, so callers whose data
already follows the convention pass no extra arguments. The two TLC
references (tlc, tlc_lln) are optional: when either resolves to
a column not present in data, the spirometry-only fallback
triggers without raising an error.
When the three spirometry inputs (fev1, fvc, fev1fvc) and
their LLNs are all present but TLC is missing, pft_classify()
falls back to a spirometry-only branch instead of returning NA.
Under both standards, an "Obstructed" row is still recognisable
from FEV1/FVC < LLN alone (Mixed would require TLC to distinguish
but Mixed is itself an obstructive defect, so the row is labelled
"Obstructed"). Under the 2005 standard, rows with FVC
LLN classify deterministically because the 2005 flowchart does not
consult TLC in that branch (so "Normal" is emitted for normal
spirometry). Cells where TLC would have been the disambiguating
input (Normal vs Restricted, Non-specific vs Restricted under
2022; Normal vs Restricted, Obstructed vs Mixed under 2005) remain
NA. Rows where any spirometry input is itself missing always
return NA. See pft_prism() for the spirometry-only PRISm
screen which is reported as a separate logical column.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. The 2022 classifier follows the spirometry interpretation flowchart in Figure 8 and the pattern definitions in Tables 5 and 8.
Pellegrino R, Viegi G, Brusasco V, et al. Interpretative strategies for lung function tests. Eur Respir J. 2005;26(5):948-968. doi:10.1183/09031936.05.00035205. The 2005 classifier follows Figure 2.
pft_prism() for the spirometry-only PRISm screen (no TLC
required). pft_severity() / pft_severity_2005() grade
per-measure severity. pft_interpret() runs the classifier as
part of the one-call workflow and also accepts the standard
argument for end-to-end reclassification.
data <- data.frame(fev1 = c(3.453, 2.385), fev1_lln_2022 = c(3.303, 3.384), fvc = c(4.733, 3.485), fvc_lln_2022 = c(4.214, 4.24), fev1fvc = c(0.600, 0.827), fev1fvc_lln_2022 = c(0.681, 0.700), tlc = c(1.5, 2.3), tlc_lln = c(2, 2.5)) pft_classify(data) pft_classify(data, standard = "2005") # Column-name override: data using non-canonical names. alt <- data.frame(my_fev1 = 3.0, my_fev1_lln = 2.5, fvc = 4.0, fvc_lln_2022 = 3.5, fev1fvc = 0.65, fev1fvc_lln_2022 = 0.70, tlc = 6.0, tlc_lln = 5.0) pft_classify(alt, fev1 = my_fev1, fev1_lln = my_fev1_lln)data <- data.frame(fev1 = c(3.453, 2.385), fev1_lln_2022 = c(3.303, 3.384), fvc = c(4.733, 3.485), fvc_lln_2022 = c(4.214, 4.24), fev1fvc = c(0.600, 0.827), fev1fvc_lln_2022 = c(0.681, 0.700), tlc = c(1.5, 2.3), tlc_lln = c(2, 2.5)) pft_classify(data) pft_classify(data, standard = "2005") # Column-name override: data using non-canonical names. alt <- data.frame(my_fev1 = 3.0, my_fev1_lln = 2.5, fvc = 4.0, fvc_lln_2022 = 3.5, fev1fvc = 0.65, fev1fvc_lln_2022 = 0.70, tlc = 6.0, tlc_lln = 5.0) pft_classify(alt, fev1 = my_fev1, fev1_lln = my_fev1_lln)
pft_diffusion() computes ATS-compliant upper and lower normal limits
for carbon monoxide measured diffusion capacity and European equivalents
including DLCO (or TLCO), KCO, and VA.
pft_diffusion(data, SI.units = FALSE, sex = sex, age = age, height = height)pft_diffusion(data, SI.units = FALSE, sex = sex, age = age, height = height)
data |
A data frame containing columns for sex ("M","F"),
age (in years, in the range 5-90 per the GLI 2017 spline
tables) and height (in centimeters).
If |
SI.units |
Logical, default |
sex, age, height
|
Column references. By default |
The original data frame with extra columns appended for each measure:
<measure>_pred: predicted (median) value.
<measure>_lln: lower limit of normal (5th percentile).
<measure>_uln: upper limit of normal (95th percentile).
If a <measure>_measured column was supplied in data, two
additional columns are emitted:
<measure>_zscore: LMS z-score ((measured/M)^L - 1) / (L*S).
<measure>_pctpred: percent predicted (measured / pred) * 100.
The caller selects the unit system with SI.units; pft does
not infer the unit system from column names or numeric
magnitude, and it does not convert measured values between unit
systems. Measured diffusion columns must already be in the selected
system:
SI.units |
Measured columns read | Units |
FALSE (default) |
dlco_measured, kco_tr_measured, va_measured |
mL/min/mmHg, mL/min/mmHg/L, L |
TRUE |
tlco_measured, kco_si_measured, va_measured |
mmol/min/kPa, mmol/min/kPa/L, L |
Measured columns belonging to the other unit system are left
untouched and do not produce z-scores (for example, a
dlco_measured column is ignored when SI.units = TRUE). Supplying
values in the wrong units under the right column name (for example,
SI values in dlco_measured) cannot be detected and will produce
incorrect z-scores and percent-predicted values.
The GLI 2017 source publication describes its TLCO/DLCO reference
equations as being for Caucasians: about 85% of the collated data
were from Caucasian participants, and the non-Caucasian data were
insufficient to derive separate equations (Stanojevic et al. 2017).
The race-neutral GLI Global 2022 spirometry equations available in
pft_spirometry() do not make these diffusion equations
race-neutral or multi-ethnic. Users should consider whether the
source reference population is appropriate for the population under
study before interpreting outputs. Ages outside the 5-90 year spline
range return NA reference values.
Stanojevic S, Graham BL, Cooper BG, et al. Official ERS technical standards: Global Lung Function Initiative reference values for the carbon monoxide transfer factor for Caucasians. Eur Respir J. 2017;50(3):1700010. doi:10.1183/13993003.00010-2017. (Author correction: doi:10.1183/13993003.50010-2017, applied here.)
pft_spirometry() and pft_volumes() for the analogous
reference-value functions. pft_severity() grades DLCO impairment
severity from the z-score column produced here. pft_interpret()
composes all three reference functions in one call.
data <- data.frame(sex=c("M","F"), age=c(30,5.1), height=c(178,50)) pft_diffusion(data)data <- data.frame(sex=c("M","F"), age=c(30,5.1), height=c(178,50)) pft_diffusion(data)
Takes per-patient dlco_zscore, va_zscore, and kco_zscore
columns (the outputs of pft_diffusion() when _measured columns
are supplied) and assigns a clinical interpretive category per the
Hughes & Pride 2012 framework adopted by the ERS/ATS Stanojevic
2017 task force.
pft_diffusion_interpret( data, SI.units = FALSE, dlco = NULL, va = NULL, kco = NULL )pft_diffusion_interpret( data, SI.units = FALSE, dlco = NULL, va = NULL, kco = NULL )
data |
A data frame containing the three z-score input columns. |
SI.units |
Logical, default |
dlco, va, kco
|
Column references for the three z-score inputs.
|
The classifier consumes z-scores only and is unit-agnostic, but the
default input column names differ between unit systems. Set
SI.units = TRUE to pick up the SI-units column set
(tlco_zscore, va_zscore, kco_si_zscore); otherwise the
traditional-units column set (dlco_zscore, va_zscore,
kco_tr_zscore) is used. Override individual column names via the
dlco / va / kco arguments.
Typically called via pft_interpret() as part of the one-call
workflow; exported for callers who want to apply the classifier to
pre-computed z-score columns directly.
The original data frame with a single appended column:
diffusion_category. Possible values:
"Normal"All three z-scores above LLN.
"Parenchymal"Low DLCO, low KCO, normal VA.
"Volume loss"Low DLCO, low VA, normal or elevated KCO.
"Mixed"Low DLCO, low VA, low KCO.
"Vascular (suggested)"Low DLCO, normal VA, low or elevated KCO.
"Elevated KCO"Normal DLCO with elevated KCO (z > +1.645).
"Other"Combination not matching any of the above patterns (e.g., low VA in isolation).
NARequired z-score columns missing.
The category labels describe the z-score pattern only; differential diagnosis is left to the clinician (see the Hughes & Pride 2012 source paper for clinical interpretation).
Each column-reference argument accepts three forms:
a bare column name – dlco = my_dlco
a string – dlco = "my_dlco"
an injected value – dlco = !!my_var where my_var <- "my_dlco"
dlco and kco default to NULL, which selects the canonical
name based on SI.units (traditional or SI). Passing an explicit
reference overrides this selection.
Hughes JM, Pride NB. Examination of the carbon monoxide diffusing capacity (DL(CO)) in relation to its KCO and VA components. Am J Respir Crit Care Med. 2012;186(2):132-139. doi:10.1164/rccm.201112-2160CI.
Stanojevic S, Graham BL, Cooper BG, et al. ERS/ATS technical standard: Global Lung Function Initiative reference values for the carbon monoxide transfer factor for Caucasians. Eur Respir J. 2017;50:1700010. doi:10.1183/13993003.00010-2017. (Provides the z-score reference standard whose LLN at z = -1.645 is used here. The clinical interpretation framework is from Hughes & Pride 2012, adopted by the 2017 task force.)
pft_diffusion() to compute the input z-scores;
pft_interpret() for the one-call workflow that auto-runs this
classifier when diffusion outputs are present.
# Three patients: normal, parenchymal (low DLCO/KCO, normal VA), # and volume loss (low DLCO/VA, normal KCO). d <- data.frame( dlco_zscore = c(-0.5, -2.0, -2.0), va_zscore = c(-0.5, -0.5, -2.0), kco_tr_zscore = c(-0.5, -2.0, -0.5) ) pft_diffusion_interpret(d) # SI units (TLCO / KCO_SI). Pass SI.units = TRUE. d_si <- data.frame( tlco_zscore = c(-0.5, -2.0), va_zscore = c(-0.5, -0.5), kco_si_zscore = c(-0.5, -2.0) ) pft_diffusion_interpret(d_si, SI.units = TRUE)# Three patients: normal, parenchymal (low DLCO/KCO, normal VA), # and volume loss (low DLCO/VA, normal KCO). d <- data.frame( dlco_zscore = c(-0.5, -2.0, -2.0), va_zscore = c(-0.5, -0.5, -2.0), kco_tr_zscore = c(-0.5, -2.0, -0.5) ) pft_diffusion_interpret(d) # SI units (TLCO / KCO_SI). Pass SI.units = TRUE. d_si <- data.frame( tlco_zscore = c(-0.5, -2.0), va_zscore = c(-0.5, -0.5), kco_si_zscore = c(-0.5, -2.0) ) pft_diffusion_interpret(d_si, SI.units = TRUE)
Applies the Cotes 1972 hemoglobin correction to a measured carbon
monoxide transfer factor (DLCO or TLCO), returning the value that
would have been measured if the patient's hemoglobin equaled the
age- and sex-specific reference. The correction is intended for
clinical interpretation: after applying it, the corrected DLCO can
be passed to pft_diffusion() (whose reference equations assume
the reference Hb).
pft_dlco_hb_correct(dlco, hemoglobin, sex, age = NA_real_)pft_dlco_hb_correct(dlco, hemoglobin, sex, age = NA_real_)
dlco |
Numeric vector of measured DLCO or TLCO values, any unit system (the correction is multiplicative and unit-agnostic). |
hemoglobin |
Numeric vector of measured hemoglobin in g/L (routine clinical adult range 120-160 g/L). Pass g/L directly; the function does not detect or convert g/dL inputs. |
sex |
Character vector ("M"/"F"). Soft-corrected via
the internal |
age |
Optional numeric vector. When supplied, males aged < 15
yr use the female / child reference (134 g/L) per Stanojevic
2017 p. 11. When omitted (the default |
Stanojevic et al. ERJ 2017 explicitly recommends (Table 4 p. 9) that the published GLI TLCO reference values be uncorrected for Hb, with Hb levels considered separately during interpretation. This function provides that interpretive step.
Formula (Cotes 1972; reformulated from Stanojevic 2017 p. 9, with constants 1.7 and 0.7 reflecting the membrane / capillary diffusing capacity ratio of 0.7 mL min^-1 mmHg^-1 mL-blood^-1):
Reference Hb levels (Stanojevic 2017 p. 11):
Males aged >= 15 yr: 146 g/L
Females (any age) and children < 15 yr: 134 g/L
When hemoglobin == Hb_ref the correction factor is exactly 1
(no adjustment). Anaemic patients (hemoglobin < Hb_ref) receive
an UPWARD correction reflecting that less Hb means less CO uptake
and a depressed measured value.
Numeric vector of Hb-corrected DLCO / TLCO values, same
units as dlco. NA propagates from any input.
Stanojevic S, Graham BL, Cooper BG, et al. Official ERS technical standards: Global Lung Function Initiative reference values for the carbon monoxide transfer factor for Caucasians. Eur Respir J. 2017;50(3):1700010. doi:10.1183/13993003.00010-2017. Hb correction is discussed on p. 9 and the Cotes formula context on p. 11.
Cotes JE, Dabbs JM, Elwood PC, et al. Iron-deficiency anaemia: its effect on transfer factor for the lung (diffusing capacity) and ventilation and cardiac frequency during sub-maximal exercise. Clin Sci. 1972;42:325-335.
pft_diffusion() for the reference-value computation that
consumes the Hb-corrected DLCO.
# An anaemic adult male (Hb = 100 g/L vs reference 146 g/L) with # measured DLCO of 20 mL/min/mmHg has a Hb-adjusted DLCO of # 20 x (1.7 x 146) / (100 + 0.7 x 146) ~= 24.55. pft_dlco_hb_correct(dlco = 20, hemoglobin = 100, sex = "M", age = 40) # No-op when Hb equals reference. pft_dlco_hb_correct(20, hemoglobin = 146, sex = "M") # Children use the female / child reference (134 g/L) regardless # of sex. pft_dlco_hb_correct(20, hemoglobin = 134, sex = "M", age = 10)# An anaemic adult male (Hb = 100 g/L vs reference 146 g/L) with # measured DLCO of 20 mL/min/mmHg has a Hb-adjusted DLCO of # 20 x (1.7 x 146) / (100 + 0.7 x 146) ~= 24.55. pft_dlco_hb_correct(dlco = 20, hemoglobin = 100, sex = "M", age = 40) # No-op when Hb equals reference. pft_dlco_hb_correct(20, hemoglobin = 146, sex = "M") # Children use the female / child reference (134 g/L) regardless # of sex. pft_dlco_hb_correct(20, hemoglobin = 134, sex = "M", age = 10)
A small synthetic cohort of 20 patients spanning the clinical patterns the package classifies (Normal, Obstructed, Restricted, Mixed, Non-specific, PRISm) plus a few bronchodilator-response cases. Demographics are random within plausible ranges; measured values were hand-picked so that each row lands in its target pattern after the GLI reference equations are applied.
pft_examplepft_example
A tibble with 20 rows and 11 columns:
Integer 1-20. Synthetic identifier.
Character: "M" or "F".
Numeric, years.
Numeric, centimetres.
One of "Caucasian", "AfrAm", "NEAsia", "SEAsia".
Measured FEV1 in litres. May be NA.
Measured FVC in litres.
Measured FEV1/FVC ratio.
Measured TLC in litres. May be NA for rows
intended to demonstrate spirometry-only workflows.
Pre-bronchodilator FEV1 in litres. NA when not a BDR scenario.
Post-bronchodilator FEV1 in litres. NA when not a BDR scenario.
No real patient data. Generated by
data-raw/build_pft_example.R. Provided for examples, vignettes,
and quick experimentation.
data-raw/build_pft_example.R (synthetic; no real patient data).
data(pft_example) head(pft_example) pft_interpret(pft_example)data(pft_example) head(pft_example) pft_interpret(pft_example)
Computes the FEV1Q survival index proposed by Miller & Pedersen (ERJ 2010) and discussed as an adult alternative to the conditional change score in Box 3 (p. 13) of the Stanojevic et al. ERJ 2022 interpretation standard. FEV1Q expresses FEV1 in relation to a "bottom line" required for survival, rather than how far an individual's result is from their predicted value; the race-neutral evidence base was consolidated by Balasubramanian et al. (ERJ 2024).
pft_fev1q(fev1, sex, age = NA_real_)pft_fev1q(fev1, sex, age = NA_real_)
fev1 |
Numeric vector of FEV1 measurements in litres. |
sex |
Character vector of patient sex. Accepts the soft-
correctable variants from |
age |
Optional numeric vector. When supplied, rows with |
Formula (Box 3 verbatim):
where and are the
sex-specific 1st percentiles of the FEV1 distribution in adult
lung-disease populations. The index approximates the number of
turnovers remaining of a lower survivable limit of FEV1; values
closer to 1 indicate greater risk of death.
The 2022 standard cautions (running text on p. 13, immediately
preceding Box 3): "FEV1Q is not appropriate for children and
adolescents." When age is supplied,
rows with age < 18 return NA_real_. When age is omitted, the
age guard is skipped and the caller is responsible for restricting
input to adults.
For longitudinal interpretation in adults the 2022 standard
suggests FEV1Q as an alternative to the conditional change score
(see pft_change()): under normal circumstances 1 unit of FEV1Q is
lost approximately every 18 years (every ~10 years in smokers and
the elderly).
Numeric vector of FEV1Q ratios, same length as fev1. NA
propagates from any input.
Miller MR, Pedersen OF. New concepts for expressing forced expiratory volume in 1 s arising from survival analysis. Eur Respir J. 2010;35(4):873-882. doi:10.1183/09031936.00025809. Original proposal of the FEV1Q index and the sex-specific 1st-percentile denominators (0.5 L male, 0.4 L female).
Balasubramanian A, Wise RA, Stanojevic S, Miller MR, McCormack MC. FEV1Q: a race-neutral approach to assessing lung function. Eur Respir J. 2024;63(4):2301622. doi:10.1183/13993003.01622-2023. Race-neutral validation of the FEV1Q index.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. Discusses FEV1Q in Box 3 (p. 13) as an adult alternative to the conditional change score.
pft_change() for the conditional change score (the
children / young-people sibling); pft_severity() for the
z-score-based severity grading.
# Stanojevic 2022 Box 3 worked example: a 70-year-old woman with # FEV1 of 0.9 L has FEV1Q of 0.9 / 0.4 = 2.25. pft_fev1q(0.9, "F", age = 70) # Vectorised across sex. pft_fev1q(c(1.0, 1.0), c("M", "F")) # Adolescents return NA when age is supplied. pft_fev1q(1.0, "F", age = 10)# Stanojevic 2022 Box 3 worked example: a 70-year-old woman with # FEV1 of 0.9 L has FEV1Q of 0.9 / 0.4 = 2.25. pft_fev1q(0.9, "F", age = 70) # Vectorised across sex. pft_fev1q(c(1.0, 1.0), c("M", "F")) # Adolescents return NA when age is supplied. pft_fev1q(1.0, "F", age = 10)
Compute the GOLD 2026 spirometric airflow-limitation grade (GOLD 1-4) from post-bronchodilator FEV1 percent predicted. This helper implements the spirometric grading component of the GOLD COPD framework; it does not diagnose COPD.
pft_gold(fev1_pctpred, fev1fvc = NA_real_)pft_gold(fev1_pctpred, fev1fvc = NA_real_)
fev1_pctpred |
Numeric vector of post-bronchodilator FEV1
percent-predicted values (e.g. the |
fev1fvc |
Optional numeric vector of post-bronchodilator
FEV1/FVC ratios, used to enforce the GOLD 2026 fixed-ratio
prerequisite. When supplied, rows with |
A GOLD grade is not, by itself, a diagnosis of COPD. Per the GOLD
2026 report, a clinical diagnosis of COPD requires clinical context
(symptoms and/or exposure to risk factors) beyond spirometry, and
that clinical assessment is outside the scope of pft. pft_gold()
implements only the spirometric grade defined in Figure 2.10 of the
GOLD 2026 report; it does not implement the GOLD symptom/exacerbation
assessment or any treatment recommendation.
GOLD 2026 grades of airflow limitation, based on post-bronchodilator FEV1 (Figure 2.10 of the GOLD 2026 report, content page 38; labels as given by GOLD):
| Grade | GOLD label | Post-BD FEV1 % predicted |
| GOLD 1 | Mild | >= 80 |
| GOLD 2 | Moderate | >= 50 and < 80 |
| GOLD 3 | Severe | >= 30 and < 50 |
| GOLD 4 | Very severe | < 30
|
GOLD 2026 states the prerequisite "In patients with COPD (FEV1/FVC <
0.7)" explicitly above Figure 2.10's grade table, and the surrounding
text (content p. 37) repeats that a post-bronchodilator FEV1/FVC <
0.7 is required. Supplying fev1fvc enforces this fixed-ratio
spirometric prerequisite only; it does not establish a clinical
diagnosis. Callers wanting an LLN-based airflow-obstruction
criterion instead should use pft_classify() to identify tests
showing airflow obstruction and mask pft_gold() output by hand.
The grade boundaries are those of the GOLD 2026 report. They are not
updated automatically; any change to a future GOLD report would be
introduced in a versioned pft release.
Character vector with values "GOLD 1", "GOLD 2",
"GOLD 3", "GOLD 4", or NA. NA is returned for rows with
missing fev1_pctpred OR (when fev1fvc is supplied) rows that
do not meet the fixed-ratio spirometric prerequisite.
GOLD 2026 defines both the grade and its fixed-ratio prerequisite on
post-bronchodilator spirometry: the grade uses post-bronchodilator
FEV1 percent predicted, and the prerequisite is post-bronchodilator
FEV1/FVC < 0.70. pft_gold() does not infer whether a supplied value
is pre- or post-bronchodilator; the caller is responsible for
supplying post-bronchodilator values for both arguments.
Global Initiative for Chronic Obstructive Lung Disease (GOLD). Global Strategy for the Diagnosis, Management and Prevention of Chronic Obstructive Pulmonary Disease, 2026 Report. Figure 2.10. https://goldcopd.org.
pft_classify() for LLN-based airflow obstruction
identification (Stanojevic 2022); pft_severity() for the
z-score-based severity scheme (which differs from GOLD's
percent-predicted scheme).
# Post-bronchodilator FEV1 % predicted for four tests. post_fev1_pctpred <- c(85, 65, 40, 25) # Without the prerequisite check (backward-compatible): one grade per # non-NA input. pft_gold(post_fev1_pctpred) # -> "GOLD 1" "GOLD 2" "GOLD 3" "GOLD 4" # With the fixed-ratio prerequisite: the third test has a # post-bronchodilator FEV1/FVC of 0.75 (prerequisite not met) and is # returned NA. post_fev1fvc <- c(0.65, 0.60, 0.75, 0.55) pft_gold(post_fev1_pctpred, fev1fvc = post_fev1fvc) # -> "GOLD 1" "GOLD 2" NA "GOLD 4"# Post-bronchodilator FEV1 % predicted for four tests. post_fev1_pctpred <- c(85, 65, 40, 25) # Without the prerequisite check (backward-compatible): one grade per # non-NA input. pft_gold(post_fev1_pctpred) # -> "GOLD 1" "GOLD 2" "GOLD 3" "GOLD 4" # With the fixed-ratio prerequisite: the third test has a # post-bronchodilator FEV1/FVC of 0.75 (prerequisite not met) and is # returned NA. post_fev1fvc <- c(0.65, 0.60, 0.75, 0.55) pft_gold(post_fev1_pctpred, fev1fvc = post_fev1fvc) # -> "GOLD 1" "GOLD 2" NA "GOLD 4"
pft_interpret() combines the core reference-value and routine
interpretive stages of this package into a one-call research
workflow following the Stanojevic et al. ERJ 2022 standard. It
auto-detects which computations are possible from the input columns
and skips modalities it cannot compute:
If sex / age / height (and race, for year = 2012) are present,
it computes spirometry reference values via pft_spirometry().
If sex / age / height are present, it computes lung-volume
reference values via pft_volumes().
If sex / age / height are present, it computes diffusion
reference values via pft_diffusion().
For each measure whose _measured column is present, z-score
and percent-predicted are appended (see the individual reference
functions for details).
For each measure with a z-score, a <measure>_severity
column is appended via pft_severity() (or pft_severity_2005()
when standard = "2005").
If fev1_measured, fvc_measured, fev1fvc_measured, and
tlc_measured columns are present, the ATS pattern classifier
(pft_classify()) labels each row.
If tlc_measured, rv_tlc_measured, and fev1fvc_measured
are present (with their LLNs / ULNs computable), the lung-
volume sub-pattern classifier (pft_volume_subpattern()) adds
a volume_subpattern column. When frc_tlc_measured /
frc_tlc_uln are also present, both volume ratios are
consulted per Stanojevic 2022 Figure 10.
If the diffusion z-score columns (dlco_zscore / va_zscore
/ kco_tr_zscore, or their _si variants) are present, the
Hughes & Pride 2012 diffusion clinical-category classifier
(pft_diffusion_interpret()) is applied.
If fev1_measured, fev1fvc_measured, and their LLNs are
resolvable, pft_prism() adds a prism flag (independent of
TLC).
If <measure>_pre and <measure>_post columns are present
for any spirometry measure, pft_bdr() adds
<measure>_bdr_pct and <measure>_bdr_significant columns
(or pft_bdr_2005() when standard = "2005").
The following helpers are exported for stand-alone use and are
not invoked automatically by pft_interpret(); call them
directly when needed:
pft_gold() – GOLD 2026 spirometric airflow-limitation
grade (GOLD 1-4).
pft_fev1q() – FEV1Q survival index (adult-only).
pft_quality() – Graham 2019 A-F spirometry session grade
computed from maneuvers already judged acceptable by technical
review (it does not assess acceptability itself).
pft_change() – Stanojevic 2022 Box 2 conditional-change
z-score for paired serial FEV1 z-scores.
pft_dlco_hb_correct() – Cotes 1972 haemoglobin correction
for DLCO / TLCO.
This is the recommended entry point for one-call research use; the individual reference-value, interpretive, and extension functions are exported for callers who need finer-grained control. The wrapper is intended for research and education, not for regulated clinical decision-making.
pft_interpret( data, year = 2022, SI.units = FALSE, standard = c("2022", "2005"), sex = sex, age = age, height = height, race = race )pft_interpret( data, year = 2022, SI.units = FALSE, standard = c("2022", "2005"), sex = sex, age = age, height = height, race = race )
data |
A data frame containing whatever inputs are available. See Details for the column-name conventions. |
year |
GLI spirometry equation year. Defaults to |
SI.units |
Whether to report diffusion in SI units. See
|
standard |
Interpretive standard whose downstream rules to
apply: |
sex, age, height, race
|
Column references. By default
|
To trigger z-scores and percent-predicted on a measure, include the
corresponding <measure>_measured column in data (e.g.
fev1_measured, frc_measured, dlco_measured).
To trigger BDR, include both columns of one or more of the pairs
fev1_pre / fev1_post, fvc_pre / fvc_post, and
fev1fvc_pre / fev1fvc_post. A _pre column holds the baseline
value obtained before bronchodilator administration, and the
matching _post column holds the value obtained after
bronchodilator administration under the laboratory's protocol, in
the same units. These labels are interpreted literally: pft does
not infer pre/post status from timestamps, values, medication
records, or curve shape, and the _measured columns are not used
for BDR. Under standard = "2022" the BDR denominator is the
<measure>_pred_<year> column, so the spirometry reference values
must be computable for that row.
Diffusion units follow SI.units; measured diffusion columns must
already be in that unit system (see pft_diffusion()).
No fixed number of decimal places is required for any numeric
input. pft does not screen measured values for physiologic
plausibility or technical quality; see vignette("input-format").
All outputs trace to a specific equation, table, or figure in
Stanojevic et al. ERJ 2022 or the underlying GLI reference papers; see
the @references blocks on the individual functions.
The original data frame with every applicable reference value, z-score, percent predicted, severity grade, pattern label, PRISm flag, and BDR result appended.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021.
patient <- data.frame( sex = "M", age = 45, height = 178, race = "Caucasian", fev1_measured = 2.5, fvc_measured = 3.8, fev1fvc_measured = 2.5/3.8, tlc_measured = 6.0 ) pft_interpret(patient)patient <- data.frame( sex = "M", age = 45, height = 178, race = "Caucasian", fev1_measured = 2.5, fvc_measured = 3.8, fev1fvc_measured = 2.5/3.8, tlc_measured = 6.0 ) pft_interpret(patient)
pft_result to long formReshapes a wide pft_interpret() / pft_spirometry() / pft_volumes() /
pft_diffusion() output (one row per patient, one column per measure ×
statistic) into long form (one row per (patient, measure, year) with
columns for each statistic). This is the natural shape for dplyr /
ggplot2 faceting, cohort modelling, and broom-style downstream
workflows.
pft_long(x, ...)pft_long(x, ...)
x |
A data frame; typically a |
... |
Currently unused; reserved for forward compatibility. |
Discovery is keyed off <measure>_pred columns; the four-digit GLI
year is extracted from the column suffix and recorded in the year
column. Spirometry outputs from pft_spirometry() / pft_interpret()
always carry a year suffix (fev1_pred_2012, fev1_pred_2022, ...)
and produce a populated year; lung-volume (Hall 2021) and
diffusion (GLI 2017) outputs are unsuffixed and produce year = NA
until a competing standard ships and the same suffixing convention
is adopted there. Columns whose suffix does not match a recognised
statistic are ignored, so id / demographic columns are dropped (use
the .patient integer to join back).
A tibble with columns .patient (integer row position),
measure, year (character; NA for non-suffixed outputs),
pred, lln, uln, measured, zscore, pctpred, and
severity. Missing statistics fill with NA of the appropriate
type.
pft_interpret()
to produce the wide-form input.
patient <- data.frame( sex = c("M","F"), age = c(45, 60), height = c(178, 165), race = "Caucasian", fev1_measured = c(2.5, 1.8), fvc_measured = c(3.8, 2.4) ) result <- pft_interpret(patient) pft_long(result)patient <- data.frame( sex = c("M","F"), age = c(45, 60), height = c(178, 165), race = "Caucasian", fev1_measured = c(2.5, 1.8), fvc_measured = c(3.8, 2.4) ) result <- pft_interpret(patient) pft_long(result)
pft_plot() draws a single-patient z-score figure: one row per
measure, points at the patient's z-score, shaded reference bands
for the four Stanojevic 2022 severity grades returned by
pft_severity(): normal (z >= -1.645), mild (-2.5 <= z < -1.645),
moderate (-4 <= z < -2.5), and severe (z < -4). The normal band
extends symmetrically above zero to the upper limit of normal;
values above the ULN are shown but are not a 2022 severity grade.
Requires the ggplot2 package (a Suggested dependency).
pft_plot(data)pft_plot(data)
data |
A single-row data frame produced by |
A ggplot object.
pft_interpret() for the input data shape.
patient <- data.frame( sex = "M", age = 45, height = 178, race = "Caucasian", fev1_measured = 2.5, fvc_measured = 3.8, fev1fvc_measured = 2.5 / 3.8, tlc_measured = 6.0 ) pft_plot(pft_interpret(patient))patient <- data.frame( sex = "M", age = 45, height = 178, race = "Caucasian", fev1_measured = 2.5, fvc_measured = 3.8, fev1fvc_measured = 2.5 / 3.8, tlc_measured = 6.0 ) pft_plot(pft_interpret(patient))
PRISm is the spirometry-only manifestation of the "non-specific" pattern when TLC is not available: a low FEV1, a low FVC, and a preserved (normal) FEV1/FVC ratio. The 2022 ERS/ATS interpretation standard (Stanojevic et al.) classifies it in Table 5 with row "Non-specific pattern" (FEV1 reduced, FVC reduced, FEV1/FVC normal).
Typically called via pft_interpret() as part of the one-call
workflow; exported for callers who want to apply the screen to
pre-computed columns directly.
This function adds a prism logical column to the data frame.
PRISm is a spirometry-only screen and does not require a TLC
measurement.
pft_prism( data, year = 2022, fev1 = fev1, fev1_lln = NULL, fvc = fvc, fvc_lln = NULL, fev1fvc = fev1fvc, fev1fvc_lln = NULL )pft_prism( data, year = 2022, fev1 = fev1, fev1_lln = NULL, fvc = fvc, fvc_lln = NULL, fev1fvc = fev1fvc, fev1fvc_lln = NULL )
data |
A data frame containing the six input columns named below. |
year |
GLI year suffix used when looking up the LLN columns
( |
fev1, fev1_lln, fvc, fvc_lln, fev1fvc, fev1fvc_lln
|
Column references
for the six required columns. Defaults are the canonical names
( |
The original data frame with a prism logical column
appended. NA propagates from any of the six input columns.
Each column-reference argument accepts three forms:
a bare column name – fev1 = my_fev1
a string – fev1 = "my_fev1"
an injected value – fev1 = !!my_var where my_var <- "my_fev1"
Defaults are the canonical pft column names, so callers whose data already follows the convention pass no extra arguments.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. PRISm appears in Table 5 as the spirometry-only form of the non-specific pattern.
pft_classify() for the full ATS pattern classification
when TLC is available; pft_interpret() runs both PRISm and
full classification automatically when the relevant columns are
present.
d <- data.frame(fev1 = 2.0, fev1_lln_2022 = 2.5, fvc = 2.6, fvc_lln_2022 = 3.0, fev1fvc = 0.80, fev1fvc_lln_2022 = 0.70) pft_prism(d) # Column-name override: data using non-canonical names. d2 <- data.frame(my_fev1 = 2.0, my_fev1_lln = 2.5, fvc = 2.6, fvc_lln_2022 = 3.0, fev1fvc = 0.80, fev1fvc_lln_2022 = 0.70) pft_prism(d2, fev1 = my_fev1, fev1_lln = my_fev1_lln)d <- data.frame(fev1 = 2.0, fev1_lln_2022 = 2.5, fvc = 2.6, fvc_lln_2022 = 3.0, fev1fvc = 0.80, fev1fvc_lln_2022 = 0.70) pft_prism(d) # Column-name override: data using non-canonical names. d2 <- data.frame(my_fev1 = 2.0, my_fev1_lln = 2.5, fvc = 2.6, fvc_lln_2022 = 3.0, fev1fvc = 0.80, fev1fvc_lln_2022 = 0.70) pft_prism(d2, fev1 = my_fev1, fev1_lln = my_fev1_lln)
Computes the Graham et al. ATS/ERS 2019 (Table 10) session grade, A-F, for ONE measure (FEV1 or FVC) from the values of maneuvers that have already been judged acceptable. The grade depends only on the number of accepted maneuvers and their repeatability (the difference between the two largest values).
pft_quality(values, age = NA_real_)pft_quality(values, age = NA_real_)
values |
Numeric vector of measurements (litres) from each
maneuver already judged acceptable, for ONE person, ONE measure (FEV1 or
FVC), and ONE maneuver set (pre- or post-bronchodilator). |
age |
Patient age, in years. The repeatability thresholds tighten
for children aged 6 or younger; the threshold is the greater of
the absolute child value (0.100 / 0.150 / 0.200 L for A / C / D)
and 10% of the highest measured value, per Table 10's footnote.
Defaults to |
This function does not assess maneuver acceptability or overall test quality. See "Precondition" below.
Grade definitions (Table 10, paper p. e83). Adult thresholds in
parentheses; child (age <= 6) thresholds are max(absolute, 0.10 · max(values)):
A: >= 3 acceptable maneuvers; best two within 0.150 L (0.100 L for child).
B: 2 acceptable maneuvers; best two within 0.150 L (0.100 L for child).
C: >= 2 acceptable maneuvers; best two within 0.200 L (0.150 L for child).
D: >= 2 acceptable maneuvers; best two within 0.250 L (0.200 L for child).
E: >= 2 acceptable maneuvers with best-two diff exceeding the D threshold, OR exactly 1 acceptable maneuver.
F: 0 acceptable maneuvers.
Grade U ("0 acceptable AND >= 1 usable") from Table 10 is NOT currently distinguished from F. The API receives only accepted values, so with zero accepted values it cannot tell 0 acceptable += 1 usable (U) apart from 0 acceptable + 0 usable (F), andreturns "F" unconditionally. Implementing U would requireextending the API to take a separate vector of usable-but-not-acceptable maneuvers.
A length-1 character with value "A", "B", "C", "D",
"E", or "F".
values must contain only measurements from maneuvers that an
appropriately trained technologist or reviewer has already judged
acceptable, using the complete flow-volume and volume-time curves and
the full ATS/ERS 2019 acceptability criteria. pft_quality()
receives scalar values only, so it cannot determine acceptability:
it cannot evaluate curve morphology, start-of-test or
end-of-forced-expiration criteria, cough, glottic closure, leak,
mouthpiece obstruction, effort, or any other feature that requires
the raw curves or the testing context.
The returned grade therefore supplements, and does not replace, technical review of the test session. It is not an independent assessment of overall PFT quality, and a grade computed from maneuvers that were not reviewed for acceptability is not meaningful.
Per Graham et al. 2019 (p. e83), FEV1 and FVC are graded separately,
and the pre-bronchodilator and post-bronchodilator maneuver sets are
graded separately. Call pft_quality() once per measure per
maneuver set.
Graham BL, Steenbruggen I, Miller MR, et al. Standardization of Spirometry 2019 Update. An Official American Thoracic Society and European Respiratory Society Technical Statement. Am J Respir Crit Care Med. 2019;200(8):e70-e88. doi:10.1164/rccm.201908-1590ST.
pft_interpret() for the downstream interpretation once
acceptable maneuvers have been selected by technical review.
pft_quality(c(3.20, 3.12, 3.10)) # Grade A (n>=3 within 0.150) pft_quality(c(3.20, 3.12)) # Grade B (n=2 within 0.150) pft_quality(c(3.20, 3.02)) # Grade C (n>=2 within 0.200) pft_quality(c(3.20, 2.97)) # Grade D (n>=2 within 0.250) pft_quality(c(3.20, 2.80)) # Grade E (n>=2 diff > 0.250) pft_quality(c(3.20)) # Grade E (only 1) pft_quality(numeric(0)) # Grade F (none)pft_quality(c(3.20, 3.12, 3.10)) # Grade A (n>=3 within 0.150) pft_quality(c(3.20, 3.12)) # Grade B (n=2 within 0.150) pft_quality(c(3.20, 3.02)) # Grade C (n>=2 within 0.200) pft_quality(c(3.20, 2.97)) # Grade D (n>=2 within 0.250) pft_quality(c(3.20, 2.80)) # Grade E (n>=2 diff > 0.250) pft_quality(c(3.20)) # Grade E (only 1) pft_quality(numeric(0)) # Grade F (none)
Assigns one of four severity categories
("normal", "mild", "moderate", "severe") to a z-score per
the Stanojevic et al. ERS/ATS 2022 interpretation standard. The
same grading applies uniformly to spirometry, lung-volume, and
diffusion measures.
Boundary conventions (matching the function's implementation):
| Grade | z-score |
| normal | z >= -1.645 |
| mild | -2.5 <= z < -1.645 |
| moderate | -4 <= z < -2.5 |
| severe | z < -4
|
pft_severity(zscore)pft_severity(zscore)
zscore |
Numeric vector of z-scores. |
Character vector the same length as zscore with values
"normal", "mild", "moderate", "severe", or NA.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. The cut points are taken from the "Severity of lung function impairment" section.
pft_classify() for the pattern label that severity sits
alongside; pft_gold() for the GOLD 2026 spirometric
airflow-limitation grade from post-bronchodilator FEV1 percent
predicted; pft_interpret() applies this grading to every
z-score column in one call.
pft_severity(c(0, -1.7, -3, -5)) # -> "normal" "mild" "moderate" "severe"pft_severity(c(0, -1.7, -3, -5)) # -> "normal" "mild" "moderate" "severe"
Assigns a five-band severity grade from FEV1 percent predicted, per
the Pellegrino et al. ERJ 2005 standard (the predecessor to the
2022 z-score-based grading implemented by pft_severity()).
Boundary conventions (matching the function's implementation):
| Grade | FEV1 % predicted |
| mild | >= 70% |
| moderate | 60% - 69% |
| moderately severe | 50% - 59% |
| severe | 35% - 49% |
| very severe | < 35%
|
Note that unlike pft_severity(), the 2005 grading has no
"normal" tier – the grades describe the severity of an impairment
that has already been identified, and "normal" lung function is
indicated by the pattern classifier returning "Normal" rather than
by the severity grade itself. Pass only percent-predicted values
from patients with an identified impairment.
pft_severity_2005(pctpred)pft_severity_2005(pctpred)
pctpred |
Numeric vector of FEV1 percent predicted values
(e.g. the |
Character vector the same length as pctpred with values
"mild", "moderate", "moderately severe", "severe",
"very severe", or NA.
Pellegrino R, Viegi G, Brusasco V, et al. Interpretative strategies for lung function tests. Eur Respir J. 2005;26(5):948-968. doi:10.1183/09031936.05.00035205. Severity bands taken from Table 4.
pft_severity() for the current Stanojevic 2022
z-score-based grading. pft_classify() with standard = "2005"
for the matching 2005-era pattern classifier.
pft_severity_2005(c(85, 65, 55, 40, 30)) # -> "mild" "moderate" "moderately severe" "severe" "very severe"pft_severity_2005(c(85, 65, 55, 40, 30)) # -> "mild" "moderate" "moderately severe" "severe" "very severe"
pft_spirometry() computes ATS-compliant upper and lower normal limits
for common spirometry measures including FEV1, FVC, FEV1/FVC, FEF2575, and FEF75.
pft_spirometry( data, year = 2022, sex = sex, age = age, height = height, race = race )pft_spirometry( data, year = 2022, sex = sex, age = age, height = height, race = race )
data |
A data frame containing columns for sex ("M","F"),
race ("AfrAm","NEAsia","SEAsia","Other/mixed", "Caucasian"),
age (in years, in the range 3-95 for FEV1 / FVC / FEV1/FVC and
3-90 for FEF25-75 / FEF75 per the GLI spline tables), and
height (in centimeters).
Rows with If |
year |
The year of GLI published equations. Valid options are
2012 (multi-ethnic, requires a |
sex, age, height, race
|
Column references. By default
|
The original data frame with extra columns appended for each
measure. Every output column carries the GLI year as a suffix so
a single result frame can hold multiple equation outputs
side-by-side (fev1_pred_2012, fev1_pred_2022, ...).
<measure>_pred_<year>: predicted (median) value.
<measure>_lln_<year>: lower limit of normal (5th percentile).
<measure>_uln_<year>: upper limit of normal (95th percentile).
If a <measure>_measured column was supplied in data, two
additional columns are emitted:
<measure>_zscore_<year>: LMS z-score ((measured/M)^L - 1) / (L*S).
<measure>_pctpred_<year>: percent predicted (measured / pred) * 100.
Quanjer PH, Stanojevic S, Cole TJ, et al. Multi-ethnic reference values for spirometry for the 3-95-yr age range: the global lung function 2012 equations. Eur Respir J. 2012;40(6):1324-1343. doi:10.1183/09031936.00080312.
Bowerman C, Bhakta NR, Brazzale D, et al. A race-neutral approach to the interpretation of lung function measurements. Am J Respir Crit Care Med. 2023;207(6):768-774. doi:10.1164/rccm.202205-0963OC.
pft_volumes() and pft_diffusion() for the analogous
reference-value functions for lung volumes and diffusion capacity.
pft_classify() consumes the LLN columns produced here to assign
ATS interpretive patterns. pft_interpret() is the one-call
wrapper that combines spirometry, volumes, diffusion, and all
downstream interpretation primitives.
data <- data.frame(sex=c("M","F"), age=c(30.1,5.1), height=c(178,50), race=c("SEAsia","NEAsia")) pft_spirometry(data)data <- data.frame(sex=c("M","F"), age=c(30.1,5.1), height=c(178,50), race=c("SEAsia","NEAsia")) pft_spirometry(data)
Differentiates the six lung-volume sub-patterns described in the
2022 ERS/ATS interpretive standard: Normal lung volumes,
Large lungs, Hyperinflation, Simple restriction,
Complex restriction, and Mixed disorder. These are the
patterns that pft_classify() collapses into "Restricted",
"Mixed", and "Obstructed" / "Normal" – this function recovers the
finer-grained labels when lung-volume ratios (FRC/TLC and / or
RV/TLC) are available.
Typically called via pft_interpret() as part of the one-call
workflow; exported for callers who want to apply the sub-pattern
classifier to pre-computed columns directly.
pft_volume_subpattern( data, year = 2022, tlc = tlc, tlc_lln = tlc_lln, tlc_uln = tlc_uln, fev1fvc = fev1fvc, fev1fvc_lln = NULL, rv_tlc = rv_tlc, rv_tlc_uln = rv_tlc_uln, frc_tlc = NULL, frc_tlc_uln = NULL )pft_volume_subpattern( data, year = 2022, tlc = tlc, tlc_lln = tlc_lln, tlc_uln = tlc_uln, fev1fvc = fev1fvc, fev1fvc_lln = NULL, rv_tlc = rv_tlc, rv_tlc_uln = rv_tlc_uln, frc_tlc = NULL, frc_tlc_uln = NULL )
data |
A data frame containing at minimum:
Optional columns to refine the elevated-volumes branch:
|
year |
GLI year suffix used when looking up the spirometry
FEV1/FVC LLN column. Defaults to |
tlc, tlc_lln, tlc_uln, fev1fvc, fev1fvc_lln, rv_tlc, rv_tlc_uln
|
Column references for the seven required inputs. Defaults are the
canonical names ( |
frc_tlc, frc_tlc_uln
|
Column references for the optional FRC/TLC
pair. Default |
Implements the decision tree in Figure 10 of Stanojevic et al. ERJ 2022 (p. 21) verbatim:
TLC < 5th percentile (LLN)?
YES -> Restriction:
FRC/TLC OR RV/TLC > 95th percentile (ULN)?
YES:
FEV1/FVC < 5th percentile?
YES -> "Mixed disorder"
NO -> "Complex restriction"
NO -> "Simple restriction"
NO:
TLC > 95th percentile?
YES (possible hyperinflation):
FRC/TLC OR RV/TLC > 95th percentile?
YES -> "Hyperinflation"
NO -> "Large lungs"
NO:
FRC/TLC OR RV/TLC > 95th percentile?
YES -> "Hyperinflation"
NO -> "Normal lung volumes"
RV/TLC reference ranges are produced by pft_volumes() (per
Hall 2021 Table 3 row for RV/TLC). FRC/TLC is not fitted in the
Hall 2021 standard; if the caller has FRC/TLC and its ULN
available, supply them as columns frc_tlc / frc_tlc_uln to
refine the OR-condition. When absent (the typical case), only
RV/TLC is consulted – the function degrades gracefully.
The input data with a new volume_subpattern character
column appended. Values are one of "Normal lung volumes",
"Large lungs", "Hyperinflation", "Simple restriction",
"Complex restriction", "Mixed disorder", or NA_character_
if any required column is NA for that row.
Each column-reference argument accepts three forms:
a bare column name – tlc = my_tlc
a string – tlc = "my_tlc"
an injected value – tlc = !!my_var where my_var <- "my_tlc"
Defaults are the canonical pft column names, so callers whose data
already follows the convention pass no extra arguments. The optional
FRC/TLC pair (frc_tlc, frc_tlc_uln) defaults to NULL to enable
canonical-name auto-pickup; pass explicit column references to
override.
Stanojevic S, Kaminsky DA, Miller MR, et al. ERS/ATS technical standard on interpretive strategies for routine lung function tests. Eur Respir J. 2022;60(1):2101499. doi:10.1183/13993003.01499-2021. Lung-volume sub-patterns defined in Figure 10 (p. 21) and Table 7 (p. 22).
pft_classify() for the five-band airflow / restriction
classification; pft_volumes() to obtain rv_tlc / rv_tlc_uln
per Hall 2021; pft_interpret() composes both classifications
when the input columns are present.
# Mixed disorder: TLC < LLN, RV/TLC > ULN, FEV1/FVC < LLN. data.frame( tlc = 4.0, tlc_lln = 5.0, tlc_uln = 7.0, fev1fvc = 0.55, fev1fvc_lln_2022 = 0.70, rv_tlc = 0.55, rv_tlc_uln = 0.45 ) |> pft_volume_subpattern() # Simple restriction: TLC < LLN, both ratios normal. data.frame( tlc = 4.0, tlc_lln = 5.0, tlc_uln = 7.0, fev1fvc = 0.80, fev1fvc_lln_2022 = 0.70, rv_tlc = 0.30, rv_tlc_uln = 0.45 ) |> pft_volume_subpattern()# Mixed disorder: TLC < LLN, RV/TLC > ULN, FEV1/FVC < LLN. data.frame( tlc = 4.0, tlc_lln = 5.0, tlc_uln = 7.0, fev1fvc = 0.55, fev1fvc_lln_2022 = 0.70, rv_tlc = 0.55, rv_tlc_uln = 0.45 ) |> pft_volume_subpattern() # Simple restriction: TLC < LLN, both ratios normal. data.frame( tlc = 4.0, tlc_lln = 5.0, tlc_uln = 7.0, fev1fvc = 0.80, fev1fvc_lln_2022 = 0.70, rv_tlc = 0.30, rv_tlc_uln = 0.45 ) |> pft_volume_subpattern()
pft_volumes() computes ATS-compliant upper and lower normal limits
for lung volume measures including FRC, TLC, RV, ERV, IC, and VC.
pft_volumes(data, sex = sex, age = age, height = height)pft_volumes(data, sex = sex, age = age, height = height)
data |
A data frame containing columns for sex ("M","F"),
age (in years, in the range 5-80 per the GLI 2021 spline tables)
and height (in centimeters). If |
sex, age, height
|
Column references. By default |
The original data frame with extra columns appended for each measure:
<measure>_pred: predicted (median) value.
<measure>_lln: lower limit of normal (5th percentile).
<measure>_uln: upper limit of normal (95th percentile).
If a <measure>_measured column was supplied in data, two
additional columns are emitted:
<measure>_zscore: LMS z-score ((measured/M)^L - 1) / (L*S).
<measure>_pctpred: percent predicted (measured / pred) * 100.
The GLI 2021 static lung-volume equations were derived from 7,190
observations from participants of European ancestry aged 5-80
years; data from non-European ancestry populations were insufficient
to develop multi-ethnic equations (Hall et al. 2021). The source
authors note that it is not clear whether these equations are
appropriate for people of non-European ancestry. The race-neutral
GLI Global 2022 spirometry equations available in pft_spirometry()
do not make these lung-volume equations race-neutral or
multi-ethnic. Users should consider whether the source reference
population is appropriate for the population under study before
interpreting outputs. Ages outside the 5-80 year spline range
return NA reference values.
Hall GL, Filipow N, Ruppel G, et al. Official ERS technical standard: Global Lung Function Initiative reference values for static lung volumes in individuals of European ancestry. Eur Respir J. 2021;57(3):2000289. doi:10.1183/13993003.00289-2020.
pft_spirometry() and pft_diffusion() for the analogous
reference-value functions. pft_classify() uses TLC and its LLN
(produced by this function) to identify restrictive impairments.
pft_interpret() composes all three reference functions in one
call.
data <- data.frame(sex=c("M","F"), age=c(30,5.1), height=c(178,50)) pft_volumes(data)data <- data.frame(sex=c("M","F"), age=c(30,5.1), height=c(178,50)) pft_volumes(data)