mixle.stats.combinator.censored module

Censoring likelihood combinator with explicitly typed exact and interval evidence.

CensoredDistribution wraps a base distribution and scores censored observations – ones that are not observed exactly but are known only to fall in some interval [a, b]. Such an observation contributes its interval probability mass

P(a <= X <= b) = F(b) - F(a)

to the likelihood, where F is the base distribution’s CDF (for a discrete base, whose CDF has a jump at a, the point mass P(X = a) is added back in – see Data type below). This is distinct from truncation: truncation renormalizes the density over a restricted support (the observation is exact but the support is limited), whereas censoring keeps the original distribution and only coarsens what was observed. Survival-analysis right/left/interval censoring (and Tobit-style bounds) are all this combinator.

Data type: each observation is one of

  • ExactObservation(x) or a raw child value x – exact evidence;

  • CensoredInterval(a, b) – interval evidence over the closed interval [a, b].

Raw two-element tuples are exact child observations, so bivariate child values remain representable. Censored evidence is a likelihood contribution, not an outcome sampled by the base model: an observation process must be modeled separately when interval selection itself is random.

The base distribution must expose cdf. Estimation of the base parameters under censoring has no generic closed form (the censored MLE couples the bounds with the base parameters), so the supplied estimator fits the base on the exact observations only and re-wraps; document and prefer a dedicated censored MLE when the censored fraction is large.

class ExactObservation(value)[source]

Bases: object

Explicit exact evidence; useful when the child value could otherwise look like metadata.

Parameters:

value (Any)

class CensoredInterval(lower, upper)[source]

Bases: object

Closed interval likelihood evidence with validated ordered bounds.

Parameters:
class CensoredDistribution(base, name=None, keys=None, *, fit_receipt=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A base distribution whose observations may be interval/left/right censored.

Parameters:
  • base (SequenceEncodableProbabilityDistribution)

  • name (str | None)

  • keys (str | None)

  • fit_receipt (CensoredExactOnlyFitReceipt | None)

density(x)[source]

Return the contribution of x (density for exact, interval mass for censored).

Parameters:

x (Any)

Return type:

float

log_density(x)[source]

Log-likelihood contribution of x.

Exact observation x -> log p_base(x); interval (a, b) -> log P(a <= X <= b) (see _interval_log_mass()).

Parameters:

x (Any)

Return type:

float

seq_log_density(x)[source]

Per-row censored log-densities for an encoded batch.

Parameters:

x (tuple[Any, ndarray, ndarray, ndarray, ndarray])

Return type:

ndarray

sampler(seed=None)[source]

Return exact evidence from the base; censoring requires a separate observation process.

Parameters:

seed (int | None)

Return type:

CensoredSampler

estimator(pseudo_count=None)[source]

Return the explicit exact-only projection estimator with an effective-count receipt.

This is not a censored MLE. Use likelihood_estimator() with a base-family-specific fitting callback when interval evidence must influence the fitted parameters.

Parameters:

pseudo_count (float | None)

Return type:

ExactOnlyCensoredEstimator

likelihood_estimator(fit)[source]

Return a likelihood-aware estimator backed by a declared base-family fitting callback.

fit(observations, weights, initial_base) receives validated exact/interval evidence and must optimize the full censored objective, returning a fitted base distribution.

Parameters:

fit (Callable[[tuple[Any, ...], ndarray, Any], SequenceEncodableProbabilityDistribution])

Return type:

CensoredLikelihoodEstimator

dist_to_encoder()[source]

Return the data encoder (exact observations + censoring intervals split out).

Return type:

CensoredDataEncoder

class CensoredSampler(dist, seed=None)[source]

Bases: DistributionSampler

Draw exact values from the base distribution (the sampler is not censored).

Parameters:
  • dist (CensoredDistribution)

  • seed (int | None)

sample(size=None, *, batched=True)[source]

Draw exact value(s) from the base distribution.

Parameters:
class CensoredExactOnlyFitReceipt(exact_weight, censored_weight, censored_fraction, likelihood_aware)[source]

Bases: NamedTuple

Parameters:
  • exact_weight (float)

  • censored_weight (float)

  • censored_fraction (float)

  • likelihood_aware (bool)

exact_weight: float

Alias for field number 0

censored_weight: float

Alias for field number 1

censored_fraction: float

Alias for field number 2

likelihood_aware: bool

Alias for field number 3

class CensoredExactOnlyStatistics(schema_version, child, exact_weight, censored_weight)[source]

Bases: NamedTuple

Parameters:
schema_version: int

Alias for field number 0

child: Any

Alias for field number 1

exact_weight: float

Alias for field number 2

censored_weight: float

Alias for field number 3

class CensoredAccumulator(base_accumulator, keys=None)[source]

Bases: SequenceEncodableStatisticAccumulator

Explicit exact-only projection statistics with auditable discarded interval weight.

Parameters:
  • base_accumulator (SequenceEncodableStatisticAccumulator)

  • keys (str | None)

update(x, weight, estimate)[source]

Accumulate one weighted observation under an optional current estimate.

Parameters:
  • x (Any)

  • weight (float)

  • estimate (CensoredDistribution | None)

Return type:

None

initialize(x, weight, rng)[source]

Initialize sufficient statistics from one weighted observation.

Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]

