Skip to contents

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 the ate, both must be the same length, and a component weighting a continuous exposure must be stabilized. The order is the factorization's order: w_a weights the first treatment and w_e weights 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 a psw() 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_type

The two components' exposure types, in order.

stabilized

Whether 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
#>