Mutation data with cBioPortal

Before trying to predict a mutation from H&E: prevalence, co-occurrence, survival and the slide-level label file

cBioPortal web interface and API: EGFR/KRAS/TP53/STK11 mutation frequency, mutual exclusivity and survival in TCGA lung adenocarcinoma; from samples to diagnostic slides and a label matrix for MIL.

⏱ Time: 60 min (part A without code 25 min; B–D in Python 35 min) 🧩 Prerequisites: a browser for A; Python 3.10+, requests, pandas, scipy, lifelines for B–D 🔗 Related: Slide Bag Net · Series

What this module is for

In Slide Bag Net the labels come from a script and the classes are balanced. In reality the label comes from sequencing, mutations are rare, and they are correlated with each other. cBioPortal (Cerami et al. 2012; Gao et al. 2013; Bruijn et al. 2023) answers three questions on public cohorts without code: How common is this mutation? Which other mutations does it co-occur with or exclude? Is it associated with survival? The last part shows how to reach the diagnostic slides of the same cases and how to build the label file for a MIL run.

🎯 Learning objectives

  • Distinguish a study, a sample list and a molecular profile
  • Read the OncoPrint, mutual exclusivity and survival tabs of the web interface
  • Pull a gene × sample binary mutation matrix from the API and compute frequencies
  • Test co-occurrence / mutual exclusivity with Fisher’s exact test; plot survival with Kaplan–Meier
  • Go from a TCGA sample barcode to the diagnostic slide file; build the label file at patient level
  • Say what the “mutant” label covers (driver / VUS, exome / panel, hypermutators)

Worked example: TCGA lung adenocarcinoma

The module uses the Lung Adenocarcinoma (TCGA, PanCancer Atlas) study throughout (The Cancer Genome Atlas Research Network 2014; Hoadley et al. 2018): study id luad_tcga_pan_can_atlas_2018, 566 samples with mutation data. Mutation calls come from the MC3 pipeline (Ellrott et al. 2018). Four genes: EGFR (targeted therapy; the classic target of the H&E-prediction literature (Coudray et al. 2018)), KRAS (the most frequent driver), TP53 (the most frequently altered gene), STK11 (associated with immunotherapy response).

Where the numbers come from

The frequencies below were pulled from the cBioPortal API on 7 November 2026: among 566 sequenced samples, EGFR is mutated in 70 samples (12.4%), 86 mutation records (some samples carry more than one); L858R (23) and E746_A750del (16) lead. KRAS about 30%, TP53 about 50%, STK11 about 15%; the code in part B reproduces them exactly. Numbers change with the data release; record the date of your own run.

A · Web interface (no code)

1

cbioportal.org → Query tab. Type lung adenocarcinoma tcga pancancer in the search box and tick Lung Adenocarcinoma (TCGA, PanCancer Atlas). On the right, the “Mutations” row reads 566.

2

Query By Gene → Select Molecular Profiles: leave only Mutations ticked (untick CNA and mRNA; our label is mutation). In Enter Genes type EGFR KRAS TP53 STK11 → Submit Query.

3

OncoPrint tab. Each column is a sample, each row a gene. The percentage at the row head is the fraction of altered samples. Scan the columns: EGFR and KRAS almost never appear in the same column. The Download button at the top right exports the table.

4

Mutual Exclusivity tab. For every gene pair: log2 odds ratio, p-value and a “Mutual exclusivity / Co-occurrence” label. EGFR–KRAS is mutually exclusive (one of two drivers in the same pathway suffices); TP53 is independent of or co-occurs with most genes.

5

Comparison/Survival tab → Survival. Groups arrive as “Altered” and “Unaltered”; read the Overall Survival curve and the log-rank p-value. Note that the groups are defined by alteration in any of the four genes. For a single gene, go back to Query with that gene only, or use the code in part D.

6

Download tab → Mutation data (sample × gene matrix) or Alteration data. This is the no-code version of the MIL label matrix: one row per sample, one column per gene, the protein change in the cell when mutated.

What does “mutant” mean?

