Skip to contents

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.

Usage

n_multi(indicators, ...)

# Default S3 method
n_multi(
  indicators,
  ...,
  domains = NULL,
  domain_sampling = c("separate", "natural"),
  min_n_domain = NULL,
  prop_method = c("wald", "wilson", "logodds", "beta"),
  resp_rate = 1,
  plan = NULL
)

# S3 method for class 'svyplan_prec'
n_multi(indicators, ...)

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: p for 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) or cv (coefficient of variation). Each row must specify exactly one.

For svyplan_prec objects: a precision result from prec_multi().

...

Additional arguments passed to methods. Unused arguments are rejected.

domains

Character vector of column names in indicators to treat as domain variables, or NULL (default) for no domains. All names must exist in indicators. 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 reports n as 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 a share column giving each domain's expected share of the population, constant within a domain, and reports n as max(.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 to min_n_domain.

prop_method

Proportion CI method, one of "wald" (default), "wilson", "logodds", or "beta". This is passed to n_prop() for proportion rows and ignored for mean rows. An optional prop_method column in indicators overrides this default on a per-row basis. "wilson", "logodds" and "beta" size from an interval half-width, so those rows need moe rather than cv.

resp_rate

Default expected response rate at the ultimate unit, in (0, 1]. Used for rows whose resp_rate column is absent or NA; 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:

n

The sample size required by the binding indicator.

detail

Per-indicator sample-size results.

binding

Name or index of the binding (most demanding) indicator.

indicators

The input indicators data frame.

With domains, the object additionally contains:

n

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

The largest single domain requirement, and the one binding refers to.

domains

Data frame with one row per domain, including domain variables, .n, .binding, and .share under "natural".

Details

Building the indicators data frame

Each row of indicators represents one survey indicator. The two key decisions per row are:

  1. 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 p or var column.

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

name

Indicator label (optional). If omitted, row numbers are used in output.

p

Expected 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 p or var per row.

var

Population variance of a continuous indicator. Use this for means (e.g. income, expenditure, weight). One of p or var per row.

mu

Population mean (finite and non-zero; it may be negative). It is required when var is used with cv because CV = SE / abs(mean).

moe

Margin 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).

rmoe

Margin of error relative to the row's estimand, so 0.12 asks for a half-width of 12 percent of p or of abs(mu). It is converted to moe on ingestion, so the row needs p or mu, and it is read under the row's own prop_method.

cv

Target coefficient of variation (relative standard error). For example, 0.10 means the SE should be at most 10\ of the estimate.

alpha

Significance level for the confidence interval (default 0.05, giving a 95 percent CI).

deff

Design effect multiplier (default 1). Set > 1 to inflate the sample size for complex designs (e.g. 1.5 for a cluster design).

N

Population size (default Inf). A finite value applies a finite population correction, reducing the required sample size.

prop_method

Proportion 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 with p. See n_prop() for how to choose.

df

Degrees 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$.df gives one value per domain to match in.

min_cases

Minimum 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-stage n_multi() only: a multistage design sizes stages against a cost and has no single total for a count to raise.

unit_relvar

Unit relvariance. If omitted, derived automatically from p (as (1 - p) / p) or from var / mu^2.

resp_rate

Expected 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.90 to compensate for 10 percent non-response. It means the same thing in n_multi_cluster(), which additionally takes resp_rate_psu for whole clusters that cannot be worked and resp_rate_ssu for 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