Package 'pft'

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

Help Index


Bronchodilator response per the ERS/ATS 2022 criterion

Description

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.

Usage

pft_bdr(pre, post, predicted, threshold = BDR_THRESHOLD_PCT_PRED)

Arguments

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 pre.

predicted

Numeric vector of predicted (median) values for the same measure and units, typically the ⁠<measure>_pred_<year>⁠ column from a previous call to pft_spirometry().

threshold

Percent-of-predicted change considered significant. Defaults to 10 (the Stanojevic 2022 criterion).

Value

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- and post-bronchodilator values

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.

Column naming

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.

References

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.

See Also

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.

Examples

pft_bdr(pre = 2.5, post = 3.0, predicted = 4.0)
# -> 12.5% of predicted change, is_significant = TRUE

Bronchodilator response per the Pellegrino 2005 standard

Description

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.

Usage

pft_bdr_2005(pre, post)

Arguments

pre, post

Numeric vectors of baseline (pre-bronchodilator) and corresponding post-bronchodilator measurements, in litres, same length. As with pft_bdr(), the caller identifies which values are pre and post; pft does not infer test phase.

Value

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.

Column naming

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.

Boundary convention (why strict >)

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.

References

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.

See Also

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.

Examples

# +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)

Conditional change score for serial PFT measurements

Description

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):

CCS=(z2−r⋅z1)/1−r2CCS = (z_2 - r \cdot z_1) / \sqrt{1 - r^2}

Where the autocorrelation r is itself a function of the time interval between measurements and the patient's age at the first time point:

r=0.642−0.04⋅time(years)+0.020⋅age(years)r = 0.642 - 0.04 \cdot time(years) + 0.020 \cdot age(years)

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().

Usage

pft_change(z1, z2, age_t1 = NULL, time_years = NULL, r = NULL)

Arguments

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 ⁠(-1, 1)⁠. If supplied, used directly in place of the paper's age/time formula – useful for callers who have a population-specific autocorrelation estimate. If NULL (the default), r is computed from age_t1 and time_years via the Box 2 formula.

Value

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).

References

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).

See Also

pft_spirometry() to produce the FEV1 z-scores at each time point.

Examples

# 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

Classify ATS spirometry patterns from spirometry and lung-volume measurements

Description

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.

Usage

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
)

Arguments

data

A data frame containing the six spirometry input columns (fev1, fev1_lln, fvc, fvc_lln, fev1fvc, fev1fvc_lln) and optionally tlc and tlc_lln. TLC columns are optional – when either is absent from data, the classifier routes via the spirometry-only fallback (see "Missing TLC" below).

standard

Which interpretive standard's classifier to apply. "2022" (default) follows Stanojevic et al. ERJ 2022 Figure 8 and recognises five labels: Normal, Non-specific, Obstructed, Restricted, Mixed. Restricted and Mixed both require TLC below its LLN (see "Restriction requires a low TLC" below). "2005" follows Pellegrino et al. ERJ 2005 Figure 2 and recognises four labels (Normal, Obstructed, Restricted, Mixed). The 2005 algorithm only consults TLC when FVC is below LLN; when FVC is normal it routes directly to Normal or Obstructed regardless of TLC. This is the dominant source of 2005 -> 2022 reclassification: rows with low TLC but normal FVC (NNNA, ANNA) become Restricted under 2022 but stay Normal under 2005; rows with low FEV1/FVC and low TLC but normal FVC (NNAA, ANAA) become Mixed under 2022 but stay Obstructed under 2005; the isolated-low-FVC cells (NANN, AANN) become Non-specific under 2022 but Normal under 2005.

year

GLI year suffix to use when looking up the spirometry LLN columns (fev1_lln, fvc_lln, fev1fvc_lln). Defaults to 2022 (GLI Global, race-neutral). Set to match the year argument used in the upstream pft_spirometry() / pft_interpret() call. The TLC columns (volumes reference) are unsuffixed and are not affected by year.

