Skip to contents

Internal method. An abstract R6 Class encapsulating the data and functionality for an experimental design. This class takes care of data storage and response handling.

Details

Throughout the package, treatment assignment vectors \(w\) use the \(\{0, 1\}\) encoding: \(1\) indicates a treated subject and \(0\) a control subject. All public methods that return or accept \(w\) (e.g. get_w(), draw_ws_according_to_design()) use this convention. A handful of variance estimators (e.g. InferenceIncidCMH, InferenceIncidExtendedRobins) recode to a signed \(\{-1,+1\}\) contrast internally where their formulas require it; that recoding is local to those classes and does not affect this public convention.

Saving and loading

Design (and its DesignSeqOneByOne subclasses) is the unit of persistence for a trial. Persist a des_obj with base R's saveRDS()/readRDS() – there is no dedicated save_edi_design()/load_edi_design() wrapper, and none is planned: the audit behind this section found nothing that needs transformation on load beyond what is documented here. Inference* objects are disposable, cheaply reconstructed from a Design object on demand (see each class's $new()), and must never be saveRDS()'d directly – nothing currently prevents it (they serialize "successfully" like any R6 object), but the result is a frozen snapshot a user could easily mistake for something that stays live against the design, and re-running inference from a reloaded Design is both cheap and the only tested path.

Worked example (mirrors the round-trip tests in R/EDI/tests/testthat/test-save-load-design.R):


des_obj = DesignSeqOneByOneBernoulli$new(n = 20, response_type = "continuous")
for (i in 1:10) {
  des_obj$add_one_subject_to_experiment_and_assign(data.frame(x1 = rnorm(1)))
  des_obj$add_one_subject_response(i, y = rnorm(1))
}
saveRDS(des_obj, "trial.rds", version = 2)

# ...new R session...
des_obj = readRDS("trial.rds")
for (i in 11:20) {
  des_obj$add_one_subject_to_experiment_and_assign(data.frame(x1 = rnorm(1)))
  des_obj$add_one_subject_response(i, y = rnorm(1))
}
inf_obj = InferenceContinOLS$new(des_obj) # reconstructed fresh, never persisted
inf_obj$compute_estimate()

