Changelog
Source:NEWS.md
balancing 0.0.0.9000
Added
balance(), which fits optimization-based balancing weights to a data frame with tidyselect covariate selection.Added six balancing methods, each a method specification passed to
balance(): entropy balancing (bw_entropy()), inverse probability tilting (bw_ipt()), the covariate balancing propensity score (bw_cbps()), energy balancing (bw_energy()), characteristic function distance balancing (bw_cfd()), and stable balancing weights (bw_sbw()). The methods support binary, categorical, and continuous exposures across the ATE, ATT, ATU, and (for the covariate balancing propensity score) ATO estimands.balance()resolves its exposure type through causalgenerics, which owns the detection heuristics, the announcement, and the refusal of a declared type across the r-causal packages. Four things change with the move. Missing values no longer count as a level of their own, so a two-valued exposure that also carries missingness reads as binary rather than continuous;balance()refuses a missing exposure before it reads a type, so no fit reaches the new reading. A factor or character exposure taking a single level reads as binary rather than categorical, and is refused for taking one level either way. The announcement of a detected type loses its trailing period. Anexposure_typethe data cannot carry is refused with thecausalgenerics_forced_exposure_typecondition rather thanbalancing_exposure_type_error, which stays the class of balancing’s own refusal of an exposure type the method cannot fit.Added
balance_terms()to specify the covariate functions a method balances, covering moments, pairwise interactions, quantile indicators, and per-covariate balance tolerances.bw_sbw()supports three weight-dispersion norms:"l2"(the default, sum of squared weights),"l1"(sum of absolute deviations from uniform), and"linf"(the maximum deviation from uniform).Added a summary surface for fitted results:
print()andsummary()report the method, estimand, solver status, constraint count, and largest imbalance;weights()returns thebwweight vector; and the fit carries its balance table and solver dual variables for inspection. Broader balance assessment, including covariates the weights did not target, lives in the halfmoon package.Added an
ipw()method so a balancing fit drives the same effect-estimation workflow as a propensity score model. For the estimating-equation methods with a binary or categorical exposure, standard errors come from a stacked M-estimator that accounts for having estimated the weights, exposed throughestimating_equations(). The outcome model may adjust for covariates alongside the exposure, in which case the marginal means are standardized over the estimand’s target population. A categorical exposure reports each non-reference level against the reference level, and the estimates table names each contrast in acontrastcolumn.ipw()also accepts a continuous exposure from an entropy balancing fit with exact balance, whose estimand is the average treatment effect. It reports the dose response of a weighted marginal structural model. An exposure entering through one design column is the whole of that response, so its coefficient is the response’s slope and the table holds a single row named for the outcome model’s link:slopefor an identity link,log(or)for a logit, andlog(rr)for a log link. The standard error comes from the same stacked M-estimator, the weight parameters above the outcome-model score, and is a large-sample one: at a few hundred observations it runs anticonservative.A continuous marginal structural model may spread the exposure over several design columns, as in
y ~ exposure + I(exposure^2)ory ~ splines::ns(exposure, 3). What it has to keep is variable membership: every term reading the exposure reads the exposure alone, and a term reading a covariate alongside it is refused, since its coefficient is a change in the dose response per unit of that covariate rather than an effect a row could name. Such a model reports one row per exposure coefficient and gains acontrastcolumn naming each row after the coefficient the fit names. The scale word steps back tocoefat an identity link, since a curve has a different slope at every dose; a logit still reportslog(or)and a log linklog(rr), because a coefficient of those models is a log ratio whatever column it multiplies.ipw()takes a.byargument naming a modifier to report the effects within the levels of. A result carrying one reports the whole-sample effects, then those same effects within each subgroup, then each non-reference subgroup against the reference one, and the estimates table gains agroupcolumn naming the subgroup each row was estimated in. A subgroup row carries the collapsible measures alone, since the odds ratio over a sample is not an average of the odds ratios within its subgroups. Every added row is a parameter of the same stacked system, so the subgroups covary and a contrast of two of them is estimated rather than differenced after the fact. An outcome model with no term reading both the exposure and the modifier is reported with a warning rather than refused, and a continuous exposure, which reports coefficients rather than contrasts of standardized means, has no subgroup effect for the argument to name and refuses it.An exposure declared as a crossing of two treatments by causalgenerics’
joint_exposure()is reported in those two treatments rather than cell against cell: the counterfactual mean of each cell, the simple effects of each treatment within a fixed level of the other, and the interaction between the two treatments, reported once because it is symmetric in them. The cell-against-cell rows are replaced rather than supplemented, so their labels appear nowhere a row is named. Every row is a parameter of the same stacked system, which makes each interaction a double difference of cell means by construction. A crossing whose two treatments carry one name is refused, since every row is keyed by the treatment it contrasts and the level the other is held at, and a declared crossing is refused with.byor with an estimand other than the ATE.Missing covariate data is handled by imputing first and analyzing within each completed dataset. Balance, weight, and estimate once per imputation, then pool the results with
pool_ipw(), which balancing re-exports from causalgenerics. The complete-data degrees of freedom come from the outcome models automatically, since a balancing result reports none of its own.An
ipw()result reads through the accessors causalgenerics registers on the class:coef(),vcov(),confint(),nobs(), andweights().vcov()returns the covariance of the reported effects, labeled the way the result labels its rows, so the covariances between effect measures that the estimates table cannot report are available to a caller combining them. The stored outcome model carries the outcome-model block of the same covariance, sovcov()on it accounts for having estimated the weights.ipw()takes aneffectsargument recording the reading the result it builds presents:"marginal", the default, for the population-averaged causal contrasts, or"conditional"for the outcome model’s coefficient surface. The stacked system is solved whichever value is named, so the argument settles which reading the result presents and nothing else. In the conditional reading,coef(),vcov(), andconfint()report the outcome model’s coefficients against the outcome block of the stacked sandwich, so their standard errors account for having estimated the weights rather than treating the weights as fixed.as_marginal()andas_conditional(), which move a result between the two readings afterwards, are re-exported here, so a result is flipped without a second attachment. A printed result names its reading beside the estimand and again over the table it decides.A pooled result moves between the two readings as an unpooled one does.
pool_ipw()pools both readings of a set of results, soas_marginal()andas_conditional()flip the pooled result afterwards, and itscoef(),vcov(),confint(), andas.data.frame()take aneffectsargument for a single call. The conditional reading is there to pool becauseipw()hands every outcome model over already wrapped with its block of the stacked covariance; the pooling itself comes from causalgenerics.The balancing fit an
ipw()result stores carries the leading block of the stacked covariance, the one belonging to the weight parameters alone, sovcov()on that fit reports the covariance of the weights under theirtheta_wnames. A fit that has not been through such a result carries no covariance and raisesbalancing_vcov_error, pointing at theipw()route when its weights solve estimating equations and at the bootstrap workflow of the inference vignette when they do not.Added the
bwweight vector class, a sibling ofpropensity::pswunder the sharedcausal_wtsparent.The numerical solvers are implemented in a Rust core.
Added a
NEWS.mdfile to track changes to the package.