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()).
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).
The caller chooses the unit system with the SI.units
argument. It applies to both the reference outputs and the measured
inputs:
SI.units |
Measured columns read | Units |
|---|---|---|
FALSE (default) |
dlco_measured, kco_tr_measured,
va_measured |
mL/min/mmHg, mL/min/mmHg/L, L |
TRUE |
tlco_measured, kco_si_measured,
va_measured |
mmol/min/kPa, mmol/min/kPa/L, L |
Measured diffusion values must already be in the selected
unit system. pft does not infer units from column
names or numeric magnitude and does not convert measured values between
systems. Columns that belong to the other system are ignored (for
example, dlco_measured produces no z-score when
SI.units = TRUE). Values entered in the wrong units under
the right column name cannot be detected and will give incorrect
z-scores. VA is in litres either way.
The GLI 2017 source publication describes its reference equations as being for Caucasians. About 85% of the collated data came from Caucasian participants, and there were too few non-Caucasian data to derive separate equations. Using the race-neutral GLI Global 2022 spirometry equations does not make the diffusion equations race-neutral. Consider whether this source population is appropriate for your study population before interpreting the outputs.
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().
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:
\[ \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.98795Pass 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.
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 KCOThe 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 MixedAlveolar volume (VA) is the axis that splits restriction into the classifier’s two volume-loss categories:
These are descriptive labels for the z-score pattern; clinical interpretation of what underlies the pattern is the reader’s job.
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 1vignette("interpretation-guide") – pattern decision
tree and severity bands.vignette("longitudinal-analysis") – serial DLCO change
and decline.?pft_diffusion, ?pft_diffusion_interpret,
?pft_dlco_hb_correct for the function references.