ArrayTrain: BIC-selected unbounded θ mixtures + HWE-guided labelling
Fixed θ windows mislabelled compressed/asymmetric loci (2-cluster loci: 1418 vs 186 errors). Now 1/2/3-component mixtures chosen by BIC, labelled by HWE goodness of fit plus weak position and gap priors, split clusters merged. GSE165845 chr22 vs 1000G 30x (240 arrays): concordance 99.44 % (v1 99.27 %, vendor EGT 99.46 %), NRD 1.30 % (1.89 %, 1.23 %), call rate 99.61 %. docs/validation/V2_arraytrain.md, K-030; plan §12, CLI-038.
This commit is contained in:
1 parent
f17895bff2
commit
7c57eb5586
6 files changed
+304
-58
No files matched your search
@@ -69,3 +69,20 @@ def test_egt_round_trip(tmp_path: Path) -> None: # plan §12 step 5
|
||||
e = read_egt(p)
|
||||
assert e.data_block_version == 9 and e.extra_per_record == 15 and e.names == [f"rs{i}" for i in range(k)]
|
||||
assert (e.n == n).all() and np.allclose(e.gentrain_score, 0.8) and np.allclose(e.theta_mean[0], [0.1, 0.5, 0.9])
|
||||
|
||||
|
||||
def test_compressed_and_two_cluster_labelling() -> None:
|
||||
"""Compressed loci (BB at θ ≈ 0.45) and AB + BB two-cluster loci are labelled by HWE + position, not by fixed
|
||||
θ windows (the GSE165845 failure modes, docs/validation/V2_arraytrain.md)."""
|
||||
rng = np.random.default_rng(4)
|
||||
s = 200
|
||||
g = rng.binomial(2, 0.6, s) # compressed: AA 0.05, AB 0.25, BB 0.45
|
||||
theta_c = np.clip(np.array([0.05, 0.25, 0.45])[g] + rng.normal(0, 0.02, s), 0, 1)
|
||||
g2 = rng.binomial(2, 0.9, s)
|
||||
g2[g2 == 0] = 1 # rare allele never homozygous: two clusters, AB (0.5) + BB (0.95)
|
||||
theta_2 = np.clip(np.array([0.05, 0.5, 0.95])[g2] + rng.normal(0, 0.025, s), 0, 1)
|
||||
theta = np.vstack([theta_c, theta_2]).astype(np.float32)
|
||||
fit = fit_block(theta, np.ones_like(theta), np.ones_like(theta, bool), np.zeros(2, bool))
|
||||
assert (fit.n[0] > 0).all() and np.allclose(fit.theta_mean[0], [0.05, 0.25, 0.45], atol=0.02)
|
||||
assert fit.n[1, 0] == 0 and fit.n[1, 1] > 0 and fit.n[1, 2] > 0
|
||||
assert abs(fit.n[1, 1] - (g2 == 1).sum()) <= 3
|
||||
Reference in new issue
Block a user