Changelog
Source:NEWS.md
hvtiRpropensity 0.2.1
0.2.1 fixes formula handling across the model functions and the findings of a rollup review of the 0.2.0 series. Two changes move published numbers; check them before upgrading a study:
Pooled degrees of freedom are Barnard and Rubin’s, as
mice::pool()reports them, so pooled p-values and intervals change.Ordinal standard errors come from the expected information, as SAS reports them, so ordinal p-values and intervals change, by a fraction of a percent on the package’s test data.
fit_logistic()accepts a dot formula such asy ~ .. It stopped with “Required column(s) not found indata: .” before. The dot expands to every column except the outcome,id_colandimputation_col, as it already did inbs_continuous()andfit_linear().bs_count()checksoutcome_colthe waybs_continuous()does: a value that is not the formula’s response, or a response naming more than one column, is refused. A dot formula no longer fits theid_colandimputation_colcolumns as covariates.fit_logistic(),fit_linear(),bs_continuous()andbs_count()check only the predictors the model keeps. A variable the formula subtracts, as iny ~ . - dropme, is no longer checked for matching levels across imputations, andfit_logistic()no longer lists it in$meta$predictors.fit_linear(),bs_continuous()andbs_count()require the response to read exactly one column even whenoutcome_colis supplied.I(count + other)withoutcome_col = "count"fitted the sum while recordingcountas the outcome;cbind(count, other)stopped with an unhelpful subscript error.Pooled estimates from
fit_logistic(),fit_linear(),bs_continuous()andbs_count()use Barnard and Rubin’s (1999) small-sample degrees of freedom, with complete-data degrees of freedom n - k, asmice::pool()does. They used Rubin’s (1987) large-sample degrees of freedom, which can run into the millions when little is missing. Thedfcolumn, and the p-values and intervals built on it, change for every pooled model; estimates and standard errors do not. A coefficient with no between-imputation variance, which had infinitedfand a normal test, now gets a finitedf. To reproduce the values in SAS, runPROC MIANALYZEwithEDF=set to n - k. The help pages for all four functions explain the calculation under “Pooled degrees of freedom”.Pooling over imputations stops with an error when the first imputation’s model has no residual degrees of freedom (n - k of zero or less), as a saturated model does. A saturated pooled logistic model returned meaningless
dfand p-values before.The SAS oracle fixtures for
fit_logistic()andfit_linear()now include aPROC MIANALYZE EDF=run, and the tests check the pooled degrees of freedom, p-values and intervals against it directly. No change in behaviour.ps_forest()accepts a dot formula such astavr ~ .. It stopped with “Required column(s) not found indata: .” before. The dot expands to every column except the treatment andid_col, as it does inps_logistic(). A column the formula subtracts, as in. - x3, is no longer fitted, checked for missing values or reported in the SMD table.ps_logistic(),ps_ordinal()andps_nominal()leave a constant column the formula subtracts out of the SMD table.The SAS oracle fixtures for
fit_logistic()andbs_count()are exported at full precision, with thePROC LOGISTICfits run atGCONV=1e-12. The binary, nominal and pooled comparisons, and thePROC GENMODPoisson comparison, are tested at tolerances near their measured agreement. No change in behaviour.fit_linear(),bs_continuous(),bs_count()andps_forest()check every column the formula names, subtracted ones included, asfit_logistic()does. A subtracted column missing fromdatareachedlm()orglm()as “object not found”, or picked up a same-named object from the calling environment; inps_forest()a misspelled one was ignored, so the column meant to be dropped was fitted.bs_count()refuses a transformed response such aslog(count + 1), which it fitted as counts with a warning.fit_logistic()refuses one too, matching its error message.A
.that leaves no columns once the response,id_colandimputation_colare set aside stops with a clear error.Ordinal models in
fit_logistic()andps_ordinal()report standard errors from the expected (Fisher) information, as SASPROC LOGISTICdoes, instead ofMASS::polr()’s observed information. The two differ for the cumulative logit; on the package’s SAS oracle data the standard errors moved by up to 0.4%, and the p-values, intervals and pooled results with them.vcov()on the storedpolr()model still returns the observed version.polr()runs at a tighter tolerance (reltol = 1e-14), so ordinal estimates move slightly (about 2e-5 on the oracle data) and now agree with SAS to about 1e-7.
hvtiRpropensity 0.2.0
0.2.0 consolidates the work since 0.1.0 and the fixes from a rollup review of that series (#79 to #93). Before upgrading a study, check these changes, each described in full below:
The four
sa_*()functions returnps_dataobjects; results move into$metaand$tables.sa_evalue()needsrare = TRUEorFALSEfor an odds ratio or a hazard ratio, and its earlier conversions were the wrong way round.ps_rmst()on aps_match()object analyses the matched pairs and resamples them by pair; score weighting of a matched object is refused.Weighted SMDs normalise the weights within each arm, so they no longer depend on
stabiliseand can differ from SAS%stddiffwhen the weights do not average 1.ps_forest()writes matching weights, not overlap weights, tomt_wt.ps_ordinal()needs an ordered factor ortreatment_levels.ps_weight()stabilises ATE weights only.Rows with a missing treatment or score are excluded and counted, not crashed on or silently kept.
DESCRIPTIONnow declares the Quarto command line tool inSystemRequirements. The vignettes have always needed it to build; the field makes that visible to installers and toR CMD check.Articles on the pkgdown site put the table of contents on the left and use the full width of the window, through
pkgdown/extra.css. The installed vignettes are unchanged: the Quarto vignette engine renders them in its own minimal format, which has no sidebar layout.ps_match()gainsscale = c("probability", "logit")andcaliper_sd, so pairs can be matched on the logit of the propensity score with a caliper in pooled standard-deviation units,sqrt((var_t + var_c) / 2).scale = "logit", caliper_sd = 0.2is the Austin (2011) recommendation. The default stays"probability", which keeps the [0, 1] check onscore_col.$metanow recordsscaleandcaliper_sd, and$meta$caliperis the absolute caliper applied.The SAS templates in
inst/sas-templates/no longer carry the study they were taken from: each study path is now/studies/<study>, and the study title and cohort line read<study description>andCCF, 19xx to 20xx, n=xxxx, as the%let STUDYDESCand%let STUDYPOPlines already did. The analysis code is unchanged.fit_linear()fits an ordinary least-squares model to one dataset or to stacked imputations, with Rubin-pooled inference verified againstPROC MIANALYZE. It returns the same data, models, meta and tables shape asfit_logistic(), and its$tables$fit_qualitygives each fit’s residual standard error and R-squared. A single fit, here and inbs_continuous()’s new estimates, is inferred on the t distribution with residual degrees of freedom, asPROC REGreports it. A dot formula such asef ~ .expands over the data’s columns excluding the identifier and imputation columns, here and inbs_continuous(), so the patient ID is never fitted as a covariate.bs_continuous()now carries fit status, coefficient estimates, covariance and per-imputation estimates, asbs_count()does. Its balancing score is unchanged. A transformed exposure, such aslog(hct_nadr) ~ ..., now works.bs_continuous()now stops, naming the imputation, when a term is aliased (for example a covariate that duplicates another), where it used to return a score from a rank-deficient fit.The pooled-covariance check against
PROC MIANALYZEintest_lm_sas_oracle.Rnow compares like with like. SAS’sTCovis the multivariate total covariance,(1 + rbar) * Ubar, not the elementwise Rubin total thatfit_logistic()reports, so the test checks the diagonal against the SAS standard errors and rebuildsTCovfrom the per-imputation fits, at1e-5rather than1e-4. No package code changed.The help pages for
ps_match(),ps_weight()and the package now namehvtiPlotR::hv_mirror_hist()andhvtiPlotR::hv_balance()as code rather than linking to them. ‘hvtiPlotR’ is not a declared dependency, soR CMD checkcould not resolve the links on a machine without it and reported “Unknown package ‘hvtiPlotR’ in Rd xrefs”.Behaviour change:
ps_forest()now writes Li-Greene matching weights (ATM; Li & Greene, 2013) toweight_col(defaultmt_wt),min(p, 1 - p) / pfor treated patients andmin(p, 1 - p) / (1 - p)for controls, exactly asps_logistic()does. It previously wrote overlap (ATO) weights,1 - pandp, under the same name, so swapping one scoring function for the other silently changed the estimand. Anyone who readmt_wtfrom aps_forest()result, for weighted balance,ps_mw_var()orhvtiPlotR::hv_mirror_hist(weight_col = "mt_wt"), gets different weights and should rerun. Both help pages now state the estimand (#83).Behaviour change:
ps_ordinal()now stops unless the treatment is an ordered factor ortreatment_levelsgives the level order. It used to order a character or unordered-factor treatment alphabetically, so"Low"/"Medium"/"High"was fitted as High < Low < Medium, with no warning and coefficients of the wrong sign. The error says how to declare the order (#87).ps_logistic()withtreated_levelalone now accepts any treatment with two values, such as a"TAVR"/"SAVR"factor, asps_forest()already did, and gives the same scores as the 0/1 coding. A treatment with more than two values still stops (#87).ps_logistic(),ps_ordinal(),ps_nominal(),fit_logistic()andfit_linear()checkid_colonly whenimputation_colis set, as their documentation says. Data without anidcolumn no longer stops a single-dataset fit (#87).ps_stddiff()now normalises the weights to mean 1 within each group before computing a weighted Gaussian or rank difference, so multiplying a group’s weights by a constant no longer changes it (#82). As a resultps_weight()$tables$smd_weightedis the same withstabilise = TRUEandstabilise = FALSE, andps_stddiff_perm()withweight_colinherits the fix.Weighted SMDs change for Gaussian and
nong_ordvariables whenever the weights do not already average 1 in each group, as matching weights and unstabilised IPTW weights do not. They then no longer equal the%stddiffmacro’s output, which depends on the scale of the weights; they still equal it when the weights average 1 in each group. Binary and categorical differences, and every unweighted difference, are unchanged.sa_evalue()now applies the odds-ratio and hazard-ratio conversions the right way round (VanderWeele & Ding 2017). For a rare outcome the OR or HR is used as the risk ratio directly; for a common outcome the OR becomessqrt(OR)and the HR becomes(1 - 0.5^sqrt(HR)) / (1 - 0.5^sqrt(1/HR)). Earlier versions always took the square root, which understated the E-value for a rare outcome (an OR of 2.5 gave 2.54 instead of 4.44).Behaviour change:
sa_evalue()gains arareargument, and a call withtype = "OR"or"HR"now aborts unless it setsrare = TRUEorrare = FALSE. Rarity is the outcome’s prevalence in the study population (under about 15% at the end of follow-up) and is not inferred fromp0, which is used only fortype = "RD". The returned list records the choice as$rare. The function also aborts unlessci_lo <= estimate <= ci_hi.ps_rmst(),ps_support(),sa_overlap()andsa_trim_sweep()now back-transform the score of aps_match()object built withscale = "logit"withplogis(), so its weights, trim rule and overlap diagnostics are on the probability scale. Before, they read the logit as a probability:ps_rmst()clipped it to [0.001, 0.999],ps_support()trimmed logits at 0.1 and 0.9, and the twosa_*()functions stopped with a range error (#80).ps_support()andps_rmst()record$meta$scale, carried from their input, so a chain such asps_rmst(ps_support(matched), ...)still reads the logit correctly.ps_match()warns whenscale = "logit"is given a score column whose every value lies in [0, 1], which is most likely a probability such as the defaultprob_t. Its [0, 1] range error now suggestsscale = "logit"for a logit score.NEWS.mdcorrections: the 0.1.0 entry now lists all sixteen initial exports, and the 0.1.8ps_forest(), 0.1.9group_countsand 0.1.11 seed entries no longer claim more than the code did. Each correction is dated in place (#92).The default covariates of the balance tables in
ps_weight(),ps_match(),ps_logistic(),ps_nominal()andps_ordinal()now leave out the id column, the treatment and score columns, the columns this package appends (prob_t,logit_t,mt_wt,iptw,match,pair_id,quintile,decile,bs,cluster, and theprob_<level>scoresps_nominal()andps_ordinal()generate) and constant columns. A predictor named in the model formula is always kept. Before,idandmatchappeared in theps_weight()table ofsample_ps_data(), andprob_tin aps_match()table matched onlogit_t. Acovariatesvector you supply is used unchanged (#85).ps_weight()gainsid_col = "id", asps_match()has, so the identifier stays out of its balance tables.fit_logistic()checks that ‘MASS’ (ordinal) or ‘nnet’ (nominal) is installed before it fits, and the tests that use either package skip when it is absent (#89).The test suite and the
ps_forest()example cap ‘randomForestSRC’ at two cores (#89).ps_rmst()on aps_match()object now analyses the matched pairs only, sonis twice the number of matched pairs, and its bootstrap resamples whole pairs bypair_idinstead of rows. A matched input runs"unweighted"by default and refuses score weighting, which would adjust twice. Earlier releases used every row of the cohort and ignored the pairing.$meta$resamplingrecords"pair"or"row".ps_rmst()on aps_weight()object now uses its stored weight column for the scheme that matches its estimand ("ate"or"att"), so trimming is kept, where it used to recompute the weights from the score.$meta$stored_weightingnames that scheme.ps_rmst()now stops whentauis past the last observed time in either arm of an analysed subset, counting only rows that enter the weighted curve (time and event present, weight above zero), where it used to carry the last survival value forward. The help page now also says that failed bootstrap draws are counted inn_failedand thatrefit = FALSEintervals ignore the uncertainty in estimating the score.ps_weight()now stabilises weights by the marginal treatment probability forestimand = "ATE"only. For"ATT"and"ATC",stabilisehas no effect, so treated weights under the ATT and control weights under the ATC are exactly 1, as documented; before, the defaultstabilise = TRUEscaled them by the arm’s marginal probability.$meta$stabilisedrecords whether stabilisation was applied, andsa_trim_sweep()follows the same rule. ATE weights are unchanged (#88).ps_match()accepts only the number 1 forratio.ratio = 1.5andratio = "1"were silently accepted and now stop with an error naming the value (#88).ps_support()stops when aflagselement is named"trim"or"common", which the built-in rule silently overwrote, and whentrimis not two increasing probabilities in [0, 1] (#88).An
NAincaliperorcaliper_sd(ps_match()) ortrim(ps_weight(),ps_support()) now stops with an error naming the argument instead of “missing value where TRUE/FALSE needed” (#88).Rows with a missing treatment or a missing score are now excluded and counted rather than crashing or misleading (#84).
ps_match()leaves them out of the treated and control pools,ps_weight()gives them a missing weight and leaves them out of the stabilising proportion and the balance tables, andps_support()computes the common-support interval on the remaining rows and gives them missing flags. Each states the count in one warning and records it in$meta$n_excluded.ps_support()also works with a single rule (trim = NULLorcommon = FALSE).ps_logistic()counts a patient with a missing treatment out ofgroup_counts, the strata and the balance tables, keeps their predicted score, says so in one accurate warning, and records the count in$meta$n_missing_treatment.fit_statusnow reports the rows a model dropped for every family.ps_nominal()andfit_logistic(family = "nominal")reported none, becausennethas nonobs()method.bs_continuous()andbs_count()give a missing balancing score missing strata instead of ranking it into the top stratum, and needid_colonly whenimputation_colis set.-
Breaking change:
sa_rosenbaum(),sa_evalue(),sa_overlap()andsa_trim_sweep()now returnps_dataobjects of classessa_rosenbaum,sa_evalue,sa_overlapandsa_trim_sweep, with the four slots$data,$meta,$tablesand$models, aprint()method each, and the sharedsummary(). Code that read the old lists reaches the same values here:-
sa_rosenbaum():res$boundsisres$tables$bounds;res$sensitivity_valueandres$n_pairsareres$meta$sensitivity_valueandres$meta$n_pairs. The pairs used are in the newres$tables$pairs. -
sa_evalue():evalue_estimate,evalue_ci,rr_estimate,typeandraremove underres$meta, andres$tables$evalueholds both E-values as a table.res$datais an empty data frame. -
sa_overlap():summary,outside_overlapandpositivity_flagsmove underres$tables, andoverlap_regionunderres$meta. -
sa_trim_sweep(): the data frame it returned isres$tables$sweep.
-
Breaking change: the four
sa_*()functions follow one rule for arguments aps_datainput already records. A supplied argument that disagrees with the object’s$metanow aborts, naming both values, where it used to be silently overridden:sa_rosenbaum(m, "ef", treated_level = 0),sa_trim_sweep(w_ate, estimand = "ATT")andsa_overlap(m, score_col = "other")all abort. An argument not supplied takes the metadata, then its default.sa_rosenbaum()now also readspair_id_colfrom the metadata.sa_rosenbaum()corrects its variance for ties, using the sum of squared average ranks in place ofn(n+1)(2n+1)/6. Without tied absolute differences the bounds are unchanged; with ties they change slightly. At Gamma = 1 the bound now equals the one-sidedwilcox.test()p-value withexact = FALSE.sa_rosenbaum()gainsalternative = c("auto", "greater", "less"). The default"auto"keeps the earlier results, picking the direction from the sign of the median difference; the documentation now says so, and that Rosenbaum’s method assumes the direction is fixed in advance.sa_overlap()excludes patients with a missing treatment as well as those with a missing score, andsa_trim_sweep()excludes patients with a missing score, with a warning giving the count, so the effective sample sizes andpct_trimmedare computed on the analysed rows (part of #84).Tests now assert what their names claim: the caliper,
trim = NULL, estimand and stabilised-weight tests are pinned to hand-computed values, the imputation test checksprob_tagainst the mean of the per-imputation fits, the AUC test useswilcox.test()as an independent oracle, and thesa_trim_sweep()tests reach the estimand and machine-epsilon paths they name. No package code changed (#93).The help pages now list everything each function returns (#90).
fit_logistic(),validate_logistic(),ps_logistic(),ps_ordinal(),ps_nominal()andbs_count()name every$datacolumn,$metafield,$tablesentry and their$models;ps_match()andps_weight()addtreated_level, andps_match()says thatn_unmatchedcounts unmatched treated patients only.fit_logistic()documents the choices forfamilyand the role ofid_col.ps_logistic(),ps_ordinal(),ps_nominal()andfit_logistic()describe their handling of multiple imputation correctly: per-patient probabilities are averaged across imputations on the probability scale, the “across” approach of Leyrat et al. (2019), and not Rubin’s rules, which pool only the coefficients.$dataand the balance table use the lowest-numbered imputation’s covariates.ps_logistic()gives the “within” recipe that Leyrat et al. recommend.The help pages name the estimand:
ps_match()the ATT, narrowed by a caliper to the treated patients who could be matched;ps_weight()the ATE, ATT or ATC;ps_mw_var()with the matching weight the ATM; andps_support()says that trimming changes it.Corrections:
ps_logistic()’s matching weightmt_wtfeeds neitherps_match()norps_weight();ps_stddiff()no longer callsps_weight()’s weights matching weights;ps_forest()lists its own$meta;ps_mw_var()cites Li and Greene (2013) with its DOI;sample_ps_data_count(n_imputations > 1)says that it draws independent patients per imputation; and a stray backtick no longer breaks thetreatment_colentry ofsa_overlap()andsa_trim_sweep().The vignette’s
hvtiPlotRexamples now run.hv_mirror_hist()takes column names as strings (score_col,group_col,match_col,weight_col) and returns a data object forplot().hv_balance()does not read$tablesdirectly: it needs one long data frame withvariable,groupandstd_diffin percent, and the vignette now shows the reshape. The package help and the README no longer say the tables feed it as they are. ‘hvtiPlotR’ is now inSuggests(#91).The vignette now covers
ps_forest(),ps_stddiff(),ps_stddiff_perm(),ps_mw_var(),ps_rmst()on matched pairs,ps_support(), matching on the logit withcaliper_sd = 0.2, and therareargumentsa_evalue()needs for odds and hazard ratios. Everyps_match()call in it gives a seed. It no longer says all foursa_*()functions measure unmeasured confounding:sa_overlap()andsa_trim_sweep()check positivity and overlap.The README quick start runs: it weighted a data frame whose score column it had just removed. Its status block now says which parts SAS oracles check (since 0.1.10), and its function tables list all 25 exports.
The package help page lists all 25 exports, says which return
ps_dataobjects (all but the four data generators andis_ps_data()), and points to the SAS programs shipped insystem.file("sas-templates", package = "hvtiRpropensity").
hvtiRpropensity 0.1.11
- A seed now makes every random function reproducible whatever the session drew before it.
ps_forest(),ps_match(),ps_rmst(),ps_stddiff_perm()andps_mw_var()set the stream toabs(seed)immediately before their draws and restore the caller’s stream afterwards.ps_forest()now does this as well as passingseed = -abs(seed)torandomForestSRC::rfsrc(); before, it set no R seed. The default staysseed = NULL, which draws from the caller’s stream as before. A positive seed gives the same result as in 0.1.10, except inps_forest(), whose 0.1.10 result depended on the session’s earlier draws; a negative one now gives the same result as its absolute value. (Corrected 2026-10-13: the original entry did not exemptps_forest().) - Each of those five functions records the seed it used in
$meta$seed,NAwhen none was given.ps_stddiff_perm()andps_mw_var()used to storeNULLthere. A seed that is not one whole number is an error. -
ps_forest()documents the thread trade-off: if two runs with the same seed disagree, setoptions(rf.cores = 1L). - A new test scans every function in the package and fails on a random draw that no seeding call precedes.
-
sample_ps_data(),sample_ps_data_ordinal(),sample_ps_data_nominal()andsample_ps_data_count()no longer warn when called withseed = NULL.NULLnow draws from the caller’s random-number stream, asps_match()andps_rmst()do, soset.seed()before the call reproduces the data. Before, the generators reseeded from the clock and warned that.Random.seedwasNULL. - The generators now check
seedand use its absolute value, as the five functions above do: a negative seed gives the same data as its absolute value, and a seed that is not one whole number is an error. - Now requires R 4.4.0 or newer, up from 4.1.0, to match the rest of the HVTI family.
hvtiR::install()installs the members together, and several already required 4.4.0, so on an older R the install failed whatever this package declared.
hvtiRpropensity 0.1.10
- New tests compare
ps_stddiff(),ps_stddiff_perm()andps_mw_var()with the CCF macros%stddiff,%stddiffciand%mw_var, run in SAS on a synthetic dataset (tests/testthat/fixtures/stddiff-sas/). All three agree with the macros: standardized differences to within 4e-7, the%mw_varestimate to 3e-10 (#34). - New
treated_levelargument onps_match(),ps_weight(),ps_stddiff(),ps_stddiff_perm(),ps_mw_var(),sa_overlap(),sa_trim_sweep()andsa_rosenbaum(). It names the treated value, so the treatment column may hold any two values (for example"surgical"and"transcatheter"), asps_logistic()already allowed. Results equal those from the same data coded 0/1, the group tables show the study’s own level names, and$datakeeps the column as it was given.ps_forest()accepts such a column through its existingtreated_level. The default,NULL, keeps the 0/1 or logical requirement, so existing calls are unchanged (#60). -
sa_overlap(),sa_trim_sweep()andsa_rosenbaum()read the treated level from a scored or matched object, so an object scored withtreated_level = 0is no longer analysed with the arms swapped. -
sa_trim_sweep()andsa_rosenbaum()now check the treatment column. A column that is not 0/1 or logical used to be coerced with warnings and return a result; it is now an error unlesstreated_levelis given. -
sa_trim_sweep()excludes patients whose treatment is missing, with a warning. It used to keep them, which made every effective sample sizeNA. - The group tables of
ps_match(),ps_weight(),ps_forest()andsa_overlap()no longer label the groupscontrolandtreated.groupnow holds the value intreatment_col(0/1, orFALSE/TRUE), and a new logicaltreatedcolumn says which group is treated, as inps_logistic(). Code that matchedgroup == "treated"should use thetreatedcolumn (#51). -
ps_ordinal()andps_nominal()now return a balance table,$tables$smd, asps_logistic()does. It has one row per covariate and pair of treatment levels (variable,level,versus,smd), each computed on the patients in those two levels. A newsmd_pairsargument chooses the pairs:"reference"(default) compares each level with the reference,"adjacent"(ordinal only) compares each level with the one below it, and"all"compares every pair. The choices can be combined (#48). -
ps_rmst()now defaults toseed = NULL, asps_match(),ps_stddiff_perm()andps_mw_var()already did. The default wasseed = 1024L, which gave the same bootstrap draws on every call. A call that relied on the default now gives an interval that varies from run to run; passseedfor a reproducible one. Point estimates are unchanged. -
sample_ps_data_count(seed = NULL)no longer errors and returns a fresh dataset, as the other three generators did. The generators keep their fixed default,seed = 42L, so that examples share one demo dataset (#53). -
fit_logistic()andvalidate_logistic()have examples. - The examples for
ps_ordinal(),ps_nominal()andps_rmst()run only when the suggested package they need (‘MASS’, ‘nnet’, ‘survival’) is installed. - The package help no longer says
sa_rosenbaum()requires ‘rbounds’. It never did: the bounds are computed in base R. -
DESCRIPTIONspells out CORR and SMD, quotes ‘hvtiPlotR’ and cites Rosenbaum and Rubin (1983). Help pages use ASCII punctuation in place of em dashes and arrows (#53).
hvtiRpropensity 0.1.9
-
ps_match(),ps_rmst()and thesample_ps_data*()generators restore the caller’s random number stream when they return. Each calledset.seed()and left the stream reseeded, so everything the caller drew afterwards depended on the seed passed here. They now usewithr::local_seed(), asps_stddiff_perm()andps_mw_var()already did. Results for a given seed are unchanged. -
bs_count()and other count-family models now name the exponentiated coefficient columnrate_ratio. It was labelledodds_ratio, but the exponent of a log-link Poisson or negative-binomial coefficient is a rate ratio. Binary, ordinal and nominal models keepodds_ratio(#46). -
ps_logistic()’s$tables$group_countsnames each row by the declared treatment level instead ofcontrol/treated, and adds a logicaltreatedcolumn. (Corrected 2026-10-13: the original entry said this made it read the same asps_ordinal()andps_nominal(), whosegroup_countshavegroupandnonly.) Withouttreatment_levelsthe groups are0/1(orFALSE/TRUE) (#47).
hvtiRpropensity 0.1.8
- New
ps_forest(): out-of-bag random-forest propensity score in aps_dataobject (needs the suggestedrandomForestSRC). (Corrected 2026-10-13: the original entry said it had the same contract asps_logistic(), but itsmt_wtheld overlap weights rather than matching weights, and its$metaand$tableslack the model-bundle fields.) - New
ps_support(): flags weak-support patients under common-support, score-trimming and user-supplied rules (e.g. isolation-forest tails) and tabulates pairwise agreement (kappa, Jaccard). - New
ps_rmst(): observed-outcome restricted mean survival contrast from weighted Kaplan-Meier curves with a stratified bootstrap interval; its$tables$estimates/$tables$curvesfeedhvtiPlotRRMST plots.
hvtiRpropensity 0.1.7
-
ps_ordinal()now creates its documented rank-based quintile and decile columns from the averaged probability of the highest ordered treatment level, including for stacked imputations. Existing columns with either name are rejected instead of overwritten. -
bs_count()bundles now record their bundle version, count-model family and R/package versions, matching the saved-model metadata contract introduced in 0.1.6.
hvtiRpropensity 0.1.6
- Adds
fit_logistic()for binary, proportional-odds ordinal, and nominal generalized-logit model bundles, including explicit outcome-level contracts, retained per-imputation fits, averaged patient predictions, and Rubin-pooled coefficients and covariance. - Adds
validate_logistic()for calibration, observed-versus-expected events, AUC, and Brier validation of a saved version-1 binary bundle without refitting or modifying it. - Existing binary, ordinal, and nominal propensity functions and count balancing scores retain their scored columns and diagnostics while gaining fitted models, inference, covariance, and fit-status tables. Negative-binomial bundles also retain theta for every imputation.
- Stacked-imputation workflows now require the same patient keys in every imputation. They error on missing patients instead of silently averaging a patient’s score over fewer imputations.
hvtiRpropensity 0.1.5
ps_mw_var()estimates a matching-weight treatment effect with a bootstrap variance, ported from the CCF%mw_varSAS macro (Rajeswaran 2014, after Li and Greene 2013). For each outcome it reports thePROC MEANSweighted mean, SD and sum of weights by group, the difference, and fromn_repreplicates resampled within each group the bootstrap SD,PCTLDEF=1percentiles (quantile(type = 4)), z and p. As in the macro the weights are held fixed across replicates, so the SD ignores the uncertainty in estimating them; the documentation says so. The macro’s output labelled the sum of weightsN, reported here assumwgt_0andsumwgt_1.withrmoves from Suggests to Imports.ps_stddiff_perm()andps_mw_var()take aseedand restore the caller’s random number stream afterwards, throughwithr::local_seed(). Restoring the stream by hand means writing.Random.seedin the global environment, whichR CMD checkreports as a NOTE.ps_stddiff_perm()gives each standardized difference a permutation reference, ported from the CCF%stddiffciSAS macro (Artis 2020). It reports the observedps_stddiff()value beside the 2.5, 16, 50, 84 and 97.5 percentiles of the same statistic overn_permshuffles of the group labels, using SASPCTLDEF=5(quantile(type = 2)), the macro’sPROC UNIVARIATEdefault. The percentiles describe what label shuffling alone produces, not a confidence interval. Weights that depend on the group are recomputed for every permutation by areweightfunction, which is required withweight_col; the macro instead expected those permuted weights to exist as columns already. Aseedmakes the result reproducible and leaves the caller’s random number stream as it was.ps_stddiff()computes standardized differences for every variable type, ported from the CCF%stddiffSAS macro (Artis 2019): Gaussian, non-Gaussian or ordinal by pooled ranks, binary, and categorical by Yang and Dalton’s Mahalanobis form, each optionally weighted. It returns aps_stddiffobject whose$tables$stddiffholds one row per variable. The denominator averages the two group variances as the macro does. A categorical variable whose groups share no levels getsNAwith a warning. Design: hvtiRtemplatesdev/specs/2026-09-16-standardized-difference-design.md; tracked in #34.The SMD tables from
ps_match(),ps_logistic()andps_weight()now come fromps_stddiff(), and their numbers change. Every covariate is treated as Gaussian, as before, but the denominator is now the macro’ssqrt((var1 + var0) / 2). The unweighted tables (smd_before,smd_after,smd,smd_unweighted) used to pool the SD by sample size, which differs whenever the groups are unequal, sosmd_beforeis the table most likely to move; with equal groups it does not. The weighted table (smd_weighted) used to divide its variances bysum(w)and now divides byn - 1, asPROC MEANSdoes. The tables keep theirvariableandsmdcolumns and 4-place rounding. A covariate named incovariatesmust now be numeric; a character column used to yieldNAwith a warning and now stops.ps_weight()stops with a clear error when a propensity score of exactly 0 or 1 gives an infinite weight, naming the count and pointing totrim. It used to fail later with “missing value where TRUE/FALSE needed” from inside the SMD calculation. Withtrimset, the infinite weight is winsorised as before and the call succeeds.
hvtiRpropensity 0.1.4
- Fixes an empty changelog on the pkgdown site.
NEWS.mdopened with a bare# hvtiRpropensitytitle line, so level one was the title level and every version heading sat at level two. pkgdown reads the top heading level in the file as the version level, found no versions there, and warned “no version headings found” on every build. That title line is removed and the four version headings are promoted to level one, matching the rest of the family. A level-one heading that names no version, such as the(unreleased)one above, is skipped by pkgdown rather than counted, so it does not reintroduce the problem.utils::news()was unaffected throughout and still reports the same four versions.
hvtiRpropensity 0.1.3
- Removed the explicit
Maintainer:field fromDESCRIPTIONand moved the maintainer address tojohn.ehrlinger@gmail.cominAuthors@R, matching the rest of thehvtiR*family. The two fields had disagreed:Authors@Rnamed theehrlinj@ccf.orgaddress whileMaintainer:named the gmail one, whichR CMD check --as-cranreported as a note.Maintainer:is now derived from thecrerole at build time, so the two cannot drift apart again.
hvtiRpropensity 0.1.2
- Qualified the
rnorm()calls in thesample_ps_data*()generators asstats::rnorm(), matching every other statistics call in the package, and declaredstatsunderImports. This clears theR CMD checknote about an undefined global function. Generated data is unchanged: the seeds and the RNG draw order are identical.
hvtiRpropensity 0.1.1
- Renamed from
hvtiPropensityScoresinto thehvtiR*package family (hvtiRtemplates,hvtiRutilities,hvtiRlifetables,hvtiRtables). No user-facing function names or behaviour changed.
hvtiRpropensity 0.1.0
- Initial development scaffold.
- (Added 2026-10-13: the original 0.1.0 entry listed four exports. These were exported too:
ps_logistic(),ps_ordinal(),ps_nominal(),bs_continuous(),bs_count(),sa_rosenbaum(),sa_evalue(),sa_overlap(),sa_trim_sweep(),sample_ps_data_ordinal(),sample_ps_data_nominal()andsample_ps_data_count().) - Added
sample_ps_data(): reproducible synthetic cardiac-surgery dataset. - Added
ps_match(): greedy nearest-neighbour 1:1 propensity score matching without replacement, with optional caliper. - Added
ps_weight(): IPTW weighting supporting ATE, ATT, and ATC estimands, with optional stabilisation and weight winsorisation. - Added
is_ps_data()predicate andprint/summaryS3 methods for the commonps_database class.