Passing version = 2 to saveRDS() is recommended, matching the one existing internal precedent for RDS serialization in this package (SimulationFramework's replication cache); it is not required for a same-R-version round trip.

Version stamp. Every Design object records the package version it was constructed under (get_edi_version_created()). This is stamped once at construction and is not refreshed by readRDS() – it reflects the version that originally built the object, not whatever version is currently loaded. The first "resume the trial" call after a reload (draw_ws_according_to_design() for fixed designs, add_one_subject_to_experiment_and_assign() for sequential designs) compares the stamped version's major component against the currently loaded package's major component and emits a one-time warning() on a mismatch; minor/patch differences are silent, since most field additions are additive under this class's lock_objects = FALSE R6 fields and do not warrant nagging on every routine upgrade. Objects saved before this field existed self-initialize it to the currently loaded version the first time it is read, rather than erroring on the missing field.

RNG/reproducibility caveat. private$seed is consumed only once, inside maybe_set_seed() at construction time, and is not re-applied on readRDS(). Continuing to enroll subjects after a reload therefore draws from whatever the global .Random.seed happens to be in the new session, not a deterministic continuation of the original stream. This is almost certainly the right behavior for a real trial (bit-for-bit-reproducible continuation across a process restart is not a property a production trial should have), but it means a same-seed reload-and-continue is not expected to reproduce the same draws as an uninterrupted run with that seed – do not rely on that for testing.

Known non-serializable case. A DesignFixedOptimal constructed with objective = "custom" from a raw RcppXPtrUtils::cppXPtr() external pointer (rather than a C++ source string) cannot be safely reloaded: compiled function pointers do not survive a saveRDS()/readRDS() round trip, and there is no retained source to recompile from. This is detected on first use after reload and raises a clear error rather than failing silently; supply custom_objective as a C++ source string instead of a pre-built cppXPtr() object if you need this design to survive a save/reload cycle – that form recompiles itself automatically the first time it is used post-reload. Every other audited private cache on Design and its components (all_subject_data_cache, permutations_cache, lin_centered_covariates, matching/blocking/cluster component state such as m, xm_structural, boot_pair_rows) was traced to its originating C++ return type and confirmed to hold only plain R matrices/vectors/lists, not external pointers or other non-serializable values.

Active bindings

num_cores

Current number of cores in the global budget.

Methods

Public methods


Design$is_blocking_design()

Check whether this design currently has blocking structure.

The base implementation returns FALSE. Designs that compose BlockingStructure override this method with the structural check.

Usage

Design$is_blocking_design()

Returns

FALSE for designs without BlockingStructure.


Design$is_matching_design()

Check whether this design currently has matching structure.

The base implementation returns FALSE. Designs that compose MatchingStructure override this method with the structural check.

Usage

Design$is_matching_design()

Returns

FALSE for designs without MatchingStructure.


Design$is_a_kk_matching_capable()

Characterization: is this a KK matching-on-the-fly-capable design (sequential KK or its fixed binary-match equivalent)? Default FALSE; overridden to TRUE on DesignSeqOneByOneKK14 and DesignFixedBinaryMatch.

Usage

Design$is_a_kk_matching_capable()


Design$is_a_cluster_capable()

Characterization: is this a cluster-structured design? Default FALSE; overridden to TRUE on DesignFixedCluster and DesignFixedBlockedCluster.

Usage

Design$is_a_cluster_capable()


Design$is_a_bernoulli_capable()

Characterization: is this a Bernoulli-randomized design? Default FALSE; overridden to TRUE on DesignSeqOneByOneBernoulli and DesignFixedBernoulli.

Usage

Design$is_a_bernoulli_capable()


Design$new()

Initialize an experimental design

Usage

Design$new(
  response_type,
  prob_T = 0.5,
  include_is_missing_as_a_new_feature = FALSE,
  n = NULL,
  verbose = FALSE,
  missingness_method = "impute",
  design_formula = ~.,
  ordinal_levels = NULL,
  seed = NULL
)

Arguments

response_type

"continuous", "incidence", "proportion", "count", "survival", or "ordinal".

prob_T

Probability of treatment assignment.

include_is_missing_as_a_new_feature

Flag for missingness indicators.

n

The sample size (if fixed).

verbose

Flag for verbosity.

missingness_method

How to handle missing values in covariates when building the model matrix for inference. One of:

"impute" (default)

Missing values are filled in using random-forest imputation (missRanger, falling back to missForest on failure). The response vector is included as an auxiliary predictor when available. This preserves all covariates and all subjects but introduces imputed values that influence inference.

"drop_column"

Any covariate column that contains at least one missing value is dropped entirely from the model matrix before inference. No values are invented; the remaining complete columns are used as-is. This is conservative but transparent.

"error"

An error is thrown as soon as any missing value is detected in the covariate matrix. Use this when you want to guarantee that inference runs on exactly the data you supplied, with no silent modification.

design_formula

A formula object used to create the design matrix from covariates. Default is ~ ..

ordinal_levels

If the response type is "ordinal", the labels for the levels.

seed

Integer seed for reproducibility.

Returns

A new `Design` object


Design$add_one_subject_response()

For CARA designs, add a single subject response.

Usage

Design$add_one_subject_response(t, y = NULL, y_L = NULL, y_R = NULL)

Arguments

t

The subject index.

y

The exact response value. Supply this XOR both y_L and y_R – never together, never just one of the two.

y_L

For a censored survival response, the lower bound of the event-time interval. Right-censored: the last known event-free time (pair with y_R = Inf). Left-censored: 0, which must be stated explicitly rather than defaulted. Interval-censored: the interval's lower bound. Storage accepts any well-formed left-/interval-censored value; whether a given Inference class can actually consume it depends on that class (most survival Inference classes still only accept exact/right-censored data and will reject construction with a clear error otherwise – see individual class docs).

y_R

For a censored survival response, the upper bound of the event-time interval. Right-censored: Inf. Left-/ interval-censored: the confirmed-by time / interval upper bound.


Design$add_all_subject_responses()

For non-CARA designs, add all subject responses.

Usage

Design$add_all_subject_responses(ys = NULL, y_Ls = NULL, y_Rs = NULL)

Arguments

ys

The exact responses as a numeric vector, NA for any subject whose response is censored (supply y_Ls/y_Rs for those instead).

y_Ls

The censored-response lower bounds, NA for any subject with an exact response in ys. Right-censored: the last known event-free time (pair with y_Rs = Inf). Left-censored: 0, stated explicitly. Interval-censored: the interval's lower bound. Storage accepts any well-formed left-/interval-censored value; whether a given Inference class can actually consume it depends on that class (most survival Inference classes still only accept exact/ right-censored data and will reject construction with a clear error otherwise – see individual class docs).

y_Rs

The censored-response upper bounds, NA for any subject with an exact response in ys. Right-censored: Inf. Left-/interval-censored: the confirmed-by time / interval upper bound.


Design$overwrite_all_subject_assignments()

For analysis on already-completed experimental data

Usage

Design$overwrite_all_subject_assignments(w)

Arguments

w

A {0,1} vector of subject assignments (1 = treated, 0 = control).


Design$is_fixed_sample_size()

Check if this design was initialized with a fixed sample size n

Usage

Design$is_fixed_sample_size()

Returns

TRUE if fixed.


Design$assert_all_subjects_arrived()

Asserts if all subjects arrived.

Usage

Design$assert_all_subjects_arrived()


Design$assert_all_responses_recorded()

Asserts if all responses are recorded.

Usage

Design$assert_all_responses_recorded()


Design$check_experiment_completed()

Checks if the experiment is completed.

Usage

Design$check_experiment_completed()

Returns

TRUE if experiment is complete, FALSE otherwise.


Design$assert_even_allocation()

Checks if the experiment has a 50-50 allocation.

Usage

Design$assert_even_allocation()


Design$assert_fixed_sample()

Checks if the experiment has a fixed sample size.

Usage

Design$assert_fixed_sample()


Design$any_censoring()

Checks if the experiment has any censored responses

Usage

Design$any_censoring()

Returns

TRUE if any censored.


Design$has_general_censoring()

Checks if the experiment has any left- or interval-censored survival responses – i.e. any subject whose y_R is finite (right-censored subjects have y_R = Inf, which is excluded). Most survival Inference classes cannot yet consume this shape of data (see get_effective_time()/get_effective_dead()); this is the check Inference$initialize() uses to reject construction cleanly for those classes.

Usage

Design$has_general_censoring()

Returns

TRUE if any subject is left- or interval-censored.


Design$get_t()

Get t

Usage

Design$get_t()

Returns

The current number of subjects.


Design$get_X_raw()

Get raw X information

Usage

Design$get_X_raw()

Returns

A data frame of subject data.


Design$get_X_imp()

Get imputed X information

Usage

Design$get_X_imp()

Returns

Same as Xraw except with imputations.


Design$get_X()

Get X matrix

Usage

Design$get_X()

Returns

A numeric matrix of subject data.


Design$get_y()

Get y

Usage

Design$get_y()

Returns

A numeric vector of subject responses.


Design$get_y_original()

Get y_original

Usage

Design$get_y_original()

Returns

A numeric vector of the original subject responses.


Design$get_w()

Get w

Usage

Design$get_w()

Returns

A {0,1} vector of subject assignments (1 = treated, 0 = control).


Design$draw_ws_according_to_design()

Draw treatment assignment vectors according to the design.

Usage

Design$draw_ws_according_to_design(r = 1L)

Arguments

r

Number of vectors to draw. Default is 1.

Returns

A matrix of size n x r with {0,1} entries (1 = treated, 0 = control).


Design$capabilities()

Returns the capabilities this design instance exposes (see fix_design_hierarchy.md, "Capability Model").

Deliberately instance-level, not a class-registry read (fix_design_hierarchy.md, TODO-28): is_blocking_design()/is_matching_design() depend on real construction-time state (e.g. private$m/private$blocking_capable), not just which components a class composes – DesignFixediBCRD constructed with an unknown n, for instance, composes BlockingStructure but is not blocking-capable for that particular instance. A class-registry-only answer (this function briefly unioned in get_effective_design_capabilities(), a purely class-level, component- composition-based check) would silently report "blocking" for every instance of such a class regardless of its actual construction state – confirmed as a real, reproducible false positive during this TODO's implementation, not a hypothetical. get_effective_design_capabilities()/ design_class_registry.R's direct_components still exist and are correct – they're the right tool for a generator-only query with no instance in hand (see design_class_generator_supports_batch_w_pregeneration()), just not for this instance-level method.

Usage

Design$capabilities()

Returns

A character vector of capability names.


Design$supports()

Returns whether this design object supports a capability. See capabilities().

Usage

Design$supports(capability)

Arguments

capability

A capability name, e.g. "blocking", "matching", or "batch_w_pregeneration".

Returns

TRUE if the capability is present, FALSE otherwise.


Design$applicable_inference_class_names()

Returns the sorted character vector of concrete, exported Inference class names legal for this design object under default constructor arguments, derived purely from this design's own normalized metadata (response type, KK-matching capability, blocking, and both censoring axes) filtered through the registry's compatibility predicates – the same normalization and predicate logic InferenceSuite uses for discovery (see normalize_inference_design_metadata() and is_inference_class_compatible_with_design_metadata() in inference_suite.R). No candidate class is constructed to determine applicability, so this has no side effects and cannot be influenced by a constructor failure or a missing optional package (see unavailable_inference_classes_due_to_missing_packages() for that case, reported separately). A class whose censoring tolerance depends on non-default constructor arguments (e.g. InferenceSurvivalCoxPHRegr only tolerates general censoring with testing_type = "wald") is listed here when its default configuration is compatible; a construction-time error for an incompatible non-default argument combination remains the documented behavior of that class's initialize().

Usage

Design$applicable_inference_class_names()

Returns

A sorted character vector of applicable Inference class names.


Design$unavailable_inference_classes_due_to_missing_packages()

Companion to applicable_inference_class_names(): returns the subset of otherwise design-compatible Inference classes that are excluded solely because a registered required_packages entry is not installed, as a named list (class name -> character vector of missing package names) – kept separate from plain design incompatibility so callers can tell "not applicable to this design" apart from "applicable, but an optional dependency isn't installed."

Usage

Design$unavailable_inference_classes_due_to_missing_packages()

Returns

A named list, class name -> missing package names; empty list if none.


Design$incompatible_inference_classes_due_to_design_structure()

Companion to applicable_inference_class_names(): returns the subset of otherwise design-compatible Inference classes that are excluded because they declared a design_compatibility_reason predicate (a design-*structure* requirement, e.g. even treatment allocation or equal block sizes, beyond what response type/KK/blocking/censoring metadata alone can express) and this design object fails it, as a named list (class name -> one-line reason string) – kept separate from plain design incompatibility and from a missing package for the same reason unavailable_inference_classes_due_to_missing_packages() is kept separate: so callers can tell exactly why a class is missing from applicable_inference_class_names() instead of only discovering it as a construction-time error.

Usage

Design$incompatible_inference_classes_due_to_design_structure()

Returns

A named list, class name -> reason string; empty list if none.


Design$randomization_family()

Returns this design object's registry-backed randomization family (see fix_design_hierarchy.md, "Class Metadata"), e.g. "kk14", "bernoulli", "rerandomization". Replaces class-identity (inherits()/is()) dispatch at call sites that need to distinguish design variants (see "Class-Identity Dispatch Replacement"). Returns NA_character_ if the class is not registered or is one of the unsplit/timing-root abstract bases.

Usage

Design$randomization_family()

Returns

A single character string (or NA_character_).


Design$supports_resampling()

Check if the design supports resampling at all – FALSE only for the abstract timing-family bases themselves (DesignFixed, DesignSeqOneByOne, and their custom-extension abstract bases) instantiated directly; TRUE for every concrete subclass, including ObservationalDesign. This is the general check for resampling methods that never need the design's own randomization mechanism – plain nonparametric bootstrap, Bayesian bootstrap, m-out-of-n bootstrap, PRW subsampling – which only resample already-observed units/rows and their fixed, observed assignment, so they remain valid and available even for a design with no randomization mechanism at all (see ObservationalDesign's class documentation: "resampling subjects with their observed, fixed assignment does not require a known randomization probability"). Contrast with supports_randomization_draw()/ supports_resampling_replay() below, which gate the narrower set of methods that actually do need to invoke the design's mechanism (a plain randomization test/CI, or a bootstrap randomization test that re-randomizes resampled data) and are therefore FALSE for ObservationalDesign specifically – see fix_design_hierarchy.md, "Observational Design Migration" for the live bug that split fixes.

