blinklinmult.compare.clusters

The unit of resampling: what counts as one independent observation.

This module exists because getting this wrong is the most common way a model comparison overstates its own certainty, and it fails silently – the arithmetic is identical, only the interval is wrong, and it is wrong in the flattering direction.

A recording is one observation. A window is not. Two 15-frame windows cut from the same recording share a subject, a camera, an illuminant, an eyelid shape and a blink rate. They are close to duplicates. Resampling them independently pretends the corpus holds far more information than it does: measured on RN30, 7312 windows come from 35 recordings, and treating the windows as independent shrank the confidence interval on a model difference by 5.9x – turning an inconclusive result into a significant one.

The same argument applies one level down. The left and right eye of one subject are two samples, but they are not independent: they blink together. So both eyes land in the same cluster, which cluster_of() enforces by construction.

Attributes

SAMPLE_SEPARATOR

Separator inside a sample id, matching data.schema.build_sample_id.

Exceptions

ClusterError

Raised when clusters cannot be determined from the given identifiers.

Functions

cluster_of(→ str)

Return the cluster a sample belongs to.

clusters_from_signals(→ list[str])

Reduce recording keys to their sorted, unique clusters.

resample(→ numpy.ndarray)

Draw bootstrap resamples of cluster indices.

Module Contents

blinklinmult.compare.clusters.SAMPLE_SEPARATOR = '|'

Separator inside a sample id, matching data.schema.build_sample_id.

exception blinklinmult.compare.clusters.ClusterError[source]

Bases: ValueError

Raised when clusters cannot be determined from the given identifiers.

Initialize self. See help(type(self)) for accurate signature.

blinklinmult.compare.clusters.cluster_of(sample_id: str) → str[source]

Return the cluster a sample belongs to.

Sample ids are video_id|frame_group|eye_side (see build_sample_id()), so the cluster is the leading component. Both eyes of one subject map to the same cluster: they blink together, so resampling them independently would be the window-level error one level down.

Parameters:

sample_id (str) – An id built by build_sample_id(), or a bare recording name, which is already its own cluster.

Returns:

The cluster key.

Return type:

str

Raises:

ClusterError – If the id is empty, which would silently pool everything into one nameless cluster.

blinklinmult.compare.clusters.clusters_from_signals(recordings: collections.abc.Sequence[str]) → list[str][source]

Reduce recording keys to their sorted, unique clusters.

Parameters:

recordings (Sequence[str]) – Keys from load_signals(). Read the archive through that function rather than numpy.load(): it checks which target the truth arrays hold, and reading a blink_presence archive as eye_state once fabricated 200 045 phantom false positives.

Returns:

Unique clusters, sorted so a run is reproducible.

Return type:

list[str]

Raises:

ClusterError – If no recordings were given, since there is nothing to resample.

blinklinmult.compare.clusters.resample(n_clusters: int, rounds: int, seed: int) → numpy.ndarray[source]

Draw bootstrap resamples of cluster indices.

One array for every model to share. Drawing once and evaluating every model on the same draw is what makes the comparison paired: a resample heavy on hard recordings drags all models down together, so the difference between them is stable where their absolute scores are not. At n=20-35 clusters that between-recording variance dominates, and an unpaired interval would be too wide to resolve anything.

Parameters:
  • n_clusters (int) – How many clusters the corpus has.

  • rounds (int) – Bootstrap replicates.

  • seed (int) – Seed, recorded in the output so a result can be reproduced.

Returns:

(rounds, n_clusters) indices, drawn with replacement.

Return type:

np.ndarray

Raises:

ClusterError – If either argument is not positive.