---
title: "Coordinated replication: what travels between waves"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Coordinated replication: what travels between waves}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

A rotating panel measures part of the same sample twice. That shared part is what
makes a **change** more precise than the two levels it is built from -- and it is
also what makes the change harder to estimate, because the two samples are not
independent. With replication, the covariance is not something you compute from a
formula: it is something the replicates either carry or lose. This article is
about the mechanism that carries it, so that `$strata` can be read as the quality
indicator it is meant to be.

## What has to travel

In a rescaling bootstrap each replicate draws, for every stratum, how many times
each PSU is selected -- its **multiplicity** `c_hi` -- and turns it into the
replicate factor

$$\lambda_{hi} = 1 - a_h + a_h \frac{n_h}{m_h} c_{hi}, \qquad a_h = \sqrt{\frac{m_h}{n_h - 1}}.$$

For replicate *b* of period *t* to be comparable with replicate *b* of period
*t-1*, a PSU present in both must be resampled **the same way** in both. So what
travels between periods is the multiplicity matrix itself, not the random seed and
not the uniforms behind it.

The distinction matters. Re-drawing period *t* from a stored uniform looks
equivalent and is not: the exact multinomial draw is a joint constraint over the
whole stratum (the multiplicities must add up to `m_h`), so re-drawing cannot both
keep the stratum total right *and* give every shared PSU exactly the multiplicity
it had. Transferring the integers does both. This is the Statistics Canada LFS
procedure (cat. 71-526-X, sec. 7.2.2, after Roberts, Kovacevic, Mantel and
Phillips 2001), and it is what `wave_step()` implements.

## The four cases

Which of these applies is decided by the stratum, not by the design as a whole,
and it is decided anew every period:

| case | situation | what happens | exact? |
|---|---|---|---|
| **i** | the stratum keeps exactly its PSUs | each PSU inherits its own multiplicity | yes |
| **ii** | some PSUs rotate out and as many rotate in, `n_h` unchanged | each incoming PSU is paired with an outgoing one and inherits its multiplicity: a permutation | yes |
| **iii** | the stratum has **fewer** PSUs than before | the transfer leaves the column short, and multiplicity is added at random until it closes on `m_h` | no |
| **iv** | the stratum has **more** PSUs than before | the column overshoots, and multiplicity is dropped at random | no |

Cases i and ii preserve two things at once: the stratum total stays exactly `m_h`,
so the replicate is a valid Rao-Wu replicate, and every shared PSU keeps exactly
its resampling, so the covariance is transmitted intact. Cases iii and iv can only
preserve the first, so some replicates have to be adjusted and the coordination
becomes approximate.

Two labels are not cases: `fresh` (the first period of a chain -- there is nothing
to coordinate with) and `singleton` (a stratum with one PSU, which has no
within-stratum variance to resample).

## Reading `$strata`

A rotating design with four strata, five PSUs each, two of which are replaced
between waves:

```{r design}
set.seed(11)
mk <- function(psus, shift = 0) {
  do.call(rbind, lapply(psus, function(k) {
    data.frame(stratum = (k - 1L) %/% 100L, psu = k, w = 20,
               y = rnorm(8, 10 + 2 * ((k %% 7) - 3) + shift, 2))
  }))
}
w1 <- mk(unlist(lapply(1:4, function(h) h * 100L + 1:5)))              # PSUs 1-5
w2 <- mk(unlist(lapply(1:4, function(h) h * 100L + c(3:5, 6:7))), 0.5) # 3-7: rotates 2 of 5
w3 <- mk(unlist(lapply(1:4, function(h) h * 100L + c(5:7, 8))), 1)     # 5-8: only 4 left

sp  <- function(d) weighting_spec(d, base_weights = w)
EST <- list(mean_y = function(w, d) weighted.mean(d$y, w))
```

```{r chain}
t1 <- wave_step(sp(w1), estimands = EST, replicates = 300, strata = "stratum",
                psu = "psu", period = "T1", seed = 1, progress = FALSE)
k1 <- wave_carry(t1)

t2 <- wave_step(sp(w2), previous = k1, estimands = EST, replicates = 300,
                strata = "stratum", psu = "psu", period = "T2", seed = 2, progress = FALSE)
t2$strata
```