Usage

Design$supports_resampling()

Returns

TRUE if supported.


Design$supports_randomization_draw()

Check if this design can draw a fresh treatment assignment from its own randomization mechanism – the eligibility condition for permutation-style randomization tests/CIs (compute_rand_two_sided_pval() and friends), which redraw \(w\) directly. FALSE for the abstract timing-family bases themselves (same as supports_resampling()) and, unlike supports_resampling(), also FALSE for ObservationalDesign (no draw mechanism at all – \(w\) is supplied by the user, so there is nothing to redraw); TRUE for every other concrete subclass. See supports_resampling()'s documentation for why this is a narrower, separate capability rather than reusing that one, and "Observational Design Migration" for the live bug this fixes (ObservationalDesign previously answered the old, unsplit supports_resampling() TRUE, silently passing the randomization-test eligibility assert before failing later and deeper, inside draw_ws_raw()'s throwing stub).

Usage

Design$supports_randomization_draw()

Returns

TRUE if a fresh randomization draw is supported.


Design$supports_resampling_replay()

Check if this design's mechanism can be faithfully replayed against resampled data – the eligibility condition specifically for the bootstrap randomization test (BRT), which resamples units and then re-randomizes each resample using the design's own mechanism (see inference_all_abstract_rand_bootstrap.R's repeated draw_ws_according_to_design() calls). Not the eligibility condition for plain nonparametric/Bayesian/m-out-of-n/PRW-subsampling bootstrap – those never redraw \(w\) at all (they resample already-observed units and their fixed, observed assignment) and are gated by the broader supports_resampling() instead, which stays TRUE for ObservationalDesign. FALSE for the same abstract timing-family bases as supports_randomization_draw() and for ObservationalDesign (no randomization mechanism to replay); TRUE for every other concrete subclass. See supports_randomization_draw()'s documentation for why this is a separate capability rather than the same flag reused.

Usage

Design$supports_resampling_replay()

Returns

TRUE if bootstrap-randomization-test-style replay is supported.


Design$prepare_for_resampling_replay()

Hook invoked by the bootstrap-randomization-test machinery on a design object whose assignment mechanism is about to be replayed against resampled data (once per replicate draw site, ahead of draw_ws_according_to_design(1L)). The base implementation is a no-op; designs whose replay is a full re-optimization (DesignFixedOptimal) override it to switch to their per-replicate solver profile (solver_args$brt_*). Idempotent.

Usage

Design$prepare_for_resampling_replay()

Returns

invisible(NULL).


Design$warm_all_subject_data_cache()

Warm the per-subject assignment-data cache, when this design uses covariates. This is an internal optimization hook for randomization inference; it keeps cache mutation inside the Design object instead of exposing its private environment to callers.

Usage

Design$warm_all_subject_data_cache()

Returns

TRUE invisibly when a cache warm-up was attempted, or FALSE invisibly when the design does not use covariates.


Design$get_n()

Get n, the sample size

Usage

Design$get_n()

Returns

The number of subjects.


Design$get_y_L()

Get y_L

Usage

Design$get_y_L()

Returns

A numeric vector of censored-response lower bounds (NA for exact-response subjects).


Design$get_y_R()

Get y_R

Usage

Design$get_y_R()

Returns

A numeric vector of censored-response upper bounds (NA for exact-response subjects).


Design$get_effective_time()

Get the effective response time per subject: the exact value y where recorded, or the lower bound y_L for a censored subject. This reconstructs "the one informative number" every response type other than left-/interval-censored survival data has always had, for code that needs a single numeric value per subject rather than the y/y_L/ y_R triple directly.

Usage

Design$get_effective_time()

Returns

A numeric vector, one value per subject.


Design$get_effective_dead()

Get the effective event indicator per subject: 1 for an exact response, 0 for a censored one. This reconstructs today's dead semantics for right-censored survival data (and is trivially all-1 for every other response type, which never has censoring). It is only valid for exact/right-censored data – a left- or interval-censored subject also returns 0 here, which is not meaningful right-censoring status, so callers must confirm (e.g. via any_censoring() plus their own censoring-shape checks) that no such rows are present before relying on this value.

Usage

Design$get_effective_dead()

Returns

An integer vector, one value per subject.


Design$get_prob_T()

Get probability of treatment

Usage

Design$get_prob_T()

Returns

The specified probability.


Design$get_response_type()

Get response type

Usage

Design$get_response_type()

Returns

The specified response type.


Design$get_response_type_original()

Get the original response type

Usage

Design$get_response_type_original()

Returns

The original specified response type.


Design$get_ordinal_levels()

Get ordinal levels

Usage

Design$get_ordinal_levels()

Returns

The levels of the ordinal response.


Design$get_original_ordinal_levels()

Get original ordinal levels

Usage

Design$get_original_ordinal_levels()

Returns

The labels for the levels of the original ordinal response.


Design$get_missingness_method()

Get the missingness method

Usage

Design$get_missingness_method()

Returns

The missingness handling method: "impute", "drop_column", or "error".


Design$get_edi_version_created()

Get the EDI package version this object was created under.

Stamped once, at construction time, from utils::packageVersion("EDI"); never re-stamped on readRDS() reload, so it reflects the version that originally built the object rather than whatever version is currently loaded. Objects saved before this field existed self-initialize it to the currently loaded version the first time it is read (there is no way to recover the true original version for those objects), rather than erroring on the missing field.

Usage

Design$get_edi_version_created()

Returns

A character string, e.g. "1.0.0".


Design$transform_y()

Transform the response vector y

Usage

Design$transform_y(
  transform_fun,
  transformed_response_type,
  ordinal_levels = NULL
)

Arguments

transform_fun

A function that takes y_original and returns a new y.

transformed_response_type

The response type of the transformed y.

ordinal_levels

If the transformed response type is "ordinal", the labels for the levels.


Design$get_design_formula()

Get the model formula

Usage

Design$get_design_formula()

Returns

The model formula.


Design$duplicate()

Duplicate this design object

Usage

Design$duplicate(verbose = FALSE)

Arguments

verbose

A flag for verbosity.

Returns

A new `Design` object with the same data


Design$clone()

The objects of this class are cloneable with this method.

Usage

Design$clone(deep = FALSE)

Arguments

deep

Whether to make a deep clone.

Examples

if (FALSE) { # \dontrun{
# Design is abstract and cannot be instantiated directly; construct a
# concrete subclass instead, e.g.:
seq_des = DesignSeqOneByOneBernoulli$new(n = 6, response_type = 'continuous')
seq_des$add_one_subject_to_experiment_and_assign(data.frame(x1 = rnorm(1)))
} # }