# pft <a href="https://overdodactyl.github.io/pft/"><img src="man/figures/logo.png" align="right" height="120" alt="pft website" /></a>

<!-- badges: start -->
[![R-CMD-check](https://github.com/overdodactyl/pft/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/overdodactyl/pft/actions/workflows/R-CMD-check.yaml)
[![Codecov test coverage](https://codecov.io/gh/overdodactyl/pft/graph/badge.svg)](https://app.codecov.io/gh/overdodactyl/pft)
[![pkgdown](https://github.com/overdodactyl/pft/actions/workflows/pkgdown.yaml/badge.svg)](https://github.com/overdodactyl/pft/actions/workflows/pkgdown.yaml)
[![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-green.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable)
<!-- badges: end -->

Compute ATS / ERS-compliant reference values, lower/upper limits of normal,
and interpretive pattern classifications for pulmonary function tests in R.

## Status

> **Research and education use only.** This package is not a regulated medical
> device. The reference equations it implements come from published clinical
> standards, but the package itself has not been validated for diagnostic
> decision-making. All outputs require interpretation by a qualified clinician.

## Installation

```r
# install.packages("remotes")
remotes::install_github("overdodactyl/pft")
```

## What it does

`pft` implements the Stanojevic 2022 ERS/ATS interpretive standard
for pulmonary function tests. Reference values come from the GLI
family of equations (GLI 2012 and GLI Global 2022 spirometry,
GLI 2021 static lung volumes, GLI 2017 TLCO with the 2020 author
correction). On top of those, the package computes z-scores and
percent predicted, ATS pattern classification, severity grading,
bronchodilator response, PRISm, conditional change scores and
longitudinal slopes, GLI 2012 vs GLI Global 2022 reclassification,
the Hughes & Pride diffusion category, lung age, and ggplot2-based
single-patient and cohort figures.

### Reference value functions

| Function | Computes | Source standard |
|---|---|---|
| `pft_spirometry()` | FEV1, FVC, FEV1/FVC, FEF25-75, FEF75 | GLI 2012 (Quanjer) or GLI Global 2022 (Bowerman) |
| `pft_volumes()` | FRC, TLC, RV, RV/TLC, ERV, IC, VC | GLI 2021 static lung volumes (Hall) |
| `pft_diffusion()` | TLCO/DLCO, KCO, VA (SI or traditional units) | GLI 2017 TLCO (Stanojevic, corrected 2020) |

Each reference function emits `<measure>_pred`, `<measure>_lln`, `<measure>_uln`. If a `<measure>_measured` column is also present, `<measure>_zscore` and `<measure>_pctpred` are appended automatically.

Spirometry outputs additionally carry a four-digit GLI-year suffix (e.g. `fev1_pred_2012`, `fev1_pred_2022`) so a single result frame can hold the GLI 2012 and GLI Global 2022 columns side-by-side, and so the suffix isn't ambiguous when the default `year` changes in a future release. Lung-volume (Hall 2021) and diffusion (GLI 2017) outputs stay unsuffixed because only one standard currently ships for each — the same suffixing convention will be adopted there if competing standards emerge.

### Interpretation functions

| Function | Purpose | Source |
|---|---|---|
| `pft_interpret()` | Single-call wrapper combining every primitive below | Stanojevic 2022 |
| `pft_classify()` | Normal / Non-specific / Obstructed / Restricted / Mixed | Stanojevic 2022 Fig 8, Tables 5/8 |
| `pft_volume_subpattern()` | Six lung-volume sub-patterns (Hyperinflation, Simple/Complex restriction, etc.) | Stanojevic 2022 Fig 10 |
| `pft_severity()` | normal / mild / moderate / severe per measure z-score | Stanojevic 2022 |
| `pft_diffusion_interpret()` | Parenchymal / Volume loss / Vascular / Mixed / Elevated KCO category | Hughes & Pride 2012 |
| `pft_bdr()` | >10% of predicted change in FEV1 or FVC | Stanojevic 2022 (BDR section) |
| `pft_prism()` | Preserved Ratio Impaired Spirometry flag | Stanojevic 2022 |
| `pft_change()` | Conditional change z-score for two-point serial measurements | Stanojevic 2022 |
| `pft_fev1q()` | FEV1Q adult-mortality index | Stanojevic 2022 Box 3 |
| `pft_dlco_hb_correct()` | Hemoglobin correction for DLCO/TLCO | Cotes 1972 / Stanojevic 2017 |
| `pft_quality()` | Spirometry quality grade (A-F) from a set of maneuvers | Graham 2019 |
| `pft_gold()` | COPD severity (GOLD 1-4) from FEV1 % predicted | GOLD reports |
| `pft_plot()` | Single-patient z-score lollipop with severity bands | — |
| `pft_long()` | Wide → long pivot; `broom::tidy` dispatches to it | — |

All functions take a data frame and return a data frame, so they
compose with `dplyr` pipelines.

## Quick start

```r
library(pft)
library(dplyr)

# Demographics for two patients
patients <- data.frame(
  sex    = c("M", "F"),
  age    = c(45,  60),
  height = c(178, 165),
  race   = c("Caucasian", "AfrAm")
)

# Compute spirometry, lung volume, and diffusion reference values
patients |>
  pft_spirometry(year = 2022) |>
  pft_volumes() |>
  pft_diffusion(SI.units = TRUE)

# Add measured values to also get z-scores and percent predicted
patients$fev1_measured <- c(3.2, 2.1)
patients$fvc_measured  <- c(4.5, 2.8)
pft_spirometry(patients, year = 2022)
# -> adds fev1_pred_2022, fev1_lln_2022, fev1_uln_2022,
#         fev1_zscore_2022, fev1_pctpred_2022, and equivalents for fvc.
```

To classify a patient's pattern, attach their measured values plus LLNs and
pipe through `pft_classify()`:

```r
patient <- data.frame(
  fev1 = 2.5,  fev1_lln = 3.0,
  fvc  = 3.8,  fvc_lln  = 3.5,
  fev1fvc = 0.66, fev1fvc_lln = 0.70,
  tlc = 6.2,   tlc_lln = 5.0
)
pft_classify(patient)
#>   fev1 fev1_lln  fvc fvc_lln fev1fvc fev1fvc_lln tlc tlc_lln ats_classification ats_pattern_combination
#> 1  2.5      3.0  3.8     3.5    0.66        0.70 6.2     5.0         Obstructed                    ANAN
```

### End-to-end pipeline

For the canonical "interpret → visualise" workflow on a cohort:

```r
cohort <- data.frame(
  sex    = c("M", "F"),
  age    = c(45,  60),
  height = c(178, 165),
  race   = c("Caucasian", "AfrAm"),
  fev1_measured = c(3.2, 1.8),
  fvc_measured  = c(4.5, 2.4),
  tlc_measured  = c(7.0, 4.8)
)

result <- pft_interpret(cohort)   # spirometry + volumes + classify + severity
pft_plot(result[1, ])             # per-patient z-score figure
```

## Common workflows

### Cohort analysis with broom-style tidiers

```r
result <- pft_interpret(cohort)
result |> pft_long()              # one row per (patient, measure)
```

## Reference equations

Reference data is built reproducibly from official ERS / ATS source documents.
The `data-raw/build_*.R` scripts read the published lookup-table workbooks
(or, where unavailable, the equation tables printed in the article PDFs) and
regenerate the package's internal data. The source documents are copyrighted
and not included in this repository; obtain them from the publishers if you
want to regenerate the data.

| Package data | Build script | Source |
|---|---|---|
| GLI 2012 spirometry | `data-raw/build_gli_2012.R` | ERS DC1 supplement workbook |
| GLI Global 2022 spirometry | `data-raw/build_gli_2022.R` | ERS supplement workbook |
| GLI 2021 lung volumes | `data-raw/build_gli_2021_volumes.R` | ERS supplement workbook + paper Table 3 |
| GLI 2017 TLCO | `data-raw/build_gli_2017_diffusion.R` | ERS supplement workbooks + paper Table 2 (corrected) |

The validation tests anchor predicted, LLN, and ULN outputs against ground
truth from the official GLI web calculator at
[gli-calculator.ersnet.org](http://gli-calculator.ersnet.org).

## Citations

Reference equations:

- **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](https://doi.org/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](https://doi.org/10.1164/rccm.202205-0963OC)
- **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](https://doi.org/10.1183/13993003.00289-2020)
- **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](https://doi.org/10.1183/13993003.00010-2017)
  (Author correction:
  [doi:10.1183/13993003.50010-2017](https://doi.org/10.1183/13993003.50010-2017))

Pattern interpretation:

- **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](https://doi.org/10.1183/13993003.01499-2021)
- **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](https://doi.org/10.1183/09031936.05.00035205)

Use `citation("pft")` to retrieve the package and dependent reference list as
a `bibentry` for your own publications.

## License

MIT. See `LICENSE`.

The numerical reference values reproduced inside this package are facts
derived from published statistical models and are not themselves
copyrightable. The source publications are © their respective publishers;
copies are not redistributed with this package.

## Contributing

Bugs, feature requests, and clinical-validation feedback welcome via the
issue tracker. For changes that affect classification logic or reference
equation implementations, please include a citation to the relevant
standards document in the PR description.
