---
title: "Composite estimation: borrowing strength from the previous wave"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Composite estimation: borrowing strength from the previous wave}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

In a rotating panel most of this month's sample was also in last month's. The
**composite estimator** uses that: it adds the previous wave's labour status to the
calibration as an auxiliary variable, with control totals taken from the previous
wave's own composite estimates. The result is a level series that is smoother and,
much more to the point, a **change** measured far more precisely -- because the
estimator is partly built from the same units twice.

`step_cre()` implements the regression composite estimator of Fuller and Rao (2001)
and Gambino, Kennedy and Singh (2001), in the form specified for the Canadian
Labour Force Survey (Statistics Canada, cat. 71-526-X, sec. 6.3.1) and used by
Uruguay's ECH (sec. 8.4). This article states that form, maps it onto the
arguments of `step_cre()`, measures what it buys, and explains the one thing that
makes its variance a special case.

## The estimator

Start from the nonresponse-adjusted weights $w_l^{NA}$ -- whatever the recipe
produced up to this step. Composite calibration looks for final weights
$w_l^{CC}$ as close as possible to those, in the chi-square distance

$$\min_{w^{CC}} \; \sum_{l \in s_r} \frac{\left(w_l^{CC} - w_l^{NA}\right)^2}{w_l^{NA}}$$

subject to **two** blocks of constraints rather than the usual one:

$$\sum_{l \in s_r} w_l^{CC} \begin{pmatrix} \mathbf{x}_l \\ \mathbf{z}_{\bullet l} \end{pmatrix} = \begin{pmatrix} \mathbf{X} \\ \widehat{\mathbf{Z}} \end{pmatrix}$$

The upper block is ordinary calibration: $\mathbf{x}_l$ are the demographic
auxiliaries and $\mathbf{X}$ their **known** population totals (census
projections). The lower block is what makes the estimator composite:
$\mathbf{z}_{\bullet l}$ are indicators of the *previous* wave's labour status and
$\widehat{\mathbf{Z}}$ are their totals **estimated from the previous wave**. The
solution is the familiar GREG form,

$$w_l^{CC} = w_l^{NA} g_l^{CC}, \qquad g_l^{CC} = \left(\mathbf{x}_l', \mathbf{z}_{\bullet l}'\right) \left[\sum_{l \in s_r} w_l^{NA} \begin{pmatrix} \mathbf{x}_l \\ \mathbf{z}_{\bullet l}\end{pmatrix}\left(\mathbf{x}_l', \mathbf{z}_{\bullet l}'\right)\right]^{-1} \begin{pmatrix} \mathbf{X} \\ \widehat{\mathbf{Z}} \end{pmatrix}$$

with $g_l^{CC}$ near 1 when the sample already reproduces both blocks.

## The birth rotation group, and why there are two imputations

$\mathbf{z}_{\bullet l}$ is not observed for everyone: the units that entered the
sample this wave -- the **birth rotation group** $s_r^b$ -- have no previous month.
Their values have to be imputed, and the LFS methodology uses *two* imputations
because they are efficient for two different things.

**MR1, mean imputation**, is efficient for the **level** at month $t$:

$$\mathbf{z}_{\bullet l}^{(1)} = \begin{cases} \mathbf{z}_{t-1,l} & l \in s_r - s_r^b \\ \widehat{\mathbf{Z}} / N_{15+} & l \in s_r^b \end{cases}$$

**MR2, carry-backward imputation**, is efficient for the **difference** between two
successive months. The birth group carries its current value backwards, and the
non-birth groups are corrected for the change that this erases:

$$\mathbf{z}_{\bullet l}^{(2)} = \begin{cases} \mathbf{z}_{t-1,l} + \left(\delta_l^{-1} - 1\right)\left(\mathbf{z}_{t-1,l} - \mathbf{z}_{t,l}\right) & l \in s_r - s_r^b \\ \mathbf{z}_{t,l} & l \in s_r^b \end{cases}$$

Here $\delta_l$ is the probability of *not* being in the birth group -- the overlap
rate, $5/6$ in a six-month rotation -- estimated from the weights themselves as
$\hat\delta = \sum_{s_r - s_r^b} w_l^{NA} \big/ \sum_{s_r} w_l^{NA}$. The
correction factor $\delta^{-1} - 1$ is what preserves asymptotic unbiasedness:
without it, carry-backward would quietly remove one sixth of the month-to-month
change.

Neither is used alone. The composite auxiliaries are the mix

$$\mathbf{z}_{\bullet l} = (1 - \alpha)\,\mathbf{z}_{\bullet l}^{(1)} + \alpha\,\mathbf{z}_{\bullet l}^{(2)}, \qquad \alpha = 2/3$$

with $\alpha$ a tuning constant: $\alpha = 0$ targets the level alone, $\alpha = 1$
the change alone. Both the LFS and the ECH use $2/3$, following the study of Chen
and Liu (2002).

