API reference

Stats

How many independent data points do you actually have? That question runs through this whole subsystem. reward_lens.stats is pure numpy and scipy, no torch, and it is where the library keeps its honesty about sample size, effect size, and what a correlation is worth.

Effective sample size and clones

Thirty rollouts drawn from six seeds are not thirty data points. effective_sample_size computes the Kish effective size from seed labels, detect_clones finds the duplication by content hash, and the cluster routines resample at the seed level so a confidence interval widens to match the independence you really have. The worked example is effective sample size of an eval set.

# func

effective_sample_size

Source

reward_lens.stats.ess.effective_sample_size

effective_sample_size(seed_labels: Sequence) -> float

Lineage-aware effective sample size from seed multiplicities (Kish).

Group the rows by their seed label and let w_s be the number of rows carrying seed s. The effective sample size is the Kish formula

ESS = (sum_s w_s)**2 / sum_s w_s**2

which is the inverse-Simpson count of “how many independent seeds is this dataset worth.” Three anchor cases pin the intuition:

  • N rows that are all clones of ONE seed: one weight w = N, so ESS = N2 / N2 = 1.0. Cloning buys nothing.
  • N rows from N distinct seeds: ESS = N**2 / N = N. Every row is its own lineage, so the effective n is the nominal n.
  • A balanced design, e.g. 30 seeds each expanded to 5 mutations: weights are all 5, so ESS = 150**2 / (30 * 25) = 30. The effective n is the number of seeds, not the number of expanded rows.

Empty input returns 0.0.

# func

detect_clones

Source

reward_lens.stats.ess.detect_clones

detect_clones(content_hashes: Sequence[str]) -> dict

Detect exact-duplicate content and collapse it to weighted items.

Exact-duplicate content hashes are byte-identical stimuli that must not count as independent observations. This is the ingest-time check R7 calls for: it reports how many rows are duplicates and the per-hash multiplicity so the data plane can collapse them to weights with a warning.

Parameters

Name Type Default Description
content_hashes Sequence[str] required One content hash per row.

Returns

Type Description
dict

A dict with:

  • n_rows: total number of rows.
  • n_unique: number of distinct content hashes.
  • weights: mapping from content hash to its row count.
  • duplicate_fraction: fraction of rows that are duplicates of an already-seen hash, (n_rows - n_unique) / n_rows. 0.0 when every row is unique or the input is empty.
# func

cluster_bootstrap

Source

reward_lens.stats.ess.cluster_bootstrap

cluster_bootstrap(
    values: Sequence[float] | np.ndarray,
    seed_labels: Sequence,
    statistic: Callable[[np.ndarray], float] = np.mean,
    n_resamples: int = 10000,
    ci: float = 0.95,
    seed: Optional[int] = None,
    allow_clone_resampling: bool = False
) -> BootstrapResult

Bootstrap CI that resamples at the seed (cluster) level by default.

The default draws whole seeds with replacement (a cluster/block bootstrap): on each resample it picks K seeds from the K observed seeds with replacement and takes all rows belonging to each drawn seed, then computes the statistic on the pooled rows. This is the honest interval, because the unit of independence is the seed, not the expanded row. On a dataset of clones of a single seed there is only one cluster to draw, so a CI cannot be formed and the bounds are nan (method "bootstrap-cluster"): the routine refuses to manufacture precision from clones.

Setting allow_clone_resampling=True reverts to resampling individual rows with replacement, which is exactly the v1 behaviour that inflated n. It is kept because there are legitimate uses (a dataset that genuinely has one independent row per seed), but it stamps the result with method="bootstrap-CLONE-INFLATED" so any Evidence built from it is marked as potentially inflated and no downstream reader is misled.

Parameters

Name Type Default Description
values Sequence[float] | np.ndarray required One observation per row.
seed_labels Sequence required One seed/lineage label per row (same length as values).
statistic Callable[[np.ndarray], float] np.mean Callable taking a numpy array of pooled rows, returning a scalar.
n_resamples int 10000 Number of resamples.
ci float 0.95 Confidence level.
seed Optional[int] None RNG seed.
allow_clone_resampling bool False Opt into row-level resampling (marks the result).

Returns

Type Description
BootstrapResult BootstrapResult with the appropriate method set.
# func

cluster_permutation

Source

reward_lens.stats.ess.cluster_permutation

cluster_permutation(
    a: Sequence[float] | np.ndarray,
    b: Sequence[float] | np.ndarray,
    seed_labels: Sequence,
    n_permutations: int = 10000,
    statistic: str = 'mean_diff',
    alternative: str = 'two-sided',
    seed: Optional[int] = None
) -> float

Paired permutation test that flips signs at the seed (cluster) level.

The row-level paired_permutation_test flips the sign of each row’s difference independently, which over-counts evidence when many rows share a seed: fifty clones of one stimulus give fifty independent sign flips that the lineage says should move together. Here the sign is drawn once per seed and applied to every row of that seed, so the permutation null respects the unit of independence. With K seeds the null explores 2**K sign assignments, not 2**n.

Parameters

Name Type Default Description
a Sequence[float] | np.ndarray required First set of paired observations, one per row.
b Sequence[float] | np.ndarray required Second set of paired observations, one per row (same length as a).
seed_labels Sequence required One seed label per row (same length as a).
n_permutations int 10000 Number of seed-level sign-flip permutations.
statistic str 'mean_diff' One of {“mean_diff”, “median_diff”} on the row differences.
alternative str 'two-sided' One of {“two-sided”, “greater”, “less”}.
seed Optional[int] None RNG seed.

Returns

