---
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` computes reference values and routine interpretation for
pulmonary function tests: predicted values and limits of normal,
z-scores, percent predicted, ATS/ERS pattern classification,
severity grading, bronchodilator response, PRISm screening, and
conditional change scores, all from a data-frame-pipeline API. The
interpretive primitives implement the current ATS/ERS technical
standard (Stanojevic et al. 2022); the predecessor Pellegrino 2005
primitives are also available for reclassification analyses.

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 current ATS/ERS technical 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 `<measure>_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 ATS/ERS 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")
```
