# Diffusion capacity (DLCO / TLCO): reference, Hb correction, interpretation

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:

\[ \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

- `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_correct` for the function references.