Data Generation: Mechanisms and Examples

Quick start with rmst.sim()

For common settings — AFT or PH baseline, random censoring, and standard covariate distributions — rmst.sim() generates data from one function call. Use covar_continuous(), covar_binary(), and covar_categorical() to define covariates directly.

Code
df <- rmst.sim(
  n              = 200,
  model          = "aft_lognormal",
  baseline       = list(mu = 2.5, sigma = 0.6),
  treat_effect   = -0.25,
  covariates     = list(
    covar_continuous("age",  mean = 62, sd = 10),
    covar_binary("sex",      p = 0.45),
    covar_continuous("x",    dist = "lognormal", meanlog = 0, sdlog = 0.5)
  ),
  target_censoring = 0.25,
  L    = 36,
  seed = 42
)

print(df)
── Simulated RMST Dataset ────────────────────────────────────
  Model          : AFT Log-Normal 
  N (total)      : 200  |  Arm 0: 108  |  Arm 1: 92
  Events         : 150 (75.0%)
  Censored       : 50 (25.0%)
  Truncation time: 36  (stored as attribute)

First 6 rows:
       time status arm      age sex         x
1  7.731940      1   1 55.73546   1 1.5633592
2 23.370037      1   1 63.83643   0 0.5923551
3 16.726338      1   0 53.64371   1 2.6796031
4  5.139254      0   1 77.95281   1 0.8254587
5 11.223345      1   0 65.29508   1 2.2866152
6  6.159279      1   0 53.79532   1 2.1299667

The result is a data frame of class rmst_simdata. Pass it directly to rmst.power() or rmst.ss():

Code
rmst.power(Surv(time, status) ~ age + sex + x,
           data = df, arm = "arm",
           sample_sizes = c(100, 200, 300), L = 36)

For designs requiring explicit covariate-dependent censoring or stratified assignment, use the full recipe system described below.


What this covers

We show how to construct a recipe (covariates (X), treatment (A), event time (Y), and censoring (C)), and then simulate datasets with simulate_from_recipe(). We also show batch generation via generate_recipe_sets() and how to read compact metadata back with load_recipe_sets().

The simulator takes a plain list as the recipe. No YAML is required.

Recipe skeleton

Code
list(
  n = 300,
  covariates = list(defs = list(/* see Covariates */)),
  treatment  = list(/* see Treatment */),
  event_time = list(/* see Event-time engines */),
  censoring  = list(/* see Censoring */),
  seed = 42
)

Covariates

Each covariate has a name, type ("continuous" or "categorical"), a dist with params, and optional transform steps applied after generation (e.g., "center(a)", "scale(b)").

Available distributions

Type dist (string) Parameters
continuous normal mean, sd
continuous lognormal meanlog, sdlog
continuous gamma shape, scale
continuous weibull shape, scale
continuous uniform min, max
continuous beta shape1, shape2
continuous t df
categorical bernoulli p (probability of 1)
categorical categorical prob = c(...), labels = c(...) (opt.)
categorical ordinal prob = c(...), labels = c(...) (opt.)

Example: define covariates

Code
covs <- list(
  list(name="age",   type="continuous",  dist="normal",     params=list(mean=62, sd=10),
       transform=c("center(60)","scale(10)")),
  list(name="sex",   type="categorical", dist="bernoulli",  params=list(p=0.45)),
  list(name="stage", type="categorical", dist="ordinal",
       params=list(prob=c(0.3,0.5,0.2), labels=c("I","II","III"))),
  list(name="x",     type="continuous",  dist="lognormal",  params=list(meanlog=0, sdlog=0.6))
)

Treatment

Choose one assignment:

Assignment Key fields Meaning
"randomization" allocation = "a:b" Bernoulli with probability (p_1 = a/(a+b)).
"stratified" allocation, stratify_by = c(...) Same allocation within each stratum defined by listed categorical covariates.
"logistic_ps" ps_model = list(formula, beta) Treatment probability is plogis(eta) from the user-specified model.

Examples

Randomization:

Code
tr_rand <- list(assignment="randomization", allocation="1:1")

