| Title: | Pulmonary Function Test Interpretation per ERS/ATS 2022 |
|---|---|
| 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.1 |
| Built: | 2026-07-11 10:54:59 UTC |
| Source: | https://github.com/cran/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, post
|
Numeric vectors of pre- and post-bronchodilator measurements (same units, same length). |
predicted |
Numeric vector of predicted (median) values for the
same measure, 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.
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 if both the relative change
from baseline is at least 12% AND the absolute change is at least
200 mL. Replaced in 2022 by pft_bdr()'s simpler
"> 10% of predicted" rule.
pft_bdr_2005(pre, post)pft_bdr_2005(pre, post)
pre, post
|
Numeric vectors of pre- and post-bronchodilator measurements, in litres, same length. |
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,
both inequalities strict per the paper's wording on p. 959:
"(>12% of control and >200 mL)"). 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.
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.
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.
pft_bdr_2005(pre = c(2.5, 2.0), post = c(2.8, 2.1)) # -> first row significant (>=12% AND >=200 mL), # second row not (only 5% and 100 mL increase)pft_bdr_2005(pre = c(2.5, 2.0), post = c(2.8, 2.1)) # -> first row significant (>=12% AND >=200 mL), # second row not (only 5% and 100 mL increase)
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.
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 |
A boolean. Returns the reference values in SI units if TRUE . and Traditional units if FALSE. |
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.
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)
Returns the GOLD spirometric severity grade (1-4) for one or more patients given their FEV1 expressed as a percent of predicted, optionally enforcing the GOLD-mandated prerequisite of confirmed airflow obstruction (FEV1/FVC < 0.7).
pft_gold(fev1_pctpred, fev1fvc = NA_real_)pft_gold(fev1_pctpred, fev1fvc = NA_real_)
fev1_pctpred |
Numeric vector of FEV1 % predicted values (e.g.
the |
fev1fvc |
Optional numeric vector of post-bronchodilator
FEV1/FVC ratios (e.g. the |
GOLD severity grades for airflow obstruction (Figure 2.10 of the GOLD 2026 report, content page 38):
| Grade | Severity | FEV1 % predicted |
| GOLD 1 | Mild | >= 80 |
| GOLD 2 | Moderate | >= 50 and < 80 |
| GOLD 3 | Severe | >= 30 and < 50 |
| GOLD 4 | Very severe | < 30
|
GOLD specifies the prerequisite "In patients with COPD (FEV1/FVC <
0.7)" explicitly above Figure 2.10's grade table; the surrounding
text (content p. 37) repeats this requirement. Supplying fev1fvc
enforces the GOLD fixed-cutoff prerequisite. Callers wanting an
LLN-based prerequisite instead should use pft_classify() to
identify obstructed patients and mask pft_gold() output by hand.
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
fail the airflow-obstruction prerequisite.
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).
# Without prerequisite check (backward-compatible): one grade per # non-NA input. pft_gold(c(85, 65, 40, 25)) # -> "GOLD 1" "GOLD 2" "GOLD 3" "GOLD 4" # With prerequisite check: the third patient has FEV1/FVC = 0.75 # (no airflow obstruction) and is returned NA. pft_gold(c(85, 65, 40, 25), fev1fvc = c(0.65, 0.60, 0.75, 0.55)) # -> "GOLD 1" "GOLD 2" NA "GOLD 4"# Without prerequisite check (backward-compatible): one grade per # non-NA input. pft_gold(c(85, 65, 40, 25)) # -> "GOLD 1" "GOLD 2" "GOLD 3" "GOLD 4" # With prerequisite check: the third patient has FEV1/FVC = 0.75 # (no airflow obstruction) and is returned NA. pft_gold(c(85, 65, 40, 25), fev1fvc = c(0.65, 0.60, 0.75, 0.55)) # -> "GOLD 1" "GOLD 2" NA "GOLD 4"
pft_interpret() is a single-call workflow that combines every
interpretation primitive in this package into a complete clinical
report per the Stanojevic et al. ERJ 2022 standard. It auto-detects
which computations are possible from the input columns and skips
anything it cannot do:
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().
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 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.
This is the recommended entry point for clinical-style reporting; the individual reference and interpretation functions are exported for callers who need finer-grained control.
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 <measure>_pre and <measure>_post columns for any of FEV1,
FVC, FEV1/FVC.
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)
Assigns one of grades A-F to a set of acceptable spirometry maneuvers for a single measure (FEV1 or FVC) per the Graham et al. ATS/ERS 2019 technical standard, Table 10. Grades depend on the number of acceptable maneuvers and the difference between the best two values.
pft_quality(values, age = NA_real_)pft_quality(values, age = NA_real_)
values |
Numeric vector of measurements (litres) from each
acceptable maneuver for ONE patient and ONE measure. Length 0 is
allowed and yields grade |
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 |
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. Implementing U would require extending the API to take a separate vector of usable-but-not- acceptable maneuvers; with zero acceptable values, the function returns F unconditionally.
A length-1 character with value "A", "B", "C", "D",
"E", or "F".
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.
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 COPD-specific severity from 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.
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)