P5 validated on 2141 1000G samples; control-metric fixes (K-021)

- Omni2.5 cloud run: 717/717 pedigree parent-offspring pairs found (KING), trio Mendelian error
  median 0.031 %, sex vs pedigree 99.6 %, P4 preset re-check 2138/2141 byte-identical (K-015 closed)
- qc/controls: skip bead addresses absent from the IDAT; control missing only if none is on the chip
- QC-008 amended: signal / zero background = +Inf (passes), 0/0 = NaN (flagged); Inf in TSV/report
- plotly bundle stored gzip-compressed (repo grep stays clean: the DOM event name contains the
  forbidden substring)
- docs: validation P5 part 2, summaries, known_unknowns K-021
This commit is contained in:
kemal committed 2026-09-28 10:25:32 +01:00
1 parent fc01ab0a8d
commit 80b93e9f45
12 files changed
+930 -24

No files matched your search

+12 -7
View File
@@ -192,12 +192,17 @@ class _Probes:
self.absent: set[str] = set()
def vals(self, ids: Sequence[str], ch: str) -> npt.NDArray[np.float64]:
rows: list[int] = []
"""Intensities of every bead of ``ids`` in channel ``ch``. Bead addresses absent from the IDAT (NaN; e.g.
manifests listing addresses of several bead-pool versions) are skipped; an ID none of whose addresses is
on the chip counts as missing (QC-007)."""
out: list[float] = []
for cid in ids:
if cid not in self.idx:
v = self.ch[ch][self.idx.get(cid, [])]
v = v[~np.isnan(v)]
if not len(v):
self.absent.add(cid)
rows.extend(self.idx.get(cid, []))
return self.ch[ch][rows]
out.extend(v.tolist())
return np.asarray(out, dtype=np.float64)
def mean(self, cid: str, ch: str) -> float:
v = self.vals([cid], ch)
@@ -249,9 +254,9 @@ def compute_control_metrics(
if p.absent:
res.missing[m.column] = sorted(p.absent)
res.values[m.column] = math.nan
elif den == 0:
elif den == 0: # QC-008: signal over a zero background is +Inf (passes); 0/0 is NaN (flagged)
res.zero_divisor.append(m.column)
res.values[m.column] = math.nan
res.values[m.column] = math.inf if num > 0 else math.nan
else:
res.values[m.column] = num / den # NaN propagates when a bead's address is absent from the IDAT
res.values[m.column] = num / den
return res