Stratified by "stage":

Code
tr_strat <- list(assignment="stratified", allocation="2:1", stratify_by=c("stage"))

Logistic propensity:

Code
tr_ps <- list(
  assignment = "logistic_ps",
  ps_model  = list(
    formula = "~ 1 + x + sex",
    beta    = c(-0.3, 1.2, -0.6)  # (Intercept), x, sex
  )
)

Event-time engines

Let Z be treatment (0/1), X be covariates, and eta be the linear predictor (defined in effects, below). Supported engines and baseline parameterizations:

Model (user-facing) model value Baseline parameters Notes
AFT Lognormal "aft_lognormal" mu, sigma log(T) = mu + eta + sigma * epsilon, with epsilon ~ N(0, 1).
AFT Weibull "aft_weibull" shape, scale Baseline survival S0(t) = exp(-(t/scale)^shape) with an AFT shift via eta.
AFT Log-Logistic "aft_loglogistic" shape, scale Log-logistic AFT model with T = scale * exp(eta) * (U/(1-U))^(1/shape).
PH Exponential "ph_pwexp" rates = c(rate), cuts = numeric(0) Piecewise exponential with a single segment is exponential.
PH Weibull "ph_weibull" shape, scale Proportional hazards with a Weibull baseline.
PH Gompertz "ph_gompertz" rate, gamma Hazard h(t) = rate * exp(gamma * t).
PH Piecewise Exp "ph_pwexp" rates = c(r1,r2,...), cuts = c(...) Segment-specific hazard is r_s * exp(eta).

Effects and linear predictor

Specify effects on the appropriate scale (AFT: log-time; PH: log-hazard):

Code
effects = list(
  intercept  = 0,                      # default is 0
  treatment  = -0.25,
  covariates = list(age = 0.01, sex = -0.2)
  # or: formula="~ age + sex", beta=c(0.01, -0.2)
)

effects$covariates must be a named list (e.g., list(age=0.01)), not a named vector from c().


Censoring

We assume a single censoring mechanism that may depend on observed covariates (but not on the unobserved event time after conditioning on those covariates). Two user-facing modes are supported:

Mode Fields Semantics
"target_overall" target, admin_time Finds an exponential random-censoring rate so the overall censoring fraction is approximately target, with an administrative floor at admin_time (if provided).
"explicit" Any of: administrative = list(time=...); random = list(dist="exponential", params=list(rate=...)); dependent = list(formula="~ ...", base=..., beta=c(...)) Compose administrative, random, and covariate-dependent censoring directly. The dependent block defines a covariate-dependent log-rate model; it does not denote competing risks.

Note. No cause-specific or competing-risk censoring is generated here; the simulator’s dependent option is strictly covariate-dependent censoring under a single mechanism.

Examples

Target overall censoring

Code
cz_target <- list(
  mode        = "target_overall",
  target      = 0.25,   # desired overall censoring fraction
  admin_time  = 36      # optional administrative cutoff (same units as event time)
)

Explicit mix (administrative + random)

Code
cz_explicit <- list(
  mode = "explicit",
  administrative = list(time = 36),                                      # admin cutoff
  random         = list(dist = "exponential", params = list(rate = 0.02))# random censoring
)

Explicit covariate-dependent censoring

The dependent block uses a log-rate model: \[ \log \lambda_c(X) = \log(b) + X^\top \beta, \] where b > 0 is the baseline rate. Implementation uses base as a rate, and beta on the log-rate scale. When base is supplied, the formula must not include an intercept.

Code
cz_dep <- list(
  mode = "explicit",
  dependent = list(
    # sex is coded numeric 0/1 in these recipes; if yours is a factor, use I(as.numeric(sex)-1)
    formula = "~ 0 + age + sex",   # NO intercept because we use 'base' as the rate
    base    = 0.03,                # positive baseline rate
    beta    = c(0.012, 0.35)       # (age, sex) on log-rate scale
  ),
  administrative = list(time = 48) # optional admin cutoff
)