Accumulate weighted sufficient statistics from sequence-encoded observations.

Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]

Initialize sufficient statistics from sequence-encoded observations.

Parameters:
Return type:

None

combine(suff_stat)[source]

Merge serialized sufficient statistics into this accumulator.

Parameters:

suff_stat (CensoredExactOnlyStatistics)

Return type:

CensoredAccumulator

value()[source]

Return this accumulator’s serialized sufficient statistics.

Return type:

CensoredExactOnlyStatistics

from_value(x)[source]

Restore this accumulator from serialized sufficient statistics.

Parameters:

x (CensoredExactOnlyStatistics)

Return type:

CensoredAccumulator

scale(c)[source]

Scale linear sufficient statistics in-place by c.

The structural default is correct for ordinary weighted sums, nested tuples/lists/dicts, and numeric arrays. Families whose value() payload includes non-linear metadata such as support bounds must override this method and leave that metadata unscaled.

Parameters:

c (float)

Return type:

CensoredAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators whose key does not cover their whole state (several named keys, a wrapped child accumulator, a non-accumulator payload) override this – and ONLY those: as of 2026-07-14 every whole-state family uses this default, after a sweep found sixteen hand-rolled copies that had drifted into three broken shapes. If you are tempted to reimplement this per family, don’t; extend the base or override with super() calls. A keys of None (the default) is a no-op.

Why this exact shape is correct – and the drifted shapes are not: the POOL must live in the dict. Combining INTO the dict-held accumulator (below) mutates the pool in place, so after all sites merge, the dict entry holds everyone’s statistics and key_replace hands that pool to every site. The observed broken variants each violate one half: pulling the dict’s stats into self without writing back (the dict keeps the FIRST site only – later sites’ data silently discarded); merging only when the key is already present (nobody ever inserts: both passes are no-ops and tying does nothing); and replacing by pushing self INTO the dict (the LAST site overwrites the pool). The keyed-protocol sweep (keyed_protocol_sweep_test.py) enforces this behaviorally for every catalog family.

Parameters:

stats_dict (dict[str, Any])

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict[str, Any])

Return type:

None

acc_to_encoder()[source]

Return a sequence encoder compatible with this accumulator.

Return type:

CensoredDataEncoder

class CensoredAccumulatorFactory(base_factory, keys=None)[source]

Bases: StatisticAccumulatorFactory

Factory for CensoredAccumulator.

Parameters:
  • base_factory (StatisticAccumulatorFactory)

  • keys (str | None)

make()[source]

Create an empty censored-data accumulator.

Return type:

CensoredAccumulator

class ExactOnlyCensoredEstimator(base_estimator, name=None, keys=None)[source]

Bases: ParameterEstimator

Projection estimator that deliberately excludes interval evidence and reports its weight.

Parameters:
  • base_estimator (ParameterEstimator)

  • name (str | None)

  • keys (str | None)

accumulator_factory()[source]

Return a factory for censored-data sufficient-statistic accumulators.

Return type:

CensoredAccumulatorFactory

estimate(nobs, suff_stat)[source]

Estimate a distribution from accumulated sufficient statistics.

Parameters:
Return type:

CensoredDistribution

CensoredEstimator

alias of ExactOnlyCensoredEstimator

class CensoredLikelihoodStatistics(schema_version, observations, weights)[source]

Bases: NamedTuple

Parameters:
schema_version: int

Alias for field number 0

observations: tuple[Any, ...]

Alias for field number 1

weights: ndarray

Alias for field number 2

class CensoredEvidenceEncoder[source]

Bases: DataSequenceEncoder

Identity encoder that validates explicit exact/interval evidence for likelihood fitting.

seq_encode(x)[source]

Encode the iid observation sequence x for vectorized evaluation.

Parameters:

x (Sequence[Any])

Return type:

tuple[Any, …]

row_count(x)[source]

Return the number of observations represented by x.

