Stratification divides the population before sampling. An allocation then decides how many units to select from each stratum. A domain is different. It is a population for which an estimate is required, and it can contain one or more strata.
This article covers one-indicator allocation. See Multiple indicators and domains for requirements that combine into one total, and Joint allocation when one allocation must satisfy several estimands and domains simultaneously.
Allocate a fixed sample
The frame has one row per stratum. N is the population
size, sd is the anticipated within-stratum standard
deviation, and mean supplies the scale for a CV
calculation:
frame <- data.frame(
stratum = c("Urban", "Peri-urban", "Rural"),
N = c(4000, 3000, 3000),
sd = c(10, 15, 8),
mean = c(50, 60, 55)
)
allocation <- n_alloc(frame, n = 600, alloc = "neyman")
allocation
#> Stratum allocation (neyman, 3 strata)
#> field design: n = 600, cv = 0.0079, cost = 600
#> continuous optimum: n = 600, cv = 0.0079, se = 0.4305
#> (deff = 1)
#> design df = 597Neyman allocation places more sample where N_hS_h is larger. The result keeps a
continuous optimum in $detail$n and a whole-unit fieldwork
proposal in $detail$n_int:
allocation$detail[, c("stratum", "N", "n", "n_int", "weight")]
#> stratum N n n_int weight
#> 1 Urban 4000 220.1835 220 18.16667
#> 2 Peri-urban 3000 247.7064 248 12.11111
#> 3 Rural 3000 132.1101 132 22.70833Use the operational column when issuing a sample. Its planned
weight is the population-to-sample ratio for the
allocation, not a final analysis weight adjusted for realized selection
and response.
Check an operational allocation
prec_alloc() evaluates any proposed set of stratum
sample sizes. Passing the planning result evaluates its continuous
design. Supply the integer column to check the whole-unit fieldwork
design:
prec_alloc(allocation)
#> Sampling precision for alloc
#> n = 600 (3 strata)
#> se = 0.4305, moe = 0.8438, cv = 0.0079, rmoe = 0.0155
prec_alloc(allocation, n = allocation$detail$n_int)
#> Sampling precision for alloc
#> n = 600 (3 strata)
#> se = 0.4305, moe = 0.8438, cv = 0.0079, rmoe = 0.0155The detail table shows how much each stratum contributes to overall
variance. .share sums to 1:
assessment <- prec_alloc(allocation, n = allocation$detail$n_int)
assessment$detail[, c("stratum", "n", ".se", ".cv", ".share")]
#> stratum n .se .cv .share
#> 1 Urban 220 0.6553972 0.01310794 0.3708035
#> 2 Peri-urban 248 0.9122818 0.01520470 0.4041246
#> 3 Rural 132 0.6808195 0.01237854 0.2250719Solve for precision or budget
Instead of fixing total n, ask for a target CV:
n_alloc(frame, cv = 0.04, alloc = "neyman")
#> Stratum allocation (neyman, 3 strata)
#> field design: n = 27, cv = 0.0384, cost = 27
#> continuous optimum: n = 24.93353, cv = 0.0400, se = 2.1800
#> (deff = 1)
#> design df = 24For budget allocation, add a cost per sampled unit and use the optimal cost-adjusted rule:
cost_frame <- transform(frame, unit_cost = c(1, 1.5, 2.5))
n_alloc(cost_frame, budget = 900, alloc = "optimal")
#> Stratum allocation (optimal, 3 strata)
#> field design: n = 621, cv = 0.0079, cost = 900
#> continuous optimum: n = 621.7392, cv = 0.0079, se = 0.4290
#> (deff = 1)
#> design df = 618The three common rules answer different questions:
| Rule | Relative allocation | Use |
|---|---|---|
"proportional" |
N_h | Equal sampling fractions. |
"neyman" |
N_hS_h | Minimize variance with equal unit costs. |
"optimal" |
N_hS_h/\sqrt{c_h} | Minimize variance with unequal unit costs. |
alloc = "power" implements Bankier allocation (Bankier 1988),
with weights proportional to the population CV
sd / abs(mean) times alloc_measure^alloc_q.
The allocation measure defaults to N * abs(mean), and with
that default alloc_q = 1 gives Neyman allocation. At
alloc_q = 0 the weights are the population CVs whatever the
measure. Means must be nonzero. The population CV describes variation
among units and differs from the cv precision target, which
describes the relative standard error of an estimate.
Supply a positive alloc_measure column to use another
stratum measure:
bankier_frame <- transform(frame, alloc_measure = N)
n_alloc(bankier_frame, n = 600, alloc = "power", alloc_q = 0.5)
#> Stratum allocation (power, 3 strata)
#> field design: n = 600, cv = 0.0079, cost = 600
#> continuous optimum: n = 600, cv = 0.0079, se = 0.4308
#> (deff = 1)
#> design df = 597With a custom measure, alloc_q = 1 need not give Neyman
allocation. Stratum-specific response rates and design effects multiply
the weights by sqrt(deff / resp_rate). Bankier allocation
does not generally equalize domain CVs. Use explicit domain targets when
comparable precision is required.
Add operational constraints
Minimum stratum sizes, maximum planned weights, and census strata can
be declared with min_n_stratum, max_weight,
and take_all:
constrained_frame <- transform(
frame,
unit_cost = c(1, 1.5, 1),
max_weight = c(25, 20, NA),
take_all = c(FALSE, FALSE, TRUE)
)
constrained <- n_alloc(
constrained_frame,
budget = 3500,
alloc = "optimal",
min_n_stratum = 40
)
constrained
#> Stratum allocation (optimal, 3 strata)
#> field design: n = 3403, cv = 0.0076, cost = 3500
#> continuous optimum: n = 3403.425, cv = 0.0076, se = 0.4125
#> (min_n_stratum = 40, deff = 1)
#> design df = 401The constraints change the optimum, so assess the returned operational design as a unit. Independently rounding continuous stratum values can violate a budget or precision requirement.
Fieldwork assumptions by stratum
deff and resp_rate can be scalar values or
frame columns. Stratum-specific response rates change both the number
issued and the best allocation:
response_frame <- transform(
frame,
resp_rate = c(0.92, 0.80, 0.65)
)
response_plan <- n_alloc(response_frame, n = 600, alloc = "neyman")
response_plan$detail[, c("stratum", "n", "n_int")]
#> stratum n n_int
#> 1 Urban 205.4620 205
#> 2 Peri-urban 247.8752 248
#> 3 Rural 146.6628 147The response adjustment sets an expected responding count. It does not remove nonresponse bias. Use two-phase planning when a subsample of nonrespondents will receive additional follow-up.
Require precision for domains
When each domain is a union of complete strata, domains
requests a target for every domain:
domain_frame <- data.frame(
province = c("North", "North", "South", "South"),
stratum = c("Urban", "Rural", "Urban", "Rural"),
N = c(2000, 3000, 1800, 3200),
sd = c(12, 18, 10, 16),
mean = c(55, 48, 58, 50)
)
domain_plan <- n_alloc(
domain_frame,
domains = "province",
cv = 0.04,
alloc = "power",
alloc_q = 0.3
)
domain_plan
#> Stratum allocation (power, 4 strata)
#> field design: n = 110, cv = 0.0271, cost = 110
#> continuous optimum: n = 108.2371, cv = 0.0273, se = 1.4160
#> (deff = 1)
#> design df = 106
#> Domains: 2
#>
#> province .domain .n .se .moe .rmoe .cv .cost
#> North 5_North 58.67031 2.03200 3.982647 0.07839856 0.0400 59
#> South 5_South 49.56679 1.97265 3.866324 0.07311505 0.0373 50
domain_plan$domains
#> province .domain .n .se .moe .rmoe .cv .cost
#> 1 North 5_North 58.67031 2.03200 3.982647 0.07839856 0.04000000 58.67031
#> 2 South 5_South 49.56679 1.97265 3.866324 0.07311505 0.03730428 49.56679These domains are known on the frame. For a domain that appears naturally within one sample and is not identified on the frame, size the sample from the domain’s expected share, as described in Multiple indicators and domains. That calculation applies to single-stage indicator tables only, because a population share alone does not describe how domain members spread across clusters.
Construct candidate strata
When a continuous auxiliary variable is available on the frame,
strata_bound() constructs candidate boundaries. The
objective is the precision of the auxiliary variable itself, so its
usefulness for a study variable depends on how closely the two are
related.
set.seed(12345)
x <- rlnorm(5000, meanlog = 6, sdlog = 1.2)
boundaries <- strata_bound(
x,
n_strata = 4,
cv = 0.05,
method = "cumrootf"
)
boundaries
#> Strata boundaries (Dalenius-Hodges, 4 strata)
#> n = 63, cv = 0.0491, allocation: neyman
#>
#> stratum lower upper N share sd mean n
#> 1 3.8267 400 2473 0.495 105.3 191.4 10
#> 2 400 1200 1647 0.329 222.7 700.6 14
#> 3 1200 3100 672 0.134 519.5 1871.7 14
#> 4 3100 21958 208 0.042 3094.3 5543.6 25Four methods are available:
| Method | Character |
|---|---|
"cumrootf" |
Fast Dalenius–Hodges cumulative root frequency rule. |
"geo" |
Fast geometric progression, useful for skewed values. |
"lh" |
LH-inspired coordinate search. |
"kozak" |
Kozak random search, exact by enumeration on small problems. |
The last two are heuristics. Their results are candidates rather than certified global optima.
The Kozak search starts from more points than the LH search, so it costs more run time, but more starting points do not guarantee a better partition. At a fixed sample size, compare the CV each method reaches on the frame at hand:
set.seed(2024)
kozak_boundaries <- strata_bound(x, n_strata = 4, n = 300, method = "kozak")
kozak_boundaries
#> Strata boundaries (Kozak random search, 4 strata, converged)
#> n = 300, cv = 0.0195, allocation: neyman
#>
#> stratum lower upper N share sd mean n
#> 1 3.8267 490.12 2835 0.567 129.7 223.6 70
#> 2 490.12 1356.2 1406 0.281 236.4 816.2 63
#> 3 1356.2 3571.6 596 0.119 579.7 2101.8 66
#> 4 3571.6 21958 163 0.033 3234.5 6160.1 101
method_cv <- c(
sapply(c("cumrootf", "geo", "lh"), function(method) {
strata_bound(x, n_strata = 4, n = 300, method = method)$cv
}),
kozak = kozak_boundaries$cv
)
round(method_cv, 4)
#> cumrootf geo lh kozak
#> 0.0199 0.0320 0.0195 0.0195On this variable the LH and Kozak searches reach the same smallest CV, the cumulative root frequency rule comes close, and the geometric rule falls well behind.
The search allocates each candidate partition by Neyman allocation
unless alloc says otherwise. With
alloc = "power", it uses the Bankier rule (Bankier
1988)
n_h \propto \frac{S_h}{|\bar{X}_h|}\left(N_h|\bar{X}_h|\right)^q,
where S_h and \bar{X}_h are the standard deviation and mean
of x in stratum h, N_h is the stratum population, and q is alloc_q. The population CV
S_h/|\bar{X}_h| and the measure N_h|\bar{X}_h| are recomputed for every
candidate partition, so the allocation rule shapes the cut points:
strata_bound(x, n_strata = 4, n = 300, alloc = "power", alloc_q = 0.5)
#> Strata boundaries (LH-inspired coordinate search, 4 strata, converged)
#> n = 300, cv = 0.0197, allocation: power (alloc_q = 0.50)
#>
#> stratum lower upper N share sd mean n
#> 1 3.8267 510.33 2893 0.579 134.2 229.1 89
#> 2 510.33 1378.7 1361 0.272 236.8 834.9 57
#> 3 1378.7 3571.6 583 0.117 575.6 2118.1 56
#> 4 3571.6 21958 163 0.033 3234.5 6160.1 98At alloc_q = 1 the rule is Neyman allocation. At
alloc_q = 0 the sample is proportional to the stratum
population CVs. The rule does not generally equalize stratum CVs.
Because it divides by each stratum mean, x should be a
positive size measure. To use an allocation measure other than N_h|\bar{X}_h|, construct the strata first
and then add an alloc_measure column to the frame passed to
n_alloc().
predict() applies the learned boundaries to new
observations:
predict(
boundaries,
newdata = c(100, 500, 1500, 5000),
labels = paste0("S", 1:4)
)
#> [1] S1 S2 S3 S4
#> Levels: S1 S2 S3 S4The strata table connects directly to n_alloc():
allocated_boundaries <- n_alloc(boundaries$strata, cv = 0.05)
allocated_boundaries
#> Stratum allocation (neyman, 4 strata)
#> field design: n = 63, cv = 0.0491, cost = 63
#> continuous optimum: n = 60.95014, cv = 0.0500, se = 40.3814
#> (deff = 1)
#> design df = 59Both functions use the same variance calculation. Design assumptions
carry through both, passed directly or through a svyplan()
profile:
plan <- svyplan(deff = 1.8, resp_rate = 0.85)
planned_boundaries <- strata_bound(x, n_strata = 4, cv = 0.05, plan = plan)
c(
strata_bound = planned_boundaries$n,
n_alloc = n_alloc(planned_boundaries$strata, cv = 0.05, plan = plan)$n
)
#> strata_bound n_alloc
#> 118.0000 116.4124The difference between the two totals is whole-unit rounding, not a
change of model. strata_bound() reports the whole-unit
allocation in $strata$n, whereas n_alloc()
reports the continuous optimum.
A scalar deff does not move the cut points. At a fixed
sample size it multiplies the variance of every candidate partition by
the same factor, so the same partition remains best and only the
reported CV changes:
rbind(
deff_1.0 = strata_bound(x, n_strata = 4, n = 300)$boundaries,
deff_1.8 = strata_bound(x, n_strata = 4, n = 300, deff = 1.8)$boundaries
)
#> [,1] [,2] [,3]
#> deff_1.0 490.1171 1356.216 3571.62
#> deff_1.8 490.1171 1356.216 3571.62Under a cv target, what deff changes is the
n the target requires.
Compare the stratum sampling fractions before deciding whether the proposed boundaries create workable fieldwork loads:
plot(boundaries)
Sampling fractions under the candidate four-stratum design. Compare each stratum with the dashed overall fraction to identify where sampling is most intensive.
More design workflows
Multi-indicator surveys
Multiple indicators and domains explains how indicator requirements combine, including separate quotas and naturally occurring domains.
Joint indicator and domain targets
Joint allocation shows how to satisfy several indicator and overlapping domain requirements with one allocation.
Stratified two-stage designs
The stratified-cluster
workflow estimates components within strata, constructs the
n_alloc() frame, and checks the whole-unit design and PSU
availability bounds.
Two-phase designs
Two-phase planning covers screening, phase-specific response, operational precision, assurance, and nonresponse follow-up.
The allocation formulas and distinctions used here follow standard survey-sampling treatments (Cochran 1977; Valliant, Dever, and Kreuter 2018).