Skip to contents

A 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.58840

The 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 pass

A 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 pass

Public 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      TRUE

Changing 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.