oncodsl / Read docs /PROMPT_confound_pass.md
govindbalki's picture
Upload folder using huggingface_hub
0fff343 verified
|
Raw
History Blame Contribute Delete
7.58 kB

Prompt β€” confound pass: extend Effect's confounders + add site-stratified / purity-survival flags to the module ranking

Goal: let us tell whether a gene/module separates HPV for a mechanistic reason or via a confounder (sex, ancestry, anatomic site, immune composition). Read the named code first; don't assume. Airgap stays absolute (clinical/technical covariates are named non-gene variables β€” allowed, same category as the existing stage/age; purity is derived in the validation layer, never in the engine). pytest + tsc after.

VERIFIED data availability (don't re-assume β€” checked against the repo)

Raw HNSC clinical files (data/raw_hnsc/data_clinical_patient.txt / _sample.txt) contain: SEX, RACE, ETHNICITY, AJCC_PATHOLOGIC_TUMOR_STAGE, AGE, ICD_O_3_SITE (patient) and TUMOR_TISSUE_SITE / ONCOTREE_CODE (sample). The processed clinical.parquet (build_hnsc.py ~lines 110-117) currently keeps only site(=ONCOTREE_CODE), age, sex, stage, hpv_status. So:

  • sex β€” already in the parquet. βœ“
  • race / ethnicity β€” in the raw files, NOT yet extracted. Need a build change.
  • anatomic site for oropharynx β€” ONCOTREE_CODE is insufficient; use TUMOR_TISSUE_SITE (sample) or ICD_O_3_SITE (patient). Need a build change.
  • smoking β€” NOT in this download at all. Do not attempt to adjust for it.
  • tumor purity β€” NOT in clinical. Must be derived from expression (see Part E).

Part A β€” extend the HNSC build to carry the confounders (data)

Note (verified): read_clinical_tsv returns the FULL dataframe, so RACE / TUMOR_TISSUE_SITE / ICD_O_3_SITE are reachable via merged.get(...) β€” no need to add them to HNSC_REQUIRED_*_COLS. In data_pipeline/build_hnsc.py (the clinical dict ~lines 110-117): read and write into clinical.parquet:

  • race (from RACE; optionally also ethnicity).
  • tissue_site β€” a clean anatomic-site field from TUMOR_TISSUE_SITE (preferred) or ICD_O_3_SITE, plus a derived boolean is_oropharynx (map the oropharynx/tonsil/base-of-tongue values; print the distinct site values + counts so we can confirm the mapping). Keep the existing site(ONCOTREE) column too.
  • sex is already present β€” leave it. Print the non-null counts for race / tissue_site / is_oropharynx so missingness is visible. (Colorectal build unchanged.)

Part B β€” carry the new clinical fields through the API (no airgap change)

_prepare_lab_data (api/app.py) currently subsets cohort.clinical[["stage","age"]]. For HNSC, widen the kept set to include sex, race, tissue_site/is_oropharynx (only the columns that exist; guard each). These are named clinical variables, not gene symbols β€” airgap-clean. Keep colorectal as-is. The returned clinical DataFrame is what both Effect (via the run's ctx) and the module endpoint (Part D/E) will read, so the new columns must be in the RETURN, not just kept somewhere upstream.

Part C β€” generalize Effect to a configurable confounder set (engine)

Effect.execute (engine_v2/nodes.py ~436-471) hardcodes stage + age. Generalize it to adjust on a configurable list of confounders pulled from ctx.clinical (one-hot encode categoricals like sex/race/site; keep age continuous; dropna per the design matrix; guard rank/collinearity). Default must stay stage + age so existing MSI/TMB/HPV runs are byte-for-byte unchanged. When the extra clinical columns are present, allow Effect to include sex + race (NOT smoking β€” unavailable). The confounder set is fixed by config, not GP-selectable (the program shouldn't roam over arbitrary confounders). Update/extend the relevant engine_v2 tests.

Part D β€” site-stratified survival flag on the module ranking (validation layer)

IMPORTANT (verified): _compute_module_ranking currently discards clinical β€” line ~1282 is M_all, y_all, _clin, _extra = _prepare_lab_data(...) with clinical thrown away as _clin. Stop discarding it: capture the clinical frame, and align is_oropharynx to the held-out patients (the same test_ids it already reconstructs) so you can subset. Then, for the HNSC/HPV case add per-module:

  • combined_holdout_oropharynx: the module's mean-aggregate AUROC recomputed within the oropharynx subgroup only (held-out patients with is_oropharynx == True), and a survives_site boolean = it stays close to the full-cohort combined AUROC (within a tolerance, e.g. drop < ~0.05) rather than collapsing toward 0.5. This tells us whether the module separates HPV beyond just marking oropharynx tissue.
  • Guard small subgroups (skip / mark "n too small" if the oropharynx held-out has too few per class).
  • Gate to HNSC; colorectal modules omit this gracefully.

Part E β€” purity-survival flag (validation layer; heaviest part β€” may be phased)

Purity isn't in the data, so derive an immune-infiltration proxy in the validation layer (names allowed here, exactly like the rank diagnostics): per patient, the mean expression of a small standard immune-marker set (e.g. CD8A, GZMB, PRF1, CD3D, CD2 β€” a fixed curated list in one place). Since the matrix M is opaque-columned, resolve those marker SYMBOLS to their opaque IDs via the bounded reveal path (same as reference sets), then mean those columns of M per patient. Higher proxy = more infiltrate = lower purity. Then per module add:

  • combined_holdout_highpurity: the module's mean-aggregate AUROC recomputed within the low-infiltration (high-purity) subset of held-out patients (e.g. bottom tertile of the proxy), and a survives_purity boolean (same tolerance idea). This catches modules whose HPV signal is really an immune-composition artifact (they'll collapse in the high-purity subset).
  • This derivation lives in the validation/API layer β€” never in engine/ or engine_v2/. If scope is tight, ship Parts A-D first and land Part E second; mark it clearly.

Part F β€” UI: surface the flags + the winner's-curse caveat (presentation)

  • In the Coordinated-modules table, show the survival flags per module (e.g. small "site βœ“/βœ—" and "purity βœ“/βœ—" chips, or the stratified AUROCs on expand). A module that survives both is a real candidate; one that collapses is a likely confound artifact.
  • Add to the modules "?" the winner's-curse caveat we agreed on, verbatim intent: "These groups are scored by re-evaluating ~2,900 explored sets on the same small held-out set and showing the best β€” so the very top values are optimistically biased (the luckiest of thousands). The winning program was chosen by cross-validation, which guards against that, so trust it as the engine's pick even when a table row scores higher."

CONSTRAINTS

  • Airgap: clinical confounders (sex/race/site) and the derived purity proxy are named non-gene variables / validation-layer computations β€” never enter engine/ or engine_v2/ as gene identities. Module payloads stay opaque-ID + scores + flags.
  • Defaults preserve current behaviour (Effect default = stage+age; non-HNSC unaffected). Colorectal + existing tests stay green.

Checkpoint

  • HNSC clinical.parquet carries race + tissue_site + is_oropharynx (with printed counts); sex already there.
  • Effect adjusts on a configurable set, default stage+age unchanged; with HNSC clinical present it can include sex+race.
  • Module ranking shows site-stratified (and, when Part E lands, purity) survival flags for HNSC/HPV; modules that are site/immune artifacts visibly collapse.
  • Modules "?" carries the winner's-curse caveat.
  • pytest green, tsc clean, airgap tests untouched.