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.
Arguments
- x
A
sampling_design, atbl_sample(the stored design is saved along with an execution receipt), aframe_stack, or a sample carrying shared weights.- path
File path to write to. Conventionally with a
.jsonextension.- 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 aframe_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.
prettyfollows..., so it is matched exactly and must be named.- pretty
Whether to pretty-print the JSON. Defaults to
TRUEfor files andFALSEfordesign_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 withvalidate_frame().Fingerprint (written when
frameis supplied): portable dimensions and column types inframe, plus the R source label, native classes, and content hash intools.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)