This vignette documents what each samplyr operation
assumes, what it computes, and what is preserved or lost at
survey export.
Use this as a topic reference while working through the tutorials.
The main reading path is vignette("introduction"), then
vignette("survey-analysis").
Components, scope and transitions
A design is a sequence of stages. Each stage declares optional strata
and allocation, an optional sampling-unit key, and one selection
specification. draw() completes the stage, which then takes
no further strata, clusters or selection, so a design is extended with
add_stage(). execute() interprets it against
frames. Without allocation, scalar n applies per stratum.
With allocation it is a total over those strata. At later stages, both
meanings apply within each selected parent pool. For random-size
methods, the target is an expectation.
| Operation | Input | Result and statistical meaning |
|---|---|---|
sampling_design() and specification verbs |
Column names and selection rules | An inspectable specification, with no sample drawn |
frame_summary(design, frame) |
Specification and registers | Resolved pool targets and chances, without random draws |
execute(design, frame) |
Specification and population register(s) | A realized sample with selection metadata and weights |
execute(partial_sample, listing) |
An unfinished design’s sample and its next register | Continuation with completed-stage probabilities retained |
execute(new_design, previous_sample) |
A new design and an earlier realized sample | New phase with conditional selection factors compounded with prior weights |
execute(master, wave = t) |
A master sample with a panel schedule | Activation by conditional selection under the frozen panel quotas |
as_svydesign(sample) or
as_svrepdesign(sample)
|
A supported sample and collected observations | An analysis representation, subject to the inference restrictions |
The composition laws are that previews agree with execution’s resolved chances, continuation under the same frame order and uninterrupted RNG stream agrees with one-call execution, and serialization preserves the declared specification. Weight interpretation must survive every transformation, and an export must preserve the required stage information or refuse the design. Agreement of numerical targets does not turn approximate probabilities into exact ones. Phase-wise conditional factors also need not be unconditional marginal inclusion probabilities.
Assumptions at a glance
For a compact selection table, see ?selection_methods,
and for how each method’s variance is estimated,
?variance_estimation. A complete three-stage fieldwork
example is in vignette("introduction").
The following table summarizes what samplyr assumes and
what the user must verify. Each row is expanded in the sections
below.
| Assumption | Applies to | samplyr behavior | User must verify |
|---|---|---|---|
| Strata are exhaustive and disjoint | stratify_by() |
Partitions the frame by unique combinations of strata variables | Strata variables define a valid partition |
| Independent sampling across strata | stratify_by() |> draw() |
Each stratum sampled with a separate call to
sondage, except stratified cube
|
Correct by construction when strata are disjoint.
Stratified cube balances across strata in one call |
| Cluster variables constant within clusters | cluster_by() |
Validated at execution: MOS and strata variables may not vary within a cluster | Cluster ID, read within its strata and earlier-stage units, identifies the group |
| Conditional selection laws | add_stage() |
Stage \(k\) sampling depends only on the units selected at stage \(k - 1\) and its declared inputs | Later frame construction and selection depend on prior randomization only through selected ancestors and recorded design variables |
| FPC records method-specific stage information | execute() |
Stores a population or cluster count for WOR and
Inf for WR or PMR. PPS WOR is converted to \(\pi_i\) at survey export |
Frame size at execution is treated as the population, one of several definitions when a population changes between waves |
| Weights are inverse selection chances, unadjusted | All methods | Uses inclusion probabilities for WOR and expected hit counts for WR or PMR | Downstream adjustment is the analyst’s responsibility |
| PRN values are iid \(\text{U}(0, 1)\) | draw(prn = ...) |
Validated: strictly in \((0, 1)\), no NAs, numeric | Independence and uniformity of the PRN column |
Notation
Throughout this vignette:
- \(U = \{1, \ldots, N\}\): finite population (the frame).
- \(S \subseteq U\): selected population units for a WOR design.
- \(K_i\): number of realized hits for unit \(i\) in a WR or PMR design.
- \(q_i\): stage chance, equal to \(\pi_i = \Pr(i \in S)\) for WOR and \(E(K_i)\) for WR or PMR.
- \(\pi_{kl} = \Pr(k \in S \text{ and } l \in S)\): joint (second-order) inclusion probability for WOR.
- \(E(K_k K_l)\): joint expected hits for WR or PMR.
- \(w_i = 1 / q_i\): design weight per selected unit or realized occurrence.
- \(N_h\): number of the stage’s sampling units in stratum \(h\) (\(h = 1, \ldots, H\)), so PSUs at a clustered stage.
- \(n_h\): sample size drawn from stratum \(h\).
- \(K\): number of sampling stages.
- \(\pi_i^{(k \mid S^{(k-1)})}\): conditional inclusion probability of unit \(i\) at stage \(k\), given the set \(S^{(k-1)}\) selected at stage \(k - 1\).
execute() produces design weights only.
Calibration, trimming, and nonresponse adjustment are out of scope and
belong in the estimation layer (survey, srvyr,
or similar).
One declared transformation departs from that, and says so in the
object it returns: share_weights() produces
estimation weights for a population other than the one
that was sampled. Its semantics, and the contract that keeps the two
kinds of weight apart, are the subject of a section below.
What each verb means
sampling_design(): frame-independent plan
A sampling_design object is a specification, not a
computation. Column names referenced in stratify_by(),
cluster_by(), and draw() are stored as strings
(deferred resolution). No data is touched until execute()
binds the design to a frame. This separation means the same design can
be applied to different frames or re-executed with different seeds.
library(samplyr)
library(dplyr)
design <- sampling_design(title = "SRS WOR") |>
stratify_by(region) |>
draw(n = 50, method = "srswor")
# No data has been sampled yet
design
#> ── Sampling Design: SRS WOR ────────────────────────────────────────────────────
#>
#> ℹ 1 stage
#>
#> ── Stage 1 ─────────────────────────────────────────────────────────────────────
#> • Strata: region
#> • Draw: n = 50 (per stratum), method = srswor
stratify_by(): partition into independent
sub-populations
stratify_by() partitions the frame into \(H\) strata defined by the Cartesian product
of its variables:
\[ U = U_1 \cup U_2 \cup \cdots \cup U_H, \qquad U_h \cap U_{h'} = \emptyset \; \text{for } h \neq h'. \]
Sampling within each stratum is independent. This holds by
construction because samplyr invokes sondage
algorithms separately for each stratum. Stratified cube is
the exception: its global flight phase balances across strata, so its
strata are not selected independently.
When an allocation method is specified, the total sample size \(n\) is distributed across strata:
| Allocation | Formula | Requires |
|---|---|---|
"equal" |
\(n_h = n / H\) | |
"proportional" |
\(n_h \propto N_h\), or \(X_h\) if given |
importance, optional |
"neyman" |
\(n_h \propto N_h \, S_h\) | variance |
"optimal" |
\(n_h \propto N_h \, S_h / \sqrt{C_h}\) |
variance, cost
|
"power" |
\(n_h \propto \text{CV}_h \cdot X_h^q\) |
cv, importance
|
Each formula gives a factor \(w_h\). The allocation is the solution of
\[ n_h = \operatorname{clamp}(\lambda w_h, \; \ell_h, \; u_h), \qquad \ell_h = \min(\texttt{min\_n}, N_h), \qquad u_h = \min(\texttt{max\_n}, N_h) \]
with \(\lambda\) chosen so that
\(\sum_h n_h = n\). The population size
\(N_h\) is an upper bound whether or
not max_n was supplied, so a fixed-size without-replacement
design satisfies \(0 \le n_h \le N_h\)
and \(\sum_h n_h = n\) for every
feasible request.
Saturated strata are fixed at their bound and removed from the problem, then \(\lambda\) is recomputed over the strata still free. Redistribution therefore follows the same factors \(w_h\) the method is defined by, which is what keeps a Neyman allocation Neyman after a stratum saturates: redistributing in proportion to unused capacity instead would silently change the criterion. When every free stratum has \(w_h = 0\) the criterion cannot distinguish them and the remainder is split equally in stratum order.
One consequence is worth stating plainly: a named rule can be
departed from when a stratum is too small to hold its share. With
populations \((10, 490, 500)\) and
\(n = 300\), "equal"
yields 10/145/145, not 100/100/100. execute() reports this
once per run.
The constrained real-valued targets are then rounded with largest-remainder (Hare-Niemeyer) rounding that preserves \(\sum n_h = n\) and stays inside every bound.
With-replacement (srswr, pps_multinomial)
and minimum-replacement (pps_chromy) designs can select a
unit more than once, so \(N_h\) is not
a bound on the number of draws and only min_n and
max_n apply. Per-stratum sizes supplied directly, rather
than through alloc, are instructions and are never
redistributed.
cluster_by(): define the sampling unit
cluster_by() declares the variable(s) that identify the
primary sampling unit (PSU) at the current stage. Operationally,
samplyr:
- Groups the frame by the cluster variable(s).
- Draws a sample of clusters (not individual units).
- Returns all population units within selected clusters.
Any variable used in PPS size measures (mos) or in
stratification must be constant within each cluster.
samplyr validates this at execution time and raises an
error if cluster-level variables vary within groups.
cluster_by() is purely structural. It does not affect
inclusion probabilities directly. They are determined by
draw() applied to the cluster-level aggregates.
Cluster ids may be numbered within the level above them. A unit is
identified by its id together with the units earlier stages selected and
the strata of its own stage, so town 1 of county A and town 1 of county
B are two towns. cluster_by(nest = FALSE) instead declares
the ids unique across strata and turns that into a check, which refuses
a cluster coded into two strata instead of splitting it.
?cluster_by gives the rules, including what a separate
register for a later stage must carry.
draw(): specify the selection mechanism
draw() attaches a selection method and sample size (or
fraction) to the current stage. The inclusion probabilities and weights
depend on the method class:
Equal-probability without replacement
(srswor, systematic,
bernoulli):
\[ \pi_i = \frac{n}{N} \quad (\text{or } \pi_i = f \text{ for bernoulli}), \qquad w_i = \frac{1}{\pi_i}. \]
Fixed-size for srswor and systematic and
random-size for bernoulli. Systematic sampling induces
implicit stratification by row order. Use control to
specify the sort variable.
PPS methods (pps_brewer,
pps_systematic, pps_cps,
pps_sampford, pps_poisson,
pps_sps, pps_pareto):
\[ \pi_i = n \, \frac{\text{mos}_i}{\sum_j \text{mos}_j} \]
computed by sondage::inclusion_prob(), which iteratively
caps units with \(\pi_i \geq 1\) and
redistributes. These “certainty” units (\(\pi_i = 1\)) receive stage weight 1 and
contribute zero first-stage variance. In multi-stage designs, final
.weight still compounds all stages, so certainty at one
stage does not imply overall weight 1. For Poisson sampling, \(\pi_i = f \cdot N \cdot \text{mos}_i / \sum
\text{mos}\), capped at 1 (random-size design).
Two of the seven attain that \(\pi_i\) only approximately.
pps_sps and pps_pareto are order sampling
designs, which fix the sample size at the cost of the marginals: the
quantity above is the target inclusion probability
handed to the algorithm, and the realized inclusion probability is close
to it but not equal, approaching it as \(n\) and \(N\) grow (Tillé 2020, sec. 5.11; Rosén 1997). Both sit in the
"approximate" probabilities tier. Everywhere below that
reads \(\pi_i\) off
.weight inherits that distinction. The other five methods
satisfy it exactly.
With-replacement and PMR (srswr,
pps_multinomial, pps_chromy):
The quantity of interest is the expected number of hits \(E(n_i) = n p_i = n \, \text{mos}_i / \sum
\text{mos}\). If unit \(i\) is
selected \(k\) times,
samplyr creates \(k\) rows
with a .draw column indexing each occurrence. The weight on
every output row is \(w_i = 1 / E(n_i) = 1 /
(n p_i)\). For WR the draws are independent and this is the
per-hit Hansen-Hurwitz estimator. No finite population correction
applies (FPC = \(\infty\)).
Chromy’s sequential PPS (pps_chromy) is classified as
probability minimum replacement (PMR): each unit receives \(\lfloor E(n_i) \rfloor\) or \(\lceil E(n_i) \rceil\) hits. Its
occurrences are not independent WR draws. At survey export, samplyr uses
the Hansen-Hurwitz approximation documented below. When all \(E(n_i) < 1\), the selection itself
reduces to WOR.
Balanced and spatially balanced sampling
(cube, lpm2, scps):
The cube method (Deville and Tillé 2004) selects
a fixed-size sample that satisfies (or nearly satisfies) the balancing
equations \(\sum_{i \in S} a_{ij} / \pi_i =
\sum_{i \in U} a_{ij}\) for auxiliary variables \(a_j\) specified via aux.
Without mos, \(\pi_i =
n/N\) and probabilities are equal. With mos, \(\pi_i\) is computed by
sondage::inclusion_prob(). A bound() marker
adds hard adjacent-integer category-count constraints.
When stratified, the stratified cube algorithm (Chauvet 2009) runs flight phases per stratum, then a global flight phase, then per-stratum landing phases.
LPM2 and SCPS instead use the coordinates supplied through
spread. They do not accept cube auxiliaries or count
bounds. At most 2 stages may use a balanced-family method. Certainty
selection is not supported, and of the two only SCPS accepts a PRN.
PRN-compatible methods (bernoulli,
pps_poisson, pps_sps, pps_pareto,
scps):
When prn is specified, the permanent random numbers are
passed through to sondage. Selection becomes deterministic
given the PRN values, so the seed argument to
execute() has no effect on the selection outcome. For
scps it is also fixed by the order in which the pool’s
units are visited.
add_stage(): delimit stages
add_stage() is syntactic: it closes the current stage
and opens a new one. It carries no statistical content of its own.
Stages are numbered \(1, 2, \ldots, K\)
in definition order.
execute(): materialize the sample
execute() binds the design to one or more frames and
runs the selection:
- For each stage \(k\), the frame is restricted to units belonging to clusters selected at stage \(k - 1\) (or the full frame for \(k = 1\)).
- Strata and cluster groupings are resolved against the (subsetted) frame.
- The selection algorithm from
draw()is invoked viasondage. - The
.weight,.fpc,.draw, and.certaintycolumns are computed where they apply and suffixed with the stage number (?sample_columns).
One frame or one per stage. The number of frames
schedules the stages. execute(design, hierarchy) runs every
stage against one table, and
execute(design, schools, classes, students) gives each
stage its own register, mapped by position. Any other count is refused.
The frames may equally be held as a value with
execute(design, list(schools, classes, students)). A name
on a list element is a diagnostic label for that frame.
A register is supplied whole. samplyr restricts it to the units its parent stage selected and carries onto it the variables earlier stages introduced, so a class register needs neither pre-filtering to the sampled schools nor a copy of the school-level stratification column. A register that omits such a carried variable is fine. One whose own copy disagrees with the parent is an error.
Partial execution.
execute(design, psu_frame, stages = 1) runs only the first
stage and returns a tbl_sample. That object stores both the
unchanged design and the realized PSU selection. Use it as the first
argument of the later call and supply the new listing as the frame:
execute(psu_sample, listing_frame). Execution resumes at
the next unexecuted stage, carries forward the PSU weights and FPC, and
remains one multi-stage design.
The stages argument may select any initial contiguous
batch beginning at stage 1 and on a partial tbl_sample, it
may select the next contiguous batch. Omitting it executes all remaining
stages, with one exception. If more than one stage remains and a single
frame is supplied, that frame could be the next stage’s register or a
hierarchy covering the rest. The two draw different samples, so
execute() asks for stages rather than
guessing.
The three spellings are one implementation. A
three-stage design executed against one hierarchy, against three
registers in a single call, or stage by stage as a continuation runs the
same stage transition each time. All three draw the same sample,
provided the continuation runs under a single RNG stream and names
stages on every intermediate call:
one_call <- execute(design, schools, classes, students, seed = 7)
hierarchical <- execute(design, hierarchy, seed = 7)
continued <- withr::with_seed(7, {
s1 <- execute(design, schools, stages = 1)
s2 <- execute(s1, classes, stages = 2)
execute(s2, students, stages = 3)
})Note where the seed goes. One seed wraps the whole chain, and the individual calls take none. Seeding each call separately is a different and equally valid RNG boundary, but it is not this equivalence and will select different units. The equivalence is a tested invariant rather than an incidental property. The continued sample is therefore the one the single call would have drawn.
Two-phase sampling. A new phase starts with a new
design: execute(design2, phase1_sample). Here the phase-1
tbl_sample is the frame argument, not the execution object.
samplyr records a previous-phase link and compounds the phase-1 and
phase-2 weights multiplicatively. The conditional phase-2 weight is
computed at survey export time. Thus
execute(psu_sample, listing_frame) means “continue this
design’s stages,” whereas execute(new_design, prior_sample)
means “sample a new phase.”
Weight compounding across stages
Single-stage. Let \(q_i\) be the stage selection quantity. For WOR, \(q_i = \pi_i\). For WR or PMR, \(q_i = E(K_i)\), the expected hit count. The design weight is:
\[ w_i = \frac{1}{q_i}. \]
Multi-stage. Let \(q_i^{(k)}\) denote the quantity resolved within the clusters selected at prior stages. The compound weight is:
\[ w_i = \prod_{k=1}^{K} \frac{1}{q_i^{(k)}} = \prod_{k=1}^{K} w_i^{(k)}. \]
For an all-WOR exact design this is the inverse product of
conditional inclusion probabilities. With a WR or PMR stage, each output
row is a realized occurrence and that stage contributes an inverse
expected-hit factor. samplyr joins each stage’s weight to
its parent by the shared cluster variables and multiplies. The per-stage
weights remain in .weight_1, .weight_2, and so
on. The .weight column always equals their product.
two_stage <- sampling_design() |>
add_stage("Districts") |>
stratify_by(province) |>
cluster_by(district) |>
draw(n = 2, method = "srswor") |>
add_stage("EAs") |>
draw(n = 5, method = "srswor") |>
execute(zwe_eas, seed = 123456)
#> Warning: Stage 1: sample size exceeded the pool population in 1 of 10 pools.
#> ✖ Requested 20 units, selected 19.
#> ℹ Capped pools: "Bulawayo".
#> ℹ Inspect with `frame_summary(sample, detail = "pool")` and the capped column.
# Compound weight equals the product of per-stage weights
all.equal(two_stage$.weight,
two_stage$.weight_1 * two_stage$.weight_2)
#> [1] TRUEThe warning is the design meeting the frame. Bulawayo is a
single-district province, so a take of two districts selects the one
that exists. execute() reports every pool it could not
fill, once per stage, because a capped pool makes the design
non-self-weighting. The weights above still compound exactly and
Bulawayo’s first-stage weight is simply 1.
Two-phase. When a new phase-2 design is executed
with the phase-1 tbl_sample as its frame, the phase-1
weight is stored internally and multiplied into the final
.weight. The conditional phase-2 weight \(w_{\text{cond}} = w_{\text{overall}} /
w_{\text{phase 1}}\) is computed during survey export for use
with survey::twophase().
Finite population corrections
samplyr stores the FPC as a population count in
.fpc_k. The interpretation depends on the method, and the
conversion for survey export happens automatically in
as_svydesign():
| Method class |
.fpc_k stores |
Passed to survey as | survey interpretation |
|---|---|---|---|
| EP-WOR (srswor, systematic) | \(N_h\) (stage units) | \(N_h\) directly | Sampling fraction \(f_h = n_h / N_h\) |
| PPS-WOR (pps_brewer, …) | \(N_h\) (stage units) | \(1 / w_k = \pi_i\) | Inclusion probability |
| WR / PMR (srswr, pps_chromy, …) | \(\infty\) (synthetic) | \(\infty\) | No FPC (Hansen-Hurwitz variance) |
The PPS conversion merits explanation.
survey::svydesign() interprets FPC values less than 1 as
inclusion probabilities and values \(\geq
1\) as population sizes. samplyr stores \(N_h\) for every without-replacement method,
then at export time, as_svydesign() creates a per-row
column .fpc_pi_k = 1 / .weight_k = \pi_i for PPS-WOR
stages. The transformation is lossless: the per-stage weight already
encodes \(\pi_i\), so nothing is
approximated in converting it. What is approximate for
pps_sps and pps_pareto is the quantity being
transformed, since their \(\pi_i\) is a
target rather than the realized inclusion probability.
For WR and PMR methods, a synthetic column filled with
Inf is created. The survey package interprets this as “no
finite population correction,” which gives the Hansen-Hurwitz variance
estimator.
The table describes one represented stage, or several without a
PPS-WOR stage. survey::svydesign() needs every FPC term on
the same scale, so a multi-stage design with a PPS-WOR stage passes
fractions instead: \(1 / w_k\) for
every without-replacement stage and 0 for WR and PMR stages.
?as_svydesign gives both encodings.
The population the correction refers to.
samplyr treats the frame as executed as the population, so
\(N_h\) is counted from the frame the
stage drew from. That is one convention among several. Where the
sampling fraction is not negligible and the population itself changes
between occasions, the finite population correction matters more and
admits several competing definitions, particularly for measures of
change across coordinated or overlapping samples (Tam 1984; Laniel 1988; Nordberg 2000; Berger 2004).
samplyr does not choose among them for you: it records the
frame size it saw, and a different convention has to be imposed
downstream.
Independence across stages
samplyr assumes that the stage-\(k\) selection mechanism depends on stage
\(k - 1\) only through the
set of selected clusters, not through the specific
randomization that produced it. Formally:
\[ \Pr(S^{(k)} \mid S^{(k-1)}) \text{ depends on } S^{(k-1)} \text{ as a set.} \]
This is the standard assumption in multi-stage sampling theory (Cochran 1977, ch. 11; Särndal, Swensson, and Wretman 1992, ch. 4.3).
Consequence for variance estimation. Under this
assumption, the recursive multi-stage variance formula applies (Särndal, Swensson, and
Wretman 1992, ch. 4.3). For methods with a supported
linearization estimator, as_svydesign() exports every
executed stage to survey::svydesign(): one ids
term, one fpc term, and (when the stage is stratified) one
strata term per stage. A first-stage census, for example,
correctly attributes all variance to the later stages.
A stage that samples with replacement is handled differently: later stages nested in it contribute nothing beyond the between-draw variability, which the Hansen-Hurwitz estimator already captures.
Bounded cube, LPM2, and SCPS do not currently have supported
linearization estimators. Their variance needs
as_svrepdesign(type = "subbootstrap") or
"mrbbootstrap", which ?variance_estimation
describes.
Weights for a population other than the one sampled
Two transformations of an executed sample produce a weight for units that were never in the frame. Neither changes the draw.
share_weights() is the generalized weight share method
(Lavallée
2007). A sample is selected from population \(U^A\), and estimates are wanted for a
second population \(U^B\) linked to it:
children through their parents, establishments through their
enterprises, persons through their dwellings. Every target unit in a
reached cluster \(i\) receives
\[ w_i = \sum_j \frac{I(j \in S^A)}{\pi_j} \frac{L_{ji}}{L_i}, \]
the source weights carried across the links and divided by the
population number of links \(L_i\) to that cluster. \(\pi_j\) is needed only for selected source
units, which .weight already holds. \(L_i\) sums over the whole of \(U^A\), so counting a supplied link table
asserts that the table is a complete population register. That assertion
has no default: either name a column holding the population
multiplicity, or state it with complete_links().
?share_weights gives the target-cluster choices and
weighted links.
stack_frames() collects samples selected independently
from two or more registers of the same population, each incomplete. The
multiplicity estimator for overlapping frames is the weight share method
with the standardized link matrix, since the number of links to a unit
is the number of frames containing it (Lohr 2021, sec. 3.2; Mecatti 2007). A stack records which
frames each sampled unit belongs to and leaves the compositing factor to
the export, because the appropriate factor depends on the estimand. A
stack does not assert that the frames together cover the target
population.
The weight contract
A shared weight is an estimation weight and is not generally the reciprocal of a target unit’s inclusion probability. The design the object carries describes selection of the source population. The result records which kind of weight it holds, and every statistical consumer states what it does about that record rather than treating the sample as ordinary.
| Consumer | On a shared weight |
|---|---|
design_effect(), effective_n()
|
Supported. They read .weight and nothing else |
as_svrepdesign() |
Supported. It replicates the source design and shares inside every replicate |
| The data-manipulation methods | Preserve the record |
as_svydesign() |
Supported. It exports one row per source-target contribution |
joint_expectation(), varcomp()
|
Refuse |
A further execute(), and the panel verbs |
Refuse |
Each refusal has its own condition class and all of them are also
catchable as samplyr_error_weight_contract. Neither
as_tbl_sample() nor an ordinary dplyr reconstruction can
launder an estimation weight back into a design-weight sample.
A target cluster with no link to the source population can never be
surveyed, and the estimator understates totals by exactly its share.
share_weights() records that finding without warning, and
the warning fires where the estimate is formed. For a
stack_frames() collection it is evaluated over the union of
the frames and names only the clusters no frame reaches.
What is lost at survey export
as_svydesign() translates a tbl_sample into
a survey::svydesign() object. The translation is faithful
for standard variance estimators, but some samplyr metadata
is not representable in the survey framework.
1. Element sampling before later stages
An unclustered element-sampling stage followed by further stages is
not nested cluster sampling: the later selections operate on the
realized element sample, not within larger units, so there is no
hierarchy for the multi-stage variance recursion (or for
replicate-weight resampling) to use. This is phase sampling.
execute() refuses this shape
(samplyr_error_stage_parent_id), and the supported path is
a two-phase sample: execute the element stage under its first-phase
design, execute a new second-phase design with that sample as its frame,
and export with as_svydesign(), which uses
survey::twophase(). A final unclustered stage is
fully supported: it is exported with a synthesized row-identity
ids term and its own FPC.
2. Variance by selection method
How each selection method is linearized, what a joint matrix from
joint_expectation() changes, how with-replacement and
Chromy stages are represented, and how certainty units form a take-all
stratum are in ?variance_estimation, with the measured
error of the approximations that were measured.
3. Panel assignments
The .panel column is not passed to survey. Panels are
randomized fixed-quota partitions for rotation or workload distribution.
The full-sample weights in .weight are correct for the
combined sample. Panel assignment is not an additional
probability-sampling phase, so a single panel does not automatically
have inclusion probability \(\pi_i /
k\). Conditional on the frozen quotas, its probability is the
block’s realized quota over the block size. Multiplying one panel’s
weights by \(k\) is therefore not
generally valid for population inference.
4. Design metadata
samplyr stores the full sampling_design
object, stage labels, seed, and execution history as attributes of the
tbl_sample. These are accessible via
get_design() and get_stages_executed() but are
not round-tripped through the survey export.
Panel partitioning semantics
There are two distinct operations here and they have different
statistical status. Partitioning
(panels = k with no schedule) splits one execution into
\(k\) rotation groups by randomized
fixed quota inside ordered blocks. It changes no inclusion probability
and no .weight, so the combined sample is analyzed with the
stored weights. Wave activation (a scheduled
panels plus execute(wave = t)) selects a
subset of those groups, and that is a second sampling phase.
Its activation probability is the block’s frozen quota for the active
panels over the block size, and the weight is multiplied by the inverse:
the block size over that quota. For a block of eight units with four
activated, the factor is 2. It is exact and generally not the nominal
\(k / r\) for \(r\) active panels of \(k\), and it is why the block sizes and
quotas are recorded with the sample.
Neither operation replenishes the sample. A program that draws
refreshment cohorts against later frame vintages is a registry of
separate executions, not a partition of one. See
rotation_program(). ?panel_assignment gives
the assignment rules, the choice of assignment stage and what it costs,
certainty units, schedules and small pools.
PRN and sample coordination semantics
samplyr passes permanent random numbers through to
sondage without transformation. PRN-compatible methods
(bernoulli, pps_poisson, pps_sps,
pps_pareto, scps) produce a deterministic
selection given the PRN values, and for scps the visiting
order. When PRN is supplied, the seed argument to
execute() does not affect selection.
Positive and negative coordination across survey waves is a
user-level workflow, not a package feature. samplyr passes
PRNs to the selection method, the user manages their persistence and
update. The negative-coordination update is \(u_{\text{new}} = (u - \pi) \bmod 1\) (Ohlsson
1995).