Five PSUs before, five after, three of them shared: **case ii**, and
`coordinated = 1` -- every one of the 300 replicates closed without adjustment.
That column is the quality indicator. It is the share of replicates in which the
transferred multiplicities already summed to `m_h`, so nothing had to be added or
dropped at random. At 1 the coordination is exact and the reported covariance is
the design's; below 1, that fraction of replicates has been perturbed.

Now let the third wave lose a PSU per stratum:

```{r caseiii}
k2 <- wave_carry(t2)
t3 <- wave_step(sp(w3), previous = list(k2, k1), estimands = EST, replicates = 300,
                strata = "stratum", psu = "psu", period = "T3", seed = 3, progress = FALSE)
t3$strata
```

**Case iii**, and `coordinated` falls to about 0.42: in roughly three of every five
replicates the transferred column came up short of `m_h` and had to be topped up at
random. The estimate is still usable -- this is a nuisance, not a failure -- but it
is no longer exact, and a figure whose `coordinated` column sits near zero is one
to report with that caveat, or to fix upstream by keeping the stratum's PSU count
stable across waves.

## The covariance *is* the pairing

The claim that coordination transmits the covariance can be verified in one line. The
carry stores the `R` replicate values of every declared estimand, so the
correlation between the two periods' replicate vectors should be exactly the `rho`
reported for the change:

```{r pairing}
c(cor_replicates = cor(k1$theta$mean_y, k2$theta$mean_y),
  rho_reported   = t2$change$rho)
```

They agree to three decimals. The two are not literally the same arithmetic --
`$change` centres the replicates on the point estimate and divides by `R`, while
`cor()` centres on the replicate mean and divides by `R - 1` -- but they are two
readings of the same alignment, and nothing else in the run produces it. The
pairing is the whole of it -- destroy the alignment and the covariance goes with it, even though
both vectors are untouched as sets:

```{r shuffle}
set.seed(4)
cor(k1$theta$mean_y, sample(k2$theta$mean_y))
```

That is the reason the variance of a change cannot be recovered after the fact from
two independently produced sets of replicate weights, however carefully each was
built. It has to be arranged while the second set is drawn.

## `previous` is a list, and the lag is a design property

`wave_step()` takes a **list** of carries because which earlier periods share
sample with this one is decided by the rotation calendar, not by proximity. A
6-consecutive design overlaps at lags 1 to 5; a `4-8-4` design overlaps at lags 1-3
and again at 9-15; a `1(2)5` design shares nothing at all with the adjacent
quarter. Each PSU inherits from the **most recent** carry that contains it, so gaps
and returning cohorts resolve themselves and no window parameter is needed. Supply
every carry whose period shares sample with this one; `panel_design()` prints the
overlap profile that tells you which those are.

The third wave above was given both earlier carries, so it reports a change against
each, with the correlation decaying as the overlap thins:

```{r lags}
t3$change[, c("from", "to", "estimate", "se", "rho", "deff_change")]
```

`deff_change` is `V / (V1 + V2)`: what the overlap saved relative to treating the
two periods as independent samples. Note that ignoring the overlap is not
conservative -- it is simply wrong, and in which direction depends on the sign of
the covariance.

## What coordination does not touch

The published cross-sectional weights. `t2$weights` is identical to
`prep(spec)$final_weight`:

```{r untouched}
identical(t2$weights, prep(sp(w2))$final_weight)
```

Coordination adds the change, the diagnostics and the carry. It never moves the
point estimate the office publishes, which is what makes it safe to add to a
production run that already exists.

## Where to look next

`?wave_step` and `?wave_carry` for the chaining engine, `?wave_contrast` for linear
combinations over a chain (a rolling quarter, an annual average),
`?panel_design` for the rotation calendar and the overlap profile it implies, and
`vignette("rotating-panels")` for the production workflow these pieces serve. The
agreement of the resulting change variance with an analytic estimator from a
different family is checked in
`vignette("validation")`.
