Boos & Stefanski (2013): M-Estimation (Estimating Equations)
Source:vignettes/articles/boos-stefanski-ch7.qmd
Note
This article is translated from the Boos & Stefanski (2013): M-Estimation (Estimating Equations) example in the documentation of delicatessen, deli’s Python counterpart.
Selected examples from Chapter 7 of Boos & Stefanski (2013), Essential Statistical Inference, demonstrating M-estimation with the empirical sandwich variance estimator.
7.2.2 Mean and Variance
Simultaneous estimation of the mean and variance using stacked estimating equations.
Data
By-hand estimating equations
# By-hand mean and variance EE
psi <- function(theta) {
rbind(
matrix(y_mv - theta[1], nrow = 1), # EE for the mean
matrix((y_mv - theta[1])^2 - theta[2], nrow = 1) # EE for the variance
)
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0))
estr@theta
#> theta_1 theta_2
#> 9.906689 2.405768
confint(estr)
#> lower upper
#> theta_1 9.691728 10.12165
#> theta_2 2.039566 2.77197Built-in ee_mean_variance
psi <- function(theta) {
ee_mean_variance(theta, y = y_mv)
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0))
estr@theta
#> mean variance
#> 9.906689 2.405768
confint(estr)
#> lower upper
#> mean 9.691728 10.12165
#> variance 2.039566 2.771977.2.3 Ratio Estimator
Estimating the ratio \theta = E[Y] / E[X] via the single estimating equation \psi_i(\theta) = Y_i - \theta X_i.
Single-equation version
psi <- function(theta) {
matrix(y_mv - theta[1] * x_mv, nrow = 1)
}
estr <- m_estimate(stacked_equations = psi, init = c(1))
estr@theta
#> theta_1
#> 2.002463
confint(estr)
#> lower upper
#> theta_1 1.929498 2.075427Three-equation version
Stacking mean of Y, mean of X, and the ratio:
psi <- function(theta) {
rbind(
matrix(y_mv - theta[1], nrow = 1), # EE for E[Y]
matrix(x_mv - theta[2], nrow = 1), # EE for E[X]
matrix(rep(theta[1] - theta[3] * theta[2], n), nrow = 1) # ratio constraint
)
}
estr <- m_estimate(stacked_equations = psi, init = c(10, 5, 2))
estr@theta
#> theta_1 theta_2 theta_3
#> 9.906689 4.947253 2.002463
confint(estr)
#> lower upper
#> theta_1 9.691728 10.121650
#> theta_2 4.808608 5.085899
#> theta_3 1.929498 2.0754277.2.4 Delta Method via Stacking
Rather than applying the delta method after estimation, additional rows can be stacked to propagate uncertainty through transformations. Here we add \sqrt{\sigma^2} and \log(\sigma^2) as additional parameters.
psi <- function(theta) {
rbind(
matrix(y_mv - theta[1], nrow = 1), # mean
matrix((y_mv - theta[1])^2 - theta[2], nrow = 1), # variance
matrix(rep(theta[3] - sqrt(theta[2]), n), nrow = 1), # sqrt(var)
matrix(rep(theta[4] - log(theta[2]), n), nrow = 1) # log(var)
)
}
estr <- m_estimate(stacked_equations = psi, init = c(10, 2, 1, 1))
estr@theta
#> theta_1 theta_2 theta_3 theta_4
#> 9.9066890 2.4057679 1.5510538 0.8778691
confint(estr)
#> lower upper
#> theta_1 9.6917279 10.121650
#> theta_2 2.0395657 2.771970
#> theta_3 1.4330043 1.669103
#> theta_4 0.7256507 1.0300887.2.6 Instrumental Variable
Correcting for measurement error using an instrumental variable. Consider the model Y = \alpha + \beta X^* + \epsilon where X^* is measured with error as X = X^* + U. An instrument W satisfies \text{Cov}(W, \epsilon) = 0.
Data
set.seed(12345)
n_iv <- 500
xstar <- rnorm(n_iv, mean = 0, sd = 1) # true X
e <- rnorm(n_iv, mean = 0, sd = 1) # outcome error
u <- rnorm(n_iv, mean = 0, sd = 0.5) # measurement error
w <- xstar + rnorm(n_iv, mean = 0, sd = 0.5) # instrument
x_iv <- xstar + u # mismeasured X
y_iv <- 1 + 0.5 * xstar + e # outcomeIV estimator: two-equation formulation
Using (1, W) as instruments:
psi <- function(theta) {
resid <- y_iv - theta[1] - theta[2] * x_iv
rbind(
matrix(resid, nrow = 1), # instrument: constant
matrix(resid * w, nrow = 1) # instrument: W
)
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0))
estr@theta
#> theta_1 theta_2
#> 1.0219527 0.4812642
confint(estr)
#> lower upper
#> theta_1 0.9313164 1.112589
#> theta_2 0.3815893 0.580939IV estimator: four-equation formulation
Including the mean of X and W to allow further inference:
psi <- function(theta) {
resid <- y_iv - theta[1] - theta[2] * x_iv
rbind(
matrix(resid, nrow = 1), # instrument: constant
matrix(resid * w, nrow = 1), # instrument: W
matrix(x_iv - theta[3], nrow = 1), # mean of X
matrix(w - theta[4], nrow = 1) # mean of W
)
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0, 0, 0))
estr@theta
#> theta_1 theta_2 theta_3 theta_4
#> 1.02195273 0.48126418 0.06070097 0.07382810
confint(estr)
#> lower upper
#> theta_1 0.93131640 1.1125891
#> theta_2 0.38158935 0.5809390
#> theta_3 -0.03928175 0.1606837
#> theta_4 -0.02223276 0.16988907.4.1 Robust Location (Huber)
The Huber loss function down-weights large residuals using a tuning constant k. The estimating equation is \psi_i(\theta) = h_k(Y_i - \theta) where h_k is the Huber function.
Data
By-hand Huber robust mean
k <- 3
psi <- function(theta) {
resid <- y_rob - theta[1]
# Huber loss: clip residuals to [-k, k]
clipped <- pmax(pmin(resid, k), -k)
matrix(clipped, nrow = 1)
}
estr <- m_estimate(stacked_equations = psi, init = 0)
estr@theta
#> theta_1
#> -0.02039958
confint(estr)
#> lower upper
#> theta_1 -0.1410716 0.1002724Built-in ee_mean_robust
psi <- function(theta) {
ee_mean_robust(theta, y = y_rob, k = 3, loss = "huber")
}
estr <- m_estimate(stacked_equations = psi, init = 0)
estr@theta
#> theta_1
#> -0.02039958
confint(estr)
#> lower upper
#> theta_1 -0.1410716 0.10027247.5.1 Linear Regression
Linear regression Y_i = \beta_0 + \beta_1 X_i + \beta_2 Z_i + \epsilon_i with the estimating equation \psi_i(\beta) = (Y_i - X_i^T \beta) X_i.
Data
By-hand linear regression
psi <- function(theta) {
yhat <- X_lm %*% theta # predicted values (n-by-1)
resid <- y_lm - yhat # residuals (n-by-1)
t(X_lm * as.numeric(resid)) # score: p-by-n
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0, 0))
estr@theta
#> theta_1 theta_2 theta_3
#> 0.5342416 1.9374201 -0.9359291
confint(estr)
#> lower upper
#> theta_1 0.4121997 0.6562835
#> theta_2 1.8425613 2.0322790
#> theta_3 -1.1134217 -0.7584366Built-in ee_regression
psi <- function(theta) {
ee_regression(theta, X = X_lm, y = y_lm, model = "linear")
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0, 0))
estr@theta
#> theta_1 theta_2 theta_3
#> 0.5342416 1.9374201 -0.9359291
confint(estr)
#> lower upper
#> theta_1 0.4121997 0.6562835
#> theta_2 1.8425613 2.0322790
#> theta_3 -1.1134217 -0.75843667.5.4 Robust Regression
Huber robust regression with tuning constant k = 1.345. The estimating equation is \psi_i(\beta) = h_k(Y_i - X_i^T \beta) X_i.
By-hand robust regression
k_rr <- 1.345
psi <- function(theta) {
yhat <- X_lm %*% theta
resid <- y_lm - as.numeric(yhat)
# Huber loss applied to residuals
clipped <- pmax(pmin(resid, k_rr), -k_rr)
t(X_lm * clipped)
}
estr <- m_estimate(
stacked_equations = psi,
init = c(0, 0, 0),
solver = "nleqslv"
)
estr@theta
#> theta_1 theta_2 theta_3
#> 0.5245726 1.9337397 -0.9112539
confint(estr)
#> lower upper
#> theta_1 0.402750 0.6463953
#> theta_2 1.834690 2.0327898
#> theta_3 -1.095599 -0.7269085Built-in ee_robust_regression
psi <- function(theta) {
ee_robust_regression(theta, X = X_lm, y = y_lm,
model = "linear", k = 1.345, loss = "huber")
}
estr <- m_estimate(
stacked_equations = psi,
init = c(0, 0, 0),
solver = "nleqslv"
)
estr@theta
#> theta_1 theta_2 theta_3
#> 0.5245726 1.9337397 -0.9112539
confint(estr)
#> lower upper
#> theta_1 0.402750 0.6463953
#> theta_2 1.834690 2.0327898
#> theta_3 -1.095599 -0.72690857.5.5 Generalized Linear Models
Using ee_glm with different distribution/link combinations to estimate common epidemiologic effect measures from binary data.
Data
Logistic regression (odds ratio)
psi <- function(theta) {
ee_glm(theta, X = X_glm, y = y_glm,
distribution = "binomial", link = "logit")
}
estr <- m_estimate(stacked_equations = psi, init = c(0, 0))
estr@theta
#> theta_1 theta_2
#> -0.6230037 1.0244966
confint(estr)
#> lower upper
#> theta_1 -0.8234004 -0.422607
#> theta_2 0.7965750 1.252418Log-binomial regression (risk ratio)
psi <- function(theta) {
ee_glm(theta, X = X_glm, y = y_glm,
distribution = "binomial", link = "log")
}
estr <- m_estimate(stacked_equations = psi, init = c(-1, 0))
estr@theta
#> theta_1 theta_2
#> -1.404725 1.185587
confint(estr)
#> lower upper
#> theta_1 -1.6444513 -1.164999
#> theta_2 0.9745417 1.396632Identity-binomial regression (risk difference)
psi <- function(theta) {
ee_glm(theta, X = X_glm, y = y_glm,
distribution = "binomial", link = "identity")
}
estr <- m_estimate(stacked_equations = psi, init = c(0.3, 0))
estr@theta
#> theta_1 theta_2
#> 0.3681685 0.2098780
confint(estr)
#> lower upper
#> theta_1 0.3333530 0.402984
#> theta_2 0.1828439 0.236912