blinklinmult.compare.bootstrap

The paired cluster bootstrap, and the refusal to report one when it cannot.

The estimator is ordinary; the two decisions around it are what matter.

Resampling is paired. One draw of clusters is scored by every model, so the difference between two models is computed within a draw rather than between two independent draws. See resample().

Below :data:`MIN_CLUSTERS_FOR_INTERVAL` clusters, no interval is returned at all. This is deliberate and is the module’s sharpest opinion. A percentile bootstrap over one cluster resamples that same cluster every time: the spread is exactly zero and the “interval” has zero width, which reads as a precise estimate when it is the opposite. TalkingFace (one recording, 524 windows, 61 events) is the case this exists for. The alternative – silently falling back to window-level resampling to manufacture a number – is the error this whole package was written to avoid.

Attributes

MIN_CLUSTERS_FOR_INTERVAL

Fewest clusters that can carry a confidence interval.

DEFAULT_ROUNDS

Bootstrap replicates.

DEFAULT_SEED

Seed for the resampling draw, recorded alongside every result.

Classes

Interval

A point estimate, with an interval when the data can carry one.

Functions

bootstrap_statistic(→ tuple[Interval, numpy.ndarray])

Resample clusters and summarise a statistic over the draws.

two_sided_p(→ float)

Bootstrap p-value for the null that the difference is zero.

Module Contents

blinklinmult.compare.bootstrap.MIN_CLUSTERS_FOR_INTERVAL = 10

Fewest clusters that can carry a confidence interval.

Below this the resampling distribution is too coarse for its tails to mean anything: with n clusters a resample can only take n distinct values per slot, and the 2.5th percentile is estimated from a handful of distinct compositions. Ten is a judgement, not a theorem – it admits RN15 (20) and RN30 (35) while excluding TalkingFace (1) and MRL (6 subjects).

blinklinmult.compare.bootstrap.DEFAULT_ROUNDS = 10000

Bootstrap replicates.

Large enough that the 2.5%/97.5% endpoints rest on ~250 replicates each, so the Monte Carlo noise on an endpoint is small next to the sampling noise the interval is reporting. Cheap here because a replicate is array arithmetic over precomputed per-recording scores, never a re-run of a model.

blinklinmult.compare.bootstrap.DEFAULT_SEED = 20260905

Seed for the resampling draw, recorded alongside every result.

class blinklinmult.compare.bootstrap.Interval[source]

A point estimate, with an interval when the data can carry one.

Parameters:
  • point (float) – The statistic on the observed sample.

  • low (float | None) – Lower bound, or None when refused.

  • high (float | None) – Upper bound, or None when refused.

  • n_clusters (int) – Independent units behind the estimate. The honest sample size, which is why it travels with the number.

  • rounds (int) – Replicates drawn.

  • seed (int) – Seed used.

  • reason (str) – Why no interval was produced; empty when there is one.

property reportable: bool

Whether an interval was produced.

Returns:

True when both bounds are present.

Return type:

bool

property excludes_zero: bool

Whether the interval lies wholly above or below zero.

Returns:

False when there is no interval, so an unreportable result is never mistaken for a significant one.

Return type:

bool

describe() → str[source]

One line for a table cell.

Returns:

e.g. "0.5464 [-0.0670, +0.0270]", or the refusal.

Return type:

str

blinklinmult.compare.bootstrap.bootstrap_statistic(statistic: collections.abc.Callable[[collections.abc.Sequence[int]], float], n_clusters: int, rounds: int = DEFAULT_ROUNDS, seed: int = DEFAULT_SEED) → tuple[Interval, numpy.ndarray][source]

Resample clusters and summarise a statistic over the draws.

Parameters:
  • statistic (Callable[[Sequence[int]], float]) – Maps cluster indices to a scalar. Called once per replicate, so it should be arithmetic over precomputed per-cluster values rather than anything that re-scores.

  • n_clusters (int) – Clusters available.

  • rounds (int) – Replicates.

  • seed (int) – Seed.

Returns:

The summary, and the raw draws for a downstream p-value.

Return type:

tuple[Interval, np.ndarray]

blinklinmult.compare.bootstrap.two_sided_p(draws: numpy.ndarray) → float[source]

Bootstrap p-value for the null that the difference is zero.

Uses the (1 + count) / (B + 1) form rather than a bare proportion. The correction matters: without it a difference that never changes sign reports p = 0, claiming certainty no finite resampling can support. With it the floor is 2 / (B + 1), which is exactly what B replicates can evidence – and that identity is what tests/compare/test_bootstrap.py pins.

Parameters:

draws (np.ndarray) – Bootstrap distribution of the difference.

Returns:

Two-sided p-value in (0, 1].

Return type:

float