rivaldefs
Which of two rival labeling rules sits inside the other, and is the gap between the rules larger than the gap between their implementations?
Two rules applied to the same units split them into four groups: positive under both, under the first only, under the second only, under neither. Agreement statistics such as κ reduce the four to one number and lose the direction of the disagreement. Two pairs of rules can share κ while one pair is nested (one rule is a stricter cut of the other) and the other crosses (each rule picks out units the other misses).
rivaldefs reports the direction as a quantity, with its uncertainty, and compares the
disagreement between two rules with the disagreement among admissible implementations of each
rule. It implements the indices of a frozen preregistration (criteria-direction, PREREG_v2,
OSF https://osf.io/bpw6e/).
Terms
The package was written before the manuscript settled its vocabulary. The code that ran the analyses is released unchanged, so the identifiers keep the earlier words.
| package | manuscript | meaning |
|---|---|---|
rule (Rule) |
case definition | a family of admissible implementations |
| variant | implementation | one way of coding a definition |
| labeler | an implementation's classifications | one 0/1 column of the units × labelers matrix |
| direction | orientation | which of two definitions has fewer positives: a smaller, b smaller or equal |
% inside, C |
containment | share of the smaller positive set that is also positive under the other definition |
H |
H | 1 − m/E0 = κ/κmax where defined |
Version and scope
The analyses of the criteria-direction paper (simulation, MIMIC-IV, NHANES) ran on commit
76cd25a684614de93e6ae96b5b58527782825d64. The SHA-256 of src/rivaldefs, taken as the
analysis code takes it (sha256 over name\0<sha256 of the file>\n for each *.py file in
name order), is 76c690d8363f95f569f6c9349cfa2296e1d70cd1d9801aff0bfac9921809df7b, the value
recorded in the result files. Version 0.1.0 has the same src/.
rivaldefs is the statistical core. The simulation generators and the MIMIC-IV and NHANES
classification pipelines are in the criteria-direction repository.
Known limitations in 0.1.0
None of the three affects the recorded analyses; the two checks that establish this are in
criteria-direction,
papers/paper1_bmc_mrm_rival_definitions/experiments/PREREG_v2_rival_definitions/results/rivaldefs_release_preflight_2026-10-07.json.
qualifying_cellsreadsorientation_error=Noneas no adverse evidence, andOCRecorddoes not require thatNonego with an undefined population orientation. A record with a defined orientation and no orientation error would not block its cell. PassNoneonly for pairs of equal population prevalence.ladderorders weighted prevalences by their floating values and does not applyTIE_RTOL. Two prevalences that tie under the pairwise rule but differ in the last bits can be ordered either way, which can change the on-ladder share.gramandgrid_from_gramtrust their input.grid_from_gramchecks shape, symmetry and N, and does not check that the matrix is a feasible Gram matrix of binary columns;gramdoes not validate weights.gridvalidates before calling them.
The problem, concretely
N = 1,000, two pairs of rules:
| positives (x, y) | both | only first | only second | κ | H | % inside | |
|---|---|---|---|---|---|---|---|
| pair 1 | 100, 300 | 95 | 5 | 205 | 0.38 | 0.93 | 95% |
| pair 2 | 200, 200 | 101 | 99 | 99 | 0.38 | 0.38 | 51% |
κ is 13/34 for the first pair and 61/160 for the second, 0.38 both. In the first pair five units break the nesting where independence would put seventy; in the second each rule has 99 units the other misses.
Install
git clone https://github.com/elliottower/rivaldefs && cd rivaldefs && pip install -e ".[test]"
Python 3.10+, NumPy.
Use
import numpy as np
import rivaldefs as rd
t = rd.PairTable(n11=95, n10=5, n01=205, n00=695)
t.orientation # 'a smaller'
t.h # 0.928... = 1 - m/E0 = 13/14, Loevinger's H (kappa / kappa_max)
t.c # 0.95 share of the smaller set inside the larger
t.kappa # 0.382...
# Many labelers at once: a units x labelers 0/1 matrix.
rng = np.random.default_rng()
theta = rng.normal(size=2000)
labels = np.column_stack([
theta > 1.0, # rule A, implementation 0
theta > 0.9, # rule A, implementation 1
theta + rng.normal(scale=0.8, size=2000) > 0.6, # rule B, implementation 0
theta + rng.normal(scale=0.8, size=2000) > 0.7, # rule B, implementation 1
]).astype(int)
g = rd.grid(labels) # K x K grids of H, C, kappa, discordance, NODF, orientation
g.h[0, 2]
a = rd.Rule("A", columns=(0, 1), levels=((0,), (1,)))
b = rd.Rule("B", columns=(2, 3), levels=((0,), (1,)))
cmp = rd.compare_rules(g, a, b) # factor-balanced implementation distribution
cmp.delta # D_between - max(D_within), percentage points
cmp.s # probability a random implementation pair keeps the reference orientation
res = rd.bootstrap(labels, pairs=[(0, 2)], rule_pairs=[rd.RulePair(a, b)], n_boot=2000)
res.pairs[0].h # Interval(estimate, lo, hi, fraction_undefined, reported)
res.pairs[0].orientation_probabilities
res.rule_pairs[0].delta
What it computes
- Pairs. The 2×2 table (counts or weights), orientation (the smaller set, or
equal), the minority cell m and its independence value E0, H = 1 − m/E0, % inside C, κ, κmax, discordance d and the NODF pair score. Undefined values are NaN: H when a prevalence is 0 or 1, C when the smaller set is empty. - Many labelers. K × K grids, Mokken's scale H, and the prevalence ladder (on-ladder share, its independence value, ladder H).
- Rules and implementations. A
Ruleis a family of labelers with factor levels. Its implementation distribution is factor-balanced by default (uniform on a full factorial), reference-weighted as an alternative, and the within-rule draws can be conditioned on being distinct.compare_rulesreturns D_between, D_within, Δ, R, S and the weighted quantiles of H over implementation pairs, computed exactly over every variant pair. - Uncertainty.
bootstrapresamples the counts of observed label patterns (the participant bootstrap, from pattern counts) and recomputes every labeler, orientation, H and rule comparison per replicate.replicate_weightsdoes the same from survey replicate weights. A replicate where H or R is undefined is excluded; an interval is reported only when at least 90% of replicates are defined, and the fraction undefined is always returned. The result objects apply the registered gates:PairResult.h_delta_interval(the delta-method interval) is None unless the modal orientation has bootstrap probability ≥ 0.95 and 0 < m < E0, andRulePairResult.ris withheld (NaN,reported=False) unless the lower percentile of R's denominator exceeds 0.5 percentage points. The ungated values sit inh_delta_interval_ungatedandr_ungated, for diagnostics. - Ties and undefined terms. Two positive counts are equal when they differ by at most
TIE_RTOL× N (2^20 machine epsilons), so floating survey weights that sum to the same prevalence tie; integer counts never merge. Under the conditional-distinct-pair law a rule with one implementation has no within term, and Δ and R are then NaN whichever side it is on. - Orientation rule.
qualifying_cellsreads a simulation's operating characteristics by E0 band × prevalence-ratio band;orientation_statusmarks a real pair resolved when its cells qualify and its modal orientation has bootstrap probability ≥ 0.95. A pair with E0 = 0 (a prevalence of 0 or 1) is never resolved, and malformed records or probabilities are refused rather than read. - Missing inputs.
delta_boundsgives conservative outer bounds on Δ from each unit's possible completions.
Tests
make test
The tests check exact values on hand-worked tables, H against an independent count of
Guttman errors, H = κ/κmax on random tables, the invariance of Δ, R and S to duplicated
implementations, the equivalence of the pattern-count and unit bootstrap (for H, Δ, R and S),
survey replicate intervals against a hand computation, the registered gates on both sides of
their thresholds, and that every completion of a small table with missing inputs falls inside
the bounds. Randomized tests draw a new seed each run and print it when they fail; replay one
with RIVALDEFS_TEST_SEED=<seed> make test.
License
MIT
Metadata
Release files for rivaldefs 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| rivaldefs-0.1.0.tar.gz | 65.5 kB | With provenance | Details for rivaldefs-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| rivaldefs-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for rivaldefs-0.1.0-py3-none-any.whl |
Total release size: 89.7 kB
Release files / rivaldefs-0.1.0.tar.gz
| Download URL | rivaldefs-0.1.0.tar.gz |
|---|---|
| Size | 65.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
85156586b9d02b568f45e197b89344e5202ad46e41330354a8bbfcbe2b3f52cd
|
|
BLAKE2b-256 checksum How to use checksums |
01a0e60984ba544108dff3ac6c7d5d4e0ef7b63e7e7f291c83030a55df6208a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / rivaldefs-0.1.0-py3-none-any.whl
| Download URL | rivaldefs-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b4266c7ed31239de10ad16054741328d1df4bf16a793689d67142737acbc8d35
|
|
BLAKE2b-256 checksum How to use checksums |
72dd0e273409c7243baf8bdc4953d1b3cd9c3992d2b9894aafddd119ea51642b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logSecurity for rivaldefs 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for rivaldefs-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.