Skip to content

"CAS" is hardcoded in the admixture step, but the label vocabulary is user-supplied #293

Description

@dvitale199

AncestryModel._predict_admixed special-cases the literal label "CAS"
(genotools/ancestry/model.py:684):

cas_train = train_pca_admix[train_pca_admix["label"] == "CAS"]
other_train = train_pca_admix[train_pca_admix["label"] != "CAS"]

if len(cas_train) > 0:
    ...
    birch = Birch(n_clusters=self.config.admixed.n_clusters_cas)

and, when the split runs, invents the label "CAS2" for the second subcluster.

Why this is a problem. The ancestry label vocabulary is user-supplied — it
comes from the --ref-labels TSV through an unconstrained LabelEncoder, or
from a pickled model's encoder. Hardcoding a label is the one thing this code is
otherwise careful not to do; CLAUDE.md calls it out by name, and it was the
second defect behind the old --amr-het flag (label == "AMR", round 11).

What it costs today. The special-casing exists because CAS is bimodal in
the GP2 reference panel
. On any other panel:

  • a panel with no CAS gets no subclustering, which is correct by accident —
    the len(cas_train) > 0 guard makes it a no-op rather than a crash (that
    guard is what fixed Ancestry prediction with clinical exome #202);
  • a panel whose bimodal group is called something else gets no subclustering at
    all, silently, with no warning that the treatment its data needs was skipped;
  • a panel that happens to use CAS for an unrelated group gets it split in two
    for no reason.

So it is not currently a live bug — it is a correctness hazard that produces
quiet, panel-dependent behaviour.

Possible shapes for a fix, roughly in increasing order of ambition:

  1. Move the label out of the code and into AdmixedConfig (it already owns
    n_clusters_cas), so a panel can name its own bimodal group — or name none.
  2. Warn when the configured label is absent from the training panel, so the
    silent no-op becomes visible.
  3. Detect bimodality rather than declaring it, which removes the need for any
    label at all.

Found while triaging #202. The missing test for the guard is #294.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ancestry-qualityAncestry model accuracy, calibration, validationcorrectnessWrong or fragile behaviour

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions