When to register
Do the OSF Preregistration when the specification YAML is final and the shared skeletons exist. s0 built the plan, you generated the Excel specification from it, and no stage after s0 has run.
Every answer of the form then comes from two files. These are the
specification YAML, spec_vNNN.yaml, and the Excel
specification, spec_vNNN.xlsx. After s0 the Excel
specification holds two sheets, “Study Specification” and “Target trial
protocol”. The protocol sheet documents one ETT, the first of the grid
unless protocol_ett_id names another. Once s1 has run, a
“Rare conditions” sheet follows the Study Specification sheet when the
spec has a rare_condition_rule. This article maps each OSF
section to a YAML key and to a section of the Study Specification
sheet.
Produce the Excel specification
$excel_spec_summary() writes the Excel specification
from the plan that s0 builds. Run tteplan_check_spec() on
the YAML first. It reads the YAML and no data.
swereg::tteplan_check_spec("/path/to/spec/spec_v001.yaml")
study <- swereg::registrystudy_load("/path/to/meta")
plan <- swereg::tteplan_from_spec_and_registrystudy(
study = study,
candidate_dir_spec = "/path/to/spec",
candidate_dir_tteplan = "/path/to/plans/my_study",
candidate_dir_results = "/path/to/results",
spec_version = "v001"
)
plan$excel_spec_summary()
plan$print_target_checklist()s0 reads the specification YAML and the registry codes of the
RegistryStudy. It also loads the first skeleton batch to
validate the specification:
- It checks that each named column exists. It reads the outcome columns by name only, and reads no outcome value.
- It reads the values of the treatment, washout and categorical confounder columns, and the type of the observation column.
- It takes the last week of the data as the administrative end.
A failed check stops s0 or gives a warning. A message can state a
count of prevalent-user weeks, or a category value that the data hold
and the specification lacks. You MAY pass
global_max_isoyearweek to set the administrative end
yourself, and s0 then skips this validation.
tteplan_excel_spec_summary() writes the Excel
specification from the specification YAML and a code registry, without a
plan. It reads no skeleton, so it runs before s0, and while the
skeletons do not yet hold a new code. Pass the
RegistryStudy, or its $summary_table(), as
code_registry, and each variable that the registry
generates shows its codes. The Admin censoring: row reads
not built (spec only) unless you pass
global_max_isoyearweek. Every other cell matches
$excel_spec_summary() at s0 for the same spec, code
registry, period_width and protocol_ett_id. Do
not edit the workbook by hand: change the YAML and write the workbook
again.
Register after s0 and before s1.
Write the OSF text
tteplan_osf_text() writes the text of each OSF field
that the specification determines, from the specification YAML and the
code registry. It reads no skeleton, so it runs before s0. It returns a
named character vector of Markdown texts, one for each field:
osf <- swereg::tteplan_osf_text(
"/path/to/spec/spec_v001.yaml",
code_registry = study,
global_max_isoyearweek = "2025-01",
people = c("woman", "women")
)
cat(osf[["Data inclusion and exclusion"]])The text prints the name of each criterion, covariate
and outcome, the description of each outcome and treatment,
and the arm labels, as the specification writes them. Write those fields
for a reader of the registration: no variable names, no internal version
or approach numbers. The categories of a covariate are data values, so
they appear in the code list only. The author writes the fields that the
specification cannot answer: the title, the research question and the
primary result, the foreknowledge, the data sources and the ethics
approval.
Build the registration document from these texts, for example in a
.qmd that calls the function. Do not copy the text by hand:
a copy goes stale when the specification changes.
The OSF sections, field by field
The YAML column names a key of the specification.
vignette("tte-spec-schema") lists every legal key. The
Excel column names a section of the Study Specification sheet, and a row
label where one applies. TARGET items come from
$print_target_checklist() on the same plan.
| OSF section | YAML key | Excel specification |
|---|---|---|
| Research questions or hypotheses |
study$description,
outcomes[], enrollments[]$treatment$arms
|
Outcomes; Enrollments, rows “Treatment:” and “Comparator:” |
| Foreknowledge of data or evidence | n/a | n/a. See “Foreknowledge”. |
| Study type | Non-randomized study. Adjustment or control-based design: the treatment and censoring weights. | n/a |
| Intention for causal interpretation | The estimands of TARGET item 6f | n/a |
| Blinding of experimental treatments | No blinding is involved. | n/a |
| Additional blinding during research or analysis | n/a | n/a. See “Foreknowledge”. |
| Study design |
target_trial, enrollments[],
follow_up[]
|
Study, row “Design:”; Follow-up; Enrollments; the Target trial protocol sheet. TARGET items 6a to 6h and 7. |
| Randomization | Not applicable. The comparator draw is seeded sampling,
with enrollments[]$treatment$implementation$seed. |
n/a |
| Data collection procedures |
inclusion_criteria,
exclusion_criteria,
enrollments[]$additional_inclusion,
enrollments[]$additional_exclusion,
enrollments[]$observed_var
|
Inclusion criteria (global); Exclusion criteria (global); Enrollments, rows “Additional inclusion:” and “Additional exclusion:” |
| Sample size | n/a | n/a. See “Sample size”. |
| Sample size rationale | enrollments[]$treatment$implementation$comparator_to_intervention_ratio |
Enrollments, row “Comparator-to-intervention ratio:”. See “Sample size”. |
| Starting and stopping rules |
inclusion_criteria$isoyears,
follow_up[]$weeks, the global_max_isoyearweek
argument of s0 |
Inclusion criteria (global), row “Isoyears:”; Follow-up; Study, row “Admin censoring:” |
| Manipulated variables | None | n/a |
| Measured variables |
enrollments[]$treatment,
outcomes[], confounders[], the criteria,
subgroups[]
|
Enrollments; Outcomes; Confounders; the criteria
sections. The sheet does not list subgroups[]. |
| Indices |
confounders[] with
implementation$computed: true, and their
source_variable and window
|
Confounders, rows “Variable:” and “Window:” |
| Statistical models | Fixed by swereg |
vignette("tte-methods"), section 1. TARGET
item 6h. |
| Transformations | n/a | See “Transformations”. |
| Inference criteria |
study$implementation$conf_level, default
0.95, and min_expected_events_per_arm_per_year
|
vignette("tte-methods"), section 1.8.6.
tteplan_osf_text() writes the minimum-events rule as the
Inference criteria field. |
| Data inclusion and exclusion | The criteria of “Data collection procedures”, and
rare_condition_rule
|
Confounders, rows of “Rare-condition rule (rare_condition_rule)”. TARGET items 6a and 7. See “The rare-condition rule”. |
| Missing data | Fixed rules, and the impute_fn argument of
s1 |
vignette("tte-methods"), section 1.8.7.
TARGET item 11. |
| Other planned analyses |
subgroups[], the untruncated weights, and
the intention-to-treat estimand beside the per-protocol one |
vignette("tte-methods"), sections 1.8.8
and 1.9 |
| Context and additional information | study$implementation$version |
Study, row “Version:”. See “Deviations after registration”. |
Statistical models
Section 1 of vignette("tte-methods") states the
estimators as swereg implements them. Copy it without change, so the
protocol matches the code. Then state each value of its list “Values
your protocol must state”.
Transformations
swereg does not transform a confounder. The treatment weight model
and the censoring models hold each confounder as one main-effect term,
with no interaction and no spline. A numeric column enters as one linear
term. A logical column enters as one term for TRUE. A
character or factor column enters through the default treatment coding
of R, so its first level is the reference.
Derive any other coding in the skeleton, and register the derived
column. Section 1.8.9 of vignette("tte-methods") states the
time terms of the outcome and censoring models.
The rare-condition rule
A rare_condition_rule block in the specification is a
pre-specified rule. Each candidate confounder held by fewer than
min_n persons in the smallest arm of the driving
comparisons becomes an exclusion criterion of every comparison. The
other candidates stay confounders. Each candidate is the variable of a
confounder with computed: true.
confounders:
- name: "Prior ischaemic stroke"
implementation:
source_variable: osd_i63
window: lifetime_before_baseline
computed: true
rare_condition_rule:
min_n: 100
decided_by: ["01"]
candidates: [rd_no_osd_i63_everbefore]decided_by names the driving comparisons. The rule is
decided from baseline counts in the eligible population, before the
comparator draw and without follow-up outcomes. s1 makes that
decision.
At registration, nobody knows which conditions the rule excludes. Register the threshold, the driving comparisons and the candidates. The Excel specification states the rule and the row “Decision:” reads “not decided yet (s1 has not run)”. TARGET item 6a gives the same rule as prose. Paste it into “Data inclusion and exclusion”.
Sample size
At registration there are no study counts. Base the rationale on external information, such as a published incidence, or an earlier extraction that you document as such.
The minimum detectable effect (MDE) columns of the
$export_tables() workbook are not a preregistration input.
swereg computes them after s3, from the study’s own events.
To plan from external numbers, apply the naive Poisson formula that the MDE columns use. Under the null, both arms share one rate. In the workbook, that rate is the pooled weighted rate of both arms. At registration, it is the external rate:
mde <- function(rate, py_int, py_cmp, power = 0.8, conf_level = 0.95) {
e_int <- rate * py_int
e_cmp <- rate * py_cmp
se <- sqrt(1 / e_int + 1 / e_cmp)
k <- qnorm(1 - (1 - conf_level) / 2) + qnorm(power)
round(c(
intervention_events = e_int,
comparator_events = e_cmp,
mde_protective = exp(-k * se),
mde_harmful = exp(k * se)
), 2)
}
# External rate: 4 events per 1,000 person-years in both arms.
# 3,000 intervention person-trials and 6,000 comparator person-trials,
# with a 5-year horizon.
mde(rate = 4 / 1000, py_int = 3000 * 5, py_cmp = 6000 * 5)
#> intervention_events comparator_events mde_protective mde_harmful
#> 60.00 120.00 0.64 1.56The person-years in this example are the person-trials times the horizon. That is an upper bound, because loss, death and the end of data stop follow-up early. The formula ignores the repeated contributions of one person to many trials. So it can differ from the detectable effect of the weighted estimator with person-clustered standard errors.
The minimum-events rule computes the same expected events under the
null for each follow-up year of 52 weeks. The expected events of an arm
are the pooled weighted rate of both arms times the weighted
person-years of the arm. When an arm falls below
min_expected_events_per_arm_per_year in one year, s3
withholds the rate ratio, the risk difference and the number needed to
treat of that estimand. Check each arm and year against the threshold,
which is 5 in this example:
# Expected events per arm and follow-up year. Each person-trial contributes
# at most 1 person-year per year, so these values are an upper bound.
expected_per_year <- (4 / 1000) * c(intervention = 3000, comparator = 6000)
expected_per_year
#> intervention comparator
#> 12 24
expected_per_year >= 5
#> intervention comparator
#> TRUE TRUEAt s3, the pooled rate holds the observed events of both arms. So the
years that pass depend on the outcome data. The rule is a prespecified
reporting rule. The rule decides the per-protocol estimand, and the
intention-to-treat estimand follows that decision. The
Minimum events (Y or -) sheet of the workbook shows the
verdict of the rule. The Minimum events (#) sheet shows the
largest cut at which each estimate still passes the rule.
Foreknowledge
At registration the authors ran s0 only, and s0 reads no outcome value. State that under “Explanation of foreknowledge”.
After registration, s1 reads outcome data. A person enters a trial
only if they reach time zero free of every outcome of the enrollment. So
every s1 count from the attrition step “Event before time zero” onwards
depends on outcome events before time zero. The s1 panel files,
file_raw and file_imp, also hold the follow-up
outcomes. Do not open those files before the analysis. If an author sees
an outcome-dependent result before the analysis, record it in the
deviations log.
Deviations after registration
After registration, a change to the specification is a new specification version, with a new Excel specification:
- Copy the registered
spec_vNNN.yamlto the next version number. - Make the change, and set
study$implementation$versionanddate. - Build the plan from s0, and write the new Excel specification with
$excel_spec_summary(). - Write one log entry: the date, the change and the reason.
Each version writes to its own results directory, because
plan$dir_results appends the version. s0 stops when
study$implementation$version differs from its
spec_version argument. Do not edit the registered version.
Compare two versions with git diff --no-index on their YAML
files. Report the log with the paper.
