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.
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.
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:
|
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. |
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.
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.
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. Withm_ithe leave-one-out estimates andmbartheir 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.
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.
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. |
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.
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.