If you prefer a log-intercept style, omit base and include an intercept in the formula and in beta, e.g. formula="~ 1 + age + sex"; beta=c(-3.5, 0.012, 0.35).


Using a censoring recipe inside a full simulation

Code
# Covariates (sex will be numeric 0/1 here)
covs <- list(
  list(name="age", type="continuous",  dist="normal",    params=list(mean=62, sd=10)),
  list(name="sex", type="categorical", dist="bernoulli", params=list(p=0.45))
)

# Censoring: single covariate-dependent mechanism using a positive base rate
cz_dep <- list(
  mode = "explicit",
  dependent = list(
    formula = "~ 0 + age + sex",  # NO intercept; sex is numeric 0/1
    base    = 0.03,               # baseline censoring rate (>0)
    beta    = c(0.012, 0.35)      # (age, sex) on log-rate scale
  ),
  administrative = list(time = 48)
)

# Quick sanity check (optional)
mm   <- model.matrix(~ 0 + age + sex, data.frame(age=c(50,70), sex=c(0,1)))
rate <- cz_dep$dependent$base * exp(mm %*% matrix(cz_dep$dependent$beta, ncol=1))
stopifnot(all(rate > 0 & is.finite(rate)))

# Recipe
rec <- list(
  n = 300,
  covariates = list(defs = covs),
  treatment  = list(assignment="randomization", allocation="1:1"),
  event_time = list(
    model    = "ph_pwexp",
    baseline = list(rates = c(0.05), cuts = numeric(0)), # exponential baseline
    effects  = list(intercept=0, treatment=-0.3, covariates=list(age=0.01, sex=-0.2))
  ),
  censoring  = cz_dep,
  seed = 2025
)

# Simulate
dat <- simulate_from_recipe(validate_recipe(rec))

# Output
knitr::kable(
  data.frame(achieved_censoring = attr(dat, "achieved_censoring")),
  caption = "Achieved censoring rate"
)
Achieved censoring rate
achieved_censoring
0.4666667
Code
knitr::kable(utils::head(dat, 8), caption = "First 8 rows of simulated data")
First 8 rows of simulated data
time status arm age sex
18.7861607 1 0 47.80127 0
28.4958510 1 0 52.92040 0
17.4147421 0 0 70.47633 0
2.1688157 1 1 66.99463 1
1.9444436 0 0 55.62174 0
0.0312674 0 1 86.15505 1
31.0842017 1 1 76.80908 1
0.3933869 0 1 54.61018 1

Worked examples

We now build full recipes and call simulate_from_recipe(). We report realized censoring via attr(dat, "achieved_censoring").

Example 1 — AFT Lognormal

Code
# Covariates
covs1 <- list(
  list(name="age",   type="continuous",  dist="normal",     params=list(mean=62, sd=10),
       transform=c("center(60)","scale(10)")),
  list(name="sex",   type="categorical", dist="bernoulli",  params=list(p=0.45)),
  list(name="stage", type="categorical", dist="ordinal",
       params=list(prob=c(0.3,0.5,0.2), labels=c("I","II","III"))),
  list(name="x",     type="continuous",  dist="lognormal",  params=list(meanlog=0, sdlog=0.6))
)

# Recipe
rec1 <- list(
  n = 300,
  covariates = list(defs = covs1),
  treatment  = list(assignment="randomization", allocation="1:1"),
  event_time = list(model="aft_lognormal",
                    baseline=list(mu=3.0, sigma=0.6),
                    effects=list(intercept=0, treatment=-0.25,
                                 covariates=list(age=0.01, sex=-0.2, x=0.05))),
  censoring  = list(mode="target_overall", target=0.25, admin_time=36),
  seed = 11
)

# Simulate
dat1 <- simulate_from_recipe(validate_recipe(rec1))

# Display: data (first rows)
knitr::kable(utils::head(dat1, 8), caption = "Example 1 — AFT Lognormal: first 8 rows")
Example 1 — AFT Lognormal: first 8 rows
time status arm age sex stage x
22.584990 1 1 -0.9484864 0 II 1.0093289
17.658806 1 1 0.6443458 1 II 1.0588519
20.540033 1 1 0.2119294 0 II 1.8124911
7.427722 1 1 0.1907200 0 II 1.2929821
5.884245 1 1 -0.1023776 1 III 1.9560519
16.663357 1 0 0.6923550 1 III 0.6415970
18.282139 1 1 -0.4027196 1 II 0.2898592
25.896230 0 1 -0.4822765 0 II 0.3896278
Code

# Display: key attributes (compact)
attr_tbl1 <- data.frame(
  attribute = c("achieved_censoring", "n_rows", "n_cols", "class"),
  value = c(
    as.character(attr(dat1, "achieved_censoring")),
    nrow(dat1),
    ncol(dat1),
    paste(class(dat1), collapse = ", ")
  )
)
knitr::kable(attr_tbl1, caption = "Example 1 — Attributes")
Example 1 — Attributes
attribute value
achieved_censoring 0.216666666666667
n_rows 300
n_cols 7
class data.frame

Example 2 — AFT Weibull

Code
# Start from Example 1 and tweak the event-time engine
rec2 <- rec1
rec2$event_time <- list(
  model    = "aft_weibull",
  baseline = list(shape=1.3, scale=12),
  effects  = list(intercept=0, treatment=-0.20, covariates=list(age=0.008, x=0.04))
)

dat2 <- simulate_from_recipe(validate_recipe(rec2), seed=12)

# Display: data (first rows)
knitr::kable(utils::head(dat2, 8), caption = "Example 2 — AFT Weibull: first 8 rows")
Example 2 — AFT Weibull: first 8 rows
time status arm age sex stage x
0.8767022 1 1 0.1684122 0 I 1.2786805
3.7917670 0 0 -0.3638602 0 III 0.6734713
5.3436817 1 1 -0.0695371 0 II 1.7285599
8.2146778 1 0 -0.2834247 1 II 0.8830733
16.3483547 1 0 1.2408825 0 III 1.9996407
8.5485716 1 1 2.1120583 1 I 2.1527826
6.0849117 1 1 2.1253492 0 II 1.7435251
7.6357950 0 0 0.9624655 0 II 0.9642604
Code

# Display: key attributes (compact)
attr_tbl2 <- data.frame(
  attribute = c("achieved_censoring", "n_rows", "n_cols", "class"),
  value = c(
    as.character(attr(dat2, "achieved_censoring")),
    nrow(dat2),
    ncol(dat2),
    paste(class(dat2), collapse = ", ")
  )
)
knitr::kable(attr_tbl2, caption = "Example 2 — Attributes")
Example 2 — Attributes
attribute value
achieved_censoring 0.226666666666667
n_rows 300
n_cols 7
class data.frame

Example 3 — PH Exponential (single segment)

Code
rec3 <- list(
  n = 400,
  covariates = list(defs = covs1),
  treatment  = list(assignment="randomization", allocation="1:1"),
  event_time = list(
    model    = "ph_pwexp",
    baseline = list(rates = c(0.05), cuts = numeric(0)),
    effects  = list(intercept=0, treatment=-0.3, covariates=list(age=0.01, x=0.03))
  ),
  censoring  = list(mode="target_overall", target=0.20, admin_time=30),
  seed = 13
)

dat3 <- simulate_from_recipe(validate_recipe(rec3))

# Display: data (first rows)
knitr::kable(utils::head(dat3, 8), caption = "Example 3 — PH Exponential: first 8 rows")
Example 3 — PH Exponential: first 8 rows
time status arm age sex stage x
22.4112596 1 0 1.903383 0 II 1.0272998
1.2157570 1 1 1.551547 0 II 1.1600019
16.3664550 1 1 -0.256743 1 II 1.0494973
30.0000000 0 1 1.923054 0 II 0.9904563
7.2081849 1 1 -1.782442 1 III 0.5716505
24.0173726 1 0 1.696357 0 III 0.5826786
0.6726071 1 1 1.067309 1 I 1.9916004
2.9351145 1 0 -0.084403 1 II 1.6589311
Code

