Skip to contents

write_design() saves a sampling design (or the design carried by an executed sample) to a human-readable, samplyr-native JSON file. read_design() reads it back into a sampling_design that executes identically to the original.

The file format is versioned JSON and diffable in version control. It stores the complete design specification (stages, stratification, clustering, draw settings, including per-stratum vectors and data frames), never the frame data itself.

Usage

write_design(x, path, frame = NULL, ..., pretty = TRUE)

read_design(file)

Arguments

x

A sampling_design, a tbl_sample (the stored design is saved along with an execution receipt), a frame_stack, or a sample carrying shared weights.

path

File path to write to. Conventionally with a .json extension.

frame

Optional sampling frame. A data frame is the one frame the design was built against. An ordered list of data frames is the stage registers, in the order execute() received them, and each is fingerprinted separately. One frame written as a one-element list is still one frame and is recorded identically. The number of frames must be one the design could be executed with, and for an executed sample must be the number its receipt records, so a file cannot say it was drawn from one frame and carry fingerprints for three. When supplied, a fingerprint (name, dimensions, column types, content hash) is stored so the frame can be verified later. The frame data is never written. The content hash covers column names, column values, and row order. It does not depend on the class of the data frame (tibble or data frame) or on the order of its columns. For a frame_stack, a list named by component, holding what each component was drawn from. Matched by name rather than position, since a list in the wrong order would fingerprint each component against another's register. For a shared-weight sample, the register the source selection was drawn from.

...

These dots are for future extensions and must be empty. pretty follows ..., so it is matched exactly and must be named.

pretty

Whether to pretty-print the JSON. Defaults to TRUE for files and FALSE for design_json().

file

A path to a local file, or a JSON string produced by design_json(). URLs are refused: read_design() never fetches remote files. Download the file first and read the local copy.

Value

write_design() returns x invisibly. read_design() returns a sampling_design, a frame_stack_design for a frame collection file, or a shared_sample_design for a shared-weight sample file. Any frame information and execution receipt in the file are attached as the "frame_info" and "execution" attributes.

Details

Lifecycle

The serialization interface and its samplyr-native file format are experimental. They support samplyr persistence and replay. They are not a finalized cross-tool survey-sampling interchange standard. The structure may change while that separate specification is developed.

Frame information

Designs are frame-independent, and so are design files. Two derived blocks describe the frame without embedding it:

  • Requirements (always written): the columns each stage needs (stratification, clustering, mos, prn, aux, and control variables), so any candidate frame can be checked before execution with validate_frame().

  • Fingerprint (written when frame is supplied): portable dimensions and column types in frame, plus the R source label, native classes, and content hash in tools.samplyr. Together these can verify that a frame is the exact one the design was built against without putting R details in the common metadata.

Execution receipts

When x is a tbl_sample, the file additionally records an execution receipt: every argument of the execute() call that affects the result (seed, executed stages, panels, reps, and the per replicate seeds), the execution-time RNG configuration and package versions, plus the number of selected units and the execution timestamp. Together with the frame fingerprint this makes a single-call sample reproducible when the same frame, compatible package implementations, and any recorded custom methods are available. Running replay_design(read_design(path), frame) then obtains the same tbl_sample (the same rows in the same order, including .panel and .replicate assignments) with only the execution timestamp differing. The sampled rows themselves are not stored. Use a data format (CSV, parquet) for those.

Receipts describe one execute() call. A sample built by several calls (a stage continuation or a multi-phase pipeline) is flagged as chained in the receipt and write_design() warns: replaying the final call alone cannot reproduce it, so save and replay each phase or stage batch separately. A sample whose rows or design columns were modified after execution is likewise flagged (modified). Its receipt describes the original execution, not the modified object.

The receipt also records how frames were mapped to stages: the frame mode, how many frames were supplied, their optional labels, and the frame position each executed stage drew from. This is what a chained receipt describes too, for its final call only, so the mapping never implies that a chained sample can be replayed. A receipt written before these fields existed is read as the one-frame call it can only have been.

Control expressions

draw(control = ...) expressions are stored as declarative JSON terms, not R code. Each term records an ordering type ("ascending", "descending", or "serpentine") and its variables. Only bare column names, dplyr::desc(), and serp() can be represented. write_design() errors on anything else.

Declarative and implementation metadata

The design, frame, and execution blocks use declarative JSON rather than R expressions. Selection methods carry samplyr's internal semantic descriptor. The tools.samplyr block records exact method names, R classes, the R-derived frame hash, and execution environment needed to rebuild and replay the native object. These descriptors are not a finalized external method vocabulary.

Frame collections

A frame_stack from stack_frames() is written as samplyr/frame-stack, its own format. Each component entry is a complete samplyr/design document plus the two fields that make it a component: its name, and the column saying which frames its units belong to. The collection's key and any overlaps declared with overlap_probabilities() or overlap_weights() are recorded alongside. Give frame as a list keyed by component name, since the components are separate selections with separate registers.

read_design() returns the components' designs and receipts rather than the collection, which needs the registers; replay_design() executes each against its register and stacks the results.

Shared-weight samples

A sample carrying shared weights from share_weights() is written as samplyr/shared-sample, its own format. It records the source selection and the transformation's arguments, and nothing else: the links and the target register are supplied again to replay_design(), the way a frame is, so no unit-level data and no linkage is written.

Two integrity records travel with it, and replay is checked against both. A source that does not reproduce means frame is not the register selected from; a result that does not means links or targets is not the table the transformation was built from.

What the format does not carry

A design and one execution receipt per component, and nothing beyond them. Frame data, target data and link tables are never written. So what cannot be described that way is refused rather than written in part:

  • a collection whose overlaps come from exante_overlaps(). Those resolve to one chance per selected unit when the collection is formed, which is unit-level data, and the request that produced them is not kept.

  • a shared-weight sample used as a component of a collection. A component entry is a design document, and a collection replays from one register per component with nowhere to put a link table.

Serialize the sample on its own, or the collection without them, and rebuild afterwards. saveRDS() preserves any of these objects whole.

See also

replay_design() for reproducing a sample from its receipt, design_json() for in-memory JSON, validate_frame() for checking a frame against a design, get_design() for extracting the design from a sample.

Other serialization: as.list.sampling_design(), design_json(), replay_design()

Examples

design <- sampling_design(title = "Household Survey") |>
  stratify_by(region, alloc = "proportional") |>
  draw(n = 200, method = "systematic", control = c(province, ea_id))

path <- tempfile(fileext = ".json")
write_design(design, path, frame = bfa_eas)

restored <- read_design(path)
restored
#> ── Sampling Design: Household Survey ───────────────────────────────────────────
#> 
#> ℹ 1 stage
#> 
#> ── Stage 1 ─────────────────────────────────────────────────────────────────────
#> • Strata: region (proportional)
#> • Draw: n = 200 (total), method = systematic, control = c(province, ea_id)
#> 

# The restored design executes identically
s1 <- execute(design, bfa_eas, seed = 42)
s2 <- execute(restored, bfa_eas, seed = 42)
identical(s1$ea_id, s2$ea_id)
#> [1] TRUE

# Saving an executed sample records a reproducibility receipt
write_design(s1, path, frame = bfa_eas)
attr(read_design(path), "execution")$seed
#> [1] 42

# Replay the receipt to reproduce the sample exactly
s3 <- replay_design(read_design(path), bfa_eas)
identical(s3$ea_id, s1$ea_id)
#> [1] TRUE

unlink(path)