---
title: "Panel data: random intercepts, fixed effects, and the Mundlak device"
author: "Benjamin E. Bagozzi"
output:
  rmarkdown::html_vignette:
    toc: true
vignette: >
  %\VignetteIndexEntry{Panel data: random intercepts, fixed effects, and the Mundlak device}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4,
                      message = FALSE)
```

Most applications of the inflated ordered models are panels -- country-years,
agreement-years, campaign-years -- and every estimator in `iop` offers the same
four ways of handling unit heterogeneity:

| Tool                                 | What it does                                                      | Use when                                               |
|--------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------|
| `cluster = "unit"`                   | cluster-robust standard errors, point estimates unchanged         | always sensible as a baseline                          |
| `re = "unit"`                        | a unit random intercept integrated out by adaptive quadrature     | the unit effect is uncorrelated with the covariates    |
| `mundlak()` + `re =` or `cluster =`  | unit means of the covariates (correlated random effects)          | the unit effect may be correlated with the covariates; short panels |
| `fe = "unit"`                        | a dummy per unit (with `fe_correction = "jackknife"`)             | long panels                                            |

The package's Monte Carlo, summarized at the end, is the reason for the "use
when" column.

## Cluster-robust standard errors

`cluster =` names a column and switches `se` to `"cluster"`; the point
estimates are those of the pooled model.

```{r cluster}
library(iop)
data(bp)
f <- violence ~ loggdppc + parliament + disaster | loggdppc + parliament + disaster
m_pool <- iop(f, data = bp, inflate = "bottom")
m_cl   <- iop(f, data = bp, inflate = "bottom", cluster = "country")
round(cbind(estimate = coef(m_pool), se_iid = sqrt(diag(vcov(m_pool))),
            se_cluster = sqrt(diag(vcov(m_cl)))), 3)
```

A nonparametric bootstrap is the other route: `se = "bootstrap"` refits the
model `nboot` times on resamples of the clusters (when `cluster =` is given),
of the random-intercept units, or of the rows, keeps the replicate estimates
in `$boot`, and `confint(type = "percentile")` then gives percentile
intervals. Dale and Sirchenko (2021) find it better calibrated than the
asymptotic standard errors for the error correlation of the correlated models
in small samples. It costs `nboot` refits (`cores =` parallelizes them), so it
is not run here:

```{r boot-se, eval = FALSE}
m_boot <- iop(f, data = bp, inflate = "bottom", se = "bootstrap", cluster = "country",
              nboot = 200, cores = 4)
confint(m_boot, type = "percentile")
```

## Random intercepts

`re = "unit"` adds a normal random intercept to the outcome equation and
integrates it out by adaptive Gauss--Hermite quadrature (`nAGQ` nodes per
unit; the default 15 is accurate for the panels in this package, and fewer
nodes can leave the fit short of a clean optimum, which the fit reports).

```{r re}
m_re <- oprobit(violence ~ loggdppc + parliament + disaster, data = bp, re = "country")
m_re
```

The same fit from `ordinal::clmm(violence ~ loggdppc + parliament + disaster +
(1 | country), link = "probit", nAGQ = 15)` has log-likelihood -1077.816 and
random-intercept SD 1.306, which is what `iop` reports; the package's tests
check this agreement on simulated panels. `ranef()` returns the empirical-Bayes
unit effects:

```{r ranef}
head(ranef(m_re), 4)
```

Reported probabilities and first differences are *marginal* over the random
intercept (population-averaged); `type = "prob_conditional"` gives the
probabilities for a unit with a zero intercept:

```{r re-predict}
head(cbind(marginal = predict(m_re)[, "civil war"],
           conditional = predict(m_re, type = "prob_conditional")[, "civil war"]), 3)
```

Random intercepts work identically for `iop()` and `iol()`, where
`re_inflation = TRUE` also gives the inflation equation an independent unit
intercept (two-dimensional quadrature). A fit whose random-intercept SD goes
to zero is flagged as a boundary case; see `vignette("model")`.

## Unit fixed effects

`fe = "unit"` adds a dummy per unit to the outcome equation (and, with
`fe_inflation = TRUE`, to the inflation equation). Two things happen
automatically:

* units whose response never leaves an extreme category have no finite fixed
  effect and are dropped with a message;
* when the median unit has fewer than 10 observations the fit warns about the
  incidental-parameters bias of maximum-likelihood dummies (Greene 2004).

```{r fe, message = TRUE}
m_fe <- oprobit(violence ~ loggdppc + disaster, data = bp, fe = "country")
m_fe
```

A covariate that does not vary within units is collinear with the dummies and
is refused with a message naming it -- `parliament`, which changes in only one
country of this panel, is the example here:

```{r fe-tiv, eval = FALSE}
oprobit(violence ~ loggdppc + parliament + disaster, data = bp, fe = "country")
#> Error: Covariate(s) parliament do not vary within the units of 'country' on the
#> estimation rows and are collinear with the unit fixed effects. Drop them from the
#> outcome equation, or keep them with re = "country" or mundlak() instead of fe =.
```

### The split-panel jackknife

`fe_correction = "jackknife"` applies the split-panel jackknife of Dhaene and
Jochmans (2015): the model is refit on the first and second half of every
unit's observations (ordered by `time`), and the common parameters are
bias-corrected as `2 * full - (half1 + half2) / 2`. On a simulated short panel
(T = 8, so the fit issues its short-panel warning) with a covariate correlated
with the unit effect, the correction removes most of the incidental-parameters
bias:

```{r jackknife}
set.seed(42)
G <- 100; Tn <- 8
alpha <- rnorm(G); unit <- rep(1:G, each = Tn)
x1 <- rnorm(G * Tn) + 0.5 * alpha[unit]; x2 <- rnorm(G * Tn)
ystar <- 0.8 * x1 - 0.5 * x2 + alpha[unit] + rnorm(G * Tn)
d <- data.frame(y = findInterval(ystar, c(-1, 0.3, 1.2)), x1, x2, unit, t = rep(1:Tn, G))
m_jk <- oprobit(y ~ x1 + x2, data = d, fe = "unit", fe_correction = "jackknife", time = "t")
rbind(uncorrected = m_jk$coefficients_uncorrected[c("x1", "x2")],
      jackknife   = coef(m_jk)[c("x1", "x2")],
      truth       = c(0.8, -0.5))