fev1, fev1_lln, fvc, fvc_lln, fev1fvc, fev1fvc_lln, tlc, tlc_lln

Column references for the eight inputs. Defaults are the canonical names (fev1, fev1_lln, ...); override with a bare name, a string, or !!var (see "Column-name overrides" below).

Value

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.

Restriction requires a low TLC

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 (≥\ge 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.

Column-name overrides

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.

Missing TLC (spirometry-only fallback)

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 ≥\ge 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.

References

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.

See Also

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.

Examples

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)

Compute carbon monoxide diffusion capacity or transfer factor reference values for given demographics

Description

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.

Usage

pft_diffusion(data, SI.units = FALSE, sex = sex, age = age, height = height)

Arguments

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 data also contains a ⁠<measure>_measured⁠ column for any of the active measures (tlco, kco_si, va under SI units; dlco, kco_tr, va under traditional), the measured value is used to compute z-score and percent-predicted (see Value).

SI.units

Logical, default FALSE. Selects the unit system for both the reference outputs and any measured inputs (see "Units" below). FALSE (traditional): DLCO in mL/min/mmHg, KCO (kco_tr) in mL/min/mmHg/L, VA in L. TRUE (SI): TLCO in mmol/min/kPa, KCO (kco_si) in mmol/min/kPa/L, VA in L.

sex, age, height

Column references. By default pft_diffusion() reads from sex, age, and height. Override via a bare name (sex = Sex), a string (sex = "Sex"), or an rlang injection (sex = !!my_var). The user's original column names are preserved in the output.

Value

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.

Units

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.

Applicability (source population)

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.

References

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.)

See Also

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.

Examples

data <- data.frame(sex=c("M","F"),
                   age=c(30,5.1),
                   height=c(178,50))
pft_diffusion(data)

Classify a diffusion result into a clinical pattern category

Description

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.

Usage

pft_diffusion_interpret(
  data,
  SI.units = FALSE,
  dlco = NULL,
  va = NULL,
  kco = NULL
)

Arguments

data

A data frame containing the three z-score input columns.

SI.units

Logical, default FALSE. Selects the default column names for dlco and kco. Traditional units (FALSE): dlco_zscore and kco_tr_zscore. SI units (TRUE): tlco_zscore and kco_si_zscore. va defaults to va_zscore in both unit systems.

dlco, va, kco

Column references for the three z-score inputs. dlco and kco default to NULL, which means: pick the canonical column name based on SI.units. Pass a bare name, a string, or !!var to override (see "Column-name overrides").

Details

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.

Value

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).

NA

Required 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).

Column-name overrides

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.

References

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.)

See Also

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.

Examples

# 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)

Adjust a measured DLCO / TLCO for the patient's hemoglobin

Description

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).

Usage

pft_dlco_hb_correct(dlco, hemoglobin, sex, age = NA_real_)

Arguments

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 normalize_sex_vec() helper so "Male", "female", etc. work.

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 NA_real_), males default to the adult reference (146).

Details

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):

TLCOHb=TLCOmeasured×1.7⋅HbrefHb+0.7⋅HbrefTLCO_{Hb} = TLCO_{measured} \times \frac{1.7 \cdot Hb_{ref}}{Hb + 0.7 \cdot Hb_{ref}}

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.

Value

Numeric vector of Hb-corrected DLCO / TLCO values, same units as dlco. NA propagates from any input.

References

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.

See Also

pft_diffusion() for the reference-value computation that consumes the Hb-corrected DLCO.

Examples

# 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)

Synthetic example PFT cohort

Description

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.

Usage

pft_example

Format

A tibble with 20 rows and 11 columns:

patient_id

Integer 1-20. Synthetic identifier.

sex

Character: "M" or "F".

age

Numeric, years.

height

Numeric, centimetres.

race

One of "Caucasian", "AfrAm", "NEAsia", "SEAsia".

