Compute the sample size that satisfies precision requirements for multiple survey indicators simultaneously under a simple sampling design. Optional domain columns support separate requirements by subpopulation.
Arguments
- indicators
For the default method: a data frame where each row is one survey indicator you want to measure. For example, a prevalence (proportion) or a population mean. Surveys typically track several indicators simultaneously and the sample must be large enough for the most demanding one.
n_multi()finds that size.See the Details section for the full column reference. At minimum, each row needs:
What to measure:
pfor a proportion (e.g. 0.30 for 30\ population variance. Each row must use exactly one.How precise:
moe(margin of error),rmoe(margin of error relative to the estimand) orcv(coefficient of variation). Each row must specify exactly one.
For
svyplan_precobjects: a precision result fromprec_multi().- ...
Additional arguments passed to methods. Unused arguments are rejected.
- domains
Character vector of column names in
indicatorsto treat as domain variables, orNULL(default) for no domains. All names must exist inindicators. When specified, sizing runs independently for each domain combination.- domain_sampling
How the per-domain requirements combine into one overall size, either
"separate"(default) or"natural". It applies only when domains are present."separate"treats the domains as disjoint quotas drawn independently, the usual case for regional or urban/rural domains in one national survey, and reportsnas their sum. Every quota has to be met, so no smaller total delivers the design."natural"treats them as analytic domains that arise at their own rate inside one sample. It requires asharecolumn giving each domain's expected share of the population, constant within a domain, and reportsnasmax(.n / share). That is an expected yield: a sample of that size delivers each domain's quota on average, not with certainty in any one realized sample.- min_n_domain
Numeric scalar or
NULL(default). Minimum total sample size per domain. It applies only when domains are present. Per-domain sample sizes are floored tomin_n_domain.- prop_method
Proportion CI method, one of
"wald"(default),"wilson","logodds", or"beta". This is passed ton_prop()for proportion rows and ignored for mean rows. An optionalprop_methodcolumn inindicatorsoverrides this default on a per-row basis."wilson","logodds"and"beta"size from an interval half-width, so those rows needmoerather thancv.- resp_rate
Default expected response rate at the ultimate unit, in (0, 1]. Used for rows whose
resp_ratecolumn is absent orNA; a non-missing row value overrides it.- plan
Optional
svyplan()object providing design defaults.
Value
A svyplan_n object. The output class is the same with or without
domains.
Without domains, the object contains:
nThe sample size required by the binding indicator.
detailPer-indicator sample-size results.
bindingName or index of the binding (most demanding) indicator.
indicatorsThe input indicators data frame.
With domains, the object additionally contains:
nThe overall size, read according to
domain_sampling: the sum of the domain quotas under"separate", or the size whose expected yield meets every quota under"natural". It is not the largest domain requirement, which is a different and smaller number.n_domain_maxThe largest single domain requirement, and the one
bindingrefers to.domainsData frame with one row per domain, including domain variables,
.n,.binding, and.shareunder"natural".
Details
Building the indicators data frame
Each row of indicators represents one survey indicator. The two key
decisions per row are:
Type of indicator: is it a proportion (binary variable like "stunted yes/no") or a mean (continuous variable like "household expenditure")? This determines whether you fill the
porvarcolumn.Precision target: do you want an absolute margin of error (
moe, e.g. +/- 5 percentage points), the same margin stated relative to the estimand (rmoe, e.g. 12 percent of the proportion, as MICS and DHS state it), or a relative coefficient of variation (cv, e.g. 10 percent relative error)?
A minimal example for three health indicators:
indicators <- data.frame(
name = c("stunting", "vaccination", "expenditure"),
p = c(0.30, 0.70, NA),
var = c(NA, NA, 2500),
moe = c(0.05, 0.05, 10)
)Rows with p are treated as proportions, whereas rows with var (and
p = NA) are treated as means. You cannot have both p and var non-NA
in the same row.
Column reference
nameIndicator label (optional). If omitted, row numbers are used in output.
pExpected proportion, in (0, 1). Use this for binary indicators such as prevalences or coverage rates. The value is your best prior guess (e.g. from a previous survey or literature). One of
porvarper row.varPopulation variance of a continuous indicator. Use this for means (e.g. income, expenditure, weight). One of
porvarper row.muPopulation mean (finite and non-zero; it may be negative). It is required when
varis used withcvbecause CV = SE / abs(mean).moeMargin of error, the half-width of the confidence interval you want. For proportions, this is on the probability scale (e.g. 0.05 for +/- 5 percentage points). For means, it is in the same units as the variable (e.g. 10 dollars).
rmoeMargin of error relative to the row's estimand, so 0.12 asks for a half-width of 12 percent of
por ofabs(mu). It is converted tomoeon ingestion, so the row needspormu, and it is read under the row's ownprop_method.cvTarget coefficient of variation (relative standard error). For example, 0.10 means the SE should be at most 10\ of the estimate.
alphaSignificance level for the confidence interval (default 0.05, giving a 95 percent CI).
deffDesign effect multiplier (default 1). Set > 1 to inflate the sample size for complex designs (e.g. 1.5 for a cluster design).
NPopulation size (default
Inf). A finite value applies a finite population correction, reducing the required sample size.prop_methodProportion CI method:
"wald"(default),"wilson","logodds", or"beta"."wilson"is recommended for rare proportions (below 0.1 or above 0.9), and"beta"(Korn-Graubard) when the expected number of positive counts is small enough that the interval should stay inside[0, 1]by construction. It is used only for rows withp. Seen_prop()for how to choose.dfDegrees of freedom of the planned variance estimator, typically sampled PSUs minus strata, and available from
design_df(). It switches that row's interval quantile from normal to t, under every method;NA(the default) applies no adjustment. A df is a property of the design rather than of an indicator, so every row of a single-domain table shares one value. The column earns its place when the rows are domains, each covering its own set of strata:design_df(alloc)$domains$.dfgives one value per domain to match in.min_casesMinimum expected number of positive cases the row must yield, a floor on that row's own size, as in
n_prop()(min_cases = ). Proportion rows only;NA(the default) sizes the row on precision alone. The row that ends up largest is still the binding one, whichever constraint raised it. Single-stagen_multi()only: a multistage design sizes stages against a cost and has no single total for a count to raise.unit_relvarUnit relvariance. If omitted, derived automatically from
p(as(1 - p) / p) or fromvar/mu^2.resp_rateExpected response rate at the ultimate unit, in (0, 1]. Default 1 (no adjustment). A value of 0.90 inflates the sample size by
1 / 0.90to compensate for 10 percent non-response. It means the same thing inn_multi_cluster(), which additionally takesresp_rate_psufor whole clusters that cannot be worked andresp_rate_ssufor second-stage units in a three-stage design. Naming a stage the design does not have is an error rather than a column carried along and ignored.
Domain columns are specified via the domains parameter. When domains
are present, sizing runs independently for each domain combination, and
domain_sampling decides how those requirements combine into the one
number n reports. The two readings answer different questions and give
different sizes, so the choice belongs to the design rather than to a
default: separate quotas need every requirement met and therefore their
sum, while natural domains need a sample large enough that the rarest
demanding domain turns up often enough. Neither is the largest single
requirement, which n_domain_max reports separately.
Columns beyond those listed are carried along and ignored, so the table
can keep questionnaire modules, sources, or other bookkeeping. The
exception is a name the rest of the package would lead you to expect
here: mean (this table takes mu, while the n_alloc() frame takes
mean), indicator or label (it takes name), method (it takes
prop_method), and icc or var_ratio (which are per stage here, so
icc_psu and var_ratio_psu). Those are rejected with the name they
should have carried rather than silently ignored. A column named
through domains is exempt, so a domain may be called label or
method.
The dispersion of a continuous indicator may be given as var or as
sd, whichever the source reports, exactly as in n_mean(). They are
different quantities rather than two names for one, so supplying both
in the same table is an error rather than a preference; sd is squared
on the way in and everything downstream reads var.
n_multi() computes sample size per indicator by delegating proportion
rows to n_prop() and mean rows to n_mean(),
then takes the maximum per domain. Use prop_method or a
indicators$prop_method column to choose "wald", "wilson",
"logodds" or "beta" for proportion rows.
References
Cochran, W. G. (1977). Sampling Techniques (3rd ed.). Wiley.
Valliant, R., Dever, J. A., and Kreuter, F. (2018). Practical Tools for Designing and Weighting Survey Samples (2nd ed.). Springer.
See also
n_prop() and n_mean() for single-indicator sizing,
n_alloc() to split a multi-indicator size across strata or domains,
n_multi_cluster() for multistage cluster designs, and prec_multi()
for the inverse.
Other sample size functions:
n_alloc(),
n_change(),
n_cluster(),
n_mean(),
n_multi_cluster(),
n_panel(),
n_pooled(),
n_prop(),
n_twophase()
Examples
# Simple mode: three indicators, take the max
indicators <- data.frame(
name = c("stunting", "vaccination", "anemia"),
p = c(0.30, 0.70, 0.10),
moe = c(0.05, 0.05, 0.03)
)
n_multi(indicators)
#> Multi-indicator sample size
#> n = 385 (binding: anemia)
#>
#> name .n .cv_target .cv_achieved .binding
#> stunting 323 0.08503558 0.07793639
#> vaccination 323 0.03644382 0.03340131
#> anemia 385 0.15306404 0.15306404 *
# MICS/DHS-style: state precision as a relative margin of error
targets_rmoe <- data.frame(
name = c("stunting", "vaccination", "anemia"),
p = c(0.30, 0.70, 0.10),
rmoe = 0.12,
deff = c(2.0, 1.5, 2.5)
)
n_multi(targets_rmoe)
#> Multi-indicator sample size
#> n = 6003 (binding: anemia)
#>
#> name .n .cv_target .cv_achieved .binding
#> stunting 1245 0.06122561 0.02788337
#> vaccination 172 0.06122561 0.01034902
#> anemia 6003 0.06122561 0.06122561 *
# Continuous indicators: 'var' for the dispersion, 'mu' for the mean.
# A CV target needs 'mu', because a relative standard error is relative
# to something; a MOE target does not.
targets_mean <- data.frame(
name = c("expenditure", "hh_size"),
var = c(250000, 4.0),
mu = c(1200, 5.4),
cv = c(0.05, 0.03)
)
n_multi(targets_mean)
#> Multi-indicator sample size
#> n = 153 (binding: hh_size)
#>
#> name .n .cv_target .cv_achieved .binding
#> expenditure 70 0.05 0.03375
#> hh_size 153 0.03 0.03000 *
# Proportions and means in one table, sized to a common CV
targets_both <- data.frame(
name = c("stunting", "expenditure"),
p = c(0.30, NA),
var = c(NA, 250000),
mu = c(NA, 1200),
cv = c(0.08, 0.05)
)
n_multi(targets_both)
#> Multi-indicator sample size
#> n = 365 (binding: stunting)
#>
#> name .n .cv_target .cv_achieved .binding
#> stunting 365 0.08 0.08000000 *
#> expenditure 70 0.05 0.02182179
# Rare proportion: use Wilson globally in simple mode
n_multi(indicators[3, , drop = FALSE], prop_method = "wilson")
#> Multi-indicator sample size
#> n = 388 (binding: anemia)
#>
#> name .n .cv_target .cv_achieved .binding
#> anemia 388 0.1524953 0.1524953 *
# Korn-Graubard for an indicator whose expected count is small, with the
# degrees of freedom of the planned variance estimator. 'df' is read on
# beta rows only, so the other rows carry NA.
targets_rare <- data.frame(
name = c("stunting", "cocaine_use"),
p = c(0.30, 0.02),
moe = c(0.05, 0.01),
prop_method = c("wald", "beta"),
df = c(NA, 25)
)
n_multi(targets_rare)
#> Multi-indicator sample size
#> n = 949 (binding: cocaine_use)
#>
#> name .n .cv_target .cv_achieved .binding
#> stunting 323 0.08503558 0.04960846
#> cocaine_use 949 0.22733454 0.22733454 *
# Per-row proportion methods in a mixed target table
targets_mixed <- data.frame(
name = c("rare_prop", "mean_ind"),
p = c(0.05, NA),
var = c(NA, 100),
moe = c(0.02, 2),
prop_method = c("wilson", NA)
)
n_multi(targets_mixed)
#> Multi-indicator sample size
#> n = 469 (binding: rare_prop)
#>
#> name .n .cv_target .cv_achieved .binding
#> rare_prop 469 0.2014559 0.2014559 *
#> mean_ind 97 NA NA
# Simple mode with domains
targets_dom <- data.frame(
name = rep(c("stunting", "anemia"), each = 2),
p = c(0.30, 0.25, 0.10, 0.15),
moe = c(0.05, 0.05, 0.03, 0.03),
region = rep(c("North", "South"), 2)
)
n_multi(targets_dom, domains = "region")
#> Multi-indicator sample size (2 domains, separate quotas)
#> n = 929 (sum of domain quotas)
#> Largest single domain = 545 (binding: anemia)
#>
#> region .n .binding
#> North 385 anemia
#> South 545 anemia
# Two-stage CV mode
targets_cl <- data.frame(
name = c("stunting", "anemia"),
p = c(0.30, 0.10),
cv = c(0.10, 0.15),
icc_psu = c(0.02, 0.05)
)
n_multi_cluster(targets_cl, stage_cost = c(500, 50))
#> Multi-indicator optimal allocation (2-stage)
#> field design: n_psu = 52 | n_per_psu = 12 -> total n = 624
#> worst cv = 0.1495, cost = 57200 (binding: anemia)
#> continuous optimum: n_psu = 47.5681 | n_per_psu = 13.78405 (cv = 0.1500, cost = 56568)
#>
#> name .n .cv_target .cv_achieved .binding
#> stunting 292.9922 0.10 0.0668
#> anemia 655.6810 0.15 0.1500 *
# Two-stage with MOE (converted to CV internally)
targets_moe <- data.frame(
name = c("stunting", "anemia"),
p = c(0.30, 0.10),
moe = c(0.05, 0.03),
icc_psu = c(0.02, 0.05)
)
n_multi_cluster(targets_moe, stage_cost = c(500, 50))
#> Multi-indicator optimal allocation (2-stage)
#> field design: n_psu = 50 | n_per_psu = 12 -> total n = 600
#> worst cv = 0.1525, cost = 55000 (binding: anemia)
#> continuous optimum: n_psu = 45.68272 | n_per_psu = 13.78405 (cv = 0.1531, cost = 54326)
#>
#> name .n .cv_target .cv_achieved .binding
#> stunting 405.1863 0.08503558 0.0682
#> anemia 629.6929 0.15306404 0.1531 *
# Joint budget allocation across domains
targets_jnt <- data.frame(
name = rep(c("stunting", "anemia"), each = 2),
p = c(0.30, 0.25, 0.10, 0.15),
cv = c(0.10, 0.10, 0.15, 0.15),
icc_psu = c(0.02, 0.03, 0.05, 0.04),
region = rep(c("Urban", "Rural"), 2)
)
n_multi_cluster(
targets_jnt,
stage_cost = c(500, 50),
domains = "region",
budget = 100000,
allocation = "joint"
)
#> Multi-indicator optimal allocation (2-stage, 2 domains, joint)
#> Total n = 1232 (unrounded: 1209)
#>
#> region n_psu n_per_psu .total_n .cv .cost .binding
#> Urban 52 14 728 0.1437 61621 anemia
#> Rural 28 18 504 0.0958 38379 stunting