ettbc (development version)
Updated the reusable GitHub Actions workflows to call
Morrison-Lab/ghainstead ofd-morrison/gha, following that repository’s move. Actions does not follow repository-rename redirects foruses:, so the calls were failing to resolve before any job started.Added
apply_eligibility_criteria(), the cohort eligibility/enrollment logic from García-Albéniz et al. (SASc01_eligibility.sas, item 3). Given a demographics table, a monthly enrollment table, and screening mammogram events, it selects participants alive at the age threshold, with a qualifying mammogram near that age (which becomes the derived study entry month), twelve consecutive months of fee-for-service Medicare enrollment ending at entry, and entitlement by age rather than disability or end-stage renal disease. Complementsgagne_weights()/comorbidity_score()(#27) as the other half of cohort construction (#31, #12).fit_weighted_logistic()now muffles the expectednon-integer #successes in a binomial glm!warning that non-integer IPW weights trigger. The fit is unchanged and other warnings (non-convergence, separation) still surface; this just stopsbootstrap_ci()from emitting one such warning per resample.Added
fit_weighted_logistic(): the shared weighted pooled-logistic fitting primitive behindfit_outcome_hr()andpredict_survival_ipw(), which both now call it instead of repeating the weighted-GLM block. Exported so sibling packages can reuse the same fit; first step of the cross-package outcome-model consolidation (#25). No change to existing results (#12).Added
gagne_weights()andcomorbidity_score(), the Gagne combined comorbidity score used by García-Albéniz et al. for cohort eligibility and adjustment (item 3).gagne_weights()returns the 20 published per-condition weights from the official ICD-9-CM / ICD-10-CM combined-comorbidity-score program – including the two negative weights (HIV/AIDS and hypertension).comorbidity_score()applies a named weight vector to per-person 0/1 condition indicators, defaulting to the Gagne weights. Mapping ICD codes to the condition flags is left to the user (e.g. the {comorbidity} package) (#12).bootstrap_ci()now seeds its resampling withwithr::with_seed()instead of a hand-rolled.Random.seedsave/restore, and its bootstrap loop was factored into a helper. Behavior and results are unchanged (#12).Added
standardized_rate_difference(), the treatment-pattern secondary analyses from García-Albéniz et al. (SAScann26/cann27/cann28: surgery, chemotherapy, and radiotherapy receipt among screen-detected cancers). It computes a direct-standardized difference in a binary outcome rate between the CONTINUE and STOPBASE arms, standardized to the pooled sample over user-specified strata (age, comorbidity) and restricted to the common-support strata, with a bootstrap percentile confidence interval and RNG-state restoration, matching the SASPROC STDRATE(method = direct,effect = diff) approach (#12).Added
deterministic_bias_analysis()andprobabilistic_bias_analysis(), the unmeasured-confounding sensitivity analyses from García-Albéniz et al. (supplementary analysis). For a single dichotomous, time-fixed confounder with a given prevalence in each arm and an additive outcome effect, the deterministic version adjusts an observed arm risk difference by(prev_continue - prev_stopbase) * confounder_effect(vectorized over a grid of assumptions); the probabilistic version draws the bias parameters from uniform priors and the observed risk difference from its sampling distribution to return a Monte Carlo simulation interval. The caller’s RNG state is restored on exit (#12).Exported
compute_rcs_basis(), the Harrell restricted-cubic-spline basis (SAS%RCSPLINEparameterization) used internally by the outcome and propensity models, so other packages can reuse the same parameterization instead of writing it again. Behavior is unchanged; the function moved to its own file and gained a public man page, examples, and tests (#12).Added
negative_control_analysis()and negative-control outcome support, the falsification check from García-Albéniz et al. (death from cancer of the corpus uteri).expand_to_long()gains an optionalnc_died_colargument that builds a cause-specificnc_dead_t1outcome the same way asbc_dead_t1(the shared logic is factored into one helper);simulate_screening_cohort(negative_control = TRUE)adds annc_deathindicator to the simulated cohort.negative_control_analysis()runsfit_outcome_hr()on the negative-control outcome and reportsnull_consistent, whether the arm effect’s confidence interval covers the null. Continued screening should have no effect on a death unrelated to the breast; a clearly non-null result would flag residual bias. The default behavior ofexpand_to_long()andsimulate_screening_cohort()is unchanged (#12).Added
simulate_screening_cohort(): an exported generator that simulates a synthetic cohort of arbitrary size and returns the three linked data frames (cohort,screening_mammograms,diagnostic_mammograms) the pipeline needs. It wraps the internal generators behind a single seeded random-number stream, sosimulate_screening_cohort(100, 108, seed = 2020)reproduces the shipped example datasets exactly; the seed is applied withwithr::with_seed(), so the caller’s RNG stream is left untouched. The “Using ettbc” article uses it to demonstrate the fullclone_censor()->compute_ipw_weights()->fit_outcome_hr()->predict_survival_ipw()->bootstrap_ci()pipeline end to end on a larger simulated cohort (#12).Added
augment_long_covariates(): builds the time-varying screening covariates the weight and propensity steps need from the long-format data and the mammogram events, porting the SAScann17baugmentation. It addsscrmammo,dxmammo,anymammo,tslm,tslm_lag, andmonthBC, forcing a screen at entry, resetting the time-since-last-mammogram clock at any mammogram, not counting a screen in the month after a breast-cancer diagnosis, and reclassifying a screen withindx_reclass_monthsof the previous mammogram as diagnostic (#12).Added
fit_screening_propensity(): fits the pooled logistic screening-propensity model (SAScann17bdenominator) and returns the predictedp_scrmammothatcompute_ipw_weights()consumes. The linear predictor usestslm_lag(linear plus a restricted-cubic-spline basis),month2andmonth2^2, and any user-supplied baseline/time-varying covariates; the model is fit on thetslm_lag >= min_tslm_lagdecision window, deduplicated to one row per participant-month. Together withaugment_long_covariates()this letsclone_censor()output drivecompute_ipw_weights()end to end without hand-set columns (#12).Removed leftover package-template scaffolding: the
example_function()function (and its test and man page), thequarto_vignette.qmdandquarto_article.qmdtemplate-demo vignettes, and the genericCHECKLIST.mdandUSAGE.mdtemplate setup guides. RewroteREADME(.Rmd/.md) and the “Getting Started” vignette to describe{ettbc}and demonstrate theclone_censor()->expand_to_long()pipeline on the synthetic example data, and replaced the placeholderinst/extdata/README.md.Added
predict_survival_unadjusted(),predict_survival_baseline_adjusted(), andpredict_survival_ipw(): fit pooled logistic regression models with restricted cubic spline time terms and arm-by-time interactions, then apply g-computation to produce marginal survival curves for each trial arm. Time is modeled with a full-rank Harrell restricted-cubic-spline basis (a linearmonth3term pluslength(rcs_knots) - 2nonlinear terms) following the SAS%RCSPLINEmacro, rather thansplines::ns(), which previously aliased with the linear term and left the design rank-deficient. The marginal survival curves are unchanged; the spline terms now vanish at month 0, so thefit_outcome_hr()arm odds ratio is the well-defined contrast at baseline. All three functions apply themax_monthfilter before model fitting, require both arms to be present (before and after filtering), and validateweight_colwhen supplied (#1).Added
compute_ipw_weights(): computes stabilized cumulative IPW weights for each participant-arm-month, truncated at the 99th percentile computed separately within each arm. CONTINUE-arm weight logic matches the SAScann17bimplementation: updates occur at every month in thetslm_lag11–13 compliance window (not only whenscrmammo == 1), using conditional uniform probabilities (1/3, 1/2, 1 at months 11, 12, 13 respectively), and stop after a breast-cancer diagnosis (#1).Added
fit_outcome_hr(): fits an IPW-weighted pooled logistic regression and returns the odds ratio (hazard ratio approximation) for the STOPBASE arm with a 95% Wald CI. Uses cluster-robust variance viasandwich::vcovCL()whensandwichis available (newcluster_id_colargument). Validates bothweight_colandcluster_id_colbefore fitting. AddedsandwichtoSuggests(#1).Added
bootstrap_ci(): nonparametric bootstrap for 95% percentile confidence intervals on the IPW-estimated survival difference. Failed iterations are counted; a warning is issued when the failure rate exceedsfail_threshold(default 10%). The caller’s RNG state is saved and restored on exit. Validates thatn_bootis a positive integer (#1).Added
false_positives(): computes false positive rates for histological evaluations, stratified by trial arm and screening round. Filters evaluations to within each arm’s observed follow-up and deduplicates repeat evaluations withinwindow_monthsper participant-arm (#1).Added
extract_screening_mammograms(),extract_any_mammograms(), andextract_diagnostic_mammograms(): template functions for extracting mammogram events from Medicare claims data by HCPCS code (#1).Added
clitoImportsinDESCRIPTION(the restricted cubic spline basis is computed directly, sosplinesis no longer a dependency).Added
clone_censor(): implements the clone-censor step of the target trial emulation methodology. Creates two clones per participant (STOPBASE and CONTINUE arms) and applies the corresponding censoring rules (#1).Added
expand_to_long(): converts cloned data to one row per participant-arm-month for use in discrete-time survival analysis (#1).Added example synthetic datasets:
cohort,screening_mammograms, anddiagnostic_mammograms(#1).Added vignette article “Using ettbc: Emulating a Target Trial for Breast Cancer Screening” (#1).
Updated
DESCRIPTIONtitle and description to reflect the package purpose.Migrated the
@claude, Claude review, andNEWS.mdchangelog-check GitHub Actions workflows to the reusable workflows ind-morrison/gha.Internal refactor: decomposed the per-participant clone-censor logic in
clone_censor()into smaller helper functions, factored the cumulative mortality computation in the “Using ettbc” vignette into a reusable helper, and split the example-data generation into focused simulation functions now living inR/. Helper functions were reorganized to one function per file, anonymous functions replaced with named helpers, and nested calls rewritten as pipes. No user-facing behavior changes.
ettbc 0.0.0.9000
- Initial development version