R6 class overview
swereg uses R6 classes for every piece of state that lives across function calls. That state includes the skeleton pipeline, the per-batch skeleton files, the trial schema, the per-enrollment data container and the ETT grid builder. This vignette describes all six classes and where each one fits in the data flow.
It stays at a high level. For the framework, trim, codes and randvars
mechanics see vignette("skeleton-pipeline"). For the full
TTE workflow see vignette("tte-workflow"). For the
reference page of each class see ?CandidatePath,
?RegistryStudy, ?Skeleton,
?TTEDesign, ?TTEEnrollment and
?TTEPlan.
The six classes at a glance
| Class | What it owns | Created by | Persisted? |
|---|---|---|---|
CandidatePath |
An ordered list of filesystem paths where a directory might live on different hosts, plus a cached resolution. | $new(candidates, label) |
No. swereg clears the cache on save, so objects are portable. |
RegistryStudy |
The full skeleton pipeline: directories, batch config, the four-phase pipeline (framework / trim / codes / randvars), and the per-batch processing loop. |
RegistryStudy$new(...) in the runner script. |
Yes, as registrystudy.qs2. |
Skeleton |
One batch’s person-week data.table plus its
phase-provenance (framework hash, trim identity, phase order, applied
randvars, applied code entries). |
Implicitly by RegistryStudy$process_skeletons();
accessed via study$load_skeleton(i). |
Yes, one skeleton_NNN.qs2 per batch. |
TTEDesign |
The column name schema for a trial (id, treatment, outcome, confounder, time). |
TTEDesign$new(...) from the spec during
tteplan_from_spec_and_registrystudy(). |
Yes, embedded in TTEPlan /
TTEEnrollment. |
TTEEnrollment |
One sequence of sequential trials: the data, the design and the lifecycle state. |
TTEEnrollment$new(data, design, ratio = ...) during
Loop 1. |
Yes, as file_raw and file_imp (after Loop
1) and file_analysis (after Loop 2). |
TTEPlan |
The ETT grid (one row per outcome × follow-up × age group),
references back to the RegistryStudy, and the two loops
that produce analysis files. |
tteplan_from_spec_and_registrystudy(spec, study). |
Yes, as tteplan.qs2. |
The dependency picture
+-------------------+
| CandidatePath |
+---------+---------+
|
(used as fields on both R6 owners below)
|
+-------------+-------------+
| |
+--------v--------+ +--------v--------+
| RegistryStudy | | TTEPlan |
+--------+--------+ +--------+--------+
| produces | references
v v
+-----+------+ +---------+---------+
| Skeleton | | TTEEnrollment |
+------------+ +---------+---------+
|
| `$data`
v
+---------+---------+
| trial panel |
| (data.table) |
+-------------------+
The picture shows two properties:
CandidatePathis a leaf: other classes hold instances of it, butCandidatePathholds nothing from swereg. It is the single type for “multi-host directory” knowledge, so its resolution behavior cannot drift between users.TTEPlanholds a reference back to theRegistryStudyit was built against. That reference tells a Loop 1 worker where the skeleton files live, throughstudy$load_skeleton(i). It also lets the plan assert every skeleton is pipeline-consistent before any expensive work starts.
The data flow, in seven steps
Data flows through seven shapes in a full swereg pipeline. Each shape has a name, an owner, and a position in the workflow.
1. Raw registry files (bytes on disk)
The original .txt / .sas7bdat /
.csv files from SCB, SOS, Riksstroke, etc. swereg does not
own these. They are in study$data_raw_dir, which is a
CandidatePath-resolved location. The runner script reads
them with swereg::fread_raw() and hands them to
RegistryStudy$save_rawbatch().
2. Rawbatches (per-group-per-batch qs2 files)
A rawbatch is the intermediate form between raw
registry files and skeletons. It keeps one large
inpatient.txt out of memory.
$save_rawbatch("inpatient", sv) splits the inpatient data
by person-batch, which is the set of person IDs assigned to batch 1,
batch 2, and so on. It then writes one .qs2 file per
(batch, group) pair:
/data/.../2026/rawbatch/
001_rawbatch_inpatient.qs2
001_rawbatch_outpatient.qs2
001_rawbatch_lmed.qs2
...
150_rawbatch_other.qs2
Rawbatches are not an R6 class. They are plain data.tables serialized
with qs2. study$load_rawbatch(batch_number) reads them and
returns a named list of data.tables keyed by group name. They exist as a
separate step for memory. Each group is loaded and processed separately,
so peak RAM during skeleton processing equals
max(group_size) and not sum(all_groups).
RegistryStudy owns the rawbatch directory via its
data_rawbatch_cp field (a CandidatePath) and
exposes $save_rawbatch(), $load_rawbatch(),
and $delete_rawbatches() methods.
3. Skeletons (per-batch Skeleton R6 objects)
A skeleton is the person-week time grid for one
batch, with derived columns. Most of the pipeline state is in the
skeleton. Each skeleton_NNN.qs2 file is a serialized
Skeleton R6 object carrying:
-
data: the underlyingdata.table. -
framework_fn_hash: the xxhash64 of the phase-1 function that built the base grid. -
trim_fn_hash: the identity of the phase-1b trim that deleted rows from that grid. A sentinel when the study registers no trim, andNULLwhen the skeleton file carries no trim identity. -
phase_order: the order the phases ran in. When the current pipeline runs the phases in a different order, swereg rebuilds the file and does not reuse it. -
applied_registry: a fingerprint-keyed map of the applied phase-2 code entries, with the metadata needed to recompute which columns each entry wrote. -
randvars_state: a named ordered list of the applied phase-3 steps, with their hashes and the columns each one added.
Skeleton objects are rarely constructed directly. The
normal lifecycle is:
# Load from disk
sk <- study$load_skeleton(batch_number = 1L)
# Mutate via the methods on Skeleton itself or via RegistryStudy's
# sync helpers -- $sync_randvars() and $sync_with_registry().
# These are called implicitly by $process_skeletons().
# Save back to disk
study$save_skeleton(sk)RegistryStudy$process_skeletons() orchestrates the four
phases across all batches, reading and writing Skeleton
objects one per batch. vignette("skeleton-pipeline")
describes how phase invalidation works.
4. Person-week panels (data.table inside a TTEEnrollment)
In Loop 1 sub-step s1d, each worker reads one skeleton and hands a
filtered person-week data.table for one enrollment to a fresh
TTEEnrollment object. Sub-step s1e then binds the panels of
all skeletons of that enrollment into one:
enrollment <- TTEEnrollment$new(
data = filtered_person_week_dt,
design = tte_design,
ratio = 2
)
enrollment$data_level # "person_week"At this point $data is still one row per person per ISO
week. The TTEDesign object supplies the column name schema,
and no enrollment redefines it. The schema names the treatment column,
the outcome column and the confounder columns.
5. Trial panels (data.table after $enroll())
Passing ratio to TTEEnrollment$new()
triggers $enroll() automatically. This step turns
person-week rows into counting-process trial rows. Each
trial row is one person, one trial and one follow-up period, and it
carries tstart / tstop columns in the
Andersen-Gill style. The comparator draw takes comparator individuals by
incidence density sampling within each enrollment period. It takes
comparator_to_intervention_ratio times that period’s count
of intervention individuals, or every remaining comparator when the
enrollment period holds fewer. The draw is seeded and stratified by the
enrollment period. It reads no other variable, and it forms no matched
set.
enrollment$data_level # "trial"
# now has: enrollment_period_id, period_id, tstart, tstop, intervention, ...The remaining Loop 1 steps work on this trial panel:
# impute_fn at trial entry; the default draws one hot-deck value
enrollment$s1_impute_confounders(confounder_vars = entry_confounder_cols)
enrollment$s1b_fill_followup_confounders() # carry forward through follow-up
enrollment$s2_ipw() # stabilized logistic IPW
enrollment$s3_truncate_weights() # winsorize at 1/99 percentiles$enrollment_stage reads "enrolled" as soon
as $data_level becomes "trial", which the
comparator draw in $new(ratio = ) does. It does not track
the numbered steps. Loop 1 serializes the result as
file_imp, one per enrollment_id.
6. Analysis panels (one per ETT)
Loop 2 reloads each file_imp, prepares an outcome
column, fits IPCW-PP, combines weights, and saves the result as
file_analysis:
enrollment <- swereg::qs2_read(x_file_imp)
enrollment$s4_prepare_for_analysis(
outcome = x_outcome,
follow_up = x_follow_up_weeks
)
# enrollment$enrollment_stage -> "analysis_ready"
# analysis_weight_pp = ipw * ipcw_pp (not truncated)
# analysis_weight_pp_trunc = analysis_weight_pp truncated at 1/99 percentilesLoop 2 writes one file_analysis per row of
plan$ett. Estimation reads these files.
7. Estimates (numbers)
enrollment <- swereg::qs2_read(x_file_analysis)
enrollment$irr(weight_col = "analysis_weight_pp_trunc")
enrollment$rates(weight_col = "analysis_weight_pp_trunc")
enrollment$survival_curve(weight_col = "analysis_weight_pp_trunc")$irr()/$rates() wrap
survey::svyglm and $survival_curve() computes
a weighted discrete-time survival curve from the panel. All three apply
the weight column. $irr() also includes the
trial-as-covariate term and returns effect estimates with person-level
clustered standard errors.
Class responsibilities in detail
CandidatePath: portable filesystem paths
CandidatePath owns an ordered list of filesystem paths
and caches the first one that exists on the current host. The cache is
host-specific, and swereg does not persist it across save and load.
[invalidate_candidate_paths()] walks an R6 object tree before
serialization and clears every CandidatePath cache it
finds. A registrystudy.qs2 written on a Linux host
therefore reads correctly on a Windows host, by re-walking its candidate
lists from scratch.
Construction takes the candidates and an optional label used in error messages:
cp <- CandidatePath$new(
candidates = c(
"//shared-drive/registry/2026/rawbatch/",
"/mnt/shared/registry/2026/rawbatch/",
"C:/shared/registry/2026/rawbatch/"
),
label = "data_rawbatch_dir"
)
cp$resolve() # first existing, cached
cp$is_resolved() # TRUE if cache still valid
cp$invalidate() # clear cacheEnd users rarely construct CandidatePath directly. You
pass a character vector to
RegistryStudy$new(data_rawbatch_dir = ...) and the
constructor wraps it internally. The active bindings
study$data_rawbatch_dir,
study$data_skeleton_dir, etc. call $resolve()
on the backing CandidatePath, so plain character access
returns the resolved path.
RegistryStudy: the skeleton pipeline
RegistryStudy holds the top-level state of the skeleton
pipeline. Its responsibilities fall into four groups:
-
Portable directories via
CandidatePathactive bindings. -
Batch configuration (
set_ids(),batch_size, thebatch_id_list). -
The declarative pipeline
(
register_framework(),register_trim(),register_codes(),register_derived_codes(),register_randvars()). Only the trim may delete skeleton rows. A code entry or a randvars step that changes the row count stops the run. -
The batch processing loop
(
process_skeletons()) plus helpers (load_rawbatch,save_rawbatch,load_skeleton,save_skeleton,pipeline_hash,assert_skeletons_consistent).
RegistryStudy$process_skeletons() runs the pipeline. For
each batch it loads the rawbatch data, runs the four phases with
incremental invalidation, and saves a new Skeleton object
to data_skeleton_dir. When n_workers > 1, a
batch-runner worker pool processes the batches in parallel.
Skeleton: data and provenance of one batch
Skeleton is the on-disk unit produced by
RegistryStudy$process_skeletons(), one file per batch. The
class holds a data field with a data.table and five
provenance fields that record what produced it. The methods
(sync_with_registry, sync_randvars,
apply_code_entry, drop_code_entry,
refresh_code_entry_counts, pipeline_hash) are
invoked by RegistryStudy during processing and can also be
called manually for inspection or debugging.
The skeleton is an R6 object and not a plain data.table,
because swereg needs the provenance. Without it, only a full rebuild
answers “is this file in sync with the current pipeline?”. With it,
sk$pipeline_hash() == study$pipeline_hash() is a cheap
check that RegistryStudy$assert_skeletons_consistent() runs
across every persisted batch before Loop 1 consumes them.
TTEDesign: the trial schema
TTEDesign holds column names and nothing else. It names
the person ID column, the treatment column, the confounder columns, the
time variables (time_treatment_var,
time_outcome_var) and the eligibility variable.
Enrollment data passes through many methods
($s1_impute_confounders, $s2_ipw,
$s4_prepare_for_analysis, $rates,
$irr, $survival_curve), and every one of them
needs the same schema. The design travels as a single R6 field on each
TTEEnrollment. Nobody has to pass column names around as
arguments, and a column rename is a one-line edit on the shared
TTEDesign.
TTEDesign is constructed inside
tteplan_from_spec_and_registrystudy() from the spec YAML,
not by end users. A plan stores one TTEDesign per distinct
enrollment; TTEEnrollment instances reference the same
TTEDesign via their $design field.
TTEEnrollment: the workflow container
TTEEnrollment holds most of the TTE workflow state:
-
$data: the data.table currently attached (person-week or trial or analysis-ready, depending on lifecycle stage). -
$design: a reference to theTTEDesigndescribing the schema. -
$data_level:"person_week"or"trial". It tracks the major shape transition during enrollment. -
$enrollment_stage:"pre_enrollment"/"enrolled"/"analysis_ready". It is a read-only active binding. It reads"pre_enrollment"while$data_levelis"person_week","analysis_ready"once$steps_completedholds"prepare_outcome", and"enrolled"otherwise.
Its public methods carry stage numbers that give the intended order:
# Loop 1
$s1_impute_confounders() # impute_fn; default hot-deck draw
$s1b_fill_followup_confounders() # carry forward through follow-up
$s2_ipw() # baseline stabilized IPW
$s3_truncate_weights() # winsorize
# Loop 2
$s4_prepare_for_analysis() # outcome + per-protocol censoring +
# IPCW-PP + weight combinationMethods mutate in place and return invisible(self) for
$-chaining. A skipped stage does not error.
$s2_ipw() before $s1_impute_confounders() fits
the propensity model on the person-trials with no missing confounder,
and leaves ipw as NA on the rest.
$s3_truncate_weights() before $s2_ipw() warns
No weight columns to truncate and changes nothing. Run the
stages in their numbered order.
Estimation methods at the end:
$rates(weight_col = ...)
$irr(weight_col = ...)
$survival_curve(weight_col = ...)
$heterogeneity_test(weight_col = ...)Each returns an estimate object, a data.table or a list,
and mutates nothing. $survival_curve() also saves a plot
when save_path is given.
TTEPlan: the ETT grid and two loops
TTEPlan is the top-level container for a target trial
emulation project. It owns:
-
$spec: the parsed spec YAML. -
$registrystudy: theRegistryStudythe skeletons came from. -
$ett: the ETT grid (one row per outcome × follow-up combination × enrollment_id, withfile_raw,file_imp, andfile_analysispath columns). - Output directories via
CandidatePath(dir_tteplan,dir_results).
Two methods run the workflow:
plan$s1_generate_enrollments_and_ipw(n_workers = 4L)
# Loop 1: enroll + collapse + impute + IPW + truncate + save
# Five sub-steps. Each item runs in a fresh worker subprocess:
# s1a one item per skeleton, n_workers at a time
# s1b one item, only when the spec has a rare_condition_rule
# s1c one item per enrollment, one at a time
# s1d one item per enrollment x skeleton, n_workers at a time
# s1e one item per enrollment, one at a time
plan$s2_generate_analysis_files_and_ipcw_pp(n_workers = 4L)
# Loop 2: per-ETT outcome prep + IPCW-PP + weight combine + save
# One worker subprocess per ETT and estimand (pp, itt),
# n_workers at a time (default 1L).In Loop 1, n_workers sets how many s1a and s1d items run
at the same time. It also sets the threads of each worker: the core
count divided by n_workers, rounded down, and at least 1.
The default is default_n_workers("s1"). That is the option
swereg.n_workers.s1, then the environment variable
SWEREG_N_WORKERS_S1, then 1.
Plus helpers: $print_target_checklist() for reporting,
$save(dir) / TTEPlan$load(dir) for
persistence, $length() / $[[ S3 methods for
ETT grid access.
Where the classes interact
The diagram below shows the pipeline from raw files to ETT estimates, and the class that owns each step.
[raw registry files]
|
v
study$save_rawbatch(group, data) // RegistryStudy owns
|
v
[001_rawbatch_inpatient.qs2, ...]
|
v
study$process_skeletons() // RegistryStudy orchestrates
| // framework -> trim -> codes -> randvars
v // produces one Skeleton per batch
[skeleton_001.qs2, ...] // each file is a Skeleton R6
|
v
tteplan_from_spec_and_registrystudy( // reads spec + study, builds TTEPlan
spec, study // creates one TTEDesign per enrollment
) //
|
v
[TTEPlan with ett grid]
|
v
plan$s1_generate_enrollments_and_ipw() // Loop 1: s1a and s1d run
| // n_workers at a time. s1d builds
| // one TTEEnrollment per enrollment
| // x skeleton. s1e binds them per
| // enrollment_id and runs s1, s1b,
v // s2 and s3
[file_imp per enrollment_id]
|
v
plan$s2_generate_analysis_files_and_ // Loop 2: one subprocess per ETT
ipcw_pp() // and estimand, n_workers at a time;
| // reloads TTEEnrollment, runs s4,
| // which runs s5, then s6 for pp
|
v
[file_analysis per ETT]
|
v
enrollment$irr(weight_col = ...) // TTEEnrollment estimation
enrollment$rates(weight_col = ...) // methods, one weight column
enrollment$survival_curve(weight_col = ...)
|
v
[estimates]
Every step in the diagram above is either a class method call or a
persisted file. Rawbatches and skeletons are the only persisted forms
between raw data and the TTE workflow. file_imp and
file_analysis are persisted TTEEnrollment
objects. The rest is in-memory state during a pipeline run.
When to construct each class by hand
End users of swereg construct:
-
CandidatePath: almost never. Pass character vectors toRegistryStudy$new(), and it wraps them internally. -
RegistryStudy: once per data-generation project, at the top of the runner script.
End users of swereg never construct:
-
Skeleton: produced byprocess_skeletons(), and read withstudy$load_skeleton(i). -
TTEDesign: constructed bytteplan_from_spec_and_registrystudy()from the spec YAML. -
TTEEnrollment: constructed by the Loop 1 / Loop 2 workers. -
TTEPlan: constructed bytteplan_from_spec_and_registrystudy().
The classes are internal machinery. The user works with the runner
script, the spec YAML and the per-ETT analysis loop. You construct a
TTEEnrollment by hand when you debug something or write a
test. Neither is the main workflow.
Summary
-
Six R6 classes, each with one job.
-
CandidatePath: directory resolution. -
RegistryStudy: pipeline orchestrator. -
Skeleton: per-batch data and provenance. -
TTEDesign: trial schema. -
TTEEnrollment: workflow container. -
TTEPlan: ETT grid and two loops.
-
-
Rawbatches are not a class. They are per-batch
per-group qs2 files managed by
RegistryStudy$save_rawbatch()and$load_rawbatch(). They bound peak RAM by the largest group, and not by the sum of all groups. -
Data flow: raw files → rawbatches → skeletons →
person-week panel → trial panel →
file_imp→file_analysis→ estimates. At most steps, one class owns the transformation. At the edges (raw files, persisted qs2), files are plain data.tables. -
End users construct
RegistryStudyonce. Everything else is constructed by swereg internals.
For next steps:
-
vignette("skeleton-pipeline"): howRegistryStudyandSkeletonwork together during$process_skeletons(). -
vignette("tte-workflow"): howTTEDesign,TTEEnrollment, andTTEPlanwork together during Loop 1 and Loop 2. -
vignette("tte-methodology"): mapping to reference papers. -
?CandidatePath,?RegistryStudy,?Skeleton,?TTEDesign,?TTEEnrollment,?TTEPlan: full method reference.
