
Diffusion capacity (DLCO / TLCO): reference, Hb correction, interpretation
Source:vignettes/diffusion-capacity.Rmd
diffusion-capacity.RmdDiffusion 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).
Choosing the unit system
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.
Source population
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.
Example
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:
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.
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 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 Mixed4. 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 1See also
-
vignette("interpretation-guide")– pattern decision tree and severity bands. -
vignette("longitudinal-analysis")– serial DLCO change and decline. -
?pft_diffusion,?pft_diffusion_interpret,?pft_dlco_hb_correctfor the function references.