Skip to contents

Chromy’s method of sequential random sampling with probability minimum replacement, in the open. The procedure selects units one at a time down an ordered list, each with probability proportional to its size, so that every unit’s realised number of selections differs from its expected number by less than one and the sample size is always exact. It is how many large establishment and household surveys draw their samples, and how PROC SURVEYSELECT METHOD=PPS_SEQ works; until now it lived only inside proprietary software and one Stata module.

With the selection comes the apparatus it is used with in practice:

  • chromy_sample() — the whole design in one call: size, order, head, selection, weights;
  • serpentine_order() — hierarchical serpentine sorting of the frame, the implicit stratification that makes a sequential selection worth having;
  • mpps_size() and eqcv_allocation() — a maximal size measure over several auxiliary variables, and the allocation that equalises their anticipated coefficients of variation, for one sample that serves several estimates;
  • expected_hits() — sizes into expected hits, certainties resolved by iteration, or units allowed several hits under minimum replacement;
  • estimate_total() — the multiplicity estimator that matches the design, with the successive-difference variance.

The numerical engine is the Rust crate chromy-core; the Python binding is pychromy. All randomness comes from R’s own generator, so set.seed() makes a selection exactly reproducible.

Installation

# install.packages("pak")
pak::pak("castlaboratory/chromy")

A Rust toolchain (rustc ≥ 1.77) is needed to build from source; CRAN binaries, once published, will not need one.

A design in five lines

library(chromy)
set.seed(2026)

frame <- data.frame(
  region = sample(c("north", "east", "south", "west"), 300, replace = TRUE),
  pupils = round(rlnorm(300, 4.5)),
  outcome = rlnorm(300, 5)
)

frame |>
  chromy_sample(n = 40, size = "pupils",
                order_by = c("region", "chromy_size")) |>
  estimate_total("outcome")

The sample size is exact, every unit’s hits are the floor or the ceiling of its expected hits, and the weights on s$sample are ready for any design-based analysis. vignette("chromy") walks through a full design; vignette("method") opens the procedure itself; vignette("validation") checks the promises by simulation.

How the selection works

The expected hits accumulate down the ordered list; every time the running sum crosses an integer, one selection is released, and a single uniform per unit decides where it lands:

Six units whose expected hits sum to three; the cumulative path crosses three integer levels, and each crossing releases one selection.

One engine serves three languages — the uniforms arrive as arguments, so each language’s own generator governs its selections:

A frame with sizes and the caller's uniforms go into chromy-core, used from Rust, from R as chromy and from Python as pychromy, all pinned to one reference by golden tests.

Fidelity

The package was distilled from a reference implementation kept verbatim in the test suite as an oracle: given the same seed, chromy_sample() reproduces it draw for draw, across every design the two share. The engine is pinned to the same reference by golden fixtures, integer outputs exact and floating-point outputs at machine precision. Floating-point drift in the expected hits is measured, assigned to a single unit, and reported — never silently rescaled.

References

  • Chromy, J. R. (1979). Sequential sample selection methods. Proceedings of the American Statistical Association, Survey Research Methods Section, 401–406.
  • Chauvet, G. and Le Gleut, R. (2021). Properties of Chromy’s sampling procedure. Survey Methodology, 47(2), 321–347.
  • Folsom, R. E., Potter, F. J. and Williams, S. R. (1987). Notes on a composite size measure for self-weighting samples in multiple domains. Proceedings of the ASA, Section on Survey Research Methods, 792–796.

License

GPL (≥ 3). A project of the CAST Lab, UFPE.