wt_joint() builds the weight for a joint intervention on two treatments by
multiplying the two component weight vectors. A joint exposure needs a weight
for the pair, and that weight factorizes sequentially: the density of the
first treatment given the covariates, times the density of the second given
the first treatment and the covariates. wt_joint() is the product, plus the
checks that make the product a joint weight rather than an arbitrary one.
is_joint_wt() reports whether a set of weights was built this way, and
joint_wt_meta() reads back what each component was.
Usage
wt_joint(w_a, w_e, exposure_type = c("binary", "binary"))
is_joint_wt(x)
joint_wt_meta(x)Arguments
- w_a, w_e
The two component weight vectors, as
psw()objects. Both must target theate, both must be the same length, and a component weighting a continuous exposure must be stabilized. The order is the factorization's order:w_aweights the first treatment andw_eweights the second, whose model conditions on the first.- exposure_type
A character vector of length two naming each component's exposure type, one of
"binary","categorical", or"continuous", in the order the components were given. Defaults to two binary components. The types are supplied rather than read off the weights, because apsw()records its estimand and its stabilization but not the kind of exposure it weights, and the stabilization requirement needs to tell a continuous component from a binary one.- x
An object to test, or the weights to read the record from.
Value
wt_joint() returns a psw() vector of the elementwise product, carrying a
joint_wt_meta record.
is_joint_wt() returns a single logical.
joint_wt_meta() returns the record as a list with two elements, or NULL
for weights that are not a product:
exposure_typeThe two components' exposure types, in order.
stabilizedWhether each component was stabilized, in order.
The record names the components rather than the observations, so it survives
subsetting and every other operation that keeps the vector a psw().
The factorization
The weight for a joint intervention on A and E is
\(1 / [f(A | L) f(E | A, L)]\). The second factor conditions on the first
treatment. The product of two marginal weights,
\(1 / [f(A | L) f(E | L)]\), is a different quantity, and it is not the
joint weight for any data in which E depends on A.
Nothing downstream can tell the two apart. Both are ordinary vectors of
positive numbers, both fit an outcome model without complaint, and both
return estimates with standard errors. That is why joint_wt_models()
refuses a second model that does not condition on the first treatment, and
why the dependence should be modeled flexibly rather than as a single
additive term: an additive term satisfies the factorization but may model it
badly, and a badly modeled dependence biases the joint weight in a way that
is equally invisible.
Stabilization
A continuous component must be stabilized. The unstabilized density ratio
\(1 / f(A | L)\) has a heavy right tail on its own, and multiplying it by a
second weight inherits that tail, leaving the product with no usable
variance. Build a continuous component with stabilize = TRUE. A binary or
categorical component needs no stabilization and is accepted either way.
What the product records
The result is a psw() with estimand "ate", since the product of two ate
weights targets the joint ate. It is marked stabilized when either
component is, because a stabilizing numerator on either factor is a
stabilizing numerator on the product; joint_wt_meta() keeps the
per-component truth, so the coarse flag hides nothing.
The product records nothing about trimming, truncation, or calibration of the propensity scores the components were built from. Those records name the observations of one component, and the product is not that component.
See also
joint_wt_models() for the container recording the two treatment
models, and wt_ate() for building the components. These are used by
ipw() methods for joint treatment models.
Examples
set.seed(4)
n <- 200
x1 <- rnorm(n)
a <- rbinom(n, 1, plogis(0.3 * x1))
e <- rbinom(n, 1, plogis(-0.2 + 0.5 * x1 - 0.8 * a))
dat <- data.frame(x1, a, e)
# The second model conditions on the first treatment, and does so flexibly
mod_a <- glm(a ~ x1, data = dat, family = binomial())
mod_e <- glm(e ~ a * x1, data = dat, family = binomial())
w <- wt_joint(wt_ate(mod_a), wt_ate(mod_e))
#> ℹ Using exposure variable "a" from GLM model
#> ℹ Treating `.exposure` as binary
#> ℹ Using exposure variable "e" from GLM model
#> ℹ Treating `.exposure` as binary
head(w)
#> <psw{estimand = ate}[6]>
#> [1] 4.486306 4.196643 4.279964 5.496731 2.693151 2.638138
estimand(w)
#> [1] "ate"
is_joint_wt(w)
#> [1] TRUE
joint_wt_meta(w)
#> $exposure_type
#> [1] "binary" "binary"
#>
#> $stabilized
#> [1] FALSE FALSE
#>
# A continuous component must be stabilized
d <- 0.5 + 0.6 * x1 - 0.7 * a + rnorm(n)
mod_d <- lm(d ~ a * x1, data = dat)
w_d <- wt_ate(
fitted(mod_d),
d,
exposure_type = "continuous",
stabilize = TRUE
)
joint_wt_meta(wt_joint(wt_ate(mod_a), w_d, c("binary", "continuous")))
#> ℹ Using exposure variable "a" from GLM model
#> ℹ Treating `.exposure` as binary
#> $exposure_type
#> [1] "binary" "continuous"
#>
#> $stabilized
#> [1] FALSE TRUE
#>