# Display: a small summary + attributes
summ3 <- data.frame(
  Metric = c("n", "Events", "Censoring rate", "Mean time", "Median time"),
  Value  = c(nrow(dat3),
             sum(dat3$status == 1, na.rm = TRUE),
             round(mean(dat3$status == 0, na.rm = TRUE), 3),
             round(mean(dat3$time,   na.rm = TRUE), 2),
             round(stats::median(dat3$time, na.rm = TRUE), 2))
)
knitr::kable(summ3, caption = "Example 3 — Summary")
Example 3 — Summary
Metric Value
n 400.000
Events 289.000
Censoring rate 0.278
Mean time 16.340
Median time 14.910
Code

attr_tbl3 <- data.frame(
  attribute = c("achieved_censoring", "n_rows", "n_cols", "class"),
  value = c(
    as.character(attr(dat3, "achieved_censoring")),
    nrow(dat3),
    ncol(dat3),
    paste(class(dat3), collapse = ", ")
  )
)
knitr::kable(attr_tbl3, caption = "Example 3 — Attributes")
Example 3 — Attributes
attribute value
achieved_censoring 0.2775
n_rows 400
n_cols 7
class data.frame

Example 4 — PH Piecewise Exponential (multi-segment)

Code
rec4 <- list(
  n = 500,
  covariates = list(defs = list(
    list(name="age", type="continuous",  dist="normal",    params=list(mean=60, sd=8)),
    list(name="sex", type="categorical", dist="bernoulli", params=list(p=0.5)),
    list(name="x",   type="continuous",  dist="lognormal", params=list(meanlog=0, sdlog=0.5))
  )),
  treatment  = list(assignment="randomization", allocation="1:1"),
  event_time = list(
    model    = "ph_pwexp",
    baseline = list(rates=c(0.10, 0.06, 0.03), cuts=c(6, 18)),
    effects  = list(intercept=0, treatment=-0.4, covariates=list(age=0.01, x=0.03))
  ),
  censoring  = list(mode="target_overall", target=0.25, admin_time=30),
  seed = 123
)

dat4 <- simulate_from_recipe(validate_recipe(rec4))

# Display: data (first rows)
knitr::kable(utils::head(dat4, 8), caption = "Example 4 — PH Piecewise Exponential (multi-segment): first 8 rows")
Example 4 — PH Piecewise Exponential (multi-segment): first 8 rows
time status arm age sex x
1.8844656 1 1 62.78288 0 1.1852957
0.1803433 1 0 52.52264 0 0.4197519
30.0000000 0 0 72.74434 0 2.1693298
21.2203789 0 0 72.03558 0 2.9706366
3.1738917 1 0 54.10477 0 1.3775195
2.4408164 1 0 59.40696 0 0.3945189
2.1540147 1 0 64.18567 1 0.8663525
1.9268445 0 1 66.97149 1 0.7599901
Code

# Display: summary + attributes
summ4 <- data.frame(
  Metric = c("n", "Events", "Censoring rate", "Mean time", "Median time"),
  Value  = c(nrow(dat4),
             sum(dat4$status == 1, na.rm = TRUE),
             round(mean(dat4$status == 0, na.rm = TRUE), 3),
             round(mean(dat4$time,   na.rm = TRUE), 2),
             round(stats::median(dat4$time, na.rm = TRUE), 2))
)
knitr::kable(summ4, caption = "Example 4 — Summary")
Example 4 — Summary
Metric Value
n 500.00
Events 370.00
Censoring rate 0.26
Mean time 6.65
Median time 4.02
Code

attr_tbl4 <- data.frame(
  attribute = c("achieved_censoring", "n_rows", "n_cols", "class"),
  value = c(
    as.character(attr(dat4, "achieved_censoring")),
    nrow(dat4),
    ncol(dat4),
    paste(class(dat4), collapse = ", ")
  )
)
knitr::kable(attr_tbl4, caption = "Example 4 — Attributes")
Example 4 — Attributes
attribute value
achieved_censoring 0.26
n_rows 500
n_cols 6
class data.frame

Generate data based on formula (event & censoring)

