Skip to contents

How execute() partitions a sample into panels with panels, where panel_stage puts the assignment, what a rotation schedule declares, and how execute(master, wave = t) weights the wave it materializes.

Details

How units are assigned

Assignment is randomized with fixed quotas. Within each selection stratum of the assignment stage the assignment units are ordered, cut into consecutive blocks of 2 * panels, given a fixed quota per panel inside each block, and permuted within their block. Every unit therefore carries each panel with probability 1 / panels, and panel sizes within a pool differ by at most one. It is a partition of the selected sample, not an additional probability-sampling phase. It changes no inclusion probability and no .weight, so the combined sample is analyzed with the stored weights.

Blocking is what preserves order. Units adjacent in the control order of the assignment stage's draw() fall in the same block, so every panel inherits the same spread over that order. A pool holding fewer than 2 * panels units is a single block, still assigned, with no block-level order left to preserve.

Panels are assigned once. .panel is carried forward by a stage continuation, and redeclaring panels on a sample that already carries an assignment is an error.

Assignment below the first stage

Panels are assigned at stage 1 by default, and every unit below inherits its ancestor's panel, so whole primary units rotate. panel_stage moves the assignment to a lower stage, which retains the primary units and rotates the units inside them: the address-panel design, in which selected areas stay in the survey and households rotate within them. A pool is then the assignment stage's strata inside each realized parent and never crosses a parent, so pools are smaller and small_pool matters more.

Under a with-replacement assignment stage the assignment unit is the realized draw, the unit the estimator uses, so one population unit selected twice may take two different panels. A parent selected twice likewise gives two separate populations of households, and a household reached under both hits is assigned once for each.

Assigning below the first stage is not a default and should not be treated as one. Holding the parent fixed while its members rotate can bias cross-sectional estimates over time, and a unit that cannot move between parents is balanced for net change but not for gross change.

Certainty units

Certainty units are labelled from their own pools and consume no rotating quota. A certainty unit is in the sample at every occasion, since a schedule that rotated it out would drop the very units the certainty stratum exists to enumerate. Certainty counts only at the assignment stage, in both directions: a certainty primary unit does not make the units below it permanent when panel_stage names a lower stage, and a certainty selection below the assignment stage does not keep its assignment unit in every wave. A unit selected with probability one inside a rotating parent is absent from the waves that parent sits out.

Rotation schedules and waves

Passing a schedule to panels instead of a count declares which panels are active at which occasion, and lets execute(master, wave = t) materialize one of them. A schedule is a data frame with an integer panel column, an integer wave column and an optional logical active column, and a combination left out is inactive:

schedule <- data.frame(
  panel  = rep(1:4, times = 3),
  wave   = rep(1:3, each = 4),
  active = c(TRUE, TRUE, FALSE, FALSE,
             FALSE, TRUE, TRUE, FALSE,
             FALSE, FALSE, TRUE, TRUE)
)
master <- execute(design, frame, seed = 1, panels = schedule)
wave_2 <- execute(master, wave = 2)

The schedule is read at the master draw, because the block size follows from it. A schedule whose leanest wave activates r of the k panels blocks at k * ceiling(2 / r) rather than at the worst case 2k, which keeps more of the assignment order while still leaving two units per block in the take.

Materializing wave t is a probability subsample rather than a filter: a two-phase sample whose second phase is the activation, with the blocks as phase-2 strata and their frozen quotas as phase-2 population counts. It multiplies .weight by the inverse of the activation probability. That probability is the block's frozen quota for the active panels over the block size, so it is exact rather than nominal, and it is generally not k / r. Permanent certainty units are activated at every wave with probability one and their weights are untouched.

A materialized wave is a sample in its own right, with its own integrity record, so frame_summary() and the weight diagnostics work on it. It retains the master as its first phase, and as_svydesign() exports it through survey::twophase() with the activation as the second phase. Its receipt records the master's execution, so replay_design() rebuilds the master and materializes the wave again.

The schedule states which groups are live when. It does not replenish the sample: every panel comes from the frame vintage the master was drawn from, and steady-state replenishment is a fresh execute() against a later frame. For an svyplan_schedule, execute() takes the startup activity and checks its panel parameters before assignment. A gradual launch has one whole startup cohort and needs no partition.

Pools too small for a schedule

A pool of m units leaves panels - m panels empty, so a wave activating r of them selects nothing from that pool when m <= panels - r. Those units would have inclusion probability zero in that wave rather than a small weight, which biases the wave's estimator. small_pool = "error", the default, refuses such an assignment before any panel is drawn and names the pools. "permanent" activates them at every wave with probability one and warns with samplyr_warning_panel_small_pool, which is exact but changes the operational design: wave sizes, overlap and repeated interviewing all increase. It matters only with a schedule, since a panel count declares no wave to protect, and it governs positivity only. A pool with a single active unit is still assigned, and is marked as carrying no within-block variance estimate. An svyplan_schedule refuses "permanent", and selection-certainty units (samplyr_error_plan_certainty), because its overlap describes a fully rotating life.

Weights of a subset of panels

Weights are not adjusted for panel membership. They reflect the full-sample inclusion probability and are valid for the combined sample. A subset of the panels is a simple random subsample without replacement within each block, but its conditional probability is the block's realized quota over the block size, not 1 / panels, so multiplying one panel's weights by panels is not generally valid for population inference. The block sizes and realized quotas are recorded with the sample and written to the design file by write_design(), because they are what such a subset has to be computed against. joint_expectation() with waves reads how two occasions overlap from the same record.

See also

execute(), rotation_program() for cohort programs, stack_waves() for stacking materialized waves.