Skip to content

Commit c30e443

Browse files
ahmed-shuaibiclaude
andcommitted
scripts: MSK per-cancer-type stratification + driver; parameterize orchestrator
split_msk_by_cancer_type.py splits MSK-IMPACT/CHORD panel MAFs by clinical CANCER_TYPE (ONCOTREE_CODE is too granular: 502 codes) into per-type cohorts >= min samples. MSK-IMPACT -> 34 cohorts (NSCLC 7680 ... 100), MSK-CHORD -> 5. run_all_msk.sh drives them via MAF_DIR + ROOT env vars (run_cohort_pipeline.sh now parameterized; TCGA defaults unchanged). All 3 BMRs per Ahmed; run after the TCGA sweep to avoid contention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4ca3fd4 commit c30e443

3 files changed

Lines changed: 114 additions & 2 deletions

File tree

scripts/run_all_msk.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
# Drive the per-cohort pipeline across the MSK per-cancer-type sub-cohorts.
3+
#
4+
# Reuses run_cohort_pipeline.sh via MAF_DIR + ROOT env vars. All three BMRs are run
5+
# (per Ahmed's choice) -- note CBaSE/MutSig are calibrated for exome, not a ~500-gene
6+
# panel, so for MSK they also document how those BMRs behave on panel data; DIG is the
7+
# methodologically appropriate provider here. Two passes (fast CBaSE+DIG, then MutSig),
8+
# fully idempotent. Run AFTER the TCGA sweep to avoid CPU contention.
9+
set -u
10+
11+
run_study() {
12+
local split_dir="$1" out_root="$2"
13+
local mafs; mafs=$(ls "${split_dir}"/*.maf 2>/dev/null)
14+
echo "##### MSK STUDY ${out_root} ($(echo "$mafs" | wc -w) cohorts) $(date) #####"
15+
for maf in $mafs; do
16+
C=$(basename "$maf" .maf)
17+
echo "----- passA ${out_root} ${C} $(date +%H:%M:%S) -----"
18+
MAF_DIR="$split_dir" ROOT="$out_root" SKIP_MUTSIG=1 \
19+
bash scripts/run_cohort_pipeline.sh "$C"
20+
done
21+
for maf in $mafs; do
22+
C=$(basename "$maf" .maf)
23+
echo "----- passB ${out_root} ${C} $(date +%H:%M:%S) -----"
24+
MAF_DIR="$split_dir" ROOT="$out_root" \
25+
bash scripts/run_cohort_pipeline.sh "$C"
26+
done
27+
}
28+
29+
run_study data/mafs_msk_split/IMPACT2026 output/msk/IMPACT2026
30+
run_study data/mafs_msk_split/CHORD2024 output/msk/CHORD2024
31+
echo "##### ALL MSK COHORTS DONE $(date) #####"

scripts/run_cohort_pipeline.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
# A failed stage is logged and the rest continue. Usage: run_cohort_pipeline.sh ACC
1111
set -u
1212
C="$1"
13-
ROOT="output/pancan"
14-
MAF="data/mafs_pancan/${C}.maf"
13+
# Parameterized (defaults = TCGA pancan); MSK runs set MAF_DIR + ROOT in the environment.
14+
ROOT="${ROOT:-output/pancan}"
15+
MAF_DIR="${MAF_DIR:-data/mafs_pancan}"
16+
MAF="${MAF_DIR}/${C}.maf"
1517
DIG_RESULTS="external/DIGDriver/run/Pancan.genes.results.txt"
1618
DIALECT="/opt/anaconda3/envs/dialect/bin/dialect"
1719
PY="/opt/anaconda3/envs/dialect/bin/python"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Split an MSK panel MAF into per-cancer-type sub-cohort MAFs for DIALECT.
2+
3+
MSK-IMPACT/MSK-CHORD pool many cancer types in one cohort; running DIALECT on the pooled
4+
conflates a cancer-type-composition confound with real CO. This splits the MAF by the
5+
clinical ``CANCER_TYPE`` (broad level; ONCOTREE_CODE is far too granular -- 502
6+
codes for IMPACT) into one MAF per type with at least ``--min-samples`` mutated samples,
7+
mirroring the TCGA per-cohort design.
8+
9+
Usage::
10+
11+
python scripts/split_msk_by_cancer_type.py \
12+
--maf data/mafs_msk/MSK_IMPACT_2026.maf \
13+
--clinical data/mafs_msk/MSK_IMPACT_2026.clinical.txt \
14+
--out data/mafs_msk_split/IMPACT2026 --min-samples 100
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import argparse
20+
import io
21+
import re
22+
from pathlib import Path
23+
24+
import pandas as pd
25+
26+
27+
def slug(name: str) -> str:
28+
"""Filesystem-safe cohort label from a cancer-type string."""
29+
s = re.sub(r"[^0-9A-Za-z]+", "_", str(name)).strip("_")
30+
return s or "UNKNOWN"
31+
32+
33+
def load_sample_to_type(clinical: Path, field: str) -> dict:
34+
"""Map SAMPLE_ID -> cancer type, skipping cBioPortal's leading '#' comment lines."""
35+
with clinical.open() as fh:
36+
lines = [ln for ln in fh if not ln.startswith("#")]
37+
df = pd.read_csv(io.StringIO("".join(lines)), sep="\t", dtype=str)
38+
return dict(zip(df["SAMPLE_ID"], df[field], strict=False))
39+
40+
41+
def main() -> None:
42+
"""Write one per-cancer-type MAF for each type with enough mutated samples."""
43+
ap = argparse.ArgumentParser(description=__doc__)
44+
ap.add_argument("--maf", type=Path, required=True)
45+
ap.add_argument("--clinical", type=Path, required=True)
46+
ap.add_argument("--out", type=Path, required=True)
47+
ap.add_argument("--field", default="CANCER_TYPE")
48+
ap.add_argument("--min-samples", type=int, default=100)
49+
args = ap.parse_args()
50+
args.out.mkdir(parents=True, exist_ok=True)
51+
52+
sample_to_type = load_sample_to_type(args.clinical, args.field)
53+
maf = pd.read_csv(args.maf, sep="\t", low_memory=False, comment="#")
54+
matched = maf["Tumor_Sample_Barcode"].isin(sample_to_type)
55+
print(f"MAF rows: {len(maf)}; sample-barcode match rate: {matched.mean():.1%}")
56+
maf = maf[matched].copy()
57+
maf["_TYPE"] = maf["Tumor_Sample_Barcode"].map(sample_to_type)
58+
59+
kept, dropped = [], []
60+
for ctype, grp in maf.groupby("_TYPE"):
61+
n_samp = grp["Tumor_Sample_Barcode"].nunique()
62+
label = slug(ctype)
63+
if n_samp < args.min_samples:
64+
dropped.append((label, n_samp))
65+
continue
66+
out_fn = args.out / f"{label}.maf"
67+
grp.drop(columns="_TYPE").to_csv(out_fn, sep="\t", index=False)
68+
kept.append((label, n_samp, len(grp)))
69+
70+
kept.sort(key=lambda x: -x[1])
71+
print(f"\nKept {len(kept)} cohorts (>= {args.min_samples} samples):")
72+
for label, n_samp, n_mut in kept:
73+
print(f" {label:<45} {n_samp:>6} samples {n_mut:>8} muts")
74+
print(f"\nDropped {len(dropped)} types below threshold "
75+
f"({sum(n for _, n in dropped)} samples total).")
76+
77+
78+
if __name__ == "__main__":
79+
main()

0 commit comments

Comments
 (0)