Behaviour and parity with R¶
finite-selection reproduces the R package
FSM 1.0.0 (Chattopadhyay, Morris and
Zubizarreta). The code is written independently, from the published algorithm.
Agreement with R is established black-box: tests/fixtures/make_r_fixtures.R
calls R’s exported functions on a grid of cases and records the inputs and
outputs, and the test suite checks the Python functions against those records.
The R parity workflow regenerates the records from a fresh R install weekly.
This page lists the numerical conventions the parity depends on, and the places where Python deliberately departs from R.
What “parity” means here¶
Deterministic results are compared exactly.
Given the same selection order and
ties="smallest",fsmandfsm_batchreturn the same allocation and the same selection sequence as R.Their criterion matrices agree to R’s seventh rounded decimal.
perm_test,ess_rand,ess_model,tasmd_randandmake_sq_interagree to 1e-10 or better on identical inputs.
Random draws are compared as distributions.
R and numpy use different random number generators, so
som,crdandfsm(ties="random")cannot match R draw for draw.What is exact is everything downstream of a draw. For example, SCOMARS’s conditional probabilities along R’s own path match exactly.
The draws themselves are tested against 10,000 R draws, stage by stage or unit by unit.
The bundled Lalonde data matches R’s
Lalondeto 1e-9.It is rebuilt from the public NBER file by
scripts/build_lalonde.py, not copied from R.
Conventions¶
The last column names test cases that fail when the convention is changed; each was checked by mutating it and rerunning the suite.
Convention |
Value |
Pinned by |
|---|---|---|
Standardization |
columns centred and divided by the sample SD (n − 1) |
41 cases, e.g. |
Design matrix |
intercept column first, then covariates |
all |
Ridge |
|
|
Empty group |
criterion |
24 D- and A-optimal cases |
Regularization switch |
if LAPACK’s 1-norm reciprocal condition estimate of the R factor of |
|
Criterion rounding |
7 decimals before the argmax |
|
Ties |
|
45 cases; |
|
|
|
|
first principal component scores of the centred covariates |
|
|
sum of squared deviations of the group plus candidate, on the first PC or the row mean |
|
|
trace of the covariance of the group plus candidate; with a single covariate, the sum of squares instead (same choice, different reported value) |
|
Collinear covariates |
an error, unless |
|
|
standardization, |
|
SCOMARS |
|
|
Global percentage |
the group with the smallest |
|
Randomized chunk |
each block of |
|
ESS (randomization) |
|
|
ESS (model) |
regression imputation at the full-sample covariate mean; residual variance on |
|
TASMD |
|
|
|
|
|
Where Python departs from R¶
Each departure is deliberate and has a test that pins both R’s behaviour and Python’s.
ess_randuses every assignment.R 1.0.0 reads only the first N assignments of each design, N being the number of units, and fails when there are fewer.
On the first N assignments the two agree exactly.
perm_testwithtau ≠ 0imputes the sharp null.Under
Y_i(1) − Y_i(2) = tau, Python fills in each unit’s missing potential outcome and recomputes the statistic for every reference assignment. Two-sided p-values measure distance fromtau.R keeps the outcomes fixed and subtracts
taufrom every statistic. Its one-sided p-values therefore never depend ontau.For the default contrast at
tau = 0, the reference statistics agree. General two-group contrasts require imputation even whentau = 0.
perm_testcounts ties.Statistics such as rank means can tie the observed value exactly while differing in the last floating-point bits.
R compares raw floats and drops some of these ties. Python counts them, when computing the tail probability.
R’s p-value always lies between the tie-free and the tie-inclusive value.
Criteria of exactly −1 are reported. R marks unavailable units with −1 before converting to NA, so a genuine criterion of −1 also becomes NA.
tasmd_randaccepts designs with different numbers of assignments. R fails with “subscript out of bounds”.Clear errors.
Collinear covariates raise a
ValueErrorthat suggestsq_initial, where R fails insidesolve().make_sq_interwith every option off raises, where R fails on an undefined object.
API differences¶
The Python API follows Python conventions; results are unchanged.
Names are snake case:
s_function="negative_dopt",method="randomized_chunk",alternative="two-sided".Covariates are arrays or data frames without an index column.
FSMResult.selectedholds 0-based row positions.ess_randtakes a list of per-design arrays (one assignment per row). R takes a 3-D array.ess_modeltakes one assignment per row; R takes one per column.tasmd_randtakes a dict of named designs and returns unrounded values.love_plotdraws on matplotlib axes.Its TASMD values are tested against
tasmd.Its ASMD option divides by the root mean of the two group variances. R exposes no ASMD numbers to compare against.
Randomization-test contract¶
For two groups, perm_test imputes the null
c_1 Y_i(1) + c_2 Y_i(2) = tau for every unit, including tau=0. Both
coefficients must be nonzero. For more than two groups, tau=0 with a
nonzero contrast summing to zero tests the stronger sharp no-effect null,
not just the single contrast constraint. Rank statistics also require the
no-effect null; a nonzero effect in outcome units is not a shift in rank units.
By default, reference assignments are independent draws from the experiment’s
design. The p-value is (b + 1) / (B + 1), including the observed statistic
once. Do not deliberately add the observed assignment to the sampled rows.
Set exact=True only for the full equally weighted reference distribution,
including the observed assignment. The software checks its presence but
cannot establish that a supplied distribution is complete or correctly weighted.
The R fixtures include the observed assignment as their first row; parity
tests pass the remaining rows as sampled references.
tests/test_perm_test.py checks null imputation against explicit potential
outcomes, all three alternatives, Monte Carlo correction, outcome rescaling,
and rejection of invalid or unsupported inputs.