This example uses ~ formulas for both event-time covariate effects and covariate-dependent censoring. We keep the treatment effect as a separate scalar to avoid design ambiguity.

Code
# Covariates (sex numeric 0/1)
covs_f <- list(
  list(name="age", type="continuous",  dist="normal",    params=list(mean=62, sd=10)),
  list(name="sex", type="categorical", dist="bernoulli", params=list(p=0.45))
)

trt_f <- list(assignment="randomization", allocation="1:1")

# Event-time engine: AFT lognormal with formula-based covariate effects
evt_f <- list(
  model    = "aft_lognormal",
  baseline = list(mu = 3.1, sigma = 0.55),
  effects  = list(
    treatment  = -0.25,                 # effect of treatment (log-time scale)
    formula    = "~ 0 + age + sex",     # covariate effects via formula (no intercept)
    beta       = c(0.010, -0.20)        # (age, sex) -- match order in formula
  )
)

# Censoring: covariate-dependent via log-rate model (single mechanism)
cz_f <- list(
  mode = "explicit",
  dependent = list(
    formula = "~ 0 + age + sex",  # NO intercept; sex numeric 0/1
    base    = 0.03,               # positive rate
    beta    = c(0.012, 0.35)      # (age, sex) on log-rate scale
  ),
  administrative = list(time = 48)  # optional admin cutoff
)

# Full recipe
rec_f <- list(
  n = 350,
  covariates = list(defs = covs_f),
  treatment  = trt_f,
  event_time = evt_f,
  censoring  = cz_f,
  seed = 777
)

# Simulate
dat_f <- simulate_from_recipe(validate_recipe(rec_f))

# Display
knitr::kable(
  data.frame(achieved_censoring = attr(dat_f, "achieved_censoring")),
  caption = "Achieved censoring rate (formula-based example)"
)
Achieved censoring rate (formula-based example)
achieved_censoring
0.8714286
Code
knitr::kable(utils::head(dat_f, 8), caption = "First 8 rows — formula-based example")
First 8 rows — formula-based example
time status arm age sex
0.7388634 0 1 49.95591 1
4.0901774 0 0 53.08751 1
7.8761496 0 1 62.85469 1
4.1360942 0 1 66.66486 0
8.6479773 0 0 65.48561 1
13.2841235 0 0 66.07921 0
6.5725020 0 1 71.87909 1
11.6268551 0 0 58.79355 1

Batch generation with metadata

For simulation studies, write multiple scenarios and formats together. The writer creates a manifest.rds with a list-column meta describing each dataset. The loader reattaches attributes when reading back.

Code
# Build a base recipe here (self-contained)
base <- validate_recipe(list(
  n = 200,
  covariates = list(defs = list(
    list(name="age", type="continuous",  dist="normal", params=list(mean=62, sd=10)),
    list(name="sex", type="categorical", dist="bernoulli", params=list(p=0.45)),
    list(name="x",   type="continuous",  dist="lognormal", params=list(meanlog=0, sdlog=0.6))
  )),
  treatment  = list(assignment="randomization", allocation="1:1"),
  event_time = list(
    model    = "aft_weibull",
    baseline = list(shape=1.3, scale=12),
    effects  = list(intercept=0, treatment=-0.20, covariates=list(age=0.008, x=0.04))
  ),
  censoring  = list(mode="target_overall", target=0.25, admin_time=36),
  seed = 3026
))

out_dir <- file.path(tempdir(), "rmstss-manifest-demo")
unlink(out_dir, recursive = TRUE, force = TRUE)

man <- generate_recipe_sets(
  base_recipe = base,
  vary = list(n = c(200, 400),
              "event_time.effects.treatment" = c(-0.15, -0.25)),
  out_dir  = out_dir,
  formats  = c("rds","csv"),
  n_reps   = 1,
  seed_base = 2025
)

# Inspect the first row's compact metadata (fields only; no file paths)
m <- readRDS(file.path(out_dir, "manifest.rds"))
names(m)
 [1] "scenario_id"                     "rep"                            
 [3] "seed"                            "achieved_censoring"             
 [5] "n"                               "file_txt"                       
 [7] "file_csv"                        "file_rds"                       
 [9] "file_rdata"                      "p__n"                           
