Methods for a pooled inverse probability weighted result
Source:R/ipw-pool-methods.R
ipw-pooled-methods.RdThe methods the class pool_ipw() returns carries.
print()summarizes the pooling and tabulates the pooled effects.coef()returns the pooled estimates.vcov()returns their pooled covariance.confint()returns their confidence limits.nobs()returns the smallest number of observations any pooled analysis was estimated from.as.data.frame()reports the pooled effects as a tidier-shaped table.estimand()returns the estimand the pooled analyses targeted.
Usage
# S3 method for class 'ipw_pooled'
print(x, ...)
# S3 method for class 'ipw_pooled'
coef(object, ..., effects = NULL)
# S3 method for class 'ipw_pooled'
vcov(object, ..., effects = NULL)
# S3 method for class 'ipw_pooled'
confint(object, parm, level = 0.95, ..., effects = NULL)
# S3 method for class 'ipw_pooled'
nobs(object, ...)
# S3 method for class 'ipw_pooled'
as.data.frame(
x,
row.names = NULL,
optional = FALSE,
...,
conf.int = FALSE,
conf.level = NULL,
exponentiate = FALSE,
effects = NULL
)
# S3 method for class 'ipw_pooled'
estimand(x, ...)Arguments
- x
An
ipw_pooledobject.- ...
Further arguments. These methods ignore them.
- object
An
ipw_pooledobject.- effects
The reading to report, either
"marginal"or"conditional".NULL, the default, reports the reading the result records; any other value overrides it for the one call and leaves the result as it is. A reading the pooling could not compute is refused with an error of classcausalgenerics_pool_missing_surface, which is whereas_marginal()andas_conditional()refuse it, and a result pooled before both readings were kept refuses a request for the other reading with the same classes, having nothing recorded to report.- parm
The rows to report an interval for, given either as the effect labels or as their positions. Missing means all of them.
- level
The confidence level. At the level a row stores, that row's own limits are returned; at any other level they are rebuilt from t on the row's degrees of freedom.
- row.names
A character vector of row names for the returned table, or
NULLfor the automatic ones.- optional
Accepted for the
base::as.data.frame()generic. Every column of the table is named, so there is nothing for it to make optional.- conf.int
If
TRUE, appendconf.lowandconf.highcolumns after the rest of the table. Default isFALSE.- conf.level
The confidence level the bounds report.
NULL, the default, uses the level the result records.- exponentiate
If
TRUE, move the estimates that are on a log scale to their natural scale, as the section above describes. Default isFALSE.
Value
print() returns its input invisibly.
coef() returns a named numeric vector of pooled estimates, named by effect
label in the marginal reading and by coefficient name in the conditional one.
vcov() returns the pooled covariance of those estimates, with the same
labels as dimnames on both margins. A result whose analyses did not all carry
a covariance records none, and raises an error of class
causalgenerics_no_vcov_ipw_pooled, and of the general class
causalgenerics_no_vcov, rather than returning one built from the standard
errors, which would report the effects as uncorrelated.
confint() returns a matrix with one row per effect parm selects, in the
order parm gives them, and two columns holding the lower and upper limit,
named by effect label and by the two tail probabilities as percentages. A
character parm naming an effect the result does not report raises an error
of class causalgenerics_invalid_argument.
nobs() returns a single integer.
as.data.frame() returns a plain data frame with the columns term, then
contrast when the result names contrasts, then group when it names
subgroups, then estimate, std.error, statistic, df, and p.value,
with conf.low and conf.high appended when they are asked for. The pooled
covariance travels on it under the ipw_vcov attribute unless the table was
exponentiated.
estimand() returns the estimand the pooled results agreed on, which is the
one the weights their estimates were computed under targeted.
Details
These live here for the reason the ipw methods do. Two packages each
registering print.ipw_pooled() would collide in the shared S3 method table,
and a caller writing against a pooled result would then get whichever package
was installed last rather than the contract.
Everything they read is a field pool_ipw() wrote. They recompute no
pooling, so a result read back out of a saved file answers exactly as one
just built does.
ipw_pooled deliberately does not inherit from ipw, and these methods are
deliberately separate from the ones registered against that class. The two
share column names and differ in what the numbers under them mean: a pooled
result reports several analyses rather than one fit and refers its inference
to t rather than to z, so a pooled result reaching confint.ipw() would come
back with normal limits and nothing would say so.
The reading these methods report
A pooled result carries both readings whenever pool_ipw() could compute
both: the one it presents and, under alternate, the one it does not.
coef(), vcov(), confint(), and as.data.frame() report the reading the
result presents, and take an effects argument naming one for a single call.
Naming a reading reports it and leaves the result as it was; as_marginal()
and as_conditional() are what change which reading a result presents.
The reading is settled before anything else the call asks for. confint()
selects the rows parm names from the reading it was asked for, and
as.data.frame() decides what exponentiate = TRUE moves and what it
relabels from that reading too, so a conditional table is gated on the outcome
model link whichever reading the result itself records. A reading the pooling
could not compute is refused rather than answered with the one the result
holds, and a value naming neither reading raises an error of class
causalgenerics_invalid_argument_effects, and of the general class
causalgenerics_invalid_argument, as it does everywhere else in this package.
print() reports the reading the result presents and takes no such argument.
It names that reading in its heading, and a caller who wants the other one
moves the result to it first. nobs() and estimand() describe the pooled
analyses rather than a reading of them, so they answer the same way in either
reading and take no effects argument.
The degrees of freedom
Every pooled effect carries its own degrees of freedom, in the df column of
the estimates frame. They are the Barnard-Rubin adjusted count, which
depends on how much of that effect's variance came from between the
imputations, so two effects pooled from the same analyses ordinarily differ
in them. confint() and as.data.frame() refer each row to t on its own
count rather than to the normal or to a single count for the table; with few
imputations those counts can be in single figures, where the difference is
large.
There is deliberately no df.residual() method. Residual degrees of freedom
are a property of one fit, and a pooled result has as many as it had
imputations. The per-effect count above is not a residual count and differs
from row to row, so it is reported in the column beside the interval and the
p-value it was used for, and df.residual() on a pooled result finds no
method and no field and gives NULL.
The confidence limits
confint() returns the limits the result stores for any row reported at the
level asked for, and rebuilds the rest. That is the rule confint() on an
unpooled result keeps, row by row: a stored pair need not be the one
recomputing gives, since it may have been rounded on its way into the frame.
as.data.frame() applies the same rule to the frame as a whole, which is
what as.data.frame() on an unpooled result does. The stored pair comes back
only when every row records the level asked for, since a table mixing stored
limits with rebuilt ones would report two intervals under one pair of column
headings. conf.level = NULL, its default, names the level the frame records,
which is the level pool_ipw() built its limits at; a frame whose rows
disagree records none, and the limits are rebuilt at 0.95.
Exponentiating
On a marginal table exponentiate = TRUE means what it means for an unpooled
result. The rows labeled log(rr) and log(or) are matched exactly, their
estimate and their bounds move to the natural scale, and the two terms are
relabeled rr and or. The interval is settled before the scale is, so a
rebuilt bound is a t half width on the log scale added to an estimate on the
log scale and exponentiated afterwards. The standard error, the statistic,
and the p-value describe the log scale and stay there, and the ipw_vcov
attribute is dropped rather than carried, since it would describe neither the
table it sits on nor anything else.
A conditional table has no rows labeled as ratios to pick out, so the link
the outcome models were fitted with settles the question for the whole table:
a logit link puts every coefficient on the log odds scale and a log link
puts every coefficient on the log risk scale, and both are scales an
exponential undoes. Every estimate moves and no term is relabeled, since the
terms are coefficient names and a coefficient does not change its name with
the scale its estimate is reported on. Every other link raises an error of
class causalgenerics_exponentiate_link, and of the classes
causalgenerics_invalid_argument_exponentiate and
causalgenerics_invalid_argument, rather than exponentiating coefficients
that describe nothing once exponentiated.
See also
pool_ipw(), which produces these results, as_marginal() and
as_conditional(), which move one between the readings it carries, and
new_ipw() for the unpooled result they are pooled from.
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)
)
imputed <- lapply(1:3, function(i) {
completed <- dat
completed$y[i] <- 1 - completed$y[i]
completed
})
estimate <- list(c(0.20, 0.55), c(0.30, 0.60), c(0.40, 0.65))
std_err <- list(c(0.10, 0.25), c(0.20, 0.30), c(0.30, 0.35))
fits <- Map(
function(completed, estimate, std.err) {
new_ipw(
estimand = "ate",
wt_mod = glm(z ~ x, family = binomial(), data = completed),
outcome_mod = glm(y ~ z, family = quasibinomial(), data = completed),
estimates = data.frame(
effect = c("rd", "log(rr)"),
estimate = estimate,
std.err = std.err,
z = estimate / std.err,
ci.lower = estimate - 1.96 * std.err,
ci.upper = estimate + 1.96 * std.err,
conf.level = 0.95,
p.value = 2 * pnorm(-abs(estimate / std.err))
),
se_method = "linearization",
fit = NULL
)
},
imputed,
estimate,
std_err
)
pooled <- pool_ipw(fits)
pooled
#> Pooled Inverse Probability Weight Estimator
#> Estimand: ATE
#> Effects: marginal (population-averaged)
#> Imputations: 3
#> Complete-data df: 18
#>
#> Pooled marginal estimates:
#> estimate std.err t df ci.lower ci.upper conf.level p.value
#> rd 0.30000 0.24495 1.2247 9.6489 -0.24848 0.84848 0.95 0.24973
#> log(rr) 0.60000 0.30822 1.9467 15.5637 -0.05489 1.25489 0.95 0.06986 .
#> ---
#> Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
coef(pooled)
#> rd log(rr)
#> 0.3 0.6
# Wider than the normal limits would be, since the degrees of freedom are
# what few imputations leave.
confint(pooled)
#> 2.5 % 97.5 %
#> rd -0.2484843 0.8484843
#> log(rr) -0.0548904 1.2548904
nobs(pooled)
#> [1] 20
# The tidier-shaped table, with the degrees of freedom the statistic beside
# it is referred to.
as.data.frame(pooled)
#> term estimate std.error statistic df p.value
#> 1 rd 0.3 0.2449490 1.224745 9.648903 0.24972994
#> 2 log(rr) 0.6 0.3082207 1.946657 15.563733 0.06985665
# With an interval, and the ratio on its natural scale.
as.data.frame(pooled, conf.int = TRUE, exponentiate = TRUE)
#> term estimate std.error statistic df p.value conf.low conf.high
#> 1 rd 0.300000 0.2449490 1.224745 9.648903 0.24972994 -0.2484843 0.8484843
#> 2 rr 1.822119 0.3082207 1.946657 15.563733 0.06985665 0.9465889 3.5074540
# Residual degrees of freedom belong to one fit, so a pooled result has no
# method for them; the per-effect count is in the table above.
df.residual(pooled)
#> NULL