---
title: "An Introduction to `Mestim`"
subtitle: "Applied M-estimation and computation of the empirical sandwich variance."
author:
- name: François Grolleau
affiliation: Clincal epidemiology — Hôpital Hôtel-Dieu
date: "December 17, 2022"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Introduction to Mestim}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
This vignette serves as a short introduction to computing the variance-covariance matrix of a multidimensional parameter using M-estimation and the empirical sandwich variance.
## Baby review of M-estimation
Denoting $F$ a probability distribution, a $p$-dimensional M-estimator of $\psi$-type $T$ solves the vector equation
$$\int_\mathcal{Z}\psi\big(z, T(F)\big)dF(z)=\boldsymbol{0}.$$
In practice, this means that the estimator $T$ has an *unbiased estimating function* $\psi(z,\theta)=\{\psi_1(z,\theta), \ldots, \psi_p(z,\theta)\}^T$ with solution $\hat{\theta}~(p\times 1)$ solving in $\theta$ the $(p\times 1)$ set of “stacked” estimating equations given by $$ \sum_{i=1}^{n}\psi(Z_i,\theta)=\boldsymbol{0}.$$
For a $\psi$-type M-estimator $T$ with estimate $\hat{\theta}$, under suitable regularity conditions for $\psi$, the central limit theorem and Slutsky's theorem yield
$$\sqrt{n}\big(\hat{\theta}-T(F)\big)\xrightarrow{\mathcal{D}}\mathcal{N}\big(0, \Sigma)$$
where $$\Sigma=\Bigg[\mathbb{E}\bigg\{\frac{\partial \psi\big(Z,T(F)\big) }{\partial\theta^T}\bigg\}\Bigg]^{-1}\mathbb{E}\Big\{ \psi\big(Z,T(F)\big) \psi^T\big(Z,T(F)\big)\Big\}\Bigg[\mathbb{E}\bigg\{\frac{\partial \psi\big(Z,T(F)\big) }{\partial\theta^T}\bigg\}\Bigg]^{-T}.$$
This implies that the $p$-dimensional M-estimator $\hat{\theta}$ is an asymptotically normal, $\sqrt{n}$-consistent, estimator for $T(F)$.
See [Boos and Stefanski (2013)](http://ndl.ethernet.edu.et/bitstream/123456789/61932/1/265.pdf) for a full introduction to M-estimation.
## What `Mestim` does
Provided with observed data $(Z_i)_{1≤i≤n}$, a $p$-dimensional vector of estimates $\hat{\theta}$ and a $(p\times 1)$ *unbiased estimating function* $\psi$, the `Mestim` package computes the sandwich formula
$$\hat{\Sigma}=\Bigg[n^{-1}\sum_{i=1}^n\bigg\{\frac{\partial \psi\big(Z_i,\hat{\theta}\big) }{\partial\theta^T}\bigg\}\Bigg]^{-1}
n^{-1}\sum_{i=1}^n\Big\{ \psi\big(Z_i,\hat{\theta}\big) \psi^T\big(Z_i,\hat{\theta}\big)\Big\}
\Bigg[n^{-1}\sum_{i=1}^n\bigg\{\frac{\partial \psi\big(Z_i,\hat{\theta}\big) }{\partial\theta^T}\bigg\}\Bigg]^{-T}.$$
The estimated asymptotic variance-covariance matrix of $\hat{\theta}$ is $n^{-1} \hat{\Sigma}$, and so, we have
$$\hat{\theta} \mathrel{\dot\sim} \mathcal{N}\big(0, n^{-1} \hat{\Sigma}).$$
Under the hood, `Mestim` algorithmically computes the Jacobian matrix of $\psi$; derivatives and outer products in $\hat{\Sigma}$ are then computed in parallel. To compute the asymptotic variance-covariance matrix, the analyst thus only need to provide a list detailing the “stacked” estimating functions in $\psi$. Below, we give examples of growing complexity to illustrate how `Mestim` can leverage the flexibility of M-estimation theory to calculate asymptotic standard errors (and confidence intervals) for parameter estimates $\hat{\theta}$.
## Example 1: Prediction task via logistic regression
Let's try to compute the asymptotic standard errors of estimated parameters in a logistic regression model. This simple example serves to get familiar with `Mestim` commands.
Let's generate synthetic data with two predictors and a binary outcome such that $Z=(X_1,X_2,Y)^T$.
*click here to see the data generating process.*
Here we use
$$X_1 \sim \mathcal{N}(0,1)$$
$$X_2 \sim \mathcal{N}(0,3)$$
$$Y|X \sim \mathcal{B}\Big(\text{expit}(\beta_1^{0}X_1+\beta_2^{0}X_2)\Big)$$
with $\beta_1^{0}=4$, $\beta_2^{0}=6$.
NB: We use superscript $^{0}$ to denote true values of the parameters.
```{r, message=FALSE, warning=FALSE}
gen_lr_dat <- function(n, seed=123)
{
set.seed(seed)
X_1 <- rnorm(n, sd = 1); X_2 <- rnorm(n, sd = 3) # generate x_1 and x_2
true_betas <- c(4,5) # generate true parameters
X <- model.matrix(~-1+X_1+X_2) # build the design matrix
Y <- rbinom(n, 1, 1/(1 + exp(-X %*% true_betas)) ) # generate Y from X and true_betas
dat <- data.frame(X_1=X_1, X_2=X_2, Y=Y) # build a simulated dataset
return(dat)
}
```
```{r, message=FALSE, warning=FALSE}
dat <- gen_lr_dat(5000)
head(dat)
```
Let's fit a logistic regression model and put the estimated parameters in a list.
```{r, message=FALSE, warning=FALSE, results='hide'}
mod <- glm(Y~-1 + X_1 + X_2, data=dat, family = "binomial")
thetas_hat <- list(thetas_1=coef(mod)[1], thetas_2=coef(mod)[2])
```
Recall that the estimated parameters $\hat{\theta}=(\hat{\theta}_1, \hat{\theta}_2)^T$ from this logistic regression model jointly solve $$\sum_{i=1}^{n}\bigg(\Big[1+\exp\big\{-{(\theta_1X_{1,i}+\theta_2X_{2,i})\big\}\Big]^{-1}}-Y_i\bigg)X_{1,i}
=0$$
and
$$\sum_{i=1}^{n}\bigg(\Big[1+\exp\big\{-{(\theta_1X_{1,i}+\theta_2X_{2,i})\big\}\Big]^{-1}}-Y_i\bigg)X_{2,i}
=0.$$ Therefore, we can identify
$$\psi_1(Z_i,\theta_1)=\bigg(\Big[1+\exp\big\{-{(\theta_1X_{1,i}+\theta_2X_{2,i})\big\}\Big]^{-1}}-Y_i\bigg)X_{1,i}$$
and
$$\psi_2(Z_i,\theta_1)=\bigg(\Big[1+\exp\big\{-{(\theta_1X_{1,i}+\theta_2X_{2,i})\big\}\Big]^{-1}}-Y_i\bigg)X_{2,i}.$$
With that in mind, let's build a list for the unbiased estimating function $\psi(z,\theta)=\Big(\psi_1(z,\theta_1), \psi_2(z,\theta_2)\Big)^T$.
```{r, message=FALSE, warning=FALSE, results='hide'}
psi_1 <- expression( ((1/(1+exp(-(thetas_1 * X_1 + thetas_2 * X_2)))) - Y) * X_1 )
psi_2 <- expression( ((1/(1+exp(-(thetas_1 * X_1 + thetas_2 * X_2)))) - Y) * X_2 )
psi <- list(psi_1, psi_2)
```
NB: parameters' names (here `thetas_1` and `thetas_2`) must be consistent with the previous list.
We are finally ready to pass these arguments to the `get_vcov` function form the `Mestim` package.
```{r, message=FALSE, warning=FALSE, results='hide'}
library(Mestim)
res <- get_vcov(data=dat, thetas=thetas_hat, M=psi)
```
You can obtain the variance-covariance matrix from a `get_vcov` result as follows
```{r, message=FALSE, warning=FALSE}
res$vcov
```
*click here to verify that the variance-covariance matrix from `get_vcov` is similar to that of `glm`.*
```{r, message=FALSE, warning=FALSE}
vcov(mod)
```
This is indeed very close the results in`res$vcov`.
Asymptotic standard errors are square root of the diagonal elements from the estimated variance-covariance matrix. These are stored in the `se` attribute.
```{r, message=FALSE, warning=FALSE}
res$se
```
## Example 2: Average treatment effect via outcome regression
Let's generate synthetic observational data with treatment allocation $A$, continuous outcome $Y$ and a single confounder $X$ such that $Z=(X,A,Y)^T$.
*click here to see the data generating process.*
Here we use
$$X \sim \mathcal{N}(0,1)$$
$$A|X \sim \mathcal{B}\Big(\text{expit}(2X)\Big)$$
$$\epsilon \sim \mathcal{N}(0,20)$$
$$Y|X,\epsilon = \gamma_1^{0} X + \gamma_2^{0} A + \gamma_3^{0} AX + \epsilon$$
with $\gamma_1^{0}=4$, $\gamma_2^{0}=3$, and $\gamma_3^{0}=2$.
NB: We use superscript $^{0}$ to denote true values of the parameters.
```{r, message=FALSE, warning=FALSE}
gen_obs_dat <- function(n, seed=123)
{
set.seed(seed)
X <- rnorm(n) # generate X
A <- rbinom(n, 1, 1/(1 + exp(- 2 * X)) ) # generate treatment allocation A
X_mat <- model.matrix(~ -1 + X + A + A:X) # build the design matrix
true_gammas <- c(4,3,2)
epsi <- rnorm(n,0,20) # generate gaussian noise
Y <- (X_mat %*% true_gammas) + epsi # generate observed outcomes
dat <- data.frame(X=X, A=A, Y=Y) # build a simulated dataset
return(dat)
}
```
```{r, message=FALSE, warning=FALSE}
dat <- gen_obs_dat(5000)
head(dat)
```
In this example, the goal is to calculate standard errors for the outcome regression average causal effect estimator
$$\hat{\delta}=n^{-1}\sum_{i=1}^n\mathbb{\hat{E}}(Y|X=X_i, A=1)-\mathbb{\hat{E}}(Y|X=X_i, A=0).$$
For $\mathbb{E}(Y|X, A)$, let's specify the regression model $m(X, A;\boldsymbol{\gamma})=\gamma_1X + \gamma_2A + \gamma_3AX$ and store the estimated parameters.
```{r, message=FALSE, warning=FALSE, results='hide'}
m <- lm(Y~ -1 + X + A + A:X, data = dat)
gamma_1_hat <- coef(m)[1]
gamma_2_hat <- coef(m)[2]
gamma_3_hat <- coef(m)[3]
```
Recall that the estimated parameters $\hat{\boldsymbol{\gamma}}=(\hat{\gamma}_1,\hat{\gamma}_2,\hat{\gamma}_3)^T$ from this linear regression model jointly solve
$$\sum_{i=1}^n (Y_i-\gamma_1X_i - \gamma_2A_i - \gamma_3A_iX_i)X_i=0,$$
$$\sum_{i=1}^n (Y_i-\gamma_1X_i - \gamma_2A_i - \gamma_3A_iX_i)A_i=0,$$
$$\sum_{i=1}^n (Y_i-\gamma_1X_i - \gamma_2A_i - \gamma_3A_iX_i)A_iX_i=0.$$
Disregarding the summation sign, we can straightforwardly identify the three first elements of the estimating function $\psi(z,\theta)$.
Before building a list detailing the function $\psi$, we need to identify the estimating function of our main parameter of interest $\delta.$
To do so, recall that we can estimate $\delta$ as
$$\hat{\delta}=n^{-1}\sum_{i=1}^nm(X_i,1;\hat{\boldsymbol{\gamma}})-m(X_i,0;\hat{\boldsymbol{\gamma}}) \\
=n^{-1}\sum_{i=1}^n\{\hat{\gamma}_1+ \hat{\gamma}_2 \times 1 +\hat{\gamma}_3 \times 1 \times X_i\} -
\{\hat{\gamma}_1+ \hat{\gamma}_2 \times 0 +\hat{\gamma}_3 \times 0 \times X_i\} \\
=n^{-1}\sum_{i=1}^n \hat{\gamma}_2 +\hat{\gamma}_3 X_i.
$$
Let's first compute it.
```{r, message=FALSE, warning=FALSE, results='hide'}
delta_hat <- mean(gamma_2_hat + gamma_3_hat*dat$X)
```
Note that rearranging the last equality we have
$$\sum_{i=1}^n \hat{\gamma}_2 +\hat{\gamma}_3 X_i - \hat{\delta} = 0$$
which straightforwardly yields the last element of the estimating function $\psi(z,\theta)$.
Disregarding the summation sign yields the last estimating function which we can now “stack” with the previous ones. Let's now build a list detailing the full function $\psi(z,\theta)$.
```{r, message=FALSE, warning=FALSE, results='hide'}
psi_1 <- expression( (Y - gamma_1*X - gamma_2*A - gamma_3*A*X) * X )
psi_2 <- expression( (Y - gamma_1*X - gamma_2*A - gamma_3*A*X) * A )
psi_3 <- expression( (Y - gamma_1*X - gamma_2*A - gamma_3*A*X) * A*X )
psi_4 <- expression( gamma_2 + gamma_3 * X - delta )
psi <- list(psi_1, psi_2, psi_3, psi_4)
```
Let's also store all the estimated parameters $\hat{\theta}=(\hat{\gamma}_1,\hat{\gamma}_2,\hat{\gamma}_3,\hat{\delta})^T$ in a list.
```{r, message=FALSE, warning=FALSE, results='hide'}
thetas_hat <- list(gamma_1=gamma_1_hat,
gamma_2=gamma_2_hat,
gamma_3=gamma_3_hat,
delta=delta_hat)
```
Let's pass the relevant arguments to `get_vcov` and check results for the variance-covariance matrix.
```{r, message=FALSE, warning=FALSE}
res <- get_vcov(data=dat, thetas=thetas_hat, M=psi)
res$vcov
```
Let's see how the results compare with standard errors obtained from the bootstrap.
*click here to see the bootstrap procedure*
```{r, message=FALSE, warning=FALSE}
boot_fun <- function(d, i=1:nrow(d)) {
z<-d[i,]
mod <- lm(Y~ -1 + X + A + A:X, data = z)
gamma_1_hat <- coef(mod)[1]
gamma_2_hat <- coef(mod)[2]
gamma_3_hat <- coef(mod)[3]
delta_hat <- mean(gamma_2_hat*1 + gamma_3_hat*1*z$X)
return( c(gamma_1_hat, gamma_2_hat, gamma_3_hat, delta_hat) )
}
boot_start_time <- Sys.time()
res_boot <- boot::boot(dat, boot_fun, R=999)
boot_end_time <- Sys.time()
paste("Bootstrapping took", round(as.numeric(boot_end_time - boot_start_time), 2), "seconds.")
```
```{r, message=FALSE, warning=FALSE, echo=FALSE}
cov(res_boot$t)
Mestim_start_time <- Sys.time()
res <- get_vcov(data=dat, thetas=thetas_hat, M=psi)
Mestim_end_time <- Sys.time()
#paste("Mestim took", round(as.numeric(Mestim_end_time - Mestim_start_time), 2), "seconds.")
```
This is pretty close to the results in `res$vcov` that we obtained `r round(as.numeric(boot_end_time - boot_start_time)/as.numeric(Mestim_end_time - Mestim_start_time),1)` times faster with `Mestim`.
## Example 3: Value estimation for dynamic treatment regime
Let's generate synthetic observational data for dynamic clinical decisions. We note $S_t$ the observed state at time $t$, $A_t$ the binary action taken at time $t$, and $Y$ the terminal outcome for the sequence where higher values indicate worse disease symptoms. For illustrative purpose, we consider only $T=2$ decision points so that we have data $Z=(S_1,A_1,S_2,A_2,Y)^T.$
*click here to see the data generating process.*
The data are generated via the following hidden Markov process, where we only get to observe $S_t$, which is a noisy version of the underlying state $X_t$:
$$X_1 \sim \mathcal{N}(0,0.1)$$
$$X_{t+1}|X_t, A_t \sim \mathbf{1}_{X_t>0} \mathcal{N}(1.1X_t - 0.5A_t, 0.05) + \mathbf{1}_{X_t<0}X_t$$
$$S_t|X_t \sim \mathcal{N}(X_t,0.1)$$
$$A_1|S_1 \sim \mathcal{B}\Big(\text{expit}(-0.1+\log S_t)\Big)$$
$$A_2|S_2,A_1 \sim \mathcal{B}\Big(\text{expit}(0.1+\log S_t + 3A_1)\Big)$$
$$Y|X_3,A_2,A_1 \sim \text{exp}\Bigg(\mathcal{N}\Big(X_3 + \lambda(A_2+A_1),0.1\Big)\Bigg).$$
We consider that receiving treatment actions has a side effect penalty of $\lambda=0.1$.
```{r, message=FALSE, warning=FALSE}
gen_dtr_dat <- function(n, seed=456)
{
set.seed(seed)
expit <- function(x) 1/(1+exp(-x))
X_1 <- rnorm(n, sd=.1)
S_1 <- exp(rnorm(n, mean = X_1, sd = .1))
A_1 <- rbinom(n, size = 1, prob = expit(-.1+log(S_1)))
X_2 <- (X_1>0) * rnorm(n, mean = 1.1*X_1 - .5 * A_1, sd=.05) + (X_1<0) * X_1
S_2 <- exp(rnorm(n, mean = X_2, sd = .1))
A_2 <- rbinom(n, size = 1, prob = expit(.1+log(S_2)+3*A_1))
X_3 <- (X_2>0) * rnorm(n, mean = 1.1*X_2 - .5 * A_2, sd=.05) + (X_2<0) * X_2
Y <- exp(rnorm(n, mean = X_3 + .1*(A_1 + A_2), sd = .1)) #0.1 penalty for treating
dat <- data.frame(S_1=S_1, A_1=A_1, S_2=S_2, A_2=A_2, Y)
return(dat)
}
```
```{r, message=FALSE, warning=FALSE}
dat <- gen_dtr_dat(5000)
head(dat)
```
Given any treatment action regime $d=\{d_1,\ldots, d_T\}^T$, an estimator for $\mathbb{E}_{Z\sim d}(Y)$ is
\begin{equation}
\hat{\mathcal{V}}_{IPW}(d)=n^{-1}\sum_{i=1}^nY_i\prod_{t=1}^T\frac{\mathbf{1}\big({d_t(H_{t,i})=A_{t,i}}\big)}{\hat{e}_t(H_{t,i})^{d_t(H_{t,i})}\big\{1-\hat{e}_t(H_{t,i})\big\}^{1-d_t(H_{t,i})} } \quad \quad (1)
\end{equation}
where we use the history notation $H_t=(S_{t},A_{t-1},S_{t-1}, \ldots,S_{1})^T$ and write the relevant generalization of the propensity score as $e_t(H_t)=\mathbb{E}(A_{t}|H_t)$ for clarity.
In this example we consider the regime $\tilde{d}(Z)=\{\tilde{d}_1(H_1)=\mathbf{1}_{S_1>1},\tilde{d}_2(H_2)=\mathbf{1}_{S_2>1}\}^T$. The goal is to calculate standard errors for $\hat{\mathcal{V}}_{IPW}(\tilde{d})$.
As $T=2$, we need specify models for $e_1(H_1)$ and $e_2(H_2)$. Let's specify the parametric regression models
$$e_1(H_1;\boldsymbol{\delta})=\text{expit}\big(\delta_1+\delta_2\log S_1)$$
and $$e_2(H_2;\boldsymbol{\phi})=\text{expit}\big(\phi_1+\phi_2\log S_2 +\phi_3A_1).$$ We fit and store the estimated parameters as follows.
```{r, message=FALSE, warning=FALSE, results='hide'}
e_1 <- glm(A_1~I(log(S_1)), data=dat, family = "binomial")
delta_1_hat <- coef(e_1)[1]
delta_2_hat <- coef(e_1)[2]
e_2 <- glm(A_2~I(log(S_2)) + A_1 , data=dat, family = "binomial")
phi_1_hat <- coef(e_2)[1]
phi_2_hat <- coef(e_2)[2]
phi_3_hat <- coef(e_2)[3]
```
As in example 1, recall that for $e_1$ the estimated parameters $\hat{\boldsymbol{\delta}}=(\hat{\delta}_1, \hat{\delta}_2)^T$ jointly solve
$$\sum_{i=1}^{n}\Big[1+\exp\big\{-{(\delta_1+\delta_2 \log S_{1,i})\big\}\Big]^{-1}}-A_{1,i} =0,$$
$$\sum_{i=1}^{n}\bigg(\Big[1+\exp\big\{-{(\delta_1+\delta_2 \log S_{1,i})\big\}\Big]^{-1}}-A_{1,i}\bigg) \log S_{1,i}=0.$$
Similarly for $e_2$ the estimated parameters $\hat{\boldsymbol{\phi}}=(\hat{\phi}_1, \hat{\phi}_2, \hat{\phi}_3)^T$ jointly solve
$$\sum_{i=1}^{n}\Big[1+\exp\big\{-{(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{-1}}-A_{2,i} =0,$$
$$\sum_{i=1}^{n}\bigg(\Big[1+\exp\big\{-{(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{-1}}-A_{2,i}\bigg) \log S_{2,i}=0,$$
$$\sum_{i=1}^{n}\bigg(\Big[1+\exp\big\{-{(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{-1}}-A_{2,i}\bigg) A_{1,i}=0.$$
Disregarding the summation sign, we can straightforwardly identify the five first elements of the estimating function $\psi(z,\theta)$. Let's store them before building our final list for $\psi$.
Note that for programming convenience, we recommend to store all relevant variable transformations as columns in the original dataframe.
```{r, message=FALSE, warning=FALSE, results='hide'}
dat$log_S_1 <- log(dat$S_1) ; dat$log_S_2 <- log(dat$S_2) # For ease of programming
psi_1 <- expression( ((1/(1+exp(-(delta_1 + delta_2 * log_S_1)))) - A_1) * 1 )
psi_2 <- expression( ((1/(1+exp(-(delta_1 + delta_2 * log_S_1)))) - A_1) * log_S_1)
psi_3 <- expression( ((1/(1+exp(-(phi_1 + phi_2 * log_S_2 + phi_3 * A_1)))) - A_2) * 1 )
psi_4 <- expression( ((1/(1+exp(-(phi_1 + phi_2 * log_S_2 + phi_3 * A_1)))) - A_2) * log_S_2)
psi_5 <- expression( ((1/(1+exp(-(phi_1 + phi_2 * log_S_2 + phi_3 * A_1)))) - A_2) * A_1)
```
To obtain the last element of $\psi$, we need to do algebraic manipulations. Denoting $\mathcal{C}_{\tilde{d}, i}=\prod_{t=1}^2\mathbf{1}\big({\tilde{d}_t(H_{t,i})=A_{t,i}}\big)$ for simplicity, after substitution for $\hat{e}_1(H_1;\boldsymbol{\hat{\delta}})$ and $\hat{e}_2(H_2;\boldsymbol{\hat{\phi}})$, equation $(1)$ yields
\begin{equation}
\hat{\mathcal{V}}_{IPW}(\tilde{d})=\\
n^{-1}\sum_{i=1}^nY_i\mathcal{C}_{\tilde{d}, i}
\frac{\Big[1+\exp\big\{-(\delta_1+\delta_2 \log S_{1,i})\big\}\Big]^{\tilde{d}_1(S_{1,i})} \Big[1+\exp\big\{-(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{\tilde{d}_2(S_{2,i})}}
{\bigg(1-\Big[1+\exp\big\{-(\delta_1+\delta_2 \log S_{1,i})\big\}\Big]^{-1}\bigg)^{1-\tilde{d}_1(S_{1,i})}\bigg(1-\Big[1+\exp\big\{-(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{-1}\bigg)^{1-\tilde{d}_2(S_{2,i})}}.
\end{equation}
Rearrangements of the equation above yield
\begin{equation}
\sum_{i=1}^nY_i\mathcal{C}_{\tilde{d}, i}
\frac{\Big[1+\exp\big\{-(\delta_1+\delta_2 \log S_{1,i})\big\}\Big]^{\tilde{d}_1(S_{1,i})} \Big[1+\exp\big\{-(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{\tilde{d}_2(S_{2,i})}}
{\bigg(1-\Big[1+\exp\big\{-(\delta_1+\delta_2 \log S_{1,i})\big\}\Big]^{-1}\bigg)^{1-\tilde{d}_1(S_{1,i})}\bigg(1-\Big[1+\exp\big\{-(\phi_1+\phi_2 \log S_{2,i}+\phi_3 A_{1,i})\big\}\Big]^{-1}\bigg)^{1-\tilde{d}_2(S_{2,i})}} \\
-\hat{\mathcal{V}}_{IPW}(\tilde{d})=0.
\end{equation}
We can now straightforwardly identify and store the last elements of the estimating function $\psi$.
```{r, message=FALSE, warning=FALSE, results='hide'}
# The regime we are interested in
dat$d_1 <- dat$S_1>1
dat$d_2 <- dat$S_2>1
# For ease of programming
dat$C_d <- with(dat, d_1==A_1 & d_2==A_2)
# Store the last element of psi
psi_6 <- expression( Y * C_d *
( (1+exp(-(delta_1 + delta_2 * log_S_1)))^d_1 * # numerator
(1+exp(-(phi_1 + phi_2 * log_S_2 + phi_3 * A_1)))^d_2 )
/ ( (1-(1+exp(-(delta_1 + delta_2 * log_S_1)))^(-1))^(1-d_1) * # denominator
(1-(1+exp(-(phi_1 + phi_2 * log_S_2 + phi_3 * A_1)))^(-1))^(1-d_2) )
- V
)
```
Let's now build a list detailing the full function $\psi(z,\theta)$.
```{r, message=FALSE, warning=FALSE, results='hide'}
psi <- list(psi_1, psi_2, psi_3, psi_4, psi_5, psi_6)
```
Now, let's compute $\hat{\mathcal{V}}_{IPW}(\tilde{d})$ and stack it in a list of all the estimated parameters $\hat{\theta}=\Big(\hat{\delta}_1,\hat{\delta}_2,\hat{\phi}_1,\hat{\phi}_2,\hat{\phi}_3, \hat{\mathcal{V}}_{IPW}(\tilde{d})\Big)^T$.
For simplicity in computing $\hat{\mathcal{V}}_{IPW}(\tilde{d})$, we recommend to use a slightly modified version of `psi_6` as follows.
```{r, message=FALSE, warning=FALSE, results='hide'}
# Just delete "- V" from in the previous expression
# add _hat as appropriate
ipw_estimator <- expression( Y * C_d *
( (1+exp(-(delta_1_hat + delta_2_hat * log_S_1)))^d_1 * # numerator
(1+exp(-(phi_1_hat + phi_2_hat * log_S_2 + phi_3_hat * A_1)))^d_2 )
/ ( (1-(1+exp(-(delta_1_hat + delta_2_hat * log_S_1)))^(-1))^(1-d_1) * # denominator
(1-(1+exp(-(phi_1_hat + phi_2_hat * log_S_2 + phi_3_hat * A_1)))^(-1))^(1-d_2) )
)
# Compute individual contribution and take the average
V_hat <- with(dat, mean(eval(ipw_estimator))) # Other ways to compute this quantity are OK too.
thetas_hat <- list(delta_1=delta_1_hat,
delta_2=delta_2_hat,
phi_1=phi_1_hat,
phi_2=phi_2_hat,
phi_3=phi_3_hat,
V=V_hat)
```
Let's pass the relevant arguments to `get_vcov` and check results for the standard errors.
```{r, message=FALSE, warning=FALSE}
res <- get_vcov(data=dat, thetas=thetas_hat, M=psi)
res$se
```
Let’s see how the results compare with standard errors obtained from the bootstrap.
*click here to see the bootstrap procedure*
```{r, message=FALSE, warning=FALSE}
boot_fun <- function(d, i=1:nrow(d)) {
z<-d[i,]
e_1 <- glm(A_1~I(log(S_1)), data=z, family = "binomial")
e_2 <- glm(A_2~I(log(S_2)) + A_1 , data=z, family = "binomial")
delta_1_hat <- coef(e_1)[1]
delta_2_hat <- coef(e_1)[2]
phi_1_hat <- coef(e_2)[1]
phi_2_hat <- coef(e_2)[2]
phi_3_hat <- coef(e_2)[3]
ipw_estimator <- expression( z$Y * z$C_d *
( (1+exp(-(delta_1_hat + delta_2_hat * z$log_S_1)))^z$d_1 * # numerator
(1+exp(-(phi_1_hat + phi_2_hat * z$log_S_2 + phi_3_hat * z$A_1)))^z$d_2 )
/ ( (1-(1+exp(-(delta_1_hat + delta_2_hat * z$log_S_1)))^(-1))^(1-z$d_1) * # denominator
(1-(1+exp(-(phi_1_hat + phi_2_hat * z$log_S_2 + phi_3_hat * z$A_1)))^(-1))^(1-z$d_2) )
)
V_hat <- mean(eval(ipw_estimator))
return( c(delta_1_hat, delta_2_hat, phi_1_hat, phi_2_hat, phi_3_hat, V_hat) )
}
boot_start_time <- Sys.time()
res_boot <- boot::boot(dat, boot_fun, R=999)
boot_end_time <- Sys.time()
paste("Bootstrapping took", round(as.numeric(boot_end_time - boot_start_time), 2), "seconds.")
```
```{r, message=FALSE, warning=FALSE, echo=FALSE}
res_boot
Mestim_start_time <- Sys.time()
res <- get_vcov(data=dat, thetas=thetas_hat, M=psi)
Mestim_end_time <- Sys.time()
#paste("Mestim took", round(as.numeric(Mestim_end_time - Mestim_start_time), 2), "seconds.")
```
This is pretty close to the results in `res$se` that we obtained `r round(as.numeric(boot_end_time - boot_start_time)/as.numeric(Mestim_end_time - Mestim_start_time),1)` times faster with `Mestim`.
## References
Boos, D. D. and Stefanski, L. (2013). *Essential Statistical Inference*. Springer, New York. [https://doi.org/10.1007/978-1-4614-4818-1](https://doi.org/10.1007/978-1-4614-4818-1).
Tsiatis, A. A., Davidian, M., Holloway, S. T. & Laber, E. B. (2019), *Dynamic Treatment Regimes: Statistical Methods for Precision Medicine*, CRC Press. [https://doi.org/10.1201/9780429192692](https://doi.org/10.1201/9780429192692).