Analyses
For each wearer, privately: the spread of the window estimates, how much consecutive windows agree, and how many independent windows that leaves. Then the two sites: their difference, tested by permuting blocks of five windows, and whether it is shown to lie inside a margin, by an equivalence test of two one-sided tests (Lakens, 2017). Then the room, from the slips: the head's bias and scatter, against the room's median age.
import re
from itertools import combinations
import numpy as np
from scipy import stats
def estimates(path):
"""Each window's estimate from a saved run of age.py, in order, flagged windows left out."""
found = (re.match(r"window \d+: (\d+) years", line) for line in open(path)
if "kept out" not in line)
return np.array([float(m.group(1)) for m in found if m])
def describe(x):
"""Mean, spread, lag-one autocorrelation, and the windows' worth of independent evidence."""
r = np.corrcoef(x[:-1], x[1:])[0, 1]
n_eff = len(x) * (1 - r) / (1 + r)
return x.mean(), x.std(ddof=1), r, n_eff, x.std(ddof=1) / np.sqrt(n_eff)
def blocks(x, size=5):
n = len(x) // size
return x[:n * size].reshape(n, size).mean(axis=1)
site1, site2 = estimates("w1-site1.txt"), estimates("w1-site2.txt") # the runs you saved
for name, x in (("site 1", site1), ("site 2", site2)):
m, sd, r, n_eff, se = describe(x)
print(f"{name}: {len(x)} windows, mean {m:.1f}, sd {sd:.1f}, lag-one r {r:.2f}, "
f"worth {n_eff:.0f} independent windows, standard error {se:.1f}")
# The difference between sites, by permuting blocks of five windows.
a, b = blocks(site1), blocks(site2)
diff = b.mean() - a.mean()
both = np.r_[a, b]
null = np.array([both[list(c)].mean() - np.delete(both, list(c)).mean()
for c in combinations(range(len(both)), len(b))])
print(f"difference {diff:+.1f} years, two-sided p {np.mean(np.abs(null) >= abs(diff)):.3f}")
# Equivalence: two one-sided tests on the blocks, that the difference is inside 3 years.
margin = 3.0
p_low = stats.ttest_ind(b + margin, a, alternative="greater").pvalue
p_high = stats.ttest_ind(b - margin, a, alternative="less").pvalue
print(f"inside ±{margin:.0f} years: p = {max(p_low, p_high):.3f}")
# The room, from the slips: each wearer's estimate minus their age, and the room's median age
# minus their age, written side by side with no name.
head = np.array([7, 12, 3, 9, 15, 6, 10, -2, 8, 11])
median = np.array([2, -1, 0, 3, 1, -3, 0, -5, 1, 2])
print(f"the head: bias {head.mean():+.1f} years, scatter {head.std(ddof=1):.1f}, "
f"mean error {np.abs(head).mean():.1f}")
print(f"the room's median age: mean error {np.abs(median).mean():.1f}, "
f"paired p {stats.wilcoxon(np.abs(head), np.abs(median)).pvalue:.3f}")
Then report:
- Each wearer's site difference, with its p and its equivalence test, and what the two together say about "any site".
- The class's site differences together, and how many wearers and windows an equivalence test at your margin would need.
- The room's bias and scatter, the head against the room's median, and what the bias says about the head's training mean.
- What test of "any site" the published evaluation would need, and whether it has one.
Sources
Lakens, D. (2017). Equivalence tests: a practical primer for t tests, correlations, and meta-analyses. Social Psychological and Personality Science.
Smith, S. M., Vidaurre, D., Alfaro-Almagro, F., Nichols, T. E., and Miller, K. L. (2019). Estimation of brain age delta from brain imaging. NeuroImage.
Butler, E. R., and others (2021). Pitfalls in brain age analyses. Human Brain Mapping.
Engemann, D. A., and others (2022). A reusable benchmark of brain-age prediction from M/EEG resting-state signals. NeuroImage.
IntoMind (2026). Heads for encoder e2f9b60f411f0e73. github.com/intomind/eeg-foundation-model-heads.