### Where each symbol lives in the call

| symbol | argument | |
|---|---|---|
| $w_l^{NA}$ | — | whatever the recipe produced before this step |
| $\mathbf{x}_l$, $\mathbf{X}$ | `formula`, `totals` | as in `step_calibrate(method = "linear")` |
| $\mathbf{z}_{\bullet l}$ | `status`, `composite` | status indicators, crossed by the domains in `composite` |
| $\widehat{\mathbf{Z}}$ | `previous` | re-estimated from the prepped previous wave |
| $\alpha$ | `alpha` | default `2/3` |
| $\delta$ | `overlap` | `"auto"` estimates $\hat\delta$ from the weights; or give it (e.g. `5/6`) |
| $s_r^b$ | `birth`, `id_unit` | the birth group, named or derived by unit key |
| $N/G$ | `rotation_group` | equal-representation constraints, see below |

`composite` is the list of domain crossings that define the blocks of
$\widehat{\mathbf{Z}}$: `NULL` is the country total, `"sex"` is status crossed by
sex, a character vector is status crossed by the interaction. The ECH's own list
is `list(NULL, "sex", "department")`. `status_ref` names the status level left
implicit in each block -- one level must always be dropped, or the block is
collinear with the intercept of $\mathbf{x}$; naming it only decides which.

Units that are neither birth nor matched -- new household members, people newly of
working age, people out of scope last month -- are handled by `on_missing_prev`:
`"carry_backward"` sets $\mathbf{z}_{t-1} = \mathbf{z}_t$, `"zero"` sets
$\mathbf{z}_{\bullet l} = \mathbf{0}$, which is what the LFS does for the
out-of-scope (Gambino, Kennedy and Singh 2001).

## The recursion

The estimator is defined recursively, so a chain needs a starting point. The
**seed** wave has no $t-1$: with `previous = NULL` the composite block is empty and
`step_cre()` reduces to an ordinary linear calibration to $\mathbf{X}$. From the
second wave on, `previous` is the prepped recipe of the wave before.

```{r setup-data}
wv <- lapply(1:3, function(k) {
  d <- subset(panel_ine, wave == k & disposition == "R"); d$sex <- factor(d$sex); d
})
Xtot <- function(d) colSums(d$pw * model.matrix(~ sex, data = d))
```

```{r seed}
seed <- weighting_spec(wv[[1]], base_weights = pw) |>
  step_cre(previous = NULL, status = lf_status, formula = ~ sex,
           totals = Xtot(wv[[1]]), status_ref = "inact") |>
  prep()

wave2 <- weighting_spec(wv[[2]], base_weights = pw) |>
  step_cre(previous = seed, status = lf_status, composite = list(NULL, "sex"),
           id_unit = c("household_id", "person_no"), formula = ~ sex,
           totals = Xtot(wv[[2]]), alpha = 2/3, status_ref = "inact")
```

## What it buys

The comparison worth making is against the same recipe with an ordinary
calibration, over the same chain, with the same replicates:

```{r compare}
EST <- list(unemp_rate = function(w, d) weighted.mean(d$unemployed, w, na.rm = TRUE))

run <- function(composite) {
  prev <- NULL; out <- list()
  for (k in 1:3) {
    sp <- weighting_spec(wv[[k]], base_weights = pw)
    sp <- if (composite)
      step_cre(sp, previous = if (k == 1) NULL else out[[k - 1]]$prepped,
               status = lf_status, composite = list(NULL, "sex"),
               id_unit = c("household_id", "person_no"), formula = ~ sex,
               totals = Xtot(wv[[k]]), alpha = 2/3, status_ref = "inact")
    else
      step_calibrate(sp, method = "linear", formula = ~ sex, totals = Xtot(wv[[k]]))
    s <- wave_step(sp, previous = prev, estimands = EST, replicates = 150,
                   strata = "stratum", psu = "psu", period = paste0("T", k),
                   seed = 100 + k, progress = FALSE)
    out[[k]] <- list(step = s, carry = wave_carry(s), prepped = prep(sp))
    prev <- rev(lapply(out, function(z) z$carry))
  }
  out
}

cre   <- run(TRUE)
plain <- run(FALSE)

tab <- function(o, label) {
  ch <- o[[3]]$step$change
  data.frame(recipe = label, from = ch$from, se = round(ch$se, 5),
             rho = round(ch$rho, 3), deff_change = round(ch$deff_change, 3))
}
rbind(tab(cre, "composite (CRE)"), tab(plain, "plain calibration"))
```

Against the previous month the standard error of the change falls by about a third,
and the correlation between the two periods' estimates rises from roughly 0.6 to
roughly 0.85. That is the whole point: the composite auxiliaries make consecutive
estimates share more of their sampling error, and shared error cancels in a
difference. Statistics Canada reports the same qualitative result -- composite
estimation "significantly reduces the sampling errors" of the LFS (cat. 71-526-X,
ch. 8).

