The two readings of a result, defined for the class new_ipw() constructs
and for the pooled results pool_ipw() returns.
as_marginal()returns the result reporting the causal contrast estimates.as_conditional()returns the result presenting the outcome model's coefficient surface.
Value
x presenting the reading asked for. The methods on ipw change the
effects field and nothing else, so every other field comes back as it
went in, the covariance attached to estimates and the set of readings the
result supports included. A result that does not support the reading asked
for raises an error rather than returning one. The methods on
ipw_pooled exchange estimates, pooling, and the recorded mode with the
reading stored under alternate, so the reading that was presented is what
the returned result stores there, and the components shared by both readings
come back as they went in. A pooled result that does not carry the reading
asked for raises an error rather than returning one.
Details
Both surfaces exist on an ipw result that supports both readings, so on one
of those these generics record which one the result presents rather than
computing anything. They set the effects field of the new_ipw() contract
and read the readings field beside it, which makes them the supported way
to move a result between the two readings: a caller writes
as_conditional(res) rather than assigning to the field, and a result that
does not support the reading asked for is refused rather than left recording
one it has no surface for.
The methods on ipw hold for a result that supports both readings, which is
what a result records unless the package that built it said otherwise. Asking
such a result for either reading is always answerable: the methods never
error on one, asking twice says what asking once said, and a result that goes
out to the other reading and back is the result that went in.
A result that supports one reading is refused the other rather than moved to
it. The readings field says which ones a result can present, and a fitting
package records one of them when the other has no meaning for the analysis it
ran. Asking for a reading outside that set raises an error of class
causalgenerics_unsupported_reading_marginal or
causalgenerics_unsupported_reading_conditional, and of the general class
causalgenerics_unsupported_reading, which carries the reading asked for
under effects and the set the result records under readings. The reading
such a result does support is the no-op it is on a result carrying both.
A result built before the fields existed carries six or seven fields rather than eight, since the two were added one at a time. It reads as marginal where it records no mode, which is the mode every method produced then, and as supporting both readings where it records no set, which is what every result was assumed to support when none of them recorded otherwise.
The generics live here for the reason print() does. Two packages each
registering as_conditional.ipw() would collide in the shared S3 method
table, and a caller writing against a result would then get whichever package
was installed last rather than the contract. There is no marginal or
conditional reading of an object that is not an IPW result, so the default
method signals an error rather than inventing one.
The readings a pooled result carries
pool_ipw() pools both readings of one set of results whenever it can
compute both, and stores the one the call did not name whole, under the
alternate component. The methods on ipw_pooled therefore swap which
reading the result presents rather than setting a field: the pooled
estimates, the pooling diagnostics, and the recorded mode move together, and
the components that describe the pooled analyses rather than a reading of
them stay where they are.
Where both readings were pooled, the properties above hold. Asking a pooled
result for the reading it already presents gives that result back, and a
result taken out to the other reading and back is the result that went in.
Totality is what these methods cannot keep. A reading the pooling could not
compute is recorded as unavailable rather than computed, and asking for it
raises an error of class causalgenerics_pool_missing_surface_marginal or
causalgenerics_pool_missing_surface_conditional, and of the general class
causalgenerics_pool_missing_surface. That condition carries the reading
under effects and, under reason, the refusal that reading raised when it
was pooled, which is the same wording the caller would have seen from
pool_ipw() had they asked for it there.
A result pooled before both readings were kept records no alternate at all.
Asking such a result for the other reading is refused with the same two
classes and a reason of NULL, and the message says that pooling the
results again gives a result carrying both.
See also
new_ipw() for the result class and the field these generics set,
and pool_ipw() for the pooled result and the reading it stores beside the
one it presents.
Examples
dat <- data.frame(
x = rep(c(-1.5, -0.5, 0.5, 1.5), each = 5),
z = rep(c(0, 1), 10),
y = rep(c(0, 1, 1, 0, 1), 4)
)
# Written out literally, in the shape the `ipw()` return contract documents.
estimates <- data.frame(
effect = "rd",
estimate = 0.199882,
std.err = 0.092425,
z = 2.1626,
ci.lower = 0.018732,
ci.upper = 0.381032,
conf.level = 0.95,
p.value = 0.030570
)
res <- new_ipw(
estimand = "ate",
wt_mod = glm(z ~ x, family = binomial(), data = dat),
outcome_mod = glm(y ~ z, family = quasibinomial(), data = dat),
estimates = estimates,
se_method = "linearization",
fit = NULL
)
# A method that names no mode reports marginal effects.
res$effects
#> [1] "marginal"
as_conditional(res)$effects
#> [1] "conditional"
# Asking for the reading a result already reports gives the result back, and
# the two readings round-trip.
identical(as_marginal(res), res)
#> [1] TRUE
identical(as_marginal(as_conditional(res)), res)
#> [1] TRUE
# A result that supports one reading is refused the other rather than moved
# to it.
marginal_only <- new_ipw(
estimand = "ate",
wt_mod = glm(z ~ x, family = binomial(), data = dat),
outcome_mod = glm(y ~ z, family = quasibinomial(), data = dat),
estimates = estimates,
se_method = "linearization",
fit = NULL,
readings = "marginal"
)
try(as_conditional(marginal_only))
#> Error in as_conditional.ipw(marginal_only) :
#> This result supports the marginal reading only, so there is no conditional reading of it to report; the package that produced it records the readings it supports when it builds the result.
# Neither reading exists for an object that is not an IPW result.
try(as_marginal(1:3))
#> Error in as_marginal.default(1:3) :
#> No `as_marginal()` method for an object of class <integer>.