Joint allocation across indicators and domains
Source:vignettes/articles/joint-allocation.Rmd
joint-allocation.RmdA joint allocation assigns one sample across strata while satisfying several indicator and domain requirements. Use this interface when separate single-indicator allocations would compete for the same fieldwork sample.
The simpler allocation guide introduces strata, costs, and operational rounding. Multiple indicators and domains shows how several requirements determine a total before allocation.
Three input tables
The joint interface separates population structure, anticipated measurements, and publication requirements:
| Table | One row represents | Main contents |
|---|---|---|
frame |
An atomic stratum | Population size, domain labels, costs, and design bounds. |
measures |
One indicator in one stratum | Anticipated proportion or mean and standard deviation. |
targets |
One required estimate | Indicator, domain and level, and precision target. |
For element-level designs, a proportion in measures$p
uses the finite-population variance
N * p * (1 - p) / (N - 1) for its stratum, with
N > 1. This matches the convention in
prec_prop(). Explicit variances and standard deviations are
used as supplied. The multistage designs introduced below retain the
working proportion variance p * (1 - p).
An atomic stratum has one value for every domain classification. Crossing region with residence therefore produces four frame rows:
joint_frame <- data.frame(
stratum = c("NU", "NR", "SU", "SR"),
region = c("North", "North", "South", "South"),
residence = c("Urban", "Rural", "Urban", "Rural"),
N = c(1000, 2000, 1500, 1000),
unit_cost = c(1, 1.2, 1.5, 1)
)
joint_measures <- data.frame(
stratum = rep(joint_frame$stratum, 2),
name = rep(c("vaccination", "income"), each = 4),
p = c(0.50, 0.40, 0.60, 0.30, rep(NA, 4)),
mean = c(rep(NA, 4), 50, 55, 60, 45),
sd = c(rep(NA, 4), 10, 12, 15, 9)
)
joint_targets <- data.frame(
name = c("vaccination", "vaccination", "income"),
domain = c(".overall", "region", "residence"),
level = c(NA, "North", "Urban"),
cv = c(0.05, 0.08, NA),
moe = c(NA, NA, 2)
)The targets request an overall vaccination CV, a separate CV for vaccination in the North, and a margin of error for urban income. The optimizer minimizes variable cost while meeting all three:
joint_fit <- n_alloc(
joint_frame,
measures = joint_measures,
targets = joint_targets,
min_n_stratum = 2
)
joint_fit
#> Joint constrained allocation (Bethel)
#> question: cheapest design meeting every precision target
#> field design: n = 425, cost = 508 (3 targets, all pass)
#> continuous optimum: n = 425.113, cost = 508 (integerizing costs +0.02%)
#> binding: vaccination@.overall:cv (target 0.05, achieved 0.05)
joint_fit$constraints
#> constraint name domain level .metric .target
#> 1 vaccination@.overall:cv vaccination .overall <NA> cv 0.05
#> 2 vaccination@region=North:cv vaccination region North cv 0.08
#> 3 income@residence=Urban:moe income residence Urban moe 2.00
#> .achieved .ratio .residual .tolerance .se .cv
#> 1 0.05000000 1.0000000 2.459282e-10 1e-06 0.02272727 0.05000000
#> 2 0.07030389 0.8787986 -1.212014e-01 1e-06 0.03046502 0.07030389
#> 3 1.85266616 0.9263331 -7.366692e-02 1e-06 0.94525521 0.01687956
#> .moe .rmoe .pass .binding .multiplier .sensitivity
#> 1 0.04454464 0.09799820 TRUE TRUE 0.03001964 -18762.27
#> 2 0.05971034 0.13779309 TRUE FALSE 0.00000000 0.00
#> 3 1.85266616 0.03308332 TRUE FALSE 0.00000000 0.00$constraints shows the target and achieved precision for
every requirement. Requirements closest to their limits determine the
allocation. The remaining requirements can be better than requested
because they share the same sample.
Continuous and fieldwork allocations
The continuous optimum and the whole-unit recommendation are both retained:
joint_fit$detail[, c("stratum", "n", "n_int", "weight")]
#> stratum n n_int weight
#> 1 NU 86.67423 87 11.53746
#> 2 NR 155.00879 156 12.90249
#> 3 SU 103.99173 104 14.42422
#> 4 SR 79.43824 78 12.58840The integer allocation is constructed to remain feasible. Check both designs directly:
prec_alloc(joint_fit)
#> Joint allocation precision (3 constraints)
#> targets: all pass
#> showing 1 binding of 3
#>
#> constraint .metric .target .achieved .pass
#> vaccination@.overall:cv cv 0.05 0.05 TRUE
#>
#> ... see $detail for all rows
prec_alloc(joint_fit, n = joint_fit$detail$n_int)
#> Joint allocation precision (3 constraints)
#> targets: all passA modified named allocation can be passed in any row order because
names are matched to frame$stratum. When comparing a
simulation with the plan, compare the empirical precision with
$constraints$.achieved, not only with the requested target,
because integerization can make the fieldwork design slightly more
precise.
Fixed-take multistage allocation
The same interface supports two-stage designs when the number of ultimate units sampled per primary sampling unit (PSU) is fixed. The frame holds PSU populations, takes, and stage costs. The measures table holds the indicator-specific PSU homogeneity. Only the number of PSUs is optimized.
cluster_frame <- within(joint_frame, {
unit_cost <- NULL
N_psu <- c(100, 160, 120, 90)
n_per_psu <- c(8, 10, 12, 7)
cost_psu <- c(300, 400, 450, 350)
cost_ssu <- c(25, 30, 35, 28)
})
cluster_measures <- transform(
joint_measures,
icc_psu = rep(c(0.03, 0.05, 0.08, 0.04), 2),
var_ratio_psu = 1
)
cluster_fit <- n_alloc(
cluster_frame,
measures = cluster_measures,
targets = joint_targets,
min_n_stratum = 20
)
cluster_fit$detail[, c(
"stratum", "n_psu_int", "n_per_psu", "n_int", "weight"
)]
#> stratum n_psu_int n_per_psu n_int weight
#> 1 NU 14 8 112 9.244275
#> 2 NR 22 10 220 9.121262
#> 3 SU 16 12 192 8.152297
#> 4 SR 12 7 84 11.130697
prec_alloc(cluster_fit, n = cluster_fit$detail$n_int)
#> Joint allocation precision (3 constraints)
#> targets: all passPublic n remains the ultimate-unit count. The
operational identity is n_int = n_psu_int * n_per_psu. A
three-stage frame adds N_ssu, n_per_ssu,
cost_tsu, and the corresponding indicator-specific SSU
homogeneity. take_all is unavailable in fixed-take
multistage mode because a census of PSUs does not imply a census of
ultimate units.
Certainty PSUs from a register
For a two-stage design, a PSU register can replace
N_psu. It has one row per PSU with its stratum and size,
and its stratum totals must match frame$N.
n_alloc() identifies PSUs whose implied
probability-proportional-to-size inclusion probability reaches 1. A
certainty column can mark additional certainty PSUs.
certainty_frame <- cluster_frame
certainty_frame$N_psu <- NULL
psu_register <- do.call(rbind, lapply(seq_len(nrow(certainty_frame)), function(i) {
population <- certainty_frame$N[i]
size <- rep(10, 20)
size[1] <- floor(population / 3)
size[2] <- population - sum(size[-2])
data.frame(stratum = certainty_frame$stratum[i], N = size)
}))
certainty_fit <- n_alloc(
certainty_frame,
measures = cluster_measures,
targets = joint_targets,
psu = psu_register,
min_n_stratum = 20
)
certainty_fit$detail[, c(
"stratum", "n_psu_certain", "n_psu_draw", "n_int"
)]
#> stratum n_psu_certain n_psu_draw n_int
#> 1 NU 2 2 89
#> 2 NR 2 2 169
#> 3 SU 2 2 134
#> 4 SR 2 2 75$psu records each PSU’s classification, threshold, and
source. The operational allocation uses whole takes within certainty
PSUs and whole PSUs in the remainder.
prec_alloc(certainty_fit) reproduces the planned precision
of that fieldwork design.
The allocation uses an anticipated variance model. Homogeneity components estimated under probability-proportional-to-size selection are an approximation when reused in this model, and the approximation becomes more sensitive as PSU sizes become more unequal. Record that modeling choice when using pilot estimates.
Explore controls that remain movable
For a fixed-take multistage result, predict() can
re-solve the stored problem over candidate takes:
predict(cluster_fit, data.frame(n_per_psu = c(6, 10, 14, 20)))
#> n_per_psu n_psu n cost n_psu_int n_int cost_int .feasible
#> 1 6 87.43080 524.5848 49366.72 88 528 49752 TRUE
#> 2 10 60.14504 601.4504 41283.14 60 600 41350 TRUE
#> 3 14 48.25956 675.6338 39003.24 49 686 39614 TRUE
#> 4 20 39.11771 782.3542 38764.28 40 800 39750 TRUEChanging precision targets or the measures table defines a different
planning problem. Edit those inputs and call n_alloc()
again. See ?predict.svyplan for the supported controls and
output columns for each joint-allocation mode.
For a single indicator with independently chosen cluster counts and takes, continue with Planning multistage surveys.