By default cBioPortal counts every somatic mutation: drivers and variants of unknown significance (VUS) alike. The Mutations: Driver / VUS switch on the OncoPrint (OncoKB and Cancer Hotspots annotations) separates them. For a model that predicts “EGFR mutant” from H&E, the label should be restricted to clinically meaningful driver mutations (L858R, exon 19 deletions, …); otherwise part of the positive class consists of random VUS with no morphological correlate, which the model cannot learn.

B · API: the gene × sample matrix

cBioPortal’s public REST API (https://www.cbioportal.org/api, OpenAPI document at /api/swagger-ui/) needs no authentication. The code below builds the binary mutation matrix in three calls. It is not executed here; copy it into your own environment.

cbio_matrix.py
import requests, pandas as pd

API = "https://www.cbioportal.org/api"
STUDY = "luad_tcga_pan_can_atlas_2018"
PROFILE = f"{STUDY}_mutations"          # molecular profile: mutations
SAMPLES = f"{STUDY}_sequenced"           # sample list: samples with mutation data
GENES = ["EGFR", "KRAS", "TP53", "STK11"]

# 1) HUGO symbol → Entrez id (the API filters mutations by Entrez id)
genes = requests.post(f"{API}/genes/fetch",
                      params={"geneIdType": "HUGO_GENE_SYMBOL"},
                      json=GENES).json()
entrez = {g["hugoGeneSymbol"]: g["entrezGeneId"] for g in genes}

# 2) Sample list → ids of all sequenced samples (566)
sl = requests.get(f"{API}/sample-lists/{SAMPLES}").json()
all_samples = sl["sampleIds"]

# 3) Mutation records of the chosen genes (a sample may have several)
muts = requests.post(f"{API}/molecular-profiles/{PROFILE}/mutations/fetch",
                     params={"projection": "DETAILED"},
                     json={"sampleListId": SAMPLES,
                           "entrezGeneIds": list(entrez.values())}).json()
mdf = pd.DataFrame(muts)[["sampleId", "patientId", "entrezGeneId",
                          "proteinChange", "mutationType"]]
mdf["gene"] = mdf["entrezGeneId"].map({v: k for k, v in entrez.items()})

# Binary matrix: row = sample, column = gene, 1 = at least one mutation
M = (mdf.assign(v=1).pivot_table(index="sampleId", columns="gene",
                                  values="v", aggfunc="max")
       .reindex(all_samples).fillna(0).astype(int))
print(M.sum().sort_values(ascending=False) / len(M))   # frequencies
M.to_csv("luad_mutation_matrix.csv")

Expected output (November 2026 data): EGFR 0.124 (70/566); KRAS, TP53 and STK11 about 0.30, 0.50 and 0.15.

Driver filter

Records returned with projection=DETAILED carry driverFilter (“Putative_Driver” / “Putative_Passenger”) and driverFilterAnnotation where the study has OncoKB annotation. To restrict the label to drivers, add mdf = mdf[mdf["driverFilter"] == "Putative_Driver"] before building the matrix. Where the field is empty, filter by a protein-change list (for EGFR: L858R, exon 19 del, L861Q, G719x, S768I, exon 20 ins).

C · Co-occurrence and mutual exclusivity

A 2×2 table and Fisher’s exact test per gene pair; a reproduction of the web interface’s Mutual Exclusivity tab.

cbio_pairs.py
import itertools, numpy as np, pandas as pd
from scipy.stats import fisher_exact

M = pd.read_csv("luad_mutation_matrix.csv", index_col=0)
rows = []
for a, b in itertools.combinations(M.columns, 2):
    t = pd.crosstab(M[a], M[b]).reindex(index=[0, 1], columns=[0, 1], fill_value=0)
    odds, p = fisher_exact(t.values)
    rows.append({"A": a, "B": b, "both": t.loc[1, 1], "A_only": t.loc[1, 0],
                 "B_only": t.loc[0, 1], "neither": t.loc[0, 0],
                 "log2_OR": np.log2(odds) if odds > 0 else -np.inf, "p": p})
print(pd.DataFrame(rows).sort_values("p"))

Reading: for EGFR–KRAS, both is near zero and the log2 odds ratio is clearly negative: mutual exclusivity. This carries a question back to Slide Bag Net: is the pattern an EGFR predictor learns “EGFR present” or “KRAS absent”? In an external cohort with a different KRAS frequency the two behave differently.

D · Survival

Clinical data are at patient level; mutations are at sample level. First map sample → patient, then Kaplan–Meier.

cbio_survival.py
import requests, pandas as pd
from lifelines import KaplanMeierFitter
from lifelines.statistics import logrank_test

API = "https://www.cbioportal.org/api"; STUDY = "luad_tcga_pan_can_atlas_2018"
M = pd.read_csv("luad_mutation_matrix.csv", index_col=0)

samples = pd.DataFrame(requests.get(f"{API}/studies/{STUDY}/samples",
                                    params={"projection": "SUMMARY"}).json())
s2p = samples.set_index("sampleId")["patientId"]
M["patientId"] = s2p.reindex(M.index).values
# Patient-level label: 1 if any sample of the patient is mutated
P = M.groupby("patientId").max()

clin = pd.DataFrame(requests.get(f"{API}/studies/{STUDY}/clinical-data",
                                 params={"clinicalDataType": "PATIENT",
                                         "projection": "SUMMARY"}).json())
clin = clin.pivot(index="patientId", columns="clinicalAttributeId", values="value")
df = P.join(clin[["OS_STATUS", "OS_MONTHS"]], how="inner").dropna()
df["event"] = df["OS_STATUS"].str.startswith("1").astype(int)   # "1:DECEASED"
df["OS_MONTHS"] = df["OS_MONTHS"].astype(float)

gene = "EGFR"
km = KaplanMeierFitter()
for v, lab in [(1, f"{gene} mutant"), (0, f"{gene} wild-type")]:
    g = df[df[gene] == v]
    km.fit(g["OS_MONTHS"], g["event"], label=f"{lab} (n={len(g)})").plot_survival_function()
r = logrank_test(df[df[gene]==1]["OS_MONTHS"], df[df[gene]==0]["OS_MONTHS"],
                 df[df[gene]==1]["event"], df[df[gene]==0]["event"])
print("log-rank p =", round(r.p_value, 4))

The same table can be exported with df.to_csv("luad_os.csv") and analysed in jamovi with the ClinicoPath module; that is the no-code route.

Three traps in reading survival

TCGA cases are surgical resections and treatment data are incomplete; whether EGFR-mutant patients received a TKI is unknown. Stage confounds; a univariable log-rank does not adjust for it (add AJCC_PATHOLOGIC_TUMOR_STAGE to a Cox model). And a survival difference is a property of the sequenced label, not of a label predicted from H&E; plotting KM by predicted label is a separate validation question.

E · From sample to slide: the label file for MIL

A TCGA sample barcode such as TCGA-05-4244-01 is project – tissue source site (05) – participant (4244) – sample type (01 = primary tumour). The diagnostic H&E slide of the same case is on the GDC Data Portal as TCGA-05-4244-01Z-00-DX1.<uuid>.svs (DX = diagnostic FFPE, TS = frozen section; use DX for MIL). The tissue source site code (05, 44, 55, …) is also a proxy for the scanning and staining site: Howard et al. (2021) used it to show how site-specific signatures inflate mutation predictors. The “lab B” trap in Slide Bag Net is the simulation version of this code.

cbio_labels.py
import pandas as pd
M = pd.read_csv("luad_mutation_matrix.csv", index_col=0)
lab = pd.DataFrame({
    "sample_id": M.index,
    "patient_id": M.index.str.slice(0, 12),      # TCGA-05-4244
    "site_code": M.index.str.slice(5, 7),        # 05  → for site stratification
    "label_EGFR": M["EGFR"].values,
})
# Split at patient and site level; NEVER at slide level.
print(lab["label_EGFR"].mean(), lab["site_code"].nunique())
lab.to_csv("luad_egfr_labels.csv", index=False)

Class ratio and metric

In the simulation the positive rate is 50%; here it is 12% for EGFR. At that ratio accuracy is meaningless (a model that calls everything “wild-type” is 88% accurate). Report AUPRC alongside AUROC, plus sensitivity/specificity at the chosen threshold; use class weights or balanced sampling in the loss. Published EGFR predictors reach AUROC 0.75–0.85 (Coudray et al. 2018; Kather et al. 2020); position them as pre-screening.

Check yourself

The OncoPrint shows EGFR at 12%. If you build the label file for an H&E EGFR predictor straight from that column, what happens?

The driver / VUS distinction (OncoKB annotation or a protein-change list) is the first cleaning step of the label.

Fisher's test shows strong mutual exclusivity for EGFR–KRAS. Why does this matter for an EGFR predictor?

Correlation between labels makes it unclear what pattern the model learned; external validation and confounder analysis exist for this reason.

At which level do you split the label file into training and test?

A slide- or sample-level split inflates test performance through leakage; site leakage rewards shortcut learning.

References

Works cited on this page are listed below; for the whole series see References. cBioPortal documentation: docs.cbioportal.org; API: cbioportal.org/api/swagger-ui; R client: cbioportalR; Bioconductor: cBioPortalData.

Back to top

References

Bruijn, Ino de, Ritika Kundra, Brooke Mastrogiacomo, Thinh Ngoc Tran, Luke Sikina, Tali Mazor, Xiang Li, et al. 2023. “Analysis and Visualization of Longitudinal Genomic and Clinical Data from the AACR Project GENIE Biopharma Collaborative in cBioPortal.” Cancer Research 83 (23): 3861–67. https://doi.org/10.1158/0008-5472.CAN-23-0816.
Cerami, Ethan, Jianjiong Gao, Ugur Dogrusoz, Benjamin E. Gross, Selcuk Onur Sumer, Bülent Arman Aksoy, Anders Jacobsen, et al. 2012. “The cBio Cancer Genomics Portal: An Open Platform for Exploring Multidimensional Cancer Genomics Data.” Cancer Discovery 2 (5): 401–4. https://doi.org/10.1158/2159-8290.CD-12-0095.
Coudray, Nicolas, Paolo Santiago Ocampo, Theodore Sakellaropoulos, Navneet Narula, Matija Snuderl, David Fenyö, Andre L. Moreira, Narges Razavian, and Aristotelis Tsirigos. 2018. “Classification and Mutation Prediction from Non-Small Cell Lung Cancer Histopathology Images Using Deep Learning.” Nature Medicine 24: 1559–67. https://doi.org/10.1038/s41591-018-0177-5.
Ellrott, Kyle, Matthew H. Bailey, Gordon Saksena, Kyle R. Covington, Cyriac Kandoth, Chip Stewart, Julian Hess, et al. 2018. “Scalable Open Science Approach for Mutation Calling of Tumor Exomes Using Multiple Genomic Pipelines.” Cell Systems 6 (3): 271–281.e7. https://doi.org/10.1016/j.cels.2018.03.002.
Gao, Jianjiong, Bülent Arman Aksoy, Ugur Dogrusoz, Gideon Dresdner, Benjamin Gross, S. Onur Sumer, Yichao Sun, et al. 2013. “Integrative Analysis of Complex Cancer Genomics and Clinical Profiles Using the cBioPortal.” Science Signaling 6 (269): pl1. https://doi.org/10.1126/scisignal.2004088.
Hoadley, Katherine A., Christina Yau, Toshinori Hinoue, Denise M. Wolf, Alexander J. Lazar, Esther Drill, Ronglai Shen, et al. 2018. “Cell-of-Origin Patterns Dominate the Molecular Classification of 10,000 Tumors from 33 Types of Cancer.” Cell 173 (2): 291–304.e6. https://doi.org/10.1016/j.cell.2018.03.022.
Howard, Frederick M., James Dolezal, Sara Kochanny, Jefree Schulte, Heather Chen, Lara Heij, Dezheng Huo, et al. 2021. “The Impact of Site-Specific Digital Histology Signatures on Deep Learning Model Accuracy and Bias.” Nature Communications 12: 4423. https://doi.org/10.1038/s41467-021-24698-1.
Kather, Jakob Nikolas, Lara R. Heij, Heike I. Grabsch, Chiara Loeffler, Amelie Echle, Hannah Sophie Muti, Jeremias Krause, et al. 2020. “Pan-Cancer Image-Based Detection of Clinically Actionable Genetic Alterations.” Nature Cancer 1: 789–99. https://doi.org/10.1038/s43018-020-0087-6.
The Cancer Genome Atlas Research Network. 2014. “Comprehensive Molecular Profiling of Lung Adenocarcinoma.” Nature 511: 543–50. https://doi.org/10.1038/nature13385.