[11] "p__event_time.effects.treatment" "meta"                           
if ("meta" %in% names(m) && length(m$meta[[1]]) > 0) {
  list(model = m$meta[[1]]$model,
       baseline = m$meta[[1]]$baseline,
       effects = m$meta[[1]]$effects,
       achieved_censoring = m$meta[[1]]$achieved_censoring,
       n = m$meta[[1]]$n)
} else {
  "Manifest is minimal (older run); use rebuild_manifest() to enrich."
}
$model
[1] "aft_weibull"

$baseline
$baseline$shape
[1] 1.3

$baseline$scale
[1] 12


$effects
$effects$intercept
[1] 0

$effects$treatment
[1] -0.15

$effects$covariates
$effects$covariates$age
[1] 0.008

$effects$covariates$x
[1] 0.04



$achieved_censoring
[1] 0.275

$n
[1] 200

# Load datasets back
sets <- load_recipe_sets(file.path(out_dir, "manifest.rds"))
attr(sets[[1]]$data, "achieved_censoring")
[1] 0.275
str(sets[[1]]$meta)
List of 19
 $ dataset_id        : chr "sc001_r01"
 $ scenario_id       : int 1
 $ rep               : int 1
 $ seed_used         : int 3026
 $ n                 : int 200
 $ n_treat           : int 98
 $ n_control         : int 102
 $ event_rate        : num 0.725
 $ achieved_censoring: num 0.275
 $ model             : chr "aft_weibull"
 $ baseline          :List of 2
  ..$ shape: num 1.3
  ..$ scale: num 12
 $ effects           :List of 3
  ..$ intercept : num 0
  ..$ treatment : num -0.15
  ..$ covariates:List of 2
  .. ..$ age: num 0.008
  .. ..$ x  : num 0.04
 $ treatment         :List of 2
  ..$ assignment: chr "randomization"
  ..$ allocation: chr "1:1"
 $ censoring         :List of 3
  ..$ mode      : chr "target_overall"
  ..$ target    : num 0.25
  ..$ admin_time: num 36
 $ covariates        :List of 3
  ..$ :List of 4
  .. ..$ name  : chr "age"
  .. ..$ type  : chr "continuous"
  .. ..$ dist  : chr "normal"
  .. ..$ params:List of 2
  .. .. ..$ mean: num 62
  .. .. ..$ sd  : num 10
  ..$ :List of 4
  .. ..$ name  : chr "sex"
  .. ..$ type  : chr "categorical"
  .. ..$ dist  : chr "bernoulli"
  .. ..$ params:List of 1
  .. .. ..$ p: num 0.45
  ..$ :List of 4
  .. ..$ name  : chr "x"
  .. ..$ type  : chr "continuous"
  .. ..$ dist  : chr "lognormal"
  .. ..$ params:List of 2
  .. .. ..$ meanlog: num 0
  .. .. ..$ sdlog  : num 0.6
 $ allocation        : chr "1:1"
 $ params            :List of 2
  ..$ n                           : num 200
  ..$ event_time.effects.treatment: num -0.15
 $ files             :List of 4
  ..$ txt  : chr NA
  ..$ csv  : chr "C:\\Users\\arnab\\AppData\\Local\\Temp\\RtmpkrcmkA/rmstss-manifest-demo/sc1_r1.csv"
  ..$ rds  : chr "C:\\Users\\arnab\\AppData\\Local\\Temp\\RtmpkrcmkA/rmstss-manifest-demo/sc1_r1.rds"
  ..$ rdata: chr NA
 $ created_at        : chr "2026-04-26 15:51:24.433331"

Reproducibility tips

Set seed in the recipe or pass seed= to simulate_from_recipe().

Assumptions. The censoring hazard depends on observed covariates via a user-specified single mechanism. There are no competing risks. Datasets contain only time, status, treatment, and covariates - no dependent-censoring indicators.