Skip to contents

Diffusion measurements (DLCO in traditional units, TLCO in SI) quantify gas-exchange capacity at the alveolar-capillary membrane. The sections below cover the reference values pft computes (pft_diffusion(), GLI 2017), the hemoglobin correction (pft_dlco_hb_correct()), and the Hughes & Pride categorical classifier (pft_diffusion_interpret()).

1. Reference values

pft_diffusion() implements the GLI 2017 standard (Stanojevic et al. ERJ 2017, with the 2020 author correction applied) for adults and children aged 5-90 years (the GLI calculator caps at 85; the underlying spline tables extend to 90). By default it emits traditional units (DLCO, KCO, VA in mL/min/mmHg, mL/min/mmHg/L, and L respectively); SI.units = TRUE switches to SI units (TLCO and KCO in mmol/min/kPa and mmol/min/kPa/L). VA is the same column either way.

patient <- data.frame(
  sex = "M", age = 45, height = 178,
  dlco_measured   = 22.0,
  va_measured     = 5.8,
  kco_tr_measured = 3.79
)
out <- pft_diffusion(patient)
out[, grep("dlco|va|kco", colnames(out), value = TRUE)]
#> # A tibble: 1 × 18
#>   dlco_measured va_measured kco_tr_measured dlco_pred dlco_lln dlco_uln
#>           <dbl>       <dbl>           <dbl>     <dbl>    <dbl>    <dbl>
#> 1            22         5.8            3.79      30.3     23.4     38.3
#> # ℹ 12 more variables: dlco_zscore <dbl>, dlco_pctpred <dbl>,
#> #   kco_tr_pred <dbl>, kco_tr_lln <dbl>, kco_tr_uln <dbl>, kco_tr_zscore <dbl>,
#> #   kco_tr_pctpred <dbl>, va_pred <dbl>, va_lln <dbl>, va_uln <dbl>,
#> #   va_zscore <dbl>, va_pctpred <dbl>

The output carries the same per-measure _pred / _lln / _uln / _zscore / _pctpred shape as pft_spirometry() and pft_volumes().

2. Hemoglobin correction

DLCO measured against the standard reference Hb may misrepresent patients who are anemic (DLCO under-reads) or polycythemic (DLCO over-reads). pft_dlco_hb_correct() applies the Cotes 1972 formula to express the measured DLCO at the standard reference Hb:

DLCOadj=DLCO1.7HbrefHb+0.7Hbref. \text{DLCO}_\text{adj} = \text{DLCO} \cdot \frac{1.7 \cdot \text{Hb}_\text{ref}}{\text{Hb} + 0.7 \cdot \text{Hb}_\text{ref}}.

The reference Hb is age- and sex-dependent: 146 g/L for males aged >= 15, 134 g/L for females and for children < 15 of either sex (Cotes 1972 / Stanojevic 2017 Table 5).

# Anemic adult male: corrected DLCO is higher than measured.
pft_dlco_hb_correct(dlco = 20.0, hemoglobin = 110, sex = "M", age = 45)
#> [1] 23.39303

# Polycythemic adult male: corrected is lower than measured.
pft_dlco_hb_correct(dlco = 25.0, hemoglobin = 180, sex = "M", age = 45)
#> [1] 21.98795

Pass hemoglobin in g/L (the package does not detect or convert g/dL inputs). Apply the correction before computing z-scores when comparing across patients whose Hb varies; the GLI 2017 reference values assume Hb is at the sex-/age-specific standard.

3. Clinical sub-pattern (Hughes & Pride 2012)

pft_diffusion_interpret() classifies a diffusion result into one of six clinical categories per the Hughes & Pride 2012 framework (adopted by the Stanojevic 2017 task force). The classifier uses z-scores only, so it works identically on traditional and SI columns:

mixed_cohort <- data.frame(
  dlco_zscore   = c(-0.5, -2.0, -2.5, -2.5, -2.0,  0.0),
  va_zscore     = c(-0.5, -0.5, -2.0, -2.5, -0.5,  0.0),
  kco_tr_zscore = c(-0.5, -2.0,  0.0, -2.5,  0.5,  2.0)
)
pft_diffusion_interpret(mixed_cohort)
#>   dlco_zscore va_zscore kco_tr_zscore   diffusion_category
#> 1        -0.5      -0.5          -0.5               Normal
#> 2        -2.0      -0.5          -2.0          Parenchymal
#> 3        -2.5      -2.0           0.0          Volume loss
#> 4        -2.5      -2.5          -2.5                Mixed
#> 5        -2.0      -0.5           0.5 Vascular (suggested)
#> 6         0.0       0.0           2.0         Elevated KCO

The decision tree (Stanojevic 2017 / Hughes & Pride 2012):

Category DLCO VA KCO
Normal OK OK OK
Parenchymal low OK low
Volume loss low low OK / high
Mixed low low low
Vascular (suggested) low OK low or high
Elevated KCO OK high
Other other combinations

Categories label the z-score pattern only. Hughes & Pride 2012 describes the differential diagnosis associated with each pattern; that interpretation is out of scope for the package.

In pft_interpret() the classifier runs automatically whenever the diffusion z-score columns are present, so the diffusion_category column is attached for free in the standard workflow:

patient2 <- data.frame(
  sex = "F", age = 60, height = 165, race = "Caucasian",
  fev1_measured    = 1.6, fvc_measured    = 1.9,
  fev1fvc_measured = 0.84, tlc_measured   = 4.0,
  dlco_measured   = 10.0, va_measured    = 3.5,
  kco_tr_measured = 2.86
)
r <- pft_interpret(patient2)
r[, c("ats_classification", "diffusion_category")]
#> # A tibble: 1 × 2
#>   ats_classification diffusion_category
#>   <chr>              <chr>             
#> 1 Restricted         Mixed

4. How VA shapes the classifier output

Alveolar volume (VA) is the axis that splits restriction into the classifier’s two volume-loss categories:

  • Low VA, low KCO -> labelled Mixed (both alveolar volume and per-alveolus gas exchange reduced).
  • Low VA, normal-or-high KCO -> labelled Volume loss (fewer alveoli, each exchanging gas normally per unit volume).

These are descriptive labels for the z-score pattern; clinical interpretation of what underlies the pattern is the reader’s job.

5. Cohort-level diffusion summaries

For cohort-level breakdowns of diffusion_category, group and count with dplyr directly on a pft_interpret() result:

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
cohort <- data.frame(
  sex    = c("M","F","M","F","M","F"),
  age    = c(45,60,30,55,70,28),
  height = c(178,165,175,160,170,180),
  race   = "Caucasian",
  fev1_measured    = c(2.5, 1.8, 4.0, 1.5, 2.2, 3.8),
  fvc_measured     = c(3.8, 2.4, 5.2, 2.5, 3.5, 5.0),
  tlc_measured     = c(6.0, 4.5, 6.8, 4.0, 6.5, 7.0),
  dlco_measured    = c(20.0, 12.5, 28.0, 10.0, 18.0, 25.0),
  va_measured      = c(5.8, 4.0, 6.5, 3.5, 5.5, 6.0),
  kco_tr_measured  = c(3.5, 3.2, 4.3, 2.9, 3.3, 4.0)
)
pft_interpret(cohort) |>
  count(sex, diffusion_category)
#> # A tibble: 5 × 3
#>   sex   diffusion_category     n
#>   <chr> <chr>              <int>
#> 1 F     Mixed                  1
#> 2 F     Normal                 1
#> 3 F     Parenchymal            1
#> 4 M     Normal                 2
#> 5 M     Parenchymal            1

See also