Type Description
float p-value with the Phipson-Smyth (count + 1) / (n + 1) correction.
float Returns 1.0 when n < 2 (no power).

Effect sizes and correlation

Standardized effect sizes with bias-corrected bootstrap intervals, and a Spearman correlation that reports its own confidence interval rather than a bare number. These are the primitives the bias battery and the faithfulness measurements are built from.

# func

cohens_d

Source

reward_lens.stats.effects.cohens_d

cohens_d(
    a: Sequence[float] | np.ndarray,
    b: Optional[Sequence[float] | np.ndarray] = None,
    paired: bool = False
) -> float

Returns nan (not inf) when n < 2 or the relevant std is zero. The v1 hacking detector returned inf in those cases — that’s the bug this function exists to fix.

# func

bca_bootstrap

Source

reward_lens.stats.effects.bca_bootstrap

bca_bootstrap(
    values: Sequence[float] | np.ndarray,
    statistic: Callable[[np.ndarray], float] = np.mean,
    n_resamples: int = 10000,
    ci: float = 0.95,
    seed: Optional[int] = None
) -> BootstrapResult

Bias-corrected and accelerated (BCa) bootstrap CI.

The percentile bootstrap is biased when the sampling distribution of the statistic is skewed or when the statistic is a biased estimator (Cohen’s d and correlations both qualify). BCa corrects for that with two quantities:

  • z0 (bias correction): the normal quantile of the fraction of bootstrap replicates falling below the point estimate. If the replicates straddle the point symmetrically z0 is 0 and no bias correction is applied.
  • a (acceleration): a jackknife estimate of how fast the standard error changes with the true value, capturing skew. With m_i the leave-one-out estimates and mbar their mean, a = sum((mbar - m_i)**3) / (6 * (sum((mbar - m_i)**2))**1.5).

These map the requested central interval onto adjusted percentiles of the replicate distribution. When the acceleration denominator is zero (a constant jackknife, e.g. degenerate input) a falls back to 0, and when the bias correction is undefined (all replicates on one side of the point) the interval falls back to the plain percentile bounds. Both keep the result finite instead of propagating nan or inf.

Returns NaN bounds for n < 2, mirroring bootstrap_ci.

# func

spearman_with_ci

Source

reward_lens.stats.effects.spearman_with_ci

spearman_with_ci(
    x: Sequence[float] | np.ndarray,
    y: Sequence[float] | np.ndarray,
    n_resamples: int = 10000,
    ci: float = 0.95,
    seed: Optional[int] = None
) -> BootstrapResult

Spearman rank correlation with bootstrap CI.

Pair-bootstrap (resample (x_i, y_i) jointly, preserving pairing). The point estimate uses scipy if available, else a numpy implementation.

ROC and calibration

A detector is only as good as its operating curve. roc_pr scores a detector against a known answer key; calibration_curve checks whether a probability means what it says. Both feed the scorecards that turn an instrument into a calibrated one.

# func

roc_pr

Source

reward_lens.stats.roc.roc_pr

roc_pr(scores: np.ndarray, labels: np.ndarray) -> ROCResult

Compute ROC and PR curves from scores and binary labels.

Parameters

Name Type Default Description
scores np.ndarray required Real-valued scores, higher meaning “more positive”.
labels np.ndarray required Binary labels in {0, 1}; label 1 is the positive class.

Returns

Type Description
ROCResult A ROCResult. Non-finite scores or labels are dropped first. If after
ROCResult that either class is empty, the AUC and average precision are nan and
ROCResult the curve arrays are a single nan (the curves are undefined without
ROCResult both classes), so the caller never divides by an empty class.
# func

calibration_curve

Source

reward_lens.stats.roc.calibration_curve

calibration_curve(probs: np.ndarray, labels: np.ndarray, n_bins: int = 10) -> dict

Reliability-diagram data and the expected calibration error.

Bins the predicted probabilities into n_bins equal-width bins over [0, 1] and, per bin, reports the empirical accuracy (mean label) against the mean confidence (mean probability). A perfectly calibrated model has accuracy equal to confidence in every bin.

Parameters

Name Type Default Description
probs np.ndarray required Predicted probabilities in [0, 1].
labels np.ndarray required Binary outcomes in {0, 1}.
n_bins int 10 Number of equal-width probability bins.

Returns

Type Description
dict A dict with bin_centers, bin_accuracy, bin_confidence (each
dict length n_bins, with nan for empty bins), bin_counts, and
dict ece. The expected calibration error is the count-weighted mean gap
dict sum_b (n_b / N) * |acc_b - conf_b| over non-empty bins, which lies in
dict [0, 1].

Mutual information

A nonparametric mutual-information estimate, in bits, for the couplings the reward science measures between a feature and the reward it drives.

# func

mi_ksg

Source

reward_lens.stats.mi.mi_ksg

mi_ksg(
    x: np.ndarray,
    y: np.ndarray,
    k: int = 3,
    *,
    base: float = 2.0,
    jitter: float = 1e-10,
    seed: int = 0
) -> float

KSG estimator 1 of the mutual information between continuous x and y (Kraskov 2004).

For each point the distance eps to its k-th nearest neighbour is taken in the joint space under the maximum norm; then n_x and n_y count the points falling strictly within eps in each marginal. The estimate is I = psi(k) + psi(N) - <psi(n_x + 1) + psi(n_y + 1)> in nats, where psi is the digamma function and the marginal counts are read self-inclusive (query_ball_point includes the point itself, so the returned count is already n_x + 1). A tiny tie-breaking jitter is added first. Negative estimates (which the estimator can produce near zero MI) are clamped to zero, and the result is returned in bits by default.