The default covers simple array/list encodings and structurally aligned tuples/dicts. Encoders with flattened, ragged, or otherwise specialized layouts must override this method so metadata wrappers can verify counts instead of trusting a caller assertion.

Parameters:

x (Any)

Return type:

int

class CensoredLikelihoodAccumulator[source]

Bases: SequenceEncodableStatisticAccumulator

Retain validated evidence for a base-family-specific full censored-likelihood fit.

update(x, weight, estimate)[source]

Accumulate one weighted observation under an optional current estimate.

Parameters:
Return type:

None

initialize(x, weight, rng)[source]

Initialize sufficient statistics from one weighted observation.

Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]

Accumulate weighted sufficient statistics from sequence-encoded observations.

Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]

Initialize sufficient statistics from sequence-encoded observations.

Parameters:
Return type:

None

combine(suff_stat)[source]

Merge serialized sufficient statistics into this accumulator.

Parameters:

suff_stat (CensoredLikelihoodStatistics)

Return type:

CensoredLikelihoodAccumulator

value()[source]

Return this accumulator’s serialized sufficient statistics.

Return type:

CensoredLikelihoodStatistics

from_value(x)[source]

Restore this accumulator from serialized sufficient statistics.

Parameters:

x (CensoredLikelihoodStatistics)

Return type:

CensoredLikelihoodAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators whose key does not cover their whole state (several named keys, a wrapped child accumulator, a non-accumulator payload) override this – and ONLY those: as of 2026-07-14 every whole-state family uses this default, after a sweep found sixteen hand-rolled copies that had drifted into three broken shapes. If you are tempted to reimplement this per family, don’t; extend the base or override with super() calls. A keys of None (the default) is a no-op.

Why this exact shape is correct – and the drifted shapes are not: the POOL must live in the dict. Combining INTO the dict-held accumulator (below) mutates the pool in place, so after all sites merge, the dict entry holds everyone’s statistics and key_replace hands that pool to every site. The observed broken variants each violate one half: pulling the dict’s stats into self without writing back (the dict keeps the FIRST site only – later sites’ data silently discarded); merging only when the key is already present (nobody ever inserts: both passes are no-ops and tying does nothing); and replacing by pushing self INTO the dict (the LAST site overwrites the pool). The keyed-protocol sweep (keyed_protocol_sweep_test.py) enforces this behaviorally for every catalog family.

Parameters:

stats_dict (dict[str, Any])

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict[str, Any])

Return type:

None

acc_to_encoder()[source]

Return a sequence encoder compatible with this accumulator.

Return type:

CensoredEvidenceEncoder

class CensoredLikelihoodAccumulatorFactory[source]

Bases: StatisticAccumulatorFactory

make()[source]

Create a fresh accumulator instance.

Return type:

CensoredLikelihoodAccumulator

class CensoredLikelihoodEstimator(initial_base, fit, name=None, keys=None)[source]

Bases: ParameterEstimator

Delegate full censored-objective optimization to a declared base-family callback.

Parameters:
  • initial_base (Any)

  • fit (Callable[[tuple[Any, ...], np.ndarray, Any], SequenceEncodableProbabilityDistribution])

  • name (str | None)

  • keys (str | None)

accumulator_factory()[source]

Return the accumulator factory used to collect this estimator’s sufficient statistics.

Return type:

CensoredLikelihoodAccumulatorFactory

estimate(nobs, suff_stat)[source]

Estimate a distribution from accumulated sufficient statistics.

Parameters:
  • nobs (float | None)

  • suff_stat (CensoredLikelihoodStatistics)

Return type:

CensoredDistribution

class CensoredDataEncoder(dist=None, base_encoder=None)[source]

Bases: MaskedBaseEncoder

Split a batch into exact observations (base-encoded) and censoring intervals.

Encoded form is (exact_enc, exact_idx, lows, highs, cens_idx) where exact_idx / cens_idx are the original positions of the exact / censored rows so seq_log_density can scatter results back into the right order.

Parameters:
  • dist (CensoredDistribution | None)

  • base_encoder (DataSequenceEncoder | None)

classmethod from_base_encoder(base_encoder)[source]

Create a censored encoder from an already configured base encoder.

Parameters:

base_encoder (DataSequenceEncoder)

Return type:

CensoredDataEncoder

seq_encode(x)[source]

Encode a batch into exact values, interval bounds, and row indices.

Parameters:

x (Sequence[Any])

Return type:

tuple[Any, ndarray, ndarray, ndarray, ndarray]

row_count(x)[source]

Return the number of exact plus interval evidence rows.

Parameters:

x (Any)

Return type:

int