It is not free. The level at period $t$ now depends on the previous wave's
estimates, so an error propagates forward and the series is smoother than the data
alone would justify. That is why $\alpha$ exists and why neither agency sets it
to 1.

## The variance trap

Here is what makes `step_cre()` different from every other step. Its control totals
$\widehat{\mathbf{Z}}$ are **not known population figures**. They are *estimated*
from the previous wave, with the previous wave's weights, and they carry that
wave's sampling error.

A single-sample bootstrap does not know this. It resamples the current wave and
re-runs the recipe, but $\widehat{\mathbf{Z}}$ was computed once, from the previous
wave's frozen point weights, so every replicate calibrates to the *same* control
totals -- as if they were census figures. Writing the change as

$$V(\hat\theta_t - \hat\theta_{t-1}) = V(\hat\theta_t) + V(\hat\theta_{t-1}) - 2\,\mathrm{Cov}(\hat\theta_t, \hat\theta_{t-1})$$

the single-sample engine gets the first term wrong *and* has no way to produce the
third. The result is too small, and too small in precisely the direction that
matters. The package refuses to let this happen quietly:

```{r var04}
b <- tryCatch(bootstrap_weights(wave2, replicates = 5, strata = "stratum",
                                psu = "psu", progress = FALSE),
              warning = function(w) conditionMessage(w))
b
```

The honest version re-estimates $\widehat{\mathbf{Z}}$ inside every replicate:
replicate $b$ of period $t$ rebuilds its control totals from replicate $b$ of
period $t-1$. That is what `wave_step()` and `wave_bootstrap()` do, and it is the
reason a chain containing `step_cre()` needs the previous period's **replicate
weights**, not just its replicate estimates.

## The fat carry

Hence the two shapes of the carry. `carry = "auto"`, the default, inspects the
recipe: with no `step_cre()` it stores only the replicate values of the declared
estimands; with one, it stores the $R \times n$ matrix of replicate weights as well.

```{r sizes}
c(thin_KB = as.numeric(object.size(plain[[1]]$carry)) / 1024,
  fat_KB  = as.numeric(object.size(cre[[1]]$carry))   / 1024)
```

Thirty times larger, and the difference is entirely that matrix. For a monthly
production chain it is a few megabytes per period -- the price of an honest
variance for the composite estimator.

The injection is audited, so you can confirm it happened rather than assume it:

```{r audit}
c(injected = cre[[2]]$step$n_cre_injected, skipped = cre[[2]]$step$n_cre_skipped)
```

`n_cre_injected` counts the CRE steps that received the previous period's replicate
weights; `n_cre_skipped` those that did not, because no carry contained them or the
carry was thin. A chain running with `skipped > 0` is producing exactly the
anticonservative variance the warning above is about, and the number is there so a
production run can assert on it.

## Equal representation across rotation groups

Both methodologies impose one more constraint: each rotation group must weight to
the same working-age total,

$$\sum_{l \in s_g} w_l^{CC} = \frac{N_{15+}}{G}, \qquad g = 1, \dots, G-1$$

(ECH sec. 8.4.1; LFS sec. 6.3.1, "additional control totals are used to ensure that
the estimated number of people aged 15 and over is the same for each rotation
group"). Passing `rotation_group` adds it as $G-1$ extra columns in the demographic
block -- the last group's total follows from the others and $N_{15+}$, so
constraining it too would be redundant.

```{r rotation, eval = FALSE}
step_cre(spec, previous = seed, status = lf_status, composite = list(NULL, "sex"),
         id_unit = c("household_id", "person_no"), formula = ~ sex,
         totals = Xtot(wv[[2]]), rotation_group = "rotation_group",
         status_ref = "inact")
```

## References

Chen, E. J., and Liu, T. P. (2002). Choices of alpha value in regression composite
estimation for the Canadian Labour Force Survey. *Proceedings of the Survey
Methods Section, SSC*.

Fuller, W. A., and Rao, J. N. K. (2001). A regression composite estimator with
application to the Canadian Labour Force Survey. *Survey Methodology* 27(1), 45-51.

Gambino, J., Kennedy, B., and Singh, M. P. (2001). Regression composite estimation
for the Canadian Labour Force Survey: evaluation and implementation. *Survey
Methodology* 27(1), 65-74.

Statistics Canada (2017). *Methodology of the Canadian Labour Force Survey*,
catalogue no. 71-526-X, sec. 6.3.1.

## Where to look next

`?step_cre` for the full argument list, `vignette("coordinated-replication")` for
the mechanism that pairs the replicates across waves, `vignette("rotating-panels")`
for the production workflow, and `?wave_carry` for what travels between runs.