fev1_measured

Measured FEV1 in litres. May be NA.

fvc_measured

Measured FVC in litres.

fev1fvc_measured

Measured FEV1/FVC ratio.

tlc_measured

Measured TLC in litres. May be NA for rows intended to demonstrate spirometry-only workflows.

fev1_pre

Pre-bronchodilator FEV1 in litres. NA when not a BDR scenario.

fev1_post

Post-bronchodilator FEV1 in litres. NA when not a BDR scenario.

Details

No real patient data. Generated by data-raw/build_pft_example.R. Provided for examples, vignettes, and quick experimentation.

Source

data-raw/build_pft_example.R (synthetic; no real patient data).

Examples

data(pft_example)
head(pft_example)
pft_interpret(pft_example)

FEV1Q: ratio of FEV1 to a sex-specific survivable lower limit

Description

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).

Usage

pft_fev1q(fev1, sex, age = NA_real_)

Arguments

fev1

Numeric vector of FEV1 measurements in litres.

sex

Character vector of patient sex. Accepts the soft- correctable variants from pft_spirometry() ("Male", "female", etc.); unrecognized values yield NA.

age

Optional numeric vector. When supplied, rows with age < 18 return NA_real_ per the paper's "not appropriate for children and adolescents" caveat. Default NA_real_ skips the guard.

Details

Formula (Box 3 verbatim):

FEV1Q=FEV1/QsexFEV1Q = FEV1 / Q_{sex}

where Qmale=0.5LQ_{male} = 0.5 L and Qfemale=0.4LQ_{female} = 0.4 L 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).

Value

Numeric vector of FEV1Q ratios, same length as fev1. NA propagates from any input.

References

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.

See Also

pft_change() for the conditional change score (the children / young-people sibling); pft_severity() for the z-score-based severity grading.

Examples

# 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)

GOLD 2026 spirometric airflow-limitation grade

Description

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.

Usage

pft_gold(fev1_pctpred, fev1fvc = NA_real_)

Arguments

fev1_pctpred

Numeric vector of post-bronchodilator FEV1 percent-predicted values (e.g. the fev1_pctpred_2022 column from pft_spirometry() computed from post-bronchodilator measured FEV1).

fev1fvc

Optional numeric vector of post-bronchodilator FEV1/FVC ratios, used to enforce the GOLD 2026 fixed-ratio prerequisite. When supplied, rows with fev1fvc >= 0.70 are returned as NA_character_, because GOLD 2026 Figure 2.10 applies the grades only when post-bronchodilator FEV1/FVC < 0.70. When omitted (the default NA_real_) or all-NA, no prerequisite check is performed and a grade is returned for every non-NA fev1_pctpred; this preserves backward-compatible behaviour, and the caller is then responsible for restricting use to tests that meet the prerequisite.

Details

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.

Value

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.

Test phase (post-bronchodilator values)

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.

References

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.

See Also

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).

Examples

# 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"

One-call ATS/ERS PFT interpretation workflow

Description

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.

Usage

pft_interpret(
  data,
  year = 2022,
  SI.units = FALSE,
  standard = c("2022", "2005"),
  sex = sex,
  age = age,
  height = height,
  race = race
)

Arguments

data

A data frame containing whatever inputs are available. See Details for the column-name conventions.

year

GLI spirometry equation year. Defaults to 2022 (GLI Global, race-neutral). See pft_spirometry().

SI.units

Whether to report diffusion in SI units. See pft_diffusion().

standard

Interpretive standard whose downstream rules to apply: "2022" (default) uses Stanojevic et al. ERJ 2022 for pattern classification, severity grading, and BDR; "2005" uses the Pellegrino et al. ERJ 2005 predecessor. The selected standard does not affect the GLI reference equations (those are controlled by year) – only the downstream interpretive logic. Useful for reclassification analyses comparing the two standards on the same cohort.

sex, age, height, race

