Select a Three-Stage Household Sample
Source:vignettes/three-stage-sampling.Rmd
three-stage-sampling.RmdThe selection task
Select communes within regions, enumeration areas (EAs) within those communes, and households within those EAs. The first two stages use PPS without replacement and the last uses simple random sampling without replacement. Selection pauses while the household listing is prepared, then continues under the same design.
We use bfa_eas, the package’s derived Burkina Faso
frame. Its modeled EAs, household counts and historical administrative
labels are teaching inputs. They are not a current official household
register. Household listings and interview outcomes below are explicitly
synthetic.
Give each register its own sampling unit and MOS
library(dplyr)
ea_register <- bfa_eas |>
mutate(
region,
commune_id = paste(region, province, commune, sep = "/"),
ea_id,
ea_households = households,
.keep = "none"
)
commune_register <- ea_register |>
summarise(commune_households = sum(ea_households),
.by = c(region, commune_id))
head(commune_register, 3)
#> # A tibble: 3 × 3
#> region commune_id commune_households
#> <fct> <chr> <int>
#> 1 Boucle du Mouhoun Boucle du Mouhoun/Bale/Bagassi 5089
#> 2 Boucle du Mouhoun Boucle du Mouhoun/Bale/Bana 2586
#> 3 Boucle du Mouhoun Boucle du Mouhoun/Bale/Boromo 5857
head(ea_register, 3)
#> # A tibble: 3 × 4
#> ea_id region commune_id ea_households
#> <int> <fct> <chr> <int>
#> 1 11759 Boucle du Mouhoun Boucle du Mouhoun/Bale/Bagassi 7
#> 2 11760 Boucle du Mouhoun Boucle du Mouhoun/Bale/Bagassi 25
#> 3 11761 Boucle du Mouhoun Boucle du Mouhoun/Bale/Bagassi 8| Register | One row represents | Key | Measure of size |
|---|---|---|---|
commune_register |
A commune |
commune_id, qualified by its geography |
Sum of EA household counts in that commune |
ea_register |
An EA |
ea_id, with its parent commune_id
|
That EA’s modeled household count |
household_listing below |
A household in a selected EA |
ea_id and local hh_id
|
None: equal-probability sampling |
MOS belongs to the unit being selected. A commune’s total must not
vary across its EA rows if using one shared hierarchy. Separate
registers make the two levels explicit. Their order in
execute() determines their stage mapping. Names on a list
are labels, not a join specification.
Declare all three stages
library(samplyr)
household_design <- sampling_design("Three-stage household survey") |>
add_stage("Communes") |>
stratify_by(region) |>
cluster_by(commune_id) |>
draw(n = 4, method = "pps_brewer", mos = commune_households) |>
add_stage("EAs") |>
cluster_by(ea_id) |>
draw(n = 3, method = "pps_brewer", mos = ea_households) |>
add_stage("Households") |>
draw(n = 12)
household_design
#> ── Sampling Design: Three-stage household survey ───────────────────────────────
#>
#> ℹ 3 stages
#>
#> ── Stage 1: Communes ───────────────────────────────────────────────────────────
#> • Strata: region
#> • Cluster: commune_id
#> • Draw: n = 4 (per stratum), method = pps_brewer, mos = commune_households
#>
#> ── Stage 2: EAs ────────────────────────────────────────────────────────────────
#> • Cluster: ea_id
#> • Draw: n = 3, method = pps_brewer, mos = ea_households
#>
#> ── Stage 3: Households ─────────────────────────────────────────────────────────
#> • Draw: n = 12, method = srsworThe scopes are four communes per region, three EAs
per selected commune, and twelve households per
selected EA. There is no allocation rule at the first stage, so
n = 4 is per stratum. A pool smaller than its requested
take is a census, which execution reports. Region stratification does
not implicitly become a new stratum specification at later stages.
Select areas, then pause for listing
selected_eas <- execute(
household_design,
list(commune_register, ea_register),
stages = 1:2,
seed = 91
)
selected_eas |>
as.data.frame() |>
count(region, name = "selected_eas")
#> region selected_eas
#> 1 Boucle du Mouhoun 12
#> 2 Cascades 12
#> 3 Centre 12
#> 4 Centre-Est 12
#> 5 Centre-Nord 12
#> 6 Centre-Ouest 12
#> 7 Centre-Sud 12
#> 8 Est 12
#> 9 Hauts-Bassins 12
#> 10 Nord 12
#> 11 Plateau-Central 12
#> 12 Sahel 12
#> 13 Sud-Ouest 12The EA register covers all communes and execution restricts it to the selected parents. The retained object contains the two completed stages and their weights. It is the starting point for continuation.
In fieldwork, a listing team enumerates households in the selected EAs. Here we create artificial listings whose lengths equal the modeled counts. This creates identifiers, not observed household records, and expands only the selected EAs.
library(tidyr)
household_listing <- selected_eas |>
as.data.frame() |>
select(commune_id, ea_id, ea_households) |>
uncount(ea_households, .id = "hh_id")
stopifnot(!anyDuplicated(household_listing[c("ea_id", "hh_id")]))
head(household_listing, 3)
#> commune_id ea_id hh_id
#> 1 Boucle du Mouhoun/Kossi/Dokui 41769 1
#> 2 Boucle du Mouhoun/Kossi/Dokui 41769 2
#> 3 Boucle du Mouhoun/Kossi/Dokui 41769 3Converting to a plain data frame is intentional here: a listing enumerates candidates for the next stage. It is not an expanded final sample for analysis. The parent EA disambiguates local household IDs that repeat across EAs.
issued <- execute(selected_eas, household_listing, seed = 92)
#> Warning: Stage 3: sample size exceeded the pool population in 3 of 156 pools.
#> ✖ Requested 1872 units, selected 1859.
#> ℹ Capped pools: "Boucle du Mouhoun/Kossi/Nouna/33237",
#> "Est/Tapoa/Kantchari/31552", and "Sahel/Oudalan/Tin-Akoff/34718".
#> ℹ Inspect with `frame_summary(sample, detail = "pool")` and the capped column.
issued |>
as.data.frame() |>
select(commune_id, ea_id, hh_id, .weight_1, .weight_2, .weight_3, .weight) |>
head(4)
#> commune_id ea_id hh_id .weight_1 .weight_2 .weight_3
#> 1 Boucle du Mouhoun/Kossi/Dokui 41769 66 11.85575 24.46053 6.333333
#> 2 Boucle du Mouhoun/Kossi/Dokui 41769 55 11.85575 24.46053 6.333333
#> 3 Boucle du Mouhoun/Kossi/Dokui 41769 10 11.85575 24.46053 6.333333
#> 4 Boucle du Mouhoun/Kossi/Dokui 41769 13 11.85575 24.46053 6.333333
#> .weight
#> 1 1836.653
#> 2 1836.653
#> 3 1836.653
#> 4 1836.653
stopifnot(isTRUE(all.equal(
issued$.weight,
issued$.weight_1 * issued$.weight_2 * issued$.weight_3
)))
frame_summary(issued)
#> # A tibble: 3 × 12
#> stage unit_level scope chance_kind probabilities storage n_pools N
#> <int> <chr> <chr> <chr> <chr> <chr> <int> <dbl>
#> 1 1 cluster universe inclusion_proba… exact units 13 348
#> 2 2 cluster eligible inclusion_proba… exact units 52 12566
#> 3 3 element eligible inclusion_proba… exact consta… 156 17745
#> # ℹ 4 more variables: n_target <dbl>, n_expected <dbl>, n_realized <dbl>,
#> # take_rate <dbl>The final weight is the reciprocal of the product of the three
conditional selection probabilities. Continuation retains the earlier
factors. Starting a new design on selected_eas would
instead declare a new sampling phase. The two seeds above specify
separate reproducible randomization steps. Matching a one-call execution
exactly would require the same uninterrupted RNG stream.
Join observations and hand off to analysis
All issued households respond in this example. The artificial consumption values illustrate a collected outcome that was absent from the sampling frames.
set.seed(93)
interviews <- issued |>
as.data.frame() |>
mutate(ea_id, hh_id,
consumption = round(rlnorm(n(), log(100000), 0.5)),
.keep = "none")
observed <- issued |>
left_join(interviews, by = c("ea_id", "hh_id"), relationship = "one-to-one")
stopifnot(nrow(observed) == nrow(issued))With real missing responses, retain the issued rows and join response
indicators and measurements to them. Sampling weights describe
selection. Removing nonrespondents does not perform response adjustment.
The analyst chooses and justifies that adjustment downstream in
survey or another analysis package.
linearized <- as_svydesign(observed)
survey::svymean(~consumption, linearized)
#> mean SE
#> consumption 113266 1529.6This export uses Brewer’s variance approximation for the PPS stages. Exact first-order probabilities do not make that variance approximation exact. An optional alternative uses svrep’s RWYB replicates, also approximate for PPS without replacement:
set.seed(94)
replicated <- as_svrepdesign(observed, type = "rwyb", replicates = 500)
#> Warning: RWYB uses an approximation for unequal-probability sampling without
#> replacement.
#> ℹ It does not reproduce the sampler's exact joint inclusion probabilities.
survey::svymean(~consumption, replicated)
#> mean SE
#> consumption 113266 1485.8The warning describes the PPS approximation. Increasing the number of
replicates reduces simulation error, not error in the variance
approximation. Continue with vignette("survey-analysis")
for outcome joins, domains and other analysis designs. Use
?selection-methods for a compact comparison of selection
and inference support.