---
title: "Getting Started with deli"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting Started with deli}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r knitr-opts, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## Overview

deli is an R package for M-estimation and empirical sandwich variance
estimation. It provides:

1. `m_estimate()`, a one-step fitting function with a formula interface for
   the pre-built estimating equations and a function interface for your own
2. Pre-built estimating equations for common models (means, regression, causal
   inference, survival analysis, and more)
3. Automatic sandwich variance estimation
4. The `MEstimator` and `GMMEstimator` classes, which solve arbitrary
   user-specified estimating equations and hold the results

deli is an R port of the Python
[Delicatessen](https://github.com/pzivich/Delicatessen) library.

## Fitting your first model

The quickest way to fit a model is `m_estimate()`, which takes a formula, a
data frame, and a pre-built estimating equation. It constructs the estimator
and solves it in a single call, much as `lm()` fits a linear model in one
step. Here is a linear regression on the `mtcars` data:

```{r first-fit}
library(deli)

fit <- m_estimate(
  mpg ~ wt + hp,
  data = mtcars,
  .ee = ee_regression,
  model = "linear"
)

coef(fit)
```

The `.ee` argument names the estimating equation, and any further arguments
(such as `model = "linear"`) are passed on to it. The formula and data supply
the response and design matrix, so you do not need to build them by hand.

## Inspecting the fit

Fitted estimators support the standard R accessors. `coef()` returns the point
estimates, `vcov()` returns the sandwich variance-covariance matrix, and
`confint()` returns Wald confidence intervals:

```{r accessors}
vcov(fit)

confint(fit)
```

`summary()` collects the estimates, standard errors, test statistics, and
confidence intervals into a single table:

```{r fit-summary}
summary(fit)
```

deli also provides [broom](https://broom.tidymodels.org/) tidiers, so results
flow into tidyverse pipelines. `tidy()` returns one row per parameter, and
`glance()` returns a one-row model summary:

```{r tidiers}
tidy(fit, conf.int = TRUE)

glance(fit)
```

If you prefer deli's own inference helpers, `confidence_intervals()`,
`z_scores()`, `p_values()`, and `s_values()` each return the corresponding
quantities directly:

```{r inference-helpers}
z_scores(fit)
p_values(fit)
```

## Other regression models

The pre-built regression equation covers several model families. Switch from
linear to logistic regression by changing the `model` argument. Here `vs` is a
binary indicator of engine type:

```{r logistic-fit}
fit_logistic <- m_estimate(
  vs ~ mpg + wt,
  data = mtcars,
  .ee = ee_regression,
  model = "logistic"
)

summary(fit_logistic)
```

The sandwich variance estimator supplies robust standard errors automatically,
without any additional arguments. `ee_regression()` covers linear, logistic,
and Poisson models. See `?ee_glm` for the wider family of generalized linear
models, and the dedicated functions `?ee_robust_regression`,
`?ee_ridge_regression`, `?ee_lasso_regression`, `?ee_elasticnet_regression`,
`?ee_beta_regression`, and `?ee_tobit` for robust, ridge, LASSO, elastic net,
beta, and tobit regression.

## The estimator objects

`m_estimate()` builds and solves an `MEstimator` for you. When a pre-built
estimating equation and the formula interface do not fit your problem, for
example when you supply a custom estimating equation, give `m_estimate()` a
function in place of the formula. The workflow has two steps:

1. Define a `psi` function that returns the estimating equation contributions
2. Pass it to `m_estimate()` with starting values for the parameters

Here is a simple example that estimates a mean:

```{r mean-estimator}
y <- c(1, 2, 3, 1, 4, 5, 3, 2, 6, 7)

# Define the estimating equation
psi <- function(theta) {
  ee_mean(theta, y = y)
}

m <- m_estimate(psi, init = 0)

coef(m)
```

The estimated mean matches `mean(y)`:

```{r mean-check}
mean(y)
```

The result is an `MEstimator` object, the same class the formula interface
returns, so the same accessors work here. The point estimate and variance are
also available on the object as `m@theta` and `m@variance`:

```{r estimator-slots}
m@theta       # Point estimate (the mean)
m@variance    # Sandwich variance estimate
```

## Stacking estimating equations

A key strength of M-estimation is the ability to stack estimating equations.
When you stack equations, the sandwich variance correctly accounts for all
sources of uncertainty. Here is an example that estimates a mean and variance
simultaneously:

```{r mean-variance}
y <- c(1, 2, 3, 1, 4, 5, 3, 2, 6, 7)

psi <- function(theta) {
  ee_mean_variance(theta, y = y)
}

m <- m_estimate(stacked_equations = psi, init = c(0, 0))
m@theta  # c(mean, variance)
```

For more complex stacking with custom equations, see
`vignette("custom-estimating-equations")`.

## Causal inference: inverse probability weighting

deli includes estimating equations for causal inference. Because they combine
several components, they are a natural fit for the function interface. Here is
an example that uses inverse probability weighting (IPW) to estimate an average
treatment effect:

```{r ipw}
set.seed(42)
n <- 500
w <- rbinom(n, 1, 0.5)               # Binary confounder
A <- rbinom(n, 1, plogis(-0.5 + w))  # Treatment depends on w
Y <- 1 + 2 * A + w + rnorm(n)        # Outcome
W <- cbind(1, w)                     # Propensity score design matrix

psi <- function(theta) {
  ee_ipw(theta, y = Y, A = A, W = W)
}

# theta: ACE, E[Y(1)], E[Y(0)], beta0, beta1
m <- m_estimate(stacked_equations = psi, init = c(0, 0, 0, 0, 0))

# ACE (average causal effect) ~ 2
m
```

## Predictions

After fitting a model, use `augment()` for predicted values with confidence
intervals. Give it a data frame of new covariate values as `newdata`, with one
column for each covariate the formula names, and it returns that frame with
`.fitted`, `.se.fit`, `.lower`, and `.upper` beside it:

```{r predictions}
set.seed(42)
n <- 200
x <- rnorm(n)
y <- 1 + 2 * x + rnorm(n)
d <- data.frame(x, y)

m <- m_estimate(y ~ x, data = d, .ee = ee_regression, model = "linear")

# Predict at new values
augment(m, newdata = data.frame(x = seq(-2, 2, by = 1)))
```

Called without `newdata`, `augment()` reports the rows the model was fitted to
and adds a `.resid` column as well.

## Clustered data

For clustered or grouped data, use `aggregate_efuncs()` inside your `psi`
function to get cluster-robust variance estimates:

```{r clustered}
set.seed(42)
n <- 200
n_groups <- 50
group <- rep(1:n_groups, each = n / n_groups)
group_effect <- rnorm(n_groups, sd = 2)
y <- group_effect[group] + rnorm(n)

# Cluster-robust variance
psi <- function(theta) {
  ef <- ee_mean(theta, y = y)
  aggregate_efuncs(ef, group = group)
}

m <- m_estimate(stacked_equations = psi, init = mean(y))

m@theta
m@variance  # Accounts for within-cluster correlation
```

## Exact differentiation

The delta method needs the derivative (Jacobian) of a transform, and the
sandwich variance needs the derivative of the estimating equations. By default
deli computes these with central finite differences (`deriv_method =
"capprox"`), which introduces a small step-size approximation. Passing
`deriv_method = "exact"` switches to forward-mode automatic differentiation,
which evaluates the derivative in closed form. It returns exact derivatives with
no step size to tune. In Python Delicatessen exact differentiation is the
default only for `delta_method`; the estimators and the sandwich variance
default to a forward finite difference.

The `delta_method()` function accepts `deriv_method` directly, and the survival
prediction helpers `survival_predictions()` and `aft_predictions_function()`
forward it to the delta method. The following fits a logistic model and uses
exact differentiation to obtain the variance of the fitted probability at a
covariate pattern:

```{r exact-delta-method}
set.seed(42)
n <- 500
x <- rnorm(n)
pr <- plogis(0.5 + x)
y <- rbinom(n, 1, pr)
d <- data.frame(x, y)

m <- m_estimate(y ~ x, data = d, .ee = ee_regression, model = "logistic")

# Variance of the predicted probability at x = 1, via the delta method.
# The log-odds at the pattern (intercept = 1, x = 1) is theta[1] + theta[2].
transform <- function(theta) inverse_logit(theta[1] + theta[2])
delta_method(m, transform = transform, deriv_method = "exact")
```

That chunk computes the inverse logit twice with two different functions, and
the difference is deliberate. The simulation uses `stats::plogis()`, because it
is ordinary numeric work on plain doubles. The transform uses deli's
`inverse_logit()`, because `plogis()` cannot be differentiated exactly and would
stop the delta method with an error. The two functions return identical numbers;
only one of them survives `deriv_method = "exact"`.

## Next steps
- `vignette("custom-estimating-equations")`: Write your own estimating
  equations and stack them
- `?m_estimate`: the one-step formula and function interface
- `?ee_regression`: linear, logistic, and Poisson regression
- `?ee_glm`: generalized linear models, plus `?ee_ridge_regression`,
  `?ee_lasso_regression`, and the other penalized and robust regression
  functions
- `?ee_gformula`: G-computation / standardization
- `?ee_aipw`: Augmented inverse probability weighting
- `?ee_aft`: Accelerated failure time models
- `?ee_survival_model`: Parametric survival models