Column references. By default pft_interpret() reads from sex, age, height, and (for year = 2012) race. Override via a bare name (sex = Sex), a string (sex = "Sex"), or an rlang injection (sex = !!my_var). The ⁠_measured⁠, ⁠_pre⁠, and ⁠_post⁠ columns are still auto-detected by name and not overridable.

Details

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.

Value

The original data frame with every applicable reference value, z-score, percent predicted, severity grade, pattern label, PRISm flag, and BDR result appended.

References

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.

Examples

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)

Pivot a pft_result to long form

Description

Reshapes 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.

Usage

pft_long(x, ...)

Arguments

x

A data frame; typically a pft_result but any data frame with ⁠<measure>_pred[_<year>]⁠ columns works. Named x (rather than data) to match the S3 first-argument convention shared with print.pft_result, plot.pft_result, and the other pft_result methods.

...

Currently unused; reserved for forward compatibility.

Details

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).

Value

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.

See Also

pft_interpret() to produce the wide-form input.

Examples

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)

Clinical visualisation for a single PFT result

Description

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).

Usage

pft_plot(data)

Arguments

data

A single-row data frame produced by pft_interpret(), pft_spirometry(), pft_volumes(), or pft_diffusion() with measured values supplied (i.e. at least one ⁠<measure>_zscore⁠ column present). Errors if nrow(data) != 1.

Value

A ggplot object.

See Also

pft_interpret() for the input data shape.

Examples

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))

Screen for Preserved Ratio Impaired Spirometry (PRISm)

Description

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.

Usage

pft_prism(
  data,
  year = 2022,
  fev1 = fev1,
  fev1_lln = NULL,
  fvc = fvc,
  fvc_lln = NULL,
  fev1fvc = fev1fvc,
  fev1fvc_lln = NULL
)

Arguments

data

A data frame containing the six input columns named below.

year

GLI year suffix used when looking up the LLN columns (fev1_lln, fvc_lln, fev1fvc_lln). Defaults to 2022. Set to match the year argument used in the upstream pft_spirometry() / pft_interpret() call.

fev1, fev1_lln, fvc, fvc_lln, fev1fvc, fev1fvc_lln

Column references for the six required columns. Defaults are the canonical names (fev1, ⁠fev1_lln_<year>⁠, ...); override with a bare name, a string, or !!var (see "Column-name overrides" below).

Value

The original data frame with a prism logical column appended. NA propagates from any of the six input columns.

Column-name overrides

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.

References

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.

See Also

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.

Examples

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)

Compute the ATS/ERS 2019 spirometry session grade from maneuvers already judged acceptable

Description

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).

Usage

pft_quality(values, age = NA_real_)

Arguments

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). NA values are dropped. Length 0 is allowed and yields grade "F".

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 NA_real_, which uses the adult thresholds.

Details

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.

Value

A length-1 character with value "A", "B", "C", "D", "E", or "F".

Precondition – acceptability must already be established

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.

References

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.

See Also

pft_interpret() for the downstream interpretation once acceptable maneuvers have been selected by technical review.

Examples

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)

Grade severity of lung function impairment from a z-score

Description

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

Usage

pft_severity(zscore)

Arguments

zscore

Numeric vector of z-scores.

Value

Character vector the same length as zscore with values "normal", "mild", "moderate", "severe", or NA.

References

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.

See Also

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.

Examples

pft_severity(c(0, -1.7, -3, -5))
# -> "normal" "mild" "moderate" "severe"

Severity grading per the Pellegrino 2005 standard

Description

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.

Usage

pft_severity_2005(pctpred)

Arguments

pctpred

Numeric vector of FEV1 percent predicted values (e.g. the fev1_pctpred column from pft_spirometry() times nothing – it is already a percent).

Value

Character vector the same length as pctpred with values "mild", "moderate", "moderately severe", "severe", "very severe", or NA.

References

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.

See Also

