--- title: "Getting started with pft" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with pft} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4 ) ``` `pft` implements the Stanojevic et al. ERJ 2022 ERS/ATS interpretive strategy for pulmonary function tests: reference values, z-scores, percent predicted, ATS pattern classification, severity grading, bronchodilator response, PRISm screening, and conditional change scores, all from a data-frame-pipeline API. The sections below run the pipeline on a single patient and then on a small cohort. ```{r} library(pft) ``` # 1. Reference values from demographics alone The simplest call: pass age, sex, height (and race for GLI 2012) and get predicted values, lower limits of normal (LLN), and upper limits of normal (ULN) for every measure. ```{r} patient <- data.frame( sex = "M", age = 45, height = 178 ) ref <- pft_spirometry(patient) ref[, c("fev1_pred_2022", "fev1_lln_2022", "fev1_uln_2022", "fvc_pred_2022", "fvc_lln_2022", "fvc_uln_2022")] ``` The default is GLI 2022 ("GLI Global"), the race-neutral equation set recommended by the ERS/ATS 2022 standard. To use the predecessor GLI 2012 multi-ethnic equations, pass `year = 2012` and include a `race` column. The same pattern works for lung volumes and diffusion: ```{r} pft_volumes(patient)[, c("frc_pred", "tlc_pred", "rv_pred", "vc_pred")] pft_diffusion(patient)[, c("dlco_pred", "kco_tr_pred", "va_pred")] ``` # 2. Z-scores and percent predicted from measured values Add `_measured` columns and z-scores and percent-predicted appear automatically next to the reference values. ```{r} patient_with_measurements <- data.frame( sex = "M", age = 45, height = 178, race = "Caucasian", fev1_measured = 2.5, fvc_measured = 3.8 ) out <- pft_spirometry(patient_with_measurements) out[, c("fev1_pred_2022", "fev1_zscore_2022", "fev1_pctpred_2022", "fvc_pred_2022", "fvc_zscore_2022", "fvc_pctpred_2022")] ``` The z-score uses the standard LMS formula `((measured / M)^L - 1) / (L * S)`. Percent predicted is `(measured / M) * 100`. # 3. Severity grading `pft_severity()` maps a z-score to one of four categories per the Stanojevic 2022 cut points: ```{r} pft_severity(c(0, -1.7, -3, -5)) ``` You can grade any z-score column directly: ```{r} out$fev1_severity_2022 <- pft_severity(out$fev1_zscore_2022) out$fvc_severity_2022 <- pft_severity(out$fvc_zscore_2022) out[, c("fev1_zscore_2022", "fev1_severity_2022", "fvc_zscore_2022", "fvc_severity_2022")] ``` # 4. ATS pattern classification Given measured spirometry plus TLC and their LLNs, `pft_classify()` labels the pattern per Stanojevic 2022 Figure 8: ```{r} classification_input <- data.frame( fev1 = 2.5, fev1_lln_2022 = 3.0, fvc = 3.8, fvc_lln_2022 = 3.5, fev1fvc = 0.66, fev1fvc_lln_2022 = 0.70, tlc = 6.0, tlc_lln = 5.0 ) pft_classify(classification_input)[ , c("ats_classification", "ats_pattern_combination") ] ``` The 4-character `ats_pattern_combination` records which inputs drove the label (A = abnormal / below LLN, N = at or above LLN), in the order FEV1, FVC, FEV1/FVC, TLC. `ANAN` above means FEV1 and FEV1/FVC are low; FVC and TLC are normal. # 5. Bronchodilator response The Stanojevic 2022 BDR criterion is a >10% change relative to predicted in FEV1 or FVC (replacing the 2005 12% / 200 mL rule): ```{r} pft_bdr(pre = 2.5, post = 3.0, predicted = 4.0) ``` # 6. PRISm screening Preserved Ratio Impaired Spirometry: low FEV1 with normal FEV1/FVC. Spirometry-only; no TLC needed. ```{r} pft_prism(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 )) ``` # 7. Serial change For longitudinal monitoring, the conditional change score (CCS) adjusts for regression to the mean using a within-subject z-score autocorrelation `r`. `|CCS| > 1.96` (the Stanojevic 2022 two-sided 95% threshold) indicates a change outside the normal-limits range. ```{r} # z dropped from -0.5 to -2.5 over 1 year; r ≈ 0.7 for adult FEV1 pft_change(z1 = -0.5, z2 = -2.5, r = 0.7) ``` # 8. The one-call workflow `pft_interpret()` auto-detects every available input and produces the full Stanojevic 2022-compliant interpretation in one call: ```{r} 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, fev1_pre = 2.5, fev1_post = 2.9 ) result <- pft_interpret(patient) # A high-level subset of the ~60 columns generated: result[, c("fev1_pred_2022", "fev1_zscore_2022", "fev1_severity_2022", "fvc_zscore_2022", "fvc_severity_2022", "ats_classification", "prism", "fev1_bdr_pct", "fev1_bdr_significant")] ``` # 9. Visualisation `pft_plot()` produces a clinical-style z-score lollipop figure with severity bands. Requires `ggplot2` (Suggests). ```{r, eval = requireNamespace("ggplot2", quietly = TRUE)} pft_plot(result) ``` # 10. Cohort analyses Everything composes naturally in a pipeline. Apply `pft_interpret()` to a multi-row data frame and the output is the same data frame with ~60 interpretation columns appended: ```{r} cohort <- data.frame( sex = c("M", "F", "M"), age = c(45, 60, 30), height = c(178, 165, 175), race = c("Caucasian", "AfrAm", "Caucasian"), fev1_measured = c(2.5, 1.8, 4.0), fvc_measured = c(3.8, 2.4, 5.2), fev1fvc_measured = c(2.5/3.8, 1.8/2.4, 4.0/5.2), tlc_measured = c(6.0, 4.5, 6.8) ) interpreted <- pft_interpret(cohort) interpreted[, c("sex", "age", "fev1_zscore_2022", "fev1_severity_2022", "ats_classification", "prism")] ``` # 11. Long-form tidier for downstream analysis `pft_long()` pivots a wide `pft_result` into one row per `(patient, measure)`, the natural shape for `dplyr` / `ggplot2` workflows. ```{r} pft_long(interpreted)[1:6, ] ``` The S3 method `tidy.pft_result()` dispatches to it when `broom` is installed, so `broom::tidy(interpreted)` is identical to `pft_long(interpreted)`. # 12. Diffusion clinical category When `pft_diffusion()` outputs are available (the default in `pft_interpret()` when demographics are supplied), the Hughes & Pride 2012 categorical interpretation falls out of `dlco_zscore`, `va_zscore`, `kco_*_zscore`: ```{r} patient_dlco <- data.frame( sex = "M", age = 50, height = 178, race = "Caucasian", dlco_measured = 6, # low va_measured = 6, kco_tr_measured = 1.0 # also low -> Parenchymal pattern ) pft_interpret(patient_dlco)$diffusion_category ``` # Citations See `citation("pft")` for the package and underlying reference standards as `bibentry` objects, suitable for direct inclusion in publications. ```{r} citation("pft") ```