pft 1.0.4
Documentation and test patch release incorporating peer-review feedback. No changes to computed outputs; every exported function returns the same values as in 1.0.3.
-
pft_quality(): retitled and reframed as computing the Graham et al. 2019 Table 10 session grade from maneuvers already judged acceptable. The docs now state that maneuver acceptability must first be established by a trained reviewer from the full flow-volume and volume-time curves, that the function cannot assess raw-curve criteria, that its grade supplements rather than replaces technical review, and that FEV1/FVC and pre/post sets are graded separately. The existingU->Flimitation is explained more precisely. -
pft_gold(): retitled GOLD 2026 spirometric airflow-limitation grade, replacing the earlier COPD-severity framing. The docs state that the grade is based on post-bronchodilator FEV1 % predicted, thatfev1fvcmust be the post-bronchodilator ratio, thatpftdoes not infer test phase, that the GOLD fixed-ratio prerequisite is enforced only whenfev1fvcis supplied, and that the helper is for use within the GOLD COPD framework and does not diagnose COPD. Noyearargument was added: only the GOLD 2026 grading is implemented. -
pft_classify(): new “Restriction requires a low TLC” section.RestrictedandMixedboth require TLC below its LLN; a low FVC alone is never labelled restriction; rows without TLC are never labelledRestrictedorMixed. New regression tests pin each rule. -
pft_volumes()/pft_diffusion(): new Applicability sections stating the GLI 2021 European-ancestry and GLI 2017 Caucasian source populations, and that GLI Global 2022 spirometry does not make these equations race-neutral. -
pft_diffusion(): new Units section.SI.unitsselects the unit system for both outputs and measured inputs; units are never inferred from numeric magnitude or converted, and measured columns from the other unit system are ignored. -
pft_bdr(),pft_bdr_2005(),pft_interpret():pre/post(and the*_pre/*_postcolumns) are defined as the baseline value before, and the corresponding value after, bronchodilator administration. The caller assigns these labels andpftdoes not infer test phase. -
vignette("input-format"): new Precision and plausibility section. There is no fixed decimal-place requirement; ages outside each equation’s spline range returnNA;pftperforms no general physiologic plausibility or technical QC screening. -
vignette("interpretation-guide"): fixed the pattern example, whose printed output (row 3Mixed) disagreed with the row-by-row prose (row 3Restricted). The example now produces all five labels in the order described. The pattern bullets now match the implemented lookup. - README / pkgdown: dated Implemented standards table, a Maintenance and reproducibility section, a plain-language explanation of the MIT license, and person-first / phenotype-first wording around airflow obstruction. Unqualified “current ATS/ERS” phrasing was replaced with explicit standard years.
pft 1.0.3
Documentation-only patch release. No user-facing behaviour changes. This release folds in additional refinements from the SoftwareX manuscript review (round 2):
-
pft_bdr_2005(): added an explicit Boundary-convention section to the roxygen docs explaining why the implementation follows the Pellegrino 2005 running-text (strict>) formulation over the Table 6 inclusive-symbol formulation. Vignette (interpretation-guide) cross-references the note. -
pft_interpret()wording sweep: README table now describes the wrapper as a “one-call research workflow” that runs only the routine 2022 primitives, and the “all functions take a data frame and return a data frame” claim was corrected to name the actual exceptions. -
CITATION.cffandinst/CITATIONnow use the version-specific Zenodo DOI (with the concept DOI called out separately as the version-agnostic identifier). -
inst/benchmarks/pft_interpret_benchmark.Rrewritten to run a warm-up plus five measured repetitions and report the median elapsed time, and to use the same physiologically constrained synthetic cohort generator (FEV1 <= FVC, TLC > FVC) that the SoftwareX manuscript now uses for its reclassification example.
pft 1.0.2
Documentation-only patch release. No user-facing behaviour changes. This release resolves internal inconsistencies flagged during the SoftwareX manuscript review.
-
pft_bdr_2005(): rewrote the description and example to remove “at least 12% / at least 200 mL” wording, which conflicted with the implementation and tests. Both the implementation and the Pellegrino et al. ERJ 2005 source (p. 959, “(>12% of control and >200 mL)”) use strictly greater than 12% AND strictly greater than 200 mL; the documentation now says so, and the example demonstrates a boundary case (12% exactly with +300 mL, which is not significant) alongside a clear-significant case and a clear-negative case. The behavior tests already covered this convention and were not changed; additional boundary-condition tests were added for defensiveness. -
pft_interpret(): replaced “combines every interpretation primitive in this package into a complete clinical report” with wording that more accurately describes the one-call research workflow, and explicitly noted the helpers that are not auto-run (pft_gold(),pft_fev1q(),pft_quality(),pft_change(),pft_dlco_hb_correct()). -
pft_change()documentation: no wording changes were needed, but the SoftwareX manuscript no longer describes the function as “scalar-valued”; it returns a row-wise tibble withccs,r_used, andis_significant. - Reframed package identity to reflect the multi-standard scope. The package title, README,
_pkgdownhome block,CITATION, Zenodo metadata, paper, and top-level vignette openings no longer describepftas an “ERS/ATS 2022” implementation; they now present it as reference-value and interpretation tooling composing GLI, ATS/ERS, and GOLD standards, with the 2022 ATS/ERS interpretive statement called out as the most recent standard at the time of the current release. Function names, output column suffixes, argument defaults, and provenance citations are unchanged. - Renamed the
_pkgdownreference-index section “Predecessor 2005 standard” to “Legacy interpretive primitives”. - Vignette wording (
pft.Rmd,glossary.Rmd,interpretation-guide.Rmd) updated so the 2005 BDR criterion is quoted as strict>12% AND >200 mL, matching implementation and tests.
pft 1.0.1
CRAN release: 2026-07-11
First CRAN release. 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 (both inequalities strict per Pellegrino 2005 p. 959), 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 one-call research workflow that auto-detects which inputs are present and appends the Stanojevic 2022 reference values, z-scores, percent predicted, severity grading, ATS pattern, PRISm flag, and bronchodilator response for modalities the caller supplied. Standalone helpers (pft_gold(),pft_fev1q(),pft_quality(),pft_change(), andpft_dlco_hb_correct()) are exported for stand-alone use and are not run automatically. -
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).
