diff --git a/CLAUDE.md b/CLAUDE.md index 62867ad..ef120fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -695,13 +695,15 @@ they can't tell you: `check:splitby-perf`) are stages of `scripts/gate.sh` of their own, and the boundary suite is in `npm test`. `npm run report:coverage` — the unit suites' coverage (CI shows it on the reports job). - **Every view kind's output is pinned** by `test/view-snapshots.test.ts` (in `npm test`, no + **Every view kind's output is pinned** by the view snapshot tests (`test/view-snapshots*.test.ts`, + four parallel shards of `test/support/view-snapshots.ts`; in `npm test`, no database): one gallery example per kind runs through the whole engine against warehouse answers recorded in `test/snapshots/answers/`, and its compiled SQL and queries, computed result, figure and results text are compared with `test/snapshots/.{sql,json,svg,txt}`. A diff there is a change to review, not noise: `UPDATE_SNAPSHOTS=1` accepts output changes, and a changed query - needs `RECORD_SNAPSHOTS=1` with `.env.local` (public warehouse only; the answers hold public - studies' patient-level rows). A new view kind adds its case there. Each case also runs with its + needs `RECORD_SNAPSHOTS=1 node --env-file=.env.local --test test/view-snapshots*.test.ts` + (public warehouse only; the answers hold public studies' patient-level rows). A new view kind + adds its case to `CASES` there (a check fails until it does). Each case also runs with its answers delivered in reverse, and must not change: a runner combines what it asked for after every answer is in, in the view's order (`askEachSite` in `lib/views/shared.ts`), never inside the callback as each one lands. diff --git a/lib/ch.ts b/lib/ch.ts index 12b7f00..86aa358 100644 --- a/lib/ch.ts +++ b/lib/ch.ts @@ -231,7 +231,8 @@ export interface ChStatement { conn: ChConn; format: string; query: string; exte /** * Answers statements instead of the database, or watches them on their way to it (`send`, which * takes extra ClickHouse settings for that statement, e.g. "max_threads=1"). Only - * test/view-snapshots.test.ts sets one: it records the warehouse's answers, then replays them. + * the view snapshot tests (test/support/view-snapshots.ts) set one: they record the warehouse's + * answers, then replay them. */ export type ChTransport = (st: ChStatement, send: (settings?: string) => Promise) => Promise; let transport: ChTransport | null = null; diff --git a/lib/views/shared.ts b/lib/views/shared.ts index a0f7472..ffd0f1f 100644 --- a/lib/views/shared.ts +++ b/lib/views/shared.ts @@ -73,7 +73,7 @@ export async function studyCatalogs(c: ViewRun, studies: string[]): Promise(ctx: ViewRun, resolvers: Record, ask: (cohort: string, study: string, r: R) => Promise): Promise<{ cohort: string; study: string; answer: T }[]> { diff --git a/test/snapshots/acquired.json b/test/snapshots/acquired.json new file mode 100644 index 0000000..bfa1caf --- /dev/null +++ b/test/snapshots/acquired.json @@ -0,0 +1,189 @@ +{ + "study": "lung_msk_pdx", + "cohorts": { + "all patients": { + "count": 172, + "text": "all patients in the study", + "studies": [ + "lung_msk_pdx" + ] + } + }, + "views": [ + { + "kind": "acquired", + "cohorts": [ + "all patients" + ], + "acquired": { + "anchoredTo": "Treatment [SUBTYPE=Systemic therapy]", + "preWindowDays": null, + "minGapDays": 30, + "sampleEvents": [ + "Sample acquisition", + "Sequencing", + "Specimen" + ], + "sampleKey": "SAMPLE_ID", + "measure": "driver", + "scan": { + "top": 12, + "minPost": 3 + }, + "panels": [ + { + "cohort": "all patients", + "anchored": 142, + "dated": 142, + "withPre": 67, + "withPost": 107, + "patients": 33, + "preDays": 27, + "postDays": 279, + "gapDays": { + "q1": 154, + "median": 301, + "q3": 514 + }, + "genes": [ + { + "gene": "CDKN2B", + "pairs": 33, + "shared": 2, + "lost": 1, + "acquired": 8, + "neither": 22, + "p": 0.03906250000000003, + "q": 0.5000000000000001 + }, + { + "gene": "MYC", + "pairs": 33, + "shared": 1, + "lost": 0, + "acquired": 4, + "neither": 28, + "p": 0.125, + "q": 0.5000000000000001 + }, + { + "gene": "CARD11", + "pairs": 33, + "shared": 0, + "lost": 0, + "acquired": 4, + "neither": 29, + "p": 0.125, + "q": 0.5000000000000001 + }, + { + "gene": "DNMT3B", + "pairs": 33, + "shared": 0, + "lost": 0, + "acquired": 4, + "neither": 29, + "p": 0.125, + "q": 0.5000000000000001 + }, + { + "gene": "NKX3-1", + "pairs": 33, + "shared": 0, + "lost": 0, + "acquired": 4, + "neither": 29, + "p": 0.125, + "q": 0.5000000000000001 + }, + { + "gene": "NTRK1", + "pairs": 33, + "shared": 0, + "lost": 0, + "acquired": 4, + "neither": 29, + "p": 0.125, + "q": 0.5000000000000001 + }, + { + "gene": "RAC1", + "pairs": 33, + "shared": 0, + "lost": 0, + "acquired": 4, + "neither": 29, + "p": 0.125, + "q": 0.5000000000000001 + }, + { + "gene": "KEAP1", + "pairs": 33, + "shared": 0, + "lost": 1, + "acquired": 5, + "neither": 27, + "p": 0.21875000000000017, + "q": 0.5000000000000001 + }, + { + "gene": "ETV1", + "pairs": 33, + "shared": 1, + "lost": 0, + "acquired": 3, + "neither": 29, + "p": 0.25000000000000006, + "q": 0.5000000000000001 + }, + { + "gene": "NKX2-1", + "pairs": 33, + "shared": 1, + "lost": 0, + "acquired": 3, + "neither": 29, + "p": 0.25000000000000006, + "q": 0.5000000000000001 + }, + { + "gene": "DDR2", + "pairs": 33, + "shared": 0, + "lost": 0, + "acquired": 3, + "neither": 30, + "p": 0.25000000000000006, + "q": 0.5000000000000001 + }, + { + "gene": "INHBA", + "pairs": 31, + "shared": 0, + "lost": 0, + "acquired": 3, + "neither": 28, + "p": 0.25000000000000006, + "q": 0.5000000000000001 + } + ], + "tested": 30 + } + ] + }, + "participation": { + "studies": [ + { + "study": "lung_msk_pdx", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Patients with a sample dated on or before their first Treatment [SUBTYPE=Systemic therapy] and another at least 30 days after it; each side is one sample — the last before and the first after — dated by its first Sample acquisition/Sequencing/Specimen timeline event (attribute SAMPLE_ID). Funnel: all patients — 142 started, 67 with a sample before, 107 with one after, 33 patients with both (median 27 days before and 279 after; samples a median 301 days apart, IQR 154–514). Per gene, counting only patients whose before- AND after-sample panels both cover it: an OncoKB oncogenic alteration (v7.5) after but not before (acquired), before but not after (lost), on both (shared). The exact McNemar test asks whether acquired and lost differ; q is Benjamini–Hochberg across the genes tested in each cohort — every gene altered after treatment in at least 3 patients (all patients: 30 genes), the 12 smallest p drawn. These are few patients, and a second biopsy is clinically indicated — typically taken at progression — so they are a selected group, and \"acquired\" means seen after and not before, not caused by the treatment: a subclone the first biopsy missed, a different lesion, or a lower-purity sample under-calling one side reads the same. Which treatment ran between the samples beyond the anchor is not considered." + } + ] +} diff --git a/test/snapshots/acquired.sql b/test/snapshots/acquired.sql new file mode 100644 index 0000000..46246cb --- /dev/null +++ b/test/snapshots/acquired.sql @@ -0,0 +1,119 @@ +-- cohort all patients +-- lung_msk_pdx +SELECT DISTINCT replaceOne(patient_unique_id, 'lung_msk_pdx_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'lung_msk_pdx' + +-- view 0 acquired: acquired:funnel:all patients · lung_msk_pdx +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'lung_msk_pdx_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'lung_msk_pdx'), +anch AS ( + SELECT p.stable_id AS pid, min(ce.start_date) AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'lung_msk_pdx' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Treatment')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('SUBTYPE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('Systemic therapy'))) + AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +), +tl AS ( + SELECT p.stable_id AS pid, d.value AS sample, min(ce.start_date) AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + JOIN snapshot_db.clinical_event_data d ON d.clinical_event_id = ce.clinical_event_id AND lowerUTF8(trimBoth(d.key)) = lowerUTF8(trimBoth('SAMPLE_ID')) + WHERE cs.cancer_study_identifier = 'lung_msk_pdx' AND lowerUTF8(trimBoth(ce.event_type)) IN (lowerUTF8(trimBoth('Sample acquisition')), lowerUTF8(trimBoth('Sequencing')), lowerUTF8(trimBoth('Specimen'))) + AND p.stable_id IN (SELECT pid FROM anch) + GROUP BY pid, sample +), +dated AS ( + SELECT tl.pid AS pid, sd.sample_unique_id AS suid, tl.day - anch.day AS rel + FROM tl INNER JOIN anch ON anch.pid = tl.pid + INNER JOIN snapshot_db.sample_derived sd ON sd.cancer_study_identifier = 'lung_msk_pdx' AND sd.sample_stable_id = tl.sample AND sd.patient_stable_id = tl.pid +), +pick AS ( + SELECT pid, countIf(rel <= 0) AS n_pre, countIf(rel >= 30) AS n_post, + argMaxIf(suid, (rel, suid), rel <= 0) AS pre, argMinIf(suid, (rel, suid), rel >= 30) AS post, + maxIf(rel, rel <= 0) AS pre_rel, minIf(rel, rel >= 30) AS post_rel + FROM dated GROUP BY pid +), +ps AS ( + SELECT pre AS suid, pid, 'a' AS side FROM pick WHERE n_pre > 0 AND n_post > 0 + UNION ALL SELECT post AS suid, pid, 'b' AS side FROM pick WHERE n_pre > 0 AND n_post > 0 +) +SELECT (SELECT count() FROM anch) AS anchored, (SELECT uniqExact(pid) FROM tl) AS with_events, count() AS dated, + countIf(n_pre > 0) AS with_pre, countIf(n_post > 0) AS with_post, countIf(n_pre > 0 AND n_post > 0) AS paired, + quantileExactIf(0.5)(-pre_rel, n_pre > 0 AND n_post > 0) AS pre_median, quantileExactIf(0.5)(post_rel, n_pre > 0 AND n_post > 0) AS post_median, + quantileExactIf(0.25)(post_rel - pre_rel, n_pre > 0 AND n_post > 0) AS gap_q1, quantileExactIf(0.5)(post_rel - pre_rel, n_pre > 0 AND n_post > 0) AS gap_median, + quantileExactIf(0.75)(post_rel - pre_rel, n_pre > 0 AND n_post > 0) AS gap_q3 +FROM pick + +-- view 0 acquired: acquired:genes:all patients · lung_msk_pdx +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'lung_msk_pdx_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'lung_msk_pdx'), +anch AS ( + SELECT p.stable_id AS pid, min(ce.start_date) AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'lung_msk_pdx' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Treatment')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('SUBTYPE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('Systemic therapy'))) + AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +), +tl AS ( + SELECT p.stable_id AS pid, d.value AS sample, min(ce.start_date) AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + JOIN snapshot_db.clinical_event_data d ON d.clinical_event_id = ce.clinical_event_id AND lowerUTF8(trimBoth(d.key)) = lowerUTF8(trimBoth('SAMPLE_ID')) + WHERE cs.cancer_study_identifier = 'lung_msk_pdx' AND lowerUTF8(trimBoth(ce.event_type)) IN (lowerUTF8(trimBoth('Sample acquisition')), lowerUTF8(trimBoth('Sequencing')), lowerUTF8(trimBoth('Specimen'))) + AND p.stable_id IN (SELECT pid FROM anch) + GROUP BY pid, sample +), +dated AS ( + SELECT tl.pid AS pid, sd.sample_unique_id AS suid, tl.day - anch.day AS rel + FROM tl INNER JOIN anch ON anch.pid = tl.pid + INNER JOIN snapshot_db.sample_derived sd ON sd.cancer_study_identifier = 'lung_msk_pdx' AND sd.sample_stable_id = tl.sample AND sd.patient_stable_id = tl.pid +), +pick AS ( + SELECT pid, countIf(rel <= 0) AS n_pre, countIf(rel >= 30) AS n_post, + argMaxIf(suid, (rel, suid), rel <= 0) AS pre, argMinIf(suid, (rel, suid), rel >= 30) AS post, + maxIf(rel, rel <= 0) AS pre_rel, minIf(rel, rel >= 30) AS post_rel + FROM dated GROUP BY pid +), +ps AS ( + SELECT pre AS suid, pid, 'a' AS side FROM pick WHERE n_pre > 0 AND n_post > 0 + UNION ALL SELECT post AS suid, pid, 'b' AS side FROM pick WHERE n_pre > 0 AND n_post > 0 +), +alt AS ( + SELECT g.hugo_gene_symbol AS gene, ps.pid AS pid, ps.side AS side, max(ann.is_driver) AS hit + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + INNER JOIN ps ON ps.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'lung_msk_pdx' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM ps) + GROUP BY gene, pid, side + HAVING hit > 0 +), +cov AS ( + SELECT DISTINCT pg.gene AS gene, ps.pid AS pid, ps.side AS side + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = sp.gene_panel_id AND pg.gene IN (SELECT DISTINCT gene FROM alt) + INNER JOIN ps ON ps.suid = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'lung_msk_pdx' + AND sp.alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') +), +per AS ( + SELECT gene, pid, max(side = 'a') AS pa, max(side = 'b') AS pb, + max(side = 'a' AND hit > 0) AS aa, max(side = 'b' AND hit > 0) AS ab + FROM (SELECT gene, pid, side, hit FROM alt UNION ALL SELECT gene, pid, side, 0 AS hit FROM cov) + GROUP BY gene, pid +) +SELECT gene, count() AS pairs, countIf(aa AND ab) AS both, countIf(aa AND NOT ab) AS a_only, countIf(ab AND NOT aa) AS b_only +FROM per WHERE pa AND pb GROUP BY gene HAVING both + a_only + b_only > 0 diff --git a/test/snapshots/acquired.svg b/test/snapshots/acquired.svg new file mode 100644 index 0000000..6677618 --- /dev/null +++ b/test/snapshots/acquired.svg @@ -0,0 +1,2 @@ + +Acquired under therapy — from Treatment [SUBTYPE=Systemic therapy]lost (before only)acquired (after only)all patients33 patients sampled before and after · samples a median 301 days apart · 30 genes tested, the 12 smallest p drawnsharedpairsMcNemar qCDKN2BCDKN2B — 1 patient lost1CDKN2B — 8 patients acquired82330.50MYCMYC — 4 patients acquired41330.50CARD11CARD11 — 4 patients acquired40330.50DNMT3BDNMT3B — 4 patients acquired40330.50NKX3-1NKX3-1 — 4 patients acquired40330.50NTRK1NTRK1 — 4 patients acquired40330.50RAC1RAC1 — 4 patients acquired40330.50KEAP1KEAP1 — 1 patient lost1KEAP1 — 5 patients acquired50330.50ETV1ETV1 — 3 patients acquired31330.50NKX2-1NKX2-1 — 3 patients acquired31330.50DDR2DDR2 — 3 patients acquired30330.50INHBAINHBA — 3 patients acquired30310.501050510patients: ← lost after treatment · acquired after treatment →Patients with a sample dated on or before their first Treatment [SUBTYPE=Systemic therapy] and another at least 30 days after it; each side is one sample — the lastbefore and the first after — dated by its first Sample acquisition/Sequencing/Specimen timeline event (attribute SAMPLE_ID). Funnel: all patients — 142 started, 67 with asample before, 107 with one after, 33 patients with both (median 27 days before and 279 after; samples a median 301 days apart, IQR 154–514). Per gene, countingonly patients whose before- AND after-sample panels both cover it: an OncoKB oncogenic alteration (v7.5) after but not before (acquired), before but not after (lost), onboth (shared). The exact McNemar test asks whether acquired and lost differ; q is Benjamini–Hochberg across the genes tested in each cohort — every gene altered aftertreatment in at least 3 patients (all patients: 30 genes), the 12 smallest p drawn. These are few patients, and a second biopsy is clinically indicated — typically taken atprogression — so they are a selected group, and "acquired" means seen after and not before, not caused by the treatment: a subclone the first biopsy missed, a differentlesion, or a lower-purity sample under-calling one side reads the same. Which treatment ran between the samples beyond the anchor is not considered. diff --git a/test/snapshots/acquired.txt b/test/snapshots/acquired.txt new file mode 100644 index 0000000..82fba94 --- /dev/null +++ b/test/snapshots/acquired.txt @@ -0,0 +1,20 @@ +Analysis over lung_msk_pdx — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- all patients: 172 patients — all patients in the study + +Acquired under therapy: last sample on or before the first Treatment [SUBTYPE=Systemic therapy] against the first at least 30 days after (OncoKB oncogenic; both samples' panels cover the gene): + all patients (142 started, 67 with a sample before, 107 after, 33 with both; samples a median 301 days apart; 30 genes tested, the 12 smallest p listed): + CDKN2B: acquired 8, lost 1, shared 2, neither 22 of 33 pairs, McNemar p=0.0391 q=0.5000 + MYC: acquired 4, lost 0, shared 1, neither 28 of 33 pairs, McNemar p=0.1250 q=0.5000 + CARD11: acquired 4, lost 0, shared 0, neither 29 of 33 pairs, McNemar p=0.1250 q=0.5000 + DNMT3B: acquired 4, lost 0, shared 0, neither 29 of 33 pairs, McNemar p=0.1250 q=0.5000 + NKX3-1: acquired 4, lost 0, shared 0, neither 29 of 33 pairs, McNemar p=0.1250 q=0.5000 + NTRK1: acquired 4, lost 0, shared 0, neither 29 of 33 pairs, McNemar p=0.1250 q=0.5000 + RAC1: acquired 4, lost 0, shared 0, neither 29 of 33 pairs, McNemar p=0.1250 q=0.5000 + KEAP1: acquired 5, lost 1, shared 0, neither 27 of 33 pairs, McNemar p=0.2188 q=0.5000 + ETV1: acquired 3, lost 0, shared 1, neither 29 of 33 pairs, McNemar p=0.2500 q=0.5000 + NKX2-1: acquired 3, lost 0, shared 1, neither 29 of 33 pairs, McNemar p=0.2500 q=0.5000 + DDR2: acquired 3, lost 0, shared 0, neither 30 of 33 pairs, McNemar p=0.2500 q=0.5000 + INHBA: acquired 3, lost 0, shared 0, neither 28 of 31 pairs, McNemar p=0.2500 q=0.5000 + CAVEAT (tell the user): Patients with a sample dated on or before their first Treatment [SUBTYPE=Systemic therapy] and another at least 30 days after it; each side is one sample — the last before and the first after — dated by its first Sample acquisition/Sequencing/Specimen timeline event (attribute SAMPLE_ID). Funnel: all patients — 142 started, 67 with a sample before, 107 with one after, 33 patients with both (median 27 days before and 279 after; samples a median 301 days apart, IQR 154–514). Per gene, counting only patients whose before- AND after-sample panels both cover it: an OncoKB oncogenic alteration (v7.5) after but not before (acquired), before but not after (lost), on both (shared). The exact McNemar test asks whether acquired and lost differ; q is Benjamini–Hochberg across the genes tested in each cohort — every gene altered after treatment in at least 3 patients (all patients: 30 genes), the 12 smallest p drawn. These are few patients, and a second biopsy is clinically indicated — typically taken at progression — so they are a selected group, and "acquired" means seen after and not before, not caused by the treatment: a subclone the first biopsy missed, a different lesion, or a lower-purity sample under-calling one side reads the same. Which treatment ran between the samples beyond the anchor is not considered. diff --git a/test/snapshots/actionability.json b/test/snapshots/actionability.json new file mode 100644 index 0000000..c7ff80f --- /dev/null +++ b/test/snapshots/actionability.json @@ -0,0 +1,304 @@ +{ + "study": "msk_impact_50k_2026", + "cohorts": { + "all patients": { + "count": 48179, + "text": "all patients in the study", + "studies": [ + "msk_impact_50k_2026" + ] + } + }, + "views": [ + { + "kind": "actionability", + "cohorts": [ + "all patients" + ], + "actionability": { + "pin": "v7.5", + "levels": [ + "Level 1", + "Level 2", + "Level 3A", + "Level 3B", + "Level 4", + "No level" + ], + "groupBy": "CANCER_TYPE", + "top": 8, + "pairBy": { + "attribute": "SAMPLE_TYPE", + "a": [ + "Primary" + ], + "b": [ + "Metastasis" + ] + }, + "panels": [ + { + "cohort": "all patients", + "patients": 48179, + "sequenced": 48179, + "levels": [ + 27154, + 1527, + 3244, + 0, + 3061, + 13193 + ], + "resistance": [ + 9698, + 603 + ], + "groups": [ + { + "label": "Non-Small Cell Lung Cancer", + "sequenced": 6409, + "levels": [ + 4474, + 228, + 363, + 0, + 467, + 877 + ], + "resistance": [ + 2177, + 189 + ] + }, + { + "label": "Breast Cancer", + "sequenced": 5724, + "levels": [ + 3648, + 35, + 229, + 0, + 269, + 1543 + ], + "resistance": [ + 59, + 12 + ] + }, + { + "label": "Colorectal Cancer", + "sequenced": 4890, + "levels": [ + 3233, + 191, + 198, + 0, + 98, + 1170 + ], + "resistance": [ + 2312, + 26 + ] + }, + { + "label": "Prostate Cancer", + "sequenced": 2834, + "levels": [ + 1065, + 41, + 63, + 0, + 115, + 1550 + ], + "resistance": [ + 22, + 5 + ] + }, + { + "label": "Pancreatic Cancer", + "sequenced": 2677, + "levels": [ + 2335, + 22, + 20, + 0, + 69, + 231 + ], + "resistance": [ + 2224, + 1 + ] + }, + { + "label": "Endometrial Cancer", + "sequenced": 2325, + "levels": [ + 1888, + 33, + 208, + 0, + 35, + 161 + ], + "resistance": [ + 509, + 6 + ] + }, + { + "label": "Soft Tissue Sarcoma", + "sequenced": 2065, + "levels": [ + 390, + 46, + 413, + 0, + 175, + 1041 + ], + "resistance": [ + 55, + 14 + ] + }, + { + "label": "Ovarian Cancer", + "sequenced": 2035, + "levels": [ + 857, + 42, + 267, + 0, + 130, + 739 + ], + "resistance": [ + 193, + 8 + ] + }, + { + "label": "Other (58)", + "sequenced": 19220, + "levels": [ + 9264, + 889, + 1483, + 0, + 1703, + 5881 + ], + "resistance": [ + 2147, + 342 + ] + } + ], + "pooled": [ + "Glioma", + "Bladder Cancer", + "Melanoma", + "Esophagogastric Cancer", + "Cancer of Unknown Primary", + "Hepatobiliary Cancer", + "Renal Cell Carcinoma", + "Thyroid Cancer", + "Multiple (samples disagree)", + "Germ Cell Tumor", + "Head and Neck Cancer", + "Gastrointestinal Stromal Tumor", + "Bone Cancer", + "Skin Cancer, Non-Melanoma", + "Small Cell Lung Cancer", + "Mesothelioma", + "Salivary Gland Cancer", + "Uterine Sarcoma", + "Cervical Cancer", + "Peripheral Nervous System", + { + "…": "38 more", + "sha256": "f545457fb6c59765" + } + ], + "concordance": { + "pairs": 2278, + "table": [ + [ + 1108, + 13, + 26, + 0, + 26, + 82 + ], + [ + 18, + 32, + 1, + 0, + 3, + 6 + ], + [ + 32, + 6, + 84, + 0, + 5, + 18 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 35, + 3, + 14, + 0, + 85, + 34 + ], + [ + 173, + 11, + 40, + 0, + 35, + 388 + ] + ], + "discordant": 581 + } + } + ], + "storeTypes": [ + "MUTATION", + "CNA" + ] + }, + "participation": { + "studies": [ + { + "study": "msk_impact_50k_2026", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Each patient's highest OncoKB therapeutic level (v7.5, the pinned store) over the alterations in any of their samples sequenced for mutations — called mutations and high-level copy-number calls; the store holds no level for structural variants at this pin, so a fusion is never counted — and, separately, whether any alteration carries a resistance level (R1, R2). The level is the store's highest for the alteration in ANY cancer type, not in the patient's own: an alteration actionable only elsewhere (PTEN or PIK3CA, level 1 with capivasertib in breast cancer, in any other cancer) counts here, so these shares are an upper bound on what is actionable for these patients. Denominator: patients with at least one sample sequenced for mutations — “all patients”: 48,179 of 48,179. Panel-aware in the only way a level can be: a gene their panel did not cover is unknown, not \"no actionable alteration\" — No level means none was found on the genes sequenced. Groups are CANCER_TYPE per patient (Multiple (samples disagree) when their samples disagree); the 8 largest are drawn and the rest pooled into Other (“all patients”: 58 groups). Concordance: patients with a sequenced sample whose SAMPLE_TYPE is Primary and one whose is Metastasis (matched case-insensitively), the highest level over each side's samples compared — “all patients”: 2,278 patients, 25.5% discordant. A level present on one side only can be a real difference between the tumours, or a gene one side's panel did not cover. " + } + ] +} diff --git a/test/snapshots/actionability.sql b/test/snapshots/actionability.sql new file mode 100644 index 0000000..fa8d517 --- /dev/null +++ b/test/snapshots/actionability.sql @@ -0,0 +1,92 @@ +-- cohort all patients +-- msk_impact_50k_2026 +SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' + +-- view 0 actionability: actionability:concordance:all patients · msk_impact_50k_2026 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026'), +seq AS ( + SELECT DISTINCT sp.sample_unique_id AS sid, replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_50k_2026' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +lv AS ( + SELECT g.sample_unique_id AS sid, min(multiIf(ann.highest_sensitive_level = 'LEVEL_1', 1, ann.highest_sensitive_level = 'LEVEL_2', 2, ann.highest_sensitive_level = 'LEVEL_3A', 3, ann.highest_sensitive_level = 'LEVEL_3B', 4, ann.highest_sensitive_level = 'LEVEL_4', 5, 9)) AS s, + min(multiIf(ann.highest_resistance_level = 'LEVEL_R1', 1, ann.highest_resistance_level = 'LEVEL_R2', 2, 9)) AS r + FROM snapshot_db.genomic_event_derived g + INNER JOIN (SELECT hugo_gene_symbol, alteration_type, alteration, highest_sensitive_level, highest_resistance_level + FROM oncokb_annotation.variant_annotation + WHERE oncokb_version = 'v7.5' AND (highest_sensitive_level != '' OR highest_resistance_level != '')) ann + ON ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_impact_50k_2026' + AND ((g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED') OR (g.variant_type = 'cna' AND abs(g.cna_alteration) >= 2) OR g.variant_type = 'structural_variant') + AND g.sample_unique_id IN (SELECT sid FROM seq) + GROUP BY sid +), +st AS ( + SELECT sample_unique_id AS sid, multiIf(lower(trimBoth(attribute_value)) IN ('primary'), 'a', lower(trimBoth(attribute_value)) IN ('metastasis'), 'b', '') AS side + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' AND attribute_name = 'SAMPLE_TYPE' + AND sample_unique_id IN (SELECT sid FROM seq) +), +ps AS ( + SELECT seq.pid AS pid, minIf(if(lv.s = 0, 9, lv.s), st.side = 'a') AS sa, minIf(if(lv.s = 0, 9, lv.s), st.side = 'b') AS sb, + countIf(st.side = 'a') AS na, countIf(st.side = 'b') AS nb + FROM seq INNER JOIN st ON st.sid = seq.sid LEFT JOIN lv ON lv.sid = seq.sid + GROUP BY pid + HAVING na > 0 AND nb > 0 +) +SELECT sa AS a, sb AS b, count() AS n FROM ps GROUP BY a, b + +-- view 0 actionability: actionability:levels:all patients · msk_impact_50k_2026 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026'), +seq AS ( + SELECT DISTINCT sp.sample_unique_id AS sid, replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_50k_2026' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +lv AS ( + SELECT g.sample_unique_id AS sid, min(multiIf(ann.highest_sensitive_level = 'LEVEL_1', 1, ann.highest_sensitive_level = 'LEVEL_2', 2, ann.highest_sensitive_level = 'LEVEL_3A', 3, ann.highest_sensitive_level = 'LEVEL_3B', 4, ann.highest_sensitive_level = 'LEVEL_4', 5, 9)) AS s, + min(multiIf(ann.highest_resistance_level = 'LEVEL_R1', 1, ann.highest_resistance_level = 'LEVEL_R2', 2, 9)) AS r + FROM snapshot_db.genomic_event_derived g + INNER JOIN (SELECT hugo_gene_symbol, alteration_type, alteration, highest_sensitive_level, highest_resistance_level + FROM oncokb_annotation.variant_annotation + WHERE oncokb_version = 'v7.5' AND (highest_sensitive_level != '' OR highest_resistance_level != '')) ann + ON ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_impact_50k_2026' + AND ((g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED') OR (g.variant_type = 'cna' AND abs(g.cna_alteration) >= 2) OR g.variant_type = 'structural_variant') + AND g.sample_unique_id IN (SELECT sid FROM seq) + GROUP BY sid +), +pl AS ( + SELECT seq.pid AS pid, min(if(lv.s = 0, 9, lv.s)) AS s, min(if(lv.r = 0, 9, lv.r)) AS r + FROM seq LEFT JOIN lv ON lv.sid = seq.sid + GROUP BY pid +), +grp AS ( + SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, if(uniqExact(attribute_value) > 1, 'Multiple', any(attribute_value)) AS g + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name = 'CANCER_TYPE' AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT grp.g AS grp, pl.s AS level, pl.r AS resistance, count() AS n +FROM pl LEFT JOIN grp ON grp.pid = pl.pid +GROUP BY grp, level, resistance + +-- view 0 actionability: actionability:store · msk_impact_50k_2026 +SELECT toString(alteration_type) AS type, count() AS n FROM oncokb_annotation.variant_annotation +WHERE oncokb_version = 'v7.5' AND (highest_sensitive_level != '' OR highest_resistance_level != '') +GROUP BY type diff --git a/test/snapshots/actionability.svg b/test/snapshots/actionability.svg new file mode 100644 index 0000000..be9ba37 --- /dev/null +++ b/test/snapshots/actionability.svg @@ -0,0 +1,2 @@ + +Clinical actionability — OncoKB v7.5Level 1Level 2Level 3ALevel 3BLevel 4No levelall patientsn = 48,179all patients: Level 1 27,154 of 48,179 (56.4%)56%all patients: Level 2 1,527 of 48,179 (3.2%)all patients: Level 3A 3,244 of 48,179 (6.7%)7%all patients: Level 4 3,061 of 48,179 (6.4%)6%all patients: No level 13,193 of 48,179 (27.4%)27%1–3B 66.3% · R 21.4%Non-Small Cell Lung Cancern = 6,409Non-Small Cell Lung Cancer: Level 1 4,474 of 6,409 (69.8%)70%Non-Small Cell Lung Cancer: Level 2 228 of 6,409 (3.6%)4%Non-Small Cell Lung Cancer: Level 3A 363 of 6,409 (5.7%)6%Non-Small Cell Lung Cancer: Level 4 467 of 6,409 (7.3%)7%Non-Small Cell Lung Cancer: No level 877 of 6,409 (13.7%)14%1–3B 79.0% · R 36.9%Breast Cancern = 5,724Breast Cancer: Level 1 3,648 of 5,724 (63.7%)64%Breast Cancer: Level 2 35 of 5,724 (0.6%)Breast Cancer: Level 3A 229 of 5,724 (4.0%)4%Breast Cancer: Level 4 269 of 5,724 (4.7%)5%Breast Cancer: No level 1,543 of 5,724 (27.0%)27%1–3B 68.3% · R 1.2%Colorectal Cancern = 4,890Colorectal Cancer: Level 1 3,233 of 4,890 (66.1%)66%Colorectal Cancer: Level 2 191 of 4,890 (3.9%)4%Colorectal Cancer: Level 3A 198 of 4,890 (4.0%)4%Colorectal Cancer: Level 4 98 of 4,890 (2.0%)Colorectal Cancer: No level 1,170 of 4,890 (23.9%)24%1–3B 74.1% · R 47.8%Prostate Cancern = 2,834Prostate Cancer: Level 1 1,065 of 2,834 (37.6%)38%Prostate Cancer: Level 2 41 of 2,834 (1.4%)Prostate Cancer: Level 3A 63 of 2,834 (2.2%)Prostate Cancer: Level 4 115 of 2,834 (4.1%)4%Prostate Cancer: No level 1,550 of 2,834 (54.7%)55%1–3B 41.2% · R 1.0%Pancreatic Cancern = 2,677Pancreatic Cancer: Level 1 2,335 of 2,677 (87.2%)87%Pancreatic Cancer: Level 2 22 of 2,677 (0.8%)Pancreatic Cancer: Level 3A 20 of 2,677 (0.7%)Pancreatic Cancer: Level 4 69 of 2,677 (2.6%)Pancreatic Cancer: No level 231 of 2,677 (8.6%)9%1–3B 88.8% · R 83.1%Endometrial Cancern = 2,325Endometrial Cancer: Level 1 1,888 of 2,325 (81.2%)81%Endometrial Cancer: Level 2 33 of 2,325 (1.4%)Endometrial Cancer: Level 3A 208 of 2,325 (8.9%)9%Endometrial Cancer: Level 4 35 of 2,325 (1.5%)Endometrial Cancer: No level 161 of 2,325 (6.9%)7%1–3B 91.6% · R 22.2%Soft Tissue Sarcoman = 2,065Soft Tissue Sarcoma: Level 1 390 of 2,065 (18.9%)19%Soft Tissue Sarcoma: Level 2 46 of 2,065 (2.2%)Soft Tissue Sarcoma: Level 3A 413 of 2,065 (20.0%)20%Soft Tissue Sarcoma: Level 4 175 of 2,065 (8.5%)8%Soft Tissue Sarcoma: No level 1,041 of 2,065 (50.4%)50%1–3B 41.1% · R 3.3%Ovarian Cancern = 2,035Ovarian Cancer: Level 1 857 of 2,035 (42.1%)42%Ovarian Cancer: Level 2 42 of 2,035 (2.1%)Ovarian Cancer: Level 3A 267 of 2,035 (13.1%)13%Ovarian Cancer: Level 4 130 of 2,035 (6.4%)6%Ovarian Cancer: No level 739 of 2,035 (36.3%)36%1–3B 57.3% · R 9.9%Other (58)n = 19,220Other (58): Level 1 9,264 of 19,220 (48.2%)48%Other (58): Level 2 889 of 19,220 (4.6%)5%Other (58): Level 3A 1,483 of 19,220 (7.7%)8%Other (58): Level 4 1,703 of 19,220 (8.9%)9%Other (58): No level 5,881 of 19,220 (30.6%)31%1–3B 60.5% · R 13.0%020406080100% of patients sequenced, by their highest sensitive level (in any cancer type)all patients: 2,278 patients, 25.5% discordantPrimary ↓ Metastasis →123A3B4none11,1081326·2682218321·363A32684·5183B······435314·8534none1731140·35388Bars: each patient once, at their highest sensitive level; the right column is the share at levels 1–3B and the share with a resistance level (R1/R2) besides. Each patient'shighest OncoKB therapeutic level (v7.5, the pinned store) over the alterations in any of their samples sequenced for mutations — called mutations and high-levelcopy-number calls; the store holds no level for structural variants at this pin, so a fusion is never counted — and, separately, whether any alteration carries a resistancelevel (R1, R2). The level is the store's highest for the alteration in ANY cancer type, not in the patient's own: an alteration actionable only elsewhere (PTEN or PIK3CA,level 1 with capivasertib in breast cancer, in any other cancer) counts here, so these shares are an upper bound on what is actionable for these patients. Denominator:patients with at least one sample sequenced for mutations — “all patients”: 48,179 of 48,179. Panel-aware in the only way a level can be: a gene their panel did not coveris unknown, not "no actionable alteration" — No level means none was found on the genes sequenced. Groups are CANCER_TYPE per patient (Multiple (samples disagree)when their samples disagree); the 8 largest are drawn and the rest pooled into Other (“all patients”: 58 groups). Concordance: patients with a sequenced sample whoseSAMPLE_TYPE is Primary and one whose is Metastasis (matched case-insensitively), the highest level over each side's samples compared — “all patients”: 2,278patients, 25.5% discordant. A level present on one side only can be a real difference between the tumours, or a gene one side's panel did not cover. diff --git a/test/snapshots/actionability.txt b/test/snapshots/actionability.txt new file mode 100644 index 0000000..f76d2ef --- /dev/null +++ b/test/snapshots/actionability.txt @@ -0,0 +1,18 @@ +Analysis over msk_impact_50k_2026 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- all patients: 48,179 patients — all patients in the study + +Clinical actionability (highest OncoKB level per patient, v7.5, any cancer type, by CANCER_TYPE): + all patients (48,179 sequenced): Level 1 27154 (56.4%), Level 2 1527 (3.2%), Level 3A 3244 (6.7%), Level 3B 0 (0.0%), Level 4 3061 (6.4%), No level 13193 (27.4%); resistance R1 9698, R2 603 + Non-Small Cell Lung Cancer (6,409 sequenced): Level 1 4474 (69.8%), Level 2 228 (3.6%), Level 3A 363 (5.7%), Level 3B 0 (0.0%), Level 4 467 (7.3%), No level 877 (13.7%); resistance R1 2177, R2 189 + Breast Cancer (5,724 sequenced): Level 1 3648 (63.7%), Level 2 35 (0.6%), Level 3A 229 (4.0%), Level 3B 0 (0.0%), Level 4 269 (4.7%), No level 1543 (27.0%); resistance R1 59, R2 12 + Colorectal Cancer (4,890 sequenced): Level 1 3233 (66.1%), Level 2 191 (3.9%), Level 3A 198 (4.0%), Level 3B 0 (0.0%), Level 4 98 (2.0%), No level 1170 (23.9%); resistance R1 2312, R2 26 + Prostate Cancer (2,834 sequenced): Level 1 1065 (37.6%), Level 2 41 (1.4%), Level 3A 63 (2.2%), Level 3B 0 (0.0%), Level 4 115 (4.1%), No level 1550 (54.7%); resistance R1 22, R2 5 + Pancreatic Cancer (2,677 sequenced): Level 1 2335 (87.2%), Level 2 22 (0.8%), Level 3A 20 (0.7%), Level 3B 0 (0.0%), Level 4 69 (2.6%), No level 231 (8.6%); resistance R1 2224, R2 1 + Endometrial Cancer (2,325 sequenced): Level 1 1888 (81.2%), Level 2 33 (1.4%), Level 3A 208 (8.9%), Level 3B 0 (0.0%), Level 4 35 (1.5%), No level 161 (6.9%); resistance R1 509, R2 6 + Soft Tissue Sarcoma (2,065 sequenced): Level 1 390 (18.9%), Level 2 46 (2.2%), Level 3A 413 (20.0%), Level 3B 0 (0.0%), Level 4 175 (8.5%), No level 1041 (50.4%); resistance R1 55, R2 14 + Ovarian Cancer (2,035 sequenced): Level 1 857 (42.1%), Level 2 42 (2.1%), Level 3A 267 (13.1%), Level 3B 0 (0.0%), Level 4 130 (6.4%), No level 739 (36.3%); resistance R1 193, R2 8 + Other (58) (19,220 sequenced): Level 1 9264 (48.2%), Level 2 889 (4.6%), Level 3A 1483 (7.7%), Level 3B 0 (0.0%), Level 4 1703 (8.9%), No level 5881 (30.6%); resistance R1 2147, R2 342 + Primary vs Metastasis highest level, 2278 patients with both: 581 discordant (25.5%); rows Primary, columns Metastasis (1, 2, 3A, 3B, 4, none): [1108, 13, 26, 0, 26, 82] [18, 32, 1, 0, 3, 6] [32, 6, 84, 0, 5, 18] [0, 0, 0, 0, 0, 0] [35, 3, 14, 0, 85, 34] [173, 11, 40, 0, 35, 388] + CAVEAT (tell the user): Each patient's highest OncoKB therapeutic level (v7.5, the pinned store) over the alterations in any of their samples sequenced for mutations — called mutations and high-level copy-number calls; the store holds no level for structural variants at this pin, so a fusion is never counted — and, separately, whether any alteration carries a resistance level (R1, R2). The level is the store's highest for the alteration in ANY cancer type, not in the patient's own: an alteration actionable only elsewhere (PTEN or PIK3CA, level 1 with capivasertib in breast cancer, in any other cancer) counts here, so these shares are an upper bound on what is actionable for these patients. Denominator: patients with at least one sample sequenced for mutations — “all patients”: 48,179 of 48,179. Panel-aware in the only way a level can be: a gene their panel did not cover is unknown, not "no actionable alteration" — No level means none was found on the genes sequenced. Groups are CANCER_TYPE per patient (Multiple (samples disagree) when their samples disagree); the 8 largest are drawn and the rest pooled into Other (“all patients”: 58 groups). Concordance: patients with a sequenced sample whose SAMPLE_TYPE is Primary and one whose is Metastasis (matched case-insensitively), the highest level over each side's samples compared — “all patients”: 2,278 patients, 25.5% discordant. A level present on one side only can be a real difference between the tumours, or a gene one side's panel did not cover. diff --git a/test/snapshots/adjusted.json b/test/snapshots/adjusted.json new file mode 100644 index 0000000..bd0df4e --- /dev/null +++ b/test/snapshots/adjusted.json @@ -0,0 +1,443 @@ +{ + "study": "msk_impact_50k_2026", + "cohorts": { + "Early-onset CRC": { + "count": 1699, + "text": "CANCER_TYPE in [Colorectal Cancer] (any sample) AND AGE_AT_DX < 50", + "studies": [ + "msk_impact_50k_2026" + ] + }, + "Average-onset CRC": { + "count": 3149, + "text": "CANCER_TYPE in [Colorectal Cancer] (any sample) AND AGE_AT_DX >= 50", + "studies": [ + "msk_impact_50k_2026" + ] + } + }, + "views": [ + { + "kind": "adjusted", + "cohorts": [ + "Early-onset CRC", + "Average-onset CRC" + ], + "adjusted": { + "groups": [ + "Early-onset CRC", + "Average-onset CRC" + ], + "covariates": [ + "MSI_TYPE", + "TMB_SCORE" + ], + "measure": "any", + "minShare": 0.05, + "minAltered": 10, + "patients": [ + { + "cohort": "Early-onset CRC", + "withPanel": 1699, + "used": 1693 + }, + { + "cohort": "Average-onset CRC", + "withPanel": 3149, + "used": 3146 + } + ], + "rows": [ + { + "gene": "BRAF", + "altered": [ + 126, + 407 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.5411269597612929, + "fisherP": 2.710811860666322e-9, + "or": 0.5216396908714602, + "ciLow": 0.4183153256517837, + "ciHigh": 0.6504852928075174, + "p": 7.54781075999513e-9, + "q": 4.226774025597273e-7 + }, + { + "gene": "TCF7L2", + "altered": [ + 288, + 423 + ], + "profiled": [ + 1630, + 3032 + ], + "rawOr": 1.3236515838538858, + "fisherP": 0.0008578212348662324, + "or": 1.404435592139356, + "ciLow": 1.180502737160721, + "ciHigh": 1.6708468946135815, + "p": 0.0001268866627804068, + "q": 0.0035528265578513903 + }, + { + "gene": "TP53", + "altered": [ + 1309, + 2272 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 1.311328583480047, + "fisherP": 0.00011775748048826727, + "or": 1.3133017670395535, + "ciLow": 1.1350484541995076, + "ciHigh": 1.5195488130289565, + "p": 0.00025022227493464745, + "q": 0.0046708157987800855 + }, + { + "gene": "RNF43", + "altered": [ + 137, + 339 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.729043914128201, + "fisherP": 0.0027999834127475907, + "or": 0.6620228379095622, + "ciLow": 0.5093142741934166, + "ciHigh": 0.860518269604578, + "p": 0.0020511556715794737, + "q": 0.028716179402112633 + }, + { + "gene": "ARID1B", + "altered": [ + 85, + 201 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.7745012499690602, + "fisherP": 0.05532506054373684, + "or": 0.7489932088466228, + "ciLow": 0.557772986856673, + "ciHigh": 1.0057690854837233, + "p": 0.05464152386125222, + "q": 0.46652783687314187 + }, + { + "gene": "SOX9", + "altered": [ + 179, + 396 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.8210406575664172, + "fisherP": 0.040411894237794996, + "or": 0.8350046734269876, + "ciLow": 0.6893699851625684, + "ciHigh": 1.011405804795066, + "p": 0.06518532585899603, + "q": 0.46652783687314187 + }, + { + "gene": "APC", + "altered": [ + 1312, + 2354 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 1.1585841489439987, + "fisherP": 0.041342749543831714, + "or": 1.1387886342288704, + "ciLow": 0.9885897522112814, + "ciHigh": 1.311807603253099, + "p": 0.07171204692126021, + "q": 0.46652783687314187 + }, + { + "gene": "PTCH1", + "altered": [ + 104, + 163 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 1.1977745775210709, + "fisherP": 0.16602988006891037, + "or": 1.3250194585995907, + "ciLow": 0.9734183627616344, + "ciHigh": 1.803619731074945, + "p": 0.07365875367172238, + "q": 0.46652783687314187 + }, + { + "gene": "PTPRT", + "altered": [ + 132, + 284 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.8521623011612275, + "fisherP": 0.14706368012518412, + "or": 0.8098204828214236, + "ciLow": 0.6420211662668516, + "ciHigh": 1.021476002435317, + "p": 0.0749776880688978, + "q": 0.46652783687314187 + }, + { + "gene": "AMER1", + "altered": [ + 119, + 260 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.8391994917407878, + "fisherP": 0.13021342686620713, + "or": 0.8180709187695194, + "ciLow": 0.6445218640410384, + "ciHigh": 1.0383511645988062, + "p": 0.09881729624236307, + "q": 0.5077248205997287 + }, + { + "gene": "KMT2C", + "altered": [ + 149, + 236 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 1.189926012119083, + "fisherP": 0.1188444777909342, + "or": 1.2414079415234593, + "ciLow": 0.9596149333281411, + "ciHigh": 1.6059500782597078, + "p": 0.09973166118923242, + "q": 0.5077248205997287 + }, + { + "gene": "ALK", + "altered": [ + 98, + 150 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 1.227201671891327, + "fisherP": 0.13266423396996824, + "or": 1.2266799467712537, + "ciLow": 0.9251847438283239, + "ciHigh": 1.626425102498171, + "p": 0.1557120246584706, + "q": 0.7266561150728627, + "merged": true + }, + { + "gene": "KRAS", + "altered": [ + 728, + 1403 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.9372248217180801, + "fisherP": 0.28816415182439137, + "or": 0.923909986483505, + "ciLow": 0.8195332385181764, + "ciHigh": 1.041580283756872, + "p": 0.19569867163761537, + "q": 0.8065613922774592 + }, + { + "gene": "PTPRS", + "altered": [ + 178, + 299 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 1.1187257856220405, + "fisherP": 0.266212584088072, + "or": 1.1616780454795617, + "ciLow": 0.9229590631628046, + "ciHigh": 1.4621405598691992, + "p": 0.2016403480693648, + "q": 0.8065613922774592 + }, + { + "gene": "GRIN2A", + "altered": [ + 99, + 203 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.9004116421803438, + "fisherP": 0.4186807379137655, + "or": 0.841449059243645, + "ciLow": 0.6393279724898628, + "ciHigh": 1.1074699524636267, + "p": 0.21807411669325583, + "q": 0.8141433689881551 + }, + { + "gene": "ERBB4", + "altered": [ + 96, + 196 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.9047576450743102, + "fisherP": 0.4482855526770097, + "or": 0.8468060994525293, + "ciLow": 0.6421917952897175, + "ciHigh": 1.1166143437670426, + "p": 0.23866269386673408, + "q": 0.8353194285335693, + "merged": true + }, + { + "gene": "MGA", + "altered": [ + 88, + 181 + ], + "profiled": [ + 1630, + 3032 + ], + "rawOr": 0.8989115090540376, + "fisherP": 0.46881605021726136, + "or": 0.8470971509026441, + "ciLow": 0.6296245928768924, + "ciHigh": 1.1396848077179236, + "p": 0.2729870652389694, + "q": 0.8585545311158915, + "merged": true + }, + { + "gene": "PIK3CA", + "altered": [ + 344, + 675 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.9335024572385581, + "fisherP": 0.37501003639382813, + "or": 0.92090874427795, + "ciLow": 0.7906873515311479, + "ciHigh": 1.0725768075653632, + "p": 0.289490567367158, + "q": 0.8585545311158915 + }, + { + "gene": "FBXW7", + "altered": [ + 254, + 503 + ], + "profiled": [ + 1693, + 3146 + ], + "rawOr": 0.9274747622672581, + "fisherP": 0.38371371706610935, + "or": 0.9123746251684417, + "ciLow": 0.7680175085950384, + "ciHigh": 1.0838652079352247, + "p": 0.29669741607594663, + "q": 0.8585545311158915 + }, + { + "gene": "KMT2B", + "altered": [ + 144, + 268 + ], + "profiled": [ + 1327, + 2498 + ], + "rawOr": 1.0128562596989692, + "fisherP": 0.9128102190534733, + "or": 1.1675598503391111, + "ciLow": 0.8675492916581633, + "ciHigh": 1.5713182146900095, + "p": 0.30662661825567555, + "q": 0.8585545311158915 + }, + { + "…": "36 more", + "sha256": "02768078561eec72" + } + ], + "failed": [], + "significant": 4, + "labels": [ + "BRAF", + "TCF7L2", + "TP53", + "RNF43" + ] + }, + "participation": { + "studies": [ + { + "study": "msk_impact_50k_2026", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": true, + "aggregateOnly": [] + }, + "caveat": "Per gene, a logistic regression of mutated (yes/no) on cohort (“Early-onset CRC” = 1 against “Average-onset CRC”) and MSI_TYPE, TMB_SCORE (MSI_TYPE as categories against its commonest value, TMB_SCORE as a number; a sample attribute is the patient's first sample's); the odds ratio is the cohort term's, Wald 95% CI, and q is Benjamini–Hochberg over the 56 genes fit. Patients, not samples: a patient is mutated in a gene when any of their samples carries a called mutation in it, and is in a gene's model only when one of their mutation panels covers it (or whole-exome) — panel-aware, so a patient not profiled for a gene is left out of that gene, never counted as wild type. Tested: the 56 genes mutated in at least 5.0% (and 10) of the patients profiled for them, both cohorts together (at most 200). “Early-onset CRC”: 1,699 patients with a mutation panel, 1,693 with every covariate; “Average-onset CRC”: 3,149 patients with a mutation panel, 3,146 with every covariate. Beside each adjusted odds ratio is the raw one with its two-sided Fisher p, on the same patients; 2 genes significant raw (Fisher p < 0.05) are not after adjustment (adjusted p ≥ 0.05). For 9 genes, a covariate category in which no patient (or every patient) was mutated was merged into its reference level, since it would separate. Adjustment removes only what the covariates measure: a hypermutated tumour raises every gene's odds at once, which TMB or MSI status as a covariate absorbs and nothing else does. Cohorts are expected not to share patients; a patient in both would count on both sides. " + } + ] +} diff --git a/test/snapshots/adjusted.sql b/test/snapshots/adjusted.sql new file mode 100644 index 0000000..ffa8445 --- /dev/null +++ b/test/snapshots/adjusted.sql @@ -0,0 +1,117 @@ +-- cohort Early-onset CRC +-- msk_impact_50k_2026 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'patient' + AND attribute_name = 'AGE_AT_DX' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) < 50) + +-- cohort Average-onset CRC +-- msk_impact_50k_2026 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'patient' + AND attribute_name = 'AGE_AT_DX' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) >= 50) + +-- view 0 adjusted: adjusted:panels · msk_impact_50k_2026 +SELECT toString(pg.gene_panel_id) AS panel, groupUniqArray(pg.gene) AS genes +FROM snapshot_db.gene_panel_to_gene_derived pg +WHERE pg.gene_panel_id IN (SELECT DISTINCT gene_panel_id FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND alteration_type = 'MUTATION_EXTENDED') +GROUP BY panel + +-- view 0 adjusted: adjusted:panels · msk_impact_50k_2026 +SELECT toString(pg.gene_panel_id) AS panel, groupUniqArray(pg.gene) AS genes +FROM snapshot_db.gene_panel_to_gene_derived pg +WHERE pg.gene_panel_id IN (SELECT DISTINCT gene_panel_id FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND alteration_type = 'MUTATION_EXTENDED') +GROUP BY panel + +-- view 0 adjusted: adjusted:rows:Average-onset CRC · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'patient' + AND attribute_name = 'AGE_AT_DX' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) >= 50)), +panels AS ( + SELECT replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, groupUniqArray(toString(sp.gene_panel_id)) AS panels + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_50k_2026' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, groupUniqArray(g.hugo_gene_symbol) AS genes + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_impact_50k_2026' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' + AND replaceOne(g.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +attrs AS ( + SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, + argMinIf(attribute_value, sample_unique_id, attribute_name = 'MSI_TYPE') AS a0, + argMinIf(attribute_value, sample_unique_id, attribute_name = 'TMB_SCORE') AS a1 + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name IN ('MSI_TYPE','TMB_SCORE') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT panels.panels AS panels, mut.genes AS genes, attrs.a0 AS c0, attrs.a1 AS c1 +FROM panels + LEFT JOIN mut ON mut.pid = panels.pid + LEFT JOIN attrs ON attrs.pid = panels.pid + + +-- view 0 adjusted: adjusted:rows:Early-onset CRC · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'patient' + AND attribute_name = 'AGE_AT_DX' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) < 50)), +panels AS ( + SELECT replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, groupUniqArray(toString(sp.gene_panel_id)) AS panels + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_50k_2026' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, groupUniqArray(g.hugo_gene_symbol) AS genes + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_impact_50k_2026' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' + AND replaceOne(g.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +attrs AS ( + SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, + argMinIf(attribute_value, sample_unique_id, attribute_name = 'MSI_TYPE') AS a0, + argMinIf(attribute_value, sample_unique_id, attribute_name = 'TMB_SCORE') AS a1 + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name IN ('MSI_TYPE','TMB_SCORE') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT panels.panels AS panels, mut.genes AS genes, attrs.a0 AS c0, attrs.a1 AS c1 +FROM panels + LEFT JOIN mut ON mut.pid = panels.pid + LEFT JOIN attrs ON attrs.pid = panels.pid + diff --git a/test/snapshots/adjusted.svg b/test/snapshots/adjusted.svg new file mode 100644 index 0000000..3900f2f --- /dev/null +++ b/test/snapshots/adjusted.svg @@ -0,0 +1,2 @@ + +Adjusted enrichment — Early-onset CRC vs Average-onset CRC, adjusted for MSI_TYPE, TMB_SCORE56 genes fit · 4 at q < 0.05 after adjusting for MSI_TYPE, TMB_SCORE0246810−log₁₀ q−1.0−0.50.00.51.0Adjusted log2 odds ratioBRAF: Early-onset CRC 7.4%, Average-onset CRC 12.9% · adjusted OR 0.52, q 4.2e-7TCF7L2: Early-onset CRC 17.7%, Average-onset CRC 14.0% · adjusted OR 1.40, q 0.0036TP53: Early-onset CRC 77.3%, Average-onset CRC 72.2% · adjusted OR 1.31, q 0.0047RNF43: Early-onset CRC 8.1%, Average-onset CRC 10.8% · adjusted OR 0.66, q 0.029ARID1B: Early-onset CRC 5.0%, Average-onset CRC 6.4% · adjusted OR 0.75, q 0.47SOX9: Early-onset CRC 10.6%, Average-onset CRC 12.6% · adjusted OR 0.84, q 0.47APC: Early-onset CRC 77.5%, Average-onset CRC 74.8% · adjusted OR 1.14, q 0.47PTCH1: Early-onset CRC 6.1%, Average-onset CRC 5.2% · adjusted OR 1.33, q 0.47PTPRT: Early-onset CRC 7.8%, Average-onset CRC 9.0% · adjusted OR 0.81, q 0.47AMER1: Early-onset CRC 7.0%, Average-onset CRC 8.3% · adjusted OR 0.82, q 0.51KMT2C: Early-onset CRC 8.8%, Average-onset CRC 7.5% · adjusted OR 1.24, q 0.51ALK: Early-onset CRC 5.8%, Average-onset CRC 4.8% · adjusted OR 1.23, q 0.73KRAS: Early-onset CRC 43.0%, Average-onset CRC 44.6% · adjusted OR 0.92, q 0.81PTPRS: Early-onset CRC 10.5%, Average-onset CRC 9.5% · adjusted OR 1.16, q 0.81GRIN2A: Early-onset CRC 5.8%, Average-onset CRC 6.5% · adjusted OR 0.84, q 0.81ERBB4: Early-onset CRC 5.7%, Average-onset CRC 6.2% · adjusted OR 0.85, q 0.84MGA: Early-onset CRC 5.4%, Average-onset CRC 6.0% · adjusted OR 0.85, q 0.86PIK3CA: Early-onset CRC 20.3%, Average-onset CRC 21.5% · adjusted OR 0.92, q 0.86FBXW7: Early-onset CRC 15.0%, Average-onset CRC 16.0% · adjusted OR 0.91, q 0.86KMT2B: Early-onset CRC 10.9%, Average-onset CRC 10.7% · adjusted OR 1.17, q 0.86AXIN2: Early-onset CRC 5.0%, Average-onset CRC 5.8% · adjusted OR 0.87, q 0.86INPPL1: Early-onset CRC 5.3%, Average-onset CRC 5.0% · adjusted OR 1.19, q 0.86PREX2: Early-onset CRC 6.3%, Average-onset CRC 6.8% · adjusted OR 0.87, q 0.86MTOR: Early-onset CRC 5.3%, Average-onset CRC 5.8% · adjusted OR 0.88, q 0.86EPHA5: Early-onset CRC 6.7%, Average-onset CRC 6.0% · adjusted OR 1.11, q 0.94ATM: Early-onset CRC 8.4%, Average-onset CRC 8.7% · adjusted OR 0.92, q 0.94POLE: Early-onset CRC 6.2%, Average-onset CRC 5.6% · adjusted OR 1.12, q 0.94NOTCH4: Early-onset CRC 5.0%, Average-onset CRC 5.4% · adjusted OR 0.90, q 0.94NF1: Early-onset CRC 6.1%, Average-onset CRC 5.5% · adjusted OR 1.10, q 0.94CARD11: Early-onset CRC 6.8%, Average-onset CRC 6.4% · adjusted OR 1.09, q 0.94SMAD4: Early-onset CRC 14.9%, Average-onset CRC 15.3% · adjusted OR 0.95, q 0.94KMT2A: Early-onset CRC 5.8%, Average-onset CRC 6.2% · adjusted OR 0.92, q 0.94KMT2D: Early-onset CRC 11.1%, Average-onset CRC 11.6% · adjusted OR 0.93, q 0.94CREBBP: Early-onset CRC 7.0%, Average-onset CRC 6.7% · adjusted OR 1.07, q 0.94ARID1A: Early-onset CRC 12.2%, Average-onset CRC 12.1% · adjusted OR 1.05, q 0.94TGFBR2: Early-onset CRC 5.1%, Average-onset CRC 5.7% · adjusted OR 0.93, q 0.94CTNNB1: Early-onset CRC 6.6%, Average-onset CRC 6.9% · adjusted OR 0.95, q 0.94ANKRD11: Early-onset CRC 6.0%, Average-onset CRC 6.3% · adjusted OR 0.94, q 0.94POLD1: Early-onset CRC 5.0%, Average-onset CRC 5.5% · adjusted OR 0.93, q 0.94EP300: Early-onset CRC 5.7%, Average-onset CRC 5.5% · adjusted OR 1.06, q 0.94PTEN: Early-onset CRC 6.7%, Average-onset CRC 6.4% · adjusted OR 1.05, q 0.94FLT4: Early-onset CRC 5.8%, Average-onset CRC 6.1% · adjusted OR 0.95, q 0.94NCOR1: Early-onset CRC 5.1%, Average-onset CRC 5.1% · adjusted OR 0.95, q 0.95PIK3R1: Early-onset CRC 5.6%, Average-onset CRC 5.5% · adjusted OR 0.96, q 0.95MSH3: Early-onset CRC 6.6%, Average-onset CRC 7.3% · adjusted OR 0.95, q 0.95ASXL1: Early-onset CRC 5.3%, Average-onset CRC 5.1% · adjusted OR 1.04, q 0.95ERBB3: Early-onset CRC 5.2%, Average-onset CRC 5.0% · adjusted OR 1.04, q 0.95NOTCH3: Early-onset CRC 8.0%, Average-onset CRC 8.2% · adjusted OR 0.98, q 0.97ZFHX3: Early-onset CRC 9.5%, Average-onset CRC 9.4% · adjusted OR 1.02, q 0.97SMARCA4: Early-onset CRC 5.8%, Average-onset CRC 6.1% · adjusted OR 0.98, q 0.97ERBB2: Early-onset CRC 5.1%, Average-onset CRC 5.1% · adjusted OR 1.02, q 0.97NOTCH1: Early-onset CRC 6.0%, Average-onset CRC 6.4% · adjusted OR 0.99, q 0.97FAT1: Early-onset CRC 7.6%, Average-onset CRC 7.4% · adjusted OR 0.99, q 0.97CIC: Early-onset CRC 5.1%, Average-onset CRC 5.2% · adjusted OR 1.01, q 0.97BRCA2: Early-onset CRC 6.7%, Average-onset CRC 6.5% · adjusted OR 1.00, q 0.99SPEN: Early-onset CRC 5.8%, Average-onset CRC 5.8% · adjusted OR 1.00, q 0.99BRAFTCF7L2TP53RNF43← Average-onset CRCEarly-onset CRC →GeneEarly-onset CRCAverage-onset CRCRaw OR (Fisher p)Adjusted OR [95% CI]qBRAF126/1,693 (7.4%)407/3,146 (12.9%)0.54 (2.7e-9)0.52 [0.42–0.65]4.2e-7TCF7L2288/1,630 (17.7%)423/3,032 (14.0%)1.32 (8.6e-4)1.40 [1.18–1.67]0.0036TP531,309/1,693 (77.3%)2,272/3,146 (72.2%)1.31 (1.2e-4)1.31 [1.14–1.52]0.0047RNF43137/1,693 (8.1%)339/3,146 (10.8%)0.73 (0.0028)0.66 [0.51–0.86]0.029ARID1B85/1,693 (5.0%)201/3,146 (6.4%)0.77 (0.055)0.75 [0.56–1.01]0.47SOX9179/1,693 (10.6%)396/3,146 (12.6%)0.82 (0.04)0.84 [0.69–1.01]0.47APC1,312/1,693 (77.5%)2,354/3,146 (74.8%)1.16 (0.041)1.14 [0.99–1.31]0.47PTCH1104/1,693 (6.1%)163/3,146 (5.2%)1.20 (0.17)1.33 [0.97–1.80]0.47PTPRT132/1,693 (7.8%)284/3,146 (9.0%)0.85 (0.15)0.81 [0.64–1.02]0.47AMER1119/1,693 (7.0%)260/3,146 (8.3%)0.84 (0.13)0.82 [0.64–1.04]0.51KMT2C149/1,693 (8.8%)236/3,146 (7.5%)1.19 (0.12)1.24 [0.96–1.61]0.51ALK98/1,693 (5.8%)150/3,146 (4.8%)1.23 (0.13)1.23 [0.93–1.63]0.73KRAS728/1,693 (43.0%)1,403/3,146 (44.6%)0.94 (0.29)0.92 [0.82–1.04]0.81PTPRS178/1,693 (10.5%)299/3,146 (9.5%)1.12 (0.27)1.16 [0.92–1.46]0.81GRIN2A99/1,693 (5.8%)203/3,146 (6.5%)0.90 (0.42)0.84 [0.64–1.11]0.81+41 more genes fit (the full table is in the result and the MCP text)Per gene, a logistic regression of mutated (yes/no) on cohort (“Early-onset CRC” = 1 against “Average-onset CRC”) and MSI_TYPE, TMB_SCORE (MSI_TYPE as categoriesagainst its commonest value, TMB_SCORE as a number; a sample attribute is the patient's first sample's); the odds ratio is the cohort term's, Wald 95% CI, and q isBenjamini–Hochberg over the 56 genes fit. Patients, not samples: a patient is mutated in a gene when any of their samples carries a called mutation in it, and is in agene's model only when one of their mutation panels covers it (or whole-exome) — panel-aware, so a patient not profiled for a gene is left out of that gene, never countedas wild type. Tested: the 56 genes mutated in at least 5.0% (and 10) of the patients profiled for them, both cohorts together (at most 200). “Early-onset CRC”: 1,699patients with a mutation panel, 1,693 with every covariate; “Average-onset CRC”: 3,149 patients with a mutation panel, 3,146 with every covariate. Beside each adjustedodds ratio is the raw one with its two-sided Fisher p, on the same patients; 2 genes significant raw (Fisher p < 0.05) are not after adjustment (adjusted p ≥ 0.05). For 9genes, a covariate category in which no patient (or every patient) was mutated was merged into its reference level, since it would separate. Adjustment removes onlywhat the covariates measure: a hypermutated tumour raises every gene's odds at once, which TMB or MSI status as a covariate absorbs and nothing else does. Cohortsare expected not to share patients; a patient in both would count on both sides. diff --git a/test/snapshots/adjusted.txt b/test/snapshots/adjusted.txt new file mode 100644 index 0000000..123152a --- /dev/null +++ b/test/snapshots/adjusted.txt @@ -0,0 +1,39 @@ +Analysis over msk_impact_50k_2026 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- Early-onset CRC: 1,699 patients — CANCER_TYPE in [Colorectal Cancer] (any sample) AND AGE_AT_DX < 50 +- Average-onset CRC: 3,149 patients — CANCER_TYPE in [Colorectal Cancer] (any sample) AND AGE_AT_DX >= 50 + +Adjusted enrichment: Early-onset CRC vs Average-onset CRC, mutation per gene by logistic regression adjusted for MSI_TYPE, TMB_SCORE; 56 genes fit, 4 at q<0.05: + BRAF: Early-onset CRC 126/1693 (7.4%), Average-onset CRC 407/3146 (12.9%); raw OR 0.54 (Fisher p=2.7e-9); adjusted OR 0.52 (95% CI 0.42-0.65) p=7.5e-9 q=4.2e-7 + TCF7L2: Early-onset CRC 288/1630 (17.7%), Average-onset CRC 423/3032 (14.0%); raw OR 1.32 (Fisher p=0.0009); adjusted OR 1.40 (95% CI 1.18-1.67) p=0.0001 q=0.0036 + TP53: Early-onset CRC 1309/1693 (77.3%), Average-onset CRC 2272/3146 (72.2%); raw OR 1.31 (Fisher p=0.0001); adjusted OR 1.31 (95% CI 1.14-1.52) p=0.0003 q=0.0047 + RNF43: Early-onset CRC 137/1693 (8.1%), Average-onset CRC 339/3146 (10.8%); raw OR 0.73 (Fisher p=0.0028); adjusted OR 0.66 (95% CI 0.51-0.86) p=0.0021 q=0.0287 + ARID1B: Early-onset CRC 85/1693 (5.0%), Average-onset CRC 201/3146 (6.4%); raw OR 0.77 (Fisher p=0.0553); adjusted OR 0.75 (95% CI 0.56-1.01) p=0.0546 q=0.4665 + SOX9: Early-onset CRC 179/1693 (10.6%), Average-onset CRC 396/3146 (12.6%); raw OR 0.82 (Fisher p=0.0404); adjusted OR 0.84 (95% CI 0.69-1.01) p=0.0652 q=0.4665 + APC: Early-onset CRC 1312/1693 (77.5%), Average-onset CRC 2354/3146 (74.8%); raw OR 1.16 (Fisher p=0.0413); adjusted OR 1.14 (95% CI 0.99-1.31) p=0.0717 q=0.4665 + PTCH1: Early-onset CRC 104/1693 (6.1%), Average-onset CRC 163/3146 (5.2%); raw OR 1.20 (Fisher p=0.1660); adjusted OR 1.33 (95% CI 0.97-1.80) p=0.0737 q=0.4665 + PTPRT: Early-onset CRC 132/1693 (7.8%), Average-onset CRC 284/3146 (9.0%); raw OR 0.85 (Fisher p=0.1471); adjusted OR 0.81 (95% CI 0.64-1.02) p=0.0750 q=0.4665 + AMER1: Early-onset CRC 119/1693 (7.0%), Average-onset CRC 260/3146 (8.3%); raw OR 0.84 (Fisher p=0.1302); adjusted OR 0.82 (95% CI 0.64-1.04) p=0.0988 q=0.5077 + KMT2C: Early-onset CRC 149/1693 (8.8%), Average-onset CRC 236/3146 (7.5%); raw OR 1.19 (Fisher p=0.1188); adjusted OR 1.24 (95% CI 0.96-1.61) p=0.0997 q=0.5077 + ALK: Early-onset CRC 98/1693 (5.8%), Average-onset CRC 150/3146 (4.8%); raw OR 1.23 (Fisher p=0.1327); adjusted OR 1.23 (95% CI 0.93-1.63) p=0.1557 q=0.7267 + KRAS: Early-onset CRC 728/1693 (43.0%), Average-onset CRC 1403/3146 (44.6%); raw OR 0.94 (Fisher p=0.2882); adjusted OR 0.92 (95% CI 0.82-1.04) p=0.1957 q=0.8066 + PTPRS: Early-onset CRC 178/1693 (10.5%), Average-onset CRC 299/3146 (9.5%); raw OR 1.12 (Fisher p=0.2662); adjusted OR 1.16 (95% CI 0.92-1.46) p=0.2016 q=0.8066 + GRIN2A: Early-onset CRC 99/1693 (5.8%), Average-onset CRC 203/3146 (6.5%); raw OR 0.90 (Fisher p=0.4187); adjusted OR 0.84 (95% CI 0.64-1.11) p=0.2181 q=0.8141 + ERBB4: Early-onset CRC 96/1693 (5.7%), Average-onset CRC 196/3146 (6.2%); raw OR 0.90 (Fisher p=0.4483); adjusted OR 0.85 (95% CI 0.64-1.12) p=0.2387 q=0.8353 + MGA: Early-onset CRC 88/1630 (5.4%), Average-onset CRC 181/3032 (6.0%); raw OR 0.90 (Fisher p=0.4688); adjusted OR 0.85 (95% CI 0.63-1.14) p=0.2730 q=0.8586 + PIK3CA: Early-onset CRC 344/1693 (20.3%), Average-onset CRC 675/3146 (21.5%); raw OR 0.93 (Fisher p=0.3750); adjusted OR 0.92 (95% CI 0.79-1.07) p=0.2895 q=0.8586 + FBXW7: Early-onset CRC 254/1693 (15.0%), Average-onset CRC 503/3146 (16.0%); raw OR 0.93 (Fisher p=0.3837); adjusted OR 0.91 (95% CI 0.77-1.08) p=0.2967 q=0.8586 + KMT2B: Early-onset CRC 144/1327 (10.9%), Average-onset CRC 268/2498 (10.7%); raw OR 1.01 (Fisher p=0.9128); adjusted OR 1.17 (95% CI 0.87-1.57) p=0.3066 q=0.8586 + AXIN2: Early-onset CRC 84/1693 (5.0%), Average-onset CRC 183/3146 (5.8%); raw OR 0.85 (Fisher p=0.2348); adjusted OR 0.87 (95% CI 0.65-1.16) p=0.3290 q=0.8589 + INPPL1: Early-onset CRC 70/1327 (5.3%), Average-onset CRC 125/2498 (5.0%); raw OR 1.06 (Fisher p=0.7575); adjusted OR 1.19 (95% CI 0.83-1.69) p=0.3451 q=0.8589 + PREX2: Early-onset CRC 84/1327 (6.3%), Average-onset CRC 171/2498 (6.8%); raw OR 0.92 (Fisher p=0.5861); adjusted OR 0.87 (95% CI 0.65-1.17) p=0.3629 q=0.8589 + MTOR: Early-onset CRC 89/1693 (5.3%), Average-onset CRC 181/3146 (5.8%); raw OR 0.91 (Fisher p=0.5115); adjusted OR 0.88 (95% CI 0.66-1.17) p=0.3681 q=0.8589 + EPHA5: Early-onset CRC 113/1693 (6.7%), Average-onset CRC 188/3146 (6.0%); raw OR 1.13 (Fisher p=0.3494); adjusted OR 1.11 (95% CI 0.85-1.45) p=0.4340 q=0.9392 + ATM: Early-onset CRC 142/1693 (8.4%), Average-onset CRC 273/3146 (8.7%); raw OR 0.96 (Fisher p=0.7470); adjusted OR 0.92 (95% CI 0.73-1.16) p=0.4763 q=0.9392 + POLE: Early-onset CRC 105/1693 (6.2%), Average-onset CRC 175/3146 (5.6%); raw OR 1.12 (Fisher p=0.3667); adjusted OR 1.12 (95% CI 0.82-1.51) p=0.4765 q=0.9392 + NOTCH4: Early-onset CRC 85/1693 (5.0%), Average-onset CRC 170/3146 (5.4%); raw OR 0.93 (Fisher p=0.5900); adjusted OR 0.90 (95% CI 0.67-1.22) p=0.5053 q=0.9392 + NF1: Early-onset CRC 104/1693 (6.1%), Average-onset CRC 173/3146 (5.5%); raw OR 1.12 (Fisher p=0.3641); adjusted OR 1.10 (95% CI 0.83-1.46) p=0.5122 q=0.9392 + CARD11: Early-onset CRC 115/1693 (6.8%), Average-onset CRC 201/3146 (6.4%); raw OR 1.07 (Fisher p=0.5836); adjusted OR 1.09 (95% CI 0.84-1.41) p=0.5197 q=0.9392 + … 26 more genes fit + CAVEAT (tell the user): Per gene, a logistic regression of mutated (yes/no) on cohort (“Early-onset CRC” = 1 against “Average-onset CRC”) and MSI_TYPE, TMB_SCORE (MSI_TYPE as categories against its commonest value, TMB_SCORE as a number; a sample attribute is the patient's first sample's); the odds ratio is the cohort term's, Wald 95% CI, and q is Benjamini–Hochberg over the 56 genes fit. Patients, not samples: a patient is mutated in a gene when any of their samples carries a called mutation in it, and is in a gene's model only when one of their mutation panels covers it (or whole-exome) — panel-aware, so a patient not profiled for a gene is left out of that gene, never counted as wild type. Tested: the 56 genes mutated in at least 5.0% (and 10) of the patients profiled for them, both cohorts together (at most 200). “Early-onset CRC”: 1,699 patients with a mutation panel, 1,693 with every covariate; “Average-onset CRC”: 3,149 patients with a mutation panel, 3,146 with every covariate. Beside each adjusted odds ratio is the raw one with its two-sided Fisher p, on the same patients; 2 genes significant raw (Fisher p < 0.05) are not after adjustment (adjusted p ≥ 0.05). For 9 genes, a covariate category in which no patient (or every patient) was mutated was merged into its reference level, since it would separate. Adjustment removes only what the covariates measure: a hypermutated tumour raises every gene's odds at once, which TMB or MSI status as a covariate absorbs and nothing else does. Cohorts are expected not to share patients; a patient in both would count on both sides. diff --git a/test/snapshots/answers/acquired.json.gz b/test/snapshots/answers/acquired.json.gz new file mode 100644 index 0000000..2ae3232 Binary files /dev/null and b/test/snapshots/answers/acquired.json.gz differ diff --git a/test/snapshots/answers/actionability.json.gz b/test/snapshots/answers/actionability.json.gz new file mode 100644 index 0000000..c1fdac9 Binary files /dev/null and b/test/snapshots/answers/actionability.json.gz differ diff --git a/test/snapshots/answers/adjusted.json.gz b/test/snapshots/answers/adjusted.json.gz new file mode 100644 index 0000000..ff08af4 Binary files /dev/null and b/test/snapshots/answers/adjusted.json.gz differ diff --git a/test/snapshots/answers/attrition.json.gz b/test/snapshots/answers/attrition.json.gz index f2b96f5..3855978 100644 Binary files a/test/snapshots/answers/attrition.json.gz and b/test/snapshots/answers/attrition.json.gz differ diff --git a/test/snapshots/answers/clinical.json.gz b/test/snapshots/answers/clinical.json.gz index 9cfb148..a362be4 100644 Binary files a/test/snapshots/answers/clinical.json.gz and b/test/snapshots/answers/clinical.json.gz differ diff --git a/test/snapshots/answers/coexpression.json.gz b/test/snapshots/answers/coexpression.json.gz index 7dd1430..f8cd585 100644 Binary files a/test/snapshots/answers/coexpression.json.gz and b/test/snapshots/answers/coexpression.json.gz differ diff --git a/test/snapshots/answers/correlates.json.gz b/test/snapshots/answers/correlates.json.gz index 4a3800a..e950b06 100644 Binary files a/test/snapshots/answers/correlates.json.gz and b/test/snapshots/answers/correlates.json.gz differ diff --git a/test/snapshots/answers/cox.json.gz b/test/snapshots/answers/cox.json.gz index 43b1d00..db443f8 100644 Binary files a/test/snapshots/answers/cox.json.gz and b/test/snapshots/answers/cox.json.gz differ diff --git a/test/snapshots/answers/ctdna-chfilter.json.gz b/test/snapshots/answers/ctdna-chfilter.json.gz new file mode 100644 index 0000000..3d5c091 Binary files /dev/null and b/test/snapshots/answers/ctdna-chfilter.json.gz differ diff --git a/test/snapshots/answers/ctdna.json.gz b/test/snapshots/answers/ctdna.json.gz new file mode 100644 index 0000000..c4b8635 Binary files /dev/null and b/test/snapshots/answers/ctdna.json.gz differ diff --git a/test/snapshots/answers/cutpoint.json.gz b/test/snapshots/answers/cutpoint.json.gz new file mode 100644 index 0000000..0589ded Binary files /dev/null and b/test/snapshots/answers/cutpoint.json.gz differ diff --git a/test/snapshots/answers/diffexpr.json.gz b/test/snapshots/answers/diffexpr.json.gz index 8f09079..7ed8575 100644 Binary files a/test/snapshots/answers/diffexpr.json.gz and b/test/snapshots/answers/diffexpr.json.gz differ diff --git a/test/snapshots/answers/drivernegative.json.gz b/test/snapshots/answers/drivernegative.json.gz new file mode 100644 index 0000000..18e806c Binary files /dev/null and b/test/snapshots/answers/drivernegative.json.gz differ diff --git a/test/snapshots/answers/exclusivity.json.gz b/test/snapshots/answers/exclusivity.json.gz new file mode 100644 index 0000000..54b72aa Binary files /dev/null and b/test/snapshots/answers/exclusivity.json.gz differ diff --git a/test/snapshots/answers/forest.json.gz b/test/snapshots/answers/forest.json.gz new file mode 100644 index 0000000..477a41f Binary files /dev/null and b/test/snapshots/answers/forest.json.gz differ diff --git a/test/snapshots/answers/germline.json.gz b/test/snapshots/answers/germline.json.gz new file mode 100644 index 0000000..c21cccf Binary files /dev/null and b/test/snapshots/answers/germline.json.gz differ diff --git a/test/snapshots/answers/hla.json.gz b/test/snapshots/answers/hla.json.gz new file mode 100644 index 0000000..a6af184 Binary files /dev/null and b/test/snapshots/answers/hla.json.gz differ diff --git a/test/snapshots/answers/hotspots.json.gz b/test/snapshots/answers/hotspots.json.gz index ba6f16a..2483d90 100644 Binary files a/test/snapshots/answers/hotspots.json.gz and b/test/snapshots/answers/hotspots.json.gz differ diff --git a/test/snapshots/answers/instability.json.gz b/test/snapshots/answers/instability.json.gz new file mode 100644 index 0000000..6e81085 Binary files /dev/null and b/test/snapshots/answers/instability.json.gz differ diff --git a/test/snapshots/answers/logistic.json.gz b/test/snapshots/answers/logistic.json.gz index 514b145..b33d242 100644 Binary files a/test/snapshots/answers/logistic.json.gz and b/test/snapshots/answers/logistic.json.gz differ diff --git a/test/snapshots/answers/molsubtype.json.gz b/test/snapshots/answers/molsubtype.json.gz new file mode 100644 index 0000000..4a19dc2 Binary files /dev/null and b/test/snapshots/answers/molsubtype.json.gz differ diff --git a/test/snapshots/answers/multistate.json.gz b/test/snapshots/answers/multistate.json.gz new file mode 100644 index 0000000..41b93e6 Binary files /dev/null and b/test/snapshots/answers/multistate.json.gz differ diff --git a/test/snapshots/answers/organotropism.json.gz b/test/snapshots/answers/organotropism.json.gz new file mode 100644 index 0000000..576fe33 Binary files /dev/null and b/test/snapshots/answers/organotropism.json.gz differ diff --git a/test/snapshots/answers/paired.json.gz b/test/snapshots/answers/paired.json.gz index 4d1def6..7551f98 100644 Binary files a/test/snapshots/answers/paired.json.gz and b/test/snapshots/answers/paired.json.gz differ diff --git a/test/snapshots/answers/predictive.json.gz b/test/snapshots/answers/predictive.json.gz new file mode 100644 index 0000000..3667f16 Binary files /dev/null and b/test/snapshots/answers/predictive.json.gz differ diff --git a/test/snapshots/answers/riskmodel-validateon.json.gz b/test/snapshots/answers/riskmodel-validateon.json.gz new file mode 100644 index 0000000..dcd6c80 Binary files /dev/null and b/test/snapshots/answers/riskmodel-validateon.json.gz differ diff --git a/test/snapshots/answers/rmst.json.gz b/test/snapshots/answers/rmst.json.gz new file mode 100644 index 0000000..2abc50e Binary files /dev/null and b/test/snapshots/answers/rmst.json.gz differ diff --git a/test/snapshots/answers/sequence.json.gz b/test/snapshots/answers/sequence.json.gz index fc86537..893acc3 100644 Binary files a/test/snapshots/answers/sequence.json.gz and b/test/snapshots/answers/sequence.json.gz differ diff --git a/test/snapshots/answers/survival.json.gz b/test/snapshots/answers/survival.json.gz index 93fdd3c..3f6ae3a 100644 Binary files a/test/snapshots/answers/survival.json.gz and b/test/snapshots/answers/survival.json.gz differ diff --git a/test/snapshots/answers/two-studies-four-views.json.gz b/test/snapshots/answers/two-studies-four-views.json.gz index e87557c..523f0cd 100644 Binary files a/test/snapshots/answers/two-studies-four-views.json.gz and b/test/snapshots/answers/two-studies-four-views.json.gz differ diff --git a/test/snapshots/answers/txendpoints.json.gz b/test/snapshots/answers/txendpoints.json.gz new file mode 100644 index 0000000..fb9838e Binary files /dev/null and b/test/snapshots/answers/txendpoints.json.gz differ diff --git a/test/snapshots/ctdna-chfilter.json b/test/snapshots/ctdna-chfilter.json new file mode 100644 index 0000000..d591752 --- /dev/null +++ b/test/snapshots/ctdna-chfilter.json @@ -0,0 +1,786 @@ +{ + "study": "msk_access_2021", + "cohorts": { + "all patients": { + "count": 617, + "text": "all patients in the study", + "studies": [ + "msk_access_2021" + ] + } + }, + "views": [ + { + "kind": "ctdna", + "cohorts": [ + "all patients" + ], + "ctdna": { + "attribute": "SAMPLE_CLASS", + "plasma": [ + "cfDNA", + "ctDNA" + ], + "tissue": [ + "Tumor" + ], + "plasmaPanels": [ + "ACCESS129" + ], + "withinDays": null, + "measure": "any", + "top": 15, + "panels": [ + { + "cohort": "all patients", + "plasmaPatients": 617, + "tissuePatients": 449, + "patients": 449, + "plasmaSamples": 601, + "tissueSamples": 646, + "detected": 453, + "detectedLow": 0.6979693268835567, + "detectedHigh": 0.7675279363774432, + "genes": [ + { + "gene": "TP53", + "pairs": 449, + "both": 168, + "tissueOnly": 57, + "plasmaOnly": 14, + "neither": 210, + "ppa": 0.7466666666666667, + "ppaLow": 0.6860245271698205, + "ppaHigh": 0.7990274410007402, + "concordance": 0.8418708240534521 + }, + { + "gene": "EGFR", + "pairs": 449, + "both": 81, + "tissueOnly": 28, + "plasmaOnly": 5, + "neither": 335, + "ppa": 0.7431192660550459, + "ppaLow": 0.6538053234469022, + "ppaHigh": 0.8158802030776827, + "concordance": 0.9265033407572383 + }, + { + "gene": "KRAS", + "pairs": 449, + "both": 63, + "tissueOnly": 35, + "plasmaOnly": 5, + "neither": 346, + "ppa": 0.6428571428571429, + "ppaLow": 0.5442524878517956, + "ppaHigh": 0.7306846578277244, + "concordance": 0.910913140311804 + }, + { + "gene": "CDKN2A", + "pairs": 449, + "both": 15, + "tissueOnly": 82, + "plasmaOnly": 3, + "neither": 349, + "ppa": 0.15463917525773196, + "ppaLow": 0.09601122102239841, + "ppaHigh": 0.23957950938212214, + "concordance": 0.8106904231625836 + }, + { + "gene": "TERT", + "pairs": 449, + "both": 24, + "tissueOnly": 41, + "plasmaOnly": 1, + "neither": 383, + "ppa": 0.36923076923076925, + "ppaLow": 0.2622939587407091, + "ppaHigh": 0.4907618263744998, + "concordance": 0.9064587973273942 + }, + { + "gene": "ARID1A", + "pairs": 449, + "both": 26, + "tissueOnly": 25, + "plasmaOnly": 9, + "neither": 389, + "ppa": 0.5098039215686274, + "ppaLow": 0.37680923894569696, + "ppaHigh": 0.6414251410562926, + "concordance": 0.9242761692650334 + }, + { + "gene": "KEAP1", + "pairs": 449, + "both": 32, + "tissueOnly": 16, + "plasmaOnly": 4, + "neither": 397, + "ppa": 0.6666666666666666, + "ppaLow": 0.5254010970594798, + "ppaHigh": 0.7832321930518142, + "concordance": 0.955456570155902 + }, + { + "gene": "PIK3CA", + "pairs": 449, + "both": 25, + "tissueOnly": 19, + "plasmaOnly": 7, + "neither": 398, + "ppa": 0.5681818181818182, + "ppaLow": 0.4222414699362943, + "ppaHigh": 0.703172765914822, + "concordance": 0.9420935412026726 + }, + { + "gene": "SMAD4", + "pairs": 449, + "both": 19, + "tissueOnly": 25, + "plasmaOnly": 4, + "neither": 401, + "ppa": 0.4318181818181818, + "ppaLow": 0.2968272340851781, + "ppaHigh": 0.5777585300637057, + "concordance": 0.9354120267260579 + }, + { + "gene": "AR", + "pairs": 449, + "both": 19, + "tissueOnly": 17, + "plasmaOnly": 11, + "neither": 402, + "ppa": 0.5277777777777778, + "ppaLow": 0.37005938022987594, + "ppaHigh": 0.6801395848482095, + "concordance": 0.9376391982182628 + }, + { + "gene": "RB1", + "pairs": 449, + "both": 22, + "tissueOnly": 17, + "plasmaOnly": 8, + "neither": 402, + "ppa": 0.5641025641025641, + "ppaLow": 0.40975691648669477, + "ppaHigh": 0.7069524612431037, + "concordance": 0.9443207126948775 + }, + { + "gene": "ATM", + "pairs": 449, + "both": 22, + "tissueOnly": 8, + "plasmaOnly": 16, + "neither": 403, + "ppa": 0.7333333333333333, + "ppaLow": 0.555520383048111, + "ppaHigh": 0.8581733668040368, + "concordance": 0.9465478841870824 + }, + { + "gene": "STK11", + "pairs": 449, + "both": 29, + "tissueOnly": 15, + "plasmaOnly": 2, + "neither": 403, + "ppa": 0.6590909090909091, + "ppaLow": 0.5113914121793823, + "ppaHigh": 0.7812418048065555, + "concordance": 0.9621380846325167 + }, + { + "gene": "SMARCA4", + "pairs": 449, + "both": 9, + "tissueOnly": 30, + "plasmaOnly": 2, + "neither": 408, + "ppa": 0.23076923076923078, + "ppaLow": 0.1264580412514285, + "ppaHigh": 0.3833625722834177, + "concordance": 0.9287305122494433 + }, + { + "gene": "APC", + "pairs": 449, + "both": 20, + "tissueOnly": 11, + "plasmaOnly": 8, + "neither": 410, + "ppa": 0.6451612903225806, + "ppaLow": 0.46947954064368874, + "ppaHigh": 0.788833409061346, + "concordance": 0.9576837416481069 + } + ], + "survival": { + "curves": [ + { + "label": "ctDNA detected", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0, + "s": 0.9793 + }, + { + "t": 0.07, + "s": 0.9734 + }, + { + "t": 0.2, + "s": 0.9704 + }, + { + "t": 0.43, + "s": 0.9644 + }, + { + "t": 0.56, + "s": 0.9615 + }, + { + "t": 0.66, + "s": 0.9585 + }, + { + "t": 0.76, + "s": 0.9555 + }, + { + "t": 0.79, + "s": 0.9525 + }, + { + "t": 0.82, + "s": 0.9495 + }, + { + "t": 0.86, + "s": 0.9465 + }, + { + "t": 0.89, + "s": 0.9404 + }, + { + "t": 0.92, + "s": 0.9374 + }, + { + "t": 1.02, + "s": 0.9344 + }, + { + "t": 1.15, + "s": 0.9284 + }, + { + "t": 1.28, + "s": 0.9254 + }, + { + "t": 1.32, + "s": 0.9223 + }, + { + "t": 1.45, + "s": 0.9163 + }, + { + "t": 1.51, + "s": 0.9133 + }, + { + "t": 1.54, + "s": 0.9103 + }, + { + "…": "132 more", + "sha256": "12465eeb68bc87b8" + } + ], + "median": 31.59, + "n": 338, + "events": 171, + "atRisk": [ + 338, + 311, + 296, + 284, + 274, + 271, + 258, + 251, + 240, + 237, + 228, + 222, + 212, + 198, + 180, + 165, + 161, + 146, + 132, + 121, + { + "…": "60 more", + "sha256": "4d0fc974e5bb866f" + } + ] + }, + { + "label": "not detected", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.4, + "s": 0.9904 + }, + { + "t": 3.09, + "s": 0.9806 + }, + { + "t": 3.35, + "s": 0.9708 + }, + { + "t": 3.45, + "s": 0.961 + }, + { + "t": 3.72, + "s": 0.9509 + }, + { + "t": 4.8, + "s": 0.9406 + }, + { + "t": 4.87, + "s": 0.9304 + }, + { + "t": 6.38, + "s": 0.9199 + }, + { + "t": 6.58, + "s": 0.9095 + }, + { + "t": 8.06, + "s": 0.8987 + }, + { + "t": 9.44, + "s": 0.8877 + }, + { + "t": 9.7, + "s": 0.8767 + }, + { + "t": 10.62, + "s": 0.8657 + }, + { + "t": 11.57, + "s": 0.8543 + }, + { + "t": 11.84, + "s": 0.8429 + }, + { + "t": 12.13, + "s": 0.8315 + }, + { + "t": 15.22, + "s": 0.8158 + }, + { + "t": 18.97, + "s": 0.7949 + }, + { + "t": 21.34, + "s": 0.7708 + }, + { + "…": "6 more", + "sha256": "5a041fe6ff008265" + } + ], + "median": null, + "n": 107, + "events": 25, + "atRisk": [ + 107, + 102, + 102, + 102, + 94, + 90, + 89, + 87, + 84, + 82, + 79, + 77, + 74, + 70, + 61, + 54, + 48, + 46, + 41, + 38, + { + "…": "50 more", + "sha256": "a40f231d9d4c50d3" + } + ] + } + ], + "logrank": { + "chi2": 12.8354, + "p": 0.000340126236561827, + "df": 1, + "observed": [ + 170, + 25 + ], + "expected": [ + 148.8, + 46.2 + ], + "variance": 34.9635, + "strata": 1 + }, + "missing": 172 + }, + "ch": { + "calls": 2024, + "withNormal": 2024, + "byGene": 41, + "byNormal": 44, + "removed": 83, + "patients": 51, + "genes": [ + { + "gene": "SF3B1", + "calls": 14, + "patients": 9, + "byGene": 14, + "byNormal": 2 + }, + { + "gene": "TP53", + "calls": 14, + "patients": 5, + "byGene": 0, + "byNormal": 14 + }, + { + "gene": "DNMT3A", + "calls": 11, + "patients": 4, + "byGene": 11, + "byNormal": 0 + }, + { + "gene": "GNAS", + "calls": 8, + "patients": 6, + "byGene": 8, + "byNormal": 0 + }, + { + "gene": "IDH2", + "calls": 4, + "patients": 4, + "byGene": 4, + "byNormal": 0 + }, + { + "gene": "ATM", + "calls": 3, + "patients": 3, + "byGene": 0, + "byNormal": 3 + }, + { + "gene": "BRAF", + "calls": 3, + "patients": 1, + "byGene": 0, + "byNormal": 3 + }, + { + "gene": "RB1", + "calls": 3, + "patients": 2, + "byGene": 0, + "byNormal": 3 + }, + { + "gene": "U2AF1", + "calls": 3, + "patients": 3, + "byGene": 3, + "byNormal": 0 + }, + { + "gene": "AR", + "calls": 2, + "patients": 2, + "byGene": 0, + "byNormal": 2 + }, + { + "gene": "IDH1", + "calls": 2, + "patients": 1, + "byGene": 0, + "byNormal": 2 + }, + { + "gene": "TSC2", + "calls": 2, + "patients": 2, + "byGene": 0, + "byNormal": 2 + }, + { + "gene": "ASXL1", + "calls": 1, + "patients": 1, + "byGene": 1, + "byNormal": 0 + }, + { + "gene": "CREBBP", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "gene": "EGFR", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "gene": "FGFR1", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "gene": "FGFR2", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "gene": "KDM6A", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "gene": "MET", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "gene": "MSH3", + "calls": 1, + "patients": 1, + "byGene": 0, + "byNormal": 1 + }, + { + "…": "6 more", + "sha256": "f72c0601d98ab272" + } + ], + "before": { + "detected": 457, + "detectedLow": 0.7046854301434221, + "detectedHigh": 0.7736975705088859, + "genes": [ + { + "gene": "TP53", + "both": 168, + "plasmaOnly": 15, + "ppa": 0.7466666666666667 + }, + { + "gene": "EGFR", + "both": 81, + "plasmaOnly": 6, + "ppa": 0.7431192660550459 + }, + { + "gene": "KRAS", + "both": 63, + "plasmaOnly": 5, + "ppa": 0.6428571428571429 + }, + { + "gene": "CDKN2A", + "both": 15, + "plasmaOnly": 3, + "ppa": 0.15463917525773196 + }, + { + "gene": "TERT", + "both": 24, + "plasmaOnly": 1, + "ppa": 0.36923076923076925 + }, + { + "gene": "ARID1A", + "both": 26, + "plasmaOnly": 9, + "ppa": 0.5098039215686274 + }, + { + "gene": "KEAP1", + "both": 32, + "plasmaOnly": 4, + "ppa": 0.6666666666666666 + }, + { + "gene": "PIK3CA", + "both": 25, + "plasmaOnly": 7, + "ppa": 0.5681818181818182 + }, + { + "gene": "SMAD4", + "both": 19, + "plasmaOnly": 4, + "ppa": 0.4318181818181818 + }, + { + "gene": "AR", + "both": 19, + "plasmaOnly": 12, + "ppa": 0.5277777777777778 + }, + { + "gene": "RB1", + "both": 22, + "plasmaOnly": 10, + "ppa": 0.5641025641025641 + }, + { + "gene": "ATM", + "both": 22, + "plasmaOnly": 17, + "ppa": 0.7333333333333333 + }, + { + "gene": "STK11", + "both": 29, + "plasmaOnly": 2, + "ppa": 0.6590909090909091 + }, + { + "gene": "SMARCA4", + "both": 9, + "plasmaOnly": 2, + "ppa": 0.23076923076923078 + }, + { + "gene": "APC", + "both": 20, + "plasmaOnly": 8, + "ppa": 0.6451612903225806 + } + ], + "survival": { + "curves": [ + { + "label": "ctDNA detected", + "n": 341, + "events": 172, + "median": 31.59 + }, + { + "label": "not detected", + "n": 104, + "events": 24, + "median": null + } + ], + "p": 0.0003520559251550135 + } + } + } + } + ], + "chFilter": { + "genes": [ + "DNMT3A", + "TET2", + "ASXL1", + "PPM1D", + "JAK2", + "SF3B1", + "SRSF2", + "U2AF1", + "CBL", + "GNB1", + "GNAS", + "IDH2" + ], + "normalMinAlt": 2, + "normalMinRatio": 0.3 + } + }, + "participation": { + "studies": [ + { + "study": "msk_access_2021", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Plasma is SAMPLE_CLASS = cfDNA/ctDNA or a sample on ACCESS129, tissue SAMPLE_CLASS = Tumor. all patients: 617 patients with plasma, 449 patients with tissue, 449 patients with both (601 plasma and 646 tissue samples); ctDNA detected in 453 of the plasma patients (73.4%). Plasma and tissue are paired by patient whenever they were drawn, not by date — the tumour may have changed between them. Per gene, counting only patients whose plasma AND tissue panels both cover it, any alteration (mutation, amplification or deep deletion, structural variant) in both, tissue only, plasma only, or neither; a patient with several samples of a kind is altered there if any is. Positive percent agreement is both ÷ altered in tissue (tissue the reference), with a 95% Wilson interval; overall concordance is (both + neither) ÷ pairs. The genes are the 15 most altered in the paired patients, not chosen in advance. Clonal-haematopoiesis filter: plasma mutation calls in DNMT3A, TET2, ASXL1, PPM1D, JAK2, SF3B1, SRSF2, U2AF1, CBL, GNB1, GNAS, IDH2, or seen in the matched normal (white-cell) sample — at least 2 alternate reads there, at an allele fraction at least 0.3 × the plasma's are left out of every count below (copy-number and structural calls are kept; tissue calls are never filtered). all patients — 83 plasma mutation calls of 2,024 removed (41 by gene, 44 by the normal), in 51 patients; ctDNA detected 453 (73.4%) against 457 (74.1%) unfiltered. The gene rule also removes a tumour's own calls in those genes (GNAS, IDH2, SF3B1 and others can be tumour drivers); the allele-fraction ratio keeps calls whose normal reads are tumour DNA in the white-cell sample (far below the plasma's fraction), and a CH clone at very low fraction in blood can still pass; TP53, ATM and CHEK2 are not on the default list because they are tumour drivers as often as CH genes (Razavi et al. 2019; Bolton et al. 2020). Tissue-only calls remain those of a tumour shedding too little DNA. ctDNA detected means any alteration (mutation, amplification or deep deletion, structural variant) called in some plasma sample, over every gene the plasma panel covers. Survival is OS_MONTHS / OS_STATUS as each study records them, from its own origin, which need not be the plasma draw; when the draw comes after that origin the comparison carries immortal-time bias. " + } + ] +} diff --git a/test/snapshots/ctdna-chfilter.sql b/test/snapshots/ctdna-chfilter.sql new file mode 100644 index 0000000..debcae7 --- /dev/null +++ b/test/snapshots/ctdna-chfilter.sql @@ -0,0 +1,324 @@ +-- cohort all patients +-- msk_access_2021 +SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021' + +-- view 0 ctdna: ctdna:ch-removed:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +nm AS ( + SELECT concat(studyId, '_', sampleId) AS suid, `GENE.hugoGeneSymbol` AS gene, proteinChange AS mv, + max(normalAltCount >= 2 AND normalAltCount / (normalAltCount + normalRefCount) >= 0.3 * tumorAltCount / greatest(1, tumorAltCount + tumorRefCount)) AS hit, max(normalAltCount IS NOT NULL) AS loaded + FROM snapshot_db.mutation_derived + WHERE studyId = 'msk_access_2021' AND concat(studyId, '_', sampleId) IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +chx AS ( + SELECT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene, g.mutation_variant AS mv, + max(g.hugo_gene_symbol IN ('DNMT3A','TET2','ASXL1','PPM1D','JAK2','SF3B1','SRSF2','U2AF1','CBL','GNB1','GNAS','IDH2')) AS by_gene, max(ifNull(nm.hit, 0) = 1) AS by_normal, + max(ifNull(nm.loaded, 0) = 1) AS has_normal + FROM snapshot_db.genomic_event_derived g + LEFT JOIN nm ON nm.suid = g.sample_unique_id AND nm.gene = g.hugo_gene_symbol AND nm.mv = g.mutation_variant + WHERE g.cancer_study_identifier = 'msk_access_2021' AND g.variant_type = 'mutation' + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +) +SELECT chx.gene AS gene, countIf(by_gene OR by_normal) AS calls, uniqExactIf(s.pid, by_gene OR by_normal) AS patients, + countIf(by_gene) AS gene_calls, countIf(by_normal) AS normal_calls +FROM chx INNER JOIN sides s ON s.suid = chx.suid +GROUP BY gene HAVING calls > 0 ORDER BY calls DESC, gene LIMIT 40 + +-- view 0 ctdna: ctdna:funnel:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +nm AS ( + SELECT concat(studyId, '_', sampleId) AS suid, `GENE.hugoGeneSymbol` AS gene, proteinChange AS mv, + max(normalAltCount >= 2 AND normalAltCount / (normalAltCount + normalRefCount) >= 0.3 * tumorAltCount / greatest(1, tumorAltCount + tumorRefCount)) AS hit, max(normalAltCount IS NOT NULL) AS loaded + FROM snapshot_db.mutation_derived + WHERE studyId = 'msk_access_2021' AND concat(studyId, '_', sampleId) IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +chx AS ( + SELECT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene, g.mutation_variant AS mv, + max(g.hugo_gene_symbol IN ('DNMT3A','TET2','ASXL1','PPM1D','JAK2','SF3B1','SRSF2','U2AF1','CBL','GNB1','GNAS','IDH2')) AS by_gene, max(ifNull(nm.hit, 0) = 1) AS by_normal, + max(ifNull(nm.loaded, 0) = 1) AS has_normal + FROM snapshot_db.genomic_event_derived g + LEFT JOIN nm ON nm.suid = g.sample_unique_id AND nm.gene = g.hugo_gene_symbol AND nm.mv = g.mutation_variant + WHERE g.cancer_study_identifier = 'msk_access_2021' AND g.variant_type = 'mutation' + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +det AS ( + SELECT DISTINCT s.pid AS pid FROM snapshot_db.genomic_event_derived g + INNER JOIN sides s ON s.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'msk_access_2021' AND s.side = 'b' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') AND NOT (s.side = 'b' AND g.variant_type = 'mutation' AND (g.sample_unique_id, g.hugo_gene_symbol, g.mutation_variant) IN (SELECT suid, gene, mv FROM chx WHERE by_gene OR by_normal)) +) +SELECT (SELECT uniqExactIf(pid, side = 'b') FROM classed) AS plasma_all, (SELECT uniqExactIf(pid, side = 'a') FROM classed) AS tissue_all, + (SELECT uniqExactIf(pid, side = 'b') FROM sides) AS plasma, (SELECT uniqExactIf(pid, side = 'a') FROM sides) AS tissue, + (SELECT count() FROM paired) AS both, (SELECT count() FROM det) AS detected, + (SELECT countIf(side = 'b') FROM ps) AS plasma_samples, (SELECT countIf(side = 'a') FROM ps) AS tissue_samples, + toUInt64(0) AS dated_samples, + (SELECT count() FROM chx) AS ch_calls, (SELECT countIf(has_normal) FROM chx) AS ch_with_normal, + (SELECT countIf(by_gene) FROM chx) AS ch_by_gene, (SELECT countIf(by_normal) FROM chx) AS ch_by_normal, (SELECT countIf(by_gene OR by_normal) FROM chx) AS ch_removed, + (SELECT uniqExact(s.pid) FROM chx INNER JOIN sides s ON s.suid = chx.suid WHERE by_gene OR by_normal) AS ch_patients +FROM system.one + +-- view 0 ctdna: ctdna:genes:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +nm AS ( + SELECT concat(studyId, '_', sampleId) AS suid, `GENE.hugoGeneSymbol` AS gene, proteinChange AS mv, + max(normalAltCount >= 2 AND normalAltCount / (normalAltCount + normalRefCount) >= 0.3 * tumorAltCount / greatest(1, tumorAltCount + tumorRefCount)) AS hit, max(normalAltCount IS NOT NULL) AS loaded + FROM snapshot_db.mutation_derived + WHERE studyId = 'msk_access_2021' AND concat(studyId, '_', sampleId) IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +chx AS ( + SELECT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene, g.mutation_variant AS mv, + max(g.hugo_gene_symbol IN ('DNMT3A','TET2','ASXL1','PPM1D','JAK2','SF3B1','SRSF2','U2AF1','CBL','GNB1','GNAS','IDH2')) AS by_gene, max(ifNull(nm.hit, 0) = 1) AS by_normal, + max(ifNull(nm.loaded, 0) = 1) AS has_normal + FROM snapshot_db.genomic_event_derived g + LEFT JOIN nm ON nm.suid = g.sample_unique_id AND nm.gene = g.hugo_gene_symbol AND nm.mv = g.mutation_variant + WHERE g.cancer_study_identifier = 'msk_access_2021' AND g.variant_type = 'mutation' + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +alt AS ( + SELECT g.hugo_gene_symbol AS gene, ps.pid AS pid, ps.side AS side, 1 AS hit + FROM snapshot_db.genomic_event_derived g + INNER JOIN ps ON ps.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'msk_access_2021' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM ps) AND NOT (ps.side = 'b' AND g.variant_type = 'mutation' AND (g.sample_unique_id, g.hugo_gene_symbol, g.mutation_variant) IN (SELECT suid, gene, mv FROM chx WHERE by_gene OR by_normal)) + GROUP BY gene, pid, side + HAVING hit > 0 +), +cov AS ( + SELECT DISTINCT pg.gene AS gene, ps.pid AS pid, ps.side AS side + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = sp.gene_panel_id AND pg.gene IN (SELECT DISTINCT gene FROM alt) + INNER JOIN ps ON ps.suid = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_access_2021' + AND sp.alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') +), +per AS ( + SELECT gene, pid, max(side = 'a') AS pa, max(side = 'b') AS pb, + max(side = 'a' AND hit > 0) AS aa, max(side = 'b' AND hit > 0) AS ab + FROM (SELECT gene, pid, side, hit FROM alt UNION ALL SELECT gene, pid, side, 0 AS hit FROM cov) + GROUP BY gene, pid +) +SELECT gene, count() AS pairs, countIf(aa AND ab) AS both, countIf(aa AND NOT ab) AS a_only, countIf(ab AND NOT aa) AS b_only +FROM per WHERE pa AND pb GROUP BY gene HAVING both + a_only + b_only > 0 + +-- view 0 ctdna: ctdna:survival:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +nm AS ( + SELECT concat(studyId, '_', sampleId) AS suid, `GENE.hugoGeneSymbol` AS gene, proteinChange AS mv, + max(normalAltCount >= 2 AND normalAltCount / (normalAltCount + normalRefCount) >= 0.3 * tumorAltCount / greatest(1, tumorAltCount + tumorRefCount)) AS hit, max(normalAltCount IS NOT NULL) AS loaded + FROM snapshot_db.mutation_derived + WHERE studyId = 'msk_access_2021' AND concat(studyId, '_', sampleId) IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +chx AS ( + SELECT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene, g.mutation_variant AS mv, + max(g.hugo_gene_symbol IN ('DNMT3A','TET2','ASXL1','PPM1D','JAK2','SF3B1','SRSF2','U2AF1','CBL','GNB1','GNAS','IDH2')) AS by_gene, max(ifNull(nm.hit, 0) = 1) AS by_normal, + max(ifNull(nm.loaded, 0) = 1) AS has_normal + FROM snapshot_db.genomic_event_derived g + LEFT JOIN nm ON nm.suid = g.sample_unique_id AND nm.gene = g.hugo_gene_symbol AND nm.mv = g.mutation_variant + WHERE g.cancer_study_identifier = 'msk_access_2021' AND g.variant_type = 'mutation' + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') + GROUP BY suid, gene, mv +), +det AS ( + SELECT DISTINCT s.pid AS pid FROM snapshot_db.genomic_event_derived g + INNER JOIN sides s ON s.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'msk_access_2021' AND s.side = 'b' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') AND NOT (s.side = 'b' AND g.variant_type = 'mutation' AND (g.sample_unique_id, g.hugo_gene_symbol, g.mutation_variant) IN (SELECT suid, gene, mv FROM chx WHERE by_gene OR by_normal)) +), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid, if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead, replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM det) AS detected + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM sides WHERE side = 'b') + GROUP BY pid +) +SELECT detected, round(months, 2) AS t, dead, count() AS n +FROM os WHERE months IS NOT NULL AND months >= 0 GROUP BY detected, t, dead ORDER BY t + +-- view 0 ctdna: ctdna:unfiltered:funnel:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +det AS ( + SELECT DISTINCT s.pid AS pid FROM snapshot_db.genomic_event_derived g + INNER JOIN sides s ON s.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'msk_access_2021' AND s.side = 'b' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') +) +SELECT (SELECT uniqExactIf(pid, side = 'b') FROM classed) AS plasma_all, (SELECT uniqExactIf(pid, side = 'a') FROM classed) AS tissue_all, + (SELECT uniqExactIf(pid, side = 'b') FROM sides) AS plasma, (SELECT uniqExactIf(pid, side = 'a') FROM sides) AS tissue, + (SELECT count() FROM paired) AS both, (SELECT count() FROM det) AS detected, + (SELECT countIf(side = 'b') FROM ps) AS plasma_samples, (SELECT countIf(side = 'a') FROM ps) AS tissue_samples, + toUInt64(0) AS dated_samples +FROM system.one + +-- view 0 ctdna: ctdna:unfiltered:genes:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +alt AS ( + SELECT g.hugo_gene_symbol AS gene, ps.pid AS pid, ps.side AS side, 1 AS hit + FROM snapshot_db.genomic_event_derived g + INNER JOIN ps ON ps.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'msk_access_2021' AND g.hugo_gene_symbol IN ('TP53','EGFR','KRAS','CDKN2A','TERT','ARID1A','KEAP1','PIK3CA','SMAD4','AR','RB1','ATM','STK11','SMARCA4','APC') AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM ps) + GROUP BY gene, pid, side +), +cov AS ( + SELECT DISTINCT pg.gene AS gene, ps.pid AS pid, ps.side AS side + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = sp.gene_panel_id AND pg.gene IN ('TP53','EGFR','KRAS','CDKN2A','TERT','ARID1A','KEAP1','PIK3CA','SMAD4','AR','RB1','ATM','STK11','SMARCA4','APC') + INNER JOIN ps ON ps.suid = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_access_2021' + AND sp.alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') +), +per AS ( + SELECT gene, pid, max(side = 'a') AS pa, max(side = 'b') AS pb, + max(side = 'a' AND hit > 0) AS aa, max(side = 'b' AND hit > 0) AS ab + FROM (SELECT gene, pid, side, hit FROM alt UNION ALL SELECT gene, pid, side, 0 AS hit FROM cov) + GROUP BY gene, pid +) +SELECT gene, count() AS pairs, countIf(aa AND ab) AS both, countIf(aa AND NOT ab) AS a_only, countIf(ab AND NOT aa) AS b_only +FROM per WHERE pa AND pb GROUP BY gene + +-- view 0 ctdna: ctdna:unfiltered:survival:all patients · msk_access_2021 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_access_2021'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(sd.sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_to_gene_panel_derived WHERE cancer_study_identifier = 'msk_access_2021' AND gene_panel_id IN ('ACCESS129')), 'b', cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'msk_access_2021' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +det AS ( + SELECT DISTINCT s.pid AS pid FROM snapshot_db.genomic_event_derived g + INNER JOIN sides s ON s.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'msk_access_2021' AND s.side = 'b' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') +), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_access_2021_', '') AS pid, if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead, replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM det) AS detected + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_access_2021' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_access_2021_', '') IN (SELECT pid FROM sides WHERE side = 'b') + GROUP BY pid +) +SELECT detected, round(months, 2) AS t, dead, count() AS n +FROM os WHERE months IS NOT NULL AND months >= 0 GROUP BY detected, t, dead ORDER BY t diff --git a/test/snapshots/ctdna-chfilter.svg b/test/snapshots/ctdna-chfilter.svg new file mode 100644 index 0000000..8005476 --- /dev/null +++ b/test/snapshots/ctdna-chfilter.svg @@ -0,0 +1,2 @@ + +ctDNA vs tissuetissue onlybothplasma onlyall patients449 patients with plasma and tissue · ctDNA detected in 453 of 617 with plasma (73%)Clonal-haematopoiesis filter removed 83 of 2,024 plasma mutation calls in 51 patients (41 by gene, 44 seen in the matched normal): SF3B1 14, TP53 14, DNMT3A 11, GNAS 8, IDH2 4, ATM 3, BRAF 3, RB1 3, 18more genes · unfiltered, ctDNA detected in 457 (74%), OS log-rank p = 3.5e-4plasma-only unfilteredPPA [95% CI]concordantpairsTP53TP53 — 57 patients in tissue only57TP53 — 168 patients in both168TP53 — 14 patients in plasma only141575% [69%–80%]84%449EGFREGFR — 28 patients in tissue only28EGFR — 81 patients in both81EGFR — 5 patients in plasma only674% [65%–82%]93%449KRASKRAS — 35 patients in tissue only35KRAS — 63 patients in both63KRAS — 5 patients in plasma only564% [54%–73%]91%449CDKN2ACDKN2A — 82 patients in tissue only82CDKN2A — 15 patients in both15CDKN2A — 3 patients in plasma only315% [10%–24%]81%449TERTTERT — 41 patients in tissue only41TERT — 24 patients in both24TERT — 1 patient in plasma only137% [26%–49%]91%449ARID1AARID1A — 25 patients in tissue only25ARID1A — 26 patients in both26ARID1A — 9 patients in plasma only9951% [38%–64%]92%449KEAP1KEAP1 — 16 patients in tissue only16KEAP1 — 32 patients in both32KEAP1 — 4 patients in plasma only467% [53%–78%]96%449PIK3CAPIK3CA — 19 patients in tissue only19PIK3CA — 25 patients in both25PIK3CA — 7 patients in plasma only7757% [42%–70%]94%449SMAD4SMAD4 — 25 patients in tissue only25SMAD4 — 19 patients in both19SMAD4 — 4 patients in plasma only443% [30%–58%]94%449ARAR — 17 patients in tissue only17AR — 19 patients in both19AR — 11 patients in plasma only111253% [37%–68%]94%449RB1RB1 — 17 patients in tissue only17RB1 — 22 patients in both22RB1 — 8 patients in plasma only81056% [41%–71%]94%449ATMATM — 8 patients in tissue only8ATM — 22 patients in both22ATM — 16 patients in plasma only161773% [56%–86%]95%449STK11STK11 — 15 patients in tissue only15STK11 — 29 patients in both29STK11 — 2 patients in plasma only266% [51%–78%]96%449SMARCA4SMARCA4 — 30 patients in tissue only30SMARCA4 — 9 patients in both9SMARCA4 — 2 patients in plasma only223% [13%–38%]93%449APCAPC — 11 patients in tissue only11APC — 20 patients in both20APC — 8 patients in plasma only8865% [47%–79%]96%449050100150200250patients altered in plasma or tissueOverall survival by ctDNA detectedctDNA detected — 338 patients, 171 deaths, median 31.6 monot detected — 107 patients, 25 deaths, median not reached0%25%50%75%100%Survival probability0244872Monthslog-rank χ² = 12.8 · p = 3.4e-4 · 172 plasma patients without OS left outPlasma is SAMPLE_CLASS = cfDNA/ctDNA or a sample on ACCESS129, tissue SAMPLE_CLASS = Tumor. all patients: 617 patients with plasma, 449 patients with tissue,449 patients with both (601 plasma and 646 tissue samples); ctDNA detected in 453 of the plasma patients (73.4%). Plasma and tissue are paired by patient wheneverthey were drawn, not by date — the tumour may have changed between them. Per gene, counting only patients whose plasma AND tissue panels both cover it, anyalteration (mutation, amplification or deep deletion, structural variant) in both, tissue only, plasma only, or neither; a patient with several samples of a kind is alteredthere if any is. Positive percent agreement is both ÷ altered in tissue (tissue the reference), with a 95% Wilson interval; overall concordance is (both + neither) ÷ pairs.The genes are the 15 most altered in the paired patients, not chosen in advance. Clonal-haematopoiesis filter: plasma mutation calls in DNMT3A, TET2, ASXL1, PPM1D,JAK2, SF3B1, SRSF2, U2AF1, CBL, GNB1, GNAS, IDH2, or seen in the matched normal (white-cell) sample — at least 2 alternate reads there, at an allele fraction at least0.3 × the plasma's are left out of every count below (copy-number and structural calls are kept; tissue calls are never filtered). all patients — 83 plasma mutation calls of2,024 removed (41 by gene, 44 by the normal), in 51 patients; ctDNA detected 453 (73.4%) against 457 (74.1%) unfiltered. The gene rule also removes a tumour's owncalls in those genes (GNAS, IDH2, SF3B1 and others can be tumour drivers); the allele-fraction ratio keeps calls whose normal reads are tumour DNA in the white-cellsample (far below the plasma's fraction), and a CH clone at very low fraction in blood can still pass; TP53, ATM and CHEK2 are not on the default list because they aretumour drivers as often as CH genes (Razavi et al. 2019; Bolton et al. 2020). Tissue-only calls remain those of a tumour shedding too little DNA. ctDNA detected meansany alteration (mutation, amplification or deep deletion, structural variant) called in some plasma sample, over every gene the plasma panel covers. Survival isOS_MONTHS / OS_STATUS as each study records them, from its own origin, which need not be the plasma draw; when the draw comes after that origin the comparisoncarries immortal-time bias. diff --git a/test/snapshots/ctdna-chfilter.txt b/test/snapshots/ctdna-chfilter.txt new file mode 100644 index 0000000..0b7615e --- /dev/null +++ b/test/snapshots/ctdna-chfilter.txt @@ -0,0 +1,25 @@ +Analysis over msk_access_2021 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- all patients: 617 patients — all patients in the study + +ctDNA vs tissue (plasma SAMPLE_CLASS = cfDNA/ctDNA or panel ACCESS129, tissue Tumor; any alteration; both panels cover the gene): + all patients (449 patients with both; ctDNA detected in 453 of 617 with plasma, 73% [95% CI 70%–77%]): + TP53: both 168, tissue-only 57, plasma-only 14, neither 210 of 449; PPA 75% [69%–80%], concordance 84% + EGFR: both 81, tissue-only 28, plasma-only 5, neither 335 of 449; PPA 74% [65%–82%], concordance 93% + KRAS: both 63, tissue-only 35, plasma-only 5, neither 346 of 449; PPA 64% [54%–73%], concordance 91% + CDKN2A: both 15, tissue-only 82, plasma-only 3, neither 349 of 449; PPA 15% [10%–24%], concordance 81% + TERT: both 24, tissue-only 41, plasma-only 1, neither 383 of 449; PPA 37% [26%–49%], concordance 91% + ARID1A: both 26, tissue-only 25, plasma-only 9, neither 389 of 449; PPA 51% [38%–64%], concordance 92% + KEAP1: both 32, tissue-only 16, plasma-only 4, neither 397 of 449; PPA 67% [53%–78%], concordance 96% + PIK3CA: both 25, tissue-only 19, plasma-only 7, neither 398 of 449; PPA 57% [42%–70%], concordance 94% + SMAD4: both 19, tissue-only 25, plasma-only 4, neither 401 of 449; PPA 43% [30%–58%], concordance 94% + AR: both 19, tissue-only 17, plasma-only 11, neither 402 of 449; PPA 53% [37%–68%], concordance 94% + RB1: both 22, tissue-only 17, plasma-only 8, neither 402 of 449; PPA 56% [41%–71%], concordance 94% + ATM: both 22, tissue-only 8, plasma-only 16, neither 403 of 449; PPA 73% [56%–86%], concordance 95% + STK11: both 29, tissue-only 15, plasma-only 2, neither 403 of 449; PPA 66% [51%–78%], concordance 96% + SMARCA4: both 9, tissue-only 30, plasma-only 2, neither 408 of 449; PPA 23% [13%–38%], concordance 93% + APC: both 20, tissue-only 11, plasma-only 8, neither 410 of 449; PPA 65% [47%–79%], concordance 96% + OS: ctDNA detected n=338, 171 deaths, median 31.6mo; not detected n=107, 25 deaths, median not reached; log-rank p=0.0003 + CH filter: removed 83 of 2024 plasma mutation calls in 51 patients (41 by gene, 44 by the matched normal): SF3B1 14, TP53 14, DNMT3A 11, GNAS 8, IDH2 4, ATM 3, BRAF 3, RB1 3, U2AF1 3, AR 2, IDH1 2, TSC2 2, ASXL1 1, CREBBP 1, EGFR 1, FGFR1 1, FGFR2 1, KDM6A 1, MET 1, MSH3 1, MYCN 1, NF1 1, NTRK1 1, NTRK3 1, PTCH1 1, SMARCA4 1; unfiltered: ctDNA detected 457 (74%), OS log-rank p=0.0004; plasma-only unfiltered TP53 15, EGFR 6, KRAS 5, CDKN2A 3, TERT 1, ARID1A 9, KEAP1 4, PIK3CA 7, SMAD4 4, AR 12, RB1 10, ATM 17, STK11 2, SMARCA4 2, APC 8 + CAVEAT (tell the user): Plasma is SAMPLE_CLASS = cfDNA/ctDNA or a sample on ACCESS129, tissue SAMPLE_CLASS = Tumor. all patients: 617 patients with plasma, 449 patients with tissue, 449 patients with both (601 plasma and 646 tissue samples); ctDNA detected in 453 of the plasma patients (73.4%). Plasma and tissue are paired by patient whenever they were drawn, not by date — the tumour may have changed between them. Per gene, counting only patients whose plasma AND tissue panels both cover it, any alteration (mutation, amplification or deep deletion, structural variant) in both, tissue only, plasma only, or neither; a patient with several samples of a kind is altered there if any is. Positive percent agreement is both ÷ altered in tissue (tissue the reference), with a 95% Wilson interval; overall concordance is (both + neither) ÷ pairs. The genes are the 15 most altered in the paired patients, not chosen in advance. Clonal-haematopoiesis filter: plasma mutation calls in DNMT3A, TET2, ASXL1, PPM1D, JAK2, SF3B1, SRSF2, U2AF1, CBL, GNB1, GNAS, IDH2, or seen in the matched normal (white-cell) sample — at least 2 alternate reads there, at an allele fraction at least 0.3 × the plasma's are left out of every count below (copy-number and structural calls are kept; tissue calls are never filtered). all patients — 83 plasma mutation calls of 2,024 removed (41 by gene, 44 by the normal), in 51 patients; ctDNA detected 453 (73.4%) against 457 (74.1%) unfiltered. The gene rule also removes a tumour's own calls in those genes (GNAS, IDH2, SF3B1 and others can be tumour drivers); the allele-fraction ratio keeps calls whose normal reads are tumour DNA in the white-cell sample (far below the plasma's fraction), and a CH clone at very low fraction in blood can still pass; TP53, ATM and CHEK2 are not on the default list because they are tumour drivers as often as CH genes (Razavi et al. 2019; Bolton et al. 2020). Tissue-only calls remain those of a tumour shedding too little DNA. ctDNA detected means any alteration (mutation, amplification or deep deletion, structural variant) called in some plasma sample, over every gene the plasma panel covers. Survival is OS_MONTHS / OS_STATUS as each study records them, from its own origin, which need not be the plasma draw; when the draw comes after that origin the comparison carries immortal-time bias. diff --git a/test/snapshots/ctdna.json b/test/snapshots/ctdna.json new file mode 100644 index 0000000..c05f8d2 --- /dev/null +++ b/test/snapshots/ctdna.json @@ -0,0 +1,437 @@ +{ + "study": "nsclc_ctdx_msk_2022", + "cohorts": { + "all patients": { + "count": 1127, + "text": "all patients in the study", + "studies": [ + "nsclc_ctdx_msk_2022" + ] + } + }, + "views": [ + { + "kind": "ctdna", + "cohorts": [ + "all patients" + ], + "ctdna": { + "attribute": "SAMPLE_CLASS", + "plasma": [ + "cfDNA", + "ctDNA" + ], + "tissue": [ + "Tumor" + ], + "plasmaPanels": [], + "withinDays": null, + "measure": "any", + "panels": [ + { + "cohort": "all patients", + "plasmaPatients": 1119, + "tissuePatients": 690, + "patients": 682, + "plasmaSamples": 1158, + "tissueSamples": 945, + "detected": 759, + "detectedLow": 0.650344270459499, + "detectedHigh": 0.705004204488215, + "genes": [ + { + "gene": "EGFR", + "pairs": 682, + "both": 151, + "tissueOnly": 84, + "plasmaOnly": 7, + "neither": 440, + "ppa": 0.6425531914893617, + "ppaLow": 0.5794381844454329, + "ppaHigh": 0.7010826276900307, + "concordance": 0.8665689149560117 + }, + { + "gene": "KRAS", + "pairs": 682, + "both": 119, + "tissueOnly": 66, + "plasmaOnly": 14, + "neither": 483, + "ppa": 0.6432432432432432, + "ppaLow": 0.5719432697099277, + "ppaHigh": 0.7087154390872955, + "concordance": 0.8826979472140762 + }, + { + "gene": "TP53", + "pairs": 682, + "both": 260, + "tissueOnly": 121, + "plasmaOnly": 33, + "neither": 268, + "ppa": 0.6824146981627297, + "ppaLow": 0.6340465914989983, + "ppaHigh": 0.7271411049252663, + "concordance": 0.7741935483870968 + }, + { + "gene": "STK11", + "pairs": 682, + "both": 28, + "tissueOnly": 65, + "plasmaOnly": 7, + "neither": 582, + "ppa": 0.3010752688172043, + "ppaLow": 0.21726302757387256, + "ppaHigh": 0.40066920464800065, + "concordance": 0.8944281524926686 + }, + { + "gene": "KEAP1", + "pairs": 682, + "both": 28, + "tissueOnly": 62, + "plasmaOnly": 5, + "neither": 587, + "ppa": 0.3111111111111111, + "ppaLow": 0.22485854647009124, + "ppaHigh": 0.41282824540976554, + "concordance": 0.9017595307917888 + }, + { + "gene": "PIK3CA", + "pairs": 682, + "both": 14, + "tissueOnly": 30, + "plasmaOnly": 9, + "neither": 629, + "ppa": 0.3181818181818182, + "ppaLow": 0.19999307710853423, + "ppaHigh": 0.4655689606218224, + "concordance": 0.9428152492668622 + }, + { + "gene": "BRAF", + "pairs": 682, + "both": 15, + "tissueOnly": 21, + "plasmaOnly": 9, + "neither": 637, + "ppa": 0.4166666666666667, + "ppaLow": 0.2714056205144606, + "ppaHigh": 0.5779974842512834, + "concordance": 0.9560117302052786 + }, + { + "gene": "MET", + "pairs": 682, + "both": 25, + "tissueOnly": 62, + "plasmaOnly": 6, + "neither": 589, + "ppa": 0.28735632183908044, + "ppaLow": 0.2028572763917612, + "ppaHigh": 0.38983970932010603, + "concordance": 0.9002932551319648 + }, + { + "gene": "ERBB2", + "pairs": 682, + "both": 16, + "tissueOnly": 32, + "plasmaOnly": 13, + "neither": 621, + "ppa": 0.3333333333333333, + "ppaLow": 0.21676780694818565, + "ppaHigh": 0.4745989029405201, + "concordance": 0.9340175953079178 + }, + { + "gene": "ALK", + "pairs": 682, + "both": 24, + "tissueOnly": 38, + "plasmaOnly": 5, + "neither": 615, + "ppa": 0.3870967741935484, + "ppaLow": 0.27584645167113864, + "ppaHigh": 0.5115215646098165, + "concordance": 0.9369501466275659 + } + ], + "survival": { + "curves": [ + { + "label": "ctDNA detected", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.26, + "s": 0.9987 + }, + { + "t": 0.49, + "s": 0.9973 + }, + { + "t": 0.72, + "s": 0.996 + }, + { + "t": 0.79, + "s": 0.9946 + }, + { + "t": 0.89, + "s": 0.9932 + }, + { + "t": 0.95, + "s": 0.989 + }, + { + "t": 1.05, + "s": 0.9876 + }, + { + "t": 1.12, + "s": 0.9862 + }, + { + "t": 1.15, + "s": 0.9848 + }, + { + "t": 1.25, + "s": 0.9833 + }, + { + "t": 1.28, + "s": 0.9805 + }, + { + "t": 1.31, + "s": 0.979 + }, + { + "t": 1.35, + "s": 0.9776 + }, + { + "t": 1.38, + "s": 0.9747 + }, + { + "t": 1.48, + "s": 0.9718 + }, + { + "t": 1.51, + "s": 0.9704 + }, + { + "t": 1.54, + "s": 0.9689 + }, + { + "t": 1.58, + "s": 0.9675 + }, + { + "t": 1.61, + "s": 0.9617 + }, + { + "…": "344 more", + "sha256": "7f8fb06e17003515" + } + ], + "median": 21.75, + "n": 759, + "events": 459, + "atRisk": [ + 759, + 706, + 639, + 598, + 567, + 540, + 516, + 499, + 482, + 460, + 441, + 426, + 406, + 386, + 375, + 363, + 351, + 337, + 327, + 308, + { + "…": "162 more", + "sha256": "7b8f060d222d71a7" + } + ] + }, + { + "label": "not detected", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1.02, + "s": 0.9971 + }, + { + "t": 1.61, + "s": 0.9941 + }, + { + "t": 1.74, + "s": 0.9911 + }, + { + "t": 1.84, + "s": 0.9851 + }, + { + "t": 2.37, + "s": 0.982 + }, + { + "t": 2.56, + "s": 0.9788 + }, + { + "t": 2.63, + "s": 0.9757 + }, + { + "t": 2.89, + "s": 0.9694 + }, + { + "t": 3.61, + "s": 0.9662 + }, + { + "t": 3.94, + "s": 0.963 + }, + { + "t": 4.14, + "s": 0.9598 + }, + { + "t": 4.24, + "s": 0.9565 + }, + { + "t": 4.27, + "s": 0.9533 + }, + { + "t": 4.34, + "s": 0.9501 + }, + { + "t": 4.4, + "s": 0.9469 + }, + { + "t": 5.39, + "s": 0.9436 + }, + { + "t": 5.65, + "s": 0.9404 + }, + { + "t": 6.27, + "s": 0.9371 + }, + { + "t": 6.47, + "s": 0.9338 + }, + { + "…": "126 more", + "sha256": "daec6bcc54f8756e" + } + ], + "median": 41.46, + "n": 360, + "events": 154, + "atRisk": [ + 360, + 347, + 324, + 307, + 299, + 294, + 288, + 279, + 277, + 271, + 266, + 255, + 244, + 237, + 234, + 224, + 219, + 211, + 204, + 198, + { + "…": "135 more", + "sha256": "923ebe10daaf79c0" + } + ] + } + ], + "logrank": { + "chi2": 49.3324, + "p": 2.1606230341780607e-12, + "df": 1, + "observed": [ + 458, + 154 + ], + "expected": [ + 373.8, + 238.2 + ], + "variance": 143.8416, + "strata": 1 + }, + "missing": 0 + } + } + ] + }, + "participation": { + "studies": [ + { + "study": "nsclc_ctdx_msk_2022", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Plasma is SAMPLE_CLASS = cfDNA/ctDNA, tissue SAMPLE_CLASS = Tumor. all patients: 1,119 patients with plasma, 690 patients with tissue, 682 patients with both (1,158 plasma and 945 tissue samples); ctDNA detected in 759 of the plasma patients (67.8%). Plasma and tissue are paired by patient whenever they were drawn, not by date — the tumour may have changed between them. Per gene, counting only patients whose plasma AND tissue panels both cover it, any alteration (mutation, amplification or deep deletion, structural variant) in both, tissue only, plasma only, or neither; a patient with several samples of a kind is altered there if any is. Positive percent agreement is both ÷ altered in tissue (tissue the reference), with a 95% Wilson interval; overall concordance is (both + neither) ÷ pairs. Plasma-only calls include clonal haematopoiesis (TP53, DNMT3A, TET2, PPM1D…) when the assay does not subtract white-cell DNA, and tissue-only calls a tumour shedding too little DNA — both are biology of the assay, not error. ctDNA detected means any alteration (mutation, amplification or deep deletion, structural variant) called in some plasma sample, over every gene the plasma panel covers. Survival is OS_MONTHS / OS_STATUS as each study records them, from its own origin, which need not be the plasma draw; when the draw comes after that origin the comparison carries immortal-time bias. " + } + ] +} diff --git a/test/snapshots/ctdna.sql b/test/snapshots/ctdna.sql new file mode 100644 index 0000000..b870ab1 --- /dev/null +++ b/test/snapshots/ctdna.sql @@ -0,0 +1,116 @@ +-- cohort all patients +-- nsclc_ctdx_msk_2022 +SELECT DISTINCT replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022' + +-- view 0 ctdna: ctdna:funnel:all patients · nsclc_ctdx_msk_2022 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +det AS ( + SELECT DISTINCT s.pid AS pid FROM snapshot_db.genomic_event_derived g + INNER JOIN sides s ON s.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND s.side = 'b' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') +) +SELECT (SELECT uniqExactIf(pid, side = 'b') FROM classed) AS plasma_all, (SELECT uniqExactIf(pid, side = 'a') FROM classed) AS tissue_all, + (SELECT uniqExactIf(pid, side = 'b') FROM sides) AS plasma, (SELECT uniqExactIf(pid, side = 'a') FROM sides) AS tissue, + (SELECT count() FROM paired) AS both, (SELECT count() FROM det) AS detected, + (SELECT countIf(side = 'b') FROM ps) AS plasma_samples, (SELECT countIf(side = 'a') FROM ps) AS tissue_samples, + toUInt64(0) AS dated_samples +FROM system.one + +-- view 0 ctdna: ctdna:genes:all patients · nsclc_ctdx_msk_2022 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +alt AS ( + SELECT g.hugo_gene_symbol AS gene, ps.pid AS pid, ps.side AS side, 1 AS hit + FROM snapshot_db.genomic_event_derived g + INNER JOIN ps ON ps.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND g.hugo_gene_symbol IN ('EGFR','KRAS','TP53','STK11','KEAP1','PIK3CA','BRAF','MET','ERBB2','ALK') AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM ps) + GROUP BY gene, pid, side +), +cov AS ( + SELECT DISTINCT pg.gene AS gene, ps.pid AS pid, ps.side AS side + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = sp.gene_panel_id AND pg.gene IN ('EGFR','KRAS','TP53','STK11','KEAP1','PIK3CA','BRAF','MET','ERBB2','ALK') + INNER JOIN ps ON ps.suid = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'nsclc_ctdx_msk_2022' + AND sp.alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') +), +per AS ( + SELECT gene, pid, max(side = 'a') AS pa, max(side = 'b') AS pb, + max(side = 'a' AND hit > 0) AS aa, max(side = 'b' AND hit > 0) AS ab + FROM (SELECT gene, pid, side, hit FROM alt UNION ALL SELECT gene, pid, side, 0 AS hit FROM cov) + GROUP BY gene, pid +) +SELECT gene, count() AS pairs, countIf(aa AND ab) AS both, countIf(aa AND NOT ab) AS a_only, countIf(ab AND NOT aa) AS b_only +FROM per WHERE pa AND pb GROUP BY gene + +-- view 0 ctdna: ctdna:survival:all patients · nsclc_ctdx_msk_2022 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022'), +cls AS ( + SELECT sample_unique_id AS suid, any(lowerUTF8(trimBoth(attribute_value))) AS v + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND type = 'sample' AND attribute_name = 'SAMPLE_CLASS' + AND replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') IN (SELECT pid FROM cohort) + GROUP BY suid +), +classed AS ( + SELECT sd.sample_unique_id AS suid, sd.patient_stable_id AS pid, sd.sample_stable_id AS sample, + multiIf(cls.v IN (lowerUTF8(trimBoth('cfDNA')),lowerUTF8(trimBoth('ctDNA'))), 'b', cls.v IN (lowerUTF8(trimBoth('Tumor'))), 'a', '') AS side + FROM snapshot_db.sample_derived sd LEFT JOIN cls ON cls.suid = sd.sample_unique_id + WHERE sd.cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND sd.patient_stable_id IN (SELECT pid FROM cohort) +), +sides AS (SELECT suid, pid, side FROM classed WHERE side != ''), +paired AS (SELECT pid FROM sides GROUP BY pid HAVING countIf(side = 'a') > 0 AND countIf(side = 'b') > 0), +ps AS (SELECT suid, pid, side FROM sides WHERE pid IN (SELECT pid FROM paired)), +det AS ( + SELECT DISTINCT s.pid AS pid FROM snapshot_db.genomic_event_derived g + INNER JOIN sides s ON s.suid = g.sample_unique_id + WHERE g.cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND s.side = 'b' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sides WHERE side = 'b') +), +os AS ( + SELECT replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') AS pid, if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead, replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') IN (SELECT pid FROM det) AS detected + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'nsclc_ctdx_msk_2022' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'nsclc_ctdx_msk_2022_', '') IN (SELECT pid FROM sides WHERE side = 'b') + GROUP BY pid +) +SELECT detected, round(months, 2) AS t, dead, count() AS n +FROM os WHERE months IS NOT NULL AND months >= 0 GROUP BY detected, t, dead ORDER BY t diff --git a/test/snapshots/ctdna.svg b/test/snapshots/ctdna.svg new file mode 100644 index 0000000..af0fee9 --- /dev/null +++ b/test/snapshots/ctdna.svg @@ -0,0 +1,2 @@ + +ctDNA vs tissuetissue onlybothplasma onlyall patients682 patients with plasma and tissue · ctDNA detected in 759 of 1,119 with plasma (68%)PPA [95% CI]concordantpairsEGFREGFR — 84 patients in tissue only84EGFR — 151 patients in both151EGFR — 7 patients in plasma only64% [58%–70%]87%682KRASKRAS — 66 patients in tissue only66KRAS — 119 patients in both119KRAS — 14 patients in plasma only1464% [57%–71%]88%682TP53TP53 — 121 patients in tissue only121TP53 — 260 patients in both260TP53 — 33 patients in plasma only3368% [63%–73%]77%682STK11STK11 — 65 patients in tissue only65STK11 — 28 patients in both28STK11 — 7 patients in plasma only30% [22%–40%]89%682KEAP1KEAP1 — 62 patients in tissue only62KEAP1 — 28 patients in both28KEAP1 — 5 patients in plasma only31% [22%–41%]90%682PIK3CAPIK3CA — 30 patients in tissue only30PIK3CA — 14 patients in both14PIK3CA — 9 patients in plasma only932% [20%–47%]94%682BRAFBRAF — 21 patients in tissue only21BRAF — 15 patients in both15BRAF — 9 patients in plasma only942% [27%–58%]96%682METMET — 62 patients in tissue only62MET — 25 patients in both25MET — 6 patients in plasma only29% [20%–39%]90%682ERBB2ERBB2 — 32 patients in tissue only32ERBB2 — 16 patients in both16ERBB2 — 13 patients in plasma only1333% [22%–47%]93%682ALKALK — 38 patients in tissue only38ALK — 24 patients in both24ALK — 5 patients in plasma only39% [28%–51%]94%6820100200300400500patients altered in plasma or tissueOverall survival by ctDNA detectedctDNA detected — 759 patients, 459 deaths, median 21.8 monot detected — 360 patients, 154 deaths, median 41.5 mo0%25%50%75%100%Survival probability024487296120144168Monthslog-rank χ² = 49.3 · p = 2.2e-12Plasma is SAMPLE_CLASS = cfDNA/ctDNA, tissue SAMPLE_CLASS = Tumor. all patients: 1,119 patients with plasma, 690 patients with tissue, 682 patients with both(1,158 plasma and 945 tissue samples); ctDNA detected in 759 of the plasma patients (67.8%). Plasma and tissue are paired by patient whenever they were drawn, notby date — the tumour may have changed between them. Per gene, counting only patients whose plasma AND tissue panels both cover it, any alteration (mutation,amplification or deep deletion, structural variant) in both, tissue only, plasma only, or neither; a patient with several samples of a kind is altered there if any is. Positivepercent agreement is both ÷ altered in tissue (tissue the reference), with a 95% Wilson interval; overall concordance is (both + neither) ÷ pairs. Plasma-only calls includeclonal haematopoiesis (TP53, DNMT3A, TET2, PPM1D…) when the assay does not subtract white-cell DNA, and tissue-only calls a tumour shedding too little DNA — bothare biology of the assay, not error. ctDNA detected means any alteration (mutation, amplification or deep deletion, structural variant) called in some plasma sample, overevery gene the plasma panel covers. Survival is OS_MONTHS / OS_STATUS as each study records them, from its own origin, which need not be the plasma draw; whenthe draw comes after that origin the comparison carries immortal-time bias. diff --git a/test/snapshots/ctdna.txt b/test/snapshots/ctdna.txt new file mode 100644 index 0000000..b804830 --- /dev/null +++ b/test/snapshots/ctdna.txt @@ -0,0 +1,19 @@ +Analysis over nsclc_ctdx_msk_2022 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- all patients: 1,127 patients — all patients in the study + +ctDNA vs tissue (plasma SAMPLE_CLASS = cfDNA/ctDNA, tissue Tumor; any alteration; both panels cover the gene): + all patients (682 patients with both; ctDNA detected in 759 of 1,119 with plasma, 68% [95% CI 65%–71%]): + EGFR: both 151, tissue-only 84, plasma-only 7, neither 440 of 682; PPA 64% [58%–70%], concordance 87% + KRAS: both 119, tissue-only 66, plasma-only 14, neither 483 of 682; PPA 64% [57%–71%], concordance 88% + TP53: both 260, tissue-only 121, plasma-only 33, neither 268 of 682; PPA 68% [63%–73%], concordance 77% + STK11: both 28, tissue-only 65, plasma-only 7, neither 582 of 682; PPA 30% [22%–40%], concordance 89% + KEAP1: both 28, tissue-only 62, plasma-only 5, neither 587 of 682; PPA 31% [22%–41%], concordance 90% + PIK3CA: both 14, tissue-only 30, plasma-only 9, neither 629 of 682; PPA 32% [20%–47%], concordance 94% + BRAF: both 15, tissue-only 21, plasma-only 9, neither 637 of 682; PPA 42% [27%–58%], concordance 96% + MET: both 25, tissue-only 62, plasma-only 6, neither 589 of 682; PPA 29% [20%–39%], concordance 90% + ERBB2: both 16, tissue-only 32, plasma-only 13, neither 621 of 682; PPA 33% [22%–47%], concordance 93% + ALK: both 24, tissue-only 38, plasma-only 5, neither 615 of 682; PPA 39% [28%–51%], concordance 94% + OS: ctDNA detected n=759, 459 deaths, median 21.8mo; not detected n=360, 154 deaths, median 41.5mo; log-rank p=2.2e-12 + CAVEAT (tell the user): Plasma is SAMPLE_CLASS = cfDNA/ctDNA, tissue SAMPLE_CLASS = Tumor. all patients: 1,119 patients with plasma, 690 patients with tissue, 682 patients with both (1,158 plasma and 945 tissue samples); ctDNA detected in 759 of the plasma patients (67.8%). Plasma and tissue are paired by patient whenever they were drawn, not by date — the tumour may have changed between them. Per gene, counting only patients whose plasma AND tissue panels both cover it, any alteration (mutation, amplification or deep deletion, structural variant) in both, tissue only, plasma only, or neither; a patient with several samples of a kind is altered there if any is. Positive percent agreement is both ÷ altered in tissue (tissue the reference), with a 95% Wilson interval; overall concordance is (both + neither) ÷ pairs. Plasma-only calls include clonal haematopoiesis (TP53, DNMT3A, TET2, PPM1D…) when the assay does not subtract white-cell DNA, and tissue-only calls a tumour shedding too little DNA — both are biology of the assay, not error. ctDNA detected means any alteration (mutation, amplification or deep deletion, structural variant) called in some plasma sample, over every gene the plasma panel covers. Survival is OS_MONTHS / OS_STATUS as each study records them, from its own origin, which need not be the plasma draw; when the draw comes after that origin the comparison carries immortal-time bias. diff --git a/test/snapshots/cutpoint.json b/test/snapshots/cutpoint.json new file mode 100644 index 0000000..12c61fa --- /dev/null +++ b/test/snapshots/cutpoint.json @@ -0,0 +1,621 @@ +{ + "study": "tmb_mskcc_2018", + "cohorts": { + "all patients": { + "count": 1661, + "text": "all patients in the study", + "studies": [ + "tmb_mskcc_2018" + ] + } + }, + "views": [ + { + "kind": "cutpoint", + "cohorts": [ + "all patients" + ], + "cutpoint": { + "attribute": "TMB_NONSYNONYMOUS", + "endpoint": "OS", + "permutations": 1000, + "seed": 1, + "noValue": 0, + "n": 1661, + "events": 832, + "cut": 14.4190189078, + "lowShare": 0.8061408789885611, + "maxStat": 67.80508751969889, + "permP": 0.000999000999000999, + "naiveChi2": 59.9946, + "naiveP": 9.511889775457744e-15, + "critical95": 8.252515601462035, + "hr": { + "hr": 0.44769076519084294, + "ciLow": 0.36308135201586145, + "ciHigh": 0.5520168417473744, + "p": 5.503307533430497e-14 + }, + "low": { + "n": 1339, + "events": 732, + "median": 15 + }, + "high": { + "n": 322, + "events": 100, + "median": 47 + }, + "candidates": [ + { + "cut": 1.72939618997, + "stat": 1.805 + }, + { + "cut": 1.95743939523, + "stat": 2.4641 + }, + { + "cut": 2.2183106012, + "stat": 4.1498 + }, + { + "cut": 2.59409428495, + "stat": 2.2533 + }, + { + "cut": 2.93615909284, + "stat": 4.3318 + }, + { + "cut": 3.32746590179, + "stat": 6.9348 + }, + { + "cut": 3.45879237993, + "stat": 4.7173 + }, + { + "cut": 3.91487879046, + "stat": 2.7407 + }, + { + "cut": 4.32349047492, + "stat": 2.7978 + }, + { + "cut": 4.4366212023900005, + "stat": 3.3435 + }, + { + "cut": 4.89359848807, + "stat": 7.0967 + }, + { + "cut": 5.1881885699, + "stat": 6.8979 + }, + { + "cut": 5.54577650299, + "stat": 12.9792 + }, + { + "cut": 5.87231818569, + "stat": 19.7778 + }, + { + "cut": 6.05288666489, + "stat": 18.1524 + }, + { + "cut": 6.65493180359, + "stat": 22.226 + }, + { + "cut": 6.8510378833, + "stat": 23.5099 + }, + { + "cut": 6.9175847598699995, + "stat": 23.208 + }, + { + "cut": 7.76408710418, + "stat": 30.8829 + }, + { + "cut": 7.78228285485, + "stat": 32.2437 + }, + { + "…": "53 more", + "sha256": "317e891d3f189749" + } + ], + "curves": { + "low": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0, + "s": 0.9866 + }, + { + "t": 1, + "s": 0.9377 + }, + { + "t": 2, + "s": 0.8797 + }, + { + "t": 3, + "s": 0.8313 + }, + { + "t": 4, + "s": 0.783 + }, + { + "t": 5, + "s": 0.7445 + }, + { + "t": 6, + "s": 0.7153 + }, + { + "t": 7, + "s": 0.6803 + }, + { + "t": 8, + "s": 0.6532 + }, + { + "t": 9, + "s": 0.621 + }, + { + "t": 10, + "s": 0.5926 + }, + { + "t": 11, + "s": 0.5627 + }, + { + "t": 12, + "s": 0.5436 + }, + { + "t": 13, + "s": 0.5201 + }, + { + "t": 14, + "s": 0.5025 + }, + { + "t": 15, + "s": 0.485 + }, + { + "t": 16, + "s": 0.4713 + }, + { + "t": 17, + "s": 0.4594 + }, + { + "t": 18, + "s": 0.4455 + }, + { + "…": "29 more", + "sha256": "9e1aeded113bf9e0" + } + ], + "median": 15, + "n": 1339, + "events": 732, + "atRisk": [ + 1339, + 1313, + 1229, + 1126, + 1032, + 957, + 891, + 839, + 777, + 732, + 678, + 633, + 590, + 554, + 503, + 461, + 422, + 396, + 366, + 335, + { + "…": "59 more", + "sha256": "8220a954778bf568" + } + ] + }, + "high": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0, + "s": 0.9876 + }, + { + "t": 1, + "s": 0.9533 + }, + { + "t": 2, + "s": 0.9249 + }, + { + "t": 3, + "s": 0.9056 + }, + { + "t": 4, + "s": 0.8923 + }, + { + "t": 5, + "s": 0.872 + }, + { + "t": 6, + "s": 0.8582 + }, + { + "t": 7, + "s": 0.8367 + }, + { + "t": 8, + "s": 0.8259 + }, + { + "t": 9, + "s": 0.8183 + }, + { + "t": 10, + "s": 0.8106 + }, + { + "t": 11, + "s": 0.7947 + }, + { + "t": 12, + "s": 0.7825 + }, + { + "t": 13, + "s": 0.7619 + }, + { + "t": 14, + "s": 0.7409 + }, + { + "t": 15, + "s": 0.7234 + }, + { + "t": 16, + "s": 0.7187 + }, + { + "t": 18, + "s": 0.7134 + }, + { + "t": 19, + "s": 0.6966 + }, + { + "…": "13 more", + "sha256": "05431811010d3568" + } + ], + "median": 47, + "n": 322, + "events": 100, + "atRisk": [ + 322, + 317, + 302, + 287, + 274, + 263, + 253, + 240, + 231, + 219, + 211, + 204, + 196, + 190, + 181, + 170, + 153, + 143, + 136, + 127, + { + "…": "61 more", + "sha256": "d1312df5a86d07ac" + } + ] + } + }, + "strata": { + "attribute": "CANCER_TYPE", + "minStratum": 100, + "rows": [ + { + "stratum": "Non-Small Cell Lung Cancer", + "n": 350, + "events": 219, + "cut": 18.5956742547, + "lowShare": 0.8742857142857143, + "maxStat": 22.174094811126363, + "permP": 0.000999000999000999, + "naiveChi2": 17.6105, + "naiveP": 0.00002710833832097831, + "critical95": 8.551944700574573, + "hr": { + "hr": 0.3337471069297766, + "ciLow": 0.19405006379733222, + "ciHigh": 0.5740123409612973, + "p": 0.00007298902907972714 + }, + "low": { + "n": 306, + "events": 205, + "median": 10 + }, + "high": { + "n": 44, + "events": 14, + "median": null + } + }, + { + "stratum": "Melanoma", + "n": 320, + "events": 125, + "cut": 9.78719697615, + "lowShare": 0.55, + "maxStat": 16.30033554313912, + "permP": 0.000999000999000999, + "naiveChi2": 16.2835, + "naiveP": 0.00005453584904242975, + "critical95": 8.354446956358276, + "hr": { + "hr": 0.465886667413295, + "ciLow": 0.3188770674284734, + "ciHigh": 0.6806710455029892, + "p": 0.00007862136169300631 + }, + "low": { + "n": 176, + "events": 85, + "median": 32 + }, + "high": { + "n": 144, + "events": 40, + "median": 58 + } + }, + { + "stratum": "Bladder Cancer", + "n": 215, + "events": 95, + "cut": 14.6998676147, + "lowShare": 0.7627906976744186, + "maxStat": 6.235270219152118, + "permP": 0.12087912087912088, + "naiveChi2": 5.5873, + "naiveP": 0.01809145502893181, + "critical95": 8.05497478320218, + "hr": { + "hr": 0.5358252872150625, + "ciLow": 0.3160451669437053, + "ciHigh": 0.9084421103336937, + "p": 0.020533026289892318 + }, + "low": { + "n": 164, + "events": 78, + "median": 15 + }, + "high": { + "n": 51, + "events": 17, + "median": null + } + }, + { + "stratum": "Renal Cell Carcinoma", + "n": 151, + "events": 58, + "cut": 1.1091553006, + "lowShare": 0.11920529801324503, + "maxStat": 2.8045961162246122, + "permP": 0.4405594405594406, + "naiveChi2": 3.171, + "naiveP": 0.07495495934424953, + "critical95": 6.807368752792455, + "hr": { + "hr": 0.5414144878859748, + "ciLow": 0.2731447049735918, + "ciHigh": 1.073166136320207, + "p": 0.07880200901995046 + }, + "low": { + "n": 18, + "events": 10, + "median": 26 + }, + "high": { + "n": 133, + "events": 48, + "median": 50 + } + }, + { + "stratum": "Head and Neck Cancer", + "n": 139, + "events": 79, + "cut": 8.80847727853, + "lowShare": 0.7913669064748201, + "maxStat": 8.843128424411308, + "permP": 0.027972027972027972, + "naiveChi2": 7.8836, + "naiveP": 0.0049886295111135105, + "critical95": 7.768648543331955, + "hr": { + "hr": 0.3844419619920401, + "ciLow": 0.19114546010753247, + "ciHigh": 0.7732102141329646, + "p": 0.007331421774737635 + }, + "low": { + "n": 110, + "events": 70, + "median": 9 + }, + "high": { + "n": 29, + "events": 9, + "median": 25 + } + }, + { + "stratum": "Esophagogastric Cancer", + "n": 126, + "events": 57, + "cut": 9.98239770538, + "lowShare": 0.8571428571428571, + "maxStat": 3.026418920803918, + "permP": 0.47652347652347654, + "naiveChi2": 2.5434, + "naiveP": 0.11075818236686161, + "critical95": 7.952068583418686, + "hr": { + "hr": 0.5077848368231322, + "ciLow": 0.21476372358116003, + "ciHigh": 1.20060053070394, + "p": 0.12269513187246597 + }, + "low": { + "n": 108, + "events": 51, + "median": 13 + }, + "high": { + "n": 18, + "events": 6, + "median": 21 + } + }, + { + "stratum": "Glioma", + "n": 117, + "events": 87, + "cut": 2.93615909284, + "lowShare": 0.48717948717948717, + "maxStat": 2.1403219064907852, + "permP": 0.6203796203796204, + "naiveChi2": 2.1949, + "naiveP": 0.13846684710609466, + "critical95": 7.052061850962871, + "hr": { + "hr": 1.369457347183215, + "ciLow": 0.8949530052964434, + "ciHigh": 2.09554402818378, + "p": 0.14744356689036148 + }, + "low": { + "n": 57, + "events": 43, + "median": 15 + }, + "high": { + "n": 60, + "events": 44, + "median": 12 + } + }, + { + "stratum": "Colorectal Cancer", + "n": 110, + "events": 45, + "cut": 11.091553006, + "lowShare": 0.6454545454545455, + "maxStat": 12.811504932575595, + "permP": 0.003996003996003996, + "naiveChi2": 12.3245, + "naiveP": 0.0004470502103430181, + "critical95": 7.836548798630535, + "hr": { + "hr": 0.2587314398271425, + "ciLow": 0.11486202579730972, + "ciHigh": 0.5828032153390259, + "p": 0.0011021384463524536 + }, + "low": { + "n": 71, + "events": 37, + "median": 12 + }, + "high": { + "n": 39, + "events": 8, + "median": null + } + } + ], + "omitted": { + "values": 3, + "patients": 133 + } + } + }, + "participation": { + "studies": [ + { + "study": "tmb_mskcc_2018", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": true, + "aggregateOnly": [] + }, + "caveat": "The cut was chosen from these patients to separate their OS as much as possible, so the split is optimistic in-sample: its naive log-rank p (9.5e-15) ignores the search and overstates the evidence, and the cut needs validation in an independent cohort before it is used. Scanned: 73 candidate cuts — the distinct values of TMB_NONSYNONYMOUS leaving between 10% and 90% of patients at or below them (at most 100, evenly spaced) — by the maximally selected standardized log-rank statistic (Lausen & Schumacher; R maxstat). The corrected p is from 1000 permutations of the marker over the patients (seed 1): the share whose own best cut does as well. High is TMB_NONSYNONYMOUS > 14.42 (19.4% of patients). TMB_NONSYNONYMOUS is the patient's value, or for a sample attribute the value of the patient's first sample by sample id; 0 patients with a survival time and no numeric value are left out. The status '1:…' is an event, anything else censored; times from each study's own baseline. Per CANCER_TYPE: the largest 8 values with at least 100 patients each get their own scan and permutation test (same seed); 3 other values (133 patients) are not scanned. Each stratum's cut is as optimistic as the overall one. Computed from per-patient rows inside each patient-level study." + } + ] +} diff --git a/test/snapshots/cutpoint.sql b/test/snapshots/cutpoint.sql new file mode 100644 index 0000000..28a22be --- /dev/null +++ b/test/snapshots/cutpoint.sql @@ -0,0 +1,29 @@ +-- cohort all patients +-- tmb_mskcc_2018 +SELECT DISTINCT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' + +-- view 0 cutpoint: cutpoint:rows · tmb_mskcc_2018 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018'), +os AS ( + SELECT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +val AS ( + SELECT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(argMinIf(attribute_value, sample_unique_id, attribute_name = 'TMB_NONSYNONYMOUS')), nan)), toFloat64OrNull(argMinIf(attribute_value, sample_unique_id, attribute_name = 'TMB_NONSYNONYMOUS')), NULL) AS v, + argMinIf(trimBoth(attribute_value), sample_unique_id, attribute_name = 'CANCER_TYPE') AS s + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND attribute_name IN ('TMB_NONSYNONYMOUS', 'CANCER_TYPE') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT os.months AS m, if(os.status LIKE '1%', 1, 0) AS ev, val.v AS v, val.s AS s +FROM os LEFT JOIN val ON val.pid = os.pid +WHERE os.months IS NOT NULL AND os.months >= 0 diff --git a/test/snapshots/cutpoint.svg b/test/snapshots/cutpoint.svg new file mode 100644 index 0000000..ae16f24 --- /dev/null +++ b/test/snapshots/cutpoint.svg @@ -0,0 +1,2 @@ + +Cut-point — TMB_NONSYNONYMOUS on OSLog-rank statistic at each candidate cut of TMB_NONSYNONYMOUS020406080χ² (standardized)051015202530TMB_NONSYNONYMOUS cut (high is above it)permutation 95%: 8.25cut 14.4 · χ² 67.8best cut TMB_NONSYNONYMOUS > 14.4 (322 high, 1,339 low): permutation-corrected p = 1e-3 (1,000 permutations, seed 1) · naive log-rankp = 9.5e-15 — the naive p ignores the searchhigh vs low HR 0.45 [95% CI 0.36–0.55]OS by the chosen cut0%25%50%75%100%Survival probability0244872Months (OS)Number at risklow (≤ 14.4)low (≤ 14.4): 1,339 at risk at 0 months1,339low (≤ 14.4): 224 at risk at 24 months224low (≤ 14.4): 31 at risk at 48 months31low (≤ 14.4): 1 at risk at 72 months1high (> 14.4)high (> 14.4): 322 at risk at 0 months322high (> 14.4): 88 at risk at 24 months88high (> 14.4): 10 at risk at 48 months10high (> 14.4): 3 at risk at 72 months3low (≤ 14.4)median 15 mo · n = 1,339 · 732 eventshigh (> 14.4)median 47 mo · n = 322 · 100 eventsA cut per CANCER_TYPECANCER_TYPEn · eventscuthighHR [95% CI]corrected pnaive pNon-Small Cell Lung Cancer350 · 21918.6440.33 [0.19–0.57]p = 1e-3p = 2.7e-5Melanoma320 · 1259.791440.47 [0.32–0.68]p = 1e-3p = 5.5e-5Bladder Cancer215 · 9514.7510.54 [0.32–0.91]p = 0.12p = 0.018Renal Cell Carcinoma151 · 581.111330.54 [0.27–1.07]p = 0.44p = 0.075Head and Neck Cancer139 · 798.81290.38 [0.19–0.77]p = 0.028p = 0.005Esophagogastric Cancer126 · 579.98180.51 [0.21–1.20]p = 0.48p = 0.11Glioma117 · 872.94601.37 [0.89–2.10]p = 0.62p = 0.14Colorectal Cancer110 · 4511.1390.26 [0.11–0.58]p = 0.004p = 4.5e-41,661 patients with a value, 832 events; 0 with a survival time but no value. 3 CANCER_TYPE values (133 patients) not scanned. The cut was chosen fromthese patients to separate their OS as much as possible, so the split is optimistic in-sample: its naive log-rank p (9.5e-15) ignores the search and overstatesthe evidence, and the cut needs validation in an independent cohort before it is used. Scanned: 73 candidate cuts — the distinct values ofTMB_NONSYNONYMOUS leaving between 10% and 90% of patients at or below them (at most 100, evenly spaced) — by the maximally selectedstandardized log-rank statistic (Lausen & Schumacher; R maxstat). The corrected p is from 1000 permutations of the marker over the patients (seed 1): theshare whose own best cut does as well. High is TMB_NONSYNONYMOUS > 14.42 (19.4% of patients). TMB_NONSYNONYMOUS is the patient's value, or for asample attribute the value of the patient's first sample by sample id; 0 patients with a survival time and no numeric value are left out. The status '1:…' is anevent, anything else censored; times from each study's own baseline. Per CANCER_TYPE: the largest 8 values with at least 100 patients each get their ownscan and permutation test (same seed); 3 other values (133 patients) are not scanned. Each stratum's cut is as optimistic as the overall one. Computed fromper-patient rows inside each patient-level study. diff --git a/test/snapshots/cutpoint.txt b/test/snapshots/cutpoint.txt new file mode 100644 index 0000000..304ec66 --- /dev/null +++ b/test/snapshots/cutpoint.txt @@ -0,0 +1,18 @@ +Analysis over tmb_mskcc_2018 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- all patients: 1,661 patients — all patients in the study + +Cut-point of TMB_NONSYNONYMOUS on OS (maximally selected log-rank, 73 candidate cuts between the 10th and 90th percentiles; 1,661 patients, 832 events): + best cut TMB_NONSYNONYMOUS > 14.4190189078: high n=322, 100 events, median 47.0mo; low n=1339, 732 events, median 15.0mo; HR 0.45 (95% CI 0.36-0.55) + corrected p=0.0010 (1000 permutations, seed 1); naive log-rank p=9.5e-15, which ignores the search + per CANCER_TYPE: + Non-Small Cell Lung Cancer: n=350, cut 18.5956742547, high 44, HR 0.33, corrected p=0.0010, naive p=2.7e-5 + Melanoma: n=320, cut 9.78719697615, high 144, HR 0.47, corrected p=0.0010, naive p=5.5e-5 + Bladder Cancer: n=215, cut 14.6998676147, high 51, HR 0.54, corrected p=0.1209, naive p=0.0181 + Renal Cell Carcinoma: n=151, cut 1.1091553006, high 133, HR 0.54, corrected p=0.4406, naive p=0.0750 + Head and Neck Cancer: n=139, cut 8.80847727853, high 29, HR 0.38, corrected p=0.0280, naive p=0.0050 + Esophagogastric Cancer: n=126, cut 9.98239770538, high 18, HR 0.51, corrected p=0.4765, naive p=0.1108 + Glioma: n=117, cut 2.93615909284, high 60, HR 1.37, corrected p=0.6204, naive p=0.1385 + Colorectal Cancer: n=110, cut 11.091553006, high 39, HR 0.26, corrected p=0.0040, naive p=0.0004 + CAVEAT (tell the user): The cut was chosen from these patients to separate their OS as much as possible, so the split is optimistic in-sample: its naive log-rank p (9.5e-15) ignores the search and overstates the evidence, and the cut needs validation in an independent cohort before it is used. Scanned: 73 candidate cuts — the distinct values of TMB_NONSYNONYMOUS leaving between 10% and 90% of patients at or below them (at most 100, evenly spaced) — by the maximally selected standardized log-rank statistic (Lausen & Schumacher; R maxstat). The corrected p is from 1000 permutations of the marker over the patients (seed 1): the share whose own best cut does as well. High is TMB_NONSYNONYMOUS > 14.42 (19.4% of patients). TMB_NONSYNONYMOUS is the patient's value, or for a sample attribute the value of the patient's first sample by sample id; 0 patients with a survival time and no numeric value are left out. The status '1:…' is an event, anything else censored; times from each study's own baseline. Per CANCER_TYPE: the largest 8 values with at least 100 patients each get their own scan and permutation test (same seed); 3 other values (133 patients) are not scanned. Each stratum's cut is as optimistic as the overall one. Computed from per-patient rows inside each patient-level study. diff --git a/test/snapshots/drivernegative.json b/test/snapshots/drivernegative.json new file mode 100644 index 0000000..3132bbe --- /dev/null +++ b/test/snapshots/drivernegative.json @@ -0,0 +1,379 @@ +{ + "study": "msk_chord_2024", + "cohorts": { + "NSCLC": { + "count": 7809, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample)", + "studies": [ + "msk_chord_2024" + ] + } + }, + "views": [ + { + "kind": "drivernegative", + "cohorts": [ + "NSCLC" + ], + "drivernegative": { + "drivers": [ + "EGFR", + "KRAS", + "ALK", + "ROS1", + "BRAF", + "MET", + "RET", + "ERBB2", + "NTRK1", + "NTRK2", + "NTRK3" + ], + "alteration": "mutation", + "measure": "any", + "minAltered": 5, + "patients": 7809, + "sequenced": 7809, + "positive": 4911, + "negative": 2898, + "unprofiled": 0, + "tested": 487, + "untested": 7, + "significant": 95, + "rows": [ + { + "gene": "NF1", + "altered": [ + 358, + 244 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.3140376673397507, + "p": 5.951711756073328e-31, + "q": 2.8984836252077108e-28, + "cytoband": "17q11.2" + }, + { + "gene": "KMT2D", + "altered": [ + 370, + 259 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.2755324004681385, + "p": 1.2638262678950395e-30, + "q": 3.077416962324421e-28, + "cytoband": "12q13.12" + }, + { + "gene": "RBM10", + "altered": [ + 157, + 630 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": -1.2436280418572716, + "p": 7.874056406173726e-28, + "q": 1.2782218232688682e-25, + "cytoband": "Xp11.3" + }, + { + "gene": "TP53", + "altered": [ + 1709, + 2311 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.32559436484688875, + "p": 2.329362621384858e-24, + "q": 2.8359989915360645e-22, + "cytoband": "17p13.1" + }, + { + "gene": "NFE2L2", + "altered": [ + 161, + 77 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.8250895650580958, + "p": 4.372579799803402e-22, + "q": 4.258892725008514e-20, + "cytoband": "2q31.2" + }, + { + "gene": "CDKN2A", + "altered": [ + 295, + 251 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.9939868179368119, + "p": 1.024065950254084e-16, + "q": 8.312001962895649e-15, + "cytoband": "9p21.3" + }, + { + "gene": "RASA1", + "altered": [ + 124, + 75 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.4863368475293746, + "p": 3.264817215176029e-13, + "q": 2.2713799768438944e-11, + "cytoband": "5q14.3" + }, + { + "gene": "PTEN", + "altered": [ + 152, + 107 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.2674197546808188, + "p": 1.1272190130278383e-12, + "q": 6.861945741806965e-11, + "cytoband": "10q23.31" + }, + { + "gene": "KEAP1", + "altered": [ + 469, + 565 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.49229628285120675, + "p": 5.973021165418496e-9, + "q": 3.2320681195097863e-7, + "cytoband": "19p13.2" + }, + { + "gene": "ARID1A", + "altered": [ + 255, + 279 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.6311913526680507, + "p": 2.3386022541669852e-7, + "q": 0.000011388992977793218, + "cytoband": "1p36.11" + }, + { + "gene": "ANKRD11", + "altered": [ + 98, + 77 + ], + "profiled": [ + 2758, + 4726 + ], + "log2Ratio": 1.1249204758801736, + "p": 2.762577497288062e-7, + "q": 0.000012230684010720783, + "cytoband": "16q24.3" + }, + { + "gene": "MAP2K1", + "altered": [ + 51, + 27 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.6784970674464075, + "p": 4.1111432266614836e-7, + "q": 0.000016684389594867856, + "cytoband": "15q22.31" + }, + { + "gene": "U2AF1", + "altered": [ + 32, + 134 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": -1.305129962819392, + "p": 5.698189101457044e-7, + "q": 0.000021346293018535232, + "cytoband": "21q22.3" + }, + { + "gene": "NRAS", + "altered": [ + 46, + 24 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.6995586829742368, + "p": 0.000001289041170176372, + "q": 0.000044840217848278084, + "cytoband": "1p13.2" + }, + { + "gene": "CASP8", + "altered": [ + 43, + 22 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.7277923637031811, + "p": 0.0000020724852054888613, + "q": 0.00006728668633820503, + "cytoband": "2q33.1" + }, + { + "gene": "RB1", + "altered": [ + 195, + 208 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.6678498232468988, + "p": 0.0000022507333049473305, + "q": 0.00006850669496933437, + "cytoband": "13q14.2" + }, + { + "gene": "FBXW7", + "altered": [ + 94, + 78 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 1.0301458604537692, + "p": 0.0000029134996900037855, + "q": 0.00008346319700187314, + "cytoband": "4q31.3" + }, + { + "gene": "NOTCH1", + "altered": [ + 141, + 138 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.7919861232590046, + "p": 0.000003624359538115457, + "q": 0.00009805906083679042, + "cytoband": "9q34.3" + }, + { + "gene": "ERBB4", + "altered": [ + 169, + 181 + ], + "profiled": [ + 2898, + 4911 + ], + "log2Ratio": 0.6619927768373591, + "p": 0.000015604470491713567, + "q": 0.0003999672173402372, + "cytoband": "2q34" + }, + { + "gene": "PREX2", + "altered": [ + 137, + 139 + ], + "profiled": [ + 2288, + 3863 + ], + "log2Ratio": 0.734725635690561, + "p": 0.000017971972562761606, + "q": 0.0004219679588711926, + "cytoband": "8q13.2" + }, + { + "…": "280 more", + "sha256": "ea5fac18e1aec3a4" + } + ], + "labels": [ + "NF1", + "KMT2D", + "RBM10", + "TP53", + "NFE2L2", + "CDKN2A", + "RASA1", + "PTEN", + "KEAP1", + "ARID1A", + "ANKRD11", + "MAP2K1" + ] + }, + "participation": { + "studies": [ + { + "study": "msk_chord_2024", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Of the 7,809 patients of “NSCLC” sequenced for mutations, 4,911 (62.9%) carry an OncoKB driver (v7.5) — a called mutation, a high-level copy-number call or a structural variant the store calls oncogenic or likely oncogenic — in EGFR, KRAS, ALK, ROS1, BRAF, MET, RET, ERBB2, NTRK1, NTRK2, NTRK3; 2,898 (37.1%) carry none and were sequenced on a panel covering every one of those genes (or whole-exome): the driver-negative group. 0 (0.0%) carry none but were not profiled for every listed gene, so are neither — kept out of both sides, never counted as driver-negative. Coverage is read from the mutation panels; a fusion driver is called only where the study reports structural variants. Then every other gene's mutation frequency is compared between driver-negative and driver-positive patients, in PATIENTS over the patients profiled for that gene (a panel that lists it, or whole-exome; an off-panel call counts as measured), two-sided Fisher's exact test, Benjamini–Hochberg q over the 487 genes tested; 7 altered genes were not tested (fewer than 5 altered patients, or not profiled in a group). The log ratio is log2 of the driver-negative share over the driver-positive one. Driver-positive tumours are often mutually exclusive with other drivers by biology, so a gene enriched in the driver-negative group is a candidate alternative driver, not a proven one; smoking history and tumour mutation burden also differ between the groups and are not adjusted for. " + } + ] +} diff --git a/test/snapshots/drivernegative.sql b/test/snapshots/drivernegative.sql new file mode 100644 index 0000000..52ff216 --- /dev/null +++ b/test/snapshots/drivernegative.sql @@ -0,0 +1,149 @@ +-- cohort NSCLC +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) + +-- view 0 drivernegative: cytobands · msk_chord_2024 +SELECT g.hugo_gene_symbol AS gene, any(rgg.cytoband) AS cytoband FROM snapshot_db.gene g +JOIN snapshot_db.reference_genome_gene rgg ON rgg.entrez_gene_id = g.entrez_gene_id +WHERE rgg.reference_genome_id = (SELECT reference_genome_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_chord_2024') + AND g.hugo_gene_symbol IN ('NF1','KMT2D','RBM10','TP53','NFE2L2','CDKN2A','RASA1','PTEN','KEAP1','ARID1A','ANKRD11','MAP2K1','U2AF1','NRAS','CASP8','RB1','FBXW7','NOTCH1','ERBB4','PREX2','KMT2C','IRS2','PIK3CB','KMT2A','NOTCH3','KMT2B','COP1','PDGFRA','EPHB1','EP300','MAX','DICER1','EIF1AX','PIK3C3','SMARCA4','SUZ12','FGFR3','ZFHX3','PRKN','CREBBP','CUL3','KIT','FOXL2','RAF1','BRCA2','ATM','PBRM1','KDM6A','PTPRT','LATS2','LZTR1','SPEN','FAT1','YES1','TET1','FANCA','BRCA1','IRS1','RRAS','POLD1','PIK3CG','CSF3R','NEGR1','BRIP1','HRAS','ARID2','KDR','ARID1B','EZH2','INPPL1','NKX2-1','ARHGAP35','AURKA','PIK3C2G','STAT3','GNAQ','WHR1','WT1','ALOX12B','NFKBIA','RIT1','NSD2','GAB1','HGF','NSD3','AXL','PAK5','H3C10','RRAS2','SRSF2','TBX3','INPP4A','CBL','DAXX','PIK3R3','PGR','H1-2','JAK2','PHOX2B','DOT1L','PAK1','CD79A','PHF6','ERBB3','IL7R','MEN1','NOTCH2','IKBKE','AKT1','EPHA7','H3C3','TSC1','CYP19A1','SOX2','DDR2','SOX9','NOTCH4','H3C4','IFNGR1','SOX17','KDM5A','CTNNB1','IRF4','MAP3K1','TENT5C','RAB35','TCF7L2','VTCN1','POLE','FLT4','TSC2','JAK3','CDK12','GATA2','RPS6KA4','RICTOR','MEF2B','FOXP1','XPO1','APLNR','BARD1','FGFR1','DROSHA','PLCG2','B2M','FGFR2','CHEK1','BABAM1','MPL','FGF19','REL','RECQL4','KLF4','BAP1','MSH2','CDK6','MYC','SMARCD1','JAK1','TET2','SLFN11','UPF1','LMO1','MAPKAP1','PMS2','LATS1','ATR','HOXB13','DNAJB1','NUP93','PTPRD','GRIN2A','PIK3R1','BRD4','RAD52','HNF1A','TRAF2','CALR','IGF1','MSI1','TP63','PTP4A1','MSH3','EPAS1','H3C7','TNFAIP3','SERPINB4','MDM2','SUFU','EED','MDC1','AXIN1','MYD88','RPTOR','CIC','STAT5B','VHL','ESR1','SETDB1','IGF2','CYLD','EIF4A2','CTCF','ASXL1','BIRC3','FH','GATA1','ARAF','ACVR1','FUBP1','IGF1R','PDPK1','CXCR4','SETD2','GATA3','GPS2','APC','MLH1','RECQL','RAD51','IDH2','NF2','IKZF1','ETV6','SYK','VEGFA','SESN3','BMPR1A','REST','SMARCB1','AR','TP53BP1','TEK','LYN','MYCN','BCL6','GSK3B','RAD54L','H3-5','ERCC5','CD79B','MSI2','ERCC4','PMS1','SOCS1','STK11','SMAD4','MTOR','ZNRF3','PRDM14','BLM','EPHA5','PDCD1LG2','TRIP13','CTR9','EPHA3','TNFRSF14','MSH6','FGF3','RAD21','SMARCA2','CDH1','AKT2','BBC3','MDM4','H3-4','GREM1','PTCH1','INHBA','FYN','CDKN2C','STK40','MST1','TAP2','NSD1','TSHR','RPS6KB2','EIF4E','ID3','IDH1','MITF','ICOSLG','PTPRS','CBFB','PRKAR1A','CDK8','CSDE1','RAD50','SESN1','PRKD1','RAD51B','FLT3','FGFR4','RAC1','NBN','H3-3B','PMAIP1','SESN2','SRC','MED12') +GROUP BY gene + +-- view 0 drivernegative: drivernegative:altered · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +mp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +cov AS ( + SELECT mp.pid AS pid, max(mp.panel = 'WES') AS wes, uniqExactIf(gp.gene, gp.gene IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3')) AS ng + FROM mp LEFT JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = mp.panel + GROUP BY pid +), +drv AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.hugo_gene_symbol IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3') AND ann.is_driver + AND ((g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED') OR (g.variant_type = 'cna' AND abs(g.cna_alteration) >= 2) OR g.variant_type = 'structural_variant') + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +grp AS ( + SELECT pid, multiIf(pid IN (SELECT pid FROM drv), 'positive', wes = 1 OR ng = 11, 'negative', 'unprofiled') AS g FROM cov +) +SELECT g.hugo_gene_symbol AS gene, grp.g AS grp, uniqExact(replaceOne(g.patient_unique_id, 'msk_chord_2024_', '')) AS altered +FROM snapshot_db.genomic_event_derived g + INNER JOIN grp ON grp.pid = replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') +WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' + AND g.hugo_gene_symbol NOT IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3') AND grp.g IN ('positive', 'negative') +GROUP BY gene, grp + +-- view 0 drivernegative: drivernegative:groups · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +mp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +cov AS ( + SELECT mp.pid AS pid, max(mp.panel = 'WES') AS wes, uniqExactIf(gp.gene, gp.gene IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3')) AS ng + FROM mp LEFT JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = mp.panel + GROUP BY pid +), +drv AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.hugo_gene_symbol IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3') AND ann.is_driver + AND ((g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED') OR (g.variant_type = 'cna' AND abs(g.cna_alteration) >= 2) OR g.variant_type = 'structural_variant') + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +grp AS ( + SELECT pid, multiIf(pid IN (SELECT pid FROM drv), 'positive', wes = 1 OR ng = 11, 'negative', 'unprofiled') AS g FROM cov +) +SELECT g AS grp, count() AS n FROM grp GROUP BY grp + +-- view 0 drivernegative: drivernegative:profiled · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +mp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +cov AS ( + SELECT mp.pid AS pid, max(mp.panel = 'WES') AS wes, uniqExactIf(gp.gene, gp.gene IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3')) AS ng + FROM mp LEFT JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = mp.panel + GROUP BY pid +), +drv AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.hugo_gene_symbol IN ('EGFR', 'KRAS', 'ALK', 'ROS1', 'BRAF', 'MET', 'RET', 'ERBB2', 'NTRK1', 'NTRK2', 'NTRK3') AND ann.is_driver + AND ((g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED') OR (g.variant_type = 'cna' AND abs(g.cna_alteration) >= 2) OR g.variant_type = 'structural_variant') + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +grp AS ( + SELECT pid, multiIf(pid IN (SELECT pid FROM drv), 'positive', wes = 1 OR ng = 11, 'negative', 'unprofiled') AS g FROM cov +), +pw AS (SELECT pid, max(panel = 'WES') AS wes FROM pp GROUP BY pid) +SELECT if(pw.wes = 1, '', gp.gene) AS gene, grp.g AS grp, uniqExact(pp.pid) AS profiled +FROM pp INNER JOIN pw ON pw.pid = pp.pid INNER JOIN grp ON grp.pid = pp.pid + LEFT JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel +WHERE grp.g IN ('positive', 'negative') +GROUP BY gene, grp diff --git a/test/snapshots/drivernegative.svg b/test/snapshots/drivernegative.svg new file mode 100644 index 0000000..3eddcb7 --- /dev/null +++ b/test/snapshots/drivernegative.svg @@ -0,0 +1,2 @@ + +Driver-negative analysis — 11 drivers7,809 patients sequenced · drivers: EGFR, KRAS, ALK, ROS1, BRAF, MET, RET, ERBB2, NTRK1, NTRK2, NTRK3Driver-negative 2,898 (37.1%)Driver-positive 4,911 (62.9%)Driver-negative 2,898 (37.1%) · Driver-positive 4,911 (62.9%) · Not fully profiled 0 (0%)01020304050−log₁₀ p−4−2024Log2 ratio, driver-negative over driver-positiveNF1: driver-negative 12.4%, driver-positive 5.0% · q 2.9e-28KMT2D: driver-negative 12.8%, driver-positive 5.3% · q 3.1e-28RBM10: driver-negative 5.4%, driver-positive 12.8% · q 1.3e-25TP53: driver-negative 59.0%, driver-positive 47.1% · q 2.8e-22NFE2L2: driver-negative 5.6%, driver-positive 1.6% · q 4.3e-20CDKN2A: driver-negative 10.2%, driver-positive 5.1% · q 8.3e-15RASA1: driver-negative 4.3%, driver-positive 1.5% · q 2.3e-11PTEN: driver-negative 5.2%, driver-positive 2.2% · q 6.9e-11KEAP1: driver-negative 16.2%, driver-positive 11.5% · q 3.2e-7ARID1A: driver-negative 8.8%, driver-positive 5.7% · q 1.1e-5ANKRD11: driver-negative 3.6%, driver-positive 1.6% · q 1.2e-5MAP2K1: driver-negative 1.8%, driver-positive 0.5% · q 1.7e-5U2AF1: driver-negative 1.1%, driver-positive 2.7% · q 2.1e-5NRAS: driver-negative 1.6%, driver-positive 0.5% · q 4.5e-5CASP8: driver-negative 1.5%, driver-positive 0.4% · q 6.7e-5RB1: driver-negative 6.7%, driver-positive 4.2% · q 6.9e-5FBXW7: driver-negative 3.2%, driver-positive 1.6% · q 8.3e-5NOTCH1: driver-negative 4.9%, driver-positive 2.8% · q 9.8e-5ERBB4: driver-negative 5.8%, driver-positive 3.7% · q 4e-4PREX2: driver-negative 6.0%, driver-positive 3.6% · q 4.2e-4KMT2C: driver-negative 7.4%, driver-positive 5.0% · q 4.2e-4IRS2: driver-negative 3.2%, driver-positive 1.7% · q 4.3e-4PIK3CB: driver-negative 1.9%, driver-positive 0.8% · q 8.4e-4KMT2A: driver-negative 3.9%, driver-positive 2.3% · q 9.8e-4NOTCH3: driver-negative 4.5%, driver-positive 2.8% · q 0.0012KMT2B: driver-negative 5.0%, driver-positive 3.0% · q 0.0013COP1: driver-negative 2.3%, driver-positive 1.1% · q 0.0018PDGFRA: driver-negative 4.7%, driver-positive 3.0% · q 0.0022EPHB1: driver-negative 4.8%, driver-positive 3.1% · q 0.0026EP300: driver-negative 3.8%, driver-positive 2.3% · q 0.0026MAX: driver-negative 0.8%, driver-positive 1.8% · q 0.003DICER1: driver-negative 3.7%, driver-positive 2.2% · q 0.0031EIF1AX: driver-negative 1.2%, driver-positive 0.5% · q 0.0031PIK3C3: driver-negative 2.3%, driver-positive 1.2% · q 0.0031SMARCA4: driver-negative 8.9%, driver-positive 6.6% · q 0.0034SUZ12: driver-negative 1.2%, driver-positive 0.4% · q 0.0036FGFR3: driver-negative 1.2%, driver-positive 0.5% · q 0.0044ZFHX3: driver-negative 6.1%, driver-positive 4.2% · q 0.0049PRKN: driver-negative 2.7%, driver-positive 1.5% · q 0.005CREBBP: driver-negative 4.5%, driver-positive 3.0% · q 0.0055CUL3: driver-negative 2.7%, driver-positive 1.5% · q 0.0055KIT: driver-negative 2.9%, driver-positive 1.7% · q 0.0055FOXL2: driver-negative 1.7%, driver-positive 0.8% · q 0.0057RAF1: driver-negative 1.3%, driver-positive 0.5% · q 0.0062BRCA2: driver-negative 4.2%, driver-positive 2.7% · q 0.0067ATM: driver-negative 5.7%, driver-positive 7.7% · q 0.0067PBRM1: driver-negative 3.2%, driver-positive 1.9% · q 0.0073KDM6A: driver-negative 2.2%, driver-positive 1.2% · q 0.0073PTPRT: driver-negative 8.2%, driver-positive 6.1% · q 0.0073LATS2: driver-negative 2.4%, driver-positive 1.3% · q 0.0076LZTR1: driver-negative 2.3%, driver-positive 0.7% · q 0.0083SPEN: driver-negative 3.5%, driver-positive 2.2% · q 0.0093FAT1: driver-negative 8.4%, driver-positive 6.4% · q 0.0096YES1: driver-negative 1.0%, driver-positive 0.4% · q 0.0098TET1: driver-negative 3.9%, driver-positive 2.6% · q 0.0099FANCA: driver-negative 2.5%, driver-positive 1.5% · q 0.011BRCA1: driver-negative 3.3%, driver-positive 2.1% · q 0.011IRS1: driver-negative 2.7%, driver-positive 1.6% · q 0.013RRAS: driver-negative 0.8%, driver-positive 0.2% · q 0.013POLD1: driver-negative 2.4%, driver-positive 1.4% · q 0.016PIK3CG: driver-negative 5.2%, driver-positive 3.7% · q 0.016CSF3R: driver-negative 1.7%, driver-positive 0.9% · q 0.016NEGR1: driver-negative 1.8%, driver-positive 1.0% · q 0.017BRIP1: driver-negative 2.6%, driver-positive 1.6% · q 0.017HRAS: driver-negative 0.6%, driver-positive 0.2% · q 0.017ARID2: driver-negative 5.0%, driver-positive 3.6% · q 0.019KDR: driver-negative 4.8%, driver-positive 3.4% · q 0.021ARID1B: driver-negative 3.7%, driver-positive 2.5% · q 0.021EZH2: driver-negative 1.0%, driver-positive 0.4% · q 0.025INPPL1: driver-negative 2.6%, driver-positive 1.5% · q 0.025NKX2-1: driver-negative 1.3%, driver-positive 2.3% · q 0.025ARHGAP35: driver-negative 3.6%, driver-positive 1.6% · q 0.026AURKA: driver-negative 0.7%, driver-positive 0.2% · q 0.027PIK3C2G: driver-negative 4.1%, driver-positive 2.9% · q 0.028STAT3: driver-negative 1.2%, driver-positive 0.6% · q 0.029GNAQ: driver-negative 0.6%, driver-positive 0.2% · q 0.03WHR1: driver-negative 0.8%, driver-positive 0.3% · q 0.032WT1: driver-negative 2.6%, driver-positive 1.6% · q 0.033ALOX12B: driver-negative 0.8%, driver-positive 0.3% · q 0.034NFKBIA: driver-negative 1.0%, driver-positive 0.4% · q 0.035RIT1: driver-negative 0.9%, driver-positive 0.4% · q 0.038NSD2: driver-negative 2.1%, driver-positive 1.2% · q 0.038GAB1: driver-negative 2.0%, driver-positive 0.7% · q 0.041HGF: driver-negative 5.2%, driver-positive 3.9% · q 0.042NSD3: driver-negative 1.7%, driver-positive 0.9% · q 0.042AXL: driver-negative 1.6%, driver-positive 0.9% · q 0.043PAK5: driver-negative 3.9%, driver-positive 2.8% · q 0.044H3C10: driver-negative 0.6%, driver-positive 0.2% · q 0.044RRAS2: driver-negative 0.7%, driver-positive 0.2% · q 0.045SRSF2: driver-negative 0.8%, driver-positive 0.4% · q 0.045TBX3: driver-negative 4.2%, driver-positive 3.0% · q 0.045INPP4A: driver-negative 1.8%, driver-positive 1.1% · q 0.046CBL: driver-negative 1.6%, driver-positive 0.9% · q 0.048DAXX: driver-negative 1.1%, driver-positive 0.5% · q 0.049PIK3R3: driver-negative 0.8%, driver-positive 0.4% · q 0.05PGR: driver-negative 3.0%, driver-positive 2.1% · q 0.051H1-2: driver-negative 1.5%, driver-positive 0.9% · q 0.051JAK2: driver-negative 2.3%, driver-positive 1.5% · q 0.055PHOX2B: driver-negative 1.3%, driver-positive 0.7% · q 0.055DOT1L: driver-negative 3.3%, driver-positive 2.4% · q 0.057PAK1: driver-negative 1.2%, driver-positive 0.7% · q 0.057CD79A: driver-negative 0.6%, driver-positive 0.2% · q 0.063PHF6: driver-negative 1.1%, driver-positive 0.2% · q 0.065ERBB3: driver-negative 1.6%, driver-positive 1.0% · q 0.065IL7R: driver-negative 2.8%, driver-positive 1.9% · q 0.065MEN1: driver-negative 1.7%, driver-positive 1.0% · q 0.067NOTCH2: driver-negative 3.2%, driver-positive 2.2% · q 0.067IKBKE: driver-negative 1.2%, driver-positive 0.7% · q 0.068AKT1: driver-negative 1.1%, driver-positive 0.6% · q 0.071EPHA7: driver-negative 4.0%, driver-positive 2.9% · q 0.071H3C3: driver-negative 0.7%, driver-positive 0.3% · q 0.078TSC1: driver-negative 1.8%, driver-positive 1.1% · q 0.081CYP19A1: driver-negative 1.8%, driver-positive 0.7% · q 0.081SOX2: driver-negative 1.2%, driver-positive 0.7% · q 0.081DDR2: driver-negative 2.8%, driver-positive 2.0% · q 0.081SOX9: driver-negative 1.5%, driver-positive 0.9% · q 0.084NOTCH4: driver-negative 4.5%, driver-positive 3.5% · q 0.086H3C4: driver-negative 0.8%, driver-positive 0.4% · q 0.086IFNGR1: driver-negative 0.8%, driver-positive 0.4% · q 0.089SOX17: driver-negative 2.1%, driver-positive 1.4% · q 0.089KDM5A: driver-negative 2.2%, driver-positive 1.5% · q 0.098CTNNB1: driver-negative 2.3%, driver-positive 3.2% · q 0.098IRF4: driver-negative 1.3%, driver-positive 0.8% · q 0.10MAP3K1: driver-negative 2.2%, driver-positive 1.5% · q 0.11TENT5C: driver-negative 0.7%, driver-positive 0.3% · q 0.11RAB35: driver-negative 0.4%, driver-positive 0.1% · q 0.11TCF7L2: driver-negative 1.0%, driver-positive 0.5% · q 0.11VTCN1: driver-negative 0.6%, driver-positive 0.2% · q 0.12POLE: driver-negative 4.0%, driver-positive 3.1% · q 0.12FLT4: driver-negative 3.3%, driver-positive 2.5% · q 0.12TSC2: driver-negative 2.4%, driver-positive 1.7% · q 0.13JAK3: driver-negative 2.4%, driver-positive 1.7% · q 0.13CDK12: driver-negative 2.2%, driver-positive 1.6% · q 0.13GATA2: driver-negative 1.5%, driver-positive 0.9% · q 0.13RPS6KA4: driver-negative 1.5%, driver-positive 0.9% · q 0.13RICTOR: driver-negative 1.9%, driver-positive 1.3% · q 0.13MEF2B: driver-negative 0.6%, driver-positive 0.3% · q 0.13FOXP1: driver-negative 1.3%, driver-positive 0.8% · q 0.13XPO1: driver-negative 1.3%, driver-positive 0.8% · q 0.13APLNR: driver-negative 1.8%, driver-positive 0.7% · q 0.13BARD1: driver-negative 1.9%, driver-positive 1.3% · q 0.13FGFR1: driver-negative 1.5%, driver-positive 1.0% · q 0.13DROSHA: driver-negative 3.2%, driver-positive 2.3% · q 0.13PLCG2: driver-negative 2.3%, driver-positive 1.6% · q 0.14B2M: driver-negative 1.4%, driver-positive 0.9% · q 0.15FGFR2: driver-negative 1.4%, driver-positive 0.9% · q 0.15CHEK1: driver-negative 0.8%, driver-positive 0.4% · q 0.15BABAM1: driver-negative 0.9%, driver-positive 0.5% · q 0.16MPL: driver-negative 1.1%, driver-positive 0.7% · q 0.16FGF19: driver-negative 0.8%, driver-positive 0.5% · q 0.16REL: driver-negative 0.8%, driver-positive 0.5% · q 0.16RECQL4: driver-negative 2.2%, driver-positive 1.5% · q 0.16KLF4: driver-negative 1.3%, driver-positive 0.9% · q 0.16BAP1: driver-negative 1.7%, driver-positive 1.1% · q 0.16MSH2: driver-negative 1.4%, driver-positive 0.9% · q 0.16CDK6: driver-negative 0.8%, driver-positive 0.4% · q 0.16MYC: driver-negative 0.8%, driver-positive 0.4% · q 0.16SMARCD1: driver-negative 0.8%, driver-positive 0.4% · q 0.16JAK1: driver-negative 1.5%, driver-positive 1.0% · q 0.16TET2: driver-negative 2.6%, driver-positive 1.9% · q 0.17SLFN11: driver-negative 2.2%, driver-positive 1.2% · q 0.17UPF1: driver-negative 1.3%, driver-positive 0.8% · q 0.17LMO1: driver-negative 0.7%, driver-positive 0.3% · q 0.17MAPKAP1: driver-negative 0.8%, driver-positive 0.4% · q 0.17PMS2: driver-negative 1.0%, driver-positive 0.6% · q 0.17LATS1: driver-negative 1.9%, driver-positive 1.4% · q 0.17ATR: driver-negative 3.9%, driver-positive 3.1% · q 0.18HOXB13: driver-negative 1.1%, driver-positive 0.7% · q 0.19DNAJB1: driver-negative 0.6%, driver-positive 0.3% · q 0.19NUP93: driver-negative 1.4%, driver-positive 0.9% · q 0.19PTPRD: driver-negative 9.0%, driver-positive 7.8% · q 0.19GRIN2A: driver-negative 5.7%, driver-positive 4.7% · q 0.19PIK3R1: driver-negative 1.5%, driver-positive 1.0% · q 0.19BRD4: driver-negative 1.7%, driver-positive 1.2% · q 0.19RAD52: driver-negative 0.5%, driver-positive 0.2% · q 0.20HNF1A: driver-negative 1.6%, driver-positive 1.1% · q 0.20TRAF2: driver-negative 1.0%, driver-positive 0.6% · q 0.20CALR: driver-negative 0.7%, driver-positive 0.3% · q 0.20IGF1: driver-negative 0.7%, driver-positive 0.4% · q 0.20MSI1: driver-negative 0.9%, driver-positive 0.5% · q 0.20TP63: driver-negative 2.1%, driver-positive 1.5% · q 0.20PTP4A1: driver-negative 0.4%, driver-positive 0.2% · q 0.21MSH3: driver-negative 1.6%, driver-positive 1.1% · q 0.21EPAS1: driver-negative 1.0%, driver-positive 0.5% · q 0.21H3C7: driver-negative 0.5%, driver-positive 0.2% · q 0.21TNFAIP3: driver-negative 1.0%, driver-positive 0.6% · q 0.21SERPINB4: driver-negative 1.9%, driver-positive 0.9% · q 0.21MDM2: driver-negative 0.9%, driver-positive 0.5% · q 0.21SUFU: driver-negative 0.6%, driver-positive 0.3% · q 0.21EED: driver-negative 0.8%, driver-positive 0.4% · q 0.22MDC1: driver-negative 2.7%, driver-positive 2.1% · q 0.22AXIN1: driver-negative 1.5%, driver-positive 1.0% · q 0.22MYD88: driver-negative 0.6%, driver-positive 0.3% · q 0.22RPTOR: driver-negative 2.1%, driver-positive 1.5% · q 0.22CIC: driver-negative 2.3%, driver-positive 1.7% · q 0.22STAT5B: driver-negative 1.1%, driver-positive 0.7% · q 0.22VHL: driver-negative 0.1%, driver-positive 0.3% · q 0.22ESR1: driver-negative 1.7%, driver-positive 1.2% · q 0.22SETDB1: driver-negative 1.9%, driver-positive 1.0% · q 0.23IGF2: driver-negative 1.1%, driver-positive 0.7% · q 0.23CYLD: driver-negative 1.7%, driver-positive 1.2% · q 0.23EIF4A2: driver-negative 1.0%, driver-positive 0.6% · q 0.23CTCF: driver-negative 1.2%, driver-positive 0.8% · q 0.23ASXL1: driver-negative 2.4%, driver-positive 1.8% · q 0.23BIRC3: driver-negative 1.0%, driver-positive 0.6% · q 0.24FH: driver-negative 1.4%, driver-positive 1.0% · q 0.24GATA1: driver-negative 1.1%, driver-positive 0.7% · q 0.24ARAF: driver-negative 1.8%, driver-positive 1.3% · q 0.24ACVR1: driver-negative 0.6%, driver-positive 0.3% · q 0.24FUBP1: driver-negative 1.3%, driver-positive 0.9% · q 0.24IGF1R: driver-negative 1.8%, driver-positive 1.3% · q 0.24PDPK1: driver-negative 0.3%, driver-positive 0.1% · q 0.24CXCR4: driver-negative 0.9%, driver-positive 0.6% · q 0.24SETD2: driver-negative 5.6%, driver-positive 4.8% · q 0.25GATA3: driver-negative 2.1%, driver-positive 1.6% · q 0.25GPS2: driver-negative 0.5%, driver-positive 0.3% · q 0.25APC: driver-negative 4.7%, driver-positive 3.9% · q 0.26MLH1: driver-negative 0.9%, driver-positive 0.6% · q 0.27RECQL: driver-negative 1.0%, driver-positive 0.6% · q 0.27RAD51: driver-negative 0.3%, driver-positive 0.1% · q 0.28IDH2: driver-negative 0.7%, driver-positive 0.4% · q 0.28NF2: driver-negative 1.6%, driver-positive 1.2% · q 0.28IKZF1: driver-negative 2.6%, driver-positive 2.0% · q 0.28ETV6: driver-negative 1.1%, driver-positive 0.8% · q 0.29SYK: driver-negative 0.8%, driver-positive 0.5% · q 0.29VEGFA: driver-negative 0.4%, driver-positive 0.2% · q 0.29SESN3: driver-negative 0.7%, driver-positive 0.4% · q 0.29BMPR1A: driver-negative 0.7%, driver-positive 0.4% · q 0.29REST: driver-negative 1.5%, driver-positive 0.8% · q 0.29SMARCB1: driver-negative 0.8%, driver-positive 0.5% · q 0.31AR: driver-negative 3.0%, driver-positive 2.5% · q 0.31TP53BP1: driver-negative 2.2%, driver-positive 1.7% · q 0.31TEK: driver-negative 1.5%, driver-positive 1.1% · q 0.31LYN: driver-negative 1.1%, driver-positive 0.7% · q 0.32MYCN: driver-negative 1.2%, driver-positive 0.9% · q 0.32BCL6: driver-negative 1.0%, driver-positive 0.7% · q 0.32GSK3B: driver-negative 0.7%, driver-positive 0.5% · q 0.33RAD54L: driver-negative 0.7%, driver-positive 0.5% · q 0.33H3-5: driver-negative 0.9%, driver-positive 0.6% · q 0.33ERCC5: driver-negative 1.9%, driver-positive 1.4% · q 0.33CD79B: driver-negative 0.6%, driver-positive 0.4% · q 0.33MSI2: driver-negative 0.3%, driver-positive 0.2% · q 0.33ERCC4: driver-negative 1.3%, driver-positive 0.9% · q 0.34PMS1: driver-negative 1.5%, driver-positive 1.1% · q 0.34SOCS1: driver-negative 0.5%, driver-positive 0.3% · q 0.34STK11: driver-negative 12.3%, driver-positive 13.4% · q 0.34SMAD4: driver-negative 3.1%, driver-positive 3.7% · q 0.35MTOR: driver-negative 3.2%, driver-positive 2.7% · q 0.36ZNRF3: driver-negative 1.3%, driver-positive 0.7% · q 0.36PRDM14: driver-negative 2.0%, driver-positive 1.5% · q 0.36BLM: driver-negative 1.3%, driver-positive 1.0% · q 0.36EPHA5: driver-negative 6.1%, driver-positive 5.4% · q 0.36PDCD1LG2: driver-negative 0.5%, driver-positive 0.3% · q 0.37TRIP13: driver-negative 0.7%, driver-positive 0.3% · q 0.37CTR9: driver-negative 1.4%, driver-positive 0.8% · q 0.37EPHA3: driver-negative 5.9%, driver-positive 5.2% · q 0.37TNFRSF14: driver-negative 0.5%, driver-positive 0.3% · q 0.37MSH6: driver-negative 1.4%, driver-positive 1.1% · q 0.37FGF3: driver-negative 1.2%, driver-positive 0.9% · q 0.37RAD21: driver-negative 1.1%, driver-positive 0.8% · q 0.37SMARCA2: driver-negative 1.4%, driver-positive 2.2% · q 0.38CDH1: driver-negative 1.2%, driver-positive 0.9% · q 0.39AKT2: driver-negative 0.7%, driver-positive 0.5% · q 0.39BBC3: driver-negative 0.3%, driver-positive 0.2% · q 0.39MDM4: driver-negative 0.7%, driver-positive 0.5% · q 0.40H3-4: driver-negative 0.6%, driver-positive 0.4% · q 0.40GREM1: driver-negative 0.4%, driver-positive 0.3% · q 0.40PTCH1: driver-negative 2.1%, driver-positive 1.7% · q 0.41INHBA: driver-negative 2.5%, driver-positive 2.1% · q 0.41FYN: driver-negative 1.0%, driver-positive 0.7% · q 0.41CDKN2C: driver-negative 0.6%, driver-positive 0.4% · q 0.41STK40: driver-negative 0.6%, driver-positive 0.4% · q 0.41MST1: driver-negative <0.1%, driver-positive 0.2% · q 0.41TAP2: driver-negative 1.0%, driver-positive 0.7% · q 0.41NSD1: driver-negative 2.8%, driver-positive 2.4% · q 0.42TSHR: driver-negative 2.0%, driver-positive 1.6% · q 0.44RPS6KB2: driver-negative 0.6%, driver-positive 0.4% · q 0.44EIF4E: driver-negative 0.4%, driver-positive 0.2% · q 0.44ID3: driver-negative 0.3%, driver-positive 0.1% · q 0.44IDH1: driver-negative 0.7%, driver-positive 1.0% · q 0.44MITF: driver-negative 0.7%, driver-positive 0.4% · q 0.44ICOSLG: driver-negative 0.2%, driver-positive 0.3% · q 0.45PTPRS: driver-negative 2.5%, driver-positive 2.1% · q 0.46CBFB: driver-negative 0.4%, driver-positive 0.2% · q 0.46PRKAR1A: driver-negative 0.5%, driver-positive 0.3% · q 0.47CDK8: driver-negative 0.7%, driver-positive 0.5% · q 0.47CSDE1: driver-negative 1.2%, driver-positive 0.9% · q 0.49RAD50: driver-negative 1.4%, driver-positive 1.1% · q 0.49SESN1: driver-negative 0.3%, driver-positive 0.5% · q 0.49PRKD1: driver-negative 2.6%, driver-positive 2.1% · q 0.49RAD51B: driver-negative 0.6%, driver-positive 0.4% · q 0.50FLT3: driver-negative 2.1%, driver-positive 1.7% · q 0.50FGFR4: driver-negative 1.8%, driver-positive 1.4% · q 0.50RAC1: driver-negative 0.4%, driver-positive 0.3% · q 0.50NBN: driver-negative 1.5%, driver-positive 1.2% · q 0.50H3-3B: driver-negative 0.3%, driver-positive 0.2% · q 0.50PMAIP1: driver-negative 0.2%, driver-positive <0.1% · q 0.50SESN2: driver-negative 0.7%, driver-positive 0.5% · q 0.50SRC: driver-negative 0.8%, driver-positive 0.6% · q 0.50MED12: driver-negative 4.2%, driver-positive 4.7% · q 0.50NF1KMT2DRBM10TP53NFE2L2CDKN2ARASA1PTENKEAP1ARID1AANKRD11MAP2K1← Driver-positiveDriver-negative →Most enriched in driver-negative patientsGeneDriver-negativeDriver-positivepqNF1 17q11.2358/2,898 (12.4%)244/4,911 (5.0%)6e-312.9e-28KMT2D 12q13.12370/2,898 (12.8%)259/4,911 (5.3%)1.3e-303.1e-28TP53 17p13.11,709/2,898 (59.0%)2,311/4,911 (47.1%)2.3e-242.8e-22NFE2L2 2q31.2161/2,898 (5.6%)77/4,911 (1.6%)4.4e-224.3e-20CDKN2A 9p21.3295/2,898 (10.2%)251/4,911 (5.1%)1e-168.3e-15RASA1 5q14.3124/2,898 (4.3%)75/4,911 (1.5%)3.3e-132.3e-11PTEN 10q23.31152/2,898 (5.2%)107/4,911 (2.2%)1.1e-126.9e-11KEAP1 19p13.2469/2,898 (16.2%)565/4,911 (11.5%)6e-93.2e-7ARID1A 1p36.11255/2,898 (8.8%)279/4,911 (5.7%)2.3e-71.1e-5ANKRD11 16q24.398/2,758 (3.6%)77/4,726 (1.6%)2.8e-71.2e-5MAP2K1 15q22.3151/2,898 (1.8%)27/4,911 (0.5%)4.1e-71.7e-5NRAS 1p13.246/2,898 (1.6%)24/4,911 (0.5%)1.3e-64.5e-5Of the 7,809 patients of “NSCLC” sequenced for mutations, 4,911 (62.9%) carry an OncoKB driver (v7.5) — a called mutation, a high-level copy-number call or astructural variant the store calls oncogenic or likely oncogenic — in EGFR, KRAS, ALK, ROS1, BRAF, MET, RET, ERBB2, NTRK1, NTRK2, NTRK3; 2,898 (37.1%) carry noneand were sequenced on a panel covering every one of those genes (or whole-exome): the driver-negative group. 0 (0.0%) carry none but were not profiled for every listedgene, so are neither — kept out of both sides, never counted as driver-negative. Coverage is read from the mutation panels; a fusion driver is called only where the studyreports structural variants. Then every other gene's mutation frequency is compared between driver-negative and driver-positive patients, in PATIENTS over the patientsprofiled for that gene (a panel that lists it, or whole-exome; an off-panel call counts as measured), two-sided Fisher's exact test, Benjamini–Hochberg q over the 487genes tested; 7 altered genes were not tested (fewer than 5 altered patients, or not profiled in a group). The log ratio is log2 of the driver-negative share over thedriver-positive one. Driver-positive tumours are often mutually exclusive with other drivers by biology, so a gene enriched in the driver-negative group is a candidatealternative driver, not a proven one; smoking history and tumour mutation burden also differ between the groups and are not adjusted for. diff --git a/test/snapshots/drivernegative.txt b/test/snapshots/drivernegative.txt new file mode 100644 index 0000000..4818440 --- /dev/null +++ b/test/snapshots/drivernegative.txt @@ -0,0 +1,38 @@ +Analysis over msk_chord_2024 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- NSCLC: 7,809 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) + +Driver-negative analysis (drivers: EGFR, KRAS, ALK, ROS1, BRAF, MET, RET, ERBB2, NTRK1, NTRK2, NTRK3): of 7,809 patients sequenced, 4,911 driver-positive (62.9%), 2,898 driver-negative (37.1%), 0 not fully profiled (in neither group). + mutation frequency, driver-negative vs driver-positive, Fisher + BH over 487 genes, 95 at q<0.05: + NF1: 358/2898 (12.4%) vs 244/4911 (5.0%), log2 ratio 1.31, p=6.0e-31 q=2.9e-28 + KMT2D: 370/2898 (12.8%) vs 259/4911 (5.3%), log2 ratio 1.28, p=1.3e-30 q=3.1e-28 + RBM10: 157/2898 (5.4%) vs 630/4911 (12.8%), log2 ratio -1.24, p=7.9e-28 q=1.3e-25 + TP53: 1709/2898 (59.0%) vs 2311/4911 (47.1%), log2 ratio 0.33, p=2.3e-24 q=2.8e-22 + NFE2L2: 161/2898 (5.6%) vs 77/4911 (1.6%), log2 ratio 1.83, p=4.4e-22 q=4.3e-20 + CDKN2A: 295/2898 (10.2%) vs 251/4911 (5.1%), log2 ratio 0.99, p=1.0e-16 q=8.3e-15 + RASA1: 124/2898 (4.3%) vs 75/4911 (1.5%), log2 ratio 1.49, p=3.3e-13 q=2.3e-11 + PTEN: 152/2898 (5.2%) vs 107/4911 (2.2%), log2 ratio 1.27, p=1.1e-12 q=6.9e-11 + KEAP1: 469/2898 (16.2%) vs 565/4911 (11.5%), log2 ratio 0.49, p=6.0e-9 q=3.2e-7 + ARID1A: 255/2898 (8.8%) vs 279/4911 (5.7%), log2 ratio 0.63, p=2.3e-7 q=1.1e-5 + ANKRD11: 98/2758 (3.6%) vs 77/4726 (1.6%), log2 ratio 1.12, p=2.8e-7 q=1.2e-5 + MAP2K1: 51/2898 (1.8%) vs 27/4911 (0.5%), log2 ratio 1.68, p=4.1e-7 q=1.7e-5 + U2AF1: 32/2898 (1.1%) vs 134/4911 (2.7%), log2 ratio -1.31, p=5.7e-7 q=2.1e-5 + NRAS: 46/2898 (1.6%) vs 24/4911 (0.5%), log2 ratio 1.70, p=1.3e-6 q=4.5e-5 + CASP8: 43/2898 (1.5%) vs 22/4911 (0.4%), log2 ratio 1.73, p=2.1e-6 q=6.7e-5 + RB1: 195/2898 (6.7%) vs 208/4911 (4.2%), log2 ratio 0.67, p=2.3e-6 q=6.9e-5 + FBXW7: 94/2898 (3.2%) vs 78/4911 (1.6%), log2 ratio 1.03, p=2.9e-6 q=8.3e-5 + NOTCH1: 141/2898 (4.9%) vs 138/4911 (2.8%), log2 ratio 0.79, p=3.6e-6 q=9.8e-5 + ERBB4: 169/2898 (5.8%) vs 181/4911 (3.7%), log2 ratio 0.66, p=1.6e-5 q=0.0004 + PREX2: 137/2288 (6.0%) vs 139/3863 (3.6%), log2 ratio 0.73, p=1.8e-5 q=0.0004 + KMT2C: 215/2898 (7.4%) vs 246/4911 (5.0%), log2 ratio 0.57, p=1.8e-5 q=0.0004 + IRS2: 94/2898 (3.2%) vs 84/4911 (1.7%), log2 ratio 0.92, p=1.9e-5 q=0.0004 + PIK3CB: 55/2898 (1.9%) vs 40/4911 (0.8%), log2 ratio 1.22, p=4.0e-5 q=0.0008 + KMT2A: 114/2898 (3.9%) vs 113/4911 (2.3%), log2 ratio 0.77, p=4.8e-5 q=0.0010 + NOTCH3: 131/2898 (4.5%) vs 136/4911 (2.8%), log2 ratio 0.71, p=6.0e-5 q=0.0012 + KMT2B: 115/2288 (5.0%) vs 115/3863 (3.0%), log2 ratio 0.76, p=6.8e-5 q=0.0013 + COP1: 67/2898 (2.3%) vs 56/4911 (1.1%), log2 ratio 1.02, p=9.8e-5 q=0.0018 + PDGFRA: 136/2898 (4.7%) vs 147/4911 (3.0%), log2 ratio 0.65, p=0.0001 q=0.0022 + EPHB1: 140/2898 (4.8%) vs 153/4911 (3.1%), log2 ratio 0.63, p=0.0002 q=0.0026 + EP300: 109/2898 (3.8%) vs 111/4911 (2.3%), log2 ratio 0.73, p=0.0002 q=0.0026 + CAVEAT (tell the user): Of the 7,809 patients of “NSCLC” sequenced for mutations, 4,911 (62.9%) carry an OncoKB driver (v7.5) — a called mutation, a high-level copy-number call or a structural variant the store calls oncogenic or likely oncogenic — in EGFR, KRAS, ALK, ROS1, BRAF, MET, RET, ERBB2, NTRK1, NTRK2, NTRK3; 2,898 (37.1%) carry none and were sequenced on a panel covering every one of those genes (or whole-exome): the driver-negative group. 0 (0.0%) carry none but were not profiled for every listed gene, so are neither — kept out of both sides, never counted as driver-negative. Coverage is read from the mutation panels; a fusion driver is called only where the study reports structural variants. Then every other gene's mutation frequency is compared between driver-negative and driver-positive patients, in PATIENTS over the patients profiled for that gene (a panel that lists it, or whole-exome; an off-panel call counts as measured), two-sided Fisher's exact test, Benjamini–Hochberg q over the 487 genes tested; 7 altered genes were not tested (fewer than 5 altered patients, or not profiled in a group). The log ratio is log2 of the driver-negative share over the driver-positive one. Driver-positive tumours are often mutually exclusive with other drivers by biology, so a gene enriched in the driver-negative group is a candidate alternative driver, not a proven one; smoking history and tumour mutation burden also differ between the groups and are not adjusted for. diff --git a/test/snapshots/exclusivity.json b/test/snapshots/exclusivity.json new file mode 100644 index 0000000..9b32688 --- /dev/null +++ b/test/snapshots/exclusivity.json @@ -0,0 +1,330 @@ +{ + "study": "msk_impact_2017", + "cohorts": { + "NSCLC": { + "count": 1567, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample)", + "studies": [ + "msk_impact_2017" + ] + } + }, + "views": [ + { + "kind": "exclusivity", + "cohorts": [ + "NSCLC" + ], + "exclusivity": { + "cohort": "NSCLC", + "genes": [ + "KRAS", + "EGFR", + "BRAF", + "STK11", + "KEAP1", + "TP53" + ], + "stratifyBy": null, + "strata": [ + { + "name": "msk_impact_2017", + "samples": 1680 + } + ], + "measure": "any", + "samples": 1680, + "matrixGenes": 410, + "iterations": 3, + "converged": true, + "maxResidual": 0.00003105579151229087, + "pairs": [ + { + "a": "KRAS", + "b": "EGFR", + "profiled": 1680, + "both": 19, + "expected": 169.98, + "pExclusive": 6.3195515358849135e-58, + "qExclusive": 9.47932730382737e-57, + "pCooccur": 1, + "qCooccur": 1, + "exact": true, + "onlyA": 452, + "onlyB": 425, + "neither": 784, + "fisherP": 7.6203547362604e-48, + "fisherQ": 5.7152660521953e-47, + "log2Odds": -3.689 + }, + { + "a": "EGFR", + "b": "STK11", + "profiled": 1680, + "both": 17, + "expected": 107.25, + "pExclusive": 1.0627948717382737e-30, + "qExclusive": 7.970961538037054e-30, + "pCooccur": 0.9999999999999999, + "qCooccur": 1, + "exact": true, + "onlyA": 427, + "onlyB": 265, + "neither": 971, + "fisherP": 3.6165841110641675e-21, + "fisherQ": 1.8082920555320838e-20, + "log2Odds": -2.777 + }, + { + "a": "EGFR", + "b": "KEAP1", + "profiled": 1680, + "both": 15, + "expected": 98.47, + "pExclusive": 1.2671106684407238e-28, + "qExclusive": 6.335553342203619e-28, + "pCooccur": 1, + "qCooccur": 1, + "exact": true, + "onlyA": 429, + "onlyB": 242, + "neither": 994, + "fisherP": 1.0559086841718176e-19, + "fisherQ": 3.959657565644316e-19, + "log2Odds": -2.8 + }, + { + "a": "STK11", + "b": "KEAP1", + "profiled": 1680, + "both": 159, + "expected": 66.24, + "pExclusive": 1, + "qExclusive": 1, + "pCooccur": 4.5982295638167945e-26, + "qCooccur": 6.897344345725192e-25, + "exact": true, + "onlyA": 123, + "onlyB": 98, + "neither": 1300, + "fisherP": 6.036616619860515e-76, + "fisherQ": 9.054924929790772e-75, + "log2Odds": 4.1 + }, + { + "a": "KRAS", + "b": "TP53", + "profiled": 1680, + "both": 188, + "expected": 316.93, + "pExclusive": 1.2664184356451813e-20, + "qExclusive": 4.74906913366943e-20, + "pCooccur": 0.9999999999999974, + "qCooccur": 1, + "exact": true, + "onlyA": 283, + "onlyB": 743, + "neither": 466, + "fisherP": 1.6928016521514319e-15, + "fisherQ": 4.23200413037858e-15, + "log2Odds": -1.263 + }, + { + "a": "STK11", + "b": "TP53", + "profiled": 1680, + "both": 125, + "expected": 194.74, + "pExclusive": 1.4265507792620713e-9, + "qExclusive": 4.2796523377862135e-9, + "pCooccur": 0.9999999991865393, + "qCooccur": 1, + "exact": true, + "onlyA": 157, + "onlyB": 806, + "neither": 592, + "fisherP": 0.000045142608831261017, + "fisherQ": 0.00007523768138543503, + "log2Odds": -0.774 + }, + { + "a": "KRAS", + "b": "BRAF", + "profiled": 1680, + "both": 15, + "expected": 41.9, + "pExclusive": 7.2154946091762e-7, + "qExclusive": 0.0000018038736522940497, + "pCooccur": 0.999999764686616, + "qCooccur": 1, + "exact": true, + "onlyA": 456, + "onlyB": 84, + "neither": 1125, + "fisherP": 0.002581346274764669, + "fisherQ": 0.0038720194121470036, + "log2Odds": -1.183 + }, + { + "a": "EGFR", + "b": "BRAF", + "profiled": 1680, + "both": 14, + "expected": 39.93, + "pExclusive": 9.666715838577565e-7, + "qExclusive": 0.000002071439108266621, + "pCooccur": 0.9999996914450148, + "qCooccur": 1, + "exact": true, + "onlyA": 430, + "onlyB": 85, + "neither": 1151, + "fisherP": 0.0032218222457231333, + "fisherQ": 0.004393393971440636, + "log2Odds": -1.182 + }, + { + "a": "KEAP1", + "b": "TP53", + "profiled": 1680, + "both": 140, + "expected": 178.11, + "pExclusive": 0.0005557790110802738, + "qExclusive": 0.0010420856457755135, + "pCooccur": 0.9995967588264927, + "qCooccur": 1, + "exact": true, + "onlyA": 117, + "onlyB": 791, + "neither": 632, + "fisherP": 0.7851201114852009, + "fisherQ": 0.7851201114852009, + "log2Odds": -0.065 + }, + { + "a": "BRAF", + "b": "STK11", + "profiled": 1680, + "both": 14, + "expected": 27.24, + "pExclusive": 0.0031910513246993727, + "qExclusive": 0.005318418874498954, + "pCooccur": 0.9985125736116419, + "qCooccur": 1, + "exact": true, + "onlyA": 85, + "onlyB": 268, + "neither": 1313, + "fisherP": 0.5789882786491313, + "fisherQ": 0.6203445842669264, + "log2Odds": -0.309 + }, + { + "a": "KRAS", + "b": "STK11", + "profiled": 1680, + "both": 141, + "expected": 112.78, + "pExclusive": 0.9980689908578892, + "qExclusive": 1, + "pCooccur": 0.002619429688468521, + "qCooccur": 0.019645722663513908, + "exact": true, + "onlyA": 330, + "onlyB": 141, + "neither": 1068, + "fisherP": 7.139877113424535e-18, + "fisherQ": 2.1419631340273606e-17, + "log2Odds": 1.694 + }, + { + "a": "BRAF", + "b": "TP53", + "profiled": 1680, + "both": 58, + "expected": 70.26, + "pExclusive": 0.0666294301033032, + "qExclusive": 0.0999441451549548, + "pCooccur": 0.9489220065615466, + "qCooccur": 1, + "exact": true, + "onlyA": 41, + "onlyB": 873, + "neither": 708, + "fisherP": 0.5334021071755198, + "fisherQ": 0.6154639698179074, + "log2Odds": 0.198 + }, + { + "a": "BRAF", + "b": "KEAP1", + "profiled": 1680, + "both": 18, + "expected": 25.12, + "pExclusive": 0.0812639690743567, + "qExclusive": 0.1108145032832137, + "pCooccur": 0.9479153517148076, + "qCooccur": 1, + "exact": true, + "onlyA": 81, + "onlyB": 239, + "neither": 1342, + "fisherP": 0.3899667246596332, + "fisherQ": 0.48745840582454153, + "log2Odds": 0.319 + }, + { + "a": "EGFR", + "b": "TP53", + "profiled": 1680, + "both": 294, + "expected": 299.85, + "pExclusive": 0.35687824445888283, + "qExclusive": 0.44609780557360357, + "pCooccur": 0.6687952816870565, + "qCooccur": 1, + "exact": true, + "onlyA": 150, + "onlyB": 637, + "neither": 599, + "fisherP": 8.518637339698797e-8, + "fisherQ": 1.8254222870783137e-7, + "log2Odds": 0.882 + }, + { + "a": "KRAS", + "b": "KEAP1", + "profiled": 1680, + "both": 107, + "expected": 103.52, + "pExclusive": 0.669251830132025, + "qExclusive": 0.7722136501523367, + "pCooccur": 0.37015380888610305, + "qCooccur": 1, + "exact": true, + "onlyA": 364, + "onlyB": 150, + "neither": 1059, + "fisherP": 3.524861204902817e-7, + "fisherQ": 6.609114759192782e-7, + "log2Odds": 1.053 + } + ], + "unassessable": [] + }, + "participation": { + "studies": [ + { + "study": "msk_impact_2017", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Within “NSCLC”, over its 1,680 samples with a gene panel (every sample of each patient; a sample without a panel record is left out, never read as wild-type), counting any alteration (mutation, amplification or deep deletion, structural variant). DISCOVER's background gives each sample and gene a probability of alteration, σ(a_sample + b_gene), fitted so that every sample's expected number of altered genes is its observed burden and every gene's expected number of altered samples its observed count — over the whole altered-gene matrix (410 genes, each sample's own panels: a gene off a sample's panel has no probability there); the fit matched every margin to within 0.01% in 3 rounds. For a pair, the number of samples altered in both under independence given those probabilities is Poisson-binomial with p_i = p_iA·p_iB, over the samples profiled for both; exclusivity is its lower tail at the observed count, co-occurrence its upper tail, each Benjamini–Hochberg across the 15 pairs, all exact. Beside it, the plain two-sided Fisher test on the same samples, which assumes every tumour is equally likely to carry each alteration — heavily altered tumours then manufacture co-occurrence and lightly altered ones exclusivity: here 9 pairs are exclusive at DISCOVER q < 0.05 against 7 by Fisher. Exclusivity is a pattern, not a mechanism: synthetic lethality, redundant pathway hits, and subtypes with different drivers (the confounder stratifying removes, when the strata are the right ones) all produce it." + } + ] +} diff --git a/test/snapshots/exclusivity.sql b/test/snapshots/exclusivity.sql new file mode 100644 index 0000000..68dcfa7 --- /dev/null +++ b/test/snapshots/exclusivity.sql @@ -0,0 +1,100 @@ +-- cohort NSCLC +-- msk_impact_2017 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) + +-- view 0 exclusivity: exclusivity:columns:NSCLC · msk_impact_2017 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +sp AS ( + SELECT sample_unique_id AS suid, arrayStringConcat(arraySort(groupUniqArray(gene_panel_id)), '+') AS sig + FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') + AND sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_derived WHERE cancer_study_identifier = 'msk_impact_2017' AND patient_stable_id IN (SELECT pid FROM cohort)) + GROUP BY suid +), +cov AS ( + SELECT DISTINCT s.sig AS sig, pg.gene AS gene + FROM (SELECT DISTINCT sig, arrayJoin(splitByChar('+', sig)) AS panel FROM sp) s + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = s.panel +), +altc AS ( + SELECT DISTINCT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + INNER JOIN sp ON sp.suid = g.sample_unique_id + INNER JOIN cov ON cov.sig = sp.sig AND cov.gene = g.hugo_gene_symbol + WHERE g.cancer_study_identifier = 'msk_impact_2017' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sp) +), +strat AS ( + SELECT suid, '' AS v FROM sp +) +SELECT ifNull(strat.v, '') AS stratum, altc.gene AS gene, count() AS n +FROM altc LEFT JOIN strat ON strat.suid = altc.suid GROUP BY stratum, gene + +-- view 0 exclusivity: exclusivity:coverage:NSCLC · msk_impact_2017 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +sp AS ( + SELECT sample_unique_id AS suid, arrayStringConcat(arraySort(groupUniqArray(gene_panel_id)), '+') AS sig + FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') + AND sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_derived WHERE cancer_study_identifier = 'msk_impact_2017' AND patient_stable_id IN (SELECT pid FROM cohort)) + GROUP BY suid +), +cov AS ( + SELECT DISTINCT s.sig AS sig, pg.gene AS gene + FROM (SELECT DISTINCT sig, arrayJoin(splitByChar('+', sig)) AS panel FROM sp) s + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = s.panel +), +altc AS ( + SELECT DISTINCT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + INNER JOIN sp ON sp.suid = g.sample_unique_id + INNER JOIN cov ON cov.sig = sp.sig AND cov.gene = g.hugo_gene_symbol + WHERE g.cancer_study_identifier = 'msk_impact_2017' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sp) +), +strat AS ( + SELECT suid, '' AS v FROM sp +) +SELECT sig, gene FROM cov + +-- view 0 exclusivity: exclusivity:groups:NSCLC · msk_impact_2017 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +sp AS ( + SELECT sample_unique_id AS suid, arrayStringConcat(arraySort(groupUniqArray(gene_panel_id)), '+') AS sig + FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND alteration_type IN ('MUTATION_EXTENDED','COPY_NUMBER_ALTERATION','STRUCTURAL_VARIANT') + AND sample_unique_id IN (SELECT sample_unique_id FROM snapshot_db.sample_derived WHERE cancer_study_identifier = 'msk_impact_2017' AND patient_stable_id IN (SELECT pid FROM cohort)) + GROUP BY suid +), +cov AS ( + SELECT DISTINCT s.sig AS sig, pg.gene AS gene + FROM (SELECT DISTINCT sig, arrayJoin(splitByChar('+', sig)) AS panel FROM sp) s + JOIN snapshot_db.gene_panel_to_gene_derived pg ON pg.gene_panel_id = s.panel +), +altc AS ( + SELECT DISTINCT g.sample_unique_id AS suid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + INNER JOIN sp ON sp.suid = g.sample_unique_id + INNER JOIN cov ON cov.sig = sp.sig AND cov.gene = g.hugo_gene_symbol + WHERE g.cancer_study_identifier = 'msk_impact_2017' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND g.sample_unique_id IN (SELECT suid FROM sp) +), +strat AS ( + SELECT suid, '' AS v FROM sp +), +per AS (SELECT suid, count() AS r, maxIf(1, altc.gene = 'KRAS') + maxIf(2, altc.gene = 'EGFR') + maxIf(4, altc.gene = 'BRAF') + maxIf(8, altc.gene = 'STK11') + maxIf(16, altc.gene = 'KEAP1') + maxIf(32, altc.gene = 'TP53') AS amask FROM altc GROUP BY suid) +SELECT sp.sig AS sig, ifNull(strat.v, '') AS stratum, per.r AS r, per.amask AS amask, count() AS n +FROM sp LEFT JOIN per ON per.suid = sp.suid LEFT JOIN strat ON strat.suid = sp.suid +GROUP BY sig, stratum, r, amask diff --git a/test/snapshots/exclusivity.svg b/test/snapshots/exclusivity.svg new file mode 100644 index 0000000..6b706cc --- /dev/null +++ b/test/snapshots/exclusivity.svg @@ -0,0 +1,2 @@ + +Mutual exclusivity (DISCOVER) — NSCLCmutually exclusiveco-occurringdarker = smaller DISCOVER q; outlined = q < 0.05; the number is the qEGFRKRAS / EGFR — 19 altered in both of 1,680 profiled, 170 expected from each tumour's burden; DISCOVER exclusivity q 9.5e-57, co-occurrence q 1.00; Fisher q 5.7e-479.5e-57BRAFKRAS / BRAF — 15 altered in both of 1,680 profiled, 41.9 expected from each tumour's burden; DISCOVER exclusivity q 1.8e-6, co-occurrence q 1.00; Fisher q 0.00391.8e-6EGFR / BRAF — 14 altered in both of 1,680 profiled, 39.9 expected from each tumour's burden; DISCOVER exclusivity q 2.1e-6, co-occurrence q 1.00; Fisher q 0.00442.1e-6STK11KRAS / STK11 — 141 altered in both of 1,680 profiled, 113 expected from each tumour's burden; DISCOVER exclusivity q 1.00, co-occurrence q 0.02; Fisher q 2.1e-170.02EGFR / STK11 — 17 altered in both of 1,680 profiled, 107 expected from each tumour's burden; DISCOVER exclusivity q 8e-30, co-occurrence q 1.00; Fisher q 1.8e-208e-30BRAF / STK11 — 14 altered in both of 1,680 profiled, 27.2 expected from each tumour's burden; DISCOVER exclusivity q 0.0053, co-occurrence q 1.00; Fisher q 0.620.0053KEAP1KRAS / KEAP1 — 107 altered in both of 1,680 profiled, 104 expected from each tumour's burden; DISCOVER exclusivity q 0.77, co-occurrence q 1.00; Fisher q 6.6e-70.77EGFR / KEAP1 — 15 altered in both of 1,680 profiled, 98.5 expected from each tumour's burden; DISCOVER exclusivity q 6.3e-28, co-occurrence q 1.00; Fisher q 4e-196.3e-28BRAF / KEAP1 — 18 altered in both of 1,680 profiled, 25.1 expected from each tumour's burden; DISCOVER exclusivity q 0.11, co-occurrence q 1.00; Fisher q 0.490.11STK11 / KEAP1 — 159 altered in both of 1,680 profiled, 66.2 expected from each tumour's burden; DISCOVER exclusivity q 1.00, co-occurrence q 6.9e-25; Fisher q 9.1e-756.9e-25TP53KRAS / TP53 — 188 altered in both of 1,680 profiled, 317 expected from each tumour's burden; DISCOVER exclusivity q 4.7e-20, co-occurrence q 1.00; Fisher q 4.2e-154.7e-20EGFR / TP53 — 294 altered in both of 1,680 profiled, 300 expected from each tumour's burden; DISCOVER exclusivity q 0.45, co-occurrence q 1.00; Fisher q 1.8e-70.45BRAF / TP53 — 58 altered in both of 1,680 profiled, 70.3 expected from each tumour's burden; DISCOVER exclusivity q 0.1, co-occurrence q 1.00; Fisher q 0.620.1STK11 / TP53 — 125 altered in both of 1,680 profiled, 195 expected from each tumour's burden; DISCOVER exclusivity q 4.3e-9, co-occurrence q 1.00; Fisher q 7.5e-54.3e-9KEAP1 / TP53 — 140 altered in both of 1,680 profiled, 178 expected from each tumour's burden; DISCOVER exclusivity q 0.001, co-occurrence q 1.00; Fisher q 0.790.001KRASEGFRBRAFSTK11KEAP1Most significant pairs (12 of 15)pairbothexpectedprofiledDISCOVER qFisher qKRAS / EGFR191701,6809.5e-57 exclusive5.7e-47 exclusiveEGFR / STK11171071,6808e-30 exclusive1.8e-20 exclusiveEGFR / KEAP11598.51,6806.3e-28 exclusive4e-19 exclusiveSTK11 / KEAP115966.21,6806.9e-25 co-occurring9.1e-75 co-occurringKRAS / TP531883171,6804.7e-20 exclusive4.2e-15 exclusiveSTK11 / TP531251951,6804.3e-9 exclusive7.5e-5 exclusiveKRAS / BRAF1541.91,6801.8e-6 exclusive0.0039 exclusiveEGFR / BRAF1439.91,6802.1e-6 exclusive0.0044 exclusiveKEAP1 / TP531401781,6800.001 exclusive0.79 exclusiveBRAF / STK111427.21,6800.0053 exclusive0.62 exclusiveKRAS / STK111411131,6800.02 co-occurring2.1e-17 co-occurringBRAF / TP535870.31,6800.1 exclusive0.62 co-occurringWithin “NSCLC”, over its 1,680 samples with a gene panel (every sample of each patient; a sample without a panel record is left out, never read as wild-type), countingany alteration (mutation, amplification or deep deletion, structural variant). DISCOVER's background gives each sample and gene a probability of alteration, σ(a_sample +b_gene), fitted so that every sample's expected number of altered genes is its observed burden and every gene's expected number of altered samples its observed count— over the whole altered-gene matrix (410 genes, each sample's own panels: a gene off a sample's panel has no probability there); the fit matched every margin to within0.01% in 3 rounds. For a pair, the number of samples altered in both under independence given those probabilities is Poisson-binomial with p_i = p_iA·p_iB, over thesamples profiled for both; exclusivity is its lower tail at the observed count, co-occurrence its upper tail, each Benjamini–Hochberg across the 15 pairs, all exact. Besideit, the plain two-sided Fisher test on the same samples, which assumes every tumour is equally likely to carry each alteration — heavily altered tumours then manufactureco-occurrence and lightly altered ones exclusivity: here 9 pairs are exclusive at DISCOVER q < 0.05 against 7 by Fisher. Exclusivity is a pattern, not a mechanism:synthetic lethality, redundant pathway hits, and subtypes with different drivers (the confounder stratifying removes, when the strata are the right ones) all produce it. diff --git a/test/snapshots/exclusivity.txt b/test/snapshots/exclusivity.txt new file mode 100644 index 0000000..d26ef13 --- /dev/null +++ b/test/snapshots/exclusivity.txt @@ -0,0 +1,22 @@ +Analysis over msk_impact_2017 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- NSCLC: 1,567 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) + +Mutual exclusivity (DISCOVER background over 1,680 samples and 410 genes; any alteration; fit converged in 3 rounds) in NSCLC: + KRAS/EGFR: both 19 of 1680 profiled, 169.98 expected; DISCOVER exclusivity q=9.5e-57, co-occurrence q=1.0000; Fisher q=5.7e-47, log2 OR -3.69 + EGFR/STK11: both 17 of 1680 profiled, 107.25 expected; DISCOVER exclusivity q=8.0e-30, co-occurrence q=1.0000; Fisher q=1.8e-20, log2 OR -2.78 + EGFR/KEAP1: both 15 of 1680 profiled, 98.47 expected; DISCOVER exclusivity q=6.3e-28, co-occurrence q=1.0000; Fisher q=4.0e-19, log2 OR -2.80 + STK11/KEAP1: both 159 of 1680 profiled, 66.24 expected; DISCOVER exclusivity q=1.0000, co-occurrence q=6.9e-25; Fisher q=9.1e-75, log2 OR 4.10 + KRAS/TP53: both 188 of 1680 profiled, 316.93 expected; DISCOVER exclusivity q=4.7e-20, co-occurrence q=1.0000; Fisher q=4.2e-15, log2 OR -1.26 + STK11/TP53: both 125 of 1680 profiled, 194.74 expected; DISCOVER exclusivity q=4.3e-9, co-occurrence q=1.0000; Fisher q=7.5e-5, log2 OR -0.77 + KRAS/BRAF: both 15 of 1680 profiled, 41.9 expected; DISCOVER exclusivity q=1.8e-6, co-occurrence q=1.0000; Fisher q=0.0039, log2 OR -1.18 + EGFR/BRAF: both 14 of 1680 profiled, 39.93 expected; DISCOVER exclusivity q=2.1e-6, co-occurrence q=1.0000; Fisher q=0.0044, log2 OR -1.18 + KEAP1/TP53: both 140 of 1680 profiled, 178.11 expected; DISCOVER exclusivity q=0.0010, co-occurrence q=1.0000; Fisher q=0.7851, log2 OR -0.07 + BRAF/STK11: both 14 of 1680 profiled, 27.24 expected; DISCOVER exclusivity q=0.0053, co-occurrence q=1.0000; Fisher q=0.6203, log2 OR -0.31 + KRAS/STK11: both 141 of 1680 profiled, 112.78 expected; DISCOVER exclusivity q=1.0000, co-occurrence q=0.0196; Fisher q=2.1e-17, log2 OR 1.69 + BRAF/TP53: both 58 of 1680 profiled, 70.26 expected; DISCOVER exclusivity q=0.0999, co-occurrence q=1.0000; Fisher q=0.6155, log2 OR 0.20 + BRAF/KEAP1: both 18 of 1680 profiled, 25.12 expected; DISCOVER exclusivity q=0.1108, co-occurrence q=1.0000; Fisher q=0.4875, log2 OR 0.32 + EGFR/TP53: both 294 of 1680 profiled, 299.85 expected; DISCOVER exclusivity q=0.4461, co-occurrence q=1.0000; Fisher q=1.8e-7, log2 OR 0.88 + KRAS/KEAP1: both 107 of 1680 profiled, 103.52 expected; DISCOVER exclusivity q=0.7722, co-occurrence q=1.0000; Fisher q=6.6e-7, log2 OR 1.05 + CAVEAT (tell the user): Within “NSCLC”, over its 1,680 samples with a gene panel (every sample of each patient; a sample without a panel record is left out, never read as wild-type), counting any alteration (mutation, amplification or deep deletion, structural variant). DISCOVER's background gives each sample and gene a probability of alteration, σ(a_sample + b_gene), fitted so that every sample's expected number of altered genes is its observed burden and every gene's expected number of altered samples its observed count — over the whole altered-gene matrix (410 genes, each sample's own panels: a gene off a sample's panel has no probability there); the fit matched every margin to within 0.01% in 3 rounds. For a pair, the number of samples altered in both under independence given those probabilities is Poisson-binomial with p_i = p_iA·p_iB, over the samples profiled for both; exclusivity is its lower tail at the observed count, co-occurrence its upper tail, each Benjamini–Hochberg across the 15 pairs, all exact. Beside it, the plain two-sided Fisher test on the same samples, which assumes every tumour is equally likely to carry each alteration — heavily altered tumours then manufacture co-occurrence and lightly altered ones exclusivity: here 9 pairs are exclusive at DISCOVER q < 0.05 against 7 by Fisher. Exclusivity is a pattern, not a mechanism: synthetic lethality, redundant pathway hits, and subtypes with different drivers (the confounder stratifying removes, when the strata are the right ones) all produce it. diff --git a/test/snapshots/forest.json b/test/snapshots/forest.json new file mode 100644 index 0000000..0c244a4 --- /dev/null +++ b/test/snapshots/forest.json @@ -0,0 +1,359 @@ +{ + "study": "msk_impact_50k_2026", + "cohorts": { + "all patients": { + "count": 48179, + "text": "all patients in the study", + "studies": [ + "msk_impact_50k_2026" + ] + } + }, + "views": [ + { + "kind": "forest", + "cohorts": [ + "all patients" + ], + "forest": { + "exposure": "TP53 mutation status", + "exposedLabel": "TP53 mutated", + "endpoint": "OS", + "strata": "CANCER_TYPE", + "covariates": [], + "rows": [ + { + "label": "Non-Small Cell Lung Cancer", + "n": 6100, + "events": 2721, + "exposed": 3349, + "exposedEvents": 1647, + "beta": 0.36357731603738136, + "se": 0.039262836821375366, + "hr": 1.438466069102339, + "ciLow": 1.3319227433346783, + "ciHigh": 1.5535320215181594, + "p": 2.0426418555127666e-20, + "weight": 0.03920262198774657 + }, + { + "label": "Breast Cancer", + "n": 5177, + "events": 1756, + "exposed": 2178, + "exposedEvents": 920, + "beta": 0.6902533332799854, + "se": 0.04795177005291861, + "hr": 1.9942206717199686, + "ciLow": 1.8153342144924631, + "ciHigh": 2.1907349378236236, + "p": 5.583326742103068e-47, + "weight": 0.038962422865527195 + }, + { + "label": "Colorectal Cancer", + "n": 4581, + "events": 1490, + "exposed": 3398, + "exposedEvents": 1140, + "beta": 0.14562254982643497, + "se": 0.06111492394174943, + "hr": 1.1567594865704771, + "ciLow": 1.0261762339521532, + "ciHigh": 1.3039597541811556, + "p": 0.017183122844980508, + "weight": 0.03851534159617274 + }, + { + "label": "Prostate Cancer", + "n": 2607, + "events": 714, + "exposed": 730, + "exposedEvents": 317, + "beta": 0.9144626287506722, + "se": 0.07549567764587208, + "hr": 2.4954339170981394, + "ciLow": 2.1522069834530217, + "ciHigh": 2.893397560030587, + "p": 9.037679706388555e-34, + "weight": 0.03791992365256588 + }, + { + "label": "Pancreatic Cancer", + "n": 2437, + "events": 1443, + "exposed": 1684, + "exposedEvents": 1072, + "beta": 0.6197069180303343, + "se": 0.060840238242038686, + "hr": 1.8583833033847807, + "ciLow": 1.6494835555213765, + "ciHigh": 2.093739274174033, + "p": 2.2944840381907805e-24, + "weight": 0.0385256567260666 + }, + { + "label": "Endometrial Cancer", + "n": 2170, + "events": 580, + "exposed": 1000, + "exposedEvents": 394, + "beta": 1.0585768505642836, + "se": 0.08906737314942043, + "hr": 2.8822661735991737, + "ciLow": 2.4205819996900724, + "ciHigh": 3.432008622941795, + "p": 1.4142358829704257e-32, + "weight": 0.03726500148212853 + }, + { + "label": "Ovarian Cancer", + "n": 1903, + "events": 634, + "exposed": 1473, + "exposedEvents": 521, + "beta": 0.3730697030629234, + "se": 0.10413800676656422, + "hr": 1.4521855580864549, + "ciLow": 1.1840762803746703, + "ciHigh": 1.7810025672058725, + "p": 0.00034038051466398015, + "weight": 0.036444390694349765 + }, + { + "label": "Soft Tissue Sarcoma", + "n": 1871, + "events": 687, + "exposed": 419, + "exposedEvents": 175, + "beta": 0.3561303548110261, + "se": 0.08772616609564136, + "hr": 1.427793655905768, + "ciLow": 1.2022445028280229, + "ciHigh": 1.6956573467787965, + "p": 0.00004916354566515798, + "weight": 0.03733345822639026 + }, + { + "label": "Glioma", + "n": 1801, + "events": 944, + "exposed": 676, + "exposedEvents": 338, + "beta": -0.1546451408827191, + "se": 0.06802236717190452, + "hr": 0.8567191381459378, + "ciLow": 0.749786686667532, + "ciHigh": 0.9789019926823161, + "p": 0.02299935011166531, + "weight": 0.03824269137325659 + }, + { + "label": "Bladder Cancer", + "n": 1780, + "events": 525, + "exposed": 890, + "exposedEvents": 290, + "beta": 0.3188960090832397, + "se": 0.08790968692163965, + "hr": 1.375608266703034, + "ciLow": 1.1578863011844491, + "ciHigh": 1.6342693591642083, + "p": 0.00028613315409919363, + "weight": 0.03732413768388631 + }, + { + "label": "Melanoma", + "n": 1519, + "events": 533, + "exposed": 313, + "exposedEvents": 96, + "beta": -0.2539122821430805, + "se": 0.11276841497198939, + "hr": 0.775759847053319, + "ciLow": 0.621925906897574, + "ciHigh": 0.967644752575488, + "p": 0.024345923871667927, + "weight": 0.035935629063069525 + }, + { + "label": "Esophagogastric Cancer", + "n": 1512, + "events": 750, + "exposed": 1076, + "exposedEvents": 541, + "beta": 0.05844478342046986, + "se": 0.08151371612342567, + "hr": 1.0601864442092683, + "ciLow": 0.9036445756520477, + "ciHigh": 1.243846670217707, + "p": 0.4733782678550631, + "weight": 0.0376400558304606 + }, + { + "label": "Hepatobiliary Cancer", + "n": 1214, + "events": 739, + "exposed": 401, + "exposedEvents": 256, + "beta": 0.34642774212553135, + "se": 0.07754658655942237, + "hr": 1.414007316975658, + "ciLow": 1.214629661943243, + "ciHigh": 1.6461121896709674, + "p": 0.000007919450990096, + "weight": 0.03782649588344218 + }, + { + "label": "Cancer of Unknown Primary", + "n": 1172, + "events": 685, + "exposed": 602, + "exposedEvents": 376, + "beta": 0.2502729095476478, + "se": 0.07691931190240768, + "hr": 1.2843758873045914, + "ciLow": 1.1046337460657665, + "ciHigh": 1.4933650413675155, + "p": 0.0011390967016544121, + "weight": 0.037855287607828594 + }, + { + "label": "Renal Cell Carcinoma", + "n": 945, + "events": 313, + "exposed": 100, + "exposedEvents": 47, + "beta": 0.45987635713853164, + "se": 0.1584561996122483, + "hr": 1.5838781376620112, + "ciLow": 1.1610304302734333, + "ciHigh": 2.160727134751211, + "p": 0.0037051624242804095, + "weight": 0.032895797435589236 + }, + { + "label": "Thyroid Cancer", + "n": 736, + "events": 233, + "exposed": 122, + "exposedEvents": 76, + "beta": 1.4020475291346959, + "se": 0.14121858966977638, + "hr": 4.063511613189036, + "ciLow": 3.0810306863578134, + "ciHigh": 5.359286651585311, + "p": 3.138450062386882e-23, + "weight": 0.03409821117661216 + }, + { + "label": "Germ Cell Tumor", + "n": 557, + "events": 114, + "exposed": 65, + "exposedEvents": 33, + "beta": 1.4587654521726032, + "se": 0.20745923976348135, + "hr": 4.300646895389118, + "ciLow": 2.8638097361299635, + "ciHigh": 6.45837727467685, + "p": 2.0421329035157684e-12, + "weight": 0.029308315714585424 + }, + { + "label": "Head and Neck Cancer", + "n": 535, + "events": 318, + "exposed": 263, + "exposedEvents": 168, + "beta": 0.47828674436443186, + "se": 0.11300353430038591, + "hr": 1.6133080240489779, + "ciLow": 1.2927915686770903, + "ciHigh": 2.013288795752448, + "p": 0.000023111441304479575, + "weight": 0.03592140932514691 + }, + { + "label": "Bone Cancer", + "n": 460, + "events": 175, + "exposed": 90, + "exposedEvents": 46, + "beta": 0.7186471408686105, + "se": 0.172936585192702, + "hr": 2.0516557311079326, + "ciLow": 1.4618425524733525, + "ciHigh": 2.879442270897198, + "p": 0.00003245017453338904, + "weight": 0.031852375230101856 + }, + { + "label": "Gastrointestinal Stromal Tumor", + "n": 452, + "events": 82, + "exposed": 20, + "exposedEvents": 6, + "beta": 0.48685160039660885, + "se": 0.424235614896253, + "hr": 1.6271851178342733, + "ciLow": 0.7084789536768905, + "ciHigh": 3.737205451142963, + "p": 0.251134889528606, + "weight": 0.01599060287361155 + }, + { + "…": "10 more", + "sha256": "da267582bff571ee" + } + ], + "left": [ + { + "label": "CNS Cancer", + "n": 125, + "events": 17, + "reason": "no event among the exposed" + } + ], + "fixed": { + "beta": 0.4291387623978546, + "se": 0.016817146868705755, + "hr": 1.5359341495163585, + "ciLow": 1.4861334694741366, + "ciHigh": 1.587403661990936, + "p": 1.2468390377864348e-143 + }, + "random": { + "beta": 0.48735107985919407, + "se": 0.06941809173082336, + "hr": 1.6279980663911089, + "ciLow": 1.4209051813630573, + "ciHigh": 1.8652741498420842, + "p": 2.2104450631127374e-12 + }, + "tau2": 0.12138060207124625, + "q": 424.05302307338434, + "qDf": 29, + "qP": 9.756190227420332e-72, + "i2": 0.9316123257657299, + "n": 43375, + "events": 16953, + "notProfiled": 0, + "missing": 4135 + }, + "participation": { + "studies": [ + { + "study": "msk_impact_50k_2026", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": true, + "aggregateOnly": [] + }, + "caveat": "Hazard ratio of OS for TP53 mutated (exposed) against the rest, fit by Cox regression (Efron ties) within each CANCER_TYPE, univariate. 43,375 patients and 16,953 events had a time, the exposure and a CANCER_TYPE; 30 rows were estimated. Panel-aware: a patient not profiled for the gene is left out, never counted as unaltered. 4,135 patients lacked the endpoint, the stratum or a covariate and were left out. 669 patients whose samples carry different values of CANCER_TYPE were left out: they belong to no one stratum. “Other (34 strata)” pools 34 strata (each under 30 patients or 10 events, or past the 30 largest), fit with a baseline hazard of its own per stratum. Not estimated: CNS Cancer (no event among the exposed). Pooled by inverse variance: fixed effect, and DerSimonian–Laird random effects (τ² 0.121, I² 93%, Cochran's Q 424.1 on 29 df, p 9.8e-72). A high I² says the strata disagree beyond chance: the pooled estimate is then an average over different effects, not one effect. Association, not cause: the exposure travels with stage, treatment and other prognostic factors not adjusted for. Proportional hazards within each row is assumed, not checked." + } + ] +} diff --git a/test/snapshots/forest.sql b/test/snapshots/forest.sql new file mode 100644 index 0000000..0f8d649 --- /dev/null +++ b/test/snapshots/forest.sql @@ -0,0 +1,108 @@ +-- cohort all patients +-- msk_impact_50k_2026 +SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' + +-- view 0 forest: coxNotProfiled:OS · msk_impact_50k_2026 +SELECT countIf(c0 = 'Not profiled') AS n FROM ( +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026'), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +c0 AS ( +SELECT alt_case AS id, if(hits = 0 AND NOT (alt_case IN (SELECT profiled_id FROM (SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS profiled_id + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_50k_2026' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND (sp.gene_panel_id = 'WES' OR sp.gene_panel_id IN ( + SELECT gene_panel_id FROM snapshot_db.gene_panel_to_gene_derived WHERE gene = 'TP53'))))), 'Not profiled', v0) AS v FROM ( +SELECT replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS alt_case, if(countIf(g.variant_type = 'mutation') > 0, 'Mutated', 'Not mutated') AS v0, countIf(g.variant_type = 'mutation') AS hits +FROM snapshot_db.sample_derived sd +LEFT JOIN ( + SELECT sample_unique_id, mutation_type, variant_type, cna_alteration + FROM snapshot_db.genomic_event_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND hugo_gene_symbol IN ('TP53') + AND mutation_status != 'UNCALLED' +) g ON g.sample_unique_id = sd.sample_unique_id +WHERE sd.cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +GROUP BY alt_case) +), +c1 AS ( +SELECT id, if(countIf(pref = 1) > 0, anyIf(v, pref = 1), if(uniqExactIf(v, pref = 0) > 1, 'Multiple', anyIf(v, pref = 0))) AS v FROM ( +SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS id, attribute_value AS v, 1 AS pref + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name = 'CANCER_TYPE' AND attribute_value != '' + AND type = 'patient' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + UNION ALL +SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS id, attribute_value AS v, 0 AS pref + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name = 'CANCER_TYPE' AND attribute_value != '' + AND type = 'sample' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +) GROUP BY id +) +SELECT os.months AS m, if(os.status LIKE '1%', 1, 0) AS ev, c0.v AS c0, c1.v AS c1 +FROM os + INNER JOIN c0 ON c0.id = os.pid + INNER JOIN c1 ON c1.id = os.pid +WHERE os.months IS NOT NULL +) + +-- view 0 forest: coxRows:OS · msk_impact_50k_2026 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026'), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +c0 AS ( +SELECT id, v FROM ( +SELECT alt_case AS id, if(hits = 0 AND NOT (alt_case IN (SELECT profiled_id FROM (SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS profiled_id + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_50k_2026' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND (sp.gene_panel_id = 'WES' OR sp.gene_panel_id IN ( + SELECT gene_panel_id FROM snapshot_db.gene_panel_to_gene_derived WHERE gene = 'TP53'))))), 'Not profiled', v0) AS v FROM ( +SELECT replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') AS alt_case, if(countIf(g.variant_type = 'mutation') > 0, 'Mutated', 'Not mutated') AS v0, countIf(g.variant_type = 'mutation') AS hits +FROM snapshot_db.sample_derived sd +LEFT JOIN ( + SELECT sample_unique_id, mutation_type, variant_type, cna_alteration + FROM snapshot_db.genomic_event_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND hugo_gene_symbol IN ('TP53') + AND mutation_status != 'UNCALLED' +) g ON g.sample_unique_id = sd.sample_unique_id +WHERE sd.cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(sd.patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +GROUP BY alt_case) +) WHERE v != 'Not profiled' +), +c1 AS ( +SELECT id, if(countIf(pref = 1) > 0, anyIf(v, pref = 1), if(uniqExactIf(v, pref = 0) > 1, 'Multiple', anyIf(v, pref = 0))) AS v FROM ( +SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS id, attribute_value AS v, 1 AS pref + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name = 'CANCER_TYPE' AND attribute_value != '' + AND type = 'patient' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) + UNION ALL +SELECT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS id, attribute_value AS v, 0 AS pref + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND attribute_name = 'CANCER_TYPE' AND attribute_value != '' + AND type = 'sample' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +) GROUP BY id +) +SELECT os.months AS m, if(os.status LIKE '1%', 1, 0) AS ev, c0.v AS c0, c1.v AS c1 +FROM os + INNER JOIN c0 ON c0.id = os.pid + INNER JOIN c1 ON c1.id = os.pid +WHERE os.months IS NOT NULL diff --git a/test/snapshots/forest.svg b/test/snapshots/forest.svg new file mode 100644 index 0000000..3085cb7 --- /dev/null +++ b/test/snapshots/forest.svg @@ -0,0 +1,2 @@ + +Forest plot — TP53 mutated by CANCER_TYPECANCER_TYPEn (events)HR [95% CI]weight / pNon-Small Cell Lung Cancer6,100 (2,721)1.44 [1.33–1.55]3.9%Breast Cancer5,177 (1,756)1.99 [1.82–2.19]3.9%Colorectal Cancer4,581 (1,490)1.16 [1.03–1.30]3.9%Prostate Cancer2,607 (714)2.50 [2.15–2.89]3.8%Pancreatic Cancer2,437 (1,443)1.86 [1.65–2.09]3.9%Endometrial Cancer2,170 (580)2.88 [2.42–3.43]3.7%Ovarian Cancer1,903 (634)1.45 [1.18–1.78]3.6%Soft Tissue Sarcoma1,871 (687)1.43 [1.20–1.70]3.7%Glioma1,801 (944)0.86 [0.75–0.98]3.8%Bladder Cancer1,780 (525)1.38 [1.16–1.63]3.7%Melanoma1,519 (533)0.78 [0.62–0.97]3.6%Esophagogastric Cancer1,512 (750)1.06 [0.90–1.24]3.8%Hepatobiliary Cancer1,214 (739)1.41 [1.21–1.65]3.8%Cancer of Unknown Primary1,172 (685)1.28 [1.10–1.49]3.8%Renal Cell Carcinoma945 (313)1.58 [1.16–2.16]3.3%Thyroid Cancer736 (233)4.06 [3.08–5.36]3.4%Germ Cell Tumor557 (114)4.30 [2.86–6.46]2.9%Head and Neck Cancer535 (318)1.61 [1.29–2.01]3.6%Bone Cancer460 (175)2.05 [1.46–2.88]3.2%Gastrointestinal Stromal Tumor452 (82)1.63 [0.71–3.74]1.6%Small Cell Lung Cancer372 (242)1.01 [0.66–1.55]2.8%Skin Cancer, Non-Melanoma361 (117)1.56 [1.08–2.26]3.1%Mesothelioma343 (218)1.63 [1.09–2.42]3.0%Salivary Gland Cancer311 (153)1.16 [0.81–1.66]3.1%Uterine Sarcoma302 (143)1.32 [0.95–1.83]3.2%Cervical Cancer283 (103)1.16 [0.70–1.91]2.6%Appendiceal Cancer243 (88)2.13 [1.40–3.25]2.9%Gastrointestinal Neuroendocrine Tumor176 (73)7.65 [4.57–12.79]2.5%Small Bowel Cancer130 (59)0.95 [0.57–1.60]2.5%Other (34 strata)1,200 (304)1.95 [1.46–2.61]3.4%Random effects (DerSimonian–Laird)1.63 [1.42–1.87]2.2e-12Fixed effect1.54 [1.49–1.59]1.2e-14312510HR for TP53 mutated (log scale) — right of 1 is worse OSRows' last column is their weight in the random-effects pool; the pooled rows' is their p. Hazard ratio of OS for TP53 mutated (exposed) against the rest, fit by Coxregression (Efron ties) within each CANCER_TYPE, univariate. 43,375 patients and 16,953 events had a time, the exposure and a CANCER_TYPE; 30 rows wereestimated. Panel-aware: a patient not profiled for the gene is left out, never counted as unaltered. 4,135 patients lacked the endpoint, the stratum or a covariate andwere left out. 669 patients whose samples carry different values of CANCER_TYPE were left out: they belong to no one stratum. “Other (34 strata)” pools 34 strata(each under 30 patients or 10 events, or past the 30 largest), fit with a baseline hazard of its own per stratum. Not estimated: CNS Cancer (no event among theexposed). Pooled by inverse variance: fixed effect, and DerSimonian–Laird random effects (τ² 0.121, I² 93%, Cochran's Q 424.1 on 29 df, p 9.8e-72). A high I² saysthe strata disagree beyond chance: the pooled estimate is then an average over different effects, not one effect. Association, not cause: the exposure travels withstage, treatment and other prognostic factors not adjusted for. Proportional hazards within each row is assumed, not checked. diff --git a/test/snapshots/forest.txt b/test/snapshots/forest.txt new file mode 100644 index 0000000..4c97538 --- /dev/null +++ b/test/snapshots/forest.txt @@ -0,0 +1,41 @@ +Analysis over msk_impact_50k_2026 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- all patients: 48,179 patients — all patients in the study + +Forest plot: OS hazard ratio for TP53 mutated by CANCER_TYPE (n=43,375, 16,953 events): + Non-Small Cell Lung Cancer (n=6,100, 2721 events, 3349 exposed): HR 1.44 (95% CI 1.33-1.55) p=2.0e-20, weight 3.9% + Breast Cancer (n=5,177, 1756 events, 2178 exposed): HR 1.99 (95% CI 1.82-2.19) p=5.6e-47, weight 3.9% + Colorectal Cancer (n=4,581, 1490 events, 3398 exposed): HR 1.16 (95% CI 1.03-1.30) p=0.0172, weight 3.9% + Prostate Cancer (n=2,607, 714 events, 730 exposed): HR 2.50 (95% CI 2.15-2.89) p=9.0e-34, weight 3.8% + Pancreatic Cancer (n=2,437, 1443 events, 1684 exposed): HR 1.86 (95% CI 1.65-2.09) p=2.3e-24, weight 3.9% + Endometrial Cancer (n=2,170, 580 events, 1000 exposed): HR 2.88 (95% CI 2.42-3.43) p=1.4e-32, weight 3.7% + Ovarian Cancer (n=1,903, 634 events, 1473 exposed): HR 1.45 (95% CI 1.18-1.78) p=0.0003, weight 3.6% + Soft Tissue Sarcoma (n=1,871, 687 events, 419 exposed): HR 1.43 (95% CI 1.20-1.70) p=4.9e-5, weight 3.7% + Glioma (n=1,801, 944 events, 676 exposed): HR 0.86 (95% CI 0.75-0.98) p=0.0230, weight 3.8% + Bladder Cancer (n=1,780, 525 events, 890 exposed): HR 1.38 (95% CI 1.16-1.63) p=0.0003, weight 3.7% + Melanoma (n=1,519, 533 events, 313 exposed): HR 0.78 (95% CI 0.62-0.97) p=0.0243, weight 3.6% + Esophagogastric Cancer (n=1,512, 750 events, 1076 exposed): HR 1.06 (95% CI 0.90-1.24) p=0.4734, weight 3.8% + Hepatobiliary Cancer (n=1,214, 739 events, 401 exposed): HR 1.41 (95% CI 1.21-1.65) p=7.9e-6, weight 3.8% + Cancer of Unknown Primary (n=1,172, 685 events, 602 exposed): HR 1.28 (95% CI 1.10-1.49) p=0.0011, weight 3.8% + Renal Cell Carcinoma (n=945, 313 events, 100 exposed): HR 1.58 (95% CI 1.16-2.16) p=0.0037, weight 3.3% + Thyroid Cancer (n=736, 233 events, 122 exposed): HR 4.06 (95% CI 3.08-5.36) p=3.1e-23, weight 3.4% + Germ Cell Tumor (n=557, 114 events, 65 exposed): HR 4.30 (95% CI 2.86-6.46) p=2.0e-12, weight 2.9% + Head and Neck Cancer (n=535, 318 events, 263 exposed): HR 1.61 (95% CI 1.29-2.01) p=2.3e-5, weight 3.6% + Bone Cancer (n=460, 175 events, 90 exposed): HR 2.05 (95% CI 1.46-2.88) p=3.2e-5, weight 3.2% + Gastrointestinal Stromal Tumor (n=452, 82 events, 20 exposed): HR 1.63 (95% CI 0.71-3.74) p=0.2511, weight 1.6% + Small Cell Lung Cancer (n=372, 242 events, 336 exposed): HR 1.01 (95% CI 0.66-1.55) p=0.9600, weight 2.8% + Skin Cancer, Non-Melanoma (n=361, 117 events, 184 exposed): HR 1.56 (95% CI 1.08-2.26) p=0.0183, weight 3.1% + Mesothelioma (n=343, 218 events, 38 exposed): HR 1.63 (95% CI 1.09-2.42) p=0.0171, weight 3.0% + Salivary Gland Cancer (n=311, 153 events, 79 exposed): HR 1.16 (95% CI 0.81-1.66) p=0.4081, weight 3.1% + Uterine Sarcoma (n=302, 143 events, 125 exposed): HR 1.32 (95% CI 0.95-1.83) p=0.0992, weight 3.2% + Cervical Cancer (n=283, 103 events, 45 exposed): HR 1.16 (95% CI 0.70-1.91) p=0.5585, weight 2.6% + Appendiceal Cancer (n=243, 88 events, 84 exposed): HR 2.13 (95% CI 1.40-3.25) p=0.0004, weight 2.9% + Gastrointestinal Neuroendocrine Tumor (n=176, 73 events, 40 exposed): HR 7.65 (95% CI 4.57-12.79) p=9.0e-15, weight 2.5% + Small Bowel Cancer (n=130, 59 events, 71 exposed): HR 0.95 (95% CI 0.57-1.60) p=0.8593, weight 2.5% + Other (34 strata) (n=1,200, 304 events, 222 exposed): HR 1.95 (95% CI 1.46-2.61) p=5.6e-6, weight 3.4% + pooled, random effects (DerSimonian–Laird): HR 1.63 (95% CI 1.42-1.87) p=2.2e-12 + pooled, fixed effect: HR 1.54 (95% CI 1.49-1.59) p=1.2e-143 + heterogeneity: tau²=0.121, I²=93%, Q=424.1 on 29 df, p=9.8e-72 + not estimated: CNS Cancer (no event among the exposed) + CAVEAT (tell the user): Hazard ratio of OS for TP53 mutated (exposed) against the rest, fit by Cox regression (Efron ties) within each CANCER_TYPE, univariate. 43,375 patients and 16,953 events had a time, the exposure and a CANCER_TYPE; 30 rows were estimated. Panel-aware: a patient not profiled for the gene is left out, never counted as unaltered. 4,135 patients lacked the endpoint, the stratum or a covariate and were left out. 669 patients whose samples carry different values of CANCER_TYPE were left out: they belong to no one stratum. “Other (34 strata)” pools 34 strata (each under 30 patients or 10 events, or past the 30 largest), fit with a baseline hazard of its own per stratum. Not estimated: CNS Cancer (no event among the exposed). Pooled by inverse variance: fixed effect, and DerSimonian–Laird random effects (τ² 0.121, I² 93%, Cochran's Q 424.1 on 29 df, p 9.8e-72). A high I² says the strata disagree beyond chance: the pooled estimate is then an average over different effects, not one effect. Association, not cause: the exposure travels with stage, treatment and other prognostic factors not adjusted for. Proportional hazards within each row is assumed, not checked. diff --git a/test/snapshots/germline.json b/test/snapshots/germline.json new file mode 100644 index 0000000..e81cfe2 --- /dev/null +++ b/test/snapshots/germline.json @@ -0,0 +1,329 @@ +{ + "study": "prad_su2c_2019", + "cohorts": { + "mCRPC": { + "count": 429, + "text": "all patients in the study", + "studies": [ + "prad_su2c_2019" + ] + } + }, + "views": [ + { + "kind": "germline", + "cohorts": [ + "mCRPC" + ], + "germline": { + "genes": [ + "BRCA2", + "ATM", + "BRCA1", + "APC", + "CHEK2" + ], + "minCarriers": 2, + "chosen": "every gene with ≥ 2 carriers", + "panels": [ + { + "cohort": "mCRPC", + "patients": 429, + "genes": [ + { + "gene": "BRCA2", + "sequenced": 429, + "carriers": 17, + "outsidePanel": 0, + "carrierSomatic": 3, + "carrierCnaProfiled": 17, + "carrierDeletion": 0, + "carrierEither": 3, + "noncarriers": 412, + "noncarrierSomatic": 17, + "noncarrierCnaProfiled": 412, + "noncarrierDeletion": 13, + "noncarrierEither": 30, + "p": 0.1342974639635262, + "q": 0.3357436599088155 + }, + { + "gene": "ATM", + "sequenced": 429, + "carriers": 7, + "outsidePanel": 0, + "carrierSomatic": 1, + "carrierCnaProfiled": 7, + "carrierDeletion": 0, + "carrierEither": 1, + "noncarriers": 422, + "noncarrierSomatic": 18, + "noncarrierCnaProfiled": 422, + "noncarrierDeletion": 7, + "noncarrierEither": 24, + "p": 0.34515410909803607, + "q": 0.5752568484967268 + }, + { + "gene": "BRCA1", + "sequenced": 429, + "carriers": 6, + "outsidePanel": 0, + "carrierSomatic": 2, + "carrierCnaProfiled": 6, + "carrierDeletion": 0, + "carrierEither": 2, + "noncarriers": 423, + "noncarrierSomatic": 2, + "noncarrierCnaProfiled": 423, + "noncarrierDeletion": 0, + "noncarrierEither": 2, + "p": 0.000968115884055841, + "q": 0.004840579420279205 + }, + { + "gene": "APC", + "sequenced": 429, + "carriers": 2, + "outsidePanel": 0, + "carrierSomatic": 0, + "carrierCnaProfiled": 2, + "carrierDeletion": 0, + "carrierEither": 0, + "noncarriers": 427, + "noncarrierSomatic": 28, + "noncarrierCnaProfiled": 427, + "noncarrierDeletion": 5, + "noncarrierEither": 33, + "p": 1, + "q": 1 + }, + { + "gene": "CHEK2", + "sequenced": 429, + "carriers": 2, + "outsidePanel": 0, + "carrierSomatic": 0, + "carrierCnaProfiled": 2, + "carrierDeletion": 0, + "carrierEither": 0, + "noncarriers": 427, + "noncarrierSomatic": 4, + "noncarrierCnaProfiled": 427, + "noncarrierDeletion": 6, + "noncarrierEither": 10, + "p": 1, + "q": 1 + } + ] + } + ], + "survival": [ + { + "cohort": "mCRPC", + "gene": "BRCA2", + "curves": [ + { + "group": "BRCA2 carriers", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 9.99, + "s": 0.75 + }, + { + "t": 12.25, + "s": 0.375 + }, + { + "t": 33.02, + "s": 0 + } + ], + "median": 12.25, + "n": 5, + "events": 3, + "atRisk": [ + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 3, + 3, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + { + "…": "14 more", + "sha256": "39b3e42295cedea5" + } + ] + }, + { + "group": "non-carriers", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 2.76, + "s": 0.9915 + }, + { + "t": 2.92, + "s": 0.9831 + }, + { + "t": 3.15, + "s": 0.9746 + }, + { + "t": 3.52, + "s": 0.9661 + }, + { + "t": 3.65, + "s": 0.9576 + }, + { + "t": 3.94, + "s": 0.9492 + }, + { + "t": 4.27, + "s": 0.9321 + }, + { + "t": 4.53, + "s": 0.9235 + }, + { + "t": 5.88, + "s": 0.9149 + }, + { + "t": 6.04, + "s": 0.9062 + }, + { + "t": 8.44, + "s": 0.8972 + }, + { + "t": 8.94, + "s": 0.8881 + }, + { + "t": 9, + "s": 0.8791 + }, + { + "t": 9.23, + "s": 0.87 + }, + { + "t": 10.35, + "s": 0.8608 + }, + { + "t": 10.74, + "s": 0.8515 + }, + { + "t": 11.99, + "s": 0.842 + }, + { + "t": 12.02, + "s": 0.8326 + }, + { + "t": 12.12, + "s": 0.8231 + }, + { + "…": "50 more", + "sha256": "85b85dad542220bd" + } + ], + "median": 28.06, + "n": 123, + "events": 73, + "atRisk": [ + 123, + 123, + 120, + 116, + 111, + 107, + 106, + 102, + 101, + 98, + 95, + 91, + 89, + 85, + 83, + 79, + 77, + 71, + 66, + 63, + { + "…": "49 more", + "sha256": "ae65496c83275d59" + } + ] + } + ], + "logrank": { + "chi2": 0.9557, + "p": 0.32827950279908547, + "df": 1, + "observed": [ + 3, + 72 + ], + "expected": [ + 1.7, + 73.3 + ], + "groups": 2, + "strata": 1 + } + } + ], + "loh": "not assessed" + }, + "participation": { + "studies": [ + { + "study": "prad_su2c_2019", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Carriers are patients with a mutation the study marks GERMLINE (mutation status, case-insensitive) in the gene, counted per patient over all their samples and only among the patients sequenced for the gene (a tumour mutation panel listing it, or whole-exome); none fell outside a panel. A study reports germline variants only for the patients it analysed and consented for it, and the warehouse cannot tell a patient tested and negative from one never tested, so the carrier share is of patients sequenced, not of patients germline-tested: a lower bound where germline testing was partial. A somatic second hit in a carrier is another mutation in the same gene that is not germline (any called status but GERMLINE; uncalled and off-panel calls excluded) or a deep deletion (copy number −2, among patients profiled for copy number). Loss of heterozygosity — the commonest second hit — needs allele-specific copy number and is not assessed; a deep deletion in a carrier is rare, since it removes the germline allele too. So the second-hit rate is a floor. Non-carriers' rate is the same somatic definition among the sequenced patients without a germline call; Fisher's exact test of carriers' against non-carriers' rate per gene, Benjamini–Hochberg over the genes of each cohort. Germline calls are not pathogenicity-filtered here: whatever the study deposited as germline is counted. Overall survival of the patients sequenced for BRCA2, carriers against non-carriers, from OS_MONTHS / OS_STATUS (Kaplan–Meier, log-rank); not corrected for left truncation or for cancer type and stage. " + } + ] +} diff --git a/test/snapshots/germline.sql b/test/snapshots/germline.sql new file mode 100644 index 0000000..d615296 --- /dev/null +++ b/test/snapshots/germline.sql @@ -0,0 +1,118 @@ +-- cohort mCRPC +-- prad_su2c_2019 +SELECT DISTINCT replaceOne(patient_unique_id, 'prad_su2c_2019_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'prad_su2c_2019' + +-- view 0 germline: germline:ascn · prad_su2c_2019 +SELECT DISTINCT cs.cancer_study_identifier AS study FROM snapshot_db.allele_specific_copy_number a +JOIN snapshot_db.genetic_profile gp ON gp.genetic_profile_id = a.genetic_profile_id +JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = gp.cancer_study_id ORDER BY study + +-- view 0 germline: germline:carriers:mCRPC · prad_su2c_2019 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'prad_su2c_2019_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'prad_su2c_2019') +SELECT g.hugo_gene_symbol AS gene, uniqExact(replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '')) AS carriers +FROM snapshot_db.genomic_event_derived g +WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.variant_type = 'mutation' AND lowerUTF8(trimBoth(g.mutation_status)) = 'germline' + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +GROUP BY gene ORDER BY carriers DESC, gene LIMIT 500 + +-- view 0 germline: germline:counts:mCRPC · prad_su2c_2019 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'prad_su2c_2019_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'prad_su2c_2019'), +mprofp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'prad_su2c_2019' AND sp.alteration_type = 'MUTATION_EXTENDED' AND replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +mprof AS ( + SELECT DISTINCT x.pid AS pid, gp.gene AS gene FROM mprofp x JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = x.panel WHERE gp.gene IN ('BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2') + UNION DISTINCT + SELECT pid, arrayJoin(['BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2']) AS gene FROM mprofp WHERE panel = 'WES' +), +cprofp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'prad_su2c_2019' AND sp.alteration_type = 'COPY_NUMBER_ALTERATION' AND replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +cprof AS ( + SELECT DISTINCT x.pid AS pid, gp.gene AS gene FROM cprofp x JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = x.panel WHERE gp.gene IN ('BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2') + UNION DISTINCT + SELECT pid, arrayJoin(['BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2']) AS gene FROM cprofp WHERE panel = 'WES' +), +germ AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') AS pid, g.hugo_gene_symbol AS gene FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.hugo_gene_symbol IN ('BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2') AND g.variant_type = 'mutation' AND lowerUTF8(trimBoth(g.mutation_status)) = 'germline' + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +som AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') AS pid, g.hugo_gene_symbol AS gene FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.hugo_gene_symbol IN ('BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2') AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT (lowerUTF8(trimBoth(g.mutation_status)) = 'germline') AND NOT g.off_panel + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +del AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') AS pid, g.hugo_gene_symbol AS gene FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.hugo_gene_symbol IN ('BRCA2', 'ATM', 'BRCA1', 'APC', 'CHEK2') AND g.variant_type = 'cna' AND g.cna_alteration <= -2 AND NOT g.off_panel + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +base AS (SELECT pid, gene FROM mprof UNION DISTINCT SELECT pid, gene FROM germ), +fl AS ( + SELECT gene, toUInt8((pid, gene) IN (SELECT pid, gene FROM mprof)) AS mp, toUInt8((pid, gene) IN (SELECT pid, gene FROM germ)) AS carrier, + toUInt8((pid, gene) IN (SELECT pid, gene FROM som)) AS som, toUInt8((pid, gene) IN (SELECT pid, gene FROM cprof)) AS cp, + toUInt8((pid, gene) IN (SELECT pid, gene FROM del)) AS del + FROM base +) +SELECT gene, mp, carrier, som, cp, del, count() AS n FROM fl GROUP BY gene, mp, carrier, som, cp, del + +-- view 0 germline: germline:survival:mCRPC · prad_su2c_2019 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'prad_su2c_2019_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'prad_su2c_2019'), +mprofp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'prad_su2c_2019' AND sp.alteration_type = 'MUTATION_EXTENDED' AND replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +mprof AS ( + SELECT DISTINCT x.pid AS pid, gp.gene AS gene FROM mprofp x JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = x.panel WHERE gp.gene IN ('BRCA2') + UNION DISTINCT + SELECT pid, arrayJoin(['BRCA2']) AS gene FROM mprofp WHERE panel = 'WES' +), +cprofp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'prad_su2c_2019' AND sp.alteration_type = 'COPY_NUMBER_ALTERATION' AND replaceOne(sd.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +cprof AS ( + SELECT DISTINCT x.pid AS pid, gp.gene AS gene FROM cprofp x JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = x.panel WHERE gp.gene IN ('BRCA2') + UNION DISTINCT + SELECT pid, arrayJoin(['BRCA2']) AS gene FROM cprofp WHERE panel = 'WES' +), +germ AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') AS pid, g.hugo_gene_symbol AS gene FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.hugo_gene_symbol IN ('BRCA2') AND g.variant_type = 'mutation' AND lowerUTF8(trimBoth(g.mutation_status)) = 'germline' + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +som AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') AS pid, g.hugo_gene_symbol AS gene FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.hugo_gene_symbol IN ('BRCA2') AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT (lowerUTF8(trimBoth(g.mutation_status)) = 'germline') AND NOT g.off_panel + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +del AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') AS pid, g.hugo_gene_symbol AS gene FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'prad_su2c_2019' AND g.hugo_gene_symbol IN ('BRCA2') AND g.variant_type = 'cna' AND g.cna_alteration <= -2 AND NOT g.off_panel + AND replaceOne(g.patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) +), +base AS (SELECT pid, gene FROM mprof UNION DISTINCT SELECT pid, gene FROM germ), +os AS ( + SELECT replaceOne(patient_unique_id, 'prad_su2c_2019_', '') AS pid, if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'prad_su2c_2019' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'prad_su2c_2019_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +sv AS ( + SELECT toUInt8(mprof.pid IN (SELECT pid FROM germ)) AS carrier, os.months AS months, os.dead AS dead + FROM mprof INNER JOIN os ON os.pid = mprof.pid WHERE os.months IS NOT NULL AND os.months >= 0 +) +SELECT carrier, round(months, 2) AS t, dead, count() AS n FROM sv GROUP BY carrier, t, dead ORDER BY t diff --git a/test/snapshots/germline.svg b/test/snapshots/germline.svg new file mode 100644 index 0000000..25d21c9 --- /dev/null +++ b/test/snapshots/germline.svg @@ -0,0 +1,2 @@ + +Germline carriers and second hits — 5 genesCarriers: somatic second hit (mutation or deep deletion)Non-carriers: somatic mutation or deep deletionmCRPC · 429 patientsCarriers / sequencedSecond hit, % of carriers / non-carriersBRCA217 of 429 (4.0%)BRCA2 carriers: 3 of 17 (17.6%)17.6% (3)BRCA2 non-carriers: 30 of 412 (7.3%)7.3%q 0.34ATM7 of 429 (1.6%)ATM carriers: 1 of 7 (14.3%)14.3% (1)ATM non-carriers: 24 of 422 (5.7%)5.7%q 0.58BRCA16 of 429 (1.4%)BRCA1 carriers: 2 of 6 (33.3%)33.3% (2)BRCA1 non-carriers: 2 of 423 (0.5%)0.5%q 0.0048APC2 of 429 (0.5%)APC carriers: 0 of 2 (0%)0% (0)APC non-carriers: 33 of 427 (7.7%)7.7%q 1.00CHEK22 of 429 (0.5%)CHEK2 carriers: 0 of 2 (0%)0% (0)CHEK2 non-carriers: 10 of 427 (2.3%)2.3%q 1.000%10%20%30%40%Share with a somatic hit in the geneOverall survival by BRCA2 germline status — mCRPC0%25%50%75%100%Overall survival01224364860Months (OS_MONTHS)Number at riskBRCA2 carriersBRCA2 carriers: 5 at risk at 0 months5BRCA2 carriers: 2 at risk at 12 months2BRCA2 carriers: 1 at risk at 24 months1BRCA2 carriers: 0 at risk at 36 months0BRCA2 carriers: 0 at risk at 48 months0BRCA2 carriers: 0 at risk at 60 months0non-carriersnon-carriers: 123 at risk at 0 months123non-carriers: 89 at risk at 12 months89non-carriers: 46 at risk at 24 months46non-carriers: 11 at risk at 36 months11non-carriers: 3 at risk at 48 months3non-carriers: 1 at risk at 60 months1BRCA2 carriersn = 5 · 3 deaths · median 12.3 monon-carriersn = 123 · 73 deaths · median 28.1 moLog-rank χ² = 0.956 (1 df) · p = 0.33LOH: not assessed. Carriers are patients with a mutation the study marks GERMLINE (mutation status, case-insensitive) in the gene, counted per patient over all theirsamples and only among the patients sequenced for the gene (a tumour mutation panel listing it, or whole-exome); none fell outside a panel. A study reports germlinevariants only for the patients it analysed and consented for it, and the warehouse cannot tell a patient tested and negative from one never tested, so the carrier share is ofpatients sequenced, not of patients germline-tested: a lower bound where germline testing was partial. A somatic second hit in a carrier is another mutation in the samegene that is not germline (any called status but GERMLINE; uncalled and off-panel calls excluded) or a deep deletion (copy number −2, among patients profiled for copynumber). Loss of heterozygosity — the commonest second hit — needs allele-specific copy number and is not assessed; a deep deletion in a carrier is rare, since itremoves the germline allele too. So the second-hit rate is a floor. Non-carriers' rate is the same somatic definition among the sequenced patients without a germline call;Fisher's exact test of carriers' against non-carriers' rate per gene, Benjamini–Hochberg over the genes of each cohort. Germline calls are not pathogenicity-filtered here:whatever the study deposited as germline is counted. Overall survival of the patients sequenced for BRCA2, carriers against non-carriers, from OS_MONTHS /OS_STATUS (Kaplan–Meier, log-rank); not corrected for left truncation or for cancer type and stage. diff --git a/test/snapshots/germline.txt b/test/snapshots/germline.txt new file mode 100644 index 0000000..36727b5 --- /dev/null +++ b/test/snapshots/germline.txt @@ -0,0 +1,14 @@ +Analysis over prad_su2c_2019 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- mCRPC: 429 patients — all patients in the study + +Germline carriers and somatic second hits (every gene with ≥ 2 carriers; LOH not assessed): + mCRPC (429 patients): + BRCA2: 17/429 sequenced carry a germline call (4.0%); second hit in carriers 3/17 (17.6%: 3 somatic mutation, 0/17 deep deletion) vs non-carriers 30/412 (7.3%), Fisher p=0.1343 q=0.3357 + ATM: 7/429 sequenced carry a germline call (1.6%); second hit in carriers 1/7 (14.3%: 1 somatic mutation, 0/7 deep deletion) vs non-carriers 24/422 (5.7%), Fisher p=0.3452 q=0.5753 + BRCA1: 6/429 sequenced carry a germline call (1.4%); second hit in carriers 2/6 (33.3%: 2 somatic mutation, 0/6 deep deletion) vs non-carriers 2/423 (0.5%), Fisher p=0.0010 q=0.0048 + APC: 2/429 sequenced carry a germline call (0.5%); second hit in carriers 0/2 (0.0%: 0 somatic mutation, 0/2 deep deletion) vs non-carriers 33/427 (7.7%), Fisher p=1.0000 q=1.0000 + CHEK2: 2/429 sequenced carry a germline call (0.5%); second hit in carriers 0/2 (0.0%: 0 somatic mutation, 0/2 deep deletion) vs non-carriers 10/427 (2.3%), Fisher p=1.0000 q=1.0000 + OS by BRCA2 carrier status, mCRPC: BRCA2 carriers n=5, 3 deaths, median 12.3 mo; non-carriers n=123, 73 deaths, median 28.1 mo; log-rank p=0.3283 + CAVEAT (tell the user): Carriers are patients with a mutation the study marks GERMLINE (mutation status, case-insensitive) in the gene, counted per patient over all their samples and only among the patients sequenced for the gene (a tumour mutation panel listing it, or whole-exome); none fell outside a panel. A study reports germline variants only for the patients it analysed and consented for it, and the warehouse cannot tell a patient tested and negative from one never tested, so the carrier share is of patients sequenced, not of patients germline-tested: a lower bound where germline testing was partial. A somatic second hit in a carrier is another mutation in the same gene that is not germline (any called status but GERMLINE; uncalled and off-panel calls excluded) or a deep deletion (copy number −2, among patients profiled for copy number). Loss of heterozygosity — the commonest second hit — needs allele-specific copy number and is not assessed; a deep deletion in a carrier is rare, since it removes the germline allele too. So the second-hit rate is a floor. Non-carriers' rate is the same somatic definition among the sequenced patients without a germline call; Fisher's exact test of carriers' against non-carriers' rate per gene, Benjamini–Hochberg over the genes of each cohort. Germline calls are not pathogenicity-filtered here: whatever the study deposited as germline is counted. Overall survival of the patients sequenced for BRCA2, carriers against non-carriers, from OS_MONTHS / OS_STATUS (Kaplan–Meier, log-rank); not corrected for left truncation or for cancer type and stage. diff --git a/test/snapshots/hla.json b/test/snapshots/hla.json new file mode 100644 index 0000000..68a9a9b --- /dev/null +++ b/test/snapshots/hla.json @@ -0,0 +1,359 @@ +{ + "study": "skcm_dfci_2015", + "cohorts": { + "melanoma on checkpoint blockade": { + "count": 212, + "text": "all patients in the study — across 3 studies", + "studies": [ + "skcm_dfci_2015", + "skcm_mskcc_2014", + "mel_ucla_2016" + ] + } + }, + "views": [ + { + "kind": "hla", + "cohorts": [ + "melanoma on checkpoint blockade" + ], + "hla": { + "resolution": 2, + "endpoint": "OS", + "covariates": [], + "panels": [ + { + "cohort": "melanoma on checkpoint blockade", + "patients": 212, + "anyTyped": 212, + "typed": 212, + "homozygous": [ + 165, + 36, + 7, + 4 + ], + "loci": [ + { + "locus": "A", + "typed": 212, + "homozygous": 27 + }, + { + "locus": "B", + "typed": 212, + "homozygous": 13 + }, + { + "locus": "C", + "typed": 212, + "homozygous": 22 + } + ], + "coarser": 0, + "lohEvaluated": 0, + "loh": 0, + "byHomozygosity": { + "curves": [ + { + "label": "homozygous at ≥1 locus", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1.5, + "s": 0.9787 + }, + { + "t": 1.8, + "s": 0.9574 + }, + { + "t": 2.8, + "s": 0.9362 + }, + { + "t": 2.866666667, + "s": 0.9149 + }, + { + "t": 3.3, + "s": 0.8936 + }, + { + "t": 3.4, + "s": 0.8723 + }, + { + "t": 3.766666667, + "s": 0.8511 + }, + { + "t": 4.633333333, + "s": 0.8298 + }, + { + "t": 4.833333333, + "s": 0.8085 + }, + { + "t": 4.9, + "s": 0.7872 + }, + { + "t": 5.066666667, + "s": 0.766 + }, + { + "t": 5.1, + "s": 0.7447 + }, + { + "t": 6.1, + "s": 0.7234 + }, + { + "t": 6.180822, + "s": 0.7021 + }, + { + "t": 7.033333333, + "s": 0.6596 + }, + { + "t": 7.466666667, + "s": 0.6383 + }, + { + "t": 8.566666667, + "s": 0.617 + }, + { + "t": 8.6, + "s": 0.5957 + }, + { + "t": 8.8, + "s": 0.5745 + }, + { + "…": "12 more", + "sha256": "2d5da6d2cf50d444" + } + ], + "median": 12.13151, + "n": 47, + "events": 32, + "atRisk": [ + 47, + 47, + 45, + 43, + 40, + 37, + 35, + 33, + 30, + 27, + 27, + 24, + 23, + 21, + 21, + 20, + 18, + 18, + 18, + 17, + { + "…": "53 more", + "sha256": "306528a48f3ec966" + } + ] + }, + { + "label": "heterozygous at all", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1.133333333, + "s": 0.9939 + }, + { + "t": 1.233333333, + "s": 0.9878 + }, + { + "t": 1.266666667, + "s": 0.9817 + }, + { + "t": 1.6666666669999999, + "s": 0.9756 + }, + { + "t": 2.166666667, + "s": 0.9695 + }, + { + "t": 2.233333333, + "s": 0.9633 + }, + { + "t": 2.333333333, + "s": 0.9572 + }, + { + "t": 2.5, + "s": 0.9511 + }, + { + "t": 2.566666667, + "s": 0.9449 + }, + { + "t": 2.633333333, + "s": 0.9388 + }, + { + "t": 2.7, + "s": 0.9265 + }, + { + "t": 2.766666667, + "s": 0.9204 + }, + { + "t": 2.9666666669999997, + "s": 0.9143 + }, + { + "t": 3, + "s": 0.9081 + }, + { + "t": 3.3, + "s": 0.902 + }, + { + "t": 3.333333333, + "s": 0.8958 + }, + { + "t": 3.4666666669999997, + "s": 0.8896 + }, + { + "t": 3.5333333330000003, + "s": 0.8834 + }, + { + "t": 3.6, + "s": 0.8773 + }, + { + "…": "75 more", + "sha256": "788f9f94725dbb94" + } + ], + "median": 21.73333333, + "n": 164, + "events": 103, + "atRisk": [ + 164, + 164, + 159, + 149, + 141, + 133, + 126, + 119, + 115, + 111, + 104, + 101, + 99, + 97, + 94, + 88, + 86, + 85, + 83, + 80, + { + "…": "75 more", + "sha256": "a7b734920b09db45" + } + ] + } + ], + "logrank": { + "chi2": 0.9614, + "p": 0.32683744172010465, + "df": 1, + "observed": [ + 32, + 102 + ], + "expected": [ + 27.5, + 106.5 + ], + "variance": 21.4399, + "strata": 3 + }, + "hr": { + "hr": 1.2214354639797707, + "ciLow": 0.8179634673288572, + "ciHigh": 1.8239257036007288, + "p": 0.3281950803456205, + "n": 211, + "events": 135 + }, + "adjusted": null + }, + "byLoh": null, + "byLohWhy": "no patient here has an HLA LOH call with the endpoint", + "noOutcome": 1, + "noCovariate": 0 + } + ] + }, + "participation": { + "studies": [ + { + "study": "skcm_dfci_2015", + "mode": "patient-level", + "instance": "primary", + "source": 0 + }, + { + "study": "skcm_mskcc_2014", + "mode": "patient-level", + "instance": "primary", + "source": 0 + }, + { + "study": "mel_ucla_2016", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": true, + "aggregateOnly": [] + }, + "studies": [ + "skcm_dfci_2015", + "skcm_mskcc_2014", + "mel_ucla_2016" + ], + "caveat": "A locus is homozygous when its two alleles match at the protein level (2 fields, e.g. A*02:01); homozygosity is counted over patients typed at all three class I loci (HLA-A, -B, -C). melanoma on checkpoint blockade: 212 patients fully typed of 212 patients, 22.2% homozygous at ≥1 locus; HLA LOH in 0 patients of 0 with a call (—). HLA LOH is any of HLA_A1_LOH/HLA_A2_LOH/HLA_B1_LOH/HLA_B2_LOH/HLA_C1_LOH/HLA_C2_LOH/HLA_LOH = Loss/Yes/LOH/True in any of the patient's samples; a patient with no call in any sample is left out of that split, and a homozygous locus cannot show LOH (LOHHLA calls only heterozygous loci), so the LOH split is not independent of the genotype. OS is as each study records it, from its own origin, which is a checkpoint-blockade start only in a study of treated patients that measures from it (the melanoma immunotherapy studies do; MSK-IMPACT measures from diagnosis or sequencing). Chowell et al. report worse survival with homozygosity in patients treated with checkpoint blockade, so in a cohort not restricted to them this reads prognosis, not response. Hazard ratios are Cox (Efron ties), exposed against not, stratified by study, with the log-rank test stratified the same way; no covariates: the HR is unadjusted, and tumour mutational burden and cancer type are known to confound it. HLA supertypes and evolutionary divergence (HED) are not computed. Pooled across 3 studies whose typing methods and resolutions differ. Computed from per-patient rows inside each patient-level study." + } + ] +} diff --git a/test/snapshots/hla.sql b/test/snapshots/hla.sql new file mode 100644 index 0000000..6f30bcf --- /dev/null +++ b/test/snapshots/hla.sql @@ -0,0 +1,141 @@ +-- cohort melanoma on checkpoint blockade +-- skcm_dfci_2015 +SELECT DISTINCT replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'skcm_dfci_2015' + +-- combined at the analysis layer: each study is evaluated separately against its own +-- instance and the counts are ADDED; patient overlap across studies is not deduplicated. + +-- skcm_mskcc_2014 +SELECT DISTINCT replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'skcm_mskcc_2014' + +-- combined at the analysis layer: each study is evaluated separately against its own +-- instance and the counts are ADDED; patient overlap across studies is not deduplicated. + +-- mel_ucla_2016 +SELECT DISTINCT replaceOne(patient_unique_id, 'mel_ucla_2016_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'mel_ucla_2016' + +-- view 0 hla: hla:rows:melanoma on checkpoint blockade · mel_ucla_2016 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'mel_ucla_2016_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'mel_ucla_2016'), +gt AS ( + SELECT pid, [if(anyIf(v, k IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES')) != '', anyIf(v, k IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_A', 'HLA-A')))[1]), + if(anyIf(v, k IN ('HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES')) != '', anyIf(v, k IN ('HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_A', 'HLA-A')))[2]), + if(anyIf(v, k IN ('HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES')) != '', anyIf(v, k IN ('HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_B', 'HLA-B')))[1]), + if(anyIf(v, k IN ('HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES')) != '', anyIf(v, k IN ('HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_B', 'HLA-B')))[2]), + if(anyIf(v, k IN ('HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES')) != '', anyIf(v, k IN ('HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_C', 'HLA-C')))[1]), + if(anyIf(v, k IN ('HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES')) != '', anyIf(v, k IN ('HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_C', 'HLA-C')))[2])] AS raw + FROM (SELECT replaceOne(patient_unique_id, 'mel_ucla_2016_', '') AS pid, attribute_name AS k, trimBoth(attribute_value) AS v FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'mel_ucla_2016' AND attribute_name IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES', 'HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES', 'HLA_A', 'HLA-A', 'HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES', 'HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES', 'HLA_B', 'HLA-B', 'HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES', 'HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES', 'HLA_C', 'HLA-C') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'mel_ucla_2016_', '') IN (SELECT pid FROM cohort)) + GROUP BY pid +), +keys AS ( + SELECT pid, arrayMap(x -> if(x = '', '', arrayStringConcat(arraySlice(if(match(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), '[_:]'), splitByRegexp('[_:]', replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', '')), [substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 1, 2), substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 3)]), 1, 2), ':')), raw) AS k, + arrayMin(arrayMap(x -> if(x = '', 99, length(if(match(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), '[_:]'), splitByRegexp('[_:]', replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', '')), [substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 1, 2), substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 3)]))), raw)) AS nf0 + FROM gt +), +hom AS (SELECT pid, if(k[1] = '' OR k[2] = '', NULL, toUInt8(k[1] = k[2])) AS hA, if(k[3] = '' OR k[4] = '', NULL, toUInt8(k[3] = k[4])) AS hB, if(k[5] = '' OR k[6] = '', NULL, toUInt8(k[5] = k[6])) AS hC, toNullable(nf0) AS nf FROM keys), +loh AS ( + SELECT replaceOne(patient_unique_id, 'mel_ucla_2016_', '') AS pid, toNullable(max(lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Loss')), lowerUTF8(trimBoth('Yes')), lowerUTF8(trimBoth('LOH')), lowerUTF8(trimBoth('True'))))) AS loss + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'mel_ucla_2016' AND attribute_name IN ('HLA_A1_LOH', 'HLA_A2_LOH', 'HLA_B1_LOH', 'HLA_B2_LOH', 'HLA_C1_LOH', 'HLA_C2_LOH', 'HLA_LOH') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'mel_ucla_2016_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +os AS ( + SELECT replaceOne(patient_unique_id, 'mel_ucla_2016_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'mel_ucla_2016' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'mel_ucla_2016_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT hom.hA AS hA, hom.hB AS hB, hom.hC AS hC, hom.nf AS nf, loh.loss AS loss, + if(os.months >= 0, os.months, NULL) AS m, if(os.status LIKE '1%', 1, 0) AS ev +FROM cohort LEFT JOIN hom ON hom.pid = cohort.pid LEFT JOIN loh ON loh.pid = cohort.pid LEFT JOIN os ON os.pid = cohort.pid + +-- view 0 hla: hla:rows:melanoma on checkpoint blockade · skcm_dfci_2015 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'skcm_dfci_2015'), +gt AS ( + SELECT pid, [if(anyIf(v, k IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES')) != '', anyIf(v, k IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_A', 'HLA-A')))[1]), + if(anyIf(v, k IN ('HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES')) != '', anyIf(v, k IN ('HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_A', 'HLA-A')))[2]), + if(anyIf(v, k IN ('HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES')) != '', anyIf(v, k IN ('HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_B', 'HLA-B')))[1]), + if(anyIf(v, k IN ('HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES')) != '', anyIf(v, k IN ('HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_B', 'HLA-B')))[2]), + if(anyIf(v, k IN ('HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES')) != '', anyIf(v, k IN ('HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_C', 'HLA-C')))[1]), + if(anyIf(v, k IN ('HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES')) != '', anyIf(v, k IN ('HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_C', 'HLA-C')))[2])] AS raw + FROM (SELECT replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') AS pid, attribute_name AS k, trimBoth(attribute_value) AS v FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'skcm_dfci_2015' AND attribute_name IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES', 'HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES', 'HLA_A', 'HLA-A', 'HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES', 'HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES', 'HLA_B', 'HLA-B', 'HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES', 'HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES', 'HLA_C', 'HLA-C') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') IN (SELECT pid FROM cohort)) + GROUP BY pid +), +keys AS ( + SELECT pid, arrayMap(x -> if(x = '', '', arrayStringConcat(arraySlice(if(match(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), '[_:]'), splitByRegexp('[_:]', replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', '')), [substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 1, 2), substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 3)]), 1, 2), ':')), raw) AS k, + arrayMin(arrayMap(x -> if(x = '', 99, length(if(match(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), '[_:]'), splitByRegexp('[_:]', replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', '')), [substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 1, 2), substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 3)]))), raw)) AS nf0 + FROM gt +), +hom AS (SELECT pid, if(k[1] = '' OR k[2] = '', NULL, toUInt8(k[1] = k[2])) AS hA, if(k[3] = '' OR k[4] = '', NULL, toUInt8(k[3] = k[4])) AS hB, if(k[5] = '' OR k[6] = '', NULL, toUInt8(k[5] = k[6])) AS hC, toNullable(nf0) AS nf FROM keys), +loh AS ( + SELECT replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') AS pid, toNullable(max(lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Loss')), lowerUTF8(trimBoth('Yes')), lowerUTF8(trimBoth('LOH')), lowerUTF8(trimBoth('True'))))) AS loss + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'skcm_dfci_2015' AND attribute_name IN ('HLA_A1_LOH', 'HLA_A2_LOH', 'HLA_B1_LOH', 'HLA_B2_LOH', 'HLA_C1_LOH', 'HLA_C2_LOH', 'HLA_LOH') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +os AS ( + SELECT replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'skcm_dfci_2015' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'skcm_dfci_2015_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT hom.hA AS hA, hom.hB AS hB, hom.hC AS hC, hom.nf AS nf, loh.loss AS loss, + if(os.months >= 0, os.months, NULL) AS m, if(os.status LIKE '1%', 1, 0) AS ev +FROM cohort LEFT JOIN hom ON hom.pid = cohort.pid LEFT JOIN loh ON loh.pid = cohort.pid LEFT JOIN os ON os.pid = cohort.pid + +-- view 0 hla: hla:rows:melanoma on checkpoint blockade · skcm_mskcc_2014 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'skcm_mskcc_2014'), +gt AS ( + SELECT pid, [if(anyIf(v, k IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES')) != '', anyIf(v, k IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_A', 'HLA-A')))[1]), + if(anyIf(v, k IN ('HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES')) != '', anyIf(v, k IN ('HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_A', 'HLA-A')))[2]), + if(anyIf(v, k IN ('HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES')) != '', anyIf(v, k IN ('HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_B', 'HLA-B')))[1]), + if(anyIf(v, k IN ('HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES')) != '', anyIf(v, k IN ('HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_B', 'HLA-B')))[2]), + if(anyIf(v, k IN ('HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES')) != '', anyIf(v, k IN ('HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_C', 'HLA-C')))[1]), + if(anyIf(v, k IN ('HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES')) != '', anyIf(v, k IN ('HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES')), splitByRegexp('\\s*[/,;]\\s*', anyIf(v, k IN ('HLA_C', 'HLA-C')))[2])] AS raw + FROM (SELECT replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') AS pid, attribute_name AS k, trimBoth(attribute_value) AS v FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'skcm_mskcc_2014' AND attribute_name IN ('HLA-A1', 'HLA_A1', 'HLA_A1_ALLELE', 'HLA_A1_ALLELES', 'HLA-A2', 'HLA_A2', 'HLA_A2_ALLELE', 'HLA_A2_ALLELES', 'HLA_A', 'HLA-A', 'HLA-B1', 'HLA_B1', 'HLA_B1_ALLELE', 'HLA_B1_ALLELES', 'HLA-B2', 'HLA_B2', 'HLA_B2_ALLELE', 'HLA_B2_ALLELES', 'HLA_B', 'HLA-B', 'HLA-C1', 'HLA_C1', 'HLA_C1_ALLELE', 'HLA_C1_ALLELES', 'HLA-C2', 'HLA_C2', 'HLA_C2_ALLELE', 'HLA_C2_ALLELES', 'HLA_C', 'HLA-C') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') IN (SELECT pid FROM cohort)) + GROUP BY pid +), +keys AS ( + SELECT pid, arrayMap(x -> if(x = '', '', arrayStringConcat(arraySlice(if(match(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), '[_:]'), splitByRegexp('[_:]', replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', '')), [substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 1, 2), substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 3)]), 1, 2), ':')), raw) AS k, + arrayMin(arrayMap(x -> if(x = '', 99, length(if(match(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), '[_:]'), splitByRegexp('[_:]', replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', '')), [substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 1, 2), substring(replaceRegexpOne(lowerUTF8(x), '^(hla[-_]?)?[abc][*_]?', ''), 3)]))), raw)) AS nf0 + FROM gt +), +hom AS (SELECT pid, if(k[1] = '' OR k[2] = '', NULL, toUInt8(k[1] = k[2])) AS hA, if(k[3] = '' OR k[4] = '', NULL, toUInt8(k[3] = k[4])) AS hB, if(k[5] = '' OR k[6] = '', NULL, toUInt8(k[5] = k[6])) AS hC, toNullable(nf0) AS nf FROM keys), +loh AS ( + SELECT replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') AS pid, toNullable(max(lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Loss')), lowerUTF8(trimBoth('Yes')), lowerUTF8(trimBoth('LOH')), lowerUTF8(trimBoth('True'))))) AS loss + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'skcm_mskcc_2014' AND attribute_name IN ('HLA_A1_LOH', 'HLA_A2_LOH', 'HLA_B1_LOH', 'HLA_B2_LOH', 'HLA_C1_LOH', 'HLA_C2_LOH', 'HLA_LOH') AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +os AS ( + SELECT replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'skcm_mskcc_2014' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'skcm_mskcc_2014_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT hom.hA AS hA, hom.hB AS hB, hom.hC AS hC, hom.nf AS nf, loh.loss AS loss, + if(os.months >= 0, os.months, NULL) AS m, if(os.status LIKE '1%', 1, 0) AS ev +FROM cohort LEFT JOIN hom ON hom.pid = cohort.pid LEFT JOIN loh ON loh.pid = cohort.pid LEFT JOIN os ON os.pid = cohort.pid diff --git a/test/snapshots/hla.svg b/test/snapshots/hla.svg new file mode 100644 index 0000000..293f98d --- /dev/null +++ b/test/snapshots/hla.svg @@ -0,0 +1,2 @@ + +HLA class I genotypehomozygous at 0 loci1 locus2–3 lociHLA LOHno HLA LOHmelanoma on checkpoint blockade212 of 212 patients typed at HLA-A, -B and -C · homozygous HLA-A 12.7%, HLA-B 6.1%, HLA-C 10.4% · HLA LOH called in 0 patientsclass I homozygosity165 of 212 typed patients heterozygous at all three loci77.8%36 of 212 typed patients homozygous at one locus17.0%11 of 212 typed patients homozygous at two or three loci5.2%HLA LOHno sample has an HLA LOH callOS by homozygosity0%25%50%75%100%Survival probability0244872Months (OS)Number at riskhomozygous at ≥1 locushomozygou…homozygous at ≥1 locus: 47 at risk at 0 months47homozygous at ≥1 locus: 13 at risk at 24 months13homozygous at ≥1 locus: 4 at risk at 48 months4homozygous at ≥1 locus: 1 at risk at 72 months1heterozygous at allheterozygo…heterozygous at all: 164 at risk at 0 months164heterozygous at all: 67 at risk at 24 months67heterozygous at all: 18 at risk at 48 months18heterozygous at all: 6 at risk at 72 months6homozygous at ≥1 locusmedian 12.1 mo · n = 47 · 32 eventsheterozygous at allmedian 21.7 mo · n = 164 · 103 eventslog-rank p = 0.33 (stratified by study) · homozygous at ≥1 locus vs not: HR 1.22 [95% CI0.82–1.82], p = 0.33 (n = 211)OS by HLA LOH not drawn: no patient here has an HLA LOH call with the endpointA locus is homozygous when its two alleles match at the protein level (2 fields, e.g. A*02:01); homozygosity is counted over patients typed at all three class I loci (HLA-A,-B, -C). melanoma on checkpoint blockade: 212 patients fully typed of 212 patients, 22.2% homozygous at ≥1 locus; HLA LOH in 0 patients of 0 with a call (—). HLA LOHis any of HLA_A1_LOH/HLA_A2_LOH/HLA_B1_LOH/HLA_B2_LOH/HLA_C1_LOH/HLA_C2_LOH/HLA_LOH = Loss/Yes/LOH/True in any of the patient's samples; a patientwith no call in any sample is left out of that split, and a homozygous locus cannot show LOH (LOHHLA calls only heterozygous loci), so the LOH split is not independent ofthe genotype. OS is as each study records it, from its own origin, which is a checkpoint-blockade start only in a study of treated patients that measures from it (themelanoma immunotherapy studies do; MSK-IMPACT measures from diagnosis or sequencing). Chowell et al. report worse survival with homozygosity in patients treatedwith checkpoint blockade, so in a cohort not restricted to them this reads prognosis, not response. Hazard ratios are Cox (Efron ties), exposed against not, stratified bystudy, with the log-rank test stratified the same way; no covariates: the HR is unadjusted, and tumour mutational burden and cancer type are known to confound it. HLAsupertypes and evolutionary divergence (HED) are not computed. Pooled across 3 studies whose typing methods and resolutions differ. Computed from per-patient rowsinside each patient-level study. diff --git a/test/snapshots/hla.txt b/test/snapshots/hla.txt new file mode 100644 index 0000000..58b51c0 --- /dev/null +++ b/test/snapshots/hla.txt @@ -0,0 +1,10 @@ +Analysis over skcm_dfci_2015 + skcm_mskcc_2014 + mel_ucla_2016 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- melanoma on checkpoint blockade: 212 patients — all patients in the study — across 3 studies + +HLA class I genotype (alleles compared at 2-field resolution) and HLA LOH: + melanoma on checkpoint blockade (212 of 212 patients typed at A, B and C): homozygous at 0 loci 77.8%, 1 17.0%, 2+ 5.2%; HLA-A 12.7%, HLA-B 6.1%, HLA-C 10.4%; HLA LOH 0 of 0 with a call (—) + OS by homozygosity (≥1 locus vs none): homozygous at ≥1 locus n=47, 32 events, median 12.1mo; heterozygous at all n=164, 103 events, median 21.7mo; log-rank p=0.3268; HR 1.22 (95% CI 0.82-1.82, p=0.3282, n=211) + OS by HLA LOH not drawn: no patient here has an HLA LOH call with the endpoint + CAVEAT (tell the user): A locus is homozygous when its two alleles match at the protein level (2 fields, e.g. A*02:01); homozygosity is counted over patients typed at all three class I loci (HLA-A, -B, -C). melanoma on checkpoint blockade: 212 patients fully typed of 212 patients, 22.2% homozygous at ≥1 locus; HLA LOH in 0 patients of 0 with a call (—). HLA LOH is any of HLA_A1_LOH/HLA_A2_LOH/HLA_B1_LOH/HLA_B2_LOH/HLA_C1_LOH/HLA_C2_LOH/HLA_LOH = Loss/Yes/LOH/True in any of the patient's samples; a patient with no call in any sample is left out of that split, and a homozygous locus cannot show LOH (LOHHLA calls only heterozygous loci), so the LOH split is not independent of the genotype. OS is as each study records it, from its own origin, which is a checkpoint-blockade start only in a study of treated patients that measures from it (the melanoma immunotherapy studies do; MSK-IMPACT measures from diagnosis or sequencing). Chowell et al. report worse survival with homozygosity in patients treated with checkpoint blockade, so in a cohort not restricted to them this reads prognosis, not response. Hazard ratios are Cox (Efron ties), exposed against not, stratified by study, with the log-rank test stratified the same way; no covariates: the HR is unadjusted, and tumour mutational burden and cancer type are known to confound it. HLA supertypes and evolutionary divergence (HED) are not computed. Pooled across 3 studies whose typing methods and resolutions differ. Computed from per-patient rows inside each patient-level study. diff --git a/test/snapshots/instability.json b/test/snapshots/instability.json new file mode 100644 index 0000000..0a410d9 --- /dev/null +++ b/test/snapshots/instability.json @@ -0,0 +1,688 @@ +{ + "study": "msk_impact_50k_2026", + "cohorts": { + "Colorectal": { + "count": 4993, + "text": "CANCER_TYPE in [Colorectal Cancer] (any sample)", + "studies": [ + "msk_impact_50k_2026" + ] + }, + "Breast": { + "count": 5803, + "text": "CANCER_TYPE in [Breast Cancer] (any sample)", + "studies": [ + "msk_impact_50k_2026" + ] + }, + "Pancreatic": { + "count": 2729, + "text": "CANCER_TYPE in [Pancreatic Cancer] (any sample)", + "studies": [ + "msk_impact_50k_2026" + ] + } + }, + "views": [ + { + "kind": "instability", + "cohorts": [ + "Colorectal", + "Breast", + "Pancreatic" + ], + "instability": { + "build": "hg19", + "threshold": 0.2, + "armShare": 0.5, + "includeX": false, + "arms": [ + "1p", + "1q", + "2p", + "2q", + "3p", + "3q", + "4p", + "4q", + "5p", + "5q", + "6p", + "6q", + "7p", + "7q", + "8p", + "8q", + "9p", + "9q", + "10p", + "10q", + { + "…": "19 more", + "sha256": "a9eaa29fcdf83f5c" + } + ], + "panels": [ + { + "cohort": "Colorectal", + "samples": 5395, + "segmented": 5390, + "allArms": 5354, + "score": [ + 879, + 306, + 360, + 295, + 266, + 253, + 252, + 266, + 293, + 272, + 273, + 225, + 218, + 205, + 152, + 133, + 125, + 111, + 95, + 91, + { + "…": "20 more", + "sha256": "124e4dfe3bd0ce59" + } + ], + "fga": [ + 735, + 218, + 153, + 153, + 150, + 169, + 134, + 119, + 119, + 129, + 110, + 127, + 106, + 119, + 112, + 126, + 119, + 116, + 110, + 112, + { + "…": "81 more", + "sha256": "308a3d98e34f471c" + } + ], + "scoreSpread": { + "p5": 0, + "q1": 2, + "median": 7, + "q3": 12, + "p95": 20, + "mean": 7.735 + }, + "fgaSpread": { + "p5": 0, + "q1": 4, + "median": 15, + "q3": 28, + "p95": 51, + "mean": 18.28 + }, + "arms": [ + { + "arm": "1p", + "measured": 5390, + "gain": 21, + "loss": 903 + }, + { + "arm": "1q", + "measured": 5390, + "gain": 432, + "loss": 172 + }, + { + "arm": "2p", + "measured": 5390, + "gain": 124, + "loss": 57 + }, + { + "arm": "2q", + "measured": 5390, + "gain": 98, + "loss": 69 + }, + { + "arm": "3p", + "measured": 5390, + "gain": 71, + "loss": 438 + }, + { + "arm": "3q", + "measured": 5390, + "gain": 168, + "loss": 199 + }, + { + "arm": "4p", + "measured": 5390, + "gain": 39, + "loss": 1327 + }, + { + "arm": "4q", + "measured": 5390, + "gain": 11, + "loss": 1385 + }, + { + "arm": "5p", + "measured": 5390, + "gain": 432, + "loss": 275 + }, + { + "arm": "5q", + "measured": 5390, + "gain": 72, + "loss": 656 + }, + { + "arm": "6p", + "measured": 5390, + "gain": 388, + "loss": 217 + }, + { + "arm": "6q", + "measured": 5390, + "gain": 174, + "loss": 344 + }, + { + "arm": "7p", + "measured": 5390, + "gain": 1580, + "loss": 21 + }, + { + "arm": "7q", + "measured": 5390, + "gain": 1082, + "loss": 53 + }, + { + "arm": "8p", + "measured": 5390, + "gain": 144, + "loss": 2107 + }, + { + "arm": "8q", + "measured": 5390, + "gain": 1655, + "loss": 102 + }, + { + "arm": "9p", + "measured": 5390, + "gain": 337, + "loss": 482 + }, + { + "arm": "9q", + "measured": 5390, + "gain": 282, + "loss": 346 + }, + { + "arm": "10p", + "measured": 5390, + "gain": 156, + "loss": 357 + }, + { + "arm": "10q", + "measured": 5390, + "gain": 42, + "loss": 512 + }, + { + "…": "19 more", + "sha256": "40023e8f11323158" + } + ] + }, + { + "cohort": "Breast", + "samples": 6907, + "segmented": 6906, + "allArms": 6685, + "score": [ + 716, + 339, + 357, + 318, + 357, + 356, + 360, + 364, + 378, + 373, + 362, + 304, + 281, + 255, + 263, + 220, + 197, + 165, + 163, + 149, + { + "…": "20 more", + "sha256": "15dcd02d2d62c93d" + } + ], + "fga": [ + 520, + 184, + 127, + 134, + 130, + 134, + 147, + 128, + 159, + 119, + 122, + 130, + 153, + 142, + 127, + 116, + 117, + 118, + 132, + 148, + { + "…": "81 more", + "sha256": "1ec6dd810bea5aca" + } + ], + "scoreSpread": { + "p5": 0, + "q1": 3, + "median": 8, + "q3": 14, + "p95": 22, + "mean": 9.174 + }, + "fgaSpread": { + "p5": 0, + "q1": 9, + "median": 22, + "q3": 38, + "p95": 62, + "mean": 25.304 + }, + "arms": [ + { + "arm": "1p", + "measured": 6906, + "gain": 183, + "loss": 1180 + }, + { + "arm": "1q", + "measured": 6906, + "gain": 3139, + "loss": 44 + }, + { + "arm": "2p", + "measured": 6906, + "gain": 248, + "loss": 377 + }, + { + "arm": "2q", + "measured": 6906, + "gain": 92, + "loss": 590 + }, + { + "arm": "3p", + "measured": 6906, + "gain": 144, + "loss": 1170 + }, + { + "arm": "3q", + "measured": 6906, + "gain": 599, + "loss": 293 + }, + { + "arm": "4p", + "measured": 6904, + "gain": 119, + "loss": 1418 + }, + { + "arm": "4q", + "measured": 6906, + "gain": 113, + "loss": 1235 + }, + { + "arm": "5p", + "measured": 6906, + "gain": 964, + "loss": 436 + }, + { + "arm": "5q", + "measured": 6906, + "gain": 454, + "loss": 1010 + }, + { + "arm": "6p", + "measured": 6906, + "gain": 610, + "loss": 528 + }, + { + "arm": "6q", + "measured": 6906, + "gain": 284, + "loss": 1537 + }, + { + "arm": "7p", + "measured": 6906, + "gain": 870, + "loss": 347 + }, + { + "arm": "7q", + "measured": 6906, + "gain": 598, + "loss": 586 + }, + { + "arm": "8p", + "measured": 6906, + "gain": 280, + "loss": 2857 + }, + { + "arm": "8q", + "measured": 6906, + "gain": 2845, + "loss": 96 + }, + { + "arm": "9p", + "measured": 6900, + "gain": 299, + "loss": 1709 + }, + { + "arm": "9q", + "measured": 6906, + "gain": 175, + "loss": 1197 + }, + { + "arm": "10p", + "measured": 6906, + "gain": 694, + "loss": 419 + }, + { + "arm": "10q", + "measured": 6906, + "gain": 134, + "loss": 1026 + }, + { + "…": "19 more", + "sha256": "ee0a517fe2b474c1" + } + ] + }, + { + "cohort": "Pancreatic", + "samples": 2911, + "segmented": 2910, + "allArms": 2891, + "score": [ + 976, + 284, + 199, + 140, + 116, + 116, + 118, + 111, + 94, + 80, + 73, + 61, + 59, + 55, + 41, + 44, + 37, + 23, + 26, + 16, + { + "…": "20 more", + "sha256": "3d81a2ae726b9d09" + } + ], + "fga": [ + 805, + 231, + 126, + 94, + 80, + 75, + 71, + 58, + 51, + 52, + 48, + 47, + 48, + 45, + 44, + 45, + 43, + 52, + 27, + 39, + { + "…": "81 more", + "sha256": "a6e1604d3f6358ac" + } + ], + "scoreSpread": { + "p5": 0, + "q1": 0, + "median": 2, + "q3": 9, + "p95": 26, + "mean": 6.177 + }, + "fgaSpread": { + "p5": 0, + "q1": 0, + "median": 6, + "q3": 22, + "p95": 68, + "mean": 15.68 + }, + "arms": [ + { + "arm": "1p", + "measured": 2910, + "gain": 26, + "loss": 378 + }, + { + "arm": "1q", + "measured": 2910, + "gain": 355, + "loss": 135 + }, + { + "arm": "2p", + "measured": 2910, + "gain": 83, + "loss": 157 + }, + { + "arm": "2q", + "measured": 2910, + "gain": 97, + "loss": 141 + }, + { + "arm": "3p", + "measured": 2910, + "gain": 38, + "loss": 439 + }, + { + "arm": "3q", + "measured": 2910, + "gain": 215, + "loss": 201 + }, + { + "arm": "4p", + "measured": 2909, + "gain": 143, + "loss": 217 + }, + { + "arm": "4q", + "measured": 2910, + "gain": 107, + "loss": 275 + }, + { + "arm": "5p", + "measured": 2910, + "gain": 302, + "loss": 57 + }, + { + "arm": "5q", + "measured": 2910, + "gain": 181, + "loss": 140 + }, + { + "arm": "6p", + "measured": 2910, + "gain": 95, + "loss": 379 + }, + { + "arm": "6q", + "measured": 2910, + "gain": 28, + "loss": 603 + }, + { + "arm": "7p", + "measured": 2910, + "gain": 429, + "loss": 22 + }, + { + "arm": "7q", + "measured": 2910, + "gain": 269, + "loss": 87 + }, + { + "arm": "8p", + "measured": 2910, + "gain": 99, + "loss": 559 + }, + { + "arm": "8q", + "measured": 2910, + "gain": 411, + "loss": 123 + }, + { + "arm": "9p", + "measured": 2908, + "gain": 90, + "loss": 649 + }, + { + "arm": "9q", + "measured": 2910, + "gain": 138, + "loss": 331 + }, + { + "arm": "10p", + "measured": 2910, + "gain": 60, + "loss": 286 + }, + { + "arm": "10q", + "measured": 2910, + "gain": 54, + "loss": 260 + }, + { + "…": "19 more", + "sha256": "ceaab6b523b4834d" + } + ] + } + ], + "test": { + "name": "Kruskal–Wallis", + "scoreP": 1.975566934299693e-163, + "fgaP": 1.2451366250345867e-222 + } + }, + "participation": { + "studies": [ + { + "study": "msk_impact_50k_2026", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "From each sample's copy-number segments (hg19; autosomes only — X's log ratio mostly records sex), computed per sample in the database: FGA is the share of the sample's segmented length whose |segment mean| is at or past 0.2 (log2), counted in 1-point bins; an arm (p and q split at hg19's centromeres, from UCSC's cytoBand; the acrocentric p arms of 13, 14, 15, 21 and 22 left out, as Taylor et al. 2018 did) is gained or lost when more than 50% of its length is past ±0.2, and the aneuploidy score is how many of the 39 arms are. Colorectal: 5,390 samples scored of 5,395 (5,390 with segments; 5,354 with every arm covered); Breast: 6,906 samples scored of 6,907 (6,906 with segments; 6,685 with every arm covered); Pancreatic: 2,910 samples scored of 2,911 (2,910 with segments; 2,891 with every arm covered); an arm segments cover for less than 50% of its length is not called, which lowers that sample's score. Boxes are the quartiles, whiskers the 5th and 95th percentiles, read from the histograms; Kruskal–Wallis compares the cohorts' distributions (normal approximation with ties). Every sample of each patient counts. Panel-derived segments are noisy and purity is not corrected — a low-purity tumour's changes sit near 0 and read as stable — so these are relative measures within one assay, not absolute ploidy." + } + ] +} diff --git a/test/snapshots/instability.sql b/test/snapshots/instability.sql new file mode 100644 index 0000000..d460ce8 --- /dev/null +++ b/test/snapshots/instability.sql @@ -0,0 +1,301 @@ +-- cohort Colorectal +-- msk_impact_50k_2026 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) + +-- cohort Breast +-- msk_impact_50k_2026 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Breast Cancer')))) + +-- cohort Pancreatic +-- msk_impact_50k_2026 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Pancreatic Cancer')))) + +-- view 0 instability: instability:arms:Breast · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Breast Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +seg AS ( + SELECT s.sample_id AS sid, multiIf(s.chr IN ('23', 'X', 'chrX'), 'X', replaceOne(s.chr, 'chr', '')) AS chrom, + toInt64(s.start) AS st, toInt64(s.end) AS en, s.segment_mean AS m + FROM snapshot_db.copy_number_seg s + WHERE s.cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND s.sample_id IN (SELECT sid FROM samp) AND isFinite(s.segment_mean) +), +parts AS ( + SELECT sid, chrom, m, arm, greatest(toInt64(0), least(en, hi) - greatest(st, lo)) AS bp, hi - lo AS arm_len FROM ( + SELECT sid, chrom, m, st, en, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [125000000,93300000,91000000,50400000,48400000,61000000,59900000,45600000,49000000,40200000,53700000,35800000,17900000,17600000,19000000,36600000,24000000,17200000,26500000,27500000,13200000,14700000], 0) AS c, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [249250621,243199373,198022430,191154276,180915260,171115067,159138663,146364022,141213431,135534747,135006516,133851895,115169878,107349540,102531392,90354753,81195210,78077248,59128983,63025520,48129895,51304566], 0) AS l, + arrayJoin([(concat(chrom, 'p'), toInt64(0), toInt64(c)), (concat(chrom, 'q'), toInt64(c), toInt64(l))]) AS a, + a.1 AS arm, a.2 AS lo, a.3 AS hi + FROM seg WHERE chrom IN ('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22') AND en > st + ) WHERE NOT (arm IN ('13p','14p','15p','21p','22p')) +), +arms AS ( + SELECT sid, arm, any(arm_len) AS alen, sum(bp) AS cov, sumIf(bp, m >= 0.2) AS gbp, sumIf(bp, m <= -0.2) AS lbp + FROM parts GROUP BY sid, arm +), +calls AS ( + SELECT sid, arm, cov >= 0.5 * alen AS measured, gbp > 0.5 * alen AS gain, lbp > 0.5 * alen AS loss FROM arms +), +per AS ( + SELECT a.sid AS sid, sum(a.cov) AS cov, sum(a.gbp + a.lbp) AS alt, + countIf(c.gain OR c.loss) AS score, countIf(c.measured) AS measured + FROM arms a INNER JOIN calls c ON c.sid = a.sid AND c.arm = a.arm GROUP BY a.sid +) +SELECT arm, countIf(measured) AS n_measured, countIf(measured AND gain) AS n_gain, countIf(measured AND loss) AS n_loss FROM calls GROUP BY arm + +-- view 0 instability: instability:arms:Colorectal · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +seg AS ( + SELECT s.sample_id AS sid, multiIf(s.chr IN ('23', 'X', 'chrX'), 'X', replaceOne(s.chr, 'chr', '')) AS chrom, + toInt64(s.start) AS st, toInt64(s.end) AS en, s.segment_mean AS m + FROM snapshot_db.copy_number_seg s + WHERE s.cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND s.sample_id IN (SELECT sid FROM samp) AND isFinite(s.segment_mean) +), +parts AS ( + SELECT sid, chrom, m, arm, greatest(toInt64(0), least(en, hi) - greatest(st, lo)) AS bp, hi - lo AS arm_len FROM ( + SELECT sid, chrom, m, st, en, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [125000000,93300000,91000000,50400000,48400000,61000000,59900000,45600000,49000000,40200000,53700000,35800000,17900000,17600000,19000000,36600000,24000000,17200000,26500000,27500000,13200000,14700000], 0) AS c, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [249250621,243199373,198022430,191154276,180915260,171115067,159138663,146364022,141213431,135534747,135006516,133851895,115169878,107349540,102531392,90354753,81195210,78077248,59128983,63025520,48129895,51304566], 0) AS l, + arrayJoin([(concat(chrom, 'p'), toInt64(0), toInt64(c)), (concat(chrom, 'q'), toInt64(c), toInt64(l))]) AS a, + a.1 AS arm, a.2 AS lo, a.3 AS hi + FROM seg WHERE chrom IN ('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22') AND en > st + ) WHERE NOT (arm IN ('13p','14p','15p','21p','22p')) +), +arms AS ( + SELECT sid, arm, any(arm_len) AS alen, sum(bp) AS cov, sumIf(bp, m >= 0.2) AS gbp, sumIf(bp, m <= -0.2) AS lbp + FROM parts GROUP BY sid, arm +), +calls AS ( + SELECT sid, arm, cov >= 0.5 * alen AS measured, gbp > 0.5 * alen AS gain, lbp > 0.5 * alen AS loss FROM arms +), +per AS ( + SELECT a.sid AS sid, sum(a.cov) AS cov, sum(a.gbp + a.lbp) AS alt, + countIf(c.gain OR c.loss) AS score, countIf(c.measured) AS measured + FROM arms a INNER JOIN calls c ON c.sid = a.sid AND c.arm = a.arm GROUP BY a.sid +) +SELECT arm, countIf(measured) AS n_measured, countIf(measured AND gain) AS n_gain, countIf(measured AND loss) AS n_loss FROM calls GROUP BY arm + +-- view 0 instability: instability:arms:Pancreatic · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Pancreatic Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +seg AS ( + SELECT s.sample_id AS sid, multiIf(s.chr IN ('23', 'X', 'chrX'), 'X', replaceOne(s.chr, 'chr', '')) AS chrom, + toInt64(s.start) AS st, toInt64(s.end) AS en, s.segment_mean AS m + FROM snapshot_db.copy_number_seg s + WHERE s.cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND s.sample_id IN (SELECT sid FROM samp) AND isFinite(s.segment_mean) +), +parts AS ( + SELECT sid, chrom, m, arm, greatest(toInt64(0), least(en, hi) - greatest(st, lo)) AS bp, hi - lo AS arm_len FROM ( + SELECT sid, chrom, m, st, en, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [125000000,93300000,91000000,50400000,48400000,61000000,59900000,45600000,49000000,40200000,53700000,35800000,17900000,17600000,19000000,36600000,24000000,17200000,26500000,27500000,13200000,14700000], 0) AS c, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [249250621,243199373,198022430,191154276,180915260,171115067,159138663,146364022,141213431,135534747,135006516,133851895,115169878,107349540,102531392,90354753,81195210,78077248,59128983,63025520,48129895,51304566], 0) AS l, + arrayJoin([(concat(chrom, 'p'), toInt64(0), toInt64(c)), (concat(chrom, 'q'), toInt64(c), toInt64(l))]) AS a, + a.1 AS arm, a.2 AS lo, a.3 AS hi + FROM seg WHERE chrom IN ('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22') AND en > st + ) WHERE NOT (arm IN ('13p','14p','15p','21p','22p')) +), +arms AS ( + SELECT sid, arm, any(arm_len) AS alen, sum(bp) AS cov, sumIf(bp, m >= 0.2) AS gbp, sumIf(bp, m <= -0.2) AS lbp + FROM parts GROUP BY sid, arm +), +calls AS ( + SELECT sid, arm, cov >= 0.5 * alen AS measured, gbp > 0.5 * alen AS gain, lbp > 0.5 * alen AS loss FROM arms +), +per AS ( + SELECT a.sid AS sid, sum(a.cov) AS cov, sum(a.gbp + a.lbp) AS alt, + countIf(c.gain OR c.loss) AS score, countIf(c.measured) AS measured + FROM arms a INNER JOIN calls c ON c.sid = a.sid AND c.arm = a.arm GROUP BY a.sid +) +SELECT arm, countIf(measured) AS n_measured, countIf(measured AND gain) AS n_gain, countIf(measured AND loss) AS n_loss FROM calls GROUP BY arm + +-- view 0 instability: instability:build · msk_impact_50k_2026 +SELECT rg.name AS build FROM snapshot_db.cancer_study cs +JOIN snapshot_db.reference_genome rg ON rg.reference_genome_id = cs.reference_genome_id +WHERE cs.cancer_study_identifier = 'msk_impact_50k_2026' + +-- view 0 instability: instability:coverage:Breast · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Breast Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +) +SELECT (SELECT count() FROM samp) AS samples, + (SELECT uniqExact(sample_id) FROM snapshot_db.copy_number_seg + WHERE cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND sample_id IN (SELECT sid FROM samp)) AS segmented +FROM system.one + +-- view 0 instability: instability:coverage:Colorectal · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +) +SELECT (SELECT count() FROM samp) AS samples, + (SELECT uniqExact(sample_id) FROM snapshot_db.copy_number_seg + WHERE cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND sample_id IN (SELECT sid FROM samp)) AS segmented +FROM system.one + +-- view 0 instability: instability:coverage:Pancreatic · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Pancreatic Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +) +SELECT (SELECT count() FROM samp) AS samples, + (SELECT uniqExact(sample_id) FROM snapshot_db.copy_number_seg + WHERE cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND sample_id IN (SELECT sid FROM samp)) AS segmented +FROM system.one + +-- view 0 instability: instability:scores:Breast · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Breast Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +seg AS ( + SELECT s.sample_id AS sid, multiIf(s.chr IN ('23', 'X', 'chrX'), 'X', replaceOne(s.chr, 'chr', '')) AS chrom, + toInt64(s.start) AS st, toInt64(s.end) AS en, s.segment_mean AS m + FROM snapshot_db.copy_number_seg s + WHERE s.cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND s.sample_id IN (SELECT sid FROM samp) AND isFinite(s.segment_mean) +), +parts AS ( + SELECT sid, chrom, m, arm, greatest(toInt64(0), least(en, hi) - greatest(st, lo)) AS bp, hi - lo AS arm_len FROM ( + SELECT sid, chrom, m, st, en, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [125000000,93300000,91000000,50400000,48400000,61000000,59900000,45600000,49000000,40200000,53700000,35800000,17900000,17600000,19000000,36600000,24000000,17200000,26500000,27500000,13200000,14700000], 0) AS c, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [249250621,243199373,198022430,191154276,180915260,171115067,159138663,146364022,141213431,135534747,135006516,133851895,115169878,107349540,102531392,90354753,81195210,78077248,59128983,63025520,48129895,51304566], 0) AS l, + arrayJoin([(concat(chrom, 'p'), toInt64(0), toInt64(c)), (concat(chrom, 'q'), toInt64(c), toInt64(l))]) AS a, + a.1 AS arm, a.2 AS lo, a.3 AS hi + FROM seg WHERE chrom IN ('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22') AND en > st + ) WHERE NOT (arm IN ('13p','14p','15p','21p','22p')) +), +arms AS ( + SELECT sid, arm, any(arm_len) AS alen, sum(bp) AS cov, sumIf(bp, m >= 0.2) AS gbp, sumIf(bp, m <= -0.2) AS lbp + FROM parts GROUP BY sid, arm +), +calls AS ( + SELECT sid, arm, cov >= 0.5 * alen AS measured, gbp > 0.5 * alen AS gain, lbp > 0.5 * alen AS loss FROM arms +), +per AS ( + SELECT a.sid AS sid, sum(a.cov) AS cov, sum(a.gbp + a.lbp) AS alt, + countIf(c.gain OR c.loss) AS score, countIf(c.measured) AS measured + FROM arms a INNER JOIN calls c ON c.sid = a.sid AND c.arm = a.arm GROUP BY a.sid +) +SELECT toInt32(least(100, floor(100 * alt / greatest(1, cov)))) AS fga_bin, score, measured, count() AS n +FROM per GROUP BY fga_bin, score, measured ORDER BY fga_bin, score + +-- view 0 instability: instability:scores:Colorectal · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +seg AS ( + SELECT s.sample_id AS sid, multiIf(s.chr IN ('23', 'X', 'chrX'), 'X', replaceOne(s.chr, 'chr', '')) AS chrom, + toInt64(s.start) AS st, toInt64(s.end) AS en, s.segment_mean AS m + FROM snapshot_db.copy_number_seg s + WHERE s.cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND s.sample_id IN (SELECT sid FROM samp) AND isFinite(s.segment_mean) +), +parts AS ( + SELECT sid, chrom, m, arm, greatest(toInt64(0), least(en, hi) - greatest(st, lo)) AS bp, hi - lo AS arm_len FROM ( + SELECT sid, chrom, m, st, en, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [125000000,93300000,91000000,50400000,48400000,61000000,59900000,45600000,49000000,40200000,53700000,35800000,17900000,17600000,19000000,36600000,24000000,17200000,26500000,27500000,13200000,14700000], 0) AS c, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [249250621,243199373,198022430,191154276,180915260,171115067,159138663,146364022,141213431,135534747,135006516,133851895,115169878,107349540,102531392,90354753,81195210,78077248,59128983,63025520,48129895,51304566], 0) AS l, + arrayJoin([(concat(chrom, 'p'), toInt64(0), toInt64(c)), (concat(chrom, 'q'), toInt64(c), toInt64(l))]) AS a, + a.1 AS arm, a.2 AS lo, a.3 AS hi + FROM seg WHERE chrom IN ('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22') AND en > st + ) WHERE NOT (arm IN ('13p','14p','15p','21p','22p')) +), +arms AS ( + SELECT sid, arm, any(arm_len) AS alen, sum(bp) AS cov, sumIf(bp, m >= 0.2) AS gbp, sumIf(bp, m <= -0.2) AS lbp + FROM parts GROUP BY sid, arm +), +calls AS ( + SELECT sid, arm, cov >= 0.5 * alen AS measured, gbp > 0.5 * alen AS gain, lbp > 0.5 * alen AS loss FROM arms +), +per AS ( + SELECT a.sid AS sid, sum(a.cov) AS cov, sum(a.gbp + a.lbp) AS alt, + countIf(c.gain OR c.loss) AS score, countIf(c.measured) AS measured + FROM arms a INNER JOIN calls c ON c.sid = a.sid AND c.arm = a.arm GROUP BY a.sid +) +SELECT toInt32(least(100, floor(100 * alt / greatest(1, cov)))) AS fga_bin, score, measured, count() AS n +FROM per GROUP BY fga_bin, score, measured ORDER BY fga_bin, score + +-- view 0 instability: instability:scores:Pancreatic · msk_impact_50k_2026 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Pancreatic Cancer'))))), +samp AS ( + SELECT internal_id AS sid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_impact_50k_2026' AND replaceOne(patient_unique_id, 'msk_impact_50k_2026_', '') IN (SELECT pid FROM cohort) +), +seg AS ( + SELECT s.sample_id AS sid, multiIf(s.chr IN ('23', 'X', 'chrX'), 'X', replaceOne(s.chr, 'chr', '')) AS chrom, + toInt64(s.start) AS st, toInt64(s.end) AS en, s.segment_mean AS m + FROM snapshot_db.copy_number_seg s + WHERE s.cancer_study_id = (SELECT cancer_study_id FROM snapshot_db.cancer_study WHERE cancer_study_identifier = 'msk_impact_50k_2026') + AND s.sample_id IN (SELECT sid FROM samp) AND isFinite(s.segment_mean) +), +parts AS ( + SELECT sid, chrom, m, arm, greatest(toInt64(0), least(en, hi) - greatest(st, lo)) AS bp, hi - lo AS arm_len FROM ( + SELECT sid, chrom, m, st, en, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [125000000,93300000,91000000,50400000,48400000,61000000,59900000,45600000,49000000,40200000,53700000,35800000,17900000,17600000,19000000,36600000,24000000,17200000,26500000,27500000,13200000,14700000], 0) AS c, transform(chrom, ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22'], [249250621,243199373,198022430,191154276,180915260,171115067,159138663,146364022,141213431,135534747,135006516,133851895,115169878,107349540,102531392,90354753,81195210,78077248,59128983,63025520,48129895,51304566], 0) AS l, + arrayJoin([(concat(chrom, 'p'), toInt64(0), toInt64(c)), (concat(chrom, 'q'), toInt64(c), toInt64(l))]) AS a, + a.1 AS arm, a.2 AS lo, a.3 AS hi + FROM seg WHERE chrom IN ('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22') AND en > st + ) WHERE NOT (arm IN ('13p','14p','15p','21p','22p')) +), +arms AS ( + SELECT sid, arm, any(arm_len) AS alen, sum(bp) AS cov, sumIf(bp, m >= 0.2) AS gbp, sumIf(bp, m <= -0.2) AS lbp + FROM parts GROUP BY sid, arm +), +calls AS ( + SELECT sid, arm, cov >= 0.5 * alen AS measured, gbp > 0.5 * alen AS gain, lbp > 0.5 * alen AS loss FROM arms +), +per AS ( + SELECT a.sid AS sid, sum(a.cov) AS cov, sum(a.gbp + a.lbp) AS alt, + countIf(c.gain OR c.loss) AS score, countIf(c.measured) AS measured + FROM arms a INNER JOIN calls c ON c.sid = a.sid AND c.arm = a.arm GROUP BY a.sid +) +SELECT toInt32(least(100, floor(100 * alt / greatest(1, cov)))) AS fga_bin, score, measured, count() AS n +FROM per GROUP BY fga_bin, score, measured ORDER BY fga_bin, score diff --git a/test/snapshots/instability.svg b/test/snapshots/instability.svg new file mode 100644 index 0000000..058dae5 --- /dev/null +++ b/test/snapshots/instability.svg @@ -0,0 +1,2 @@ + +Genomic instability — aneuploidy and FGAAneuploidy score (arms altered, of 39)ColorectalColorectal — median 7 arms, IQR 2–12, 5th–95th percentile 0–20BreastBreast — median 8 arms, IQR 3–14, 5th–95th percentile 0–22PancreaticPancreatic — median 2 arms, IQR 0–9, 5th–95th percentile 0–26051015202530Aneuploidy scoreFraction of genome altered (%)ColorectalColorectal — median 15%, IQR 4–28, 5th–95th percentile 0–51BreastBreast — median 22%, IQR 9–38, 5th–95th percentile 0–62PancreaticPancreatic — median 6%, IQR 0–22, 5th–95th percentile 0–68010203040506070Fraction of genome alteredKruskal–Wallis: aneuploidy score p = 2e-163 · FGA p = 1.2e-222Arm-level gains and losses — share of the samples measuring each arm, darkest at 60%gainlossColorectalColorectal — 1p gained in 21 of 5,390 samples measuring it (0%)Colorectal — 1p lost in 903 of 5,390 samples measuring it (17%)Colorectal — 1q gained in 432 of 5,390 samples measuring it (8%)Colorectal — 1q lost in 172 of 5,390 samples measuring it (3%)Colorectal — 2p gained in 124 of 5,390 samples measuring it (2%)Colorectal — 2p lost in 57 of 5,390 samples measuring it (1%)Colorectal — 2q gained in 98 of 5,390 samples measuring it (2%)Colorectal — 2q lost in 69 of 5,390 samples measuring it (1%)Colorectal — 3p gained in 71 of 5,390 samples measuring it (1%)Colorectal — 3p lost in 438 of 5,390 samples measuring it (8%)Colorectal — 3q gained in 168 of 5,390 samples measuring it (3%)Colorectal — 3q lost in 199 of 5,390 samples measuring it (4%)Colorectal — 4p gained in 39 of 5,390 samples measuring it (1%)Colorectal — 4p lost in 1,327 of 5,390 samples measuring it (25%)Colorectal — 4q gained in 11 of 5,390 samples measuring it (0%)Colorectal — 4q lost in 1,385 of 5,390 samples measuring it (26%)Colorectal — 5p gained in 432 of 5,390 samples measuring it (8%)Colorectal — 5p lost in 275 of 5,390 samples measuring it (5%)Colorectal — 5q gained in 72 of 5,390 samples measuring it (1%)Colorectal — 5q lost in 656 of 5,390 samples measuring it (12%)Colorectal — 6p gained in 388 of 5,390 samples measuring it (7%)Colorectal — 6p lost in 217 of 5,390 samples measuring it (4%)Colorectal — 6q gained in 174 of 5,390 samples measuring it (3%)Colorectal — 6q lost in 344 of 5,390 samples measuring it (6%)Colorectal — 7p gained in 1,580 of 5,390 samples measuring it (29%)Colorectal — 7p lost in 21 of 5,390 samples measuring it (0%)Colorectal — 7q gained in 1,082 of 5,390 samples measuring it (20%)Colorectal — 7q lost in 53 of 5,390 samples measuring it (1%)Colorectal — 8p gained in 144 of 5,390 samples measuring it (3%)Colorectal — 8p lost in 2,107 of 5,390 samples measuring it (39%)Colorectal — 8q gained in 1,655 of 5,390 samples measuring it (31%)Colorectal — 8q lost in 102 of 5,390 samples measuring it (2%)Colorectal — 9p gained in 337 of 5,390 samples measuring it (6%)Colorectal — 9p lost in 482 of 5,390 samples measuring it (9%)Colorectal — 9q gained in 282 of 5,390 samples measuring it (5%)Colorectal — 9q lost in 346 of 5,390 samples measuring it (6%)Colorectal — 10p gained in 156 of 5,390 samples measuring it (3%)Colorectal — 10p lost in 357 of 5,390 samples measuring it (7%)Colorectal — 10q gained in 42 of 5,390 samples measuring it (1%)Colorectal — 10q lost in 512 of 5,390 samples measuring it (9%)Colorectal — 11p gained in 186 of 5,390 samples measuring it (3%)Colorectal — 11p lost in 259 of 5,390 samples measuring it (5%)Colorectal — 11q gained in 127 of 5,390 samples measuring it (2%)Colorectal — 11q lost in 419 of 5,390 samples measuring it (8%)Colorectal — 12p gained in 353 of 5,389 samples measuring it (7%)Colorectal — 12p lost in 280 of 5,389 samples measuring it (5%)Colorectal — 12q gained in 218 of 5,390 samples measuring it (4%)Colorectal — 12q lost in 282 of 5,390 samples measuring it (5%)Colorectal — 13q gained in 2,260 of 5,390 samples measuring it (42%)Colorectal — 13q lost in 62 of 5,390 samples measuring it (1%)Colorectal — 14q gained in 93 of 5,390 samples measuring it (2%)Colorectal — 14q lost in 982 of 5,390 samples measuring it (18%)Colorectal — 15q gained in 58 of 5,390 samples measuring it (1%)Colorectal — 15q lost in 914 of 5,390 samples measuring it (17%)Colorectal — 16p gained in 570 of 5,386 samples measuring it (11%)Colorectal — 16p lost in 182 of 5,386 samples measuring it (3%)Colorectal — 16q gained in 506 of 5,390 samples measuring it (9%)Colorectal — 16q lost in 229 of 5,390 samples measuring it (4%)Colorectal — 17p gained in 21 of 5,390 samples measuring it (0%)Colorectal — 17p lost in 1,944 of 5,390 samples measuring it (36%)Colorectal — 17q gained in 337 of 5,390 samples measuring it (6%)Colorectal — 17q lost in 473 of 5,390 samples measuring it (9%)Colorectal — 18p gained in 148 of 5,388 samples measuring it (3%)Colorectal — 18p lost in 2,329 of 5,388 samples measuring it (43%)Colorectal — 18q gained in 20 of 5,390 samples measuring it (0%)Colorectal — 18q lost in 2,835 of 5,390 samples measuring it (53%)Colorectal — 19p gained in 231 of 5,376 samples measuring it (4%)Colorectal — 19p lost in 261 of 5,376 samples measuring it (5%)Colorectal — 19q gained in 313 of 5,390 samples measuring it (6%)Colorectal — 19q lost in 235 of 5,390 samples measuring it (4%)Colorectal — 20p gained in 1,668 of 5,381 samples measuring it (31%)Colorectal — 20p lost in 489 of 5,381 samples measuring it (9%)Colorectal — 20q gained in 3,218 of 5,388 samples measuring it (60%)Colorectal — 20q lost in 7 of 5,388 samples measuring it (0%)Colorectal — 21q gained in 113 of 5,390 samples measuring it (2%)Colorectal — 21q lost in 656 of 5,390 samples measuring it (12%)Colorectal — 22q gained in 59 of 5,386 samples measuring it (1%)Colorectal — 22q lost in 1,027 of 5,386 samples measuring it (19%)BreastBreast — 1p gained in 183 of 6,906 samples measuring it (3%)Breast — 1p lost in 1,180 of 6,906 samples measuring it (17%)Breast — 1q gained in 3,139 of 6,906 samples measuring it (45%)Breast — 1q lost in 44 of 6,906 samples measuring it (1%)Breast — 2p gained in 248 of 6,906 samples measuring it (4%)Breast — 2p lost in 377 of 6,906 samples measuring it (5%)Breast — 2q gained in 92 of 6,906 samples measuring it (1%)Breast — 2q lost in 590 of 6,906 samples measuring it (9%)Breast — 3p gained in 144 of 6,906 samples measuring it (2%)Breast — 3p lost in 1,170 of 6,906 samples measuring it (17%)Breast — 3q gained in 599 of 6,906 samples measuring it (9%)Breast — 3q lost in 293 of 6,906 samples measuring it (4%)Breast — 4p gained in 119 of 6,904 samples measuring it (2%)Breast — 4p lost in 1,418 of 6,904 samples measuring it (21%)Breast — 4q gained in 113 of 6,906 samples measuring it (2%)Breast — 4q lost in 1,235 of 6,906 samples measuring it (18%)Breast — 5p gained in 964 of 6,906 samples measuring it (14%)Breast — 5p lost in 436 of 6,906 samples measuring it (6%)Breast — 5q gained in 454 of 6,906 samples measuring it (7%)Breast — 5q lost in 1,010 of 6,906 samples measuring it (15%)Breast — 6p gained in 610 of 6,906 samples measuring it (9%)Breast — 6p lost in 528 of 6,906 samples measuring it (8%)Breast — 6q gained in 284 of 6,906 samples measuring it (4%)Breast — 6q lost in 1,537 of 6,906 samples measuring it (22%)Breast — 7p gained in 870 of 6,906 samples measuring it (13%)Breast — 7p lost in 347 of 6,906 samples measuring it (5%)Breast — 7q gained in 598 of 6,906 samples measuring it (9%)Breast — 7q lost in 586 of 6,906 samples measuring it (8%)Breast — 8p gained in 280 of 6,906 samples measuring it (4%)Breast — 8p lost in 2,857 of 6,906 samples measuring it (41%)Breast — 8q gained in 2,845 of 6,906 samples measuring it (41%)Breast — 8q lost in 96 of 6,906 samples measuring it (1%)Breast — 9p gained in 299 of 6,900 samples measuring it (4%)Breast — 9p lost in 1,709 of 6,900 samples measuring it (25%)Breast — 9q gained in 175 of 6,906 samples measuring it (3%)Breast — 9q lost in 1,197 of 6,906 samples measuring it (17%)Breast — 10p gained in 694 of 6,906 samples measuring it (10%)Breast — 10p lost in 419 of 6,906 samples measuring it (6%)Breast — 10q gained in 134 of 6,906 samples measuring it (2%)Breast — 10q lost in 1,026 of 6,906 samples measuring it (15%)Breast — 11p gained in 266 of 6,906 samples measuring it (4%)Breast — 11p lost in 1,048 of 6,906 samples measuring it (15%)Breast — 11q gained in 143 of 6,906 samples measuring it (2%)Breast — 11q lost in 2,155 of 6,906 samples measuring it (31%)Breast — 12p gained in 605 of 6,902 samples measuring it (9%)Breast — 12p lost in 744 of 6,902 samples measuring it (11%)Breast — 12q gained in 524 of 6,906 samples measuring it (8%)Breast — 12q lost in 620 of 6,906 samples measuring it (9%)Breast — 13q gained in 137 of 6,906 samples measuring it (2%)Breast — 13q lost in 2,109 of 6,906 samples measuring it (31%)Breast — 14q gained in 352 of 6,906 samples measuring it (5%)Breast — 14q lost in 990 of 6,906 samples measuring it (14%)Breast — 15q gained in 218 of 6,906 samples measuring it (3%)Breast — 15q lost in 867 of 6,906 samples measuring it (13%)Breast — 16p gained in 1,848 of 6,844 samples measuring it (27%)Breast — 16p lost in 218 of 6,844 samples measuring it (3%)Breast — 16q gained in 266 of 6,873 samples measuring it (4%)Breast — 16q lost in 2,651 of 6,873 samples measuring it (39%)Breast — 17p gained in 91 of 6,906 samples measuring it (1%)Breast — 17p lost in 2,788 of 6,906 samples measuring it (40%)Breast — 17q gained in 881 of 6,904 samples measuring it (13%)Breast — 17q lost in 766 of 6,904 samples measuring it (11%)Breast — 18p gained in 420 of 6,903 samples measuring it (6%)Breast — 18p lost in 1,281 of 6,903 samples measuring it (19%)Breast — 18q gained in 254 of 6,906 samples measuring it (4%)Breast — 18q lost in 1,515 of 6,906 samples measuring it (22%)Breast — 19p gained in 260 of 6,831 samples measuring it (4%)Breast — 19p lost in 881 of 6,831 samples measuring it (13%)Breast — 19q gained in 425 of 6,903 samples measuring it (6%)Breast — 19q lost in 580 of 6,903 samples measuring it (8%)Breast — 20p gained in 911 of 6,895 samples measuring it (13%)Breast — 20p lost in 568 of 6,895 samples measuring it (8%)Breast — 20q gained in 1,556 of 6,886 samples measuring it (23%)Breast — 20q lost in 122 of 6,886 samples measuring it (2%)Breast — 21q gained in 635 of 6,905 samples measuring it (9%)Breast — 21q lost in 647 of 6,905 samples measuring it (9%)Breast — 22q gained in 238 of 6,898 samples measuring it (3%)Breast — 22q lost in 1,878 of 6,898 samples measuring it (27%)PancreaticPancreatic — 1p gained in 26 of 2,910 samples measuring it (1%)Pancreatic — 1p lost in 378 of 2,910 samples measuring it (13%)Pancreatic — 1q gained in 355 of 2,910 samples measuring it (12%)Pancreatic — 1q lost in 135 of 2,910 samples measuring it (5%)Pancreatic — 2p gained in 83 of 2,910 samples measuring it (3%)Pancreatic — 2p lost in 157 of 2,910 samples measuring it (5%)Pancreatic — 2q gained in 97 of 2,910 samples measuring it (3%)Pancreatic — 2q lost in 141 of 2,910 samples measuring it (5%)Pancreatic — 3p gained in 38 of 2,910 samples measuring it (1%)Pancreatic — 3p lost in 439 of 2,910 samples measuring it (15%)Pancreatic — 3q gained in 215 of 2,910 samples measuring it (7%)Pancreatic — 3q lost in 201 of 2,910 samples measuring it (7%)Pancreatic — 4p gained in 143 of 2,909 samples measuring it (5%)Pancreatic — 4p lost in 217 of 2,909 samples measuring it (7%)Pancreatic — 4q gained in 107 of 2,910 samples measuring it (4%)Pancreatic — 4q lost in 275 of 2,910 samples measuring it (9%)Pancreatic — 5p gained in 302 of 2,910 samples measuring it (10%)Pancreatic — 5p lost in 57 of 2,910 samples measuring it (2%)Pancreatic — 5q gained in 181 of 2,910 samples measuring it (6%)Pancreatic — 5q lost in 140 of 2,910 samples measuring it (5%)Pancreatic — 6p gained in 95 of 2,910 samples measuring it (3%)Pancreatic — 6p lost in 379 of 2,910 samples measuring it (13%)Pancreatic — 6q gained in 28 of 2,910 samples measuring it (1%)Pancreatic — 6q lost in 603 of 2,910 samples measuring it (21%)Pancreatic — 7p gained in 429 of 2,910 samples measuring it (15%)Pancreatic — 7p lost in 22 of 2,910 samples measuring it (1%)Pancreatic — 7q gained in 269 of 2,910 samples measuring it (9%)Pancreatic — 7q lost in 87 of 2,910 samples measuring it (3%)Pancreatic — 8p gained in 99 of 2,910 samples measuring it (3%)Pancreatic — 8p lost in 559 of 2,910 samples measuring it (19%)Pancreatic — 8q gained in 411 of 2,910 samples measuring it (14%)Pancreatic — 8q lost in 123 of 2,910 samples measuring it (4%)Pancreatic — 9p gained in 90 of 2,908 samples measuring it (3%)Pancreatic — 9p lost in 649 of 2,908 samples measuring it (22%)Pancreatic — 9q gained in 138 of 2,910 samples measuring it (5%)Pancreatic — 9q lost in 331 of 2,910 samples measuring it (11%)Pancreatic — 10p gained in 60 of 2,910 samples measuring it (2%)Pancreatic — 10p lost in 286 of 2,910 samples measuring it (10%)Pancreatic — 10q gained in 54 of 2,910 samples measuring it (2%)Pancreatic — 10q lost in 260 of 2,910 samples measuring it (9%)Pancreatic — 11p gained in 74 of 2,910 samples measuring it (3%)Pancreatic — 11p lost in 257 of 2,910 samples measuring it (9%)Pancreatic — 11q gained in 74 of 2,910 samples measuring it (3%)Pancreatic — 11q lost in 251 of 2,910 samples measuring it (9%)Pancreatic — 12p gained in 412 of 2,910 samples measuring it (14%)Pancreatic — 12p lost in 108 of 2,910 samples measuring it (4%)Pancreatic — 12q gained in 224 of 2,910 samples measuring it (8%)Pancreatic — 12q lost in 207 of 2,910 samples measuring it (7%)Pancreatic — 13q gained in 249 of 2,910 samples measuring it (9%)Pancreatic — 13q lost in 186 of 2,910 samples measuring it (6%)Pancreatic — 14q gained in 218 of 2,910 samples measuring it (7%)Pancreatic — 14q lost in 154 of 2,910 samples measuring it (5%)Pancreatic — 15q gained in 88 of 2,910 samples measuring it (3%)Pancreatic — 15q lost in 226 of 2,910 samples measuring it (8%)Pancreatic — 16p gained in 151 of 2,909 samples measuring it (5%)Pancreatic — 16p lost in 230 of 2,909 samples measuring it (8%)Pancreatic — 16q gained in 135 of 2,909 samples measuring it (5%)Pancreatic — 16q lost in 229 of 2,909 samples measuring it (8%)Pancreatic — 17p gained in 115 of 2,910 samples measuring it (4%)Pancreatic — 17p lost in 662 of 2,910 samples measuring it (23%)Pancreatic — 17q gained in 227 of 2,910 samples measuring it (8%)Pancreatic — 17q lost in 130 of 2,910 samples measuring it (4%)Pancreatic — 18p gained in 351 of 2,910 samples measuring it (12%)Pancreatic — 18p lost in 402 of 2,910 samples measuring it (14%)Pancreatic — 18q gained in 120 of 2,910 samples measuring it (4%)Pancreatic — 18q lost in 929 of 2,910 samples measuring it (32%)Pancreatic — 19p gained in 211 of 2,900 samples measuring it (7%)Pancreatic — 19p lost in 222 of 2,900 samples measuring it (8%)Pancreatic — 19q gained in 375 of 2,910 samples measuring it (13%)Pancreatic — 19q lost in 97 of 2,910 samples measuring it (3%)Pancreatic — 20p gained in 324 of 2,909 samples measuring it (11%)Pancreatic — 20p lost in 194 of 2,909 samples measuring it (7%)Pancreatic — 20q gained in 505 of 2,910 samples measuring it (17%)Pancreatic — 20q lost in 32 of 2,910 samples measuring it (1%)Pancreatic — 21q gained in 75 of 2,910 samples measuring it (3%)Pancreatic — 21q lost in 381 of 2,910 samples measuring it (13%)Pancreatic — 22q gained in 68 of 2,907 samples measuring it (2%)Pancreatic — 22q lost in 423 of 2,907 samples measuring it (15%)1p1q2p2q3p3q4p4q5p5q6p6q7p7q8p8q9p9q10p10q11p11q12p12q13q14q15q16p16q17p17q18p18q19p19q20p20q21q22qFrom each sample's copy-number segments (hg19; autosomes only — X's log ratio mostly records sex), computed per sample in the database: FGA is the share of thesample's segmented length whose |segment mean| is at or past 0.2 (log2), counted in 1-point bins; an arm (p and q split at hg19's centromeres, from UCSC's cytoBand;the acrocentric p arms of 13, 14, 15, 21 and 22 left out, as Taylor et al. 2018 did) is gained or lost when more than 50% of its length is past ±0.2, and the aneuploidyscore is how many of the 39 arms are. Colorectal: 5,390 samples scored of 5,395 (5,390 with segments; 5,354 with every arm covered); Breast: 6,906 samples scored of6,907 (6,906 with segments; 6,685 with every arm covered); Pancreatic: 2,910 samples scored of 2,911 (2,910 with segments; 2,891 with every arm covered); an armsegments cover for less than 50% of its length is not called, which lowers that sample's score. Boxes are the quartiles, whiskers the 5th and 95th percentiles, read fromthe histograms; Kruskal–Wallis compares the cohorts' distributions (normal approximation with ties). Every sample of each patient counts. Panel-derived segments arenoisy and purity is not corrected — a low-purity tumour's changes sit near 0 and read as stable — so these are relative measures within one assay, not absolute ploidy. diff --git a/test/snapshots/instability.txt b/test/snapshots/instability.txt new file mode 100644 index 0000000..2f264c0 --- /dev/null +++ b/test/snapshots/instability.txt @@ -0,0 +1,19 @@ +Analysis over msk_impact_50k_2026 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- Colorectal: 4,993 patients — CANCER_TYPE in [Colorectal Cancer] (any sample) +- Breast: 5,803 patients — CANCER_TYPE in [Breast Cancer] (any sample) +- Pancreatic: 2,729 patients — CANCER_TYPE in [Pancreatic Cancer] (any sample) + +Genomic instability (hg19 segments, threshold ±0.2, arm called past 50% of its length; 39 arms): + Colorectal: 5,390 samples scored; aneuploidy score median [IQR] 7 [2–12], FGA 15 [4–28]% + most often gained: 20q 60%, 13q 42%, 20p 31%, 8q 31%, 7p 29% + most often lost: 18q 53%, 18p 43%, 8p 39%, 17p 36%, 4q 26% + Breast: 6,906 samples scored; aneuploidy score median [IQR] 8 [3–14], FGA 22 [9–38]% + most often gained: 1q 45%, 8q 41%, 16p 27%, 20q 23%, 5p 14% + most often lost: 8p 41%, 17p 40%, 16q 39%, 11q 31%, 13q 31% + Pancreatic: 2,910 samples scored; aneuploidy score median [IQR] 2 [0–9], FGA 6 [0–22]% + most often gained: 20q 17%, 7p 15%, 12p 14%, 8q 14%, 19q 13% + most often lost: 18q 32%, 17p 23%, 9p 22%, 6q 21%, 8p 19% + Kruskal–Wallis: aneuploidy score p=2.0e-163, FGA p=1.2e-222 + CAVEAT (tell the user): From each sample's copy-number segments (hg19; autosomes only — X's log ratio mostly records sex), computed per sample in the database: FGA is the share of the sample's segmented length whose |segment mean| is at or past 0.2 (log2), counted in 1-point bins; an arm (p and q split at hg19's centromeres, from UCSC's cytoBand; the acrocentric p arms of 13, 14, 15, 21 and 22 left out, as Taylor et al. 2018 did) is gained or lost when more than 50% of its length is past ±0.2, and the aneuploidy score is how many of the 39 arms are. Colorectal: 5,390 samples scored of 5,395 (5,390 with segments; 5,354 with every arm covered); Breast: 6,906 samples scored of 6,907 (6,906 with segments; 6,685 with every arm covered); Pancreatic: 2,910 samples scored of 2,911 (2,910 with segments; 2,891 with every arm covered); an arm segments cover for less than 50% of its length is not called, which lowers that sample's score. Boxes are the quartiles, whiskers the 5th and 95th percentiles, read from the histograms; Kruskal–Wallis compares the cohorts' distributions (normal approximation with ties). Every sample of each patient counts. Panel-derived segments are noisy and purity is not corrected — a low-purity tumour's changes sit near 0 and read as stable — so these are relative measures within one assay, not absolute ploidy. diff --git a/test/snapshots/molsubtype.json b/test/snapshots/molsubtype.json new file mode 100644 index 0000000..36a0d4b --- /dev/null +++ b/test/snapshots/molsubtype.json @@ -0,0 +1,561 @@ +{ + "study": "ucec_tcga_pan_can_atlas_2018", + "cohorts": { + "UCEC": { + "count": 529, + "text": "all patients in the study", + "studies": [ + "ucec_tcga_pan_can_atlas_2018" + ] + } + }, + "views": [ + { + "kind": "molsubtype", + "cohorts": [ + "UCEC" + ], + "molsubtype": { + "classifier": "endometrial", + "classifierLabel": "Endometrial carcinoma molecular classification (TCGA 2013 / ProMisE / WHO 2020)", + "subtypes": [ + "POLEmut", + "MMRd", + "p53abn", + "NSMP" + ], + "msi": "MSI_SENSOR_SCORE (MSIsensor; ≥ 10 MSI-high, < 3 stable)", + "p53": "a TP53 mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic", + "poleHotspots": [ + "P286R", + "S297F", + "F367S", + "F367V", + "V411L", + "L424I", + "L424V", + "P436R", + "M444K", + "A456P", + "S459F", + "M295R", + "D368Y" + ], + "by": "SUBTYPE", + "panels": [ + { + "cohort": "UCEC", + "patients": 529, + "counts": [ + 49, + 125, + 150, + 167 + ], + "classified": 491, + "notClassifiable": 38, + "why": { + "no MSI call": 26, + "not sequenced": 12 + }, + "groups": [ + { + "label": "UCEC_CN_HIGH", + "counts": [ + 0, + 0, + 139, + 22 + ], + "classified": 161, + "notClassifiable": 2, + "why": { + "no MSI call": 2 + } + }, + { + "label": "UCEC_MSI", + "counts": [ + 1, + 122, + 0, + 3 + ], + "classified": 126, + "notClassifiable": 22, + "why": { + "no MSI call": 22 + } + }, + { + "label": "UCEC_CN_LOW", + "counts": [ + 0, + 1, + 9, + 135 + ], + "classified": 145, + "notClassifiable": 2, + "why": { + "no MSI call": 2 + } + }, + { + "label": "UCEC_POLE", + "counts": [ + 48, + 0, + 0, + 1 + ], + "classified": 49, + "notClassifiable": 0, + "why": {} + }, + { + "label": "No value recorded", + "counts": [ + 0, + 2, + 2, + 6 + ], + "classified": 10, + "notClassifiable": 12, + "why": { + "not sequenced": 12 + } + } + ], + "byTest": { + "chi2": 1219.2153, + "df": 9, + "p": 8.615875507132425e-257, + "minExpected": 4.99 + } + } + ], + "survival": [ + { + "cohort": "UCEC", + "curves": [ + { + "subtype": "POLEmut", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 13.84, + "s": 0.9773 + }, + { + "t": 106.88, + "s": 0.6515 + } + ], + "median": null, + "n": 49, + "events": 2, + "atRisk": [ + 49, + 49, + 48, + 48, + 47, + 47, + 47, + 46, + 46, + 46, + 46, + 45, + 45, + 44, + 43, + 42, + 42, + 42, + 41, + 40, + { + "…": "130 more", + "sha256": "9d0672a89fc773f9" + } + ] + }, + { + "subtype": "MMRd", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 2.37, + "s": 0.9914 + }, + { + "t": 7.99, + "s": 0.9824 + }, + { + "t": 10.26, + "s": 0.9734 + }, + { + "t": 13.45, + "s": 0.9641 + }, + { + "t": 13.58, + "s": 0.9548 + }, + { + "t": 14.76, + "s": 0.9454 + }, + { + "t": 17.85, + "s": 0.9351 + }, + { + "t": 23.31, + "s": 0.9226 + }, + { + "t": 23.87, + "s": 0.9101 + }, + { + "t": 24.33, + "s": 0.8977 + }, + { + "t": 25.22, + "s": 0.8852 + }, + { + "t": 31.13, + "s": 0.8707 + }, + { + "t": 32.78, + "s": 0.8554 + }, + { + "t": 34.36, + "s": 0.8393 + }, + { + "t": 35.54, + "s": 0.8225 + } + ], + "median": null, + "n": 124, + "events": 15, + "atRisk": [ + 124, + 120, + 117, + 114, + 113, + 112, + 112, + 111, + 110, + 110, + 109, + 107, + 106, + 106, + 103, + 100, + 96, + 92, + 90, + 86, + { + "…": "98 more", + "sha256": "7a43748e7e04ba15" + } + ] + }, + { + "subtype": "p53abn", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1.64, + "s": 0.9929 + }, + { + "t": 2.96, + "s": 0.9856 + }, + { + "t": 4.44, + "s": 0.9784 + }, + { + "t": 4.77, + "s": 0.9711 + }, + { + "t": 10.19, + "s": 0.9637 + }, + { + "t": 11.08, + "s": 0.9562 + }, + { + "t": 11.38, + "s": 0.9488 + }, + { + "t": 11.51, + "s": 0.9413 + }, + { + "t": 14.04, + "s": 0.9336 + }, + { + "t": 14.43, + "s": 0.9259 + }, + { + "t": 17.06, + "s": 0.9177 + }, + { + "t": 17.75, + "s": 0.9093 + }, + { + "t": 18.21, + "s": 0.9008 + }, + { + "t": 19.4, + "s": 0.8919 + }, + { + "t": 20.05, + "s": 0.883 + }, + { + "t": 20.25, + "s": 0.8741 + }, + { + "t": 20.51, + "s": 0.8651 + }, + { + "t": 21.99, + "s": 0.8558 + }, + { + "t": 22.03, + "s": 0.8465 + }, + { + "…": "24 more", + "sha256": "8d5496e9de838d69" + } + ], + "median": 102.31, + "n": 150, + "events": 43, + "atRisk": [ + 150, + 140, + 138, + 136, + 136, + 134, + 134, + 134, + 134, + 133, + 131, + 129, + 126, + 125, + 123, + 118, + 116, + 113, + 108, + 101, + { + "…": "106 more", + "sha256": "fe8bf5e49f8fa15e" + } + ] + }, + { + "subtype": "NSMP", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1.91, + "s": 0.9938 + }, + { + "t": 3.95, + "s": 0.9813 + }, + { + "t": 4.27, + "s": 0.975 + }, + { + "t": 8.48, + "s": 0.9687 + }, + { + "t": 9.37, + "s": 0.9623 + }, + { + "t": 10.19, + "s": 0.9559 + }, + { + "t": 10.72, + "s": 0.9494 + }, + { + "t": 11.87, + "s": 0.9429 + }, + { + "t": 12.39, + "s": 0.9364 + }, + { + "t": 21.9, + "s": 0.928 + }, + { + "t": 23.47, + "s": 0.9194 + }, + { + "t": 28.77, + "s": 0.9095 + }, + { + "t": 33.4, + "s": 0.898 + }, + { + "t": 36.13, + "s": 0.8852 + }, + { + "t": 37.05, + "s": 0.8713 + }, + { + "t": 40.8, + "s": 0.8568 + }, + { + "t": 43.3, + "s": 0.8418 + }, + { + "t": 43.82, + "s": 0.8267 + }, + { + "t": 46.95, + "s": 0.8105 + }, + { + "…": "2 more", + "sha256": "d732c53f57158b8f" + } + ], + "median": null, + "n": 167, + "events": 22, + "atRisk": [ + 167, + 162, + 160, + 159, + 157, + 155, + 154, + 154, + 154, + 152, + 150, + 146, + 145, + 143, + 141, + 136, + 131, + 126, + 123, + 120, + { + "…": "206 more", + "sha256": "8711a1c6196593f2" + } + ] + } + ], + "logrank": { + "chi2": 27.3698, + "p": 0.000004924725605267386, + "df": 3, + "observed": [ + 2, + 15, + 43, + 22 + ], + "expected": [ + 10.3, + 20.8, + 22.9, + 28 + ], + "groups": 4, + "strata": 1 + } + } + ] + }, + "participation": { + "studies": [ + { + "study": "ucec_tcga_pan_can_atlas_2018", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Endometrial carcinoma molecular classification (TCGA 2013 / ProMisE / WHO 2020) (TCGA, Nature 2013; Talhouk et al., ProMisE, Br J Cancer 2015; WHO Classification of Female Genital Tumours, 5th ed. 2020), applied per patient over all their samples, in this order, first match wins: POLE-ultramutated — a somatic POLE exonuclease-domain hotspot called in any sample (P286R, S297F, F367S, F367V, V411L, L424I, L424V, P436R, M444K, A456P, S459F, M295R, D368Y; León-Castillo et al. 2020); else MMR-deficient — MSI-high in any sample by MSI_SENSOR_SCORE (MSIsensor; ≥ 10 MSI-high, < 3 stable) (from the MSI call, not mismatch-repair immunohistochemistry, which the warehouse does not hold); else p53-abnormal — a TP53 mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic (a sequencing proxy for p53 immunohistochemistry); else NSMP. A patient is “not classifiable”, never put in a subtype by guess, when an input the hierarchy needs before reaching their subtype is missing: not sequenced, not profiled for POLE or TP53 (panel-aware), or no MSI-high or stable call (an indeterminate or unreported MSI result counts as none) — 38 patients here (no MSI call 26, not sequenced 12), kept out of the shares, tests and curves. Shares are of classified patients. By SUBTYPE per patient (“Multiple” when samples disagree; the 8 largest groups drawn, the rest pooled into a named Other), chi-squared over group × subtype per cohort, patients without a value left out of the test. Overall survival by subtype from OS_MONTHS / OS_STATUS as the study records them (for each cohort), Kaplan–Meier with a k-group log-rank; OS is not corrected for left truncation (a patient sequenced years after diagnosis entered the study alive), and the subtypes differ in stage, histology and treatment, none adjusted for. A panel-based call is not the clinical assay: POLE variants outside the hotspot list, MSI by IHC, and p53 IHC patterns are not seen. " + } + ] +} diff --git a/test/snapshots/molsubtype.sql b/test/snapshots/molsubtype.sql new file mode 100644 index 0000000..6f8f01c --- /dev/null +++ b/test/snapshots/molsubtype.sql @@ -0,0 +1,126 @@ +-- cohort UCEC +-- ucec_tcga_pan_can_atlas_2018 +SELECT DISTINCT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' + +-- view 0 molsubtype: molsubtype:counts:UCEC · ucec_tcga_pan_can_atlas_2018 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018'), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND sp.alteration_type = 'MUTATION_EXTENDED' AND replaceOne(sd.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) +), +cov AS ( + SELECT pp.pid AS pid, max(pp.panel = 'WES' OR gp.gene = 'POLE') AS pole, max(pp.panel = 'WES' OR gp.gene = 'TP53') AS tp53 + FROM pp LEFT JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel AND gp.gene IN ('POLE', 'TP53') + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, + max(g.hugo_gene_symbol = 'POLE' AND g.mutation_variant IN ('P286R', 'S297F', 'F367S', 'F367V', 'V411L', 'L424I', 'L424V', 'P436R', 'M444K', 'A456P', 'S459F', 'M295R', 'D368Y')) AS pole_hot, + max(g.hugo_gene_symbol = 'TP53' AND ann.is_driver) AS p53 + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' + AND lowerUTF8(g.mutation_status) != 'germline' AND NOT g.off_panel AND g.hugo_gene_symbol IN ('POLE', 'TP53') + AND replaceOne(g.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +msi AS ( + SELECT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, max(multiIf(if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) IS NULL OR if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) < 0, 0, if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) >= 10, 2, if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) < 3, 1, 0)) AS m + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND attribute_name = 'MSI_SENSOR_SCORE' AND replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +cls AS ( + SELECT c.pid AS pid, + multiIf(cov.pid = '', 'NC', mut.pole_hot = 1, 'POLEmut', cov.pole = 0, 'NC', msi.m = 2, 'MMRd', msi.m = 0, 'NC', cov.tp53 = 0, 'NC', mut.p53 = 1, 'p53abn', 'NSMP') AS cls, + multiIf(cov.pid = '', 'not sequenced', mut.pole_hot = 1, '', cov.pole = 0, 'not profiled for POLE', msi.m = 2, '', msi.m = 0, 'no MSI call', cov.tp53 = 0, 'not profiled for TP53', '') AS why + FROM (SELECT DISTINCT pid FROM cohort) c + LEFT JOIN cov ON cov.pid = c.pid LEFT JOIN mut ON mut.pid = c.pid LEFT JOIN msi ON msi.pid = c.pid +), +grp AS ( + SELECT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, if(uniqExact(lowerUTF8(trimBoth(attribute_value))) > 1, 'Multiple', any(trimBoth(attribute_value))) AS g + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND attribute_name = 'SUBTYPE' AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT cls.cls AS cls, cls.why AS why, grp.g AS grp, count() AS n FROM cls LEFT JOIN grp ON grp.pid = cls.pid GROUP BY cls, why, grp + +-- view 0 molsubtype: molsubtype:msi-attributes · ucec_tcga_pan_can_atlas_2018 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018') +SELECT attribute_name AS attribute, uniqExact(replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '')) AS patients, uniqExact(attribute_value) AS nvalues, arraySort(groupUniqArray(8)(attribute_value)) AS examples +FROM snapshot_db.clinical_data_derived +WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND positionCaseInsensitive(attribute_name, 'MSI') > 0 AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) +GROUP BY attribute ORDER BY patients DESC + +-- view 0 molsubtype: molsubtype:survival:UCEC · ucec_tcga_pan_can_atlas_2018 +WITH cohort AS (SELECT DISTINCT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018'), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND sp.alteration_type = 'MUTATION_EXTENDED' AND replaceOne(sd.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) +), +cov AS ( + SELECT pp.pid AS pid, max(pp.panel = 'WES' OR gp.gene = 'POLE') AS pole, max(pp.panel = 'WES' OR gp.gene = 'TP53') AS tp53 + FROM pp LEFT JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel AND gp.gene IN ('POLE', 'TP53') + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, + max(g.hugo_gene_symbol = 'POLE' AND g.mutation_variant IN ('P286R', 'S297F', 'F367S', 'F367V', 'V411L', 'L424I', 'L424V', 'P436R', 'M444K', 'A456P', 'S459F', 'M295R', 'D368Y')) AS pole_hot, + max(g.hugo_gene_symbol = 'TP53' AND ann.is_driver) AS p53 + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' + AND lowerUTF8(g.mutation_status) != 'germline' AND NOT g.off_panel AND g.hugo_gene_symbol IN ('POLE', 'TP53') + AND replaceOne(g.patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +msi AS ( + SELECT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, max(multiIf(if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) IS NULL OR if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) < 0, 0, if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) >= 10, 2, if(isFinite(ifNull(toFloat64OrNull(trimBoth(attribute_value)), nan)), toFloat64OrNull(trimBoth(attribute_value)), NULL) < 3, 1, 0)) AS m + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND attribute_name = 'MSI_SENSOR_SCORE' AND replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +cls AS ( + SELECT c.pid AS pid, + multiIf(cov.pid = '', 'NC', mut.pole_hot = 1, 'POLEmut', cov.pole = 0, 'NC', msi.m = 2, 'MMRd', msi.m = 0, 'NC', cov.tp53 = 0, 'NC', mut.p53 = 1, 'p53abn', 'NSMP') AS cls, + multiIf(cov.pid = '', 'not sequenced', mut.pole_hot = 1, '', cov.pole = 0, 'not profiled for POLE', msi.m = 2, '', msi.m = 0, 'no MSI call', cov.tp53 = 0, 'not profiled for TP53', '') AS why + FROM (SELECT DISTINCT pid FROM cohort) c + LEFT JOIN cov ON cov.pid = c.pid LEFT JOIN mut ON mut.pid = c.pid LEFT JOIN msi ON msi.pid = c.pid +), +grp AS ( + SELECT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, if(uniqExact(lowerUTF8(trimBoth(attribute_value))) > 1, 'Multiple', any(trimBoth(attribute_value))) AS g + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND attribute_name = 'SUBTYPE' AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +os AS ( + SELECT replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') AS pid, if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'ucec_tcga_pan_can_atlas_2018' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'ucec_tcga_pan_can_atlas_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT cls.cls AS cls, round(os.months, 2) AS t, os.dead AS dead, count() AS n +FROM cls INNER JOIN os ON os.pid = cls.pid WHERE os.months IS NOT NULL AND os.months >= 0 AND cls.cls != 'NC' GROUP BY cls, t, dead ORDER BY t diff --git a/test/snapshots/molsubtype.svg b/test/snapshots/molsubtype.svg new file mode 100644 index 0000000..efd8291 --- /dev/null +++ b/test/snapshots/molsubtype.svg @@ -0,0 +1,2 @@ + +Molecular subtypes — Endometrial carcinoma molecular classification (TCGA 2013 / ProMisE / WHO 2020)POLE-ultramutatedMMR-deficientp53-abnormalNSMPUCECUCEC: POLE-ultramutated 49 of 491 classified (10.0%)10%UCEC: MMR-deficient 125 of 491 classified (25.5%)25%UCEC: p53-abnormal 150 of 491 classified (30.5%)31%UCEC: NSMP 167 of 491 classified (34.0%)34%n = 491 · 38 not classifiableby SUBTYPE · χ² 1,219 (9 df), p = 8.6e-257UCEC_CN_HIGHUCEC_CN_HIGH: p53-abnormal 139 of 161 classified (86.3%)86%UCEC_CN_HIGH: NSMP 22 of 161 classified (13.7%)14%n = 161 · 2 not classifiableUCEC_MSIUCEC_MSI: POLE-ultramutated 1 of 126 classified (0.8%)UCEC_MSI: MMR-deficient 122 of 126 classified (96.8%)97%UCEC_MSI: NSMP 3 of 126 classified (2.4%)n = 126 · 22 not classifiableUCEC_CN_LOWUCEC_CN_LOW: MMR-deficient 1 of 145 classified (0.7%)UCEC_CN_LOW: p53-abnormal 9 of 145 classified (6.2%)6%UCEC_CN_LOW: NSMP 135 of 145 classified (93.1%)93%n = 145 · 2 not classifiableUCEC_POLEUCEC_POLE: POLE-ultramutated 48 of 49 classified (98.0%)98%UCEC_POLE: NSMP 1 of 49 classified (2.0%)n = 49No value recordedNo value recorded: MMR-deficient 2 of 10 classified (20.0%)20%No value recorded: p53-abnormal 2 of 10 classified (20.0%)20%No value recorded: NSMP 6 of 10 classified (60.0%)60%n = 10 · 12 not classifiableOverall survival by subtype — UCEC0%25%50%75%100%Overall survival024487296120144168192216Months (OS_MONTHS)Number at riskPOLE-ultramutatedPOLE-ultramutated: 49 at risk at 0 months49POLE-ultramutated: 35 at risk at 24 months35POLE-ultramutated: 22 at risk at 48 months22POLE-ultramutated: 10 at risk at 72 months10POLE-ultramutated: 5 at risk at 96 months5POLE-ultramutated: 2 at risk at 120 months2POLE-ultramutated: 1 at risk at 144 months1POLE-ultramutated: 0 at risk at 168 months0POLE-ultramutated: 0 at risk at 192 months0POLE-ultramutated: 0 at risk at 216 months0MMR-deficientMMR-deficient: 124 at risk at 0 months124MMR-deficient: 73 at risk at 24 months73MMR-deficient: 40 at risk at 48 months40MMR-deficient: 18 at risk at 72 months18MMR-deficient: 5 at risk at 96 months5MMR-deficient: 0 at risk at 120 months0MMR-deficient: 0 at risk at 144 months0MMR-deficient: 0 at risk at 168 months0MMR-deficient: 0 at risk at 192 months0MMR-deficient: 0 at risk at 216 months0p53-abnormalp53-abnormal: 150 at risk at 0 months150p53-abnormal: 82 at risk at 24 months82p53-abnormal: 35 at risk at 48 months35p53-abnormal: 16 at risk at 72 months16p53-abnormal: 7 at risk at 96 months7p53-abnormal: 1 at risk at 120 months1p53-abnormal: 0 at risk at 144 months0p53-abnormal: 0 at risk at 168 months0p53-abnormal: 0 at risk at 192 months0p53-abnormal: 0 at risk at 216 months0NSMPNSMP: 167 at risk at 0 months167NSMP: 105 at risk at 24 months105NSMP: 49 at risk at 48 months49NSMP: 24 at risk at 72 months24NSMP: 5 at risk at 96 months5NSMP: 3 at risk at 120 months3NSMP: 2 at risk at 144 months2NSMP: 2 at risk at 168 months2NSMP: 1 at risk at 192 months1NSMP: 1 at risk at 216 months1POLE-ultramutatedn = 49 · 2 deaths · median not reachedMMR-deficientn = 124 · 15 deaths · median not reachedp53-abnormaln = 150 · 43 deaths · median 102 moNSMPn = 167 · 22 deaths · median not reached4-group log-rank χ² = 27.4 (3 df) · p = 4.9e-6Endometrial carcinoma molecular classification (TCGA 2013 / ProMisE / WHO 2020) (TCGA, Nature 2013; Talhouk et al., ProMisE, Br J Cancer 2015; WHO Classificationof Female Genital Tumours, 5th ed. 2020), applied per patient over all their samples, in this order, first match wins: POLE-ultramutated — a somatic POLEexonuclease-domain hotspot called in any sample (P286R, S297F, F367S, F367V, V411L, L424I, L424V, P436R, M444K, A456P, S459F, M295R, D368Y; León-Castillo etal. 2020); else MMR-deficient — MSI-high in any sample by MSI_SENSOR_SCORE (MSIsensor; ≥ 10 MSI-high, < 3 stable) (from the MSI call, not mismatch-repairimmunohistochemistry, which the warehouse does not hold); else p53-abnormal — a TP53 mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic (asequencing proxy for p53 immunohistochemistry); else NSMP. A patient is “not classifiable”, never put in a subtype by guess, when an input the hierarchy needs beforereaching their subtype is missing: not sequenced, not profiled for POLE or TP53 (panel-aware), or no MSI-high or stable call (an indeterminate or unreported MSI resultcounts as none) — 38 patients here (no MSI call 26, not sequenced 12), kept out of the shares, tests and curves. Shares are of classified patients. By SUBTYPE per patient(“Multiple” when samples disagree; the 8 largest groups drawn, the rest pooled into a named Other), chi-squared over group × subtype per cohort, patients without avalue left out of the test. Overall survival by subtype from OS_MONTHS / OS_STATUS as the study records them (for each cohort), Kaplan–Meier with a k-group log-rank;OS is not corrected for left truncation (a patient sequenced years after diagnosis entered the study alive), and the subtypes differ in stage, histology and treatment, noneadjusted for. A panel-based call is not the clinical assay: POLE variants outside the hotspot list, MSI by IHC, and p53 IHC patterns are not seen. diff --git a/test/snapshots/molsubtype.txt b/test/snapshots/molsubtype.txt new file mode 100644 index 0000000..fbfc0a5 --- /dev/null +++ b/test/snapshots/molsubtype.txt @@ -0,0 +1,15 @@ +Analysis over ucec_tcga_pan_can_atlas_2018 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- UCEC: 529 patients — all patients in the study + +Molecular subtypes — Endometrial carcinoma molecular classification (TCGA 2013 / ProMisE / WHO 2020); MSI by MSI_SENSOR_SCORE (MSIsensor; ≥ 10 MSI-high, < 3 stable); p53 abnormal = a TP53 mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic: + UCEC (491 classified, 38 not classifiable): POLEmut 49 (10.0%), MMRd 125 (25.5%), p53abn 150 (30.5%), NSMP 167 (34.0%) + UCEC_CN_HIGH (161 classified, 2 not classifiable): POLEmut 0 (0.0%), MMRd 0 (0.0%), p53abn 139 (86.3%), NSMP 22 (13.7%) + UCEC_MSI (126 classified, 22 not classifiable): POLEmut 1 (0.8%), MMRd 122 (96.8%), p53abn 0 (0.0%), NSMP 3 (2.4%) + UCEC_CN_LOW (145 classified, 2 not classifiable): POLEmut 0 (0.0%), MMRd 1 (0.7%), p53abn 9 (6.2%), NSMP 135 (93.1%) + UCEC_POLE (49 classified, 0 not classifiable): POLEmut 48 (98.0%), MMRd 0 (0.0%), p53abn 0 (0.0%), NSMP 1 (2.0%) + No value recorded (10 classified, 12 not classifiable): POLEmut 0 (0.0%), MMRd 2 (20.0%), p53abn 2 (20.0%), NSMP 6 (60.0%) + by SUBTYPE: chi-squared 1219.2 (9 df), p=8.6e-257 + OS by subtype, UCEC: POLEmut n=49, 2 deaths, median not reached; MMRd n=124, 15 deaths, median not reached; p53abn n=150, 43 deaths, median 102.3 mo; NSMP n=167, 22 deaths, median not reached; log-rank chi-squared 27.4 (3 df), p=4.9e-6 + CAVEAT (tell the user): Endometrial carcinoma molecular classification (TCGA 2013 / ProMisE / WHO 2020) (TCGA, Nature 2013; Talhouk et al., ProMisE, Br J Cancer 2015; WHO Classification of Female Genital Tumours, 5th ed. 2020), applied per patient over all their samples, in this order, first match wins: POLE-ultramutated — a somatic POLE exonuclease-domain hotspot called in any sample (P286R, S297F, F367S, F367V, V411L, L424I, L424V, P436R, M444K, A456P, S459F, M295R, D368Y; León-Castillo et al. 2020); else MMR-deficient — MSI-high in any sample by MSI_SENSOR_SCORE (MSIsensor; ≥ 10 MSI-high, < 3 stable) (from the MSI call, not mismatch-repair immunohistochemistry, which the warehouse does not hold); else p53-abnormal — a TP53 mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic (a sequencing proxy for p53 immunohistochemistry); else NSMP. A patient is “not classifiable”, never put in a subtype by guess, when an input the hierarchy needs before reaching their subtype is missing: not sequenced, not profiled for POLE or TP53 (panel-aware), or no MSI-high or stable call (an indeterminate or unreported MSI result counts as none) — 38 patients here (no MSI call 26, not sequenced 12), kept out of the shares, tests and curves. Shares are of classified patients. By SUBTYPE per patient (“Multiple” when samples disagree; the 8 largest groups drawn, the rest pooled into a named Other), chi-squared over group × subtype per cohort, patients without a value left out of the test. Overall survival by subtype from OS_MONTHS / OS_STATUS as the study records them (for each cohort), Kaplan–Meier with a k-group log-rank; OS is not corrected for left truncation (a patient sequenced years after diagnosis entered the study alive), and the subtypes differ in stage, histology and treatment, none adjusted for. A panel-based call is not the clinical assay: POLE variants outside the hotspot list, MSI by IHC, and p53 IHC patterns are not seen. diff --git a/test/snapshots/multistate.json b/test/snapshots/multistate.json new file mode 100644 index 0000000..b80551b --- /dev/null +++ b/test/snapshots/multistate.json @@ -0,0 +1,1254 @@ +{ + "study": "msk_chord_2024", + "cohorts": { + "Colorectal, stage 1-3": { + "count": 3171, + "text": "CANCER_TYPE in [Colorectal Cancer] (any sample) AND STAGE_HIGHEST_RECORDED in [Stage 1-3]", + "studies": [ + "msk_chord_2024" + ] + }, + "Colorectal, stage 4": { + "count": 2372, + "text": "CANCER_TYPE in [Colorectal Cancer] (any sample) AND STAGE_HIGHEST_RECORDED in [Stage 4]", + "studies": [ + "msk_chord_2024" + ] + } + }, + "views": [ + { + "kind": "multistate", + "cohorts": [ + "Colorectal, stage 1-3", + "Colorectal, stage 4" + ], + "multistate": { + "progression": { + "eventType": "Diagnosis", + "key": "PROGRESSION", + "values": [ + "Y", + "Yes" + ] + }, + "prevalent": "include", + "horizons": [ + 12, + 24, + 60 + ], + "tMax": 104.1862, + "origin": "first sequencing", + "cohorts": [ + { + "cohort": "Colorectal, stage 1-3", + "cohortPatients": 3171, + "n": 3171, + "prevalent": 937, + "excluded": { + "noFollowup": 0, + "prevalent": 0 + }, + "notAssessed": 31, + "afterEnd": 0, + "seq0": 3171, + "late": 0, + "censored": { + "start": 1442, + "progression": 943 + }, + "rates": [ + { + "key": "01", + "events": 729, + "personMonths": 58632.9, + "rate": 1.243328635808073, + "lo": 1.1562718513414076, + "hi": 1.3369400066489403 + }, + { + "key": "02", + "events": 63, + "personMonths": 58632.9, + "rate": 0.10744815371180878, + "lo": 0.08393781177637821, + "hi": 0.13754356340422863 + }, + { + "key": "12", + "events": 723, + "personMonths": 45374.6, + "rate": 1.593401099705051, + "lo": 1.4813871957741802, + "hi": 1.7138848450856294 + } + ], + "occupation": [ + { + "t": 0, + "p0": 0.70451, + "p1": 0.29549, + "p2": 0 + }, + { + "t": 0.0329, + "p0": 0.70419, + "p1": 0.29518, + "p2": 0.00063 + }, + { + "t": 0.0657, + "p0": 0.70356, + "p1": 0.29581, + "p2": 0.00063 + }, + { + "t": 0.0986, + "p0": 0.70292, + "p1": 0.29645, + "p2": 0.00063 + }, + { + "t": 0.1314, + "p0": 0.7026, + "p1": 0.29677, + "p2": 0.00063 + }, + { + "t": 0.1971, + "p0": 0.70228, + "p1": 0.29709, + "p2": 0.00063 + }, + { + "t": 0.23, + "p0": 0.70101, + "p1": 0.29836, + "p2": 0.00063 + }, + { + "t": 0.2628, + "p0": 0.70069, + "p1": 0.29868, + "p2": 0.00063 + }, + { + "t": 0.2957, + "p0": 0.70037, + "p1": 0.299, + "p2": 0.00063 + }, + { + "t": 0.3285, + "p0": 0.70005, + "p1": 0.29932, + "p2": 0.00063 + }, + { + "t": 0.3288, + "p0": 0.69941, + "p1": 0.299, + "p2": 0.00159 + }, + { + "t": 0.3614, + "p0": 0.69845, + "p1": 0.29996, + "p2": 0.00159 + }, + { + "t": 0.3943, + "p0": 0.69781, + "p1": 0.3006, + "p2": 0.00159 + }, + { + "t": 0.3945, + "p0": 0.69749, + "p1": 0.3006, + "p2": 0.00191 + }, + { + "t": 0.4271, + "p0": 0.69653, + "p1": 0.30156, + "p2": 0.00191 + }, + { + "t": 0.4274, + "p0": 0.69621, + "p1": 0.30124, + "p2": 0.00255 + }, + { + "t": 0.46, + "p0": 0.69525, + "p1": 0.3022, + "p2": 0.00255 + }, + { + "t": 0.4603, + "p0": 0.69493, + "p1": 0.3022, + "p2": 0.00287 + }, + { + "t": 0.4928, + "p0": 0.69429, + "p1": 0.30284, + "p2": 0.00287 + }, + { + "t": 0.5257, + "p0": 0.69365, + "p1": 0.30348, + "p2": 0.00287 + }, + { + "…": "1089 more", + "sha256": "a1c4b1d0d6f27f8d" + } + ], + "cumhaz": { + "12": [ + { + "t": 0, + "h": 0, + "lo": 0, + "hi": 0 + }, + { + "t": 0.0329, + "h": 0.00107, + "lo": 0.00015, + "hi": 0.00758 + }, + { + "t": 0.3288, + "h": 0.00213, + "lo": 0.00053, + "hi": 0.00852 + }, + { + "t": 0.4274, + "h": 0.00319, + "lo": 0.00103, + "hi": 0.00988 + }, + { + "t": 0.6247, + "h": 0.00423, + "lo": 0.00159, + "hi": 0.01128 + }, + { + "t": 0.6575, + "h": 0.00528, + "lo": 0.0022, + "hi": 0.01268 + }, + { + "t": 0.7562, + "h": 0.00632, + "lo": 0.00284, + "hi": 0.01406 + }, + { + "t": 0.8877, + "h": 0.00735, + "lo": 0.00351, + "hi": 0.01542 + }, + { + "t": 0.9205, + "h": 0.00943, + "lo": 0.00491, + "hi": 0.01812 + }, + { + "t": 1.0849, + "h": 0.01046, + "lo": 0.00563, + "hi": 0.01944 + }, + { + "t": 1.1836, + "h": 0.0115, + "lo": 0.00637, + "hi": 0.02076 + }, + { + "t": 1.2493, + "h": 0.01253, + "lo": 0.00712, + "hi": 0.02207 + }, + { + "t": 1.2822, + "h": 0.01357, + "lo": 0.00788, + "hi": 0.02337 + }, + { + "t": 1.3479, + "h": 0.01564, + "lo": 0.00943, + "hi": 0.02594 + }, + { + "t": 1.5123, + "h": 0.01667, + "lo": 0.01021, + "hi": 0.02721 + }, + { + "t": 1.5452, + "h": 0.0177, + "lo": 0.011, + "hi": 0.02847 + }, + { + "t": 1.611, + "h": 0.01872, + "lo": 0.01179, + "hi": 0.02971 + }, + { + "t": 1.7425, + "h": 0.02076, + "lo": 0.01339, + "hi": 0.03218 + }, + { + "t": 1.8082, + "h": 0.02178, + "lo": 0.0142, + "hi": 0.03341 + }, + { + "t": 1.9726, + "h": 0.02279, + "lo": 0.01501, + "hi": 0.03462 + }, + { + "…": "566 more", + "sha256": "a86e52bb898bdc72" + } + ], + "01": [ + { + "t": 0, + "h": 0, + "lo": 0, + "hi": 0 + }, + { + "t": 0.0657, + "h": 0.0009, + "lo": 0.00023, + "hi": 0.00362 + }, + { + "t": 0.0986, + "h": 0.00181, + "lo": 0.00068, + "hi": 0.00483 + }, + { + "t": 0.1314, + "h": 0.00226, + "lo": 0.00094, + "hi": 0.00544 + }, + { + "t": 0.1971, + "h": 0.00272, + "lo": 0.00122, + "hi": 0.00605 + }, + { + "t": 0.23, + "h": 0.00454, + "lo": 0.00244, + "hi": 0.00843 + }, + { + "t": 0.2628, + "h": 0.00499, + "lo": 0.00276, + "hi": 0.00901 + }, + { + "t": 0.2957, + "h": 0.00545, + "lo": 0.00309, + "hi": 0.00959 + }, + { + "t": 0.3285, + "h": 0.0059, + "lo": 0.00343, + "hi": 0.01016 + }, + { + "t": 0.3614, + "h": 0.00727, + "lo": 0.00446, + "hi": 0.01187 + }, + { + "t": 0.3943, + "h": 0.00819, + "lo": 0.00516, + "hi": 0.013 + }, + { + "t": 0.4271, + "h": 0.00956, + "lo": 0.00624, + "hi": 0.01467 + }, + { + "t": 0.46, + "h": 0.01094, + "lo": 0.00734, + "hi": 0.01633 + }, + { + "t": 0.4928, + "h": 0.01186, + "lo": 0.00808, + "hi": 0.01743 + }, + { + "t": 0.5257, + "h": 0.01279, + "lo": 0.00883, + "hi": 0.01852 + }, + { + "t": 0.5585, + "h": 0.01371, + "lo": 0.00959, + "hi": 0.01961 + }, + { + "t": 0.5914, + "h": 0.01463, + "lo": 0.01035, + "hi": 0.0207 + }, + { + "t": 0.6242, + "h": 0.0151, + "lo": 0.01073, + "hi": 0.02124 + }, + { + "t": 0.6571, + "h": 0.01602, + "lo": 0.0115, + "hi": 0.02232 + }, + { + "t": 0.6899, + "h": 0.01695, + "lo": 0.01228, + "hi": 0.02339 + }, + { + "…": "462 more", + "sha256": "9dbfd1d24f7b7a45" + } + ], + "02": [ + { + "t": 0, + "h": 0, + "lo": 0, + "hi": 0 + }, + { + "t": 0.0329, + "h": 0.00045, + "lo": 0.00006, + "hi": 0.00318 + }, + { + "t": 0.3288, + "h": 0.00136, + "lo": 0.00044, + "hi": 0.00422 + }, + { + "t": 0.3945, + "h": 0.00182, + "lo": 0.00068, + "hi": 0.00484 + }, + { + "t": 0.4274, + "h": 0.00228, + "lo": 0.00095, + "hi": 0.00547 + }, + { + "t": 0.4603, + "h": 0.00274, + "lo": 0.00123, + "hi": 0.00609 + }, + { + "t": 0.6904, + "h": 0.0032, + "lo": 0.00153, + "hi": 0.00672 + }, + { + "t": 0.9534, + "h": 0.00367, + "lo": 0.00183, + "hi": 0.00734 + }, + { + "t": 1.0849, + "h": 0.00414, + "lo": 0.00215, + "hi": 0.00796 + }, + { + "t": 1.5452, + "h": 0.00461, + "lo": 0.00248, + "hi": 0.00858 + }, + { + "t": 1.7753, + "h": 0.00509, + "lo": 0.00282, + "hi": 0.00919 + }, + { + "t": 1.9068, + "h": 0.00557, + "lo": 0.00316, + "hi": 0.00981 + }, + { + "t": 2.4, + "h": 0.00606, + "lo": 0.00352, + "hi": 0.01043 + }, + { + "t": 2.9918, + "h": 0.00655, + "lo": 0.00388, + "hi": 0.01106 + }, + { + "t": 3.189, + "h": 0.00705, + "lo": 0.00425, + "hi": 0.0117 + }, + { + "t": 3.6822, + "h": 0.00755, + "lo": 0.00463, + "hi": 0.01233 + }, + { + "t": 3.9781, + "h": 0.00806, + "lo": 0.00501, + "hi": 0.01298 + }, + { + "t": 4.1096, + "h": 0.00858, + "lo": 0.0054, + "hi": 0.01362 + }, + { + "t": 4.2082, + "h": 0.00909, + "lo": 0.00579, + "hi": 0.01425 + }, + { + "t": 5.3918, + "h": 0.00961, + "lo": 0.0062, + "hi": 0.01491 + }, + { + "…": "43 more", + "sha256": "d211b66499822325" + } + ] + }, + "drawnTo": 104.1862, + "atHorizon": [ + { + "months": 12, + "p0": 0.54095, + "p1": 0.37766, + "p2": 0.08139, + "atRisk": 2557 + }, + { + "months": 24, + "p0": 0.46517, + "p1": 0.36392, + "p2": 0.17091, + "atRisk": 1770 + }, + { + "months": 60, + "p0": 0.38491, + "p1": 0.26697, + "p2": 0.34811, + "atRisk": 403 + } + ] + }, + { + "cohort": "Colorectal, stage 4", + "cohortPatients": 2372, + "n": 2372, + "prevalent": 1004, + "excluded": { + "noFollowup": 0, + "prevalent": 0 + }, + "notAssessed": 10, + "afterEnd": 0, + "seq0": 2372, + "late": 0, + "censored": { + "start": 209, + "progression": 729 + }, + "rates": [ + { + "key": "01", + "events": 1104, + "personMonths": 16936.2, + "rate": 6.518594740240378, + "lo": 6.1451972268076105, + "hi": 6.914680818074228 + }, + { + "key": "02", + "events": 55, + "personMonths": 16936.2, + "rate": 0.32474883216777245, + "lo": 0.24932831689181667, + "hi": 0.42298365989488396 + }, + { + "key": "12", + "events": 1379, + "personMonths": 50075.2, + "rate": 2.7538574878430513, + "lo": 2.612279124922894, + "hi": 2.9031090096748713 + } + ], + "occupation": [ + { + "t": 0, + "p0": 0.57673, + "p1": 0.42327, + "p2": 0 + }, + { + "t": 0.0329, + "p0": 0.57293, + "p1": 0.4258, + "p2": 0.00126 + }, + { + "t": 0.0657, + "p0": 0.57081, + "p1": 0.42792, + "p2": 0.00126 + }, + { + "t": 0.0986, + "p0": 0.56869, + "p1": 0.43004, + "p2": 0.00126 + }, + { + "t": 0.1314, + "p0": 0.56657, + "p1": 0.43217, + "p2": 0.00126 + }, + { + "t": 0.1643, + "p0": 0.5636, + "p1": 0.43514, + "p2": 0.00126 + }, + { + "t": 0.1971, + "p0": 0.56232, + "p1": 0.43641, + "p2": 0.00126 + }, + { + "t": 0.1973, + "p0": 0.56232, + "p1": 0.43599, + "p2": 0.00169 + }, + { + "t": 0.23, + "p0": 0.5602, + "p1": 0.43811, + "p2": 0.00169 + }, + { + "t": 0.2301, + "p0": 0.5602, + "p1": 0.43641, + "p2": 0.00339 + }, + { + "t": 0.2628, + "p0": 0.55723, + "p1": 0.43939, + "p2": 0.00339 + }, + { + "t": 0.263, + "p0": 0.55723, + "p1": 0.43896, + "p2": 0.00381 + }, + { + "t": 0.2957, + "p0": 0.55638, + "p1": 0.43981, + "p2": 0.00381 + }, + { + "t": 0.3285, + "p0": 0.5551, + "p1": 0.44109, + "p2": 0.00381 + }, + { + "t": 0.3616, + "p0": 0.5551, + "p1": 0.44066, + "p2": 0.00424 + }, + { + "t": 0.3943, + "p0": 0.55383, + "p1": 0.44194, + "p2": 0.00424 + }, + { + "t": 0.4271, + "p0": 0.55043, + "p1": 0.44534, + "p2": 0.00424 + }, + { + "t": 0.46, + "p0": 0.54787, + "p1": 0.44789, + "p2": 0.00424 + }, + { + "t": 0.4603, + "p0": 0.54787, + "p1": 0.44747, + "p2": 0.00466 + }, + { + "t": 0.4928, + "p0": 0.54575, + "p1": 0.44959, + "p2": 0.00466 + }, + { + "…": "1381 more", + "sha256": "3d809bb05163eb70" + } + ], + "cumhaz": { + "12": [ + { + "t": 0, + "h": 0, + "lo": 0, + "hi": 0 + }, + { + "t": 0.0329, + "h": 0.001, + "lo": 0.00014, + "hi": 0.00707 + }, + { + "t": 0.1973, + "h": 0.00197, + "lo": 0.00049, + "hi": 0.00787 + }, + { + "t": 0.2301, + "h": 0.00584, + "lo": 0.00262, + "hi": 0.013 + }, + { + "t": 0.263, + "h": 0.00681, + "lo": 0.00325, + "hi": 0.01428 + }, + { + "t": 0.3616, + "h": 0.00777, + "lo": 0.00389, + "hi": 0.01554 + }, + { + "t": 0.4603, + "h": 0.00872, + "lo": 0.00454, + "hi": 0.01676 + }, + { + "t": 0.526, + "h": 0.00966, + "lo": 0.0052, + "hi": 0.01796 + }, + { + "t": 0.6247, + "h": 0.0106, + "lo": 0.00587, + "hi": 0.01914 + }, + { + "t": 0.6904, + "h": 0.01153, + "lo": 0.00655, + "hi": 0.02031 + }, + { + "t": 0.7233, + "h": 0.01339, + "lo": 0.00793, + "hi": 0.02261 + }, + { + "t": 0.8548, + "h": 0.01431, + "lo": 0.00862, + "hi": 0.02374 + }, + { + "t": 0.8877, + "h": 0.01522, + "lo": 0.00932, + "hi": 0.02485 + }, + { + "t": 0.9205, + "h": 0.01613, + "lo": 0.01003, + "hi": 0.02596 + }, + { + "t": 0.9863, + "h": 0.01704, + "lo": 0.01073, + "hi": 0.02705 + }, + { + "t": 1.0192, + "h": 0.01885, + "lo": 0.01216, + "hi": 0.02922 + }, + { + "t": 1.0849, + "h": 0.01975, + "lo": 0.01288, + "hi": 0.0303 + }, + { + "t": 1.2164, + "h": 0.02153, + "lo": 0.0143, + "hi": 0.0324 + }, + { + "t": 1.2493, + "h": 0.0233, + "lo": 0.01574, + "hi": 0.03449 + }, + { + "t": 1.3151, + "h": 0.02418, + "lo": 0.01646, + "hi": 0.03553 + }, + { + "…": "872 more", + "sha256": "8f985699f88c0e84" + } + ], + "01": [ + { + "t": 0, + "h": 0, + "lo": 0, + "hi": 0 + }, + { + "t": 0.0329, + "h": 0.00512, + "lo": 0.00244, + "hi": 0.01073 + }, + { + "t": 0.0657, + "h": 0.00882, + "lo": 0.00501, + "hi": 0.01553 + }, + { + "t": 0.0986, + "h": 0.01254, + "lo": 0.00779, + "hi": 0.02017 + }, + { + "t": 0.1314, + "h": 0.01627, + "lo": 0.01071, + "hi": 0.02471 + }, + { + "t": 0.1643, + "h": 0.02151, + "lo": 0.01495, + "hi": 0.03096 + }, + { + "t": 0.1971, + "h": 0.02377, + "lo": 0.01681, + "hi": 0.03362 + }, + { + "t": 0.23, + "h": 0.02755, + "lo": 0.01996, + "hi": 0.03802 + }, + { + "t": 0.2628, + "h": 0.03285, + "lo": 0.02445, + "hi": 0.04415 + }, + { + "t": 0.2957, + "h": 0.03438, + "lo": 0.02575, + "hi": 0.0459 + }, + { + "t": 0.3285, + "h": 0.03667, + "lo": 0.02771, + "hi": 0.04852 + }, + { + "t": 0.3943, + "h": 0.03897, + "lo": 0.02969, + "hi": 0.05114 + }, + { + "t": 0.4271, + "h": 0.04511, + "lo": 0.03503, + "hi": 0.0581 + }, + { + "t": 0.46, + "h": 0.04975, + "lo": 0.03908, + "hi": 0.06333 + }, + { + "t": 0.4928, + "h": 0.05363, + "lo": 0.0425, + "hi": 0.06768 + }, + { + "t": 0.5257, + "h": 0.05519, + "lo": 0.04388, + "hi": 0.06942 + }, + { + "t": 0.5585, + "h": 0.05753, + "lo": 0.04595, + "hi": 0.07204 + }, + { + "t": 0.5914, + "h": 0.0591, + "lo": 0.04733, + "hi": 0.07379 + }, + { + "t": 0.6242, + "h": 0.06145, + "lo": 0.04942, + "hi": 0.0764 + }, + { + "t": 0.6571, + "h": 0.06616, + "lo": 0.05362, + "hi": 0.08164 + }, + { + "…": "473 more", + "sha256": "4805c30529494cd7" + } + ], + "02": [ + { + "t": 0, + "h": 0, + "lo": 0, + "hi": 0 + }, + { + "t": 0.0329, + "h": 0.00146, + "lo": 0.00037, + "hi": 0.00585 + }, + { + "t": 0.6575, + "h": 0.00225, + "lo": 0.00073, + "hi": 0.00699 + }, + { + "t": 0.6904, + "h": 0.00384, + "lo": 0.0016, + "hi": 0.00922 + }, + { + "t": 0.789, + "h": 0.00464, + "lo": 0.00208, + "hi": 0.01034 + }, + { + "t": 0.8219, + "h": 0.00545, + "lo": 0.0026, + "hi": 0.01143 + }, + { + "t": 0.8877, + "h": 0.00626, + "lo": 0.00313, + "hi": 0.01252 + }, + { + "t": 1.0521, + "h": 0.00708, + "lo": 0.00368, + "hi": 0.01362 + }, + { + "t": 1.2164, + "h": 0.00877, + "lo": 0.00485, + "hi": 0.01584 + }, + { + "t": 1.2493, + "h": 0.00961, + "lo": 0.00546, + "hi": 0.01694 + }, + { + "t": 1.4466, + "h": 0.01134, + "lo": 0.00671, + "hi": 0.01917 + }, + { + "t": 1.5452, + "h": 0.01222, + "lo": 0.00736, + "hi": 0.02028 + }, + { + "t": 1.6767, + "h": 0.0131, + "lo": 0.00802, + "hi": 0.0214 + }, + { + "t": 1.7096, + "h": 0.01488, + "lo": 0.00937, + "hi": 0.02364 + }, + { + "t": 2.1041, + "h": 0.01583, + "lo": 0.01008, + "hi": 0.02483 + }, + { + "t": 2.7616, + "h": 0.01683, + "lo": 0.01084, + "hi": 0.02611 + }, + { + "t": 2.9918, + "h": 0.01885, + "lo": 0.01239, + "hi": 0.02868 + }, + { + "t": 3.5507, + "h": 0.01992, + "lo": 0.01321, + "hi": 0.03003 + }, + { + "t": 3.6822, + "h": 0.021, + "lo": 0.01404, + "hi": 0.0314 + }, + { + "t": 3.7479, + "h": 0.02209, + "lo": 0.01488, + "hi": 0.03277 + }, + { + "…": "30 more", + "sha256": "a3a4d49c38ace49f" + } + ] + }, + "drawnTo": 100.4054, + "atHorizon": [ + { + "months": 12, + "p0": 0.17179, + "p1": 0.60131, + "p2": 0.2269, + "atRisk": 1684 + }, + { + "months": 24, + "p0": 0.08777, + "p1": 0.48593, + "p2": 0.4263, + "atRisk": 1076 + }, + { + "months": 60, + "p0": 0.0564, + "p1": 0.23144, + "p2": 0.71217, + "atRisk": 271 + } + ] + } + ], + "reference": "Colorectal, stage 1-3", + "cox": [ + { + "key": "01", + "n": 3602, + "events": 1833, + "rows": [ + { + "cohort": "Colorectal, stage 4", + "hr": 4.413598026076264, + "lo": 4.011952275372462, + "hi": 4.855453454758713, + "p": 2.720547643517393e-204 + } + ] + }, + { + "key": "02", + "n": 3602, + "events": 118, + "rows": [ + { + "cohort": "Colorectal, stage 4", + "hr": 2.633056072843851, + "lo": 1.8168793519637583, + "hi": 3.815874881976301, + "p": 3.1491733022396024e-7 + } + ] + }, + { + "key": "12", + "n": 3774, + "events": 2102, + "rows": [ + { + "cohort": "Colorectal, stage 4", + "hr": 1.6965216834924886, + "lo": 1.5503249954367886, + "hi": 1.8565048173975207, + "p": 1.3763786830963345e-30 + } + ] + } + ] + }, + "participation": { + "studies": [ + { + "study": "msk_chord_2024", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "An illness–death model (Rueda et al., Nature 2019) on one clock: months from the timeline's day 0, which is the day of a Sequencing event for 5,543 patients of 5,543 (day 0 = first sequencing), so the start state is “sequenced, not yet progressed”. Progression is the first Diagnosis event with PROGRESSION in Y / Yes after day 0; death and the end of follow-up are OS_STATUS / OS_MONTHS (OS_MONTHS × 30.4375 days on the timeline's clock; checked: 0 patients of 5,543 with a timeline event more than 31 days past it, a study above 5% is refused). 1,941 patients had a progression on or before day 0 and start in the progression state. Markov, clock-forward: every transition's hazard is a function of time since day 0, progression → death included (a patient enters that risk set at their progression), so time already spent progressed does not enter the model. Rates are events per 100 person-months in the state left (95% interval, Poisson); cumulative hazards Nelson–Aalen (95% interval, log scale); state occupation Aalen–Johansen from the transitions, drawn without intervals while at least 10 of a cohort's patients are followed. Hazard ratios: one Cox model per transition (Efron ties), each cohort against Colorectal, stage 1-3; a cohort with fewer than 5 events in a transition gets none. Progression here is a reading of the record: in MSK-CHORD an NLP model's call on each radiology report, so it is probabilistic and only as dense as the imaging — 41 patients in the model have no PROGRESSION record at all and could never be seen to progress, and their deaths count as death without progression. Left out: 0 without follow-up. The cohorts are not randomised, so differences are associations. Each site sends only (prevalent, progression time, end time, dead) counts." + } + ] +} diff --git a/test/snapshots/multistate.sql b/test/snapshots/multistate.sql new file mode 100644 index 0000000..a7e8b7a --- /dev/null +++ b/test/snapshots/multistate.sql @@ -0,0 +1,179 @@ +-- cohort Colorectal, stage 1-3 +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' + AND attribute_name = 'STAGE_HIGHEST_RECORDED' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Stage 1-3')))) + +-- cohort Colorectal, stage 4 +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' + AND attribute_name = 'STAGE_HIGHEST_RECORDED' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Stage 4')))) + +-- view 0 multistate: multistate:Colorectal, stage 1-3 · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' + AND attribute_name = 'STAGE_HIGHEST_RECORDED' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Stage 1-3'))))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +lastday AS ( + SELECT p.stable_id AS pid, max(greatest(ce.start_date, coalesce(ce.stop_date, ce.start_date))) AS d + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +), +prog AS ( + SELECT p.stable_id AS pid, ce.start_date AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('PROGRESSION')) AND lowerUTF8(trimBoth(value)) IN (lowerUTF8(trimBoth('Y')), lowerUTF8(trimBoth('Yes')))) +), +pp AS (SELECT pid, countIf(day <= 0) AS n_pre, countIf(day > 0) AS n_after, minIf(day, day > 0) AS first_after FROM prog GROUP BY pid), +assessed AS ( + SELECT DISTINCT p.stable_id AS pid + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('PROGRESSION')) AND 1) +), +seq0 AS ( + SELECT DISTINCT p.stable_id AS pid + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Sequencing')) AND ce.start_date = 0 +), +per AS ( + SELECT os.months AS months, os.months IS NOT NULL AS has_os, os.dead AS dead, + pp.pid != '' AND pp.n_pre > 0 AS prevalent, pp.pid != '' AND pp.n_after > 0 AS has_after, toFloat64(pp.first_after) AS fa, + a.pid != '' AS assessed, s.pid != '' AS seq0, lastday.d AS ld + FROM (SELECT DISTINCT pid FROM cohort) c + LEFT JOIN os ON os.pid = c.pid + LEFT JOIN pp ON pp.pid = c.pid + LEFT JOIN assessed a ON a.pid = c.pid + LEFT JOIN seq0 s ON s.pid = c.pid + LEFT JOIN lastday ON lastday.pid = c.pid +), +cls AS ( + SELECT multiIf(NOT has_os OR months <= 0, 'nofollowup', 'record') AS kind, + NOT prevalent AND has_after AND fa < months * 30.4375 AS hit, + NOT prevalent AND has_after AND fa >= months * 30.4375 AS afterend, + if(hit, fa / 30.4375, 0) AS tp, months AS te, + prevalent, dead, assessed, seq0, ld > months * 30.4375 + 31 AS late + FROM per +) +SELECT kind, if(kind = 'record', toUInt8(prevalent), 0) AS prevalent, if(kind = 'record', round(tp, 4), 0) AS tp, + if(kind = 'record', round(te, 4), 0) AS te, if(kind = 'record', toUInt8(dead), 0) AS dead, + toUInt8(afterend) AS afterend, toUInt8(assessed) AS assessed, toUInt8(seq0) AS seq0, toUInt8(late) AS late, count() AS n +FROM cls GROUP BY kind, prevalent, tp, te, dead, afterend, assessed, seq0, late ORDER BY te + +-- view 0 multistate: multistate:Colorectal, stage 4 · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Colorectal Cancer')))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' + AND attribute_name = 'STAGE_HIGHEST_RECORDED' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Stage 4'))))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +lastday AS ( + SELECT p.stable_id AS pid, max(greatest(ce.start_date, coalesce(ce.stop_date, ce.start_date))) AS d + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +), +prog AS ( + SELECT p.stable_id AS pid, ce.start_date AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('PROGRESSION')) AND lowerUTF8(trimBoth(value)) IN (lowerUTF8(trimBoth('Y')), lowerUTF8(trimBoth('Yes')))) +), +pp AS (SELECT pid, countIf(day <= 0) AS n_pre, countIf(day > 0) AS n_after, minIf(day, day > 0) AS first_after FROM prog GROUP BY pid), +assessed AS ( + SELECT DISTINCT p.stable_id AS pid + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('PROGRESSION')) AND 1) +), +seq0 AS ( + SELECT DISTINCT p.stable_id AS pid + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Sequencing')) AND ce.start_date = 0 +), +per AS ( + SELECT os.months AS months, os.months IS NOT NULL AS has_os, os.dead AS dead, + pp.pid != '' AND pp.n_pre > 0 AS prevalent, pp.pid != '' AND pp.n_after > 0 AS has_after, toFloat64(pp.first_after) AS fa, + a.pid != '' AS assessed, s.pid != '' AS seq0, lastday.d AS ld + FROM (SELECT DISTINCT pid FROM cohort) c + LEFT JOIN os ON os.pid = c.pid + LEFT JOIN pp ON pp.pid = c.pid + LEFT JOIN assessed a ON a.pid = c.pid + LEFT JOIN seq0 s ON s.pid = c.pid + LEFT JOIN lastday ON lastday.pid = c.pid +), +cls AS ( + SELECT multiIf(NOT has_os OR months <= 0, 'nofollowup', 'record') AS kind, + NOT prevalent AND has_after AND fa < months * 30.4375 AS hit, + NOT prevalent AND has_after AND fa >= months * 30.4375 AS afterend, + if(hit, fa / 30.4375, 0) AS tp, months AS te, + prevalent, dead, assessed, seq0, ld > months * 30.4375 + 31 AS late + FROM per +) +SELECT kind, if(kind = 'record', toUInt8(prevalent), 0) AS prevalent, if(kind = 'record', round(tp, 4), 0) AS tp, + if(kind = 'record', round(te, 4), 0) AS te, if(kind = 'record', toUInt8(dead), 0) AS dead, + toUInt8(afterend) AS afterend, toUInt8(assessed) AS assessed, toUInt8(seq0) AS seq0, toUInt8(late) AS late, count() AS n +FROM cls GROUP BY kind, prevalent, tp, te, dead, afterend, assessed, seq0, late ORDER BY te diff --git a/test/snapshots/multistate.svg b/test/snapshots/multistate.svg new file mode 100644 index 0000000..a7198b9 --- /dev/null +++ b/test/snapshots/multistate.svg @@ -0,0 +1,2 @@ + +Multistate model — Colorectal, stage 1-3, Colorectal, stage 4Share of patients in each state (Aalen–Johansen)alive, not progressedalive, progresseddeadColorectal, stage 1-3 · n = 3,1710%25%50%75%100%024487296Months from first sequencingalive, not progressedalive, progresseddeadColorectal, stage 4 · n = 2,3720%25%50%75%100%024487296Months from first sequencingalive, not progressedalive, progresseddeadColorectal, stage 1-3: 12 mo 54% / 38% / 8% · 24 mo 47% / 36% / 17% · 60 mo 38% / 27% / 35%Colorectal, stage 4: 12 mo 17% / 60% / 23% · 24 mo 9% / 49% / 43% · 60 mo 6% / 23% / 71%(not progressed / progressed / dead)Cumulative hazard of each transition (Nelson–Aalen)start → progression0.00.51.01.52.02.5024487296Monthsstart → death (no progression)0.000.050.100.150.20024487296Monthsprogression → death0.00.51.01.52.02.5024487296Monthstransition · cohorteventsperson-monthsper 100 person-months (95% CI)HR vs Colorectal, stage 1-3start → progressionColorectal, stage 1-372958,6331.2 (1.2–1.3)referenceColorectal, stage 41,10416,9366.5 (6.1–6.9)4.41 (4.01–4.86) p = 2.7e-204start → death (no progression)Colorectal, stage 1-36358,6330.11 (0.084–0.14)referenceColorectal, stage 45516,9360.32 (0.25–0.42)2.63 (1.82–3.82) p = 3.1e-7progression → deathColorectal, stage 1-372345,3751.6 (1.5–1.7)referenceColorectal, stage 41,37950,0752.8 (2.6–2.9)1.70 (1.55–1.86) p = 1.4e-30An illness–death model (Rueda et al., Nature 2019) on one clock: months from the timeline's day 0, which is the day of a Sequencing event for 5,543 patients of 5,543(day 0 = first sequencing), so the start state is “sequenced, not yet progressed”. Progression is the first Diagnosis event with PROGRESSION in Y / Yes after day 0;death and the end of follow-up are OS_STATUS / OS_MONTHS (OS_MONTHS × 30.4375 days on the timeline's clock; checked: 0 patients of 5,543 with a timelineevent more than 31 days past it, a study above 5% is refused). 1,941 patients had a progression on or before day 0 and start in the progression state. Markov,clock-forward: every transition's hazard is a function of time since day 0, progression → death included (a patient enters that risk set at their progression), so timealready spent progressed does not enter the model. Rates are events per 100 person-months in the state left (95% interval, Poisson); cumulative hazardsNelson–Aalen (95% interval, log scale); state occupation Aalen–Johansen from the transitions, drawn without intervals while at least 10 of a cohort's patients arefollowed. Hazard ratios: one Cox model per transition (Efron ties), each cohort against Colorectal, stage 1-3; a cohort with fewer than 5 events in a transition getsnone. Progression here is a reading of the record: in MSK-CHORD an NLP model's call on each radiology report, so it is probabilistic and only as dense as the imaging —41 patients in the model have no PROGRESSION record at all and could never be seen to progress, and their deaths count as death without progression. Left out: 0without follow-up. The cohorts are not randomised, so differences are associations. Each site sends only (prevalent, progression time, end time, dead) counts. diff --git a/test/snapshots/multistate.txt b/test/snapshots/multistate.txt new file mode 100644 index 0000000..aad738b --- /dev/null +++ b/test/snapshots/multistate.txt @@ -0,0 +1,15 @@ +Analysis over msk_chord_2024 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- Colorectal, stage 1-3: 3,171 patients — CANCER_TYPE in [Colorectal Cancer] (any sample) AND STAGE_HIGHEST_RECORDED in [Stage 1-3] +- Colorectal, stage 4: 2,372 patients — CANCER_TYPE in [Colorectal Cancer] (any sample) AND STAGE_HIGHEST_RECORDED in [Stage 4] + +Multistate (illness-death) model, months from first sequencing: + Colorectal, stage 1-3: n=3,171 (937 progressed by day 0); start → progression 729 events, 1.2 (1.2–1.3) per 100 person-months; start → death (no progression) 63 events, 0.11 (0.084–0.14) per 100 person-months; progression → death 723 events, 1.6 (1.5–1.7) per 100 person-months + in each state (not progressed / progressed / dead): 12 mo 54% / 38% / 8%, 24 mo 47% / 36% / 17%, 60 mo 38% / 27% / 35% + Colorectal, stage 4: n=2,372 (1,004 progressed by day 0); start → progression 1104 events, 6.5 (6.1–6.9) per 100 person-months; start → death (no progression) 55 events, 0.32 (0.25–0.42) per 100 person-months; progression → death 1379 events, 2.8 (2.6–2.9) per 100 person-months + in each state (not progressed / progressed / dead): 12 mo 17% / 60% / 23%, 24 mo 9% / 49% / 43%, 60 mo 6% / 23% / 71% + start → progression: Colorectal, stage 4 vs Colorectal, stage 1-3 HR 4.41 (95% CI 4.01-4.86, 2.7e-204) + start → death (no progression): Colorectal, stage 4 vs Colorectal, stage 1-3 HR 2.63 (95% CI 1.82-3.82, 3.1e-7) + progression → death: Colorectal, stage 4 vs Colorectal, stage 1-3 HR 1.70 (95% CI 1.55-1.86, 1.4e-30) + CAVEAT (tell the user): An illness–death model (Rueda et al., Nature 2019) on one clock: months from the timeline's day 0, which is the day of a Sequencing event for 5,543 patients of 5,543 (day 0 = first sequencing), so the start state is “sequenced, not yet progressed”. Progression is the first Diagnosis event with PROGRESSION in Y / Yes after day 0; death and the end of follow-up are OS_STATUS / OS_MONTHS (OS_MONTHS × 30.4375 days on the timeline's clock; checked: 0 patients of 5,543 with a timeline event more than 31 days past it, a study above 5% is refused). 1,941 patients had a progression on or before day 0 and start in the progression state. Markov, clock-forward: every transition's hazard is a function of time since day 0, progression → death included (a patient enters that risk set at their progression), so time already spent progressed does not enter the model. Rates are events per 100 person-months in the state left (95% interval, Poisson); cumulative hazards Nelson–Aalen (95% interval, log scale); state occupation Aalen–Johansen from the transitions, drawn without intervals while at least 10 of a cohort's patients are followed. Hazard ratios: one Cox model per transition (Efron ties), each cohort against Colorectal, stage 1-3; a cohort with fewer than 5 events in a transition gets none. Progression here is a reading of the record: in MSK-CHORD an NLP model's call on each radiology report, so it is probabilistic and only as dense as the imaging — 41 patients in the model have no PROGRESSION record at all and could never be seen to progress, and their deaths count as death without progression. Left out: 0 without follow-up. The cohorts are not randomised, so differences are associations. Each site sends only (prevalent, progression time, end time, dead) counts. diff --git a/test/snapshots/organotropism.json b/test/snapshots/organotropism.json new file mode 100644 index 0000000..d80af83 --- /dev/null +++ b/test/snapshots/organotropism.json @@ -0,0 +1,785 @@ +{ + "study": "msk_chord_2024", + "cohorts": { + "NSCLC": { + "count": 7809, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample)", + "studies": [ + "msk_chord_2024" + ] + } + }, + "views": [ + { + "kind": "organotropism", + "cohorts": [ + "NSCLC" + ], + "organotropism": { + "source": "timeline", + "sourceLabel": "timeline Diagnosis events' TUMOR_SITE", + "alteration": "mutation", + "measure": "driver", + "stratifyBy": "CANCER_TYPE", + "minPatients": 5, + "patients": 7809, + "sites": [ + { + "site": "lung", + "label": "Lung", + "evaluable": 7425, + "withSite": 7181 + }, + { + "site": "lymph nodes", + "label": "Lymph Nodes", + "evaluable": 7425, + "withSite": 5387 + }, + { + "site": "other", + "label": "Other", + "evaluable": 7425, + "withSite": 4653 + }, + { + "site": "bone", + "label": "Bone", + "evaluable": 7425, + "withSite": 3756 + }, + { + "site": "cns/brain", + "label": "CNS/Brain", + "evaluable": 7425, + "withSite": 2758 + }, + { + "site": "pleura", + "label": "Pleura", + "evaluable": 7425, + "withSite": 2372 + }, + { + "site": "liver", + "label": "Liver", + "evaluable": 7425, + "withSite": 2286 + }, + { + "site": "adrenal glands", + "label": "Adrenal Glands", + "evaluable": 7425, + "withSite": 1334 + }, + { + "site": "intra-abdominal", + "label": "Intra-Abdominal", + "evaluable": 7425, + "withSite": 777 + }, + { + "site": "reproductive organs", + "label": "Reproductive Organs", + "evaluable": 7425, + "withSite": 455 + } + ], + "genes": [ + { + "gene": "EGFR" + }, + { + "gene": "KRAS" + }, + { + "gene": "TP53" + }, + { + "gene": "STK11" + }, + { + "gene": "KEAP1" + }, + { + "gene": "SMARCA4" + } + ], + "cells": [ + { + "site": "lung", + "gene": "EGFR", + "withSite": 7181, + "withAltered": 1607, + "without": 244, + "withoutAltered": 47, + "or": 1.2085, + "log2Or": 0.273, + "p": 0.28348701990423947, + "q": 0.36976567813596456, + "strata": 2 + }, + { + "site": "lung", + "gene": "KRAS", + "withSite": 7181, + "withAltered": 2029, + "without": 244, + "withoutAltered": 65, + "or": 1.0841, + "log2Or": 0.116, + "p": 0.6353075092608361, + "q": 0.7474205991303955, + "strata": 2 + }, + { + "site": "lung", + "gene": "TP53", + "withSite": 7181, + "withAltered": 3723, + "without": 244, + "withoutAltered": 116, + "or": 1.1879, + "log2Or": 0.248, + "p": 0.20985666056959834, + "q": 0.2919959095993514, + "strata": 2 + }, + { + "site": "lung", + "gene": "STK11", + "withSite": 7181, + "withAltered": 849, + "without": 244, + "withoutAltered": 31, + "or": 0.9212, + "log2Or": -0.118, + "p": 0.7496243561344957, + "q": 0.8329159512605508, + "strata": 2 + }, + { + "site": "lung", + "gene": "KEAP1", + "withSite": 7181, + "withAltered": 560, + "without": 244, + "withoutAltered": 18, + "or": 1.062, + "log2Or": 0.087, + "p": 0.9043108042040912, + "q": 0.9499923445063648, + "strata": 2 + }, + { + "site": "lung", + "gene": "SMARCA4", + "withSite": 7181, + "withAltered": 270, + "without": 244, + "withoutAltered": 10, + "or": 0.914, + "log2Or": -0.13, + "p": 0.9183259330228193, + "q": 0.9499923445063648, + "strata": 2 + }, + { + "site": "lymph nodes", + "gene": "EGFR", + "withSite": 5387, + "withAltered": 1138, + "without": 2038, + "withoutAltered": 516, + "or": 0.7901, + "log2Or": -0.34, + "p": 0.00012202051223180176, + "q": 0.00034863003494800505, + "strata": 2 + }, + { + "site": "lymph nodes", + "gene": "KRAS", + "withSite": 5387, + "withAltered": 1496, + "without": 2038, + "withoutAltered": 598, + "or": 0.9244, + "log2Or": -0.113, + "p": 0.18007816415437294, + "q": 0.2572545202205328, + "strata": 2 + }, + { + "site": "lymph nodes", + "gene": "TP53", + "withSite": 5387, + "withAltered": 3038, + "without": 2038, + "withoutAltered": 801, + "or": 1.9967, + "log2Or": 0.998, + "p": 2.8932432875514818e-39, + "q": 1.7359459725308891e-37, + "strata": 2 + }, + { + "site": "lymph nodes", + "gene": "STK11", + "withSite": 5387, + "withAltered": 666, + "without": 2038, + "withoutAltered": 214, + "or": 1.2021, + "log2Or": 0.266, + "p": 0.02981934109578526, + "q": 0.054216983810518656, + "strata": 2 + }, + { + "site": "lymph nodes", + "gene": "KEAP1", + "withSite": 5387, + "withAltered": 471, + "without": 2038, + "withoutAltered": 107, + "or": 1.7294, + "log2Or": 0.79, + "p": 6.87645088328899e-7, + "q": 0.000002750580353315596, + "strata": 2 + }, + { + "site": "lymph nodes", + "gene": "SMARCA4", + "withSite": 5387, + "withAltered": 229, + "without": 2038, + "withoutAltered": 51, + "or": 1.7295, + "log2Or": 0.79, + "p": 0.000542574925431756, + "q": 0.0014154128489524069, + "strata": 2 + }, + { + "site": "other", + "gene": "EGFR", + "withSite": 4653, + "withAltered": 1053, + "without": 2772, + "withoutAltered": 601, + "or": 1.0578, + "log2Or": 0.081, + "p": 0.3468758361659519, + "q": 0.441143274707972, + "strata": 2 + }, + { + "site": "other", + "gene": "KRAS", + "withSite": 4653, + "withAltered": 1287, + "without": 2772, + "withoutAltered": 807, + "or": 0.9205, + "log2Or": -0.12, + "p": 0.1263823436056453, + "q": 0.1995510688510189, + "strata": 2 + }, + { + "site": "other", + "gene": "TP53", + "withSite": 4653, + "withAltered": 2547, + "without": 2772, + "withoutAltered": 1292, + "or": 1.3757, + "log2Or": 0.46, + "p": 4.205014759682705e-11, + "q": 4.2050147596827054e-10, + "strata": 2 + }, + { + "site": "other", + "gene": "STK11", + "withSite": 4653, + "withAltered": 569, + "without": 2772, + "withoutAltered": 311, + "or": 1.101, + "log2Or": 0.139, + "p": 0.21413033370619103, + "q": 0.2919959095993514, + "strata": 2 + }, + { + "site": "other", + "gene": "KEAP1", + "withSite": 4653, + "withAltered": 390, + "without": 2772, + "withoutAltered": 188, + "or": 1.2593, + "log2Or": 0.333, + "p": 0.01413479458134059, + "q": 0.03028884553144412, + "strata": 2 + }, + { + "site": "other", + "gene": "SMARCA4", + "withSite": 4653, + "withAltered": 194, + "without": 2772, + "withoutAltered": 86, + "or": 1.3575, + "log2Or": 0.441, + "p": 0.02394685725191403, + "q": 0.044900357347338805, + "strata": 2 + }, + { + "site": "bone", + "gene": "EGFR", + "withSite": 3756, + "withAltered": 971, + "without": 3669, + "withoutAltered": 683, + "or": 1.5246, + "log2Or": 0.608, + "p": 8.18752393132579e-14, + "q": 1.6375047862651579e-12, + "strata": 2 + }, + { + "site": "bone", + "gene": "KRAS", + "withSite": 3756, + "withAltered": 1015, + "without": 3669, + "withoutAltered": 1079, + "or": 0.887, + "log2Or": -0.173, + "p": 0.02156775008051049, + "q": 0.04313550016102098, + "strata": 2 + }, + { + "…": "40 more", + "sha256": "73d6aa301b8de9e4" + } + ], + "tested": 60, + "significant": 32, + "incidence": { + "site": "CNS/Brain", + "gene": "EGFR", + "curves": [ + { + "cohort": "EGFR altered", + "n": 1350, + "events": 364, + "competing": 255, + "censored": 731, + "excluded": { + "prior": 411, + "unobserved": 0, + "noFollowup": 0, + "noAnchor": 0 + }, + "late": 0, + "points": [ + { + "t": 0.0329, + "f1": 0.00444, + "f2": 0, + "se1": 0.00181, + "atRisk": 1350 + }, + { + "t": 0.0657, + "f1": 0.00891, + "f2": 0, + "se1": 0.00256, + "atRisk": 1339 + }, + { + "t": 0.0658, + "f1": 0.00891, + "f2": 0.00074, + "se1": 0.00256, + "atRisk": 1333 + }, + { + "t": 0.0986, + "f1": 0.01262, + "f2": 0.00074, + "se1": 0.00304, + "atRisk": 1332 + }, + { + "t": 0.1314, + "f1": 0.0156, + "f2": 0.00074, + "se1": 0.00338, + "atRisk": 1327 + }, + { + "t": 0.1643, + "f1": 0.01708, + "f2": 0.00074, + "se1": 0.00353, + "atRisk": 1323 + }, + { + "t": 0.1971, + "f1": 0.02006, + "f2": 0.00074, + "se1": 0.00382, + "atRisk": 1321 + }, + { + "t": 0.23, + "f1": 0.02452, + "f2": 0.00074, + "se1": 0.00422, + "atRisk": 1316 + }, + { + "t": 0.2628, + "f1": 0.02601, + "f2": 0.00074, + "se1": 0.00434, + "atRisk": 1309 + }, + { + "t": 0.2957, + "f1": 0.02676, + "f2": 0.00074, + "se1": 0.0044, + "atRisk": 1306 + }, + { + "t": 0.3285, + "f1": 0.0275, + "f2": 0.00074, + "se1": 0.00446, + "atRisk": 1305 + }, + { + "t": 0.4271, + "f1": 0.02974, + "f2": 0.00074, + "se1": 0.00463, + "atRisk": 1303 + }, + { + "t": 0.4274, + "f1": 0.02974, + "f2": 0.00149, + "se1": 0.00463, + "atRisk": 1300 + }, + { + "t": 0.46, + "f1": 0.03347, + "f2": 0.00149, + "se1": 0.00491, + "atRisk": 1298 + }, + { + "t": 0.4928, + "f1": 0.03571, + "f2": 0.00149, + "se1": 0.00506, + "atRisk": 1293 + }, + { + "t": 0.5257, + "f1": 0.03646, + "f2": 0.00149, + "se1": 0.00511, + "atRisk": 1290 + }, + { + "t": 0.526, + "f1": 0.03646, + "f2": 0.00224, + "se1": 0.00511, + "atRisk": 1289 + }, + { + "t": 0.5914, + "f1": 0.0372, + "f2": 0.00224, + "se1": 0.00516, + "atRisk": 1288 + }, + { + "t": 0.5918, + "f1": 0.0372, + "f2": 0.00298, + "se1": 0.00516, + "atRisk": 1287 + }, + { + "t": 0.6242, + "f1": 0.03795, + "f2": 0.00298, + "se1": 0.00521, + "atRisk": 1286 + }, + { + "…": "512 more", + "sha256": "b567644e812901f3" + } + ] + }, + { + "cohort": "EGFR not altered", + "n": 5107, + "events": 1042, + "competing": 1712, + "censored": 2353, + "excluded": { + "prior": 941, + "unobserved": 0, + "noFollowup": 0, + "noAnchor": 0 + }, + "late": 0, + "points": [ + { + "t": 0.0329, + "f1": 0.00313, + "f2": 0.00196, + "se1": 0.00078, + "atRisk": 5107 + }, + { + "t": 0.0657, + "f1": 0.00549, + "f2": 0.00196, + "se1": 0.00104, + "atRisk": 5060 + }, + { + "t": 0.0658, + "f1": 0.00549, + "f2": 0.00235, + "se1": 0.00104, + "atRisk": 5048 + }, + { + "t": 0.0986, + "f1": 0.00726, + "f2": 0.00255, + "se1": 0.00119, + "atRisk": 5046 + }, + { + "t": 0.1314, + "f1": 0.00805, + "f2": 0.00255, + "se1": 0.00125, + "atRisk": 5035 + }, + { + "t": 0.1315, + "f1": 0.00805, + "f2": 0.00274, + "se1": 0.00125, + "atRisk": 5031 + }, + { + "t": 0.1643, + "f1": 0.00962, + "f2": 0.00274, + "se1": 0.00137, + "atRisk": 5029 + }, + { + "t": 0.1644, + "f1": 0.00962, + "f2": 0.00314, + "se1": 0.00137, + "atRisk": 5021 + }, + { + "t": 0.1971, + "f1": 0.01198, + "f2": 0.00314, + "se1": 0.00153, + "atRisk": 5018 + }, + { + "t": 0.1973, + "f1": 0.01198, + "f2": 0.00353, + "se1": 0.00153, + "atRisk": 5006 + }, + { + "t": 0.23, + "f1": 0.01356, + "f2": 0.00353, + "se1": 0.00162, + "atRisk": 5004 + }, + { + "t": 0.2301, + "f1": 0.01356, + "f2": 0.00412, + "se1": 0.00162, + "atRisk": 4996 + }, + { + "t": 0.2628, + "f1": 0.01474, + "f2": 0.00412, + "se1": 0.00169, + "atRisk": 4990 + }, + { + "t": 0.263, + "f1": 0.01474, + "f2": 0.00432, + "se1": 0.00169, + "atRisk": 4984 + }, + { + "t": 0.2957, + "f1": 0.01612, + "f2": 0.00432, + "se1": 0.00177, + "atRisk": 4983 + }, + { + "t": 0.2959, + "f1": 0.01612, + "f2": 0.00491, + "se1": 0.00177, + "atRisk": 4976 + }, + { + "t": 0.3285, + "f1": 0.0169, + "f2": 0.00491, + "se1": 0.00181, + "atRisk": 4972 + }, + { + "t": 0.3288, + "f1": 0.0169, + "f2": 0.00589, + "se1": 0.00181, + "atRisk": 4968 + }, + { + "t": 0.3614, + "f1": 0.01809, + "f2": 0.00589, + "se1": 0.00187, + "atRisk": 4963 + }, + { + "t": 0.3943, + "f1": 0.01966, + "f2": 0.00589, + "se1": 0.00195, + "atRisk": 4954 + }, + { + "…": "1504 more", + "sha256": "2d11dbdb085560b6" + } + ] + } + ], + "late": 0, + "records": 6457, + "gray": { + "statistic": 26.729288267822987, + "p": 0.0004997501249375312, + "permutations": 2000, + "groups": 2 + }, + "horizons": [ + { + "months": 12, + "cells": [ + { + "cohort": "EGFR altered", + "f1": 0.14237870451829465, + "lo": 0.12351062192615343, + "hi": 0.16124678711043589, + "atRisk": 986 + }, + { + "cohort": "EGFR not altered", + "f1": 0.12810517649158298, + "lo": 0.11882169113810584, + "hi": 0.13738866184506013, + "atRisk": 3237 + } + ] + }, + { + "months": 24, + "cells": [ + { + "cohort": "EGFR altered", + "f1": 0.20741393253219217, + "lo": 0.18488062291229543, + "hi": 0.22994724215208892, + "atRisk": 716 + }, + { + "cohort": "EGFR not altered", + "f1": 0.17726684351024025, + "lo": 0.16643308037263344, + "hi": 0.18810060664784706, + "atRisk": 2195 + } + ] + }, + { + "months": 60, + "cells": [ + { + "cohort": "EGFR altered", + "f1": 0.3200818660934484, + "lo": 0.29100349600102005, + "hi": 0.34916023618587677, + "atRisk": 230 + }, + { + "cohort": "EGFR not altered", + "f1": 0.22726854798461762, + "lo": 0.21450841724773226, + "hi": 0.240028678721503, + "atRisk": 752 + } + ] + } + ] + } + }, + "participation": { + "studies": [ + { + "study": "msk_chord_2024", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Sites are the values of TUMOR_SITE on Diagnosis timeline events, at any date: a patient with any site recorded is evaluable for every site, and has a site when an event names it. The timeline's tumour sites include the primary tumour's own organ, so the column for the organ a cancer type starts in mostly records primaries. Per site and gene, the patients evaluable for the site and profiled for the gene (a mutation panel listing it, or whole-exome; a patient not profiled is in neither group, never wild-type) are split by metastasis there and by a mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic in the gene called on a covering panel, in any of their samples. The 2×2 tables are stratified by CANCER_TYPE per patient (“Multiple” when samples disagree, a stratum of its own; 2 strata), and tested by the Cochran–Mantel–Haenszel test (continuity-corrected) with the Mantel–Haenszel common odds ratio — so a gene is compared with metastasis WITHIN cancer types, and a cancer type that both carries the gene and spreads to the site does not make the association by itself. A cell is tested with at least 5 altered patients, 5 with the site and 5 without; Benjamini–Hochberg q over the 60 cells tested. The colour is log2 of the odds ratio. A site is only recorded where imaging, biopsy or the source's own abstraction found it, so detection follows practice (who was scanned, how often, which sites are biopsied); patients sequenced from a metastasis are more likely to have that site recorded. Associations are not adjusted for stage, time followed, treatment or tumour mutation burden. The lower panel: cumulative incidence of the first CNS/Brain event after the timeline's day 0 for patients profiled for EGFR, altered against not, death (OS_STATUS) competing, Aalen–Johansen with delta-method 95% intervals; patients with the site at or before day 0 are excluded rather than counted as free of it (EGFR altered 411, EGFR not altered 941), as are those without follow-up. Censoring at OS_MONTHS on the timeline's clock; 0 patients of 6,457 have the event after the last follow-up (refused above 5%). Gray's test by permutation (2000 shuffles, seeded). " + } + ] +} diff --git a/test/snapshots/organotropism.sql b/test/snapshots/organotropism.sql new file mode 100644 index 0000000..903a0e3 --- /dev/null +++ b/test/snapshots/organotropism.sql @@ -0,0 +1,333 @@ +-- cohort NSCLC +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) + +-- view 0 organotropism: organotropism:counts · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +evr AS ( + SELECT DISTINCT p.stable_id AS pid, lowerUTF8(trimBoth(d.value)) AS site, trimBoth(d.value) AS raw + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.clinical_event_data d ON d.clinical_event_id = ce.clinical_event_id + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND lowerUTF8(trimBoth(d.key)) = lowerUTF8(trimBoth('TUMOR_SITE')) AND trimBoth(d.value) != '' + AND p.stable_id IN (SELECT pid FROM cohort) +), +ev AS (SELECT DISTINCT pid, site FROM evr), +sl AS (SELECT site, min(raw) AS label FROM evr GROUP BY site), +ps AS ( + SELECT e.pid AS pid, s.site AS site, toUInt8((e.pid, s.site) IN (SELECT pid, site FROM ev)) AS has + FROM (SELECT DISTINCT pid FROM ev) e CROSS JOIN (SELECT site FROM sl) s +), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +prof AS ( + SELECT DISTINCT pp.pid AS pid, gp.gene AS gene FROM pp JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel WHERE gp.gene IN ('EGFR', 'KRAS', 'TP53', 'STK11', 'KEAP1', 'SMARCA4') + UNION DISTINCT + SELECT pid, arrayJoin(['EGFR', 'KRAS', 'TP53', 'STK11', 'KEAP1', 'SMARCA4']) AS gene FROM pp WHERE panel = 'WES' +), +alt AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT g.off_panel + AND g.hugo_gene_symbol IN ('EGFR', 'KRAS', 'TP53', 'STK11', 'KEAP1', 'SMARCA4') + AND ann.is_driver + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +st AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, if(uniqExact(lowerUTF8(trimBoth(attribute_value))) > 1, 'Multiple', any(trimBoth(attribute_value))) AS s + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND attribute_name = 'CANCER_TYPE' AND trimBoth(attribute_value) != '' + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT ps.site AS site, prof.gene AS gene, st.s AS stratum, count() AS n, sum(ps.has) AS with_site, + countIf(alt.pid != '') AS altered, countIf(ps.has = 1 AND alt.pid != '') AS both +FROM ps INNER JOIN prof ON prof.pid = ps.pid + LEFT JOIN alt ON alt.pid = ps.pid AND alt.gene = prof.gene + LEFT JOIN st ON st.pid = ps.pid +GROUP BY site, gene, stratum + +-- view 0 organotropism: organotropism:incidence:altered · msk_chord_2024 +WITH cohort AS (SELECT pid FROM ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))) WHERE pid IN ( + WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +prof AS ( + SELECT DISTINCT pp.pid AS pid, gp.gene AS gene FROM pp JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel WHERE gp.gene IN ('EGFR') + UNION DISTINCT + SELECT pid, arrayJoin(['EGFR']) AS gene FROM pp WHERE panel = 'WES' +), +alt AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT g.off_panel + AND g.hugo_gene_symbol IN ('EGFR') + AND ann.is_driver + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +) + SELECT pid FROM prof) + AND pid IN ( + WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +prof AS ( + SELECT DISTINCT pp.pid AS pid, gp.gene AS gene FROM pp JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel WHERE gp.gene IN ('EGFR') + UNION DISTINCT + SELECT pid, arrayJoin(['EGFR']) AS gene FROM pp WHERE panel = 'WES' +), +alt AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT g.off_panel + AND g.hugo_gene_symbol IN ('EGFR') + AND ann.is_driver + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +) + SELECT pid FROM alt)), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +anch AS (SELECT pid, toInt64(0) AS day FROM cohort), +ev AS (SELECT p.stable_id AS pid, ce.start_date AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('TUMOR_SITE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('CNS/Brain'))) + AND p.stable_id IN (SELECT pid FROM cohort)), +evs AS ( + SELECT ev.pid AS pid, countIf(ev.day <= anch.day) AS n_prior, countIf(ev.day > anch.day) AS n_after, + minIf(ev.day, ev.day > anch.day) AS first_after + FROM ev INNER JOIN anch ON anch.pid = ev.pid GROUP BY ev.pid +), +per AS ( + SELECT anch.pid AS pid, anch.day AS a, os.months AS months, os.status LIKE '1%' AS dead, + evs.pid != '' AS has_ev, evs.n_prior AS n_prior, evs.n_after AS n_after, evs.first_after AS first_after + FROM anch + LEFT JOIN os ON os.pid = anch.pid + LEFT JOIN evs ON evs.pid = anch.pid +), +cls AS ( + SELECT greatest(0, -a) / 30.4375 AS entry, months - a / 30.4375 AS tend, + (first_after - a) / 30.4375 AS tev, + multiIf(months IS NULL OR months - a / 30.4375 <= 0, 'nofollowup', + has_ev AND n_prior > 0, 'prior', + has_ev AND n_after > 0 AND first_after < 0, 'unobserved', + 'record') AS kind, + has_ev AND n_after > 0 AND (first_after - a) / 30.4375 <= months - a / 30.4375 AS hit, + has_ev AND n_after > 0 AND (first_after - a) / 30.4375 > months - a / 30.4375 AS late, + dead + FROM per +) +SELECT kind, if(kind = 'record', round(entry, 4), 0) AS entry, if(kind = 'record', round(if(hit, tev, tend), 4), 0) AS t, + if(kind != 'record', -1, if(hit, 1, if(dead, 2, 0))) AS cause, late, count() AS n +FROM cls GROUP BY kind, entry, t, cause, late ORDER BY t + +-- view 0 organotropism: organotropism:incidence:wildtype · msk_chord_2024 +WITH cohort AS (SELECT pid FROM ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))) WHERE pid IN ( + WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +prof AS ( + SELECT DISTINCT pp.pid AS pid, gp.gene AS gene FROM pp JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel WHERE gp.gene IN ('EGFR') + UNION DISTINCT + SELECT pid, arrayJoin(['EGFR']) AS gene FROM pp WHERE panel = 'WES' +), +alt AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT g.off_panel + AND g.hugo_gene_symbol IN ('EGFR') + AND ann.is_driver + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +) + SELECT pid FROM prof) + AND pid NOT IN ( + WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +pp AS ( + SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, toString(sp.gene_panel_id) AS panel + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +), +prof AS ( + SELECT DISTINCT pp.pid AS pid, gp.gene AS gene FROM pp JOIN snapshot_db.gene_panel_to_gene_derived gp ON gp.gene_panel_id = pp.panel WHERE gp.gene IN ('EGFR') + UNION DISTINCT + SELECT pid, arrayJoin(['EGFR']) AS gene FROM pp WHERE panel = 'WES' +), +alt AS ( + SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, g.hugo_gene_symbol AS gene + FROM snapshot_db.genomic_event_derived g + LEFT JOIN oncokb_annotation.variant_annotation ann + ON ann.oncokb_version = 'v7.5' AND ann.is_driver + AND ann.hugo_gene_symbol = g.hugo_gene_symbol + AND ((g.variant_type = 'mutation' AND ann.alteration_type = 'MUTATION' AND ann.alteration = g.mutation_variant) + OR (g.variant_type = 'cna' AND ann.alteration_type = 'CNA' AND ann.alteration = if(g.cna_alteration >= 2, 'AMPLIFICATION', 'DELETION')) + OR (g.variant_type = 'structural_variant' AND ann.alteration_type = 'SV' AND ann.alteration = 'Fusion')) + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND g.mutation_status != 'UNCALLED' AND NOT g.off_panel + AND g.hugo_gene_symbol IN ('EGFR') + AND ann.is_driver + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) +) + SELECT pid FROM alt)), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +anch AS (SELECT pid, toInt64(0) AS day FROM cohort), +ev AS (SELECT p.stable_id AS pid, ce.start_date AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('TUMOR_SITE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('CNS/Brain'))) + AND p.stable_id IN (SELECT pid FROM cohort)), +evs AS ( + SELECT ev.pid AS pid, countIf(ev.day <= anch.day) AS n_prior, countIf(ev.day > anch.day) AS n_after, + minIf(ev.day, ev.day > anch.day) AS first_after + FROM ev INNER JOIN anch ON anch.pid = ev.pid GROUP BY ev.pid +), +per AS ( + SELECT anch.pid AS pid, anch.day AS a, os.months AS months, os.status LIKE '1%' AS dead, + evs.pid != '' AS has_ev, evs.n_prior AS n_prior, evs.n_after AS n_after, evs.first_after AS first_after + FROM anch + LEFT JOIN os ON os.pid = anch.pid + LEFT JOIN evs ON evs.pid = anch.pid +), +cls AS ( + SELECT greatest(0, -a) / 30.4375 AS entry, months - a / 30.4375 AS tend, + (first_after - a) / 30.4375 AS tev, + multiIf(months IS NULL OR months - a / 30.4375 <= 0, 'nofollowup', + has_ev AND n_prior > 0, 'prior', + has_ev AND n_after > 0 AND first_after < 0, 'unobserved', + 'record') AS kind, + has_ev AND n_after > 0 AND (first_after - a) / 30.4375 <= months - a / 30.4375 AS hit, + has_ev AND n_after > 0 AND (first_after - a) / 30.4375 > months - a / 30.4375 AS late, + dead + FROM per +) +SELECT kind, if(kind = 'record', round(entry, 4), 0) AS entry, if(kind = 'record', round(if(hit, tev, tend), 4), 0) AS t, + if(kind != 'record', -1, if(hit, 1, if(dead, 2, 0))) AS cause, late, count() AS n +FROM cls GROUP BY kind, entry, t, cause, late ORDER BY t + +-- view 0 organotropism: organotropism:sites · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +evr AS ( + SELECT DISTINCT p.stable_id AS pid, lowerUTF8(trimBoth(d.value)) AS site, trimBoth(d.value) AS raw + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.clinical_event_data d ON d.clinical_event_id = ce.clinical_event_id + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND lowerUTF8(trimBoth(d.key)) = lowerUTF8(trimBoth('TUMOR_SITE')) AND trimBoth(d.value) != '' + AND p.stable_id IN (SELECT pid FROM cohort) +), +ev AS (SELECT DISTINCT pid, site FROM evr), +sl AS (SELECT site, min(raw) AS label FROM evr GROUP BY site), +ps AS ( + SELECT e.pid AS pid, s.site AS site, toUInt8((e.pid, s.site) IN (SELECT pid, site FROM ev)) AS has + FROM (SELECT DISTINCT pid FROM ev) e CROSS JOIN (SELECT site FROM sl) s +) +SELECT ps.site AS site, any(sl.label) AS label, count() AS evaluable, sum(ps.has) AS with_site +FROM ps INNER JOIN sl ON sl.site = ps.site GROUP BY site + +-- view 0 organotropism: organotropism:sources · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))) +SELECT + (SELECT uniqExact(replaceOne(patient_unique_id, 'msk_chord_2024_', '')) FROM snapshot_db.clinical_data_derived WHERE cancer_study_identifier = 'msk_chord_2024' AND startsWith(attribute_name, 'DMETS_DX_') AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort)) AS flag_patients, + (SELECT uniqExact(attribute_name) FROM snapshot_db.clinical_data_derived WHERE cancer_study_identifier = 'msk_chord_2024' AND startsWith(attribute_name, 'DMETS_DX_') AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort)) AS flags, + (SELECT uniqExact(p.stable_id) FROM snapshot_db.clinical_event ce + JOIN snapshot_db.clinical_event_data d ON d.clinical_event_id = ce.clinical_event_id + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) AND lowerUTF8(trimBoth(d.key)) = lowerUTF8(trimBoth('TUMOR_SITE')) AND trimBoth(d.value) != '' AND p.stable_id IN (SELECT pid FROM cohort)) AS timeline_patients, + (SELECT uniqExact(replaceOne(patient_unique_id, 'msk_chord_2024_', '')) FROM snapshot_db.clinical_data_derived WHERE cancer_study_identifier = 'msk_chord_2024' AND attribute_name = 'METASTATIC_SITE' AND trimBoth(attribute_value) != '' AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort)) AS sample_patients +FROM system.one diff --git a/test/snapshots/organotropism.svg b/test/snapshots/organotropism.svg new file mode 100644 index 0000000..20e7d3b --- /dev/null +++ b/test/snapshots/organotropism.svg @@ -0,0 +1,2 @@ + +Organotropism — 6 genes × 10 sites7,809 patients · sites from timeline Diagnosis events' TUMOR_SITE · OncoKB-driver mutations · stratified by CANCER_TYPELungLymph NodesOtherBoneCNS/BrainPleuraLiverAdrenal GlandsIntra-AbdominalReproductive OrgansEGFREGFR · Lung: altered in 1,607/7,181 (22.4%) with the site, 47/244 (19.3%) without · MH OR 1.2, p = 0.28, q = 0.37 over 2 strataEGFR · Lymph Nodes: altered in 1,138/5,387 (21.1%) with the site, 516/2,038 (25.3%) without · MH OR 0.79, p = 1.2e-4, q = 3.5e-4 over 2 strataEGFR · Other: altered in 1,053/4,653 (22.6%) with the site, 601/2,772 (21.7%) without · MH OR 1.1, p = 0.35, q = 0.44 over 2 strataEGFR · Bone: altered in 971/3,756 (25.9%) with the site, 683/3,669 (18.6%) without · MH OR 1.5, p = 8.2e-14, q = 1.6e-12 over 2 strataEGFR · CNS/Brain: altered in 775/2,758 (28.1%) with the site, 879/4,667 (18.8%) without · MH OR 1.7, p = 2.4e-20, q = 7.2e-19 over 2 strataEGFR · Pleura: altered in 615/2,372 (25.9%) with the site, 1,039/5,053 (20.6%) without · MH OR 1.4, p = 2.6e-7, q = 1.3e-6 over 2 strataEGFR · Liver: altered in 617/2,286 (27.0%) with the site, 1,037/5,139 (20.2%) without · MH OR 1.5, p = 7.7e-11, q = 6.6e-10 over 2 strataEGFR · Adrenal Glands: altered in 290/1,334 (21.7%) with the site, 1,364/6,091 (22.4%) without · MH OR 0.96, p = 0.63, q = 0.75 over 2 strataEGFR · Intra-Abdominal: altered in 189/777 (24.3%) with the site, 1,465/6,648 (22.0%) without · MH OR 1.1, p = 0.16, q = 0.23 over 2 strataEGFR · Reproductive Organs: altered in 104/455 (22.9%) with the site, 1,550/6,970 (22.2%) without · MH OR 1, p = 0.79, q = 0.86 over 2 strataKRASKRAS · Lung: altered in 2,029/7,181 (28.3%) with the site, 65/244 (26.6%) without · MH OR 1.1, p = 0.64, q = 0.75 over 2 strataKRAS · Lymph Nodes: altered in 1,496/5,387 (27.8%) with the site, 598/2,038 (29.3%) without · MH OR 0.92, p = 0.18, q = 0.26 over 2 strataKRAS · Other: altered in 1,287/4,653 (27.7%) with the site, 807/2,772 (29.1%) without · MH OR 0.92, p = 0.13, q = 0.20 over 2 strataKRAS · Bone: altered in 1,015/3,756 (27.0%) with the site, 1,079/3,669 (29.4%) without · MH OR 0.89, p = 0.022, q = 0.043 over 2 strataKRAS · CNS/Brain: altered in 744/2,758 (27.0%) with the site, 1,350/4,667 (28.9%) without · MH OR 0.91, p = 0.073, q = 0.12 over 2 strataKRAS · Pleura: altered in 567/2,372 (23.9%) with the site, 1,527/5,053 (30.2%) without · MH OR 0.73, p = 2e-8, q = 1.1e-7 over 2 strataKRAS · Liver: altered in 557/2,286 (24.4%) with the site, 1,537/5,139 (29.9%) without · MH OR 0.75, p = 4e-7, q = 1.7e-6 over 2 strataKRAS · Adrenal Glands: altered in 394/1,334 (29.5%) with the site, 1,700/6,091 (27.9%) without · MH OR 1.1, p = 0.25, q = 0.33 over 2 strataKRAS · Intra-Abdominal: altered in 218/777 (28.1%) with the site, 1,876/6,648 (28.2%) without · MH OR 0.98, p = 0.85, q = 0.91 over 2 strataKRAS · Reproductive Organs: altered in 123/455 (27.0%) with the site, 1,971/6,970 (28.3%) without · MH OR 0.91, p = 0.43, q = 0.52 over 2 strataTP53TP53 · Lung: altered in 3,723/7,181 (51.8%) with the site, 116/244 (47.5%) without · MH OR 1.2, p = 0.21, q = 0.29 over 2 strataTP53 · Lymph Nodes: altered in 3,038/5,387 (56.4%) with the site, 801/2,038 (39.3%) without · MH OR 2, p = 2.9e-39, q = 1.7e-37 over 2 strataTP53 · Other: altered in 2,547/4,653 (54.7%) with the site, 1,292/2,772 (46.6%) without · MH OR 1.4, p = 4.2e-11, q = 4.2e-10 over 2 strataTP53 · Bone: altered in 2,043/3,756 (54.4%) with the site, 1,796/3,669 (49.0%) without · MH OR 1.2, p = 3.5e-6, q = 1.3e-5 over 2 strataTP53 · CNS/Brain: altered in 1,567/2,758 (56.8%) with the site, 2,272/4,667 (48.7%) without · MH OR 1.4, p = 1.5e-11, q = 1.8e-10 over 2 strataTP53 · Pleura: altered in 1,319/2,372 (55.6%) with the site, 2,520/5,053 (49.9%) without · MH OR 1.3, p = 4.4e-6, q = 1.5e-5 over 2 strataTP53 · Liver: altered in 1,304/2,286 (57.0%) with the site, 2,535/5,139 (49.3%) without · MH OR 1.4, p = 2.4e-9, q = 1.6e-8 over 2 strataTP53 · Adrenal Glands: altered in 788/1,334 (59.1%) with the site, 3,051/6,091 (50.1%) without · MH OR 1.4, p = 3.5e-9, q = 2.1e-8 over 2 strataTP53 · Intra-Abdominal: altered in 439/777 (56.5%) with the site, 3,400/6,648 (51.1%) without · MH OR 1.2, p = 0.0074, q = 0.016 over 2 strataTP53 · Reproductive Organs: altered in 216/455 (47.5%) with the site, 3,623/6,970 (52.0%) without · MH OR 0.82, p = 0.04, q = 0.068 over 2 strataSTK11STK11 · Lung: altered in 849/7,181 (11.8%) with the site, 31/244 (12.7%) without · MH OR 0.92, p = 0.75, q = 0.83 over 2 strataSTK11 · Lymph Nodes: altered in 666/5,387 (12.4%) with the site, 214/2,038 (10.5%) without · MH OR 1.2, p = 0.03, q = 0.054 over 2 strataSTK11 · Other: altered in 569/4,653 (12.2%) with the site, 311/2,772 (11.2%) without · MH OR 1.1, p = 0.21, q = 0.29 over 2 strataSTK11 · Bone: altered in 492/3,756 (13.1%) with the site, 388/3,669 (10.6%) without · MH OR 1.3, p = 8.9e-4, q = 0.0021 over 2 strataSTK11 · CNS/Brain: altered in 373/2,758 (13.5%) with the site, 507/4,667 (10.9%) without · MH OR 1.3, p = 7e-4, q = 0.0018 over 2 strataSTK11 · Pleura: altered in 233/2,372 (9.8%) with the site, 647/5,053 (12.8%) without · MH OR 0.74, p = 2.5e-4, q = 6.7e-4 over 2 strataSTK11 · Liver: altered in 247/2,286 (10.8%) with the site, 633/5,139 (12.3%) without · MH OR 0.86, p = 0.065, q = 0.11 over 2 strataSTK11 · Adrenal Glands: altered in 223/1,334 (16.7%) with the site, 657/6,091 (10.8%) without · MH OR 1.7, p = 1.7e-9, q = 1.3e-8 over 2 strataSTK11 · Intra-Abdominal: altered in 105/777 (13.5%) with the site, 775/6,648 (11.7%) without · MH OR 1.2, p = 0.15, q = 0.23 over 2 strataSTK11 · Reproductive Organs: altered in 55/455 (12.1%) with the site, 825/6,970 (11.8%) without · MH OR 1, p = 0.96, q = 0.96 over 2 strataKEAP1KEAP1 · Lung: altered in 560/7,181 (7.8%) with the site, 18/244 (7.4%) without · MH OR 1.1, p = 0.90, q = 0.95 over 2 strataKEAP1 · Lymph Nodes: altered in 471/5,387 (8.7%) with the site, 107/2,038 (5.3%) without · MH OR 1.7, p = 6.9e-7, q = 2.8e-6 over 2 strataKEAP1 · Other: altered in 390/4,653 (8.4%) with the site, 188/2,772 (6.8%) without · MH OR 1.3, p = 0.014, q = 0.03 over 2 strataKEAP1 · Bone: altered in 330/3,756 (8.8%) with the site, 248/3,669 (6.8%) without · MH OR 1.3, p = 0.0013, q = 0.003 over 2 strataKEAP1 · CNS/Brain: altered in 261/2,758 (9.5%) with the site, 317/4,667 (6.8%) without · MH OR 1.4, p = 4e-5, q = 1.3e-4 over 2 strataKEAP1 · Pleura: altered in 180/2,372 (7.6%) with the site, 398/5,053 (7.9%) without · MH OR 0.96, p = 0.70, q = 0.79 over 2 strataKEAP1 · Liver: altered in 193/2,286 (8.4%) with the site, 385/5,139 (7.5%) without · MH OR 1.1, p = 0.17, q = 0.25 over 2 strataKEAP1 · Adrenal Glands: altered in 143/1,334 (10.7%) with the site, 435/6,091 (7.1%) without · MH OR 1.6, p = 1.3e-5, q = 4.3e-5 over 2 strataKEAP1 · Intra-Abdominal: altered in 77/777 (9.9%) with the site, 501/6,648 (7.5%) without · MH OR 1.4, p = 0.023, q = 0.045 over 2 strataKEAP1 · Reproductive Organs: altered in 41/455 (9.0%) with the site, 537/6,970 (7.7%) without · MH OR 1.2, p = 0.35, q = 0.44 over 2 strataSMARCA4SMARCA4 · Lung: altered in 270/7,181 (3.8%) with the site, 10/244 (4.1%) without · MH OR 0.91, p = 0.92, q = 0.95 over 2 strataSMARCA4 · Lymph Nodes: altered in 229/5,387 (4.3%) with the site, 51/2,038 (2.5%) without · MH OR 1.7, p = 5.4e-4, q = 0.0014 over 2 strataSMARCA4 · Other: altered in 194/4,653 (4.2%) with the site, 86/2,772 (3.1%) without · MH OR 1.4, p = 0.024, q = 0.045 over 2 strataSMARCA4 · Bone: altered in 184/3,756 (4.9%) with the site, 96/3,669 (2.6%) without · MH OR 1.9, p = 3.5e-7, q = 1.6e-6 over 2 strataSMARCA4 · CNS/Brain: altered in 137/2,758 (5.0%) with the site, 143/4,667 (3.1%) without · MH OR 1.7, p = 4.2e-5, q = 1.3e-4 over 2 strataSMARCA4 · Pleura: altered in 93/2,372 (3.9%) with the site, 187/5,053 (3.7%) without · MH OR 1.1, p = 0.69, q = 0.79 over 2 strataSMARCA4 · Liver: altered in 103/2,286 (4.5%) with the site, 177/5,139 (3.4%) without · MH OR 1.3, p = 0.033, q = 0.058 over 2 strataSMARCA4 · Adrenal Glands: altered in 94/1,334 (7.0%) with the site, 186/6,091 (3.1%) without · MH OR 2.4, p = 7.3e-12, q = 1.1e-10 over 2 strataSMARCA4 · Intra-Abdominal: altered in 42/777 (5.4%) with the site, 238/6,648 (3.6%) without · MH OR 1.5, p = 0.016, q = 0.033 over 2 strataSMARCA4 · Reproductive Organs: altered in 18/455 (4.0%) with the site, 262/6,970 (3.8%) without · MH OR 1, p = 0.95, q = 0.96 over 2 strataOdds ratio1/21/1.411.42outlined: q < 0.05 · empty: not tested60 site × gene cells tested, 32 at q < 0.05; the strongest:SiteGeneWith the siteWithoutMH ORqLymph NodesTP533,038/5,387 (56.4%)801/2,038 (39.3%)21.7e-37CNS/BrainEGFR775/2,758 (28.1%)879/4,667 (18.8%)1.77.2e-19BoneEGFR971/3,756 (25.9%)683/3,669 (18.6%)1.51.6e-12Adrenal GlandsSMARCA494/1,334 (7.0%)186/6,091 (3.1%)2.41.1e-10CNS/BrainTP531,567/2,758 (56.8%)2,272/4,667 (48.7%)1.41.8e-10OtherTP532,547/4,653 (54.7%)1,292/2,772 (46.6%)1.44.2e-10LiverEGFR617/2,286 (27.0%)1,037/5,139 (20.2%)1.56.6e-10Adrenal GlandsSTK11223/1,334 (16.7%)657/6,091 (10.8%)1.71.3e-8LiverTP531,304/2,286 (57.0%)2,535/5,139 (49.3%)1.41.6e-8Adrenal GlandsTP53788/1,334 (59.1%)3,051/6,091 (50.1%)1.42.1e-8Cumulative incidence of CNS/Brain by EGFR, death competing0%25%50%75%100%Cumulative incidence024487296Months from the timeline's day 0EGFR alteredn = 1,350 · 364 with CNS/Brain, 255 died first · 12 mo 14.2%, 24 mo 20.7%, 60 mo 32.0%EGFR not alteredn = 5,107 · 1,042 with CNS/Brain, 1,712 died first · 12 mo 12.8%, 24 mo 17.7%, 60 mo 22.7%Gray's test (permutation, 2000 shuffles) p = 5e-4Sites are the values of TUMOR_SITE on Diagnosis timeline events, at any date: a patient with any site recorded is evaluable for every site, and has a site when an eventnames it. The timeline's tumour sites include the primary tumour's own organ, so the column for the organ a cancer type starts in mostly records primaries. Per site andgene, the patients evaluable for the site and profiled for the gene (a mutation panel listing it, or whole-exome; a patient not profiled is in neither group, never wild-type)are split by metastasis there and by a mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic in the gene called on a covering panel, in any of their samples.The 2×2 tables are stratified by CANCER_TYPE per patient (“Multiple” when samples disagree, a stratum of its own; 2 strata), and tested by theCochran–Mantel–Haenszel test (continuity-corrected) with the Mantel–Haenszel common odds ratio — so a gene is compared with metastasis WITHIN cancer types, anda cancer type that both carries the gene and spreads to the site does not make the association by itself. A cell is tested with at least 5 altered patients, 5 with the site and5 without; Benjamini–Hochberg q over the 60 cells tested. The colour is log2 of the odds ratio. A site is only recorded where imaging, biopsy or the source's ownabstraction found it, so detection follows practice (who was scanned, how often, which sites are biopsied); patients sequenced from a metastasis are more likely to havethat site recorded. Associations are not adjusted for stage, time followed, treatment or tumour mutation burden. The lower panel: cumulative incidence of the firstCNS/Brain event after the timeline's day 0 for patients profiled for EGFR, altered against not, death (OS_STATUS) competing, Aalen–Johansen with delta-method 95%intervals; patients with the site at or before day 0 are excluded rather than counted as free of it (EGFR altered 411, EGFR not altered 941), as are those without follow-up.Censoring at OS_MONTHS on the timeline's clock; 0 patients of 6,457 have the event after the last follow-up (refused above 5%). Gray's test by permutation (2000shuffles, seeded). diff --git a/test/snapshots/organotropism.txt b/test/snapshots/organotropism.txt new file mode 100644 index 0000000..7a735b7 --- /dev/null +++ b/test/snapshots/organotropism.txt @@ -0,0 +1,39 @@ +Analysis over msk_chord_2024 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- NSCLC: 7,809 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) + +Metastatic organotropism (7,809 patients; sites from timeline Diagnosis events' TUMOR_SITE; OncoKB-driver mutations; CMH stratified by CANCER_TYPE): 6 genes × 10 sites, 60 cells tested, 32 at q<0.05. + Sites (patients with a metastasis there / evaluable): Lung 7181/7425, Lymph Nodes 5387/7425, Other 4653/7425, Bone 3756/7425, CNS/Brain 2758/7425, Pleura 2372/7425, Liver 2286/7425, Adrenal Glands 1334/7425, Intra-Abdominal 777/7425, Reproductive Organs 455/7425 + Lymph Nodes × TP53: altered 3038/5387 (56.4%) with the site vs 801/2038 (39.3%) without, MH OR 2.00, p=2.9e-39 q=1.7e-37 + CNS/Brain × EGFR: altered 775/2758 (28.1%) with the site vs 879/4667 (18.8%) without, MH OR 1.68, p=2.4e-20 q=7.2e-19 + Bone × EGFR: altered 971/3756 (25.9%) with the site vs 683/3669 (18.6%) without, MH OR 1.52, p=8.2e-14 q=1.6e-12 + Adrenal Glands × SMARCA4: altered 94/1334 (7.0%) with the site vs 186/6091 (3.1%) without, MH OR 2.40, p=7.3e-12 q=1.1e-10 + CNS/Brain × TP53: altered 1567/2758 (56.8%) with the site vs 2272/4667 (48.7%) without, MH OR 1.39, p=1.5e-11 q=1.8e-10 + Other × TP53: altered 2547/4653 (54.7%) with the site vs 1292/2772 (46.6%) without, MH OR 1.38, p=4.2e-11 q=4.2e-10 + Liver × EGFR: altered 617/2286 (27.0%) with the site vs 1037/5139 (20.2%) without, MH OR 1.47, p=7.7e-11 q=6.6e-10 + Adrenal Glands × STK11: altered 223/1334 (16.7%) with the site vs 657/6091 (10.8%) without, MH OR 1.66, p=1.7e-9 q=1.3e-8 + Liver × TP53: altered 1304/2286 (57.0%) with the site vs 2535/5139 (49.3%) without, MH OR 1.35, p=2.4e-9 q=1.6e-8 + Adrenal Glands × TP53: altered 788/1334 (59.1%) with the site vs 3051/6091 (50.1%) without, MH OR 1.44, p=3.5e-9 q=2.1e-8 + Pleura × KRAS: altered 567/2372 (23.9%) with the site vs 1527/5053 (30.2%) without, MH OR 0.73, p=2.0e-8 q=1.1e-7 + Pleura × EGFR: altered 615/2372 (25.9%) with the site vs 1039/5053 (20.6%) without, MH OR 1.35, p=2.6e-7 q=1.3e-6 + Bone × SMARCA4: altered 184/3756 (4.9%) with the site vs 96/3669 (2.6%) without, MH OR 1.92, p=3.5e-7 q=1.6e-6 + Liver × KRAS: altered 557/2286 (24.4%) with the site vs 1537/5139 (29.9%) without, MH OR 0.75, p=4.0e-7 q=1.7e-6 + Lymph Nodes × KEAP1: altered 471/5387 (8.7%) with the site vs 107/2038 (5.3%) without, MH OR 1.73, p=6.9e-7 q=2.8e-6 + Bone × TP53: altered 2043/3756 (54.4%) with the site vs 1796/3669 (49.0%) without, MH OR 1.24, p=3.5e-6 q=1.3e-5 + Pleura × TP53: altered 1319/2372 (55.6%) with the site vs 2520/5053 (49.9%) without, MH OR 1.26, p=4.4e-6 q=1.5e-5 + Adrenal Glands × KEAP1: altered 143/1334 (10.7%) with the site vs 435/6091 (7.1%) without, MH OR 1.56, p=1.3e-5 q=4.3e-5 + CNS/Brain × KEAP1: altered 261/2758 (9.5%) with the site vs 317/4667 (6.8%) without, MH OR 1.43, p=4.0e-5 q=0.0001 + CNS/Brain × SMARCA4: altered 137/2758 (5.0%) with the site vs 143/4667 (3.1%) without, MH OR 1.65, p=4.2e-5 q=0.0001 + Lymph Nodes × EGFR: altered 1138/5387 (21.1%) with the site vs 516/2038 (25.3%) without, MH OR 0.79, p=0.0001 q=0.0003 + Pleura × STK11: altered 233/2372 (9.8%) with the site vs 647/5053 (12.8%) without, MH OR 0.74, p=0.0002 q=0.0007 + Lymph Nodes × SMARCA4: altered 229/5387 (4.3%) with the site vs 51/2038 (2.5%) without, MH OR 1.73, p=0.0005 q=0.0014 + CNS/Brain × STK11: altered 373/2758 (13.5%) with the site vs 507/4667 (10.9%) without, MH OR 1.28, p=0.0007 q=0.0018 + Bone × STK11: altered 492/3756 (13.1%) with the site vs 388/3669 (10.6%) without, MH OR 1.27, p=0.0009 q=0.0021 + Bone × KEAP1: altered 330/3756 (8.8%) with the site vs 248/3669 (6.8%) without, MH OR 1.33, p=0.0013 q=0.0030 + Intra-Abdominal × TP53: altered 439/777 (56.5%) with the site vs 3400/6648 (51.1%) without, MH OR 1.23, p=0.0074 q=0.0165 + Other × KEAP1: altered 390/4653 (8.4%) with the site vs 188/2772 (6.8%) without, MH OR 1.26, p=0.0141 q=0.0303 + Intra-Abdominal × SMARCA4: altered 42/777 (5.4%) with the site vs 238/6648 (3.6%) without, MH OR 1.54, p=0.0157 q=0.0326 + Bone × KRAS: altered 1015/3756 (27.0%) with the site vs 1079/3669 (29.4%) without, MH OR 0.89, p=0.0216 q=0.0431 + Cumulative incidence of CNS/Brain by EGFR (death competing): 12 mo EGFR altered 14.2% [12.4–16.1] vs EGFR not altered 12.8% [11.9–13.7]; 24 mo EGFR altered 20.7% [18.5–23.0] vs EGFR not altered 17.7% [16.6–18.8]; 60 mo EGFR altered 32.0% [29.1–34.9] vs EGFR not altered 22.7% [21.5–24.0]; Gray's test p=0.0005 + CAVEAT (tell the user): Sites are the values of TUMOR_SITE on Diagnosis timeline events, at any date: a patient with any site recorded is evaluable for every site, and has a site when an event names it. The timeline's tumour sites include the primary tumour's own organ, so the column for the organ a cancer type starts in mostly records primaries. Per site and gene, the patients evaluable for the site and profiled for the gene (a mutation panel listing it, or whole-exome; a patient not profiled is in neither group, never wild-type) are split by metastasis there and by a mutation the OncoKB store (v7.5) calls oncogenic or likely oncogenic in the gene called on a covering panel, in any of their samples. The 2×2 tables are stratified by CANCER_TYPE per patient (“Multiple” when samples disagree, a stratum of its own; 2 strata), and tested by the Cochran–Mantel–Haenszel test (continuity-corrected) with the Mantel–Haenszel common odds ratio — so a gene is compared with metastasis WITHIN cancer types, and a cancer type that both carries the gene and spreads to the site does not make the association by itself. A cell is tested with at least 5 altered patients, 5 with the site and 5 without; Benjamini–Hochberg q over the 60 cells tested. The colour is log2 of the odds ratio. A site is only recorded where imaging, biopsy or the source's own abstraction found it, so detection follows practice (who was scanned, how often, which sites are biopsied); patients sequenced from a metastasis are more likely to have that site recorded. Associations are not adjusted for stage, time followed, treatment or tumour mutation burden. The lower panel: cumulative incidence of the first CNS/Brain event after the timeline's day 0 for patients profiled for EGFR, altered against not, death (OS_STATUS) competing, Aalen–Johansen with delta-method 95% intervals; patients with the site at or before day 0 are excluded rather than counted as free of it (EGFR altered 411, EGFR not altered 941), as are those without follow-up. Censoring at OS_MONTHS on the timeline's clock; 0 patients of 6,457 have the event after the last follow-up (refused above 5%). Gray's test by permutation (2000 shuffles, seeded). diff --git a/test/snapshots/predictive.json b/test/snapshots/predictive.json new file mode 100644 index 0000000..6b2f921 --- /dev/null +++ b/test/snapshots/predictive.json @@ -0,0 +1,210 @@ +{ + "study": "msk_chord_2024", + "cohorts": { + "NSCLC": { + "count": 7809, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample)", + "studies": [ + "msk_chord_2024" + ] + } + }, + "views": [ + { + "kind": "predictive", + "cohorts": [ + "NSCLC" + ], + "predictive": { + "exposure": "Treatment [SUBTYPE=Immuno]", + "endpoint": "OS", + "measure": "any", + "n": 7809, + "exposed": 2666, + "events": 3975, + "notProfiled": 0, + "noCovariate": 0, + "covariates": [], + "minMutated": 5, + "strata": 1, + "genes": [ + { + "gene": "STK11", + "exposed": 2666, + "unexposed": 5143, + "mutatedExposed": 472, + "mutatedUnexposed": 541, + "events": 3975, + "inExposed": { + "hr": 1.4820313471654858, + "ciLow": 1.322740932103988, + "ciHigh": 1.6605042307774234, + "p": 1.1916395352806596e-11 + }, + "inUnexposed": { + "hr": 1.2724109690194296, + "ciLow": 1.116655461500673, + "ciHigh": 1.4498918689791305, + "p": 0.00029900593368829424 + }, + "interaction": { + "hr": 1.1647426682493767, + "ciLow": 0.9795605089403415, + "ciHigh": 1.3849328049251732, + "p": 0.08431057609152337 + }, + "q": 0.10445866432895423 + }, + { + "gene": "KEAP1", + "exposed": 2666, + "unexposed": 5143, + "mutatedExposed": 521, + "mutatedUnexposed": 513, + "events": 3975, + "inExposed": { + "hr": 1.4072060640636472, + "ciLow": 1.2588128398325853, + "ciHigh": 1.5730923963254617, + "p": 1.8760175792195355e-9 + }, + "inUnexposed": { + "hr": 1.664279331691298, + "ciLow": 1.4671912889429146, + "ciHigh": 1.887842243045516, + "p": 2.354694183903863e-15 + }, + "interaction": { + "hr": 0.8455347832948261, + "ciLow": 0.7146422606297429, + "ciHigh": 1.0004013324532934, + "p": 0.05054908571383067 + }, + "q": 0.10109817142766135 + }, + { + "gene": "KRAS", + "exposed": 2666, + "unexposed": 5143, + "mutatedExposed": 981, + "mutatedUnexposed": 1213, + "events": 3975, + "inExposed": { + "hr": 0.9935757248478896, + "ciLow": 0.9029878475892461, + "ciHigh": 1.0932513916356341, + "p": 0.8948799557870332 + }, + "inUnexposed": { + "hr": 0.8882556779636579, + "ciLow": 0.8019785528235925, + "ciHigh": 0.9838145255341132, + "p": 0.023027856546192008 + }, + "interaction": { + "hr": 1.1185695171976484, + "ciLow": 0.9725180844499045, + "ciHigh": 1.2865547539010633, + "p": 0.11650634939425245 + }, + "q": 0.11650634939425246 + }, + { + "gene": "TP53", + "exposed": 2666, + "unexposed": 5143, + "mutatedExposed": 1602, + "mutatedUnexposed": 2418, + "events": 3975, + "inExposed": { + "hr": 1.0185714990508756, + "ciLow": 0.9276379554053747, + "ciHigh": 1.1184189830022309, + "p": 0.6997434246731516 + }, + "inUnexposed": { + "hr": 1.7216934258193264, + "ciLow": 1.5807139619663184, + "ciHigh": 1.875246454344059, + "p": 1.1658492193607049e-35 + }, + "interaction": { + "hr": 0.5916102621849268, + "ciLow": 0.5212350412928293, + "ciHigh": 0.6714872842286264, + "p": 4.534511929615143e-16 + }, + "q": 2.7207071577690857e-15 + }, + { + "gene": "EGFR", + "exposed": 2666, + "unexposed": 5143, + "mutatedExposed": 283, + "mutatedUnexposed": 1610, + "events": 3975, + "inExposed": { + "hr": 1.0401381840698867, + "ciLow": 0.9046242821108679, + "ciHigh": 1.1959522459818395, + "p": 0.5805637346628816 + }, + "inUnexposed": { + "hr": 0.898867486118003, + "ciLow": 0.8198417998883546, + "ciHigh": 0.9855105676608903, + "p": 0.02315772463858911 + }, + "interaction": { + "hr": 1.1571652108165273, + "ciLow": 0.9790004329181217, + "ciHigh": 1.3677535577106816, + "p": 0.08704888694079518 + }, + "q": 0.10445866432895423 + }, + { + "gene": "SMARCA4", + "exposed": 2666, + "unexposed": 5143, + "mutatedExposed": 283, + "mutatedUnexposed": 297, + "events": 3975, + "inExposed": { + "hr": 1.1354091434403244, + "ciLow": 0.9815259817569745, + "ciHigh": 1.3134180316859765, + "p": 0.08744673412535048 + }, + "inUnexposed": { + "hr": 1.8750772474436972, + "ciLow": 1.6057614645493874, + "ciHigh": 2.1895622491275044, + "p": 1.9173405288682856e-15 + }, + "interaction": { + "hr": 0.6055265962979581, + "ciLow": 0.48951552033507806, + "ciHigh": 0.7490313250399219, + "p": 0.0000037823926865095855 + }, + "q": 0.000011347178059528756 + } + ] + }, + "participation": { + "studies": [ + { + "study": "msk_chord_2024", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": true, + "aggregateOnly": [] + }, + "caveat": "For each gene, a Cox model of OS (Efron ties) on the gene (any mutation), exposure to Treatment [SUBTYPE=Immuno] (any such timeline event) and their product. The gene's hazard ratio among the exposed and among the unexposed are the same model's two contrasts (the interaction coded both ways), so they share its estimates; the interaction HR is their ratio, and its p asks whether the gene predicts benefit rather than merely prognosis. q is Benjamini–Hochberg over the 6 genes fitted. Each gene is fitted over the patients whose panel covers it: a patient not profiled for a gene is left out of that gene's fit, never counted as wild-type. BIASES: exposure is defined from the whole record, after the clock's start — a patient must survive long enough to be treated, so the exposed group is credited with time before their treatment (immortal-time bias), and treatment was not randomised but chosen for patients by their condition (confounding by indication). Both distort the exposure's own effect; the interaction is less exposed to them but not immune, and an interaction found here is a hypothesis for a randomised or emulated-trial design, not evidence of benefit. Left out: 0 patients with no mutation panel on record. Strongest interaction: TP53 (p 4.5e-16, q 2.7e-15). Computed from per-patient rows inside each patient-level study." + } + ] +} diff --git a/test/snapshots/predictive.sql b/test/snapshots/predictive.sql new file mode 100644 index 0000000..0f7b476 --- /dev/null +++ b/test/snapshots/predictive.sql @@ -0,0 +1,64 @@ +-- cohort NSCLC +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) + +-- view 0 predictive: predictive:panels · msk_chord_2024 +SELECT toString(pg.gene_panel_id) AS panel, groupUniqArray(pg.gene) AS genes +FROM snapshot_db.gene_panel_to_gene_derived pg +WHERE pg.gene_panel_id IN (SELECT DISTINCT gene_panel_id FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND alteration_type = 'MUTATION_EXTENDED') +GROUP BY panel + +-- view 0 predictive: predictive:rows · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status, + cityHash64(concat('cohort-lab-split-v1:msk_chord_2024:', replaceOne(patient_unique_id, 'msk_chord_2024_', ''))) % 2 AS half + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +panels AS ( + SELECT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, groupUniqArray(toString(sp.gene_panel_id)) AS panels + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, groupUniqArray(g.hugo_gene_symbol) AS genes + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +anch AS ( + SELECT p.stable_id AS pid, min(ce.start_date) AS day, 1 AS hit + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Treatment')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data + WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('SUBTYPE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('Immuno'))) + AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT os.months AS m, if(os.status LIKE '1%', 1, 0) AS ev, os.half AS half, panels.panels AS panels, mut.genes AS genes, anch.hit AS anchored, round(anch.day / 30.4375, 4) AS anchor_months +FROM os + LEFT JOIN panels ON panels.pid = os.pid + LEFT JOIN mut ON mut.pid = os.pid + LEFT JOIN anch ON anch.pid = os.pid +WHERE os.months IS NOT NULL AND os.months > 0 +ORDER BY os.pid diff --git a/test/snapshots/predictive.svg b/test/snapshots/predictive.svg new file mode 100644 index 0000000..d59e268 --- /dev/null +++ b/test/snapshots/predictive.svg @@ -0,0 +1,2 @@ + +Predictive biomarkers — Treatment [SUBTYPE=Immuno]exposed: Treatment [SUBTYPE=Immuno]unexposedHR, mutated vs wild-type [95% CI]interaction p · qSTK11mutated 472/2,666 exposed · 541/5,143 notmutated 472/2,666 exposed · 541/5,143…1.48 [1.32–1.66]1.27 [1.12–1.45]0.084 · q 0.10KEAP1mutated 521/2,666 exposed · 513/5,143 notmutated 521/2,666 exposed · 513/5,143…1.41 [1.26–1.57]1.66 [1.47–1.89]0.051 · q 0.10KRASmutated 981/2,666 exposed · 1,213/5,143 notmutated 981/2,666 exposed · 1,213/5,14…0.99 [0.90–1.09]0.89 [0.80–0.98]0.12 · q 0.12TP53mutated 1,602/2,666 exposed · 2,418/5,143 notmutated 1,602/2,666 exposed · 2,418/5,…1.02 [0.93–1.12]1.72 [1.58–1.88]4.5e-16 · q 2.7e-15EGFRmutated 283/2,666 exposed · 1,610/5,143 notmutated 283/2,666 exposed · 1,610/5,14…1.04 [0.90–1.20]0.90 [0.82–0.99]0.087 · q 0.10SMARCA4mutated 283/2,666 exposed · 297/5,143 notmutated 283/2,666 exposed · 297/5,143…1.14 [0.98–1.31]1.88 [1.61–2.19]3.8e-6 · q 1.1e-511.52HR, mutated vs wild-type (log scale) — left of 1 is lower, right is higherHR, mutated vs wild-type (log scale) — left…7,809 patients (2,666 exposed to Treatment [SUBTYPE=Immuno]), 3,975 deaths or events on OS. Each row: the mutated against the wild-type among the exposed(upper marker) and the unexposed (lower marker); the right column is the gene × exposure interaction. STK11 interaction HR 1.16, KEAP1 interaction HR 0.85, KRASinteraction HR 1.12, TP53 interaction HR 0.59, EGFR interaction HR 1.16, SMARCA4 interaction HR 0.61. For each gene, a Cox model of OS (Efron ties) on the gene(any mutation), exposure to Treatment [SUBTYPE=Immuno] (any such timeline event) and their product. The gene's hazard ratio among the exposed and among theunexposed are the same model's two contrasts (the interaction coded both ways), so they share its estimates; the interaction HR is their ratio, and its p asks whetherthe gene predicts benefit rather than merely prognosis. q is Benjamini–Hochberg over the 6 genes fitted. Each gene is fitted over the patients whose panel covers it: apatient not profiled for a gene is left out of that gene's fit, never counted as wild-type. BIASES: exposure is defined from the whole record, after the clock's start — apatient must survive long enough to be treated, so the exposed group is credited with time before their treatment (immortal-time bias), and treatment was notrandomised but chosen for patients by their condition (confounding by indication). Both distort the exposure's own effect; the interaction is less exposed to them butnot immune, and an interaction found here is a hypothesis for a randomised or emulated-trial design, not evidence of benefit. Left out: 0 patients with no mutationpanel on record. Strongest interaction: TP53 (p 4.5e-16, q 2.7e-15). Computed from per-patient rows inside each patient-level study. diff --git a/test/snapshots/predictive.txt b/test/snapshots/predictive.txt new file mode 100644 index 0000000..812e0f2 --- /dev/null +++ b/test/snapshots/predictive.txt @@ -0,0 +1,13 @@ +Analysis over msk_chord_2024 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- NSCLC: 7,809 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) + +Predictive biomarkers: gene x Treatment [SUBTYPE=Immuno] on OS (Cox per gene; 7,809 patients, 2,666 exposed, 3,975 events): + STK11: mutated 472/2666 exposed, 541/5143 unexposed; among exposed HR 1.48 (95% CI 1.32-1.66, 1.2e-11); among unexposed HR 1.27 (95% CI 1.12-1.45, 0.0003); interaction HR 1.16 (95% CI 0.98-1.38, 0.0843), q=0.1045 + KEAP1: mutated 521/2666 exposed, 513/5143 unexposed; among exposed HR 1.41 (95% CI 1.26-1.57, 1.9e-9); among unexposed HR 1.66 (95% CI 1.47-1.89, 2.4e-15); interaction HR 0.85 (95% CI 0.71-1.00, 0.0505), q=0.1011 + KRAS: mutated 981/2666 exposed, 1213/5143 unexposed; among exposed HR 0.99 (95% CI 0.90-1.09, 0.8949); among unexposed HR 0.89 (95% CI 0.80-0.98, 0.0230); interaction HR 1.12 (95% CI 0.97-1.29, 0.1165), q=0.1165 + TP53: mutated 1602/2666 exposed, 2418/5143 unexposed; among exposed HR 1.02 (95% CI 0.93-1.12, 0.6997); among unexposed HR 1.72 (95% CI 1.58-1.88, 1.2e-35); interaction HR 0.59 (95% CI 0.52-0.67, 4.5e-16), q=2.7e-15 + EGFR: mutated 283/2666 exposed, 1610/5143 unexposed; among exposed HR 1.04 (95% CI 0.90-1.20, 0.5806); among unexposed HR 0.90 (95% CI 0.82-0.99, 0.0232); interaction HR 1.16 (95% CI 0.98-1.37, 0.0870), q=0.1045 + SMARCA4: mutated 283/2666 exposed, 297/5143 unexposed; among exposed HR 1.14 (95% CI 0.98-1.31, 0.0874); among unexposed HR 1.88 (95% CI 1.61-2.19, 1.9e-15); interaction HR 0.61 (95% CI 0.49-0.75, 3.8e-6), q=1.1e-5 + CAVEAT (tell the user): For each gene, a Cox model of OS (Efron ties) on the gene (any mutation), exposure to Treatment [SUBTYPE=Immuno] (any such timeline event) and their product. The gene's hazard ratio among the exposed and among the unexposed are the same model's two contrasts (the interaction coded both ways), so they share its estimates; the interaction HR is their ratio, and its p asks whether the gene predicts benefit rather than merely prognosis. q is Benjamini–Hochberg over the 6 genes fitted. Each gene is fitted over the patients whose panel covers it: a patient not profiled for a gene is left out of that gene's fit, never counted as wild-type. BIASES: exposure is defined from the whole record, after the clock's start — a patient must survive long enough to be treated, so the exposed group is credited with time before their treatment (immortal-time bias), and treatment was not randomised but chosen for patients by their condition (confounding by indication). Both distort the exposure's own effect; the interaction is less exposed to them but not immune, and an interaction found here is a hypothesis for a randomised or emulated-trial design, not evidence of benefit. Left out: 0 patients with no mutation panel on record. Strongest interaction: TP53 (p 4.5e-16, q 2.7e-15). Computed from per-patient rows inside each patient-level study. diff --git a/test/snapshots/riskmodel-validateon.json b/test/snapshots/riskmodel-validateon.json new file mode 100644 index 0000000..7d12851 --- /dev/null +++ b/test/snapshots/riskmodel-validateon.json @@ -0,0 +1,1234 @@ +{ + "study": "msk_impact_2017", + "cohorts": { + "NSCLC (MSK-IMPACT 2017)": { + "count": 1567, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample)", + "studies": [ + "msk_impact_2017" + ] + }, + "NSCLC (MSK-CHORD)": { + "count": 7809, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) — in msk_chord_2024", + "studies": [ + "msk_chord_2024" + ] + } + }, + "views": [ + { + "kind": "riskmodel", + "cohorts": [ + "NSCLC (MSK-IMPACT 2017)" + ], + "riskmodel": { + "study": "msk_impact_2017", + "n": 1440, + "events": 468, + "notProfiled": 0, + "noStratum": 0, + "noCovariate": 0, + "noAnchor": 0, + "anchorAfterStart": 0, + "noFollowUp": 0, + "neverHeldOut": 27, + "genesCovered": 341, + "candidates": 93, + "minMutated": 30, + "strata": null, + "strataLevels": 1, + "alpha": 0.5, + "runs": 10, + "seed": 1, + "measure": "any", + "anchoredTo": null, + "covariates": [], + "reportedOn": "held-out scores", + "validation": null, + "cIndex": { + "median": 0.6126015735844189, + "q1": 0.5779661764381628, + "q3": 0.6210379768035842, + "values": [ + 0.6210379768035842, + 0.573881580323343, + 0.6284212455051419, + 0.6142946230598669, + 0.6126015735844189, + 0.5811746266707302, + 0.5582004851845435, + 0.5779661764381628, + 0.6288155740462467, + 0.6012407633341774 + ] + }, + "features": [ + { + "gene": "KEAP1", + "selected": 1, + "meanCoef": 0.38539693735888325, + "hr": 1.4701977820594718, + "mutated": 224 + }, + { + "gene": "STK11", + "selected": 0.9, + "meanCoef": 0.17038633859256822, + "hr": 1.1857628687976058, + "mutated": 235 + }, + { + "gene": "TP53", + "selected": 0.9, + "meanCoef": 0.16811283868259688, + "hr": 1.1830700991877459, + "mutated": 806 + }, + { + "gene": "EGFR", + "selected": 0.8, + "meanCoef": -0.13785784754562622, + "hr": 0.8712225293645339, + "mutated": 349 + }, + { + "gene": "IKZF1", + "selected": 0.4, + "meanCoef": 0.2794225902293096, + "hr": 1.3223660447805465, + "mutated": 32 + }, + { + "gene": "KMT2D", + "selected": 0.4, + "meanCoef": 0.14776083290881675, + "hr": 1.1592356121963068, + "mutated": 111 + }, + { + "gene": "NTRK1", + "selected": 0.4, + "meanCoef": 0.12709239831548483, + "hr": 1.1355219332482338, + "mutated": 36 + }, + { + "gene": "JAK3", + "selected": 0.3, + "meanCoef": -0.18679192974620454, + "hr": 0.8296163369100374, + "mutated": 36 + }, + { + "gene": "SMARCA4", + "selected": 0.3, + "meanCoef": 0.020427699925799726, + "hr": 1.0206377733887912, + "mutated": 128 + }, + { + "gene": "PBRM1", + "selected": 0.2, + "meanCoef": 0.10874806570415652, + "hr": 1.1148814380871648, + "mutated": 38 + }, + { + "gene": "MED12", + "selected": 0.2, + "meanCoef": -0.0609562421166403, + "hr": 0.9408644091103329, + "mutated": 62 + }, + { + "gene": "PIK3CA", + "selected": 0.2, + "meanCoef": -0.05219772493297931, + "hr": 0.9491411794121716, + "mutated": 106 + }, + { + "gene": "POLE", + "selected": 0.2, + "meanCoef": -0.035441437585562105, + "hr": 0.9651792558025496, + "mutated": 48 + }, + { + "gene": "KDR", + "selected": 0.1, + "meanCoef": 0.15283398948178678, + "hr": 1.1651315387960905, + "mutated": 56 + }, + { + "gene": "ATR", + "selected": 0.1, + "meanCoef": 0.08685522436534683, + "hr": 1.0907387558911674, + "mutated": 50 + }, + { + "gene": "ARID1B", + "selected": 0.1, + "meanCoef": -0.07117160732678922, + "hr": 0.9313020601568389, + "mutated": 47 + }, + { + "gene": "HGF", + "selected": 0.1, + "meanCoef": -0.06340309311212118, + "hr": 0.938565068313642, + "mutated": 72 + }, + { + "gene": "RET", + "selected": 0.1, + "meanCoef": -0.05615099916291561, + "hr": 0.9453963710513297, + "mutated": 39 + }, + { + "gene": "BRCA1", + "selected": 0.1, + "meanCoef": -0.03629762142908256, + "hr": 0.9643532385793903, + "mutated": 34 + }, + { + "gene": "PIK3CG", + "selected": 0.1, + "meanCoef": -0.02591353913772476, + "hr": 0.9744193351026584, + "mutated": 67 + }, + { + "…": "1 more", + "sha256": "3a0010ef50d71c44" + } + ], + "groups": [ + { + "name": "Low risk", + "from": null, + "to": 0, + "curve": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.07, + "s": 0.998 + }, + { + "t": 0.56, + "s": 0.9959 + }, + { + "t": 0.62, + "s": 0.9918 + }, + { + "t": 0.89, + "s": 0.9898 + }, + { + "t": 1.15, + "s": 0.9877 + }, + { + "t": 1.35, + "s": 0.9835 + }, + { + "t": 1.58, + "s": 0.9814 + }, + { + "t": 1.81, + "s": 0.9793 + }, + { + "t": 2.33, + "s": 0.9772 + }, + { + "t": 2.4, + "s": 0.9751 + }, + { + "t": 2.5300000000000002, + "s": 0.9729 + }, + { + "t": 2.56, + "s": 0.9708 + }, + { + "t": 2.63, + "s": 0.9686 + }, + { + "t": 2.76, + "s": 0.9665 + }, + { + "t": 2.86, + "s": 0.9643 + }, + { + "t": 2.93, + "s": 0.9622 + }, + { + "t": 3.02, + "s": 0.96 + }, + { + "t": 3.06, + "s": 0.9579 + }, + { + "t": 3.12, + "s": 0.9557 + }, + { + "…": "92 more", + "sha256": "12350b86caf221e2" + } + ], + "median": 26.56, + "n": 493, + "events": 118, + "atRisk": [ + 493, + 479, + 462, + 445, + 426, + 404, + 378, + 327, + 298, + 270, + 248, + 230, + 205, + 175, + 155, + 139, + 120, + 108, + 97, + 78, + { + "…": "13 more", + "sha256": "7445fed6c66d5c89" + } + ] + }, + "at12": 0.7853, + "at36": null, + "hr": null, + "ciLow": null, + "ciHigh": null, + "p": null + }, + { + "name": "Intermediate risk", + "from": 0, + "to": 0.16301127959913086, + "curve": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.03, + "s": 0.9978 + }, + { + "t": 0.39, + "s": 0.9955 + }, + { + "t": 0.43, + "s": 0.9933 + }, + { + "t": 0.49, + "s": 0.9911 + }, + { + "t": 0.72, + "s": 0.9866 + }, + { + "t": 0.76, + "s": 0.9843 + }, + { + "t": 0.82, + "s": 0.9821 + }, + { + "t": 0.92, + "s": 0.9798 + }, + { + "t": 0.95, + "s": 0.9775 + }, + { + "t": 0.99, + "s": 0.9753 + }, + { + "t": 1.45, + "s": 0.973 + }, + { + "t": 1.48, + "s": 0.9707 + }, + { + "t": 1.51, + "s": 0.9684 + }, + { + "t": 1.58, + "s": 0.9661 + }, + { + "t": 1.6099999999999999, + "s": 0.9639 + }, + { + "t": 1.8399999999999999, + "s": 0.9616 + }, + { + "t": 1.94, + "s": 0.9593 + }, + { + "t": 2.07, + "s": 0.957 + }, + { + "t": 2.3, + "s": 0.9547 + }, + { + "…": "108 more", + "sha256": "4ae1f3b0b11bf873" + } + ], + "median": 20.61, + "n": 449, + "events": 145, + "atRisk": [ + 449, + 431, + 417, + 405, + 380, + 359, + 334, + 293, + 257, + 222, + 195, + 175, + 147, + 122, + 110, + 93, + 79, + 70, + 58, + 50, + { + "…": "12 more", + "sha256": "0210541707688310" + } + ] + }, + "at12": 0.6843, + "at36": null, + "hr": 1.5185583404751872, + "ciLow": 1.1904294972787377, + "ciHigh": 1.937132302835405, + "p": 0.0007700381263588329 + }, + { + "name": "High risk", + "from": 0.16301127959913086, + "to": null, + "curve": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.16, + "s": 0.9979 + }, + { + "t": 0.36, + "s": 0.9957 + }, + { + "t": 0.46, + "s": 0.9936 + }, + { + "t": 0.49, + "s": 0.9893 + }, + { + "t": 0.56, + "s": 0.9871 + }, + { + "t": 0.59, + "s": 0.985 + }, + { + "t": 0.66, + "s": 0.9828 + }, + { + "t": 0.69, + "s": 0.9806 + }, + { + "t": 0.72, + "s": 0.9785 + }, + { + "t": 0.92, + "s": 0.9763 + }, + { + "t": 0.95, + "s": 0.9741 + }, + { + "t": 1.05, + "s": 0.9718 + }, + { + "t": 1.12, + "s": 0.9674 + }, + { + "t": 1.18, + "s": 0.9652 + }, + { + "t": 1.22, + "s": 0.9629 + }, + { + "t": 1.25, + "s": 0.9607 + }, + { + "t": 1.31, + "s": 0.9585 + }, + { + "t": 1.45, + "s": 0.9562 + }, + { + "t": 1.48, + "s": 0.954 + }, + { + "…": "143 more", + "sha256": "addee96a91e56f2d" + } + ], + "median": 14.96, + "n": 471, + "events": 198, + "atRisk": [ + 471, + 440, + 412, + 383, + 355, + 322, + 296, + 254, + 230, + 203, + 174, + 152, + 138, + 123, + 107, + 90, + 74, + 62, + 53, + 43, + { + "…": "19 more", + "sha256": "5d155ac2d09ab2c3" + } + ] + }, + "at12": 0.5842, + "at36": 0.3144, + "hr": 2.209321020827797, + "ciLow": 1.7582007602338594, + "ciHigh": 2.7761900025696384, + "p": 1.0287326105140663e-11 + } + ], + "logrank": { + "chi2": 48.8257, + "p": 2.4982402779304686e-11, + "df": 2, + "observed": [ + 118, + 145, + 198 + ], + "expected": [ + 179.1, + 145.4, + 136.5 + ], + "groups": 3, + "strata": 1 + }, + "refit": { + "hr": 1.3638116104895184, + "ciLow": 1.2608634744428482, + "ciHigh": 1.475165350259595, + "p": 9.307264149812706e-15 + }, + "histogram": { + "edges": [ + -0.31010503280508805, + -0.26442977656966465, + -0.2187545203342413, + -0.1730792640988179, + -0.12740400786339454, + -0.08172875162797116, + -0.03605349539254776, + 0.009621760842875582, + 0.05529701707829898, + 0.10097227331372238, + 0.14664752954914573, + 0.19232278578456907, + 0.23799804201999253, + 0.2836732982554159, + 0.3293485544908392, + 0.3750238107262627, + 0.420699066961686, + 0.46637432319710936, + 0.5120495794325328, + 0.5577248356679562, + { + "…": "11 more", + "sha256": "9aa6eeec9b485027" + } + ], + "counts": [ + 1, + 2, + 9, + 23, + 49, + 38, + 381, + 96, + 80, + 174, + 184, + 76, + 30, + 26, + 18, + 16, + 21, + 29, + 50, + 30, + { + "…": "10 more", + "sha256": "060f587966d110a3" + } + ] + }, + "scale": { + "min": -0.31010503280508805, + "max": 1.0601526542576134 + }, + "percentiles": [ + -0.31010503280508805, + -0.16709214644874631, + -0.13598582787524266, + -0.12011854092777559, + -0.11127560961224961, + -0.0933755085060019, + -0.08087926711113805, + -0.070766813135423, + -0.045155386738969724, + -0.03241372565242892, + -0.02097627251122866, + -0.009681262328982865, + -0.0034652017396736567, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + { + "…": "81 more", + "sha256": "58561e3d57dee714" + } + ], + "labelAbove": 0.5, + "cutsChosen": null, + "external": { + "cohort": "NSCLC (MSK-CHORD)", + "study": "msk_chord_2024", + "n": 7809, + "events": 3975, + "cohortPatients": 7809, + "rowsReturned": 7809, + "notProfiled": 0, + "noStratum": 0, + "noAnchor": 0, + "anchorAfterStart": 0, + "noFollowUp": 0, + "notCovered": 0, + "modelGenes": 21, + "cIndex": 0.6068560381183546, + "cIndexLo": 0.5972682687697759, + "cIndexHi": 0.6160543172683276, + "bootstrap": 200, + "cuts": [ + 0, + 0.16301127959913086 + ], + "groups": [ + { + "name": "Low risk", + "curve": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.032876676, + "s": 0.9978 + }, + { + "t": 0.065753353, + "s": 0.9975 + }, + { + "t": 0.197260058, + "s": 0.9971 + }, + { + "t": 0.230136734, + "s": 0.9968 + }, + { + "t": 0.26301341, + "s": 0.9964 + }, + { + "t": 0.295890087, + "s": 0.9957 + }, + { + "t": 0.328766763, + "s": 0.995 + }, + { + "t": 0.427396792, + "s": 0.9946 + }, + { + "t": 0.460273468, + "s": 0.9939 + }, + { + "t": 0.493150144, + "s": 0.9928 + }, + { + "t": 0.526026821, + "s": 0.9914 + }, + { + "t": 0.558903497, + "s": 0.991 + }, + { + "t": 0.591780173, + "s": 0.9906 + }, + { + "t": 0.62465685, + "s": 0.9903 + }, + { + "t": 0.657533526, + "s": 0.9895 + }, + { + "t": 0.723286879, + "s": 0.9892 + }, + { + "t": 0.756163555, + "s": 0.9885 + }, + { + "t": 0.789040231, + "s": 0.9874 + }, + { + "t": 0.821916907, + "s": 0.987 + }, + { + "…": "771 more", + "sha256": "a8d8e42b8bbaa0bc" + } + ], + "median": 66.37800945, + "n": 2790, + "events": 1093, + "atRisk": [ + 2790, + 2721, + 2663, + 2601, + 2561, + 2528, + 2488, + 2438, + 2384, + 2329, + 2267, + 2217, + 2164, + 2099, + 2049, + 1998, + 1940, + 1880, + 1826, + 1774, + { + "…": "94 more", + "sha256": "4a877e3021a607b9" + } + ] + }, + "hr": null, + "ciLow": null, + "ciHigh": null, + "p": null + }, + { + "name": "Intermediate risk", + "curve": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.032876676, + "s": 0.9972 + }, + { + "t": 0.065753353, + "s": 0.9966 + }, + { + "t": 0.131506705, + "s": 0.9963 + }, + { + "t": 0.164383381, + "s": 0.996 + }, + { + "t": 0.197260058, + "s": 0.9957 + }, + { + "t": 0.230136734, + "s": 0.9954 + }, + { + "t": 0.26301341, + "s": 0.9947 + }, + { + "t": 0.295890087, + "s": 0.9941 + }, + { + "t": 0.328766763, + "s": 0.9932 + }, + { + "t": 0.361643439, + "s": 0.9929 + }, + { + "t": 0.394520116, + "s": 0.9926 + }, + { + "t": 0.427396792, + "s": 0.9913 + }, + { + "t": 0.460273468, + "s": 0.9907 + }, + { + "t": 0.526026821, + "s": 0.9898 + }, + { + "t": 0.558903497, + "s": 0.9891 + }, + { + "t": 0.591780173, + "s": 0.9879 + }, + { + "t": 0.62465685, + "s": 0.9863 + }, + { + "t": 0.657533526, + "s": 0.9845 + }, + { + "t": 0.690410202, + "s": 0.9835 + }, + { + "…": "971 more", + "sha256": "0de78041f8297595" + } + ], + "median": 33.27119642, + "n": 3237, + "events": 1782, + "atRisk": [ + 3237, + 3136, + 3054, + 2954, + 2876, + 2797, + 2712, + 2622, + 2553, + 2478, + 2401, + 2313, + 2228, + 2171, + 2098, + 2015, + 1964, + 1908, + 1829, + 1750, + { + "…": "99 more", + "sha256": "4b053e04b0edf347" + } + ] + }, + "hr": 1.6277287421992754, + "ciLow": 1.5095384461851062, + "ciHigh": 1.755172824433477, + "p": 8.999745155526395e-37 + }, + { + "name": "High risk", + "curve": { + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.032876676, + "s": 0.9972 + }, + { + "t": 0.065753353, + "s": 0.9966 + }, + { + "t": 0.098630029, + "s": 0.9955 + }, + { + "t": 0.131506705, + "s": 0.9944 + }, + { + "t": 0.164383381, + "s": 0.9938 + }, + { + "t": 0.197260058, + "s": 0.9933 + }, + { + "t": 0.230136734, + "s": 0.9927 + }, + { + "t": 0.26301341, + "s": 0.9916 + }, + { + "t": 0.295890087, + "s": 0.9904 + }, + { + "t": 0.328766763, + "s": 0.9887 + }, + { + "t": 0.361643439, + "s": 0.9876 + }, + { + "t": 0.394520116, + "s": 0.9871 + }, + { + "t": 0.427396792, + "s": 0.9865 + }, + { + "t": 0.460273468, + "s": 0.9854 + }, + { + "t": 0.493150144, + "s": 0.9826 + }, + { + "t": 0.526026821, + "s": 0.982 + }, + { + "t": 0.558903497, + "s": 0.9792 + }, + { + "t": 0.591780173, + "s": 0.9775 + }, + { + "t": 0.62465685, + "s": 0.9764 + }, + { + "…": "618 more", + "sha256": "afc375b2cf58765c" + } + ], + "median": 20.71230607, + "n": 1782, + "events": 1100, + "atRisk": [ + 1782, + 1697, + 1609, + 1518, + 1444, + 1395, + 1319, + 1249, + 1191, + 1134, + 1079, + 1040, + 991, + 947, + 910, + 877, + 846, + 815, + 803, + 789, + { + "…": "92 more", + "sha256": "1691c27c2650bed8" + } + ] + }, + "hr": 2.1230991417937632, + "ciLow": 1.9523436405251429, + "ciHigh": 2.308789227634624, + "p": 2.5074185022917476e-69 + } + ], + "logrank": { + "chi2": 332.0044, + "p": 8.056851699601754e-73, + "df": 2, + "observed": [ + 1093, + 1782, + 1100 + ], + "expected": [ + 1603.4, + 1609.2, + 762.4 + ], + "groups": 3, + "strata": 1 + } + } + }, + "participation": { + "studies": [ + { + "study": "msk_impact_2017", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": true, + "aggregateOnly": [] + }, + "caveat": "Association, not cause: a gene's weight is its link to OS with the other candidates held fixed, in this cohort. Without strata, a gene that marks a cancer type carries that type's prognosis; on a cohort of several cancer types, stratify by CANCER_TYPE. Each patient is scored only by models that did not train on them. The group cuts are equal quantiles of the score, chosen without the outcome. Genes outside the panels every patient was sequenced on are not candidates, so a patient is never counted as wild-type for a gene they were not tested for. The C-index ignores delayed entry. Seeded (seed 1): the same document gives the same model. External validation on “NSCLC (MSK-CHORD)” (msk_chord_2024): each patient scored with the ensemble's mean coefficients (every gene's coefficient averaged over all 10 runs, zero where a run dropped it) and cut at the training thresholds; 0 patients on panels missing one of the model's 21 weighted genes are left out rather than scored as wild-type. Harrell's C-index (within strata) with a seeded bootstrap interval (200 resamples). The two studies' baselines, panels and vocabularies are not harmonized, and a patient enrolled in both under different identifiers cannot be removed. ", + "method": { + "steps": [ + { + "kind": "fetch", + "title": "One row per patient", + "scope": "NSCLC (MSK-IMPACT 2017) · msk_impact_2017", + "rowsIn": 1567, + "rowsOut": 1440, + "detail": "The riskmodel:rows query above: each patient's survival time and event, the panels their samples were sequenced on for mutations, the genes mutated in them, and their half of the engine's fixed held-out split. Patient-level: the rows never leave this server's engine, and no identifier is returned." + }, + { + "kind": "transform", + "title": "Patients, candidate genes and covariates", + "detail": "A gene is a candidate only if every patient's panels cover it, so no patient is counted as wild-type for a gene they were never tested for, and only if at least 30 patients have it mutated.", + "params": [ + [ + "genes covered for every patient", + "341" + ], + [ + "candidates", + "93" + ], + [ + "left out: no mutation panel on record", + "0" + ] + ] + }, + { + "kind": "statistic", + "title": "Elastic-net Cox, 10 random splits", + "detail": "Each run holds out a random 33% of the patients; on the rest, the penalty is chosen from a path of 25 by 3-fold cross-validated partial likelihood (Verweij & van Houwelingen), and the model at that penalty scores the held-out patients. Genes are scaled to unit variance before fitting; Breslow ties. A patient's risk score is the mean of the scores they got while held out (OncoCast's ensemble, Martin et al.).", + "params": [ + [ + "l1 share of the penalty (alpha)", + "0.5" + ], + [ + "runs", + "10" + ], + [ + "seed", + "1" + ], + [ + "test-set C-index, median (IQR)", + "0.613 (0.578–0.621)" + ], + [ + "never held out, so unscored", + "27" + ] + ] + }, + { + "kind": "statistic", + "title": "Risk groups, their survival and hazard ratios", + "detail": "Cuts at equal quantiles of the held-out scores, each moved to the nearer edge of any run of tied scores so equal scores share a group — no outcome is used to place them. Each group is drawn as a Kaplan–Meier curve and read at 12 and 36 months; the groups are compared with a log-rank test, and a Cox model of the outcome on the group indicators gives each group's hazard ratio against the lowest (Newton–Raphson, full information matrix, 95% Wald intervals). A second Cox model on the standardised score alone gives the hazard ratio per standard deviation. The score is also shown on OncoCast's 0–10 scale, (score − min) / (max − min) × 10, min and max the lowest and highest held-out score; the scale is linear, so it moves no patient between groups and changes no number above.", + "params": [ + [ + "0 on the 0–10 scale (raw score)", + "-0.3101" + ], + [ + "10 on the 0–10 scale (raw score)", + "1.0602" + ], + [ + "Low risk", + "493 patients, 118 events, median 26.6 months" + ], + [ + "Intermediate risk", + "449 patients, 145 events, median 20.6 months, HR 1.52" + ], + [ + "High risk", + "471 patients, 198 events, median 15.0 months, HR 2.21" + ] + ] + } + ], + "implementation": { + "functions": [ + "coxnetEnsemble", + "coxnetPath", + "cvLambda", + "concordance", + "coxFit" + ], + "file": "lib/coxnet.ts", + "commit": null + }, + "verification": "lib/coxnet.ts's path is checked against scikit-survival's CoxnetSurvivalAnalysis, its delayed-entry, stratified fits (with standard errors) against statsmodels' PHReg and its C-index against lifelines, by test/coxnet-golden.test.ts (fixtures from python/golden/coxnet.py)." + } + } + ] +} diff --git a/test/snapshots/riskmodel-validateon.sql b/test/snapshots/riskmodel-validateon.sql new file mode 100644 index 0000000..000c260 --- /dev/null +++ b/test/snapshots/riskmodel-validateon.sql @@ -0,0 +1,103 @@ +-- cohort NSCLC (MSK-IMPACT 2017) +-- msk_impact_2017 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) + +-- cohort NSCLC (MSK-CHORD) +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) + +-- view 0 riskmodel: riskmodel:panels · msk_chord_2024 +SELECT toString(pg.gene_panel_id) AS panel, groupUniqArray(pg.gene) AS genes +FROM snapshot_db.gene_panel_to_gene_derived pg +WHERE pg.gene_panel_id IN (SELECT DISTINCT gene_panel_id FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND alteration_type = 'MUTATION_EXTENDED') +GROUP BY panel + +-- view 0 riskmodel: riskmodel:panels · msk_impact_2017 +SELECT toString(pg.gene_panel_id) AS panel, groupUniqArray(pg.gene) AS genes +FROM snapshot_db.gene_panel_to_gene_derived pg +WHERE pg.gene_panel_id IN (SELECT DISTINCT gene_panel_id FROM snapshot_db.sample_to_gene_panel_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND alteration_type = 'MUTATION_EXTENDED') +GROUP BY panel + +-- view 0 riskmodel: riskmodel:rows · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status, + cityHash64(concat('cohort-lab-split-v1:msk_chord_2024:', replaceOne(patient_unique_id, 'msk_chord_2024_', ''))) % 2 AS half + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +panels AS ( + SELECT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid, groupUniqArray(toString(sp.gene_panel_id)) AS panels + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid, groupUniqArray(g.hugo_gene_symbol) AS genes + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_chord_2024' AND g.variant_type = 'mutation' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT os.months AS m, if(os.status LIKE '1%', 1, 0) AS ev, os.half AS half, panels.panels AS panels, mut.genes AS genes +FROM os + LEFT JOIN panels ON panels.pid = os.pid + LEFT JOIN mut ON mut.pid = os.pid +WHERE os.months IS NOT NULL AND os.months > 0 +ORDER BY os.pid + +-- view 0 riskmodel: riskmodel:rows · msk_impact_2017 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer'))))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_impact_2017_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name='OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status, + cityHash64(concat('cohort-lab-split-v1:msk_impact_2017:', replaceOne(patient_unique_id, 'msk_impact_2017_', ''))) % 2 AS half + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_impact_2017' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_impact_2017_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +panels AS ( + SELECT replaceOne(sd.patient_unique_id, 'msk_impact_2017_', '') AS pid, groupUniqArray(toString(sp.gene_panel_id)) AS panels + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_impact_2017' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND replaceOne(sd.patient_unique_id, 'msk_impact_2017_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +mut AS ( + SELECT replaceOne(g.patient_unique_id, 'msk_impact_2017_', '') AS pid, groupUniqArray(g.hugo_gene_symbol) AS genes + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_impact_2017' AND g.variant_type = 'mutation' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2) + AND replaceOne(g.patient_unique_id, 'msk_impact_2017_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT os.months AS m, if(os.status LIKE '1%', 1, 0) AS ev, os.half AS half, panels.panels AS panels, mut.genes AS genes +FROM os + LEFT JOIN panels ON panels.pid = os.pid + LEFT JOIN mut ON mut.pid = os.pid +WHERE os.months IS NOT NULL AND os.months > 0 +ORDER BY os.pid diff --git a/test/snapshots/riskmodel-validateon.svg b/test/snapshots/riskmodel-validateon.svg new file mode 100644 index 0000000..5badcee --- /dev/null +++ b/test/snapshots/riskmodel-validateon.svg @@ -0,0 +1,2 @@ + +Risk model — Low risk, Intermediate risk, High risk0%25%50%75%100%Survival probability01224Monthsgroupscore (0–10)patients · eventsmedian12 mo36 moHR vs lowest [95% CI]Low risk0–2.3493 · 11826.6 mo79%—referenceIntermediate risk2.3–3.5449 · 14520.6 mo68%—1.52 [1.19–1.94]p = 7.7e-4High risk3.5–10471 · 19815 mo58%31%2.21 [1.76–2.78]p = 1e-11log-rank χ² = 48.8 (2 df) · p = 2.5e-11risk score alone: HR 1.36 per standard deviation [95% CI 1.26–1.48] · p = 9.3e-15held-out C-index 0.613 (IQR 0.578–0.621) over 10 runsRisk score, 0–10 (held-out scores), with the group cuts0200400patients2.33.50246810risk score, 0–10 — lower risk ← → higher riskSelection frequency against mean hazard ratio (labelled: kept in ≥ 50% of runs)0%25%50%75%100%share of runs kept0.811.21.41.6mean hazard ratio where kept (log scale) — favourable ← → adverseTP53: kept in 90% of runs · mean HR 1.18 · 806 mutatedEGFR: kept in 80% of runs · mean HR 0.87 · 349 mutatedSTK11: kept in 90% of runs · mean HR 1.19 · 235 mutatedKEAP1: kept in 100% of runs · mean HR 1.47 · 224 mutatedSMARCA4: kept in 30% of runs · mean HR 1.02 · 128 mutatedKMT2D: kept in 40% of runs · mean HR 1.16 · 111 mutatedPIK3CA: kept in 20% of runs · mean HR 0.95 · 106 mutatedHGF: kept in 10% of runs · mean HR 0.94 · 72 mutatedPIK3CG: kept in 10% of runs · mean HR 0.97 · 67 mutatedMED12: kept in 20% of runs · mean HR 0.94 · 62 mutatedKDR: kept in 10% of runs · mean HR 1.17 · 56 mutatedPAK5: kept in 10% of runs · mean HR 0.98 · 55 mutatedATR: kept in 10% of runs · mean HR 1.09 · 50 mutatedPOLE: kept in 20% of runs · mean HR 0.97 · 48 mutatedARID1B: kept in 10% of runs · mean HR 0.93 · 47 mutatedRET: kept in 10% of runs · mean HR 0.95 · 39 mutatedPBRM1: kept in 20% of runs · mean HR 1.11 · 38 mutatedNTRK1: kept in 40% of runs · mean HR 1.14 · 36 mutatedJAK3: kept in 30% of runs · mean HR 0.83 · 36 mutatedBRCA1: kept in 10% of runs · mean HR 0.96 · 34 mutatedIKZF1: kept in 40% of runs · mean HR 1.32 · 32 mutatedKEAP1STK11TP53EGFRPoint size: patients mutated (largest 806). 4 of 21 genes kept in ≥ 50% of runs are named.Genes kept most often (share of runs · mean hazard ratio where kept)KEAP1100% · 224 mutatedHR 1.47STK1190% · 235 mutatedHR 1.19TP5390% · 806 mutatedHR 1.18EGFR80% · 349 mutatedHR 0.87IKZF140% · 32 mutatedHR 1.32KMT2D40% · 111 mutatedHR 1.16NTRK140% · 36 mutatedHR 1.14JAK330% · 36 mutatedHR 0.83SMARCA430% · 128 mutatedHR 1.02PBRM120% · 38 mutatedHR 1.11MED1220% · 62 mutatedHR 0.94PIK3CA20% · 106 mutatedHR 0.95POLE20% · 48 mutatedHR 0.97KDR10% · 56 mutatedHR 1.17ATR10% · 50 mutatedHR 1.090.511.52mean hazard ratio where kept (log scale)External validation on “NSCLC (MSK-CHORD)” (msk_chord_2024), cut at the training thresholds0%25%50%75%100%Survival probability024487296MonthsNumber at riskLow riskLow risk: 2,790 at risk at 0 months2,790Low risk: 1,563 at risk at 24 months1,563Low risk: 835 at risk at 48 months835Low risk: 331 at risk at 72 months331Low risk: 54 at risk at 96 months54Intermediate riskIntermediate risk: 3,237 at risk at 0 months3,237Intermediate risk: 1,481 at risk at 24 months1,481Intermediate risk: 701 at risk at 48 months701Intermediate risk: 262 at risk at 72 months262Intermediate risk: 35 at risk at 96 months35High riskHigh risk: 1,782 at risk at 0 months1,782High risk: 663 at risk at 24 months663High risk: 322 at risk at 48 months322High risk: 135 at risk at 72 months135High risk: 22 at risk at 96 months22Low riskmedian 66.4 mo · n = 2,790 · 1,093 events · referenceIntermediate riskmedian 33.3 mo · n = 3,237 · 1,782 events · HR 1.63 [1.51–1.76]High riskmedian 20.7 mo · n = 1,782 · 1,100 events · HR 2.12 [1.95–2.31]C-index 0.607 (95% bootstrap CI 0.597–0.616, 200 resamples) on 7,809 patients never trained on · log-rank χ² = 332 (2 df) · p = 8.1e-731,440 patients, 468 events; 93 candidate genes (of 341 covered for every patient, mutated in ≥30); elastic net, alpha 0.5, seed 1. The risk score isOncoCast's 0–10 scale: 0 and 10 are the lowest and highest held-out score (raw linear predictor −0.31 to 1.06). 72 of the 93 candidate genes were neverkept, so are not on the volcano. Left out: 27 never held out (so unscored and in no group). 6 more genes were kept in at least one run; they are in the result,not drawn. Validation: 7,809 of 7,809 patients of “NSCLC (MSK-CHORD)” scored; 0 on panels missing one of the model's 21 weighted genes, 0 with no panelleft out. Association, not cause: a gene's weight is its link to OS with the other candidates held fixed, in this cohort. Without strata, a gene that marks a cancertype carries that type's prognosis; on a cohort of several cancer types, stratify by CANCER_TYPE. Each patient is scored only by models that did not train onthem. The group cuts are equal quantiles of the score, chosen without the outcome. Genes outside the panels every patient was sequenced on are notcandidates, so a patient is never counted as wild-type for a gene they were not tested for. The C-index ignores delayed entry. Seeded (seed 1): the samedocument gives the same model. External validation on “NSCLC (MSK-CHORD)” (msk_chord_2024): each patient scored with the ensemble's meancoefficients (every gene's coefficient averaged over all 10 runs, zero where a run dropped it) and cut at the training thresholds; 0 patients on panels missingone of the model's 21 weighted genes are left out rather than scored as wild-type. Harrell's C-index (within strata) with a seeded bootstrap interval (200resamples). The two studies' baselines, panels and vocabularies are not harmonized, and a patient enrolled in both under different identifiers cannot beremoved. diff --git a/test/snapshots/riskmodel-validateon.txt b/test/snapshots/riskmodel-validateon.txt new file mode 100644 index 0000000..b1dfcbd --- /dev/null +++ b/test/snapshots/riskmodel-validateon.txt @@ -0,0 +1,60 @@ +Analysis over msk_impact_2017 + msk_chord_2024 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- NSCLC (MSK-IMPACT 2017): 1,567 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) +- NSCLC (MSK-CHORD): 7,809 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) — in msk_chord_2024 + +Risk model (elastic-net Cox, 10 held-out splits, alpha 0.5, seed 1; 1,440 patients, 468 events, 93 candidate genes): + held-out C-index 0.613 (IQR 0.578–0.621) + risk score on OncoCast's 0–10 scale (0 and 10 the lowest and highest held-out score; raw -0.310 to 1.060) + risk groups (held-out scores, equal quantiles): + Low risk (score 0–2.3): n=493, 118 events, median 26.6mo, 12-month 79%, 36-month n/a + Intermediate risk (score 2.3–3.5): n=449, 145 events, median 20.6mo, 12-month 68%, 36-month n/a, HR vs lowest 1.52 (95% CI 1.19–1.94, 0.0008) + High risk (score 3.5–10): n=471, 198 events, median 15.0mo, 12-month 58%, 36-month 31%, HR vs lowest 2.21 (95% CI 1.76–2.78, 1.0e-11) + log-rank p=2.5e-11 + risk score alone: HR 1.36 per SD (95% CI 1.26–1.48, 9.3e-15) + genes kept most often: KEAP1 100% HR 1.47, STK11 90% HR 1.19, TP53 90% HR 1.18, EGFR 80% HR 0.87, IKZF1 40% HR 1.32, KMT2D 40% HR 1.16, NTRK1 40% HR 1.14, JAK3 30% HR 0.83, SMARCA4 30% HR 1.02, PBRM1 20% HR 1.11, MED12 20% HR 0.94, PIK3CA 20% HR 0.95, POLE 20% HR 0.97, KDR 10% HR 1.17, ATR 10% HR 1.09 + 27 patients were never held out, so have no score + external validation on NSCLC (MSK-CHORD) (msk_chord_2024; 7809 patients scored, 0 not covered by the model's genes): C-index 0.607 (95% bootstrap CI 0.597–0.616), log-rank p=8.1e-73 + Low risk: n=2790, 1093 events, median 66.4mo + Intermediate risk: n=3237, 1782 events, median 33.3mo, HR vs lowest 1.63 (95% CI 1.51–1.76) + High risk: n=1782, 1100 events, median 20.7mo, HR vs lowest 2.12 (95% CI 1.95–2.31) + CAVEAT (tell the user): Association, not cause: a gene's weight is its link to OS with the other candidates held fixed, in this cohort. Without strata, a gene that marks a cancer type carries that type's prognosis; on a cohort of several cancer types, stratify by CANCER_TYPE. Each patient is scored only by models that did not train on them. The group cuts are equal quantiles of the score, chosen without the outcome. Genes outside the panels every patient was sequenced on are not candidates, so a patient is never counted as wild-type for a gene they were not tested for. The C-index ignores delayed entry. Seeded (seed 1): the same document gives the same model. External validation on “NSCLC (MSK-CHORD)” (msk_chord_2024): each patient scored with the ensemble's mean coefficients (every gene's coefficient averaged over all 10 runs, zero where a run dropped it) and cut at the training thresholds; 0 patients on panels missing one of the model's 21 weighted genes are left out rather than scored as wild-type. Harrell's C-index (within strata) with a seeded bootstrap interval (200 resamples). The two studies' baselines, panels and vocabularies are not harmonized, and a patient enrolled in both under different identifiers cannot be removed. + METHOD: + - One row per patient [NSCLC (MSK-IMPACT 2017) · msk_impact_2017] (1,567 → 1,440 rows): The riskmodel:rows query above: each patient's survival time and event, the panels their samples were sequenced on for mutations, the genes mutated in them, and their half of the engine's fixed held-out split. Patient-level: the rows never leave this server's engine, and no identifier is returned. + - Patients, candidate genes and covariates: A gene is a candidate only if every patient's panels cover it, so no patient is counted as wild-type for a gene they were never tested for, and only if at least 30 patients have it mutated. + genes covered for every patient: 341 + candidates: 93 + left out: no mutation panel on record: 0 + - Elastic-net Cox, 10 random splits: Each run holds out a random 33% of the patients; on the rest, the penalty is chosen from a path of 25 by 3-fold cross-validated partial likelihood (Verweij & van Houwelingen), and the model at that penalty scores the held-out patients. Genes are scaled to unit variance before fitting; Breslow ties. A patient's risk score is the mean of the scores they got while held out (OncoCast's ensemble, Martin et al.). + l1 share of the penalty (alpha): 0.5 + runs: 10 + seed: 1 + test-set C-index, median (IQR): 0.613 (0.578–0.621) + never held out, so unscored: 27 + - Risk groups, their survival and hazard ratios: Cuts at equal quantiles of the held-out scores, each moved to the nearer edge of any run of tied scores so equal scores share a group — no outcome is used to place them. Each group is drawn as a Kaplan–Meier curve and read at 12 and 36 months; the groups are compared with a log-rank test, and a Cox model of the outcome on the group indicators gives each group's hazard ratio against the lowest (Newton–Raphson, full information matrix, 95% Wald intervals). A second Cox model on the standardised score alone gives the hazard ratio per standard deviation. The score is also shown on OncoCast's 0–10 scale, (score − min) / (max − min) × 10, min and max the lowest and highest held-out score; the scale is linear, so it moves no patient between groups and changes no number above. + 0 on the 0–10 scale (raw score): -0.3101 + 10 on the 0–10 scale (raw score): 1.0602 + Low risk: 493 patients, 118 events, median 26.6 months + Intermediate risk: 449 patients, 145 events, median 20.6 months, HR 1.52 + High risk: 471 patients, 198 events, median 15.0 months, HR 2.21 + lib/coxnet.ts's path is checked against scikit-survival's CoxnetSurvivalAnalysis, its delayed-entry, stratified fits (with standard errors) against statsmodels' PHReg and its C-index against lifelines, by test/coxnet-golden.test.ts (fixtures from python/golden/coxnet.py). + METHOD: + - One row per patient [NSCLC (MSK-IMPACT 2017) · msk_impact_2017] (1,567 → 1,440 rows): The riskmodel:rows query above: each patient's survival time and event, the panels their samples were sequenced on for mutations, the genes mutated in them, and their half of the engine's fixed held-out split. Patient-level: the rows never leave this server's engine, and no identifier is returned. + - Patients, candidate genes and covariates: A gene is a candidate only if every patient's panels cover it, so no patient is counted as wild-type for a gene they were never tested for, and only if at least 30 patients have it mutated. + genes covered for every patient: 341 + candidates: 93 + left out: no mutation panel on record: 0 + - Elastic-net Cox, 10 random splits: Each run holds out a random 33% of the patients; on the rest, the penalty is chosen from a path of 25 by 3-fold cross-validated partial likelihood (Verweij & van Houwelingen), and the model at that penalty scores the held-out patients. Genes are scaled to unit variance before fitting; Breslow ties. A patient's risk score is the mean of the scores they got while held out (OncoCast's ensemble, Martin et al.). + l1 share of the penalty (alpha): 0.5 + runs: 10 + seed: 1 + test-set C-index, median (IQR): 0.613 (0.578–0.621) + never held out, so unscored: 27 + - Risk groups, their survival and hazard ratios: Cuts at equal quantiles of the held-out scores, each moved to the nearer edge of any run of tied scores so equal scores share a group — no outcome is used to place them. Each group is drawn as a Kaplan–Meier curve and read at 12 and 36 months; the groups are compared with a log-rank test, and a Cox model of the outcome on the group indicators gives each group's hazard ratio against the lowest (Newton–Raphson, full information matrix, 95% Wald intervals). A second Cox model on the standardised score alone gives the hazard ratio per standard deviation. The score is also shown on OncoCast's 0–10 scale, (score − min) / (max − min) × 10, min and max the lowest and highest held-out score; the scale is linear, so it moves no patient between groups and changes no number above. + 0 on the 0–10 scale (raw score): -0.3101 + 10 on the 0–10 scale (raw score): 1.0602 + Low risk: 493 patients, 118 events, median 26.6 months + Intermediate risk: 449 patients, 145 events, median 20.6 months, HR 1.52 + High risk: 471 patients, 198 events, median 15.0 months, HR 2.21 + lib/coxnet.ts's path is checked against scikit-survival's CoxnetSurvivalAnalysis, its delayed-entry, stratified fits (with standard errors) against statsmodels' PHReg and its C-index against lifelines, by test/coxnet-golden.test.ts (fixtures from python/golden/coxnet.py). diff --git a/test/snapshots/rmst.json b/test/snapshots/rmst.json new file mode 100644 index 0000000..31781ce --- /dev/null +++ b/test/snapshots/rmst.json @@ -0,0 +1,869 @@ +{ + "study": "tmb_mskcc_2018", + "cohorts": { + "TMB < 10": { + "count": 1201, + "text": "TMB_NONSYNONYMOUS lt 10 (any sample)", + "studies": [ + "tmb_mskcc_2018" + ] + }, + "TMB ≥ 10": { + "count": 460, + "text": "TMB_NONSYNONYMOUS gte 10 (any sample)", + "studies": [ + "tmb_mskcc_2018" + ] + } + }, + "views": [ + { + "kind": "rmst", + "cohorts": [ + "TMB < 10", + "TMB ≥ 10" + ], + "rmst": { + "endpointLabel": "OS", + "panels": [ + { + "landmark": 0, + "tau": 24, + "tauSet": true, + "curves": [ + { + "cohort": "TMB < 10", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0, + "s": 0.9858 + }, + { + "t": 1, + "s": 0.9348 + }, + { + "t": 2, + "s": 0.8777 + }, + { + "t": 3, + "s": 0.828 + }, + { + "t": 4, + "s": 0.7804 + }, + { + "t": 5, + "s": 0.744 + }, + { + "t": 6, + "s": 0.7197 + }, + { + "t": 7, + "s": 0.6846 + }, + { + "t": 8, + "s": 0.6583 + }, + { + "t": 9, + "s": 0.6235 + }, + { + "t": 10, + "s": 0.5949 + }, + { + "t": 11, + "s": 0.5646 + }, + { + "t": 12, + "s": 0.5466 + }, + { + "t": 13, + "s": 0.5206 + }, + { + "t": 14, + "s": 0.5011 + }, + { + "t": 15, + "s": 0.4843 + }, + { + "t": 16, + "s": 0.4704 + }, + { + "t": 17, + "s": 0.4586 + }, + { + "t": 18, + "s": 0.4434 + }, + { + "…": "28 more", + "sha256": "98cee81d86b85b61" + } + ], + "median": 15, + "n": 1201, + "events": 663, + "atRisk": [ + 1201, + 1177, + 1098, + 1007, + 922, + 856, + 798, + 758, + 702, + 662, + 611, + 570, + 534, + 503, + 455, + 416, + 384, + 360, + 332, + 305, + { + "…": "59 more", + "sha256": "aaa9b25b241f5930" + } + ], + "rmst": 14.469155080019739, + "se": 0.2751852508824997, + "lo": 13.929801899213421, + "hi": 15.008508260826057, + "atRiskTau": 205, + "beforeLandmark": 0 + }, + { + "cohort": "TMB ≥ 10", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0, + "s": 0.9891 + }, + { + "t": 1, + "s": 0.9564 + }, + { + "t": 2, + "s": 0.9166 + }, + { + "t": 3, + "s": 0.8918 + }, + { + "t": 4, + "s": 0.8662 + }, + { + "t": 5, + "s": 0.8353 + }, + { + "t": 6, + "s": 0.8039 + }, + { + "t": 7, + "s": 0.7789 + }, + { + "t": 8, + "s": 0.7611 + }, + { + "t": 9, + "s": 0.7532 + }, + { + "t": 10, + "s": 0.7396 + }, + { + "t": 11, + "s": 0.7202 + }, + { + "t": 12, + "s": 0.7031 + }, + { + "t": 13, + "s": 0.6885 + }, + { + "t": 14, + "s": 0.6735 + }, + { + "t": 15, + "s": 0.6547 + }, + { + "t": 16, + "s": 0.6478 + }, + { + "t": 17, + "s": 0.6442 + }, + { + "t": 18, + "s": 0.6404 + }, + { + "…": "18 more", + "sha256": "079c993502d6b418" + } + ], + "median": 42, + "n": 460, + "events": 169, + "atRisk": [ + 460, + 453, + 433, + 406, + 384, + 364, + 346, + 321, + 306, + 289, + 278, + 267, + 252, + 241, + 229, + 215, + 191, + 179, + 170, + 157, + { + "…": "61 more", + "sha256": "c254b4def5718e9f" + } + ], + "rmst": 17.7264505334303, + "se": 0.4234469106093885, + "lo": 16.896509839271147, + "hi": 18.55639122758945, + "atRiskTau": 107, + "beforeLandmark": 0 + } + ], + "comparisons": [ + { + "cohort": "TMB ≥ 10", + "vs": "TMB < 10", + "diff": 3.25729545341056, + "diffLo": 2.267495771925831, + "diffHi": 4.247095134895289, + "diffP": 1.1186966629250122e-10, + "ratio": 1.2251199489808853, + "ratioLo": 1.153952097793275, + "ratioHi": 1.300676945135906, + "ratioP": 2.940231223067336e-11 + } + ] + }, + { + "landmark": 6, + "tau": 24, + "tauSet": true, + "curves": [ + { + "cohort": "TMB < 10", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1, + "s": 0.9512 + }, + { + "t": 2, + "s": 0.9146 + }, + { + "t": 3, + "s": 0.8662 + }, + { + "t": 4, + "s": 0.8266 + }, + { + "t": 5, + "s": 0.7845 + }, + { + "t": 6, + "s": 0.7595 + }, + { + "t": 7, + "s": 0.7233 + }, + { + "t": 8, + "s": 0.6963 + }, + { + "t": 9, + "s": 0.6728 + }, + { + "t": 10, + "s": 0.6536 + }, + { + "t": 11, + "s": 0.6372 + }, + { + "t": 12, + "s": 0.6161 + }, + { + "t": 13, + "s": 0.5999 + }, + { + "t": 14, + "s": 0.5872 + }, + { + "t": 15, + "s": 0.567 + }, + { + "t": 16, + "s": 0.5505 + }, + { + "t": 17, + "s": 0.5407 + }, + { + "t": 18, + "s": 0.5275 + }, + { + "t": 19, + "s": 0.5188 + }, + { + "…": "21 more", + "sha256": "eda5cb65d9b5481a" + } + ], + "median": 22, + "n": 758, + "events": 342, + "atRisk": [ + 758, + 758, + 702, + 662, + 611, + 570, + 534, + 503, + 455, + 416, + 384, + 360, + 332, + 305, + 282, + 262, + 241, + 223, + 205, + 183, + { + "…": "53 more", + "sha256": "5d7747d6bea1f1b3" + } + ], + "rmst": 15.97471541616958, + "se": 0.3478711401796504, + "lo": 15.292900510156581, + "hi": 16.65653032218258, + "atRiskTau": 125, + "beforeLandmark": 443 + }, + { + "cohort": "TMB ≥ 10", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1, + "s": 0.9688 + }, + { + "t": 2, + "s": 0.9467 + }, + { + "t": 3, + "s": 0.9369 + }, + { + "t": 4, + "s": 0.92 + }, + { + "t": 5, + "s": 0.8959 + }, + { + "t": 6, + "s": 0.8746 + }, + { + "t": 7, + "s": 0.8564 + }, + { + "t": 8, + "s": 0.8377 + }, + { + "t": 9, + "s": 0.8143 + }, + { + "t": 10, + "s": 0.8058 + }, + { + "t": 11, + "s": 0.8013 + }, + { + "t": 12, + "s": 0.7966 + }, + { + "t": 13, + "s": 0.7814 + }, + { + "t": 15, + "s": 0.7529 + }, + { + "t": 16, + "s": 0.7467 + }, + { + "t": 18, + "s": 0.7327 + }, + { + "t": 19, + "s": 0.7253 + }, + { + "t": 21, + "s": 0.7171 + }, + { + "t": 22, + "s": 0.7086 + }, + { + "…": "11 more", + "sha256": "c40db3d1f9e230b7" + } + ], + "median": 52, + "n": 321, + "events": 83, + "atRisk": [ + 321, + 321, + 306, + 289, + 278, + 267, + 252, + 241, + 229, + 215, + 191, + 179, + 170, + 157, + 147, + 137, + 122, + 117, + 107, + 99, + { + "…": "55 more", + "sha256": "a0c67190e51a427b" + } + ], + "rmst": 19.58172100676645, + "se": 0.460378348519464, + "lo": 18.679396024406273, + "hi": 20.484045989126628, + "atRiskTau": 70, + "beforeLandmark": 139 + } + ], + "comparisons": [ + { + "cohort": "TMB ≥ 10", + "vs": "TMB < 10", + "diff": 3.60700559059687, + "diffLo": 2.4760493846132734, + "diffHi": 4.737961796580467, + "diffP": 4.0784718083330727e-10, + "ratio": 1.2257946696781756, + "ratioLo": 1.1511712996946588, + "ratioHi": 1.3052554147327822, + "ratioP": 2.1116282378373278e-10 + } + ] + }, + { + "landmark": 12, + "tau": 24, + "tauSet": true, + "curves": [ + { + "cohort": "TMB < 10", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1, + "s": 0.9523 + }, + { + "t": 2, + "s": 0.9167 + }, + { + "t": 3, + "s": 0.8859 + }, + { + "t": 4, + "s": 0.8605 + }, + { + "t": 5, + "s": 0.839 + }, + { + "t": 6, + "s": 0.8112 + }, + { + "t": 7, + "s": 0.7899 + }, + { + "t": 8, + "s": 0.7731 + }, + { + "t": 9, + "s": 0.7465 + }, + { + "t": 10, + "s": 0.7248 + }, + { + "t": 11, + "s": 0.7118 + }, + { + "t": 12, + "s": 0.6945 + }, + { + "t": 13, + "s": 0.6831 + }, + { + "t": 14, + "s": 0.6791 + }, + { + "t": 15, + "s": 0.6624 + }, + { + "t": 16, + "s": 0.645 + }, + { + "t": 17, + "s": 0.6221 + }, + { + "t": 18, + "s": 0.6022 + }, + { + "t": 19, + "s": 0.5913 + }, + { + "…": "15 more", + "sha256": "46d45753b050c9e7" + } + ], + "median": 29, + "n": 503, + "events": 169, + "atRisk": [ + 503, + 503, + 455, + 416, + 384, + 360, + 332, + 305, + 282, + 262, + 241, + 223, + 205, + 183, + 169, + 163, + 152, + 141, + 125, + 111, + { + "…": "47 more", + "sha256": "799034bdd6556105" + } + ], + "rmst": 17.37857096748266, + "se": 0.4281826922264003, + "lo": 16.539348311915518, + "hi": 18.2177936230498, + "atRiskTau": 66, + "beforeLandmark": 698 + }, + { + "cohort": "TMB ≥ 10", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 1, + "s": 0.9793 + }, + { + "t": 2, + "s": 0.9579 + }, + { + "t": 3, + "s": 0.9311 + }, + { + "t": 4, + "s": 0.9214 + }, + { + "t": 5, + "s": 0.9162 + }, + { + "t": 6, + "s": 0.9109 + }, + { + "t": 7, + "s": 0.8934 + }, + { + "t": 9, + "s": 0.8608 + }, + { + "t": 10, + "s": 0.8538 + }, + { + "t": 12, + "s": 0.8378 + }, + { + "t": 13, + "s": 0.8294 + }, + { + "t": 15, + "s": 0.8199 + }, + { + "t": 16, + "s": 0.8103 + }, + { + "t": 19, + "s": 0.7976 + }, + { + "t": 20, + "s": 0.7848 + }, + { + "t": 22, + "s": 0.7705 + }, + { + "t": 24, + "s": 0.7548 + }, + { + "t": 29, + "s": 0.7296 + }, + { + "t": 30, + "s": 0.7036 + }, + { + "…": "5 more", + "sha256": "18afc1b1450174c2" + } + ], + "median": 48, + "n": 241, + "events": 45, + "atRisk": [ + 241, + 241, + 229, + 215, + 191, + 179, + 170, + 157, + 147, + 137, + 122, + 117, + 107, + 99, + 90, + 88, + 85, + 74, + 70, + 64, + { + "…": "49 more", + "sha256": "99aea4bc9a5a0551" + } + ], + "rmst": 20.627592199096078, + "se": 0.5091485431665265, + "lo": 19.62967939170865, + "hi": 21.625505006483507, + "atRiskTau": 49, + "beforeLandmark": 219 + } + ], + "comparisons": [ + { + "cohort": "TMB ≥ 10", + "vs": "TMB < 10", + "diff": 3.249021231613419, + "diffLo": 1.9451329554313577, + "diffHi": 4.55290950779548, + "diffP": 0.000001040464399922658, + "ratio": 1.1869556039845117, + "ratioLo": 1.1085323397515772, + "ratioHi": 1.270926932222802, + "ratioP": 8.906268239316184e-7 + } + ] + } + ], + "cohorts": [ + { + "cohort": "TMB < 10", + "cohortPatients": 1201, + "n": 1201, + "noFollowup": 0, + "noAnchor": 0 + }, + { + "cohort": "TMB ≥ 10", + "cohortPatients": 460, + "n": 460, + "noFollowup": 0, + "noAnchor": 0 + } + ] + }, + "participation": { + "studies": [ + { + "study": "tmb_mskcc_2018", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Restricted mean survival time: the area under each cohort's Kaplan–Meier curve from 0 to τ — the mean months lived within the first τ — with τ = 24 months, as set. Its standard error is the Greenwood-type variance of that area (Klein & Moeschberger; R survRM2); differences and ratios against TMB < 10 use Wald intervals (the ratio on the log scale) and two-sided normal p. Unlike a hazard ratio, RMST needs no proportional hazards, so it stays interpretable when curves cross or separate late, as on immunotherapy. Endpoint OS from the study's day 0. Left out: 0 without follow-up. A landmark keeps only the patients alive and still followed at it and restarts their clock there, so each later panel answers “for those who got this far” and is free of the guarantee-time bias of grouping by what happens after the start; its patients are fewer, and its τ is counted from the landmark. Records are pooled across a cohort's studies without stratification. The cohorts are not randomised, so a difference is an association. Each site sends only (entry, time, event) counts." + } + ] +} diff --git a/test/snapshots/rmst.sql b/test/snapshots/rmst.sql new file mode 100644 index 0000000..6397df5 --- /dev/null +++ b/test/snapshots/rmst.sql @@ -0,0 +1,51 @@ +-- cohort TMB < 10 +-- tmb_mskcc_2018 +(SELECT DISTINCT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type = 'sample' + AND attribute_name = 'TMB_NONSYNONYMOUS' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) < 10) + +-- cohort TMB ≥ 10 +-- tmb_mskcc_2018 +(SELECT DISTINCT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type = 'sample' + AND attribute_name = 'TMB_NONSYNONYMOUS' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) >= 10) + +-- view 0 rmst: survivalCounts:OS · tmb_mskcc_2018 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type = 'sample' + AND attribute_name = 'TMB_NONSYNONYMOUS' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) < 10)), +os AS ( + SELECT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid, + anyIf(attribute_value, attribute_name='OS_MONTHS') AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT if(isFinite(ifNull(toFloat64OrNull(months), nan)), toFloat64OrNull(months), NULL) AS m, if(status LIKE '1%', 1, 0) AS ev, count() AS n +FROM os WHERE if(isFinite(ifNull(toFloat64OrNull(months), nan)), toFloat64OrNull(months), NULL) IS NOT NULL +GROUP BY m, ev ORDER BY m + +-- view 0 rmst: survivalCounts:OS · tmb_mskcc_2018 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type = 'sample' + AND attribute_name = 'TMB_NONSYNONYMOUS' AND if(isFinite(ifNull(toFloat64OrNull(attribute_value), nan)), toFloat64OrNull(attribute_value), NULL) >= 10)), +os AS ( + SELECT replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') AS pid, + anyIf(attribute_value, attribute_name='OS_MONTHS') AS months, + anyIf(attribute_value, attribute_name='OS_STATUS') AS status + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'tmb_mskcc_2018' AND type='patient' + AND attribute_name IN ('OS_MONTHS','OS_STATUS') + AND replaceOne(patient_unique_id, 'tmb_mskcc_2018_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +) +SELECT if(isFinite(ifNull(toFloat64OrNull(months), nan)), toFloat64OrNull(months), NULL) AS m, if(status LIKE '1%', 1, 0) AS ev, count() AS n +FROM os WHERE if(isFinite(ifNull(toFloat64OrNull(months), nan)), toFloat64OrNull(months), NULL) IS NOT NULL +GROUP BY m, ev ORDER BY m diff --git a/test/snapshots/rmst.svg b/test/snapshots/rmst.svg new file mode 100644 index 0000000..c2fa553 --- /dev/null +++ b/test/snapshots/rmst.svg @@ -0,0 +1,2 @@ + +Restricted mean survival time — OSFrom the start · RMST to τ = 24 months0%25%50%75%100%Probability alive01224364860Months from the study's day 0τ = 24Number at riskTMB < 10TMB < 10: 1,201 at risk at 0 months1,201TMB < 10: 534 at risk at 12 months534TMB < 10: 205 at risk at 24 months205TMB < 10: 66 at risk at 36 months66TMB < 10: 26 at risk at 48 months26TMB < 10: 5 at risk at 60 months5TMB ≥ 10TMB ≥ 10: 460 at risk at 0 months460TMB ≥ 10: 252 at risk at 12 months252TMB ≥ 10: 107 at risk at 24 months107TMB ≥ 10: 49 at risk at 36 months49TMB ≥ 10: 15 at risk at 48 months15TMB ≥ 10: 10 at risk at 60 months10TMB < 10RMST 14.5 mo (13.9–15) · n = 1,201TMB ≥ 10RMST 17.7 mo (16.9–18.6) · n = 460From the 6-month landmark · RMST to τ = 24 months0%25%50%75%100%Probability alive01224364860Months from the landmarkτ = 24Number at riskTMB < 10TMB < 10: 758 at risk at 0 months758TMB < 10: 332 at risk at 12 months332TMB < 10: 125 at risk at 24 months125TMB < 10: 44 at risk at 36 months44TMB < 10: 15 at risk at 48 months15TMB < 10: 4 at risk at 60 months4TMB ≥ 10TMB ≥ 10: 321 at risk at 0 months321TMB ≥ 10: 170 at risk at 12 months170TMB ≥ 10: 70 at risk at 24 months70TMB ≥ 10: 28 at risk at 36 months28TMB ≥ 10: 12 at risk at 48 months12TMB ≥ 10: 7 at risk at 60 months7TMB < 10RMST 16 mo (15.3–16.7) · n = 758TMB ≥ 10RMST 19.6 mo (18.7–20.5) · n = 321From the 12-month landmark · RMST to τ = 24 months0%25%50%75%100%Probability alive012243648Months from the landmarkτ = 24Number at riskTMB < 10TMB < 10: 503 at risk at 0 months503TMB < 10: 205 at risk at 12 months205TMB < 10: 66 at risk at 24 months66TMB < 10: 26 at risk at 36 months26TMB < 10: 5 at risk at 48 months5TMB ≥ 10TMB ≥ 10: 241 at risk at 0 months241TMB ≥ 10: 107 at risk at 12 months107TMB ≥ 10: 49 at risk at 24 months49TMB ≥ 10: 15 at risk at 36 months15TMB ≥ 10: 10 at risk at 48 months10TMB < 10RMST 17.4 mo (16.5–18.2) · n = 503TMB ≥ 10RMST 20.6 mo (19.6–21.6) · n = 241From the start · RMST to τ = 24 monthscohortnRMST (95% CI)difference (95% CI)ratio (95% CI)TMB < 101,20114.5 mo (13.9–15)referenceTMB ≥ 1046017.7 mo (16.9–18.6)+3.3 mo (2.3 to 4.2) p = 1.1e-101.23 (1.15–1.30)From the 6-month landmark · RMST to τ = 24 monthscohortnRMST (95% CI)difference (95% CI)ratio (95% CI)TMB < 1075816 mo (15.3–16.7)referenceTMB ≥ 1032119.6 mo (18.7–20.5)+3.6 mo (2.5 to 4.7) p = 4.1e-101.23 (1.15–1.31)From the 12-month landmark · RMST to τ = 24 monthscohortnRMST (95% CI)difference (95% CI)ratio (95% CI)TMB < 1050317.4 mo (16.5–18.2)referenceTMB ≥ 1024120.6 mo (19.6–21.6)+3.2 mo (1.9 to 4.6) p = 1e-61.19 (1.11–1.27)Restricted mean survival time: the area under each cohort's Kaplan–Meier curve from 0 to τ — the mean months lived within the first τ — with τ = 24 months, as set.Its standard error is the Greenwood-type variance of that area (Klein & Moeschberger; R survRM2); differences and ratios against TMB < 10 use Wald intervals (theratio on the log scale) and two-sided normal p. Unlike a hazard ratio, RMST needs no proportional hazards, so it stays interpretable when curves cross or separatelate, as on immunotherapy. Endpoint OS from the study's day 0. Left out: 0 without follow-up. A landmark keeps only the patients alive and still followed at it andrestarts their clock there, so each later panel answers “for those who got this far” and is free of the guarantee-time bias of grouping by what happens after the start;its patients are fewer, and its τ is counted from the landmark. Records are pooled across a cohort's studies without stratification. The cohorts are not randomised, soa difference is an association. Each site sends only (entry, time, event) counts. diff --git a/test/snapshots/rmst.txt b/test/snapshots/rmst.txt new file mode 100644 index 0000000..b72a685 --- /dev/null +++ b/test/snapshots/rmst.txt @@ -0,0 +1,17 @@ +Analysis over tmb_mskcc_2018 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- TMB < 10: 1,201 patients — TMB_NONSYNONYMOUS lt 10 (any sample) +- TMB ≥ 10: 460 patients — TMB_NONSYNONYMOUS gte 10 (any sample) + +Restricted mean survival time, OS: + from the start, tau=24 months: + TMB < 10: n=1,201, RMST 14.5 mo (13.9–15) + TMB ≥ 10: n=460, RMST 17.7 mo (16.9–18.6); vs TMB < 10 difference +3.3 mo (2.3 to 4.2) p = 1.1e-10, ratio 1.23 (1.15–1.30) + from the 6-month landmark, tau=24 months: + TMB < 10: n=758, RMST 16 mo (15.3–16.7) + TMB ≥ 10: n=321, RMST 19.6 mo (18.7–20.5); vs TMB < 10 difference +3.6 mo (2.5 to 4.7) p = 4.1e-10, ratio 1.23 (1.15–1.31) + from the 12-month landmark, tau=24 months: + TMB < 10: n=503, RMST 17.4 mo (16.5–18.2) + TMB ≥ 10: n=241, RMST 20.6 mo (19.6–21.6); vs TMB < 10 difference +3.2 mo (1.9 to 4.6) p = 1e-6, ratio 1.19 (1.11–1.27) + CAVEAT (tell the user): Restricted mean survival time: the area under each cohort's Kaplan–Meier curve from 0 to τ — the mean months lived within the first τ — with τ = 24 months, as set. Its standard error is the Greenwood-type variance of that area (Klein & Moeschberger; R survRM2); differences and ratios against TMB < 10 use Wald intervals (the ratio on the log scale) and two-sided normal p. Unlike a hazard ratio, RMST needs no proportional hazards, so it stays interpretable when curves cross or separate late, as on immunotherapy. Endpoint OS from the study's day 0. Left out: 0 without follow-up. A landmark keeps only the patients alive and still followed at it and restarts their clock there, so each later panel answers “for those who got this far” and is free of the guarantee-time bias of grouping by what happens after the start; its patients are fewer, and its τ is counted from the landmark. Records are pooled across a cohort's studies without stratification. The cohorts are not randomised, so a difference is an association. Each site sends only (entry, time, event) counts. diff --git a/test/snapshots/txendpoints.json b/test/snapshots/txendpoints.json new file mode 100644 index 0000000..0c4028b --- /dev/null +++ b/test/snapshots/txendpoints.json @@ -0,0 +1,331 @@ +{ + "study": "msk_chord_2024", + "cohorts": { + "NSCLC, KRAS mutant": { + "count": 2194, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) AND OQL[KRAS: MUT]", + "studies": [ + "msk_chord_2024" + ] + }, + "NSCLC, KRAS wild-type": { + "count": 5540, + "text": "CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) AND NOT OQL[KRAS:] AND profiled for KRAS MUTATION", + "studies": [ + "msk_chord_2024" + ] + } + }, + "views": [ + { + "kind": "txendpoints", + "cohorts": [ + "NSCLC, KRAS mutant", + "NSCLC, KRAS wild-type" + ], + "txendpoints": { + "endpoint": "rwpfs", + "endpointLabel": "real-world progression-free survival", + "anchoredTo": "first Treatment [SUBTYPE=Immuno]", + "lineGap": 90, + "lineClasses": "systemic", + "progression": { + "eventType": "Diagnosis", + "key": "PROGRESSION", + "values": [ + "Y", + "Yes" + ] + }, + "curves": [ + { + "cohort": "NSCLC, KRAS mutant", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.0329, + "s": 0.9945 + }, + { + "t": 0.0657, + "s": 0.9918 + }, + { + "t": 0.0986, + "s": 0.9892 + }, + { + "t": 0.1314, + "s": 0.9825 + }, + { + "t": 0.1643, + "s": 0.976 + }, + { + "t": 0.1971, + "s": 0.9694 + }, + { + "t": 0.23, + "s": 0.9669 + }, + { + "t": 0.2305, + "s": 0.9656 + }, + { + "t": 0.2628, + "s": 0.9579 + }, + { + "t": 0.2957, + "s": 0.9567 + }, + { + "t": 0.3285, + "s": 0.9503 + }, + { + "t": 0.3291, + "s": 0.949 + }, + { + "t": 0.3614, + "s": 0.9452 + }, + { + "t": 0.3615, + "s": 0.944 + }, + { + "t": 0.3943, + "s": 0.9427 + }, + { + "t": 0.3944, + "s": 0.9402 + }, + { + "t": 0.4271, + "s": 0.9327 + }, + { + "t": 0.46, + "s": 0.9239 + }, + { + "t": 0.4928, + "s": 0.9214 + }, + { + "…": "374 more", + "sha256": "4439836b9491bd7a" + } + ], + "median": 2.5626, + "n": 873, + "events": 778, + "atRisk": [ + 733, + 681, + 488, + 378, + 345, + 302, + 266, + 240, + 214, + 200, + 187, + 170, + 156, + 149, + 139, + 125, + 113, + 101, + 98, + 93, + { + "…": "53 more", + "sha256": "363ed24ee932fafc" + } + ], + "unobserved": 108, + "medianLo": 2.2341, + "medianHi": 2.7269, + "cohortPatients": 2194, + "anchored": 981, + "excluded": { + "noAnchor": 1213, + "noFollowup": 0, + "noStop": 0 + }, + "late": 0 + }, + { + "cohort": "NSCLC, KRAS wild-type", + "points": [ + { + "t": 0, + "s": 1 + }, + { + "t": 0.0329, + "s": 0.996 + }, + { + "t": 0.0345, + "s": 0.9952 + }, + { + "t": 0.0464, + "s": 0.9944 + }, + { + "t": 0.0657, + "s": 0.9912 + }, + { + "t": 0.0986, + "s": 0.9865 + }, + { + "t": 0.0992, + "s": 0.9857 + }, + { + "t": 0.1106, + "s": 0.9849 + }, + { + "t": 0.1314, + "s": 0.9818 + }, + { + "t": 0.1643, + "s": 0.9772 + }, + { + "t": 0.1733, + "s": 0.9764 + }, + { + "t": 0.1971, + "s": 0.9741 + }, + { + "t": 0.2001, + "s": 0.9733 + }, + { + "t": 0.2216, + "s": 0.9725 + }, + { + "t": 0.23, + "s": 0.9634 + }, + { + "t": 0.2348, + "s": 0.9626 + }, + { + "t": 0.2628, + "s": 0.9565 + }, + { + "t": 0.2957, + "s": 0.9497 + }, + { + "t": 0.2959, + "s": 0.949 + }, + { + "t": 0.31, + "s": 0.9482 + }, + { + "…": "563 more", + "sha256": "99684fea547c555d" + } + ], + "median": 2.5955, + "n": 1476, + "events": 1305, + "atRisk": [ + 1245, + 1149, + 843, + 667, + 587, + 519, + 463, + 402, + 367, + 329, + 298, + 272, + 245, + 225, + 205, + 188, + 172, + 160, + 152, + 142, + { + "…": "67 more", + "sha256": "d9c68266bfee51fb" + } + ], + "unobserved": 175, + "medianLo": 2.4312, + "medianHi": 2.7598, + "cohortPatients": 5540, + "anchored": 1651, + "excluded": { + "noAnchor": 3889, + "noFollowup": 0, + "noStop": 0 + }, + "late": 0 + } + ], + "logrank": { + "chi2": 0.0009, + "p": 0.9764189024658432, + "df": 1, + "observed": [ + 778, + 1305 + ], + "expected": [ + 778.7, + 1304.3 + ], + "groups": 2, + "strata": 1 + } + }, + "participation": { + "studies": [ + { + "study": "msk_chord_2024", + "mode": "patient-level", + "instance": "primary", + "source": 0 + } + ], + "requiresPatientLevel": false, + "aggregateOnly": [] + }, + "caveat": "Real-world progression-free survival: from the start of the first Treatment [SUBTYPE=Immuno] record to the first Diagnosis event after it with PROGRESSION in Y / Yes, or death. Progression read from the record (in MSK-CHORD, from notes by NLP) is only as complete as that recording; events on the anchor's own day are not counted. Censored at the last follow-up, which is OS_MONTHS × 30.4375 days on the timeline's clock; death is OS_STATUS 1:DECEASED. That OS_MONTHS counts from the timeline's day 0 was checked on these patients: 0 of 2,632 anchored patients have a timeline event more than 31 days past it (a study above 5% is refused). Left truncation as in the survival view: a patient whose anchor falls before day 0 enters the risk set at day 0. Left out: 5102 cohort patients with no Treatment [SUBTYPE=Immuno] record, 0 with no follow-up after it, and 283 anchored before day 0 whose endpoint also came before it, so were never at risk. Medians with 95% intervals from the Greenwood band on the log scale. 2-group log-rank (1 df). Treated patients are not randomised: the cohorts can differ in who was offered the treatment, when, and how sick they were, so a difference here is an association. Each site sends only (entry, time, event) counts." + } + ] +} diff --git a/test/snapshots/txendpoints.sql b/test/snapshots/txendpoints.sql new file mode 100644 index 0000000..484ef5f --- /dev/null +++ b/test/snapshots/txendpoints.sql @@ -0,0 +1,185 @@ +-- cohort NSCLC, KRAS mutant +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) +INTERSECT DISTINCT +((SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_chord_2024' + AND g.hugo_gene_symbol = 'KRAS' + AND g.mutation_status != 'UNCALLED' AND g.variant_type = 'mutation')) + +-- cohort NSCLC, KRAS wild-type +-- msk_chord_2024 +(SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) +INTERSECT DISTINCT +((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_chord_2024') +EXCEPT DISTINCT +((SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_chord_2024' + AND g.hugo_gene_symbol = 'KRAS' + AND g.mutation_status != 'UNCALLED' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2)))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND (sp.gene_panel_id = 'WES' OR sp.gene_panel_id IN ( + SELECT gene_panel_id FROM snapshot_db.gene_panel_to_gene_derived WHERE gene = 'KRAS'))) + +-- view 0 txendpoints: txendpoints:rwpfs:NSCLC, KRAS mutant · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) +INTERSECT DISTINCT +((SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_chord_2024' + AND g.hugo_gene_symbol = 'KRAS' + AND g.mutation_status != 'UNCALLED' AND g.variant_type = 'mutation'))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +arec AS ( + SELECT p.stable_id AS pid, ce.clinical_event_id AS eid, ce.start_date AS s, ce.stop_date AS e + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Treatment')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('SUBTYPE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('Immuno'))) +), +anch AS (SELECT pid, min(s) AS a FROM arec GROUP BY pid), +lastday AS ( + SELECT p.stable_id AS pid, max(greatest(ce.start_date, coalesce(ce.stop_date, ce.start_date))) AS d + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +), +prog AS ( + SELECT p.stable_id AS pid, ce.start_date AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('PROGRESSION')) AND lowerUTF8(trimBoth(value)) IN (lowerUTF8(trimBoth('Y')), lowerUTF8(trimBoth('Yes')))) +), +x AS (SELECT prog.pid AS pid, min(prog.day) AS d, 0 AS nostop FROM prog INNER JOIN anch ON anch.pid = prog.pid WHERE prog.day > anch.a GROUP BY prog.pid), +per AS ( + SELECT anch.a AS a, os.months * 30.4375 AS fu, os.months IS NOT NULL AS has_os, os.dead AS dead, + x.pid != '' AS has_x, toFloat64(x.d) AS xd, x.nostop AS nostop, lastday.d AS ld + FROM anch + LEFT JOIN os ON os.pid = anch.pid + LEFT JOIN x ON x.pid = anch.pid + LEFT JOIN lastday ON lastday.pid = anch.pid +), +cls AS ( + SELECT multiIf(NOT has_os OR fu - a <= 0, 'nofollowup', + 'record') AS kind, + greatest(0, -a) / 30.4375 AS entry, + has_x AND xd <= fu AS hit, + if(hit, xd - a, fu - a) / 30.4375 AS tt, + if(hit, 1, if(dead, 1, 0)) AS ev, + ld > fu + 31 AS late + FROM per +) +SELECT kind, if(kind = 'record', round(entry, 4), 0) AS entry, if(kind = 'record', round(tt, 4), 0) AS t, + if(kind = 'record', ev, 0) AS ev, late, count() AS n +FROM cls GROUP BY kind, entry, t, ev, late ORDER BY t + +-- view 0 txendpoints: txendpoints:rwpfs:NSCLC, KRAS wild-type · msk_chord_2024 +WITH cohort AS ((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'sample' + AND attribute_name = 'CANCER_TYPE' AND lowerUTF8(trimBoth(attribute_value)) IN (lowerUTF8(trimBoth('Non-Small Cell Lung Cancer')))) +INTERSECT DISTINCT +((SELECT DISTINCT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid FROM snapshot_db.sample_derived + WHERE cancer_study_identifier = 'msk_chord_2024') +EXCEPT DISTINCT +((SELECT DISTINCT replaceOne(g.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.genomic_event_derived g + WHERE g.cancer_study_identifier = 'msk_chord_2024' + AND g.hugo_gene_symbol = 'KRAS' + AND g.mutation_status != 'UNCALLED' AND (g.variant_type != 'cna' OR abs(g.cna_alteration) >= 2)))) +INTERSECT DISTINCT +(SELECT DISTINCT replaceOne(sd.patient_unique_id, 'msk_chord_2024_', '') AS pid + FROM snapshot_db.sample_to_gene_panel_derived sp + JOIN snapshot_db.sample_derived sd ON sd.sample_unique_id = sp.sample_unique_id + WHERE sp.cancer_study_identifier = 'msk_chord_2024' AND sp.alteration_type = 'MUTATION_EXTENDED' + AND (sp.gene_panel_id = 'WES' OR sp.gene_panel_id IN ( + SELECT gene_panel_id FROM snapshot_db.gene_panel_to_gene_derived WHERE gene = 'KRAS')))), +os AS ( + SELECT replaceOne(patient_unique_id, 'msk_chord_2024_', '') AS pid, + if(isFinite(ifNull(toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), nan)), toFloat64OrNull(anyIf(attribute_value, attribute_name = 'OS_MONTHS')), NULL) AS months, + anyIf(attribute_value, attribute_name = 'OS_STATUS') LIKE '1%' AS dead + FROM snapshot_db.clinical_data_derived + WHERE cancer_study_identifier = 'msk_chord_2024' AND type = 'patient' AND attribute_name IN ('OS_MONTHS', 'OS_STATUS') + AND replaceOne(patient_unique_id, 'msk_chord_2024_', '') IN (SELECT pid FROM cohort) + GROUP BY pid +), +arec AS ( + SELECT p.stable_id AS pid, ce.clinical_event_id AS eid, ce.start_date AS s, ce.stop_date AS e + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Treatment')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('SUBTYPE')) AND lowerUTF8(trimBoth(value)) = lowerUTF8(trimBoth('Immuno'))) +), +anch AS (SELECT pid, min(s) AS a FROM arec GROUP BY pid), +lastday AS ( + SELECT p.stable_id AS pid, max(greatest(ce.start_date, coalesce(ce.stop_date, ce.start_date))) AS d + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + GROUP BY pid +), +prog AS ( + SELECT p.stable_id AS pid, ce.start_date AS day + FROM snapshot_db.clinical_event ce + JOIN snapshot_db.patient p ON p.internal_id = ce.patient_id + JOIN snapshot_db.cancer_study cs ON cs.cancer_study_id = p.cancer_study_id + WHERE cs.cancer_study_identifier = 'msk_chord_2024' AND p.stable_id IN (SELECT pid FROM cohort) + AND lowerUTF8(trimBoth(ce.event_type)) = lowerUTF8(trimBoth('Diagnosis')) + AND ce.clinical_event_id IN (SELECT clinical_event_id FROM snapshot_db.clinical_event_data WHERE lowerUTF8(trimBoth(key)) = lowerUTF8(trimBoth('PROGRESSION')) AND lowerUTF8(trimBoth(value)) IN (lowerUTF8(trimBoth('Y')), lowerUTF8(trimBoth('Yes')))) +), +x AS (SELECT prog.pid AS pid, min(prog.day) AS d, 0 AS nostop FROM prog INNER JOIN anch ON anch.pid = prog.pid WHERE prog.day > anch.a GROUP BY prog.pid), +per AS ( + SELECT anch.a AS a, os.months * 30.4375 AS fu, os.months IS NOT NULL AS has_os, os.dead AS dead, + x.pid != '' AS has_x, toFloat64(x.d) AS xd, x.nostop AS nostop, lastday.d AS ld + FROM anch + LEFT JOIN os ON os.pid = anch.pid + LEFT JOIN x ON x.pid = anch.pid + LEFT JOIN lastday ON lastday.pid = anch.pid +), +cls AS ( + SELECT multiIf(NOT has_os OR fu - a <= 0, 'nofollowup', + 'record') AS kind, + greatest(0, -a) / 30.4375 AS entry, + has_x AND xd <= fu AS hit, + if(hit, xd - a, fu - a) / 30.4375 AS tt, + if(hit, 1, if(dead, 1, 0)) AS ev, + ld > fu + 31 AS late + FROM per +) +SELECT kind, if(kind = 'record', round(entry, 4), 0) AS entry, if(kind = 'record', round(tt, 4), 0) AS t, + if(kind = 'record', ev, 0) AS ev, late, count() AS n +FROM cls GROUP BY kind, entry, t, ev, late ORDER BY t diff --git a/test/snapshots/txendpoints.svg b/test/snapshots/txendpoints.svg new file mode 100644 index 0000000..d7c7e00 --- /dev/null +++ b/test/snapshots/txendpoints.svg @@ -0,0 +1,2 @@ + +Treatment endpoints — real-world progression-free survival from first Treatment [SUBTYPE=Immuno]0%25%50%75%100%Probability free of the event0244872Months from first Treatment [SUBTYPE=Immuno]Number at riskNSCLC, KRAS mutantNSCLC, KRAS mutant: 733 at risk at 0 months733NSCLC, KRAS mutant: 67 at risk at 24 months67NSCLC, KRAS mutant: 21 at risk at 48 months21NSCLC, KRAS mutant: 2 at risk at 72 months2NSCLC, KRAS wild-typeNSCLC, KRAS wild-t…NSCLC, KRAS wild-type: 1,245 at risk at 0 months1,245NSCLC, KRAS wild-type: 100 at risk at 24 months100NSCLC, KRAS wild-type: 30 at risk at 48 months30NSCLC, KRAS wild-type: 7 at risk at 72 months7NSCLC, KRAS mutantmedian 2.6 mo (95% CI 2.2 mo–2.7 mo) · n = 873 · 778 eventsNSCLC, KRAS wild-typemedian 2.6 mo (95% CI 2.4 mo–2.8 mo) · n = 1,476 · 1,305 events2-group log-rank χ² = 0.0009 (1 df) · p = 0.98Real-world progression-free survival: from the start of the first Treatment [SUBTYPE=Immuno] record to the first Diagnosis event after it with PROGRESSIONin Y / Yes, or death. Progression read from the record (in MSK-CHORD, from notes by NLP) is only as complete as that recording; events on the anchor's ownday are not counted. Censored at the last follow-up, which is OS_MONTHS × 30.4375 days on the timeline's clock; death is OS_STATUS 1:DECEASED. ThatOS_MONTHS counts from the timeline's day 0 was checked on these patients: 0 of 2,632 anchored patients have a timeline event more than 31 days past it(a study above 5% is refused). Left truncation as in the survival view: a patient whose anchor falls before day 0 enters the risk set at day 0. Left out: 5102cohort patients with no Treatment [SUBTYPE=Immuno] record, 0 with no follow-up after it, and 283 anchored before day 0 whose endpoint also came beforeit, so were never at risk. Medians with 95% intervals from the Greenwood band on the log scale. 2-group log-rank (1 df). Treated patients are notrandomised: the cohorts can differ in who was offered the treatment, when, and how sick they were, so a difference here is an association. Each site sendsonly (entry, time, event) counts. diff --git a/test/snapshots/txendpoints.txt b/test/snapshots/txendpoints.txt new file mode 100644 index 0000000..a69b22e --- /dev/null +++ b/test/snapshots/txendpoints.txt @@ -0,0 +1,11 @@ +Analysis over msk_chord_2024 — OncoKB pin v7.5 — (time)ms. + +Cohorts: +- NSCLC, KRAS mutant: 2,194 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) AND OQL[KRAS: MUT] +- NSCLC, KRAS wild-type: 5,540 patients — CANCER_TYPE in [Non-Small Cell Lung Cancer] (any sample) AND NOT OQL[KRAS:] AND profiled for KRAS MUTATION + +Treatment endpoint: real-world progression-free survival from first Treatment [SUBTYPE=Immuno] (Kaplan-Meier): + NSCLC, KRAS mutant: n=873, 778 events, median 2.6 mo (95% CI 2.2 mo–2.7 mo); 981 of 2194 anchored; left out 1213 without the anchor, 0 without follow-up + NSCLC, KRAS wild-type: n=1,476, 1305 events, median 2.6 mo (95% CI 2.4 mo–2.8 mo); 1651 of 5540 anchored; left out 3889 without the anchor, 0 without follow-up + log-rank chi2=0.00 (1 df), p=0.9764 + CAVEAT (tell the user): Real-world progression-free survival: from the start of the first Treatment [SUBTYPE=Immuno] record to the first Diagnosis event after it with PROGRESSION in Y / Yes, or death. Progression read from the record (in MSK-CHORD, from notes by NLP) is only as complete as that recording; events on the anchor's own day are not counted. Censored at the last follow-up, which is OS_MONTHS × 30.4375 days on the timeline's clock; death is OS_STATUS 1:DECEASED. That OS_MONTHS counts from the timeline's day 0 was checked on these patients: 0 of 2,632 anchored patients have a timeline event more than 31 days past it (a study above 5% is refused). Left truncation as in the survival view: a patient whose anchor falls before day 0 enters the risk set at day 0. Left out: 5102 cohort patients with no Treatment [SUBTYPE=Immuno] record, 0 with no follow-up after it, and 283 anchored before day 0 whose endpoint also came before it, so were never at risk. Medians with 95% intervals from the Greenwood band on the log scale. 2-group log-rank (1 df). Treated patients are not randomised: the cohorts can differ in who was offered the treatment, when, and how sick they were, so a difference here is an association. Each site sends only (entry, time, event) counts. diff --git a/test/support/view-snapshots.ts b/test/support/view-snapshots.ts new file mode 100644 index 0000000..1f50ca5 --- /dev/null +++ b/test/support/view-snapshots.ts @@ -0,0 +1,260 @@ +// Every view kind's output, pinned: one gallery example per kind (and a few that combine studies) +// runs through the whole engine (cohort compiler, runner, statistics, figure, results text) with +// the warehouse replaced by answers recorded from it. Four files per case in test/snapshots/ are +// compared byte for byte: +// .sql each cohort's compiled SQL and each view's queries, by label and study +// .json what each view computed (its result, without timings or the queries above; a long +// array, or an object of more than 50 keys, as its first 20 items, then the rest's +// count and digest) +// .svg each view's figure; .txt the results text MCP clients and the AI tools read +// A change to shared code that moves another view's numbers, figure, text or queries fails here, +// named by the case. A changed query has no recorded answer, so it fails as "not recorded". +// Each case runs twice, its answers delivered in the order its queries were sent and then in the +// reverse, and the two runs must agree exactly: output that depends on which query finished first +// fails as such. (A view's +// queries are listed sorted in .sql: independent queries form a set, and the snapshot pins +// which are sent, not the order they happen to be sent in.) +// +// The cases run in four parallel test files, test/view-snapshots.test.ts and -2, -3, -4.test.ts. +// +// UPDATE_SNAPSHOTS=1 npm test output changed on purpose, queries did not: rewrite the four files +// RECORD_SNAPSHOTS=1 node --env-file=.env.local --test test/view-snapshots*.test.ts +// queries changed: re-ask the warehouse, then rewrite everything +// Recording reads the public warehouse only (the second instance is unset first); the answers are +// stored gzipped in test/snapshots/answers/, keyed by statement, under the database name below. +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { gunzipSync, gzipSync } from "node:zlib"; +import { join } from "node:path"; + +const RECORD = !!process.env.RECORD_SNAPSHOTS; +const UPDATE = RECORD || !!process.env.UPDATE_SNAPSHOTS; +/** the database name every snapshot is written under, whatever the recording ran against */ +const SNAPSHOT_DB = "snapshot_db"; +const realDb = (process.env.CLICKHOUSE_DB || "").replace(/[^A-Za-z0-9_]/g, ""); +if (RECORD) { + for (const k of Object.keys(process.env)) if (k.startsWith("CLICKHOUSE2_") || k === "LOOPBACK_PEER") delete process.env[k]; + if (!realDb) throw new Error("RECORD_SNAPSHOTS needs the warehouse: run with node --env-file=.env.local"); +} else { + process.env.CLICKHOUSE_DB = SNAPSHOT_DB; + for (const k of Object.keys(process.env)) if (k.startsWith("CLICKHOUSE2_")) delete process.env[k]; +} + +const { setChTransport } = await import("../../lib/ch.ts"); +const { EXAMPLES } = await import("../../lib/dsl-reference.ts"); +const { runAnalysis } = await import("../../lib/analysis.ts"); +const { withRequestCustomData } = await import("../../lib/custom-data-server.ts"); +const { viewToSvg } = await import("../../lib/view-svg.ts"); +const { analysisText } = await import("../../lib/result-text.ts"); +import type { AnalysisDoc, AnalysisResult } from "../../lib/types.ts"; + +/** + * One case per view kind: the smallest gallery example that draws it (the oncoprint is pinned by + * test/oncoprint-svg.test.ts), and one per option that changes what a view asks; then examples + * that combine studies, where a view adds up answers from several databases. + */ +const CASES: Record = { + compare: "A missing alteration type is disclosed, never a false negative", + survival: "A non-standard survival endpoint, named directly", + cox: "Cox with a gene-SET covariate: any of three genes, one term", + logistic: "Logistic regression: does APC status predict MSI-high status", + course: "The clinical course: who is still on treatment, and when", + sequence: "What comes after what: lines of therapy, and what ending a path means", + hotspots: "Lollipop plot: mutations by protein position", + plot: "Plot axes from the study's own profiles", + coexpression: "Co-expression from sums alone", + clinical: "Median/IQR and a rank-sum test, single-study only", + cooccurrence: "Co-occurrence and mutual exclusivity in NSCLC", + attrition: "Cohort attrition: which criterion removed whom", + genome: "Genome-wide copy number: colorectal against breast", + pathways: "Oncogenic signalling pathways: colorectal against pancreatic", + diffexpr: "Differential expression and enrichment", + paired: "Paired samples: what differs between a patient's primary and", + clonality: "Clonality, LOH and genome doubling from FACETS", + incidence: "Brain metastasis, with death as a competing risk", + labs: "PSA response to hormone therapy, by TP53 status", + riskmodel: "A survival risk model from mutated genes in lung cancer", + cancertypes: "Cancer types summary: RAS genes across 50,000 tumours", + overlap: "Cohort overlap: how four groups share their patients", + enrichment: "Alteration enrichment: what separates colorectal from pancreatic", + assays: "Mutational signatures: what UV light and tobacco leave behind", + composition: "Composition: T and NK cell states by mutational signature", + correlates: "Co-expression: the genes that rise and fall with ESR1", + table: "Clinical data: every pancreatic cancer sample, oldest patients first", + timeline: "A patient's timeline: treatments, diagnoses, samples and lab", + mutations: "KRAS codon 12/13 mutations in colorectal cancer", + alterations: "A patient's mutations, copy-number alterations and structural", + rmst: "Months of life gained on immunotherapy with a high tumour mutational b", + acquired: "Acquired under therapy: the sample before a treatment against the samp", + germline: "Germline carriers in metastatic prostate cancer, and their tumours' se", + hla: "HLA class I homozygosity and survival on checkpoint blockade in melano", + actionability: "Clinical actionability: OncoKB levels across 50,000 tumours, and prima", + molsubtype: "The endometrial classifier against TCGA's own molecular subtypes", + ctdna: "ctDNA against tissue: what a blood test sees of the tumour, and what d", + exclusivity: "Mutual exclusivity that accounts for each tumour's burden (DISCOVER)", + cutpoint: "A tumour mutational burden cut-point for survival on immunotherapy", + drivernegative: "Driver-negative lung cancer: what is mutated when no canonical driver", + instability: "Genomic instability by cancer type: aneuploidy score, FGA and arm-leve", + txendpoints: "Real-world PFS on immunotherapy in lung cancer, by KRAS status", + organotropism: "Brain metastasis in lung cancer by EGFR, from dated tumour sites", + multistate: "From progression to death in colorectal cancer, by stage", + adjusted: "Adjusted enrichment: early-onset colorectal cancer, adjusted for MSI a", + predictive: "Which mutations change survival on immunotherapy in lung cancer?", + forest: "Pan-cancer forest: TP53 mutation and survival in 30 cancer types", + "ctdna-chfilter": "ctDNA with clonal haematopoiesis filtered out: MSK-ACCESS plasma again", + "riskmodel-validateon": "A lung cancer risk model, validated on another study", + "two-studies-survival": "Study-stratified survival across two studies", + "two-studies-four-views": "Federated end to end: two studies, four views", + "two-studies-plot": "Point layers across two studies", + "pooled-paired": "Before and after treatment, pooled across studies", +}; + +const DIR = join(import.meta.dirname, "..", "snapshots"); +const ANSWERS = join(DIR, "answers"); +const toSnapshotDb = (s: string) => realDb ? s.replaceAll(realDb + ".", SNAPSHOT_DB + ".").replaceAll("'" + realDb + "'", "'" + SNAPSHOT_DB + "'") : s; + +/** a statement's key: the instance, the format, the text and every table sent with it */ +async function keyOf(st: { conn: { id: string }; format: string; query: string; externals: { name: string; structure: string; blob: Blob }[] }): Promise { + const h = createHash("sha256").update(st.conn.id + "\n" + st.format + "\n" + toSnapshotDb(st.query)); + for (const e of st.externals) h.update("\n" + e.name + "\n" + e.structure + "\n" + await e.blob.text()); + return h.digest("hex").slice(0, 32); +} + +/** a ClickHouse answer without its timing statistics, which differ on every run */ +function steady(text: string): string { + try { const j = JSON.parse(text); delete j.statistics; return JSON.stringify(j); } catch { return text; } +} + +/** + * A long array (or an object keyed by gene, by value, …) keeps its first items in full and stands in + * for the rest by their count and digest, so a change anywhere still fails the comparison while the + * file stays small enough to review. + */ +const KEEP = 20, KEEP_KEYS = 50; +function compact(x: unknown): unknown { + if (Array.isArray(x)) { + const head = x.slice(0, KEEP).map(compact); + if (x.length <= KEEP) return head; + return [...head, { "…": x.length - KEEP + " more", sha256: createHash("sha256").update(JSON.stringify(x.slice(KEEP))).digest("hex").slice(0, 16) }]; + } + if (x && typeof x === "object") { + const entries = Object.entries(x); + const head = entries.slice(0, entries.length > KEEP_KEYS ? KEEP : entries.length).map(([k, v]) => [k, compact(v)]); + if (entries.length > KEEP_KEYS) head.push(["…", { more: entries.length - KEEP, sha256: createHash("sha256").update(JSON.stringify(entries.slice(KEEP))).digest("hex").slice(0, 16) }]); + return Object.fromEntries(head); + } + return x; +} +const json = (x: unknown) => JSON.stringify(compact(x), null, 2) + "\n"; + +/** the four files of one case */ +function render(doc: AnalysisDoc, r: AnalysisResult): Record { + const sql: string[] = []; + for (const [name, c] of Object.entries(r.cohorts)) sql.push("-- cohort " + name + "\n" + toSnapshotDb(String((c as { sql?: string }).sql ?? ""))); + // a view's independent queries form a set (see this file's header) + r.views.forEach((v, i) => sql.push(...(v.queries || []).map((q) => + "-- view " + i + " " + v.kind + ": " + q.label + " · " + q.study + (q.remote ? " · remote" : "") + "\n" + toSnapshotDb(q.sql)).sort())); + const views = r.views.map(({ ms: _ms, renderMs: _r, queries: _q, method, ...v }) => + ({ ...v, ...(method ? { method: { ...method, python: undefined } } : {}) })); + const cohorts = Object.fromEntries(Object.entries(r.cohorts).map(([n, c]) => { + const { sql: _sql, ...rest } = c as { sql?: string }; + return [n, rest]; + })); + const svg = r.views.map((v, i) => "\n" + (viewToSvg(v, { theme: "light" }) ?? "(no figure)")).join("\n") + "\n"; + return { + sql: sql.join("\n\n") + "\n", + json: json({ study: r.study, cohorts, splits: r.splits, views }), + svg, + txt: analysisText(doc, r).join("\n\n").replace(/ — \d+ms\./, " — (time)ms.") + "\n", + }; +} + +if (UPDATE) mkdirSync(ANSWERS, { recursive: true }); + +/** + * Every case's recorded answers, pooled: what the engine asks once per process (which database + * holds a study, a study's profiles) is recorded by whichever case asked it first, so a case run + * alone finds it in another case's file. + */ +const POOL: Record = {}; +if (!RECORD) for (const name of Object.keys(CASES)) { + const f = join(ANSWERS, name + ".json.gz"); + if (existsSync(f)) Object.assign(POOL, JSON.parse(gunzipSync(readFileSync(f)).toString("utf8"))); +} + +/** + * One thread per statement while recording: ClickHouse otherwise adds floats across threads in + * whatever order they finish, and returns rows of a query without ORDER BY in no fixed order, so + * a re-recorded case would differ from the last recording where nothing changed. + */ +const RECORD_SETTINGS = "max_threads=1"; + +/** + * Delivery in a chosen order: answers that are waiting together are held for a moment, then + * released in the order their statements were sent ("sent") or the reverse ("reversed"). Two runs + * in opposite orders give any two concurrent answers opposite arrival orders, so output that depends + * on which arrived first differs between them, every time. + */ +type Delivery = "sent" | "reversed"; +let waiting: (() => void)[] = []; +function arrive(order: Delivery): Promise { + return new Promise((release) => { + waiting.push(release); + if (waiting.length === 1) setTimeout(() => { + const batch = order === "reversed" ? waiting.reverse() : waiting; + waiting = []; + for (const r of batch) r(); + }, 1); + }); +} + +/** run the example once: live while recording, else from the pool, delivered in `order` */ +async function runCase(ex: (typeof EXAMPLES)[number], recorded: Record, order: Delivery | null): Promise { + setChTransport(async (st, send) => { + const key = await keyOf(st); + if (RECORD) return (recorded[key] = toSnapshotDb(steady(await send(RECORD_SETTINGS)))); + if (!(key in POOL)) throw new Error("not recorded (a query changed; RECORD_SNAPSHOTS=1, see this file's header):\n" + toSnapshotDb(st.query).slice(0, 2000)); + if (order) await arrive(order); + return POOL[key]; + }); + try { return await withRequestCustomData(ex.customData, () => runAnalysis(ex.doc as unknown as AnalysisDoc, { oncoprint: "spec" })); } + finally { setChTransport(null); } +} + +/** One shard's tests, as [name, test] pairs: every `of`-th case from `shard` (1-based), in CASES order. */ +export function snapshotCases(shard: number, of: number): [string, () => Promise][] { + return Object.entries(CASES).filter((_, i) => i % of === shard - 1).map(([kind, title]) => ["snapshot: " + kind, async () => { + const ex = EXAMPLES.find((e) => e.title.startsWith(title)); + assert.ok(ex, "no gallery example titled “" + title + "…”"); + const answersFile = join(ANSWERS, kind + ".json.gz"); + const recorded: Record = {}; + const result = await runCase(ex, recorded, RECORD ? null : "sent"); + const failed = result.views.filter((v) => v.error && /not recorded/.test(v.error)); + assert.deepEqual(failed.map((v) => v.error), [], "a view asked something the recording never answered"); + if (RECORD) writeFileSync(answersFile, gzipSync(JSON.stringify(Object.fromEntries(Object.entries(recorded).sort())), { level: 9 })); + const out = render(ex.doc as unknown as AnalysisDoc, result); + if (!RECORD) { + const again = render(ex.doc as unknown as AnalysisDoc, await runCase(ex, recorded, "reversed")); + for (const ext of Object.keys(out)) + assert.equal(again[ext], out[ext], kind + "." + ext + " depends on the order database answers arrive: combine a view's answers " + + "after they are all in, in a fixed order (askEachSite in lib/views/shared.ts), not as each one lands"); + } + for (const [ext, text] of Object.entries(out)) { + const file = join(DIR, kind + "." + ext); + if (UPDATE) { writeFileSync(file, text); continue; } + assert.ok(existsSync(file), file + " is missing — RECORD_SNAPSHOTS=1, see this file's header"); + assert.equal(text, readFileSync(file, "utf8"), kind + "." + ext + " changed — review the diff; if intended, UPDATE_SNAPSHOTS=1 npm test"); + } + }]); +} + +/** That CASES covers every view kind; the first shard runs it. */ +export function everyKindHasACase(): void { + const types = readFileSync(join(import.meta.dirname, "..", "..", "lib", "types.ts"), "utf8"); + const m = /export type View = ([^;]+);/.exec(types); + assert.ok(m, "could not find the View union in lib/types.ts — did it move?"); + const kinds = [...new Set([...m[1].matchAll(/(\w+)View/g)].map((x) => x[1].toLowerCase()))]; + assert.deepEqual(kinds.filter((k) => k !== "oncoprint" && !(k in CASES)), [], + "add a case for each to CASES above (its smallest gallery example), then RECORD_SNAPSHOTS=1"); +} diff --git a/test/view-snapshots-2.test.ts b/test/view-snapshots-2.test.ts new file mode 100644 index 0000000..fb4347e --- /dev/null +++ b/test/view-snapshots-2.test.ts @@ -0,0 +1,5 @@ +// View snapshots, shard 2 of 4 (test/support/view-snapshots.ts). +import { test } from "node:test"; +import { snapshotCases } from "./support/view-snapshots.ts"; + +for (const [name, run] of snapshotCases(2, 4)) test(name, run); diff --git a/test/view-snapshots-3.test.ts b/test/view-snapshots-3.test.ts new file mode 100644 index 0000000..afbe79f --- /dev/null +++ b/test/view-snapshots-3.test.ts @@ -0,0 +1,5 @@ +// View snapshots, shard 3 of 4 (test/support/view-snapshots.ts). +import { test } from "node:test"; +import { snapshotCases } from "./support/view-snapshots.ts"; + +for (const [name, run] of snapshotCases(3, 4)) test(name, run); diff --git a/test/view-snapshots-4.test.ts b/test/view-snapshots-4.test.ts new file mode 100644 index 0000000..eed8192 --- /dev/null +++ b/test/view-snapshots-4.test.ts @@ -0,0 +1,5 @@ +// View snapshots, shard 4 of 4 (test/support/view-snapshots.ts). +import { test } from "node:test"; +import { snapshotCases } from "./support/view-snapshots.ts"; + +for (const [name, run] of snapshotCases(4, 4)) test(name, run); diff --git a/test/view-snapshots.test.ts b/test/view-snapshots.test.ts index 77e3809..baf5408 100644 --- a/test/view-snapshots.test.ts +++ b/test/view-snapshots.test.ts @@ -1,229 +1,7 @@ -// Every view kind's output, pinned: one gallery example per kind (and a few that combine studies) -// runs through the whole engine (cohort compiler, runner, statistics, figure, results text) with -// the warehouse replaced by answers recorded from it. Four files per case in test/snapshots/ are -// compared byte for byte: -// .sql each cohort's compiled SQL and each view's queries, by label and study -// .json what each view computed (its result, without timings or the queries above; a long -// array, or an object of more than 50 keys, as its first 20 items, then the rest's -// count and digest) -// .svg each view's figure; .txt the results text MCP clients and the AI tools read -// A change to shared code that moves another view's numbers, figure, text or queries fails here, -// named by the case. A changed query has no recorded answer, so it fails as "not recorded". -// Each case runs twice, its answers delivered in the order its queries were sent and then in the -// reverse, and the two runs must agree exactly: output that depends on which query finished first -// fails as such. (A view's -// queries are listed sorted in .sql: independent queries form a set, and the snapshot pins -// which are sent, not the order they happen to be sent in.) -// -// UPDATE_SNAPSHOTS=1 npm test output changed on purpose, queries did not: rewrite the four files -// RECORD_SNAPSHOTS=1 node --env-file=.env.local --test test/view-snapshots.test.ts -// queries changed: re-ask the warehouse, then rewrite everything -// Recording reads the public warehouse only (the second instance is unset first); the answers are -// stored gzipped in test/snapshots/answers/, keyed by statement, under the database name below. +// View snapshots, shard 1 of 4, and the check that every view kind has a case; the harness and its +// documentation are in test/support/view-snapshots.ts. import { test } from "node:test"; -import assert from "node:assert/strict"; -import { createHash } from "node:crypto"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { gunzipSync, gzipSync } from "node:zlib"; -import { join } from "node:path"; +import { snapshotCases, everyKindHasACase } from "./support/view-snapshots.ts"; -const RECORD = !!process.env.RECORD_SNAPSHOTS; -const UPDATE = RECORD || !!process.env.UPDATE_SNAPSHOTS; -/** the database name every snapshot is written under, whatever the recording ran against */ -const SNAPSHOT_DB = "snapshot_db"; -const realDb = (process.env.CLICKHOUSE_DB || "").replace(/[^A-Za-z0-9_]/g, ""); -if (RECORD) { - for (const k of Object.keys(process.env)) if (k.startsWith("CLICKHOUSE2_") || k === "LOOPBACK_PEER") delete process.env[k]; - if (!realDb) throw new Error("RECORD_SNAPSHOTS needs the warehouse: run with node --env-file=.env.local"); -} else { - process.env.CLICKHOUSE_DB = SNAPSHOT_DB; - for (const k of Object.keys(process.env)) if (k.startsWith("CLICKHOUSE2_")) delete process.env[k]; -} - -const { setChTransport } = await import("../lib/ch.ts"); -const { EXAMPLES } = await import("../lib/dsl-reference.ts"); -const { runAnalysis } = await import("../lib/analysis.ts"); -const { withRequestCustomData } = await import("../lib/custom-data-server.ts"); -const { viewToSvg } = await import("../lib/view-svg.ts"); -const { analysisText } = await import("../lib/result-text.ts"); -import type { AnalysisDoc, AnalysisResult } from "../lib/types.ts"; - -/** - * One case per view kind: the smallest gallery example that draws it (the oncoprint is pinned by - * test/oncoprint-svg.test.ts); then examples that combine studies, where a view adds up answers - * from several databases. - */ -const CASES: Record = { - compare: "A missing alteration type is disclosed, never a false negative", - survival: "A non-standard survival endpoint, named directly", - cox: "Cox with a gene-SET covariate: any of three genes, one term", - logistic: "Logistic regression: does APC status predict MSI-high status", - course: "The clinical course: who is still on treatment, and when", - sequence: "What comes after what: lines of therapy, and what ending a path means", - hotspots: "Lollipop plot: mutations by protein position", - plot: "Plot axes from the study's own profiles", - coexpression: "Co-expression from sums alone", - clinical: "Median/IQR and a rank-sum test, single-study only", - cooccurrence: "Co-occurrence and mutual exclusivity in NSCLC", - attrition: "Cohort attrition: which criterion removed whom", - genome: "Genome-wide copy number: colorectal against breast", - pathways: "Oncogenic signalling pathways: colorectal against pancreatic", - diffexpr: "Differential expression and enrichment", - paired: "Paired samples: what differs between a patient's primary and", - clonality: "Clonality, LOH and genome doubling from FACETS", - incidence: "Brain metastasis, with death as a competing risk", - labs: "PSA response to hormone therapy, by TP53 status", - riskmodel: "A survival risk model from mutated genes in lung cancer", - cancertypes: "Cancer types summary: RAS genes across 50,000 tumours", - overlap: "Cohort overlap: how four groups share their patients", - enrichment: "Alteration enrichment: what separates colorectal from pancreatic", - assays: "Mutational signatures: what UV light and tobacco leave behind", - composition: "Composition: T and NK cell states by mutational signature", - correlates: "Co-expression: the genes that rise and fall with ESR1", - table: "Clinical data: every pancreatic cancer sample, oldest patients first", - timeline: "A patient's timeline: treatments, diagnoses, samples and lab", - mutations: "KRAS codon 12/13 mutations in colorectal cancer", - alterations: "A patient's mutations, copy-number alterations and structural", - "two-studies-survival": "Study-stratified survival across two studies", - "two-studies-four-views": "Federated end to end: two studies, four views", - "two-studies-plot": "Point layers across two studies", - "pooled-paired": "Before and after treatment, pooled across studies", -}; - -const DIR = join(import.meta.dirname, "snapshots"); -const ANSWERS = join(DIR, "answers"); -const toSnapshotDb = (s: string) => realDb ? s.replaceAll(realDb + ".", SNAPSHOT_DB + ".").replaceAll("'" + realDb + "'", "'" + SNAPSHOT_DB + "'") : s; - -/** a statement's key: the instance, the format, the text and every table sent with it */ -async function keyOf(st: { conn: { id: string }; format: string; query: string; externals: { name: string; structure: string; blob: Blob }[] }): Promise { - const h = createHash("sha256").update(st.conn.id + "\n" + st.format + "\n" + toSnapshotDb(st.query)); - for (const e of st.externals) h.update("\n" + e.name + "\n" + e.structure + "\n" + await e.blob.text()); - return h.digest("hex").slice(0, 32); -} - -/** a ClickHouse answer without its timing statistics, which differ on every run */ -function steady(text: string): string { - try { const j = JSON.parse(text); delete j.statistics; return JSON.stringify(j); } catch { return text; } -} - -/** - * A long array (or an object keyed by gene, by value, …) keeps its first items in full and stands in - * for the rest by their count and digest, so a change anywhere still fails the comparison while the - * file stays small enough to review. - */ -const KEEP = 20, KEEP_KEYS = 50; -function compact(x: unknown): unknown { - if (Array.isArray(x)) { - const head = x.slice(0, KEEP).map(compact); - if (x.length <= KEEP) return head; - return [...head, { "…": x.length - KEEP + " more", sha256: createHash("sha256").update(JSON.stringify(x.slice(KEEP))).digest("hex").slice(0, 16) }]; - } - if (x && typeof x === "object") { - const entries = Object.entries(x); - const head = entries.slice(0, entries.length > KEEP_KEYS ? KEEP : entries.length).map(([k, v]) => [k, compact(v)]); - if (entries.length > KEEP_KEYS) head.push(["…", { more: entries.length - KEEP, sha256: createHash("sha256").update(JSON.stringify(entries.slice(KEEP))).digest("hex").slice(0, 16) }]); - return Object.fromEntries(head); - } - return x; -} -const json = (x: unknown) => JSON.stringify(compact(x), null, 2) + "\n"; - -/** the four files of one case */ -function render(doc: AnalysisDoc, r: AnalysisResult): Record { - const sql: string[] = []; - for (const [name, c] of Object.entries(r.cohorts)) sql.push("-- cohort " + name + "\n" + toSnapshotDb(String((c as { sql?: string }).sql ?? ""))); - // a view's independent queries form a set (see this file's header) - r.views.forEach((v, i) => sql.push(...(v.queries || []).map((q) => - "-- view " + i + " " + v.kind + ": " + q.label + " · " + q.study + (q.remote ? " · remote" : "") + "\n" + toSnapshotDb(q.sql)).sort())); - const views = r.views.map(({ ms: _ms, renderMs: _r, queries: _q, method, ...v }) => - ({ ...v, ...(method ? { method: { ...method, python: undefined } } : {}) })); - const cohorts = Object.fromEntries(Object.entries(r.cohorts).map(([n, c]) => { - const { sql: _sql, ...rest } = c as { sql?: string }; - return [n, rest]; - })); - const svg = r.views.map((v, i) => "\n" + (viewToSvg(v, { theme: "light" }) ?? "(no figure)")).join("\n") + "\n"; - return { - sql: sql.join("\n\n") + "\n", - json: json({ study: r.study, cohorts, splits: r.splits, views }), - svg, - txt: analysisText(doc, r).join("\n\n").replace(/ — \d+ms\./, " — (time)ms.") + "\n", - }; -} - -if (UPDATE) mkdirSync(ANSWERS, { recursive: true }); - -/** - * Every case's recorded answers, pooled: what the engine asks once per process (which database - * holds a study, a study's profiles) is recorded by whichever case asked it first, so a case run - * alone finds it in another case's file. - */ -const POOL: Record = {}; -if (!RECORD) for (const name of Object.keys(CASES)) { - const f = join(ANSWERS, name + ".json.gz"); - if (existsSync(f)) Object.assign(POOL, JSON.parse(gunzipSync(readFileSync(f)).toString("utf8"))); -} - -/** - * One thread per statement while recording: ClickHouse otherwise adds floats across threads in - * whatever order they finish, and returns rows of a query without ORDER BY in no fixed order, so - * a re-recorded case would differ from the last recording where nothing changed. - */ -const RECORD_SETTINGS = "max_threads=1"; - -/** - * Delivery in a chosen order: answers that are waiting together are held for a moment, then - * released in the order their statements were sent ("sent") or the reverse ("reversed"). Two runs - * in opposite orders give any two concurrent answers opposite arrival orders, so output that depends - * on which arrived first differs between them, every time. - */ -type Delivery = "sent" | "reversed"; -let waiting: (() => void)[] = []; -function arrive(order: Delivery): Promise { - return new Promise((release) => { - waiting.push(release); - if (waiting.length === 1) setTimeout(() => { - const batch = order === "reversed" ? waiting.reverse() : waiting; - waiting = []; - for (const r of batch) r(); - }, 1); - }); -} - -/** run the example once: live while recording, else from the pool, delivered in `order` */ -async function runCase(ex: (typeof EXAMPLES)[number], recorded: Record, order: Delivery | null): Promise { - setChTransport(async (st, send) => { - const key = await keyOf(st); - if (RECORD) return (recorded[key] = toSnapshotDb(steady(await send(RECORD_SETTINGS)))); - if (!(key in POOL)) throw new Error("not recorded (a query changed; RECORD_SNAPSHOTS=1, see this file's header):\n" + toSnapshotDb(st.query).slice(0, 2000)); - if (order) await arrive(order); - return POOL[key]; - }); - try { return await withRequestCustomData(ex.customData, () => runAnalysis(ex.doc as unknown as AnalysisDoc, { oncoprint: "spec" })); } - finally { setChTransport(null); } -} - -for (const [kind, title] of Object.entries(CASES)) { - test("snapshot: " + kind, async () => { - const ex = EXAMPLES.find((e) => e.title.startsWith(title)); - assert.ok(ex, "no gallery example titled “" + title + "…”"); - const answersFile = join(ANSWERS, kind + ".json.gz"); - const recorded: Record = {}; - const result = await runCase(ex, recorded, RECORD ? null : "sent"); - const failed = result.views.filter((v) => v.error && /not recorded/.test(v.error)); - assert.deepEqual(failed.map((v) => v.error), [], "a view asked something the recording never answered"); - if (RECORD) writeFileSync(answersFile, gzipSync(JSON.stringify(Object.fromEntries(Object.entries(recorded).sort())), { level: 9 })); - const out = render(ex.doc as unknown as AnalysisDoc, result); - if (!RECORD) { - const again = render(ex.doc as unknown as AnalysisDoc, await runCase(ex, recorded, "reversed")); - for (const ext of Object.keys(out)) - assert.equal(again[ext], out[ext], kind + "." + ext + " depends on the order database answers arrive: combine a view's answers " + - "after they are all in, in a fixed order (askEachSite in lib/views/shared.ts), not as each one lands"); - } - for (const [ext, text] of Object.entries(out)) { - const file = join(DIR, kind + "." + ext); - if (UPDATE) { writeFileSync(file, text); continue; } - assert.ok(existsSync(file), file + " is missing — RECORD_SNAPSHOTS=1, see this file's header"); - assert.equal(text, readFileSync(file, "utf8"), kind + "." + ext + " changed — review the diff; if intended, UPDATE_SNAPSHOTS=1 npm test"); - } - }); -} +for (const [name, run] of snapshotCases(1, 4)) test(name, run); +test("every view kind has a snapshot case", everyKindHasACase);