The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
EDI is implemented with R6 classes. Advanced users can define their own R6 classes outside the package and reuse EDI’s design storage, response handling, randomization, bootstrap, and summary methods. This page is the supported extension contract: which base classes to build on, the one method each asks you to implement, and the rules that keep your class working with the rest of the package. It is written for authors working outside EDI; contributing a class to EDI itself is a different, heavier process (see the last section).
Both the Inference* and Design* hierarchies
are shallow and component-based. Inheritance answers only one question —
“is every child substitutable for this parent as the same kind of
estimator / design?” — and every optional behavior is a registered
component composed by a factory, with every optional
public method backed by a capability:
define_inference_class(), from registered
components (Wald, LikelihoodTests,
NonparametricBootstrap, RandomizationTest,
BayesianBootstrap, Jackknife,
ParametricLikelihoodBootstrap, the KK pass-through/GEE/GLMM
engines, per-model likelihood components, …). The factory validates
component contracts, name collisions, and capability tables at
definition time. The legacy algorithmic inheritance ladder
(InferenceRand, InferenceNonParamBootstrap,
InferenceAsymp, InferenceAsympLik,
InferenceParamBootstrap, …) survives only as internal
component sources with no concrete descendants — do not inherit
from those classes; they are not a supported surface and may be
removed.define_design_class()
over the design component registry (blocking, matching, cluster,
sequential-strata bootstrap, batch pre-generation), with
DesignFixed and DesignSeqOneByOne as the two
timing-family bases directly under Design.obj$capabilities() and
obj$supports("<capability>") on both
Inference and Design objects. Public optional
method presence equals capability presence: there are no
supports_*() flag pairs or throwing stubs on concrete
classes.InferenceSuite,
Design$applicable_inference_class_names(),
Design$unavailable_inference_classes_due_to_missing_packages()
— reads the package’s class registries, which are populated by scanning
the EDI namespace when the package loads.The consequence for you is simple: build on the custom shells below (they are themselves factory-built, so the components and capabilities are already wired), implement the one documented hook, and call your class directly — registry-driven discovery will never list an external class.
The shells are intentionally internal while the extension contract is
experimental. Retrieve them with getFromNamespace():
InferenceCustomAsymp <- getFromNamespace("InferenceCustomAsymp", "EDI")
InferenceCustomRand <- getFromNamespace("InferenceCustomRand", "EDI")
InferenceCustomBoot <- getFromNamespace("InferenceCustomBoot", "EDI")
DesignFixedCustom <- getFromNamespace("DesignFixedCustom", "EDI")
DesignCustomSequential <- getFromNamespace("DesignCustomSequential", "EDI")A custom asymptotic inference class inherits from
InferenceCustomAsymp (built on Inference with
the Wald and NonparametricBootstrap
components) and implements a public
fit(estimate_only = FALSE) method that returns a named list
with:
estimate: required numeric scalar treatment-effect
estimate.se: optional numeric scalar standard error.df: optional degrees of freedom. Use
NA_real_ for z inference.model: optional fitted model object retained by
get_mod().nonestimable_reason: optional character scalar used
when the estimate or standard error is unavailable; it flows through
is_nonestimable() / get_nonestimable_reason()
and the public methods return NA.When estimate_only = TRUE (resampling loops) only
estimate is needed; skip the variance work.
Read data through the public accessors, never private fields:
get_response(), get_treatment(),
get_covariates(), get_analysis_data(),
get_design_object(), get_response_type().
InferenceMedianDiff <- R6Class(
"InferenceMedianDiff",
inherit = InferenceCustomAsymp,
# Required when subclassing EDI's factory-built classes: lazily loaded
# components install their real methods onto the object after construction,
# which needs an unlocked environment.
lock_objects = FALSE,
public = list(
fit = function(estimate_only = FALSE) {
dat <- self$get_analysis_data()
y_t <- dat$y[dat$w == 1]
y_c <- dat$y[dat$w == 0]
est <- stats::median(y_t) - stats::median(y_c)
if (estimate_only) {
return(list(estimate = est))
}
list(
estimate = est,
se = sqrt(stats::var(y_t) / length(y_t) + stats::var(y_c) / length(y_c)),
df = length(y_t) + length(y_c) - 2,
model = NULL
)
}
)
)
des <- DesignFixedBernoulli$new(n = 20, response_type = "continuous", verbose = FALSE)
des$add_all_subjects_to_experiment(data.frame(x = seq_len(20)))
des$overwrite_all_subject_assignments(rep(c(0, 1), each = 10))
des$add_all_subject_responses(rnorm(20))
inf <- InferenceMedianDiff$new(des)
inf$compute_estimate()
#> [1] 0.1369039
inf$compute_asymp_two_sided_pval()
#> [1] 0.8177541
inf$compute_asymp_confidence_interval()
#> 2.5% 97.5%
#> -1.093143 1.366950
inf$compute_bootstrap_two_sided_pval(B = 101, show_progress = FALSE)
#> [1] 0.7920792
inf$capabilities()
#> [1] "jackknife" "wald"
#> [3] "randomization_test" "randomization_ci"
#> [5] "nonparametric_bootstrap"
inf$supports("wald")
#> wald
#> TRUEInferenceCustomRand is built on Inference
with the RandomizationTest component
(likelihood_tier = "none"). Implement the same
fit(estimate_only = FALSE) and return at least
estimate; you get compute_estimate() plus
EDI’s randomization-test machinery
(compute_rand_two_sided_pval()), and nothing requires a
standard error.InferenceCustomBoot is built on Inference
with the NonparametricBootstrap component (which
transitively brings the randomization-test/CI machinery it depends on).
Implement fit() returning estimate (optionally
model and nonestimable_reason) and you get the
bootstrap p-value and confidence-interval methods.InferenceMedianDiffRand <- R6Class(
"InferenceMedianDiffRand",
inherit = InferenceCustomRand,
lock_objects = FALSE,
public = list(
fit = function(estimate_only = FALSE) {
dat <- self$get_analysis_data()
list(estimate = stats::median(dat$y[dat$w == 1]) - stats::median(dat$y[dat$w == 0]))
}
)
)
inf_rand <- InferenceMedianDiffRand$new(des)
inf_rand$compute_estimate()
#> [1] 0.1369039
inf_rand$compute_rand_two_sided_pval(r = 200, show_progress = FALSE)
#> [1] 0.74
inf_rand$capabilities()
#> [1] "randomization_test" "randomization_ci"
InferenceMedianDiffBoot <- R6Class(
"InferenceMedianDiffBoot",
inherit = InferenceCustomBoot,
lock_objects = FALSE,
public = list(
fit = function(estimate_only = FALSE) {
dat <- self$get_analysis_data()
list(estimate = stats::median(dat$y[dat$w == 1]) - stats::median(dat$y[dat$w == 0]))
}
)
)
inf_boot <- InferenceMedianDiffBoot$new(des)
inf_boot$compute_bootstrap_confidence_interval(B = 101, show_progress = FALSE)
#> 2.5% 97.5%
#> -1.702487 1.242510lock_objects = FALSE when
subclassing an EDI inference or design class. Inference classes use
lazily loaded components that install methods onto private
after construction, and some classes create private config fields inside
initialize(); a locked subclass constructs but fails at
first use with a locked-binding error.*Abstract* bases, and never copy a component’s
method lists into your own class — the factory’s validation is the only
supported way to compose components, and EDI bans that pattern for its
own code.capabilities() walks class(self) and returns
the first registered class’s capabilities, so an
InferenceCustomAsymp subclass reports the Wald/bootstrap
family it inherited and supports() works. Public methods
you add on top are ordinary R6 methods — callable directly, but
not capabilities, so capability-driven filtering
(InferenceSuite, SimulationFramework) does not
see them.InferenceSuite and
Design$applicable_inference_class_names() enumerate
registered package classes only; construct and call extension classes
explicitly.Inference.
Do not redeclare private fields such as m, X,
w, y, optimization_alg, or the
caches in a subclass; read data through the public accessors above.The design shells are factory-built bases
(DesignFixedCustom inherits DesignFixed;
DesignCustomSequential inherits
DesignSeqOneByOne) that route all randomization through one
user hook:
DesignFixedCustom: implement public
draw_assignments(r) and return an n x r 0/1
assignment matrix. EDI validates the shape and values (when argument
checking is enabled) and uses it for every draw, including the
randomization-inference draws.DesignCustomSequential: implement public
assignment_rule() and return a scalar 0/1 assignment for
the current subject.EDI handles subject storage, response recording, and validation. Pass
lock_objects = FALSE here too. When your inference code
needs to know what a design can do, use des$capabilities()
/ des$supports() — the vocabulary is
"blocking", "matching",
"cluster", "batch_w_pregeneration",
"resampling", "randomization_draw",
"resampling_replay" — rather than class-identity checks.
The same unregistered-subclass fallbacks apply on this side: instance
capability queries work for any subclass, unregistered names are treated
as concrete (freely instantiable), and the package’s inference classes
are discoverable on a custom design exactly as on a built-in one because
discovery keys on design metadata, not design class.
DesignFixedAlternating <- R6Class(
"DesignFixedAlternating",
inherit = DesignFixedCustom,
lock_objects = FALSE,
public = list(
draw_assignments = function(r = 1) {
n <- self$get_n()
matrix(rep_len(c(0, 1), n), nrow = n, ncol = r)
}
)
)
des_alt <- DesignFixedAlternating$new(n = 10, response_type = "continuous", verbose = FALSE)
des_alt$add_all_subjects_to_experiment(data.frame(x = 1:10))
des_alt$assign_w_to_all_subjects()
des_alt$get_w()
#> [1] 0 1 0 1 0 1 0 1 0 1
des_alt$capabilities()
#> [1] "resampling" "randomization_draw" "resampling_replay"
des_alt$add_all_subject_responses(rnorm(10))
head(des_alt$applicable_inference_class_names())
#> [1] "InferenceAllSimpleAverageDiff" "InferenceAllSimpleMeanDiffPooledVar"
#> [3] "InferenceAllSimpleWilcox" "InferenceContinLin"
#> [5] "InferenceContinOLS" "InferenceContinQuantileRegr"
DesignSeqEveryOther <- R6Class(
"DesignSeqEveryOther",
inherit = DesignCustomSequential,
lock_objects = FALSE,
public = list(
assignment_rule = function() as.numeric(self$get_t() %% 2 == 0)
)
)
des_seq <- DesignSeqEveryOther$new(n = 6, response_type = "continuous", verbose = FALSE)
for (i in 1:6) des_seq$add_one_subject_to_experiment_and_assign(data.frame(x = i))
des_seq$get_w()
#> [1] 0 1 0 1 0 1The current shell set — DesignFixedCustom,
DesignCustomSequential, InferenceCustomAsymp,
InferenceCustomRand, InferenceCustomBoot — is
sufficient for the extension contract above. There is no exact-test or
parametric-bootstrap shell: the ExactTest component
dispatches through private exact-test implementations, and the
ParametricLikelihoodBootstrap component requires
likelihood-null simulation/refit hooks; neither is a simple
fit() shell, so exposing them would need a separate API
design. Likewise there are no response-family-specific shells — the
generic analysis-data accessors are the intended surface.
Adding a class inside the package is a different contract:
the class must go through define_inference_class() /
define_design_class() with exact component, capability, and
registry metadata, meet the package documentation standard, and be
registered with the test harnesses, C++ kernels, Python bindings, and
benchmarks. That process lives in the repository, in
R/package_metadata/contracts/new_model_creation.md, which
builds on the architecture summarized in this vignette rather than
repeating it.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.