The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Where the Getting-started vignette covers how to compute
reference values and z-scores, this one covers the interpretive
primitives that consume them: the severity bands, the pattern decision
tree, the differences between the 2022 Stanojevic and 2005 Pellegrino
standards, and worked examples showing what pft_interpret()
produces for a few representative input shapes.
pft_severity() translates a z-score into one of four
bands per the Stanojevic 2022 standard. The cut-points come straight
from the paper’s interpretation table:
data.frame(
band = c("normal", "mild", "moderate", "severe"),
z_lower = c(-1.645, -2.5, -4, -Inf),
z_upper = c( Inf, -1.645, -2.5, -4)
)
#> band z_lower z_upper
#> 1 normal -1.645 Inf
#> 2 mild -2.500 -1.645
#> 3 moderate -4.000 -2.500
#> 4 severe -Inf -4.000A vectorised call:
The 2005 Pellegrino bands grade percent predicted of FEV1
rather than z-score and have five tiers (mild, moderate,
moderately-severe, severe, very-severe). They are appropriate when
reproducing legacy reports or when matching a clinic’s existing
severity-grading convention; use pft_severity_2005():
pft_severity_2005(c(85, 65, 55, 40, 30))
#> [1] "mild" "moderate" "moderately severe"
#> [4] "severe" "very severe"The same standard = c("2022", "2005") argument flows
through pft_classify() and pft_interpret() so
a whole report can be re-rendered against either standard without
changing input data.
pft_classify() assigns one of five interpretive patterns
per Stanojevic 2022 Figure 8 / Table 5:
pft_prism().When TLC is missing, the classifier falls back to the spirometry-only branches in Table 5 (Normal, Obstructed, Non-specific / PRISm); Restricted and Mixed require TLC.
case <- data.frame(
fev1 = c(2.5, 2.5, 1.5, 1.5, 3.5),
fev1_lln_2022= c(3.0, 3.0, 2.5, 2.5, 3.0),
fvc = c(3.8, 3.8, 2.2, 2.2, 4.5),
fvc_lln_2022 = c(3.5, 3.5, 2.5, 2.5, 4.0),
fev1fvc = c(0.66, 0.66, 0.68, 0.80, 0.78),
fev1fvc_lln_2022 = 0.70,
tlc = c(6.0, 5.0, 4.0, 4.0, 6.5),
tlc_lln = c(5.5, 5.5, 5.5, 5.5, 5.5)
)
pft_classify(case)[, c("ats_classification")]
#> # A tibble: 5 × 1
#> ats_classification
#> <chr>
#> 1 Obstructed
#> 2 Mixed
#> 3 Mixed
#> 4 Restricted
#> 5 NormalReading row by row:
The two standards differ in three ways:
| Aspect | 2022 (Stanojevic) | 2005 (Pellegrino) |
|---|---|---|
| Severity input | z-score | % predicted (FEV1) |
| Bronchodilator response | > 10 % predicted | >= 12 % AND >= 200 mL |
| Pattern flowchart | Fig 8 / Table 5 | Fig 2 |
The 2022 standard is the recommended default and is what
pft_interpret() applies by default. Use the 2005 path when
reproducing a historical report or matching an EMR template that was
built against the older flowchart – run
pft_interpret(data, standard = "2005") to get the
predecessor severity and BDR outputs alongside
pft_classify(standard = "2005")’s pattern labels.
copd <- data.frame(
sex = "M", age = 68, height = 175, race = "Caucasian",
fev1_measured = 1.6,
fvc_measured = 3.0,
fev1fvc_measured = 1.6 / 3.0,
tlc_measured = 6.8
)
r <- pft_interpret(copd)
r[, c("ats_classification", "fev1_severity_2022", "fev1_zscore_2022",
"fev1_pctpred_2022")]
#> # A tibble: 1 × 4
#> ats_classification fev1_severity_2022 fev1_zscore_2022 fev1_pctpred_2022
#> <chr> <chr> <dbl> <dbl>
#> 1 Obstructed moderate -2.80 53.2The pattern is Obstructed with moderate severity. GOLD staging (FEV1 % predicted) classifies this as GOLD 2:
preserved_kco <- data.frame(
sex = "F", age = 55, height = 160, race = "Caucasian",
fev1_measured = 1.2, fvc_measured = 1.5,
fev1fvc_measured = 0.80, tlc_measured = 3.8,
rv_tlc_measured = 0.30, dlco_measured = 22.0,
va_measured = 4.6, kco_tr_measured = 4.5
)
r <- pft_interpret(preserved_kco)
r[, c("ats_classification", "diffusion_category",
"volume_subpattern")]
#> # A tibble: 1 × 3
#> ats_classification diffusion_category volume_subpattern
#> <chr> <chr> <chr>
#> 1 Restricted Normal Simple restrictionThe package labels this row as Restricted with a Volume loss diffusion category (low DLCO, low VA, preserved KCO) and a Simple restriction volume sub-pattern.
When TLC isn’t available, pft_prism() flags the
spirometry-only non-specific picture: low FEV1, low FVC, preserved
ratio.
no_tlc <- data.frame(
sex = "M", age = 50, height = 175, race = "Caucasian",
fev1_measured = 2.2, fvc_measured = 2.8,
fev1fvc_measured = 0.79
)
r <- pft_interpret(no_tlc)
r[, c("ats_classification", "prism")]
#> # A tibble: 1 × 2
#> ats_classification prism
#> <chr> <lgl>
#> 1 <NA> TRUEThe prism column is TRUE. The label flags
the spirometry pattern only; downstream clinical interpretation is out
of scope.
The package splits its public surface into two kinds of function:
pft_classify(),
pft_prism(), pft_volume_subpattern(),
pft_diffusion_interpret() – consume several paired columns
simultaneously and accept column-name overrides via NSE (bare name,
string, or !!var).pft_severity(),
pft_severity_2005(), pft_gold(),
pft_fev1q(), pft_dlco_hb_correct(),
pft_quality(), pft_change(),
pft_bdr(), pft_bdr_2005() – take one or more
numeric vectors and return a vector or a small per-row tibble. They are
designed to compose inside dplyr::mutate().A cohort run that combines reference values with severity, GOLD staging, and bronchodilator response:
library(dplyr)
out <- pft_spirometry(cohort) |>
mutate(
fev1_severity_2022 = pft_severity(fev1_zscore_2022),
fvc_severity_2022 = pft_severity(fvc_zscore_2022),
gold = pft_gold(fev1_pctpred_2022, fev1fvc = fev1fvc_measured),
bdr_sig = pft_bdr(fev1_pre, fev1_post, fev1_pred_2022)$is_significant
)Grading every z-score column in one pass with
dplyr::across(). Use matches("_zscore") rather
than ends_with("_zscore") so that year-suffixed spirometry
columns (fev1_zscore_2022) are also caught:
The split exists because the data-frame helpers need to read paired
columns (a value and its LLN/ULN, or three z-scores at once) and need to
know how to find them in your data, while the vector helpers operate on
a single named column and so compose naturally as mutate()
expressions.
vignette("longitudinal-analysis") – decline,
conditional change, FEV1Q.vignette("diffusion-capacity") – DLCO interpretation,
Hb correction, Hughes & Pride categories.vignette("input-format") – input contract and column
override syntax.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.