mixle.data.exchangeability module

Exchangeability diagnostics for fitted and synthetic-data workflows.

Fitting one distribution to a dataset, or synthesizing “more rows like these”, assumes that row order does not carry information. When the data has a trend or a regime shift, that assumption is false and a pooled marginal model can misrepresent the process.

exchangeability_check() tests the assumption with numeric probes: a permutation test for rank correlation between value and row position, plus a first-half/second-half location-shift test. The aggregate label is one of:

  • exchangeable: the probes ran and found no order signal at the tested level;

  • trend: value co-moves with position;

  • shift: the halves differ in location;

  • inconclusive: no probe could run at all – too few rows, no numeric field in the record shape, or every numeric field was non-finite (NaN/Inf). This is deliberately distinct from exchangeable: it means the assumption was never tested, not that it was tested and held.

mixle.inference.create() and mixle.inference.synthesize() record the verdict in provenance so downstream consumers can see when pooling deserves review.

class ExchangeabilityReport(label, fields=<factory>, n_examined=0, max_records=None, bounded=False, multiplicity=<factory>)[source]

Bases: object

The verdict per numeric field, plus the aggregate label the preconditions record.

Parameters:
multiplicity: dict[str, Any]

How the multiplicity across the primary test family was corrected, and how large that family was. Recorded because the label is only interpretable against it (MXR-080-1887).

property exchangeable: bool

Return True only when the probes ran and did not reject exchangeability.

This is FAILURE TO REJECT, not certification (MXR-080-1887). The probes test for a monotone trend and for a between-halves shift; data can violate exchangeability in ways neither probe looks for, and a real violation can go undetected for want of power. True means “these tests, at this corrected level, found no order signal” – it is not evidence the assumption holds. multiplicity records the correction the verdict was decided under, without which the level is not interpretable.

False both for a detected trend/shift AND for inconclusive (nothing could be tested) – a caller that only inspects this boolean can never mistake “we could not check” for “we checked and found no signal”.

as_dict()[source]

Serialize the report to a JSON-compatible dictionary.

Return type:

dict[str, Any]

exchangeability_check(data, *, alpha=0.01, n_perm=200, seed=0, max_records=10_000)[source]

Test whether row ORDER carries information (see module docstring).

alpha is deliberately strict (0.01): the check should flag clear violations, not manufacture warnings from noise. A dataset too small to have testing power (n < 20), with no numeric field in its record shape (dict/tuple/list/scalar rows are all supported), or whose only numeric field(s) are entirely non-finite (NaN/Inf) is reported inconclusive – untested, never a silent, vacuous “exchangeable”. A field containing SOME non-finite values is reported invalid and excluded from testing; any other, clean numeric field in the same record shape is still tested normally and can still produce a real verdict.

Parameters:
Return type:

ExchangeabilityReport