```

The reported covariance after the correction is the full-sample one: Dhaene
and Jochmans (2015) show that the split-panel jackknife removes the leading
bias without changing the first-order asymptotic variance, so it is the
asymptotically valid covariance for the corrected estimator (`summary()`
says so); in finite samples the corrected estimator is somewhat noisier, so
treat its intervals as approximate. A computational note: the dummies enter
the parameter vector one per unit, and the exact-Hessian steps cost roughly
the square of the parameter count (about 0.5 s at 20 units, 3 s at 100, 18 s
at 300 for an ordered probit on 4,000 rows, more for an inflated model's
multistart), so for panels with many hundreds of units prefer `re =` or
`mundlak()`. The correction is only as good as the two half-panel fits, which
are stored in `m_jk$jackknife`. When a covariate is identified mainly by a within-unit
trend -- as log GDP per capita is in `bp`, where 99 percent of its
within-country variation is linear in time -- the half-panels barely identify
it, their estimates are far from the full-sample one, and the "correction"
inherits that noise. Inspect the half-panel coefficients before reporting a
jackknifed estimate:

```{r jackknife-halves}
m_jk$jackknife$half_coefficients[, c("x1", "x2")]
```

## The Mundlak device

`mundlak()` augments the data with the unit means of the time-varying numeric
covariates of both equations and returns the augmented formula and data. Any
estimator fit on the result implements the correlated-random-effects
specification: the coefficients on the original covariates are the within-unit
effects, and the coefficients on the unit means capture -- and test -- the
correlation between the covariates and the unit effect. It pairs naturally with
cluster-robust standard errors or a random intercept, and it keeps
time-invariant covariates estimable.

```{r mundlak}
md <- mundlak(violence ~ loggdppc + parliament + disaster | loggdppc + parliament + disaster,
              data = bp, unit = "country")
md$formula
md$added
m_md <- oprobit(violence ~ loggdppc + parliament + disaster + loggdppc_mean + disaster_mean,
                data = md$data, cluster = "country")
round(summary(m_md)$coefficients[, 1:2], 3)
```

(The unit mean of `parliament` is not added to the outcome equation above
because it is time-invariant in all but one country, so its mean would be
nearly collinear with the variable itself.)

## The Monte Carlo: which device for which panel

`system.file("mc", package = "iop")` holds a Monte Carlo (`fe_bias.R`) that
compares the four devices on an ordered probit with G = 100 units, a N(0, 1)
unit effect, and a covariate correlated with it (`x1 = N(0,1) + 0.5 * alpha`),
for T = 4, 8, 16, 32 and 100 replications, together with an audit script
(`fe_bias_verify.R`) that checks the pooled bias against its closed form and
each estimator against `ordinal::clm()` / `clmm()` on identical draws. The
percent bias of the `x1` coefficient (true value 0.8):

```{r mc, echo = FALSE}
mc <- read.csv(system.file("mc", "fe_bias_results.csv", package = "iop"))
mc$estimator <- factor(mc$estimator, levels = c("pooled", "fe", "re", "mundlak"),
                       labels = c("pooled", "unit dummies (fe)", "random intercept (re)", "Mundlak"))
tab <- reshape(mc[, c("T", "estimator", "bias_pct")], idvar = "estimator", timevar = "T", direction = "wide")
names(tab) <- c("estimator", paste0("T = ", sort(unique(mc$T))))
tab[-1] <- lapply(tab[-1], function(v) sprintf("%+.1f", v))
knitr::kable(tab, row.names = FALSE, align = c("l", rep("r", 4)),
             caption = "Percent bias of the x1 coefficient, 100 replications per cell")
```

The pooled and random-intercept estimators are biased by the omitted
correlated effect (the pooled bias has a closed form under this design,
+11.8 percent); the dummies carry the incidental-parameters bias that fades
with T; the Mundlak device is essentially unbiased at every T. Hence the
recommendation: `mundlak()` (with `cluster =` or `re =`) for short panels,
`fe =` -- with the jackknife if the half-panels identify the parameters -- for
long ones.

## References

Dale, D. and Sirchenko, A. (2021). Estimation of nested and zero-inflated
ordered probit models. *Stata Journal*, 21, 3-38.

Dhaene, G. and Jochmans, K. (2015). Split-panel jackknife estimation of
fixed-effect models. *Review of Economic Studies*, 82, 991-1030.

Greene, W. (2004). The behaviour of the maximum likelihood estimator of
limited dependent variable models in the presence of fixed effects.
*Econometrics Journal*, 7, 98-119.

Mundlak, Y. (1978). On the pooling of time series and cross section data.
*Econometrica*, 46, 69-85.

Wooldridge, J.M. (2010). *Econometric Analysis of Cross Section and Panel
Data*, 2nd ed. MIT Press.
