Overview
This vignette summarizes the ALSD method and maps the technical details to functions in the activesampling package. Abbreviations: ALSD = Adaptive Learning Sampling Design; RDD = Random-Digit Dialing.
- Goal: adaptively set stratum-level selection probabilities to minimize the expected Kish design effect from weighting, increasing the effective sample size.
- Strategies implemented:
- alsd_simple: match cumulative stratum shares to population shares.
- alsd_modelled: parameterize stratum selection probabilities with a logistic model and choose parameters to minimize expected design effect, subject to an expected-respondent budget constraint. Optimization uses ADAM by default.
ALSD balances exploration (learning response propensities) with exploitation (allocating to strata that reduce variance after weighting) under a fixed budget per wave. We use a smooth penalty to target the expected number of respondents in the next wave and optionally enforce it exactly via an intercept shift on the logit scale.
Notation and data structure
- strata indexed by .
- : population size in stratum . .
- : cumulative respondents so far in stratum ; .
- : available units for next wave (to first order), and : estimated response propensity among those units.
- : model matrix (e.g., one-hot indicators for strata); parameters.
- Selection probability for next wave: , where .
- Expected respondents in next wave: . Define .
After the next wave, the expected total respondents is , and the expected respondents in stratum is .
Objective: expected Kish design effect
Let be the post-stratification “weight factor” comparing the population share to the expected sample share after the next wave:
We approximate the Kish design effect for the next wave by
This is exactly what calculate_deff() computes given .
Constraint via penalty
We encourage hitting a target number for expected respondents in the next wave via a quadratic penalty:
where is a hyper-parameter. This matches objective_deff_constrained().
Gradient structure
For optimization we use the analytic gradient. Define:
- , .
- ; .
- .
Then calculate_deff_gradient() computes via
$$ \frac{\partial\, \mathrm{D\_eff}}{\partial\, \beta_j} = -\frac{1}{S(\beta)^2}\,\Bigl(\sum_h K_h X_{h j}\Bigr)\, \sum_h S_h\,(W_h-1)^2\\ \quad + \frac{1}{S(\beta)} \sum_h \Bigl[ K_h X_{h j}\,(W_h-1)^2 + 2 (W_h-1)\, \mathrm{pop\_pct}_h\, \frac{ (\sum_{h'} K_{h'} X_{h' j}) S_h - K_h X_{h j} S }{S_h} \Bigr]. $$
The gradient of the penalty term is:
gradient_deff_constrained() returns the sum of these two parts.
Algorithms
alsd_simple (greedy share matching):
- Compute remaining availability and current respondent counts .
- Allocate next-wave draws to move cumulative sample shares toward pop_pct_h, capped by remaining availability.
- Sample and observe responses; update and ; repeat until budget.
alsd_modelled (gradient-based):
- Build from strata indicators; initialize .
- Choose and target (e.g., the wave’s expected respondents).
- Minimize with ADAM using
gradient_deff_constrained(); stop on convergence or max iters. - Set , clip to [0,1], derive integer allocations by stratum with availability caps.
- Sample and observe responses; update counts; repeat until budget.
See run_alsd_simulation() for both flows.
Package mapping
-
calculate_deff(beta, X, pop_pct, u_h_omega, n_h_omega, pi_h, ...): computes . -
calculate_deff_gradient(...): analytic $\Nabla D_\mathrm{eff}(\beta)$. -
objective_deff_constrained(...),gradient_deff_constrained(...): penalized objective and gradient. -
run_alsd_simulation(...): wrapper that drives either alsd_simple or alsd_modelled across waves.
A minimal example
Consistency and small discrepancies
Our implementation follows the equations in the manuscript:
- Expected design effect matches the Kish-based expression with stratum weights: and , with analytic gradient identical to the derivation used in the legacy scripts.
- The simple strategy uses and converts this to stratum selection via , equivalent to the “response rate sampling” expression in the paper.
- The constrained objective adds a quadratic penalty with ; in practice, we optionally enforce the target exactly via a final intercept shift on the logit scale (documented in the function reference).
Clarifications and minor differences:
- Realized vs expected : during simulation logging we report a realized Kish design effect computed from respondents’ post-stratification weights (normalize to mean 1, then ). The optimizer uses the expected formula above, as in the paper.
- Estimation error shown in vignettes is the unweighted sample mean for simplicity; the manuscript discusses weighted estimators more broadly. We can add a weighted-estimator variant if desired.
- Numerical safety: when is near zero, we guard divisions using a small positive floor and clip probabilities to [0,1]. These do not change the objective’s intent, but avoid degenerate cases in tiny strata.
Alternative objective: cross-entropy. In addition to design-effect minimization, we also expose a cross-entropy/KL objective between population shares and the expected next-wave respondent shares. Select it via objective = “entropy” in run_alsd_simulation (or call calculate_entropy/objective_entropy_constrained directly). It uses the same budget penalty and an analytic gradient.
Notes, warnings, and planned features
- Scope: This package refactors the original analysis scripts into a reusable tool. Some components in the original repo are exploratory and not ported verbatim.
- Differences vs original scripts:
- Optimizer is
optimgADAM by default; no custom ADAM or Stan backends. - Realized design effect reported here is Kish from post-strat weights with ; some legacy plots show only.
- No raking step in simulation summaries; that’s deliberate to isolate the design-effect mechanics.
- Optimizer is
- Planned:
- Weighted-estimator summaries as an option.
- Richer convergence diagnostics and early-stopping.
- Optional reporting of both and in summaries for legacy comparability.
We run a tiny simulation on the included zimbabwe_minisample so the vignette renders quickly.
library(activesampling)
library(dplyr)
set.seed(1)
data(zimbabwe_minisample)
# Keep a small subset for a fast example
mini <- zimbabwe_minisample %>% slice(1:3000)
res <- run_alsd_simulation(
population_df = mini,
strata_vars = c("gender", "region"),
outcome_var = "fcs_insuff_flag",
method = "alsd_modelled", # uses ADAM by default
initial_sample_size = 80,
wave_size = 40,
total_budget = 200,
optimizer = "adam"
)
#>
#> --- Wave 1 ---
#> Respondents collected: 0/200
#>
#> --- Wave 2 ---
#> Respondents collected: 90/200
#>
#> --- Wave 3 ---
#> Respondents collected: 121/200
#>
#> --- Wave 4 ---
#> Respondents collected: 156/200
#>
#> --- Wave 5 ---
#> Respondents collected: 156/200
#>
#> --- Wave 6 ---
#> Respondents collected: 156/200
#>
#> --- Wave 7 ---
#> Respondents collected: 156/200
#>
#> --- Wave 8 ---
#> Respondents collected: 156/200
#>
#> --- Wave 9 ---
#> Respondents collected: 156/200
#>
#> --- Wave 10 ---
#> Respondents collected: 156/200
#>
#> --- Wave 11 ---
#> Respondents collected: 156/200
#>
#> --- Wave 12 ---
#> Respondents collected: 156/200
#>
#> --- Wave 13 ---
#> Respondents collected: 156/200
#>
#> --- Wave 14 ---
#> Respondents collected: 156/200
#>
#> --- Wave 15 ---
#> Respondents collected: 156/200
#>
#> --- Wave 16 ---
#> Respondents collected: 156/200
#>
#> --- Wave 17 ---
#> Respondents collected: 156/200
#>
#> --- Wave 18 ---
#> Respondents collected: 156/200
#>
#> --- Wave 19 ---
#> Respondents collected: 156/200
#>
#> --- Wave 20 ---
#> Respondents collected: 156/200
#> Termination reason: Reached maximum waves
head(res$summary)
#> wave method n_respondents y_estimate error deff varw
#> 1 1 alsd_modelled 90 0.1888889 -0.02611111 1.195729 NA
#> 2 2 alsd_modelled 121 0.1983471 -0.01665289 1.064193 NA
#> 3 3 alsd_modelled 156 0.1987179 -0.01628205 1.037166 NA
#> 4 4 alsd_modelled 156 0.1987179 -0.01628205 1.037166 NA
#> 5 5 alsd_modelled 156 0.1987179 -0.01628205 1.037166 NA
#> 6 6 alsd_modelled 156 0.1987179 -0.01628205 1.037166 NA
#> y_estimate_weighted error_weighted
#> 1 NA NA
#> 2 NA NA
#> 3 NA NA
#> 4 NA NA
#> 5 NA NA
#> 6 NA NAThe summary data frame reports, by wave, the cumulative number of respondents, the current outcome estimate and error vs. the true mean in the simulation, and the current expected design effect from weights.
Practical notes
- Response propensity modelling uses a simple logistic regression on stratum labels with a safe fallback when only one level is present.
- Selection probabilities are clipped to [0, 1]; integer sample sizes are derived per stratum and limited by remaining unsampled units.
- The optimizer defaults to ADAM via
optimg. You can setoptimizer = "bfgs"to usestats::optiminstead. - To enforce the next-wave respondent target exactly, set
enforce_target = TRUEinrun_alsd_simulation, which applies a post-optimization intercept shift to match the expected respondents.
Motivation and discussion
- Minimizing expected Kish design effect targets variance inflation from weighting, thereby increasing effective sample size n_eff.
- A quadratic penalty for the expected-respondents target t provides a smooth trade-off between balance and progress toward the budget, avoiding brittle hard constraints.
- The logistic parameterization ensures valid selection probabilities in (0,1) and a smooth surface for gradient methods.
Link to DDC and cost-aware extensions
- Data Defect Correlation (DDC): Under the decomposition in Meng (2018), the absolute error of a weighted mean can be written as . For a fixed outcome scale and budget, reducing the variance inflation from weights (captured by ) reduces the factor via increased effective , and tends to lower the overall error even when modest residual correlation remains. Our focus on expected Kish is thus aligned with controlling the DDC term’s impact on error.
- Costs: Some ALSD variants may include heterogeneous per-complete costs . A practical extension replaces the respondent-target penalty with a cost penalty (or constraint), e.g., . This is a drop-in change to
objective_deff_constrained()andgradient_deff_constrained()with unchanged and an extra linear form in added to the penalty and its gradient. We keep the default implementation cost-neutral but the interface allows easy adaptation via the penalty term.
Hyperparameters and convergence
- Penalty weight : start with and adjust so that is close to ; larger enforces the target more tightly but can slow convergence. In practice, we found values in adequate for typical wave sizes.
- Step/iterations: ADAM uses its internal step-size schedule;
maxit = 200usually suffices. If the design effect plateaus but misses appreciably, slightly increase and re-optimize from the current . - Stopping: use either a gradient-norm or relative objective change criterion. The default
optimgsettings withmaxitact as a safe cap; in our routines we also clip probabilities to [0,1] after optimization for numerical safety.
What’s missing (roadmap)
- Weighted estimators in summaries: Report both unweighted and post-stratified (or raked) estimates and errors by wave.
- Diagnostics: Surface convergence stats (objective trace, gradient norm) and per-stratum p_h, with simple stop criteria toggles.
- Dual D_eff reporting: Optionally log var(w) in addition to Kish D_eff = mean(w^2) for easier comparison to some legacy plots.
- Cost-aware extensions: Support heterogeneous per-complete costs via a cost-penalty or constraint in the objective wiring.
- Reproducibility knobs: Expose seeds and optimizer hyperparameters more explicitly in the top-level API.