Computes the empirical sandwich variance estimator directly from a set of
estimating equations and a vector of parameter estimates. Unlike
MEstimator(), this function does not solve for the parameters; it assumes
that theta is already the root of the estimating equations and only
assembles the sandwich covariance at that point.
Usage
compute_sandwich(
stacked_equations,
theta,
deriv_method = "capprox",
dx = 1e-09,
allow_pinv = TRUE,
finite_correction = NULL,
summed_equations = NULL,
check_summed_equations = TRUE
)Arguments
- stacked_equations
A function that takes a numeric vector
thetaand returns a p-by-n matrix of estimating equation contributions, where p is the number of parameters and n is the number of observations. A list of one element per equation, each holding that equation's contributions across the observations, is accepted as well.estimate()has no support for that shape, so an estimating function written in it reaches a variance through this entry point and a Jacobian throughcompute_bread(). The list form holds exactly one element per parameter, each of one length, so an over-identified system reaches this function as a matrix rather than as a list.- theta
Numeric vector of parameter estimates. This function assumes
thetais the root ofstacked_equations; it does not solve for it.- deriv_method
Character string selecting the method used to build the bread Jacobian. One of
"capprox"(central difference),"fapprox"(forward difference),"bapprox"(backward difference), or"exact"(forward-mode automatic differentiation). Default"capprox".This default differs from Python delicatessen, whose
compute_sandwichdefaults to"approx", a forward difference computed through SciPy'sapprox_fprime. deli does not replicate the SciPy"approx"path; its"fapprox"is the hand-implemented forward difference. Code ported across the two libraries should setderiv_methodexplicitly rather than rely on the default.- dx
Numeric step size for the finite-difference methods; ignored when
deriv_method = "exact". A small value is recommended, since large steps can give poor approximations. Default1e-9. Must be a single positive finite number, which is checked whicheverderiv_methodis in force. The step is absolute and is floored at the floating-point resolution of each estimate, so a large parameter magnitude cannot silently reduce it to nothing; seeapprox_differentiation().- allow_pinv
Logical. When
TRUE(default), the Moore-Penrose pseudo-inverse is used where the bread matrix cannot be solved, which is the case for the rectangular bread of an over-identified system as well as for a square one whose solve fails; whenFALSE, a bread with no inverse raises an error carrying the classdeli_bread_not_invertible.What the two settings choose between is what to do when the solve fails, and nothing else: a bread the solve inverts is inverted under either. An ill-conditioned bread that base R returns an inverse for is one of those, however far its condition number runs, so
allow_pinv = FALSEis not a conditioning test and a caller who wants one applies it to the returned matrix.- finite_correction
Character string or
NULL. Finite-sample correction applied to the meat matrix.NULL(default) applies no correction;"HC1"rescales the meat by \(n / (n - p)\), where p is the number of parameters.- summed_equations
A function that takes a numeric vector
thetaand returns the length-p vector of row sums ofstacked_equationsattheta, orNULL(default) to derive those sums from the full p-by-n return.The bread is the Jacobian of the summed estimating equations, so each of the one or two perturbed evaluations it makes per parameter is reduced to one value per equation as soon as it is built. Deriving the reduction builds the whole p-by-n matrix 2p times for arithmetic that is linear in it; an estimating function whose sums have a closed form, such as the \(X^T r\) of a regression score, can supply them and hand the bread only what it uses. The meat is unaffected either way, since it needs the per-observation contributions and takes the one full evaluation it always took.
Under
deriv_method = "exact"the reduction is called with a tangent-carryingtheta, so it must be written in operations that carry derivatives:t(X) %*% rdoes, andbase::crossprod()does not. Seeauto_differentiation()for which operations carry a tangent and where.An argument that is neither
NULLnor a function raises an error carrying the classdeli_summed_equations_error, whatevercheck_summed_equationssays. So does a return atthetathat is not numeric, or one holding fewer values than there are parameters, both of whichcompute_bread()reads before it differentiates anything. That the return holds exactly one value per estimating equation is read by the comparison below, since only a call that has evaluated the estimating functions knows how many of them there are.- check_summed_equations
Logical. When
TRUE(default) andsummed_equationswas supplied, its value atthetais compared against the row sums of the one full evaluation the meat is built from, and a disagreement raises an error carrying the classdeli_summed_equations_disagree. The comparison costs one call tosummed_equationsand one reduction of a matrix the call already holds. Set it toFALSEto skip the comparison, which leaves a reduction that sums some other system to return a matrix with the shape of a covariance and no claim to be one.The comparison is made at
theta, which the caller states is the root, so both quantities are at rounding there. That is enough to catch a reduction of some other system and not enough to catch a reduction that is a multiple of the right one, which agrees at a root and yields that multiple of the right bread.
Value
A p-by-p covariance matrix on the asymptotic scale. The bread and meat are each divided by n internally, so the returned matrix is the variance that corresponds to the standard deviation. Dividing it by the number of observations gives the standard-error-scale variance, whose square-rooted diagonal is the vector of standard errors.
A covariance matrix is the only thing this function returns. Where the
bread has no inverse, and so no sandwich can be assembled from it, the call
raises an error carrying the class deli_bread_not_invertible rather than
returning something that has to be tested for. That covers a bread holding
NA, and, under allow_pinv = FALSE, a rectangular bread and one the
solve could not invert. estimate() makes the other choice from the same
matrices: a fit whose
bread holds NA warns and comes back with no variance, since it still
carries the estimates.
Details
The sandwich is built from a bread matrix and a meat matrix. The bread is the
negative Jacobian of the summed estimating equations, and the meat is the
cross-product of the equation evaluations. Each is scaled by 1/n internally,
so the returned matrix is on the asymptotic scale (see Value). The bread
Jacobian is obtained either by finite differences or by forward-mode
automatic differentiation.
References
Boos DD, & Stefanski LA. (2013). M-estimation (estimating equations). In Essential Statistical Inference (pp. 297-337). Springer, New York, NY.
See also
MEstimator() and GMMEstimator(), which solve for theta and
report this variance internally, and delta_method() for the variance of a
transformation of the parameters.
Examples
# A generic data set for estimating a mean and variance
y <- c(1, 2, 4, 1, 2, 3, 1, 5, 2)
# The mean and variance are the roots of ee_mean_variance, so they can be
# computed directly rather than solved for
theta <- c(mean(y), stats::var(y) * (length(y) - 1) / length(y))
# Wrap the built-in estimating equation as a function of theta alone
psi <- function(theta) ee_mean_variance(theta, y = y)
# compute_sandwich() returns the asymptotic-scale variance, so dividing by
# n puts it on the standard-error scale
sandwich <- compute_sandwich(psi, theta = theta) / length(y)
sandwich
#> [,1] [,2]
#> [1,] 0.1975308 0.2057613
#> [2,] 0.2057613 0.4883401
# The diagonal square roots are the standard errors
sqrt(diag(sandwich))
#> [1] 0.4444444 0.6988134
# The bread only ever needs the sums of the estimating equations, so an
# equation whose sums have a closed form can supply them directly
summed <- function(theta) {
c(
sum(y) - length(y) * theta[1],
sum((y - theta[1])^2) - length(y) * theta[2]
)
}
compute_sandwich(psi, theta = theta, summed_equations = summed) / length(y)
#> [,1] [,2]
#> [1,] 0.1975308 0.2057613
#> [2,] 0.2057613 0.4883401