pft_severity() for the current Stanojevic 2022 z-score-based grading. pft_classify() with standard = "2005" for the matching 2005-era pattern classifier.

Examples

pft_severity_2005(c(85, 65, 55, 40, 30))
# -> "mild" "moderate" "moderately severe" "severe" "very severe"

Compute spirometry reference values for given demographics

Description

pft_spirometry() computes ATS-compliant upper and lower normal limits for common spirometry measures including FEV1, FVC, FEV1/FVC, FEF2575, and FEF75.

Usage

pft_spirometry(
  data,
  year = 2022,
  sex = sex,
  age = age,
  height = height,
  race = race
)

Arguments

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 NA in sex, age, or height (or, for GLI 2012, in race) are returned with NA reference values. Race is ignored when year = 2022 (GLI Global equations are race-neutral).

If data also contains any of fev1_measured, fvc_measured, fev1fvc_measured, fef2575_measured, fef75_measured, the corresponding measured value is used to compute a z-score and percent-predicted for that measure (see Value).

year

The year of GLI published equations. Valid options are 2012 (multi-ethnic, requires a race column) and 2022 (race-neutral "GLI Global"; the race column, if present, is ignored). Defaults to 2022 (the race-neutral GLI Global equations, Bowerman et al. 2023).

sex, age, height, race

Column references. By default pft_spirometry() reads from sex, age, height, and (for GLI 2012) race. If your data frame names them differently, override via a bare name (sex = Sex), a string (sex = "Sex"), or an rlang injection (sex = !!my_var). The user's original column names are preserved in the output.

Value

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.

References

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.

See Also

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.

Examples

data <- data.frame(sex=c("M","F"),
                   age=c(30.1,5.1),
                   height=c(178,50),
                   race=c("SEAsia","NEAsia"))
pft_spirometry(data)

Classify lung-volume sub-pattern per Stanojevic 2022 Figure 10

Description

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.

Usage

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
)

Arguments

data

A data frame containing at minimum:

  • tlc, tlc_lln, tlc_uln

  • fev1fvc, fev1fvc_lln

  • rv_tlc, rv_tlc_uln

Optional columns to refine the elevated-volumes branch:

  • frc_tlc, frc_tlc_uln

year

GLI year suffix used when looking up the spirometry FEV1/FVC LLN column. Defaults to 2022. Set to match the year argument used in the upstream pft_spirometry() / pft_interpret() call. The TLC and RV/TLC columns (volumes reference) are unsuffixed and are not affected by year.

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 (fev1fvc_lln carries the ⁠_<year>⁠ suffix); override with a bare name, a string, or !!var (see "Column-name overrides" below).

frc_tlc, frc_tlc_uln

Column references for the optional FRC/TLC pair. Default NULL means: auto-pickup if frc_tlc and frc_tlc_uln exist in data, otherwise skip the FRC branch and classify on RV/TLC alone.

Details

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.

Value

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.

Column-name overrides

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.

References

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).

See Also

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.

Examples

# 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()

Compute lung volume reference values for given demographics

Description

pft_volumes() computes ATS-compliant upper and lower normal limits for lung volume measures including FRC, TLC, RV, ERV, IC, and VC.

Usage

pft_volumes(data, sex = sex, age = age, height = height)

Arguments

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 data also contains any of frc_measured, tlc_measured, rv_measured, rv_tlc_measured, erv_measured, ic_measured, vc_measured, the corresponding measured value is used to compute a z-score and percent-predicted (see Value).

sex, age, height

Column references. By default pft_volumes() reads from sex, age, and height. Override via a bare name (sex = Sex), a string (sex = "Sex"), or an rlang injection (sex = !!my_var). The user's original column names are preserved in the output.

Value

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.

Applicability (source population)

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.

References

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.

See Also

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.

Examples

data <- data.frame(sex=c("M","F"),
                   age=c(30,5.1),
                   height=c(178,50))
pft_volumes(data)