pft 1.0.1
CRAN pretest fixes; no user-facing behaviour changes.
- Replaced the Unicode superscript-minus characters in the
pft_dlco_hb_correct()docstring with ASCII (^-1), so the PDF version of the reference manual builds under LaTeX without special Unicode support. - Declared
R (>= 4.1)in theDependsfield to match the fact thatpft_volume_subpattern()’s example uses the base-R pipe (|>), introduced in R 4.1. - Wrapped technical acronyms (
'ATS','ERS','GLI') in single quotes in the DESCRIPTIONDescriptionfield per CRAN convention.
pft 1.0.0
Initial public release. Baseline feature set implementing routine pulmonary function test interpretation under the ERS/ATS 2022 technical standard, with GLI-family reference equations (GLI-2012 and GLI Global 2022 spirometry, GLI-2021 static lung volumes, GLI-2017 diffusion capacity with the 2020 author correction), the ERS/ATS 2022 pattern classifier, severity grading, bronchodilator response, PRISm identification, hemoglobin correction, the FEV1Q survival index, GOLD 1-4 airflow-limitation grading, and Graham 2019 A-F spirometry acceptability grading. Pellegrino et al. 2005 legacy primitives are retained for cross-standard reclassification analyses. Detailed change log for the development history follows.
Breaking changes
The default
yearargument forpft_spirometry(),pft_interpret(),pft_classify(),pft_prism(), andpft_volume_subpattern()is now2022(the GLI Global / race-neutral equations) — previously2012. This aligns the package with the current ERS/ATS 2022 technical-standard recommendation and removes the need for aracecolumn when callers omityear. The combined effect with the always-suffixed output convention is thatpft_spirometry(d)now emitsfev1_pred_2022(wasfev1_pred), andpft_classify(d)looks upfev1_lln_2022by default (wasfev1_lln). To reproduce the previous behaviour passyear = 2012explicitly and include aracecolumn.-
Spirometry reference outputs from
pft_spirometry()/pft_interpret()now always carry a four-digit GLI-year suffix:fev1_pred_2012,fev1_lln_2012,fev1_zscore_2012, … for GLI 2012, and the existing_2022columns for GLI 2022. Previously the default- emitted unsuffixed columns and only 2022 carried a suffix; the asymmetry meant flipping a default in the future would silently change column names of code written against the unsuffixed convention. The always-suffix scheme makes the source equation explicit at every column and removes the implicit “unsuffixed == default” rule.
Lung-volume (Hall 2021) and diffusion (GLI 2017) outputs stay unsuffixed for now — each module currently ships a single standard. The same suffixing convention will be adopted there when competing standards emerge.
Knock-on changes:
-
pft_classify(),pft_prism(), andpft_volume_subpattern()gain ayearargument (default2012) that suffixes the spirometry-derived_llndefaults. Standalone callers should pass the year matching their upstream reference-function call. -
pft_interpret()threadsyearthrough the internal pipeline so the helpers above receive it automatically. -
pft_long()’syearcolumn is now populated for every spirometry row; volume / diffusion rows remainyear = NAuntil those modules adopt the convention. -
print.pft_resultannotates the per-measure rows with the chosen GLI year (e.g. “FEV1 (2012)”). When both_2012and_2022columns are present, the highest year is rendered. -
pft_plot()deduplicates per measure when both years are present, picking the highest year so a single lollipop renders one z-score per measure.
Tests, vignettes, and rendered docs have been updated to the new convention. If you wrote code against
fev1_pred/fev1_zscore/ etc., add the_2012suffix to those names (or pass column-name overrides to the helpers). pft_compare(),print.pft_compare(),summary.pft_compare(), and thepft_plot(type = "compare")mode have been removed. The GLI 2012 vs GLI Global 2022 reclassification analysis can still be reproduced by callingpft_interpret(data, year = 2012)andpft_interpret(data, year = 2022)and computing deltas on the resulting columns directly. Removed for being outside the package’s core ATS/ERS reference-value + interpretation mission.pft_plot()has been simplified to the single-patientlollipopfigure; thehistogram,trajectory, andbdrmodes have been removed. The signature is nowpft_plot(data)with notypeargument. Cohort and longitudinal figures are easier to build directly frompft_long()output piped intoggplot2.pft_cohort_summary()has been removed. Its outputs (per-measure z-score quantiles, ATS pattern frequencies, PRISm prevalence, diffusion-category frequencies) are easier expressed as plaindplyr::group_by() |> summarise()calls on apft_interpret()result, optionally piped throughpft_long()first.pft_dlco_hb_correct()no longer inspects itshemoglobinargument for likely-g/dL inputs (the previous behaviour was to warn and multiply by 10 when any value was below 30). Unit detection is out of scope for the package; pass g/L directly. Callers that were relying on the auto-conversion will now get numerically wrong corrections instead of a warning, so audit any upstream code that produced this argument.pft_required_columns()has been removed. It returned a hardcoded list of column names per function — documentation written as code, which had to be hand-synced with the actual function signatures. The same content is now invignette("input-format").pft_validate()has been removed. Most of its checks (sex coding, age and height range, race level membership) duplicated warnings already emitted by the reference functions’ input normalisation, and the rest (positive-value, FEV1 ≤ FVC) would surface naturally asNaNz-scores from the LMS power transform. Callers can write these checks inline against their cohort if they still want them.pft_glance()and thebroom::glance()S3 method onpft_resulthave been removed. The function returned four passthrough columns plus three trivial row-stats (worst_zscore,n_below_lln,n_above_uln), all of which are easier computed inline from apft_long()result.pft_long()and thebroom::tidy()S3 method are kept.pft_interpret()no longer auto-derivesfev1fvc_measuredfromfev1_measured / fvc_measured(and the analogousfrc_tlc_measured). Trust-the-caller: supply the ratio column explicitly if you want pattern classification, PRISm, or the volume sub-pattern stages to run. Note that in ATS/ERS “best test” workflows the reported ratio comes from a single maneuver and may not equalfev1 / fvcconstructed from best-of-N picks, so the caller’s explicit ratio is the safer source.
New features
-
pft_classify(),pft_prism(),pft_volume_subpattern(), andpft_diffusion_interpret()now accept tidy-eval column-name overrides (bare name, string, or!!var) for every input column, matching the pattern already used bypft_spirometry(),pft_volumes(),pft_diffusion(), andpft_interpret(). Defaults are the canonical pft column names, so callers using the convention pass no extra arguments. The error message when a required column is absent now reads"required column(s) missing from input: ..."(was function-specific wording). -
pft_diffusion_interpret()gains anSI.unitsargument matchingpft_diffusion()andpft_interpret(). The previous silent traditional-vs-SI auto-detect is dropped; SI-units callers must now passSI.units = TRUE(or overridedlco/kcodirectly). Threaded throughpft_interpret()so the package-levelSI.unitsflag now reaches every stage that needs it. -
pft_classify()now tolerates absence of thetlc/tlc_llncolumns: when either is missing fromdata, the existing spirometry-only fallback (Stanojevic 2022 Table 5 / Pellegrino 2005 Fig 2) triggers without raising. Previously a caller working from spirometry-only data had to add explicit NA TLC columns first. -
pft_long()pivots apft_resultto long form (one row per(patient, measure)). Thetidy.pft_result()S3 method dispatches to it whenbroomis installed. -
pft_diffusion_interpret(data)assigns a Hughes & Pride 2012 clinical category (Normal / Parenchymal / Volume loss / Mixed / Vascular / Elevated KCO / Other) from DLCO, VA, KCO z-scores. Run automatically bypft_interpret()when the diffusion z-scores are present. -
pft_volume_subpattern(data)differentiates the six Stanojevic 2022 Figure 10 lung-volume sub-patterns (Normal lung volumes / Large lungs / Hyperinflation / Simple restriction / Complex restriction / Mixed disorder). Auto-run bypft_interpret()when the requisite ratio columns are present. -
pft_fev1q(fev1, sex, age)implements the FEV1Q adult mortality index from Stanojevic 2022 Box 3. -
pft_dlco_hb_correct(dlco, hemoglobin, sex, age)applies the Cotes 1972 / Stanojevic 2017 hemoglobin correction. Reference Hb is 146 g/L (males ≥ 15) or 134 g/L (females, males < 15). Hb input must be in g/L; the function does not detect or convert g/dL (see the breaking change above).
Bug fixes
-
pft_quality()— child age cutoff corrected fromage < 6toage <= 6per Graham 2019 Table 10; a 6-year-old is now graded as a child. -
pft_quality()— child 10%-of-highest repeatability rule (Graham 2019 Table 10 footnote) was not applied; nowmax(absolute, 0.10 * max(values))forage <= 6. -
pft_quality()— sessions withn >= 2acceptable maneuvers and best-two diff above all A/C/D thresholds were graded F; now correctly graded E (“usable but with poor repeatability”). Grade U (“0 acceptable AND ≥ 1 usable”) is not implemented because the function takes only acceptable maneuvers. -
pft_gold()— added optionalfev1fvcargument enforcing the GOLD “FEV1/FVC < 0.7” prerequisite (Figure 2.10 header). Default preserves prior behaviour for existing callers; passingfev1fvc_measuredreturnsNAfor non-obstructed rows instead of a spurious GOLD grade.
Maintenance
- Minimum R version bumped from 2.10 to 4.0 to match the actual transitive dependency floor (
rlang,tibble). - Test count: 1195 → 1424 (+229).
Predecessor 2005 standard
Pellegrino 2005 interpretive primitives are now available so the package can serve a cross-standard reclassification analysis (comparing Stanojevic 2022 against the predecessor algorithm on the same cohort). All constants and decision logic are verified line-by- line against the source PDF (papers/pellegrino_2005/); the extraction is documented in papers/pellegrino_2005/verification.md.
-
pft_classify()gains astandard = c("2022", "2005")argument. The 2022 path is the default and is unchanged. The 2005 path implements the Pellegrino et al. ERJ 2005 Figure 2 algorithm: it has four labels (Normal, Obstructed, Restricted, Mixed) – no Non-specific category, which was introduced after 2005. Cells that 2022 labels “Non-specific” are labeled “Restricted” under -
pft_severity_2005(pctpred)grades severity from FEV1 percent predicted into the five Pellegrino bands (mild / moderate / moderately severe / severe / very severe). -
pft_bdr_2005(pre, post)applies the dual >=12% AND >=200 mL criterion from the 2005 standard, without needing the patient’s predicted value. -
pft_interpret()gains a matchingstandard = c("2022", "2005")argument that dispatches all three primitives to the 2005 forms in one call.year(GLI equation year) andstandard(interpretive rules) are independent – you can pair GLI 2022 race-neutral equations with the 2005 interpretive logic, or any other combination, for nuanced reclassification analyses.
Input contract
-
pft_spirometry(),pft_volumes(),pft_diffusion(), andpft_interpret()now accept tidyverse-style column references for the demographics inputs. Bare names (sex = Sex), strings (sex = "Sex"), and rlang injection (sex = !!my_var) are all supported. Defaults match the canonical column names, so existing code keeps working unchanged. The user’s original column names are preserved in the output. - New “Input data format” vignette walks through the data-frame contract, units and types per column, the override syntax, and common errors.
Input normalization
-
Bug fix (silent-wrong-sex): any
sexvalue other than"M"was previously treated as"F"without warning, so a cohort with"Male"/"Female"/"male"etc. silently produced female predictions.pft_spirometry(),pft_volumes(), andpft_diffusion()now soft-correct common variants ("male"->"M","Female"->"F", etc.) with a warning; truly unrecognised values (e.g."X","Unknown") are set to NA rather than mis-coded. -
racevalues are similarly soft-corrected case-insensitively with whitespace and synonym tolerance ("caucasian"->"Caucasian","white"->"Caucasian","black"->"AfrAm", etc.). All normalisation findings roll up into a single consolidated warning per call. - Missing
sex,age,heightcolumns (orracefor GLI 2012) now error with a clear message listing the expected names, rather than silently producing all-NA output.
Reference equations
- All internal reference data is now built reproducibly from the official ERS / ATS source documents.
data-raw/build_gli_2012.R,build_gli_2022.R,build_gli_2021_volumes.R, andbuild_gli_2017_diffusion.Reach read the published lookup-table workbook (or, where unavailable, the equation table from the article PDF) and regenerate the corresponding.RDataand CSV artifacts. -
R/spirometry.R,R/lung_volumes.R,R/diffusion_capacity.R, andR/ats_classification.Rnow carry@referencesto the source papers (and the 2020 author correction for the diffusion equations). - Removed
data-raw/coeffs_spline_spiro.RData: an orphan blob holding GLI 2012 polynomial coefficients for ages 25-95, which the package never used (the lookup-table approach inR/spirometry.Rinterpolates directly from spline values instead).
New clinical extensions
-
pft_quality(values, age)grades a set of acceptable spirometry maneuvers A-F per the Graham et al. ATS/ERS 2019 spirometry standardization update (doi:10.1164/rccm.201908-1590ST). Tighter repeatability thresholds applied for children under 6. -
pft_gold(fev1_pctpred)returns the GOLD COPD severity grade (1-4) from FEV1 % predicted.
New interpretation primitives
-
pft_severity(zscore)returns one of"normal","mild","moderate","severe"per the Stanojevic et al. ERJ 2022 z-score cut points (>= -1.645, > -2.5, > -4, <= -4). -
pft_bdr(pre, post, predicted)classifies BDR per the 2022 criterion (>10% change relative to predicted, replacing the earlier 12%/200 mL rule). -
pft_prism(data)adds aprismlogical column flagging Preserved Ratio Impaired Spirometry (FEV1 below LLN, FEV1/FVC at or above LLN). Requires only spirometry; does not need TLC. -
pft_change(z1, z2, r)computes the conditional change z-score recommended by Stanojevic 2022 for interpreting serial PFT measurements over time. Configurable autocorrelationr.
Workflow wrappers
-
pft_interpret(data)is a single-call workflow that auto-detects which inputs are present and emits a complete Stanojevic 2022-compliant interpretation: reference values, z-scores, percent predicted, severity grading, ATS pattern, PRISm flag, and bronchodilator response. This is the recommended entry point for clinical-style reporting. -
pft_plot(result)generates a clinical-style z-score lollipop plot with severity-band shading. Requiresggplot2(Suggests).
New outputs: z-score and percent predicted
-
pft_spirometry(),pft_volumes(), andpft_diffusion()now optionally compute z-scores and percent predicted. Supply a<measure>_measuredcolumn in the input data frame (e.g.fev1_measured,frc_measured,dlco_measured) and the function appends<measure>_zscoreand<measure>_pctpredcolumns alongside the existing<measure>_pred/<measure>_lln/<measure>_uln. Backwards compatible: callers who only supply demographics continue to get the three existing reference-value columns and nothing else. - z-score uses the LMS formula
((measured/M)^L - 1) / (L*S); percent predicted is(measured / M) * 100. Both propagateNAfrom the measured value, the LMS parameters, or the LLN as expected. - Z-score formula sanity (z = 0 at predicted, ~+/-1.645 at LLN/ULN) is tested for every measure across the three functions. The GLI 2022 oracle CSV at
tests/testthat/gli_2022_oracle.csvcovers z-score and percent predicted as well as predicted and LLN, validated at tolerance1e-8.
Reference-function robustness
-
Bug fix:
pft_spirometry(),pft_volumes(), andpft_diffusion()previously crashed with a “missing value where TRUE/FALSE needed” error when any row had a missing value (NA) insex,age, orheight. They now skip such rows and emitNAfor the reference values on that row, matching the behaviour already provided for missingrace(spirometry) and forpft_classify(). Real clinical PFT data routinely contains missing demographics; the prior behaviour required callers to filterNAs themselves.
ATS classification
-
Bug fix (clinically meaningful): the
ANNNandNANNpattern labels were inverted relative to Stanojevic et al. ERJ 2022 Figure 8. The classifier now correctly returns:-
ANNN(isolated low FEV1) → “Normal” -
NANN(isolated low FVC) → “Non-specific” Previously these two were swapped. The change re-labels patients whose spirometry profile is “isolated low FEV1 + everything else normal” (previously “Non-specific”, now “Normal”) and vice versa for isolated low FVC. Seenotes/ats_classification_label_fix.mdfor the clinical-review memo.
-
-
Bug fix: the “all normal” branch was comparing FVC against
fev1_llninstead offvc_lln. Combined with the label-swap above, patients with FVC slightly below their FVC LLN but above their FEV1 LLN are now correctly routed to “Non-specific” rather than being silently labelled “Normal”. - The classifier was expanded to all 16 combinations of (FEV1, FVC, FEV1/FVC, TLC) statuses, adding the new
ats_pattern_combinationoutput column. (Initial expansion: previous internal release; bug fixes: this release.)
Tests
- Test coverage grew from 36 to 101 tests across all four test files. Additions include predicted-value (median) comparisons against the GLI web calculator ground truth,
NA-propagation tests, out-of-range tests, structural / column-contract tests, and a clinical-scenario suite forats_classificationgrounded in Stanojevic 2022 Figure 8 / Table 5 / Table 8. - New GLI 2022 / “GLI Global” oracle: a frozen 30-row ground-truth fixture at
tests/testthat/gli_2022_oracle.csvcovering predicted, LLN, z-score, and percent predicted for FEV1, FVC, and FEV1/FVC. Regenerated viadata-raw/build_gli_2022_oracle.R(see that script for provenance). Only the static CSV ships; no test-time dependency on any external package.
Documentation
- New README with installation, usage, citations, and a research-use disclaimer.
- New
inst/CITATIONsocitation("pft")returns the package and the underlying reference papers asbibentryobjects. - The MIT license file is now correctly declared in
DESCRIPTIONand shipped viaLICENSE/LICENSE.md. -
notes/ats_classification_label_fix.mdrecords the rationale and clinical-review questions for the ATS pattern-label changes above.
Internal
- Project now uses
renvfor dependency management. - Reference paper PDFs and supplement workbooks live under
papers/but are excluded from git and from theR CMD buildtarball (they are copyrighted publisher content). -
notes/(clinical-review memos and similar) is alsoRbuildignored.docs/is reserved for the pkgdown-built site (gitignored; built and deployed to thegh-pagesbranch by.github/workflows/pkgdown.yaml).
