In the Generic Statistical Business Process Model (GSBPM), the construction of analysis weights is sub-process 5.6 (“calculate weights”) and the production of estimates and their variances is 5.7 (“calculate aggregates”). In a statistical office this step is not a one-off script: it is a governed, auditable and repeatable part of the production line, run every wave, reviewed, and archived. This article shows how weightflow supports that workflow. Every piece below already exists in the package; the point is to use them together.
The core idea is that the whole weighting process is a single
declarative object. You define it once, estimate it with
prep(), and everything else (diagnostics, variance, the
report) reads from that one object. The recipe is what you version,
review in a pull request, and archive alongside the estimates.
rec <- weighting_spec(sample_survey, base_weights = pw) |>
step_nonresponse(respondent = responded, method = "weighting_class",
by = "region") |>
step_calibrate(method = "raking", id = "calib_main",
margins = list(region = c(table(population$region)),
sex = c(table(population$sex))))
fit <- prep(rec)
summary(fit)
#>
#> == Weighting specification (weightflow) ==
#> Data : 467 cases
#> Base wts: pw
#> Steps :
#> 1. nonresponse (weighting class) [nonresponse_1]
#> 2. calibration (raking) [calib_main]
#> Status : estimated (prep)
#>
#> Stage summary:
#> stage n_active sum_wts cv_wts deff_kish n_eff
#> base 467 4371 0.236 1.056 442
#> stage_1_step_nonresponse 270 4371 0.144 1.021 265
#> stage_2_step_calibrate 270 4495 0.211 1.045 258
#>
#> deff_kish = 1 + CV^2 (Kish design effect from unequal weighting);
#> n_eff = n_active / deff_kish. Both worsen with each adjustment and
#> improve with trimming.
#>
#> --- Step 1: nonresponse (weighting class) ---
#> cell n_respondents n_nonresponse factor
#> East 52 44 1.846154
#> North 78 41 1.525641
#> South 72 49 1.680556
#> West 68 63 1.926471
#> Kish deff: 1.056 -> 1.021 | n_eff: 442 -> 265
#>
#> --- Step 2: calibration (raking) ---
#> variable category target achieved n
#> region North 1570 1570 78
#> region South 1250 1250 72
#> region East 927 927 52
#> region West 748 748 68
#> sex F 2311 2311 130
#> sex M 2184 2184 140
#> (converged/iterated in 5 iterations)
#> Kish deff: 1.021 -> 1.045 | n_eff: 265 -> 258
#>
#> R-indicator (representativity of response): 0.892 (on region)Because the definition is separated from the execution, the same
recipe object is the specification, the documentation and the input to
the variance machinery. Each step has a stable id (here
calib_main, otherwise a derived one), so a step can be
referenced from a script long after the run.
Production pipelines need a machine-readable pass or fail, not a
human reading a report. prep() records every quality
incident in $alerts, readable with
weighting_alerts() and has_alerts(). That is
the hook for a continuous integration check: fail the build when the
recipe raises an incident.
if (has_alerts(fit)) {
# in CI: stop() here so the pipeline fails and the run is not published
weighting_alerts(fit)
} else {
"no quality incidents"
}
#> [1] "no quality incidents"?weightflow-alerts catalogues the incidents
prep() can raise, with the trigger and the remedy for each.
For a hard threshold that must hold (a maximum design effect, a minimum
effective sample size), add a step_assert(): it errors at
that point of the cascade if the condition fails, so a recipe that
violates it never produces weights at all.
The replication functions take a single seed, and draw
the whole resampling pattern from it up front, so a parallel run is
bit-identical to the serial one. Fix the seed, and record the exact
package and R versions used (for example in a lockfile), because a
flexible learner such as a random forest can change between versions of
its engine. The report’s reproducibility card records the versions at
run time.
report_weighting() writes a self-contained HTML report:
the cascade in prose, the target and achieved control totals, the design
effect and effective sample size, the fieldwork outcomes and response
rates when the recipe has eligibility and nonresponse steps, and a
reference-metadata header aligned to the ESS SIMS concepts and GSBPM
5.6. Archive that HTML next to the released estimates; it is the
artifact a reviewer reads.
report_weighting(
fit, replicates = boot, file = "weights_2026.html", lang = "en",
metadata = list(
survey = "Continuous Household Survey",
reference_period = "2026",
producer = "National Statistical Office",
frame = "Population and housing census 2023",
totals_source = "Population projections 2026",
version = "1.0"))The recipe-aware bootstrap and jackknife re-run the whole recipe on
each replicate, so the replicate weights carry the variability of every
adjustment. Many offices release replicate weights alongside public-use
microdata so that external users can compute design-consistent variances
without the full design. collect_replicate_weights()
returns the point weight and every replicate weight as ordinary columns,
ready to write out and to load into survey or
srvyr.
pub <- collect_replicate_weights(boot) # point + replicate weights
# survey / srvyr read them directly:
des <- as_svrepdesign(boot)The point weights and replicate weights also flow into
survey through as_svydesign() /
as_svrepdesign(), so downstream estimands and domains use
the design-based machinery the office already trusts.
The cascade maps onto the total survey error framework: unknown-eligibility redistribution and dropping out-of-scope units address coverage error, the within-household selection restores the design, the nonresponse step addresses nonresponse error, and calibration reduces coverage bias and improves precision. Placing the whole process in one auditable, re-runnable object is what the quality frameworks ask for: the UN Fundamental Principles of Official Statistics and the European Statistics Code of Practice both require sound methodology, transparency and reproducibility. weightflow does not replace the parts of 5.6 and 5.7 that belong to other tools (small-area estimation, editing, imputation); it is the reproducible spine that documents and computes the weights, and bridges to those tools honestly.
vignette("variance-estimation") for the replicate
methods, vignette("quality-report") for the report,
vignette("inspecting-auditing") for the programmatic
quality-control accessors, and ?weightflow-alerts for the
alert catalogue.