
A Fixed, Non-Bipartite-Matched-Pair Design with Within-Pair Randomization
Source:R/design_fixed_binary_match.R
DesignFixedBinaryMatch.RdA fixed-sample-size DesignFixed that (1) partitions the
\(n\) subjects into \(n/2\) disjoint matched pairs by solving a non-bipartite
(optimal) pairwise-matching problem on a covariate distance matrix, minimizing the
total within-pair distance across all pairs, then (2) randomizes treatment
within each pair independently: for pair \(k\), one of its two members is
assigned to treatment and the other to control with probability \(1/2\) each,
independently across pairs. This is the classical matched-pair randomized design (a
special case of blocking with block size 2), which guarantees exact covariate balance
within every pair (up to the matching algorithm's distance metric) while preserving
randomization-based inference validity, in contrast to
DesignFixedGreedyDOptimal, which balance the
covariates in aggregate via an optimality criterion but do not guarantee pairwise
closeness of any two subjects.
Matching algorithm. Pairing is computed once (lazily, on first call to
draw_ws_raw()/assign_w_to_all_subjects(), via
private$ensure_matching_structure_computed()) by
compute_binary_match_structure(), which forms an \(n \times n\) pairwise
distance matrix — squared Mahalanobis distance if mahal_match = TRUE (the
default; using the sample covariance of the covariates, ridge-regularized if singular),
or squared Euclidean distance otherwise — and solves the minimum-weight non-bipartite
perfect matching on that distance matrix via nonbimatch
(nbpMatching, a Suggests-only dependency; loading is deferred until matching is
actually needed, so pre-computed w vectors injected via m never require
it). For a single covariate (\(p = 1\)), pairing instead reduces to simply sorting
subjects by that covariate and pairing consecutive subjects, since the non-bipartite
matching problem is trivial in one dimension. The resulting pairing is cached in
private$bms/private$m for the lifetime of the design object (or until
explicitly reset via set_m()) and is not recomputed per draw.
Within-pair randomization. Given the fixed pairing, each replicate allocation
(see draw_binary_match_assignments_cpp()) independently flips, for every pair,
which of its two members is treated (an independent fair coin flip per pair per
replicate, using a splitmix64-seeded Mersenne Twister per replicate column for
reproducible parallel draws); this guarantees exactly \(n/2\) treated subjects
overall (only prob_T = 0.5 is supported; the constructor errors otherwise).
Passing a pre-computed m to the constructor supplies the matched-pair structure
directly (each pair ID occurring in exactly 2 rows), bypassing the matching
computation entirely while keeping the same within-pair randomization.
No-covariate fallback. If no covariates are available at draw time
(private$m is NULL, e.g. matching hasn't run and no explicit m was
supplied), draw_ws_raw() falls back to an unmatched balanced complete
randomization (a uniformly random permutation of \(n/2\) ones and \(n/2\) zeros),
since there is no covariate information to match on.
Batch pregeneration. draw_binary_match_assignments_cpp()'s output is
trusted unvalidated – it guarantees exactly n x r valid \(\{0,1\}\) columns
with \(n/2\) treated subjects per column by construction (see
fix_design_hierarchy.md, "AllocationMatrixValidation"). supports_batch_w_pregeneration() returns TRUE so that the
calling framework generates all replicate w vectors for a simulation cell in one
batch (amortizing the one-time nbpMatching matching cost across all replicates
of that cell) rather than recomputing the matching structure per replicate.
References
Greevy, R., Lu, B., Silber, J. H., and Rosenbaum, P. (2004). "Optimal multivariate matching before randomization." Biostatistics, 5(2), 263-275, doi:10.1093/biostatistics/5.2.263 , for optimal non-bipartite matched-pair designs prior to randomization. See also matched pair and Mahalanobis distance for orientation.
Super classes
Design -> DesignFixed -> DesignFixedBinaryMatch
Methods
+ inherited public methods from DesignFixed
+ inherited public methods from Design
Design$add_one_subject_response()Design$any_censoring()Design$applicable_inference_class_names()Design$assert_all_responses_recorded()Design$assert_all_subjects_arrived()Design$assert_even_allocation()Design$assert_fixed_sample()Design$capabilities()Design$check_experiment_completed()Design$draw_ws_according_to_design()Design$duplicate()Design$get_X()Design$get_X_imp()Design$get_X_raw()Design$get_design_formula()Design$get_edi_version_created()Design$get_effective_dead()Design$get_effective_time()Design$get_missingness_method()Design$get_n()Design$get_ordinal_levels()Design$get_original_ordinal_levels()Design$get_prob_T()Design$get_response_type()Design$get_response_type_original()Design$get_t()Design$get_w()Design$get_y()Design$get_y_L()Design$get_y_R()Design$get_y_original()Design$has_general_censoring()Design$incompatible_inference_classes_due_to_design_structure()Design$is_a_bernoulli_capable()Design$is_a_cluster_capable()Design$is_blocking_design()Design$is_fixed_sample_size()Design$is_matching_design()Design$prepare_for_resampling_replay()Design$randomization_family()Design$supports()Design$supports_randomization_draw()Design$supports_resampling()Design$supports_resampling_replay()Design$transform_y()Design$unavailable_inference_classes_due_to_missing_packages()Design$warm_all_subject_data_cache()
DesignFixedBinaryMatch$is_a_kk_matching_capable()
Characterization: this design computes its matched-pair structure on the fly from covariates (see class documentation), so it is KK matching-capable by construction.
DesignFixedBinaryMatch$supports_batch_w_pregeneration()
Returns TRUE so the calling framework pre-generates all
replicate w vectors for a simulation cell in one batch, paying the
one-time nbpMatching non-bipartite matching cost once per cell and
reusing the resulting pairing across all replicates, rather than
recomputing it per replicate.
DesignFixedBinaryMatch$new()
Initialize a binary (non-bipartite) matched-pair fixed
experimental design. Matching itself is deferred until the first draw (see
class documentation); this constructor only records configuration and,
if m is supplied, installs the explicit pairing immediately.
Usage
DesignFixedBinaryMatch$new(
response_type,
prob_T = 0.5,
mahal_match = TRUE,
include_is_missing_as_a_new_feature = TRUE,
n = NULL,
m = NULL,
verbose = FALSE,
missingness_method = "impute",
design_formula = ~.,
seed = NULL
)Arguments
response_typeThe data type of response values.
prob_TThe probability of the treatment assignment. Must be 0.5, since within-pair randomization only supports an even 1-treated/1-control split per pair.
mahal_matchMatch using squared Mahalanobis distance (accounting for covariate correlation/scale) if
TRUE(default), else squared Euclidean distance on the raw covariate matrix.include_is_missing_as_a_new_featureFlag for missingness indicators.
nThe sample size.
mOptional integer vector of explicit matched-pair identifiers, one per subject. If supplied, `n` must also be supplied, `length(m)` must equal `n`, all values must be positive, and each pair ID must occur exactly twice. This bypasses the package-computed matching step.
verboseFlag for verbosity.
missingness_methodHow to handle missing values in covariates.
design_formulaA formula object.
seedInteger seed for reproducibility.
DesignFixedBinaryMatch$assign_w_to_all_subjects()
Assign treatment to all subjects (see
DesignFixed$assign_w_to_all_subjects() for the
general contract). Before delegating, this override ensures the matched-pair
structure is computed (private$ensure_matching_structure_computed())
even when w_precomputed is supplied and
draw_ws_according_to_design() is therefore never called — downstream
code (e.g. blocked/matched-pair inference) still needs private$m to be
populated regardless of how w was obtained.
Examples
des = DesignFixedBinaryMatch$new(n = 10, response_type = 'continuous')
des$add_all_subjects_to_experiment(data.frame(x1 = rnorm(10)))
des$assign_w_to_all_subjects()