EDI 1.0.5
A correctness release. The algorithm audit and the comprehensive-test sweeps found results that were wrong in specific classes; those are fixed below. Seeded draws are now identical on Linux, macOS and Windows and at every thread count, and CI checks this on each platform. No new statistical capability.
Breaking changes
Installing from GitHub source now builds the same portable configuration as CRAN (
EDI_PORTABLE=1), so results match across install methods and a compiled library no longer dies with “illegal instruction” on another CPU. The machine-tuned build (-march=native,-O3) is opt-in withEDI_PORTABLE=0; see “Tuning Local Builds” in the README.Seed-pinned draws change once. Every design and resampling kernel now shuffles with a portable Fisher-Yates on EDI’s Mersenne-Twister port instead of
std::shuffle, so the same seed gives the same draws on every platform (they differed by C++ standard library).DesignFixedGreedy,DesignFixedGreedyDOptimal,DesignFixedMatchingGreedyPairSwitchingandDesignFixedRerandomizationderive their streams from the draw index, so draws no longer depend on the OpenMP thread count. Parametric- and randomization-bootstrap replicate seeds are drawn without replacement.Randomization confidence intervals (
compute_rand_confidence_interval(),compute_rand_bootstrap_confidence_interval()) invert the two-sided p-value atalpha, giving the documented1 - alphalevel; they inverted atalpha / 2before and were too wide. The defaultpval_epsilonfor these and for the exact Zhang interval tightened from 0.005 to 1e-6.Binary matching minimizes plain Mahalanobis or Euclidean distance, as
nbpMatching::gendistance()does, instead of squared distance, which can change the selected pairs.match_objective = "squared_distance"onDesignFixedBinaryMatchandDesignFixedMatchingGreedyPairSwitchingrestores the old objective.DesignSeqOneByOneKK21andDesignSeqOneByOneKK21stepwisesurvival weights come from a correct Weibull AFT fit (the previous iteration converged to the wrong fixed point), which changes survival allocations.InferenceSurvivalRestrictedMeanDifftruncates both arms at one shared horizon, the smaller of the two arms’ maximum follow-up.DesignFixedOptimalbootstrap-randomization replays reuse the observed allocation’s solver and budget; the cheaper annealing profile is opt-in throughbrt_solver. The old default replayed a weaker solver and the test was conservative.Jackknife p-values and intervals are centred on the jackknife estimate rather than the full-sample estimate.
Methods that could not give a valid answer now error instead of returning one:
InferenceIncidKKModifiedPoissonoffers Wald inference only (with a cluster-robust SE);type = "smoothed"randomization bootstrap is refused for categorical and bounded refits, Ridit and Jonckheere-Terpstra; the exact-test classes refuse bootstrap-weighted estimates; andInferenceOrdinalKKCondAdjCatLogitRegrdrops the Bayesian bootstrap.
New features
DesignSeqOneByOneKK21stepwise$new(condition_on_ws_for_rand_tests = FALSE)replays the full sequential allocation in randomization tests (exact but slow); the default keeps the fast conditional draws.Randomization intervals for
InferenceContinKKQuantileRegrOneLikandInferencePropKKQuantileRegrOneLikwork again; 1.0.2 disabled them.Studentized Bayesian-bootstrap intervals for the Weibull, KK Weibull marginal, Weibull-frailty and modified-Poisson classes.
InferenceSurvivalKMDiff$compute_bayesian_bootstrap_two_sided_pval()drops non-finite draws by default (na.rm = TRUE).
Bug fixes
InferenceOrdinalRidit(reference = "treatment")returned an estimate of 0 andp = 1for every dataset; it now reports0.5 - mean_control_riditwith the SE from the control arm. Ridit also misscored an interior category absent from the reference arm.InferenceIncidModifiedPoissonuses Zou’s robust sandwich variance for Wald inference; the model-based Poisson information gave 0.26% rejection at a nominal 5%.Randomization intervals for classes other than mean difference, OLS, Lin and KK OLS reused the null distribution shifted by
delta, which is only exact for those estimators; the others now recompute. The upper-bound refinement also moved the wrong endpoint.InferenceAllKKWilcoxIVWC: Hodges-Lehmann SEs lacked their normal calibration factors (about 2x too small), the bootstrap interval returned the asymptotic one, and randomization-bootstrap draws were on a different scale from the observed statistic. Other legacy KK*IVWCclasses (hurdle Poisson, robust regression, survival rank regression, mean difference) used placeholder variances or dropped the pair structure when resampling, givingNAor wrong p-values.Mixed models: the logistic GLMM Hessian double-counted the variance component, so SEs were wrong; logistic and Poisson GLMM and ordinal CLMM fits multi-start over the variance scale; the KK GEE exchangeable correlation was not divided by the dispersion (now matches
geepack). Bootstrap-weighted refits of the KK conditional-logit GLMM and the KK ordinal mixed classes fit the wrong model (a plain GLMM, or a fixed-effects proportional-odds model ignoring the pairs).InferenceOrdinalCloglogRegr’s parametric bootstrap: the sign mismatch left open in 1.0.2 is fixed (the kernel stores slopes with the opposite orientation from the shared simulator). Stereotype-logit and continuation-ratio fits gained multi-start.InferencePropZeroOneInflatedBetaRegr’s marginal mean difference used renormalized logistic probabilities instead of the model’s softmax.Designs:
DesignSeqOneByOneRandomBlockSizeredraws used the last subject’s stratum and mutated the design, and a singleblock_sizesvalue was mis-sampled;DesignSeqOneByOnePocockSimonmergedNAwith a literal"NA"level;DesignFixedOptimalBlockscould make block sizes differ by more than one.Incidence: the Newcombe p-value returned 1 when its root search failed and was imprecise for small p; the exact-binomial p-value is equal-tailed so it agrees with its Clopper-Pearson interval; the Zhang exact interval widens its bracket and reports
Infwhere the bound is infinite instead of a truncated finite value.Survival: stratified-Cox parametric-bootstrap simulation scaled every stratum by the global maximum;
InferenceSurvivalDepCensTransformbootstrap intervals are no longer replaced by Wald orNAbased on their sign or width; the Clayton-copula Weibull fitter overflowed at the theta bound; the KK Weibull marginal returnsNAwhen an arm has no events; Cox fallbacks use Breslow ties consistently.Stale caches: GLM, Cox and stratified-Cox design caches were keyed on
wonly; log-rank and Gehan returned stale values;Design$get_X_imp()shared memory with the raw covariates.InferenceAllSimpleAverageDifftreats bootstrap weights as frequency weights (it used a Kish effective sample size).Non-estimable cases: singular information matrices give
NASEs for the beta, NegBin, ZAP and ZINB fits instead of garbage; m-out-of-n and subsampling selection failures record a reason and returnNA; the Jonckheere-Terpstra exact enumeration is capped at 1e6 cells and falls back to the asymptotic p-value with a warning instead of hanging.Crashes: GC-protection faults in the KK21 ordinal and match-data kernels; repeated C++ compiles in
InferenceRandCustomexhaustedtempdir()inodes (compiles are now cached).
Performance
Randomization intervals cache the per-permutation slopes once, and
InferenceContinOLSrandomization runs in a C++ kernel. Bayesian-bootstrap weights are drawn in one batch, ZOIB reuses a bootstrap worker, and model matrices are built lazily.OpenMP kernels use the package thread maximum instead of resetting the global thread count, and SIMD hints apply under Apple clang without OpenMP.
EDI 1.0.2
CRAN release: 2026-10-07
This is the first version submitted to CRAN. Versions 1.0.0 and 1.0.1 were GitHub-only releases; their entries below record what changed in each. Most of 1.0.2 is correctness work found by the extended comprehensive test suite and by writing unit tests: resampling and randomization results that were silently wrong in specific classes, a few kernel defects (including one memory-safety bug), and standard-error and confidence-interval fixes.
Breaking changes
-
toggle_asserts(),set_num_cores(), and the internal thread-count bookkeeping it drives no longer set globaloptions()as a side effect (CRAN policy: a package must not modify and leave changed the user’s session options).toggle_asserts()now stores its flag in the package’s own internal state; calling it no longer changes whatgetOption("edi.run_asserts")returns (a user’s ownoptions(edi.run_asserts = ...)is still read and honored). Likewise,set_num_cores()/set_package_threads()no longer setoptions(mc.cores = ...)(nothing in EDI read it back; downstream code relying on this package to set it forparallel/pbmcapplyneeds to set it itself now) and no longer callfixest::setFixest_nthreads()(which setsoptions(fixest_nthreads), the user’s own option, not EDI’s to change). No change to which thread counts are actually used internally — only to these previously-observableoptions()side effects.
New features
-
InferenceCountQuasiPoissonnow composes the marginal-estimand component (MarginalEstimand), likeInferenceCountPoisson, so marginal (standardized) effect estimands are available for the quasi-Poisson class as well.
Bug fixes
Reused-worker resampling reused a stale cached fit across draws for classes whose fit is cached under a custom guard key (first found in
InferenceOrdinalGCompMeanDiff, whosecached_values$mdguard survived the loader’s narrow reset list). Every draw then returned the same number, the resampling distribution collapsed to a point mass, andcompute_rand_two_sided_pval()sat at its floor regardless of the true effect (reject rate 1.0 at a true null). The worker loaders for randomization, non-parametric bootstrap, m-out-of-n bootstrap and randomization-bootstrap draws now reset the cached fit state between draws, and a structural regression test asserts that every class exposing a reusable-worker method returns a non-degenerate distribution.InferencePropGCompMeanDiffandInferenceSurvivalGLMMWeibullFrailtyLoggammaIVWCreturnedNA(or a stale value) in randomization inference: the first from a sample-usability gate applied to the wrong resampling context, the second from its estimate-onlyw_starpooling. Both are fixed and no longer listed as known-broken.The KK survival compound classes
InferenceSurvivalKKLWACoxPHOneLik,InferenceSurvivalGLMMWeibullFrailtyLoggammaOneLikandInferenceSurvivalGLMMWeibullFrailtyLoggammaIVWCfed realNAs into the fitter for every censored subject during randomization inference:compute_treatment_estimate_during_randomization_inference()re-readyfrom the design and deriveddead = as.numeric(!is.na(y)), but since they/y_L/y_Rmigration the design’syusesNAto mark a censored observation. On simulated data with a strong effect the randomizationp-value was 0.02 with no censoring and 0.97 with about 20% censoring.y/deadare now derived asDesign$get_effective_time()/$get_effective_dead()do. A sweep of the other 16 survival classes found no further instance.InferenceOrdinalCloglogRegr’s parametric-bootstrapp-value was badly over-rejecting (60% at a true null). One cause is fixed: the delta-constrained null refit started from a single cold start and could stall; it is now multi-start (the same fixInferenceOrdinalStereotypeLogitRegrreceived earlier). A second cause, a sign mismatch in the shared bootstrap-data simulator, is root-caused but not fixed here.The Efron biased-coin randomization null draws (
generate_permutations) compared weighted arm counts (n_T * prob_Tvsn_C * (1 - prob_T)) instead of the raw arm countsDesignSeqOneByOneEfronactually compares, so for anyprob_T != 0.5the randomization null did not follow the design’s own assignment rule. It now uses the raw counts.fast_probit_regression’s L-BFGS objective held a danglingEigen::Refto a temporary, giving non-deterministic fits andNaNnegative log-likelihoods. The objective now owns its design matrix.fast_log_binomial_regression’s weighted log-likelihood let zero-weight rows constrain the[0, 1]mean support although they are absent from the likelihood; zero-weight rows are now skipped, so the identity- and log-link binomial fits no longer reject valid data because of them.The robust-regression bootstrap kernel accepted any
methodstring and would throw inside an OpenMP region, terminating R;methodis now validated ("M"or"MM") before the parallel region.stable_signature()(used to key cached randomization state) hashed a strided sample of the serialized object, which collided on 0/1 integer permutation matrices; it now hashes the full serialization.InferenceCountPoisson’s covariance falls back to the Fisher information when the kernel returns noX'WX;InferenceIncidLogRegrgained an estimand-aware standard error and degrees of freedom.Weibull and Cox fits with fixed (held) coefficients now compute the covariance from the free-parameter information block only and expand it back, so the free coefficients’ variances are finite and the fixed parameters’ rows and columns are
NaNby convention; the Cox cluster-robust sandwich does the same.OpenMP’s primary thread no longer polls R’s interrupt machinery while worker threads are active in the Wilcoxon-Hodges-Lehmann kernels.
fast_gaussian_lmm_gls_cpp()(the fixed-variance-component GLS solve used by the randomization and non-studentised bootstrap fast paths ofInferenceContinKKGLMM) mixedX'Xwith a cross-term scaled by an extra1/sigma_e^2, so it returned the wrong coefficient whenever the residual standard deviation was not exactly 1 (on a paired-plus-singleton fixture withsigma_e = 0.4the slope was -0.53 instead of the correct 0.63; atsigma_e = 1the two agreed). It now matches an explicit GLS to machine precision at everysigma_e.The KK21 stepwise weight selection for ordinal responses read a nonexistent field (
ssq_b_2) fromfast_ordinal_regression_with_var_cpp(); it now reads the returnedssq_b_j.fast_zero_one_inflated_beta_cpp()did not check the length of a suppliedwarm_start_params(orwarm_start_fisher_info, or the row counts ofX,X_zero_oneandy), so a mis-sized start let the optimizer read and write past its parameter buffer (heap corruption, found under valgrind; it aborted R withfree(): invalid next size). It now raises an ordinary error.InferencePropZeroOneInflatedBetaRegralways passed a correctly sized start and was not affected.fast_ordinal_clmmnow errors on mismatchedX/y/group_idlengths instead of reading out of bounds.InferenceIncidRiskDiff,InferenceCountRobustPoisson, and the zero-inflated/hurdle Poisson classes reported a confident (often zero-width) confidence interval andp-value on a perfectly or near-perfectly fit response (e.g.yfully separated byw, or constant), because the Huber-White/model-based standard error collapses to a numerically-zero-but-positive value there. Found via a rawcomprehensive_testsresults audit’s new adversarial-data/fault-injection sweep.robust_sandwich_variance()and the affected classes’ standard-error paths now treat a variance below.Machine$double.epsas non-estimable (NA), not as precision.InferenceCountHurdlePoisson/InferenceCountZeroInflatedPoissonreported a confident estimate andp-value on a hurdle fit that has no MLE by construction (every positive count equal to 1, so the zero-truncated Poisson likelihood only improves aslambda -> 0). Non-estimability is now decided from the data alone (not from what the optimizer/glmmTMB happens to return), so the result isNAon every platform.InferenceRandCustom$compute_rand_confidence_interval()’s fast path ignored the trialdeltashift during the randomization search, so the returned interval was in the custom statistic’s own scale (e.g. centred on a Welch-t value of ~2-4) rather than the response’s scale. Thecomprehensive_testsharness’s own custom-statistic randomization-CI check also fed it a non-translation-equivariant statistic (Welch t), which cannot give a meaningful CI under this or any correct implementation; it now uses a difference-in-means statistic for that check.InferenceAllSimpleWilcox$compute_asymp_confidence_interval(alpha)cached and returned the 95% Wilcoxon interval for everyalpha, so a 90% or 99% interval silently came back at the wrong nominal level.InferenceOrdinalAdjCatLogitRegr’s parametric-bootstrap confidence interval could be wildly wrong (e.g.[-30, -11]around an estimate of-0.09) because each simulated replicate’s treatment coefficient was read from the wrong field of the refit object (fit$b, slopes only) when the anchor fit’s index into its own coefficient vector (fit$params, thresholds + slopes) pointed somewhere else entirely onfit$b. Every parametric-bootstrap replicate now reads the same field the anchor fit used.InferenceOrdinalContRatioRegr(and, via the same hardened QR-column-dropping helper, 8 other ordinal threshold-model classes) could silently fit from a garbage cold start and “converge” after one iteration when the design matrix contained columns collinear with the model’s implicit per-stage intercept (e.g. a stratified design’s two complementary factor dummies) —qr()alone, with no intercept column to check against, reported the matrix as full rank. The column-dropping helper now optionally includes that implicit intercept in its rank check.A likelihood-ratio confidence interval whose Newton/bisection inversion converges to within a small tolerance of the point estimate — without landing on it exactly — was reported as a zero-width interval instead of falling back to the Wald interval (or
NA), because the “failed inversion” sentinel check required bit-identical bounds.InferenceOrdinalStereotypeLogitRegr’scompute_lik_ratio_confidence_interval()hit this on ~6% of one audited sample. Itscompute_lik_ratio_bootstrap_confidence_interval()had a related bug: a bootstrapp-value belowalphaat the point estimate itself (which should be ~1, since the LR statistic there is 0) was silently treated as a legitimate zero-width interval rather than a failed constrained-fit refit; it now reportsNA.compute_rand_bootstrap_confidence_interval(type = "smoothed")andcompute_rand_bootstrap_two_sided_pval(type = "smoothed")added raw-scale Gaussian kernel noise to count responses, so a resampled zero could become a slightly negative non-integer. Under the CI inversion’s multiplicative count shift that became a large negative integer, the Rcpp Poisson-GLMM fit gave up, and the glmmTMB fallback rejected every such draw (GLMM FIT ERROR: negative values not allowed for the 'Poisson' family), corrupting the null distribution and yielding a degenerate conservative bound (e.g.InferenceCountKKGLMM). Withuse_rcpp = FALSEevery draw failed and the p-value wasNA. Kernel noise on count responses is now rounded and floored at zero so the resampled draw stays on the non-negative integer support (the same convention the count shift already uses); other response types are unchanged.InferenceCountHurdlePoisson/InferenceCountHurdleNegBin/InferenceCountZeroInflatedPoisson/InferenceCountZeroInflatedNegBin’s standard error could fall through to the generic information-matrix-inverse fallback on a hurdle fit with no MLE by construction (every positive count equal to 1), and whether that generic inversion happened to return a spurious finite value or correctly fail depended on the LAPACK/BLAS backend (confirmed via CI to differ between this environment, where it correctly returned non-estimable, and Windows). The data-driven non-estimability check that already gated the estimate andp-value now also runs before this generic fallback, so the standard error isNAon every platform.SimulationFramework’s internal design/inference-combination builder unconditionally disabled package assertions and never restored them; a caller running withturn_off_asserts_for_speed = FALSE(assertions intentionally kept on) had assertions silently disabled for the rest of the R session after this method ran once, which could mask unrelated argument-validation bugs in later, unrelated code in the same session. It now restores whatever assert state was in effect on entry.
EDI 1.0.1
Breaking changes
-
set_custom_randomization_statistic_function()andset_custom_randomization_statistic_cpp()are removed from every concrete estimator class. They let a bare R closure readprivate$des_obj_priv_intthrough a hand-built environment proxy — an undocumented, fragile mechanism that also forced every concrete estimator’s own fast/vectorized randomization-test paths to carry a guard for a feature that had nothing to do with that estimator. Use the newInferenceRandCustomclass instead:InferenceRandCustom$new(des_obj, custom_randomization_statistic_function = function(y, w, dead) ...)(orcustom_randomization_statistic_cpp =for the same C++ source/compiled-function/RcppXPtrUtils::cppXPtr()options as before), then callcompute_rand_two_sided_pval()orcompute_rand_confidence_interval()on it. The statistic function’s calling convention is now explicit arguments —function(y, w, dead)— not implicit access to private state; existing custom statistic functions need this small rewrite.InferenceRandCustomhas its own dedicated fast kernel, so performance is unchanged or better than before, uniformly across every dataset and design (previously, speed depended on which concrete class the statistic happened to be attached to).
New features
-
InferenceIncidGCompRiskRatioandInferenceIncidKKGCompRiskRatiogain subsampling and m-out-of-n bootstrap confidence intervals and two-sided p-values (previously only available for the risk-difference gcomp classes). The risk-ratio pivot is computed on the log scale (null atlog(delta), centered onlog(estimate)), matching the convention already used by their percentile/BCa bootstrap CIs, since the raw-scale pivot correct for a difference is not correct for a ratio whose null is 1 and which is right-skewed at reduced effective sample size. -
get_local_EDI_optimization()’s hardware fingerprint now detects CPU/RAM on Windows and macOS as well as Linux.
Bug fixes
- The randomization confidence interval’s bisection search picked the wrong “conservative” fallback endpoint when it had no reliable sign change to bracket: it always fell back to the lower endpoint
lwhenever the lower p-value was non-significant, which is correct when searching for a lower bound (lis the outer/conservative end there) but wrong when searching for an upper bound, wherelis the point estimate itself and is essentially always non-significant. Every such “conservative” upper bound silently collapsed to the point estimate, producing badly-too-narrow intervals — confirmed by simulation at roughly 51-61% empirical coverage instead of the nominal 95% forInferenceContinLin,InferenceContinOLS,InferenceContinQuantileRegr, and the KK one-likelihood classes. The fallback now keys on which bound is being computed rather than which p-value was non-significant. -
SurvivalDepCensTransformSource(the dependent-censoring AFT residual transform) protected only the treatment column from the hardened QR column-dropping fallback, not the intercept — dropping the intercept left a severe bias under the null (meanbeta_hataround 0.71 instead of about 0.005, coverage around 13% instead of 95%). Both columns are now protected, the same pattern already used for the Weibull-frailty design matrix below. -
InferenceSurvivalStratCoxPHRegr’s score-test information closures returned the raw Cox partial-likelihood Hessian unnegated. Since that Hessian is negative semi-definite (a concave log-likelihood), the “information” matrix fed to the score test was itself negative-definite, so the shared score-test helper’s positivity check always failed —compute_score_two_sided_pval()/compute_score_confidence_interval()returnedNAon every call, regardless of formula. Fixed to negate, as the siblingInferenceSurvivalCoxPHRegralready did. -
get_clogit_plus_glmm_hessian_cpp()’s exported wrapper negated an objective that already returns the positive information matrix (not the raw log-likelihood Hessian), soInferencePropKKGLMMandInferenceIncidKKCondLogitGLMMIVWC/OneLikfed the score test a negative-definite matrix — the same failure mode as the StratCoxPH bug above, reproducing an approximately 100%NArate for their score test regardless of formula. The extra negation is removed. - Six ordinal-response inference classes —
InferenceOrdinalAdjCatLogitRegr,InferenceOrdinalCauchitRegr,InferenceOrdinalCloglogRegr,InferenceOrdinalOrderedProbitRegr,InferenceOrdinalContRatioRegr, andInferenceOrdinalPropOddsRegr— extractedb[length(b)](the last covariate’s slope) as the treatment coefficient instead ofb[1](treatment is always the design matrix’s first column). The two coincide only when the model has no covariates beyond treatment; with covariates present, the randomization test and the Bayesian-bootstrap machinery silently estimated and tested a different covariate’s effect instead of treatment’s (confirmed via near-total null rejection ondesign_formula = ~.paths). - The log-normal-frailty Weibull AFT model (
InferenceSurvivalGLMMWeibullFrailtyNormalIVWC/OneLik) fit its design matrix with no intercept column. Without one, the control arm’s baseline log-time was pinned at 0 and the mean-zero Gaussian frailty could only partly absorb it; the leftover bias (plus the Gumbel error’s nonzero mean) leaked into the treatment estimate, inflating Wald/score/LR/ bootstrap rejection to roughly 50-70% under the null (nominal 5%). - The zero-inflated negative binomial kernel (
fast_zinb.cpp) had two bugs in its ZIP-limit reduced fit: a fixed-parameter index was off by one, so the reduced fit pinned the parameter before the one requested (e.g. the intercept instead of the treatment coefficient); and the ZIP-limit score vector was one entry shorter than the full parameter vector and was not zero-padded, so score-test consumers compared it against a differently-sized information matrix. -
InferenceOrdinalKKGEEfit its GEE model on the raw, unreduced design matrix; rank-deficient fixtures (e.g.~0+.model matrices) made themultgee::ordLORgee()backend refuse the fit outright, silently swallowed toNULL(74-89%NArates observed on affected data shapes). It now retries through the same QR-hardened rank-reduction machinery used elsewhere before giving up. - Five classes’ Bayesian-bootstrap machinery discarded an already-computed per-replicate SE for the treatment coefficient and always reported
NAinstead, starving their studentized/BCa variants of any SE:InferenceOrdinalPropOddsRegr,InferenceCountQuasipoisson,InferenceCountRobustPoisson, the glmmTMB-based weighted refit path shared by the zero-augmented Poisson classes, andInferenceAbstractKKOrdinalCLMM(the shared base of the KK ordinal CLMM classes). -
InferenceProportionFractionalLogitwas missing quasi-binomial dispersion scaling, systematically overstating standard errors — 0 rejections out of several hundred simulated replicates under the true null (near-zero power rather than nominal-level power). -
InferenceIncidLogBinomial’s Bayesian bootstrap discarded boundary-hitting refit replicates instead of retaining them, shrinking the empirical spread of the bootstrap distribution — observed test size 25-36% instead of nominal 5%, and CI coverage 56-70% instead of 95%. -
InferenceExtPRWSubsamplingwas missing the finite-population correction for without-replacement subsampling, observed at roughly 9.75% Type-I error instead of nominal 5% at a subsample fraction of about 0.4. -
InferenceExtPRWSubsamplingandInferenceExtMOutOfNBootstrap’s failure gate checked only the absolute count of finite replicates (default minimum 5), not the fraction of the requestedB/m. WithBin the hundreds, a high (24%+) convergence-failure rate could still pass the gate, producing a falsely-precise, degenerate p-value. The gate now also requires a majority of replicates to succeed. - The zero-augmented Poisson classes’ shared information-matrix code now neutralizes the zero-inflation coefficient block when the ZI submodel’s fitted probability collapses to its boundary, preventing inflated likelihood-ratio-test Type-I error in that regime.
- The KK-GEE shared mixin’s QR-hardened column-dropping fallback read a nonexistent
attempt$X_fitfield (the correct field isattempt$fit), so the fallback candidate was never actually generated for any class composing that mixin (continuous/count/incidence/proportion/ordinal-KK GEE classes). - The generic score test (
InferenceExtInformationMatrix) returnedNAfor the large majority of calls fromInferenceContinKKGLMMandInferenceCountKKGLMM— their null-constrained refit’s nuisance-parameter information is positive definite only about 32% of the time near a variance-component boundary, which is expected behavior of the likelihood surface there, not a sign of a bad fit. This made the score test degenerate (0% Type-I error and 0% power together, rather than merely miscalibrated). A ridge-regularized fallback now activates only when the unregularized path already returned a non-finite p-value. -
DesignFixedBlockingsilently ignored user-supplied block IDs: the randomization draw always re-derived blocks from the raw covariates instead of using thema caller passed at construction. -
InferenceSurvivalGehanWilcoxandInferenceSurvivalLogRankfit their null Cox model with the default (Efron) tie-handling, inconsistent with the Breslow/Nelson-Aalen convention their fast C++ kernels assume — a genuinely different martingale residual under tied event times. Both now fit withmethod = "breslow"explicitly. -
InferenceSurvivalRestrictedMeanDiff(RMST) never populated the treatment coefficient’s SE on a full (non-estimate_only) fit, unlike every peer class, so bootstrap callers expecting it for a studentized pivot gotNA. -
InferenceOrdinalPairedSignTest: an estimate is no longerNaNwhenever any single pair-difference isNA— valid pairs are now used. Its bootstrap and jackknife distribution methods, previously disabled with an error asserting they violate the matched-pair design constraint, are available again. - Bootstrap-family methods (nonparametric, m-out-of-n, subsampling, BCa) now refuse with an explicit error for
DesignSeqOneByOneand its subclasses, exceptDesignSeqOneByOneBernoulli(whose assignments do not depend on prior subjects, so row resampling is valid there). These methods previously ran without error for sequential designs in general and were documented as merely “conservative”; that claim did not hold and has been removed along with the methods for the other sequential designs. - An OpenMP worker thread (not the master) calling into R’s interrupt or time-budget checks could raise an exception across the OpenMP thread boundary, crashing the whole R process (
SIGABRT) instead of cleanly interrupting, undernum_cores > 1. Both checks now no-op on any thread but the master. -
InferenceSurvivalCoxPHRegr’s internalcoxph.fitwrapper crashed assigning column names to a 0-column design matrix (length of 'dimnames' [2] not equal to array extent) on null-model refits — e.g.compute_lik_ratio_two_sided_pval()/compute_score_two_sided_pval()undermodel_formula = ~1— becausepaste0("x", seq_len(0))returns"x"rather thancharacter(0). The column-naming step is now skipped for a 0-column matrix. -
SimulationFramework’smirai-based parallelization (set_num_cores(force_mirai = TRUE)) could hang indefinitely if a daemon died before connecting. Daemon launch and every daemon-collection call are now bounded by a deadline, with one relaunch attempt and a clear error in place of an unbounded wait. - Randomization confidence intervals are no longer offered for the six log-hazard-ratio (Cox-family) survival classes —
InferenceSurvivalCoxPHRegr,InferenceSurvivalKKLWACoxPHIVWC/OneLik,InferenceSurvivalKKStratCoxPHIVWC/OneLik, andInferenceSurvivalStratCoxPHRegr(which already refused). The generic randomization CI inverts an accelerated-failure-time sharp null, so itsdeltaaxis is a log time ratio; these classes’ estimates are log hazard ratios, and the Cox model has no shape parameter linking the two. The search was being seeded on the wrong axis and returned bounds that were not a confidence interval for anything (on a Weibull test case: estimate −1.70, “CI”[−1.70, −1.26]). A directcompute_rand_confidence_interval()call now stops with an explanation, andInferenceSuiteno longer lists the method for them, as for incidence responses. The randomization p-value and the randomization- bootstrap CI (a percentile interval on the estimate’s own scale) are unchanged. AFT-scale survival classes (Weibull, marginal Weibull, Weibull frailty, rank regression) are unaffected.
Documentation
-
compute_rand_confidence_interval()documents the impute-then-permute construction (Rosenbaum 2002; Imbens & Rubin 2015) and, for survival responses, the AFT residual construction and its censoring assumptions (Tsiatis 1990; Wei, Ying & Lin 1990; Jin, Lin, Wei & Ying 2003). -
InferenceOrdinalStereotypeLogitRegr’s likelihood-ratio test (compute_lik_ratio_two_sided_pval()) now documents that it has inflated Type-I error (roughly 18-23% vs. a nominal 5%) for this class specifically (a non-regular case in the sense of Davies 1977), and recommends the bootstrap (~6-7%) or Bartlett-corrected (~4%) variants instead. The flagged method’s own behavior is unchanged. -
InferenceSuite’s combined-evidence documentation adds a “same-Y does not mean same estimand” caveat: rows testing the same outcome under different link functions/estimands do not share one coherent null hypothesis under the weak (asymptotic) null, only under the randomization sharp null — relevant to interpretingcombined_evidence$pval.
EDI 1.0.0
Initial release of EDI (Experimental Design and Inference): a framework that pairs randomized experimental designs — fixed-sample and sequential — with inference procedures matched to each design and response type, so that estimation and testing always reflect how the data were generated.
Experimental designs
- Fixed-sample designs (all n subjects assigned at once via
assign_w_to_all_subjects()):DesignFixedBernoulli,DesignFixediBCRD,DesignFixedFactorial,DesignFixedBlocking,DesignFixedCluster,DesignFixedBlockedCluster,DesignFixedBinaryMatch,DesignFixedMatchingGreedyPairSwitching,DesignFixedGreedy,DesignFixedGreedyDOptimal,DesignFixedOptimalandDesignFixedOptimalBlocks(mixed-integer-programming optimal designs viaompr/GLPK, with simulated-annealing and greedy alternatives), andDesignFixedRerandomization. - Sequential one-by-one designs (each subject assigned on arrival via
add_one_subject_to_experiment_and_assign(), maintaining covariate balance):DesignSeqOneByOneBernoulli,DesignSeqOneByOneiBCRD,DesignSeqOneByOneUrn,DesignSeqOneByOneEfron(biased coin),DesignSeqOneByOneAtkinson,DesignSeqOneByOnePocockSimon(minimization),DesignSeqOneByOneRandomBlockSize,DesignSeqOneByOneSPBR(stratified permuted block), and the Kapelner-Krieger matching-on-the-fly family that builds matched pairs from the accruing subject stream:DesignSeqOneByOneKK14,DesignSeqOneByOneKK21,DesignSeqOneByOneKK21stepwise. - Observational designs (containers for already-observed, non-randomized assignments with blocking/matching structure):
ObservationalDesign,ObservationalDesignBlocks,ObservationalDesignMatching. - Custom-design extension bases for user-defined assignment rules:
DesignFixedCustom,DesignCustomSequential. - Unequal allocation supported; missing covariate data imputed automatically (
missRanger/missForest).
Response types
Six response types, each with its own matched inference classes: continuous, incidence (binary), count, proportion (values in [0, 1]), ordinal, and survival — the survival response supporting exact, left-censored, right-censored, and interval-censored observations through one y/y_L/y_R interface.
Inference
- Continuous: OLS (
InferenceContinOLS), Lin’s covariate-interacted OLS, quantile regression, robust (Huber) regression, and for matched (KK) designs GLMM (InferenceContinKKGLMM), OLS/quantile/robust variants in both IVWC (inverse-variance-weighted combination) and combined-likelihood pooling, plus the Bai adjusted-t estimators (InferenceBaiAdjustedTKK14,InferenceBaiAdjustedTKK21). - Incidence: logistic, probit, log-binomial, and identity-link binomial regression, Wald and exact binomial tests, Fisher’s exact test, CMH, Newcombe and Miettinen-Nurminen risk-difference intervals, extended-Robins and Zhang (2026) exact test-inversion randomization CIs, g-computation marginal effects, modified Poisson, and conditional-logit / GLMM classes for matched designs.
- Count: Poisson, quasi-Poisson, robust Poisson, negative binomial, zero-inflated and hurdle models, composite likelihood, conditional Poisson, and GEE classes for matched designs.
- Proportion: beta regression, fractional logit, zero-one-inflated beta, quantile regression, g-computation, and matched-design GEE/quantile variants.
- Ordinal: proportional odds, partial proportional odds, adjacent-category logit, continuation-ratio, stereotype logit, ordered probit, cauchit and complementary-log-log links, g-computation, ridit scoring, Jonckheere-Terpstra, paired sign test, and CLMM-based matched-design classes; randomization confidence intervals for ordinal GLM effects are obtained by inverting the exact permutation test (following the Wang-Rosenberger approach).
- Survival: Cox proportional hazards (plain and stratified), Weibull AFT (with full censoring support), restricted mean survival time, log-rank and Gehan-Wilcoxon tests, Kaplan-Meier survival differences, Weibull frailty GLMMs (log-gamma and normal frailties), Clayton-copula and dependent-censoring-transform estimators, and LWA/rank-regression classes for matched designs.
- Cross-cutting estimators usable across response types:
InferenceAllSimpleAverageDiff,InferenceAllSimpleMeanDiffPooledVar,InferenceAllSimpleWilcox,InferenceAllKKMeanDiffIVWC,InferenceAllKKWilcoxIVWC. - Every applicable procedure at once:
InferenceSuiteruns all inference classes valid for a given design/response combination and reports a single Cauchy-combined p-value alongside the individual results. - Custom-inference extension bases:
InferenceCustomAsymp,InferenceCustomBoot,InferenceCustomRand. - Where a specialized engine is best-in-class, estimation delegates to it —
glmmTMB(GLMMs),fixest(fast GLMs),aftgee(rank-based AFT),Rfit(R-estimation),survival— with EDI’s own C++ kernels used everywhere else.
Resampling and randomization machinery
- Non-parametric, parametric, and Bayesian bootstrap; BCa intervals; jackknife; m-out-of-n bootstrap and Politis-Romano-Wolf subsampling; exchangeable-resampling-unit handling for blocked/matched/cluster structures; minimum-volatility CI selection.
- Randomization tests with sequential Monte Carlo p-values, custom randomization statistics, quantile randomization CIs, and randomization/bootstrap confidence intervals via parallel bisection test-inversion.
Simulation framework
-
SimulationFrameworkruns Monte Carlo power, size, and operating-characteristic studies across designs, response types, and inference procedures, withcoverage_pval/size_pvalcalibration diagnostics;SimulationFrameworkReportrenders results. - Helpers
generate_covariate_dataset()andtransform_cont_y_based_on_response_type(); optional parallelization viamirai(set_num_cores()/unset_num_cores()).
Performance
- All model-fitting and variance kernels implemented in C++ (Rcpp, RcppEigen/Eigen, RcppNumerical, LBFGS++, IRLS, OpenMP), exposed as documented
fast_*functions — typically one to three orders of magnitude faster than the corresponding pure-R fits (see the shipped benchmark comparisons against each canonical R baseline). - Machine-specific tuning:
tune_EDI_for_this_machine()benchmarks the local machine across four axes and persists tuned performance-policy defaults (get_local_EDI_optimization(),clear_local_EDI_optimization()). - Runtime-tunable dispatch policies for optimizer choice, cold/warm-start heuristics, and parallel/serial execution:
get_optimization_dispatch_policy()/set_optimization_dispatch_policy()and the corresponding*_cold_start_,*_warm_start_, and*_parallel_dispatch_policy()pairs, plusget_bootstrap_dispatch_policy(). - Install-time build configuration via environment variables (
EDI_PORTABLE,EDI_NATIVE_SPEED,EDI_NATIVE_LTO,EDI_UNITY,EDI_DISABLE_VECTORIZATION,EDI_DEBUG_SYMBOLS): a tuned-march=nativeunity build by default locally, and a fully portable, warning-free build for CRAN/CI (auto-selected on r-universe builders). - Runtime argument-checking can be disabled for production speed with
toggle_asserts().
Documentation and extensibility
- Five concept vignettes: notation glossary; reproducibility (RNG and seed conventions); backend contracts (
fast_*/C++ kernel conventions); validation evidence; and extending EDI with your own design and inference classes (backed by the design/inference class registries and theDesignFixedCustom/DesignCustomSequential/InferenceCustom*bases). - Utilities including
create_model_matrix_from_features(),robust_negbinreg(),robust_survreg(), androbust_survreg_with_surv_object().
