关于 alterlab-cbioportal
alterlab-cbioportal 是面向癌症基因组研究的技能,通过 cBioPortal REST API 查询和分析公开研究数据。它涵盖突变、拷贝数改变及临床数据,可用于单癌种或泛癌分析。
主要功能
- 01浏览癌症研究和分子谱
- 02查询突变与拷贝数改变
- 03获取患者临床数据
- 04分析基因改变频率
- 05支持生存及泛癌分析
适用场景
- 01比较不同癌种的基因突变频率
- 02检查基因改变与临床变量的关联
- 03按突变状态分析患者生存
- 04获取研究数据用于癌症基因组分析
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ alterlab-ieu/ alterlab-academic-skills --skill alterlab-cbioportal安装后在 Claude Code 中输入/alterlab-cbioportal确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/study_exploration.mdscripts/query_cbioportal.py详细介绍
cBioPortal 数据库
概述
cBioPortal for Cancer Genomics(https://www.cbioportal.org/)是一个开放访问资源,用于探索、可视化和分析多维癌症基因组数据。它托管来自 The Cancer Genome Atlas(TCGA)、AACR Project GENIE、MSK-IMPACT 以及数百项其他癌症研究的数据,涵盖数千个癌症样本的突变、拷贝数改变(CNA)、结构变异、mRNA/蛋白质表达、甲基化和临床数据。
关键资源:
- cBioPortal 网站:https://www.cbioportal.org/
- REST API:https://www.cbioportal.org/api/swagger-ui/index.html
- API 文档(Swagger):https://www.cbioportal.org/api/swagger-ui/index.html
- Python 客户端:
bravado或requests - GitHub:https://github.com/cBioPortal/cbioportal
何时使用此技能
在以下情况下使用 cBioPortal:
- 突变概况:某种癌症类型中有多少比例的样本在特定基因中存在突变?
- 癌基因/TSG 验证:某个基因在癌症中是否经常发生突变、扩增或缺失?
- 共突变模式:基因 A 和基因 B 的突变是互斥还是共同出现?
- 生存分析:某个基因的突变是否与更好的或更差的患者结局相关?
- 改变概况:哪些类型的改变(错义、截短、扩增、缺失)会影响某个基因?
- 泛癌分析:比较不同癌症类型的改变频率
- 临床关联:将基因组改变与临床变量(分期、分级、治疗反应)关联起来
- TCGA/GENIE 探索:系统性访问 TCGA 和临床测序数据集
不适用的情况
| 场景 | 请改用 |
|---|---|
| 生殖系变异的致病性/临床意义 | alterlab-clinvar |
| COSMIC 癌症基因普查、突变特征(SBS)、人工整理的体细胞突变目录 | alterlab-cosmic |
| 癌细胞系中的 CRISPR/RNAi 基因依赖性(必需性) | alterlab-depmap |
| 汇总的靶点–疾病关联证据和可成药性 | alterlab-opentargets |
核心功能
1. cBioPortal REST API
基础 URL:https://www.cbioportal.org/api
该 API 遵循 REST 风格,返回 JSON,访问公开数据无需 API 密钥。
import requests
BASE_URL = "https://www.cbioportal.org/api"
HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
def cbioportal_get(endpoint, params=None):
url = f"{BASE_URL}/{endpoint}"
response = requests.get(url, params=params, headers=HEADERS)
response.raise_for_status()
return response.json()
def cbioportal_post(endpoint, body):
url = f"{BASE_URL}/{endpoint}"
response = requests.post(url, json=body, headers=HEADERS)
response.raise_for_status()
return response.json()
2. 浏览研究
def get_all_studies():
"""List all available cancer studies.
The public portal hosts 540+ studies (2026-09), so a pageSize of 500 would
silently truncate the list; ask for more than you expect.
"""
return cbioportal_get("studies", {"pageSize": 100000})
# Each study has:
# studyId: unique identifier (e.g., "brca_tcga")
# name: human-readable name
# description: dataset description
# cancerTypeId: cancer type abbreviation
# referenceGenome: hg19 or hg38
# allSampleCount: samples in the study
# (pass projection=DETAILED to also get pmid, citation, sequencedSampleCount, ...)
studies = get_all_studies()
print(f"Total studies: {len(studies)}")
# Common TCGA study IDs — each cancer has several versions:
# *_tcga original TCGA Firehose Legacy (e.g. brca_tcga)
# *_tcga_pan_can_atlas_2018 harmonized PanCancer Atlas (preferred for pan-cancer work)
# *_tcga_gdc GDC re-processed data
# e.g. brca_tcga, luad_tcga, coadread_tcga, gbm_tcga, prad_tcga, skcm_tcga
# Filter for TCGA studies
tcga_studies = [s for s in studies if "tcga" in s["studyId"]]
print([s["studyId"] for s in tcga_studies[:10]])
3. 分子概况
每项研究都有多个分子概况(突变、CNA、表达等):
def get_molecular_profiles(study_id):
"""Get all molecular profiles for a study."""
return cbioportal_get(f"studies/{study_id}/molecular-profiles")
profiles = get_molecular_profiles("brca_tcga")
for p in profiles:
print(f" {p['molecularProfileId']}: {p['name']} ({p['molecularAlterationType']})")
# Alteration types:
# MUTATION_EXTENDED — somatic mutations
# COPY_NUMBER_ALTERATION — CNA (GISTIC)
# MRNA_EXPRESSION — mRNA expression
# PROTEIN_LEVEL — RPPA protein expression
# STRUCTURAL_VARIANT — fusions/rearrangements
4. 突变数据
def get_mutations(molecular_profile_id, entrez_gene_ids, sample_list_id=None):
"""Get mutations for specified genes in a molecular profile."""
body = {
"entrezGeneIds": entrez_gene_ids,
"sampleListId": sample_list_id or molecular_profile_id.replace("_mutations", "_all")
}
return cbioportal_post(
f"molecular-profiles/{molecular_profile_id}/mutations/fetch",
body
)
# BRCA1 Entrez ID is 672, TP53 is 7157, PTEN is 5728
mutations = get_mutations("brca_tcga_mutations", entrez_gene_ids=[7157]) # TP53
# Each mutation record contains:
# patientId, sampleId, entrezGeneId (the nested gene.hugoGeneSymbol only
# appears with ?projection=DETAILED on the fetch URL)
# mutationType (Missense_Mutation, Nonsense_Mutation, Frame_Shift_Del, etc.)
# proteinChange (e.g., "R175H"), variantType
# ncbiBuild, chr, startPosition, endPosition, referenceAllele, variantAllele
# mutationStatus (Somatic/Germline)
# tumorAltCount, tumorRefCount (read counts — there is no VAF field; derive it)
import pandas as pd
df = pd.DataFrame(mutations)
df["vaf"] = df["tumorAltCount"] / (df["tumorAltCount"] + df["tumorRefCount"])
print(df[["patientId", "mutationType", "proteinChange", "vaf"]].head())
print(f"\nMutation types:\n{df['mutationType'].value_counts()}")
5. 拷贝数改变数据
def get_cna(molecular_profile_id, entrez_gene_ids):
"""Get discrete CNA data (GISTIC: -2, -1, 0, 1, 2)."""
body = {
"entrezGeneIds": entrez_gene_ids,
"sampleListId": molecular_profile_id.replace("_gistic", "_all").replace("_cna", "_all")
}
return cbioportal_post(
f"molecular-profiles/{molecular_profile_id}/discrete-copy-number/fetch",
body
)
# GISTIC values:
# -2 = Deep deletion (homozygous loss)
# -1 = Shallow deletion (heterozygous loss)
# 0 = Diploid (neutral)
# 1 = Low-level gain
# 2 = High-level amplification
cna_data = get_cna("brca_tcga_gistic", entrez_gene_ids=[1956]) # EGFR
df_cna = pd.DataFrame(cna_data)
print(df_cna["value"].value_counts())
6. 改变频率(OncoPrint 风格)
def get_alteration_frequency(study_id, gene_symbols, alteration_types=None):
"""Compute alteration frequencies for genes across a cancer study."""
import requests, pandas as pd
# Denominator = samples profiled for mutations (cBioPortal's own convention).
# 'all_cases_in_study' also counts unsequenced samples and deflates the
# frequency (brca_tcga: 1,108 samples in study vs 982 sequenced).
samples = requests.get(
f"{BASE_URL}/studies/{study_id}/sample-lists",
headers=HEADERS
).json()
by_category = {s["category"]: s["sampleListId"] for s in samples}
sample_list_id = (by_category.get("all_cases_with_mutation_data")
or by_category.get("all_cases_in_study"))
total_samples = len(requests.get(
f"{BASE_URL}/sample-lists/{sample_list_id}/sample-ids",
headers=HEADERS
).json())
# Get gene Entrez IDs. /genes/fetch takes a plain JSON array of identifiers
# plus a geneIdType query param; symbols WITHOUT the param resolve to [].
gene_data = requests.post(
f"{BASE_URL}/genes/fetch",
params={"geneIdType": "HUGO_GENE_SYMBOL"},
json=gene_symbols,
headers=HEADERS
).json()
# Response order is not guaranteed; map by symbol.
entrez_by_symbol = {g["hugoGeneSymbol"]: g["entrezGeneId"] for g in gene_data}
entrez_ids = [entrez_by_symbol[g] for g in gene_symbols if g in entrez_by_symbol]
# Get mutations
mutation_profile = f"{study_id}_mutations"
mutations = get_mutations(mutation_profile, entrez_ids, sample_list_id)
freq = {}
for g_symbol, e_id in entrez_by_symbol.items():
# Count samples (not patients) so numerator and denominator match.
mutated = len(set(m["sampleId"] for m in mutations if m["entrezGeneId"] == e_id))
freq[g_symbol] = mutated / total_samples * 100
return freq
# Example
freq = get_alteration_frequency("brca_tcga", ["TP53", "PIK3CA", "BRCA1", "BRCA2"])
for gene, pct in sorted(freq.items(), key=lambda x: -x[1]):
print(f" {gene}: {pct:.1f}%")
7. 临床数据
全局 /clinical-data/fetch 端点仅支持 POST(GET 请求会返回 HTTP 405)。
对于单项研究,最简单的方式是使用该研究的 GET 端点;该端点会返回包含 {patientId, studyId, clinicalAttributeId, value} 记录的列表:
def get_patient_clinical_data(study_id, attribute_ids):
"""Patient-level clinical data via the per-study GET endpoint.
GET /studies/{studyId}/clinical-data?clinicalDataType=PATIENT&attributeId=...
accepts a single attributeId, so we query each and concatenate.
"""
records = []
for attr in attribute_ids:
records += cbioportal_get(
f"studies/{study_id}/clinical-data",
{"clinicalDataType": "PATIENT", "attributeId": attr, "pageSize": 100000},
)
return records
# Clinical attributes include:
# OS_STATUS, OS_MONTHS, DFS_STATUS, DFS_MONTHS (survival)
# AJCC_PATHOLOGIC_TUMOR_STAGE, GRADE, AGE, SEX, RACE
# Study-specific attributes vary — list them with get_clinical_attributes().
# GOTCHA: OS_STATUS / DFS_STATUS are encoded "1:DECEASED" / "0:LIVING"
# (event:label), not bare 0/1 — split on ":" before survival analysis.
def get_clinical_attributes(study_id):
"""List all available clinical attributes for a study."""
return cbioportal_get(f"studies/{study_id}/clinical-attributes")
查询工作流
工作流 1:某种癌症类型中的基因改变概况
import requests, pandas as pd
def alteration_profile(study_id, gene_symbol):
"""Full alteration profile for a gene in a cancer study."""
# 1. Get gene Entrez ID (plain array body + geneIdType param)
gene_info = requests.post(
f"{BASE_URL}/genes/fetch",
params={"geneIdType": "HUGO_GENE_SYMBOL"},
json=[gene_symbol],
headers=HEADERS
).json()[0]
entrez_id = gene_info["entrezGeneId"]
# 2. Get mutations
mutations = get_mutations(f"{study_id}_mutations", [entrez_id])
mut_df = pd.DataFrame(mutations) if mutations else pd.DataFrame()
# 3. Get CNAs
cna = get_cna(f"{study_id}_gistic", [entrez_id])
cna_df = pd.DataFrame(cna) if cna else pd.DataFrame()
# 4. Summary
n_mut = len(set(mut_df["patientId"])) if not mut_df.empty else 0
n_amp = len(cna_df[cna_df["value"] == 2]) if not cna_df.empty else 0
n_del = len(cna_df[cna_df["value"] == -2]) if not cna_df.empty else 0
return {"mutations": n_mut, "amplifications": n_amp, "deep_deletions": n_del}
result = alteration_profile("brca_tcga", "PIK3CA")
print(result)
工作流 2:泛癌基因突变频率
import requests, pandas as pd
def pan_cancer_mutation_freq(gene_symbol, cancer_study_ids=None):
"""Mutation frequency of a gene across multiple cancer types."""
studies = get_all_studies()
if cancer_study_ids:
studies = [s for s in studies if s["studyId"] in cancer_study_ids]
results = []
for study in studies[:20]: # Limit for demo
try:
freq = get_alteration_frequency(study["studyId"], [gene_symbol])
results.append({
"study": study["studyId"],
"cancer": study.get("cancerTypeId", ""),
"mutation_pct": freq.get(gene_symbol, 0)
})
except Exception:
pass
df = pd.DataFrame(results).sort_values("mutation_pct", ascending=False)
return df
工作流 3:按突变状态进行生存分析
import requests, pandas as pd
def survival_by_mutation(study_id, gene_symbol):
"""Get survival data split by mutation status."""
# This workflow fetches clinical and mutation data for downstream analysis
gene_info = requests.post(
f"{BASE_URL}/genes/fetch",
params={"geneIdType": "HUGO_GENE_SYMBOL"},
json=[gene_symbol],
headers=HEADERS
).json()[0]
entrez_id = gene_info["entrezGeneId"]
mutations = get_mutations(f"{study_id}_mutations", [entrez_id])
mutated_patients = set(m["patientId"] for m in mutations)
# Patient-level survival via the per-study GET endpoint (clinical-data/fetch
# is POST-only — a GET there returns HTTP 405).
clinical = get_patient_clinical_data(study_id, ["OS_MONTHS", "OS_STATUS"])
clinical_df = pd.DataFrame(clinical)
os_wide = clinical_df.pivot(index="patientId", columns="clinicalAttributeId", values="value")
# OS_STATUS is encoded as "1:DECEASED" / "0:LIVING"; split off the 0/1 event flag.
if "OS_STATUS" in os_wide:
os_wide["OS_EVENT"] = os_wide["OS_STATUS"].str.startswith("1").astype("Int64")
os_wide["OS_MONTHS"] = pd.to_numeric(os_wide.get("OS_MONTHS"), errors="coerce")
os_wide["mutated"] = os_wide.index.isin(mutated_patients)
return os_wide
主要 API 端点摘要
| 端点 | 说明 |
|---|---|
GET /studies | 列出所有研究 |
GET /studies/{studyId}/molecular-profiles | 某项研究的分子概况 |
POST /molecular-profiles/{profileId}/mutations/fetch | 获取突变数据 |
POST /molecular-profiles/{profileId}/discrete-copy-number/fetch | 获取 CNA 数据 |
POST /molecular-profiles/{profileId}/molecular-data/fetch | 获取表达数据 |
GET /studies/{studyId}/clinical-attributes | 可用的临床变量 |
GET /studies/{studyId}/clinical-data | 某项研究的临床数据(每次调用使用一个 attributeId) |
POST /clinical-data/fetch?clinicalDataType=PATIENT | 跨研究获取临床数据(仅支持 POST;GET → 405) |
POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL | 解析符号 → Entrez ID(请求正文是普通 JSON 数组,例如 ["TP53"]) |
GET /studies/{studyId}/sample-lists | 样本列表 |
最佳实践
- 了解你的研究 ID:使用 Swagger UI 或
GET /studies查找正确的研究 ID - 使用样本列表:每项研究都有一个
all样本列表和若干子集;请始终指定适当的列表 - TCGA 与 GENIE:TCGA 数据较全面,但年代较早;GENIE 有较新的临床测序数据,但其联盟数据发布在独立的 https://genie.cbioportal.org 门户上(需要登录),不在无需密钥的公共 API 上
- Entrez 基因 ID:API 使用 Entrez ID——通过
POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL从基因符号转换。请求正文必须是纯 JSON 数组(["TP53","KRAS"]);对象形式[{"hugoGeneSymbol":...}]会返回 HTTP 400,省略geneIdType则会在基因符号输入时静默返回[]。响应顺序不保证一致——请通过hugoGeneSymbol将结果映射回来。 - 处理 404 错误:某些分子图谱可能并非所有研究都有
- 速率限制:批量查询时请添加延迟;对于大规模分析,可考虑下载数据文件
数据下载
对于大规模分析,请直接下载研究数据(较早的
cbioportal-datahub.s3.amazonaws.com 链接现在会返回 403):
# Download TCGA BRCA (PanCancer Atlas) data
wget https://datahub.assets.cbioportal.org/brca_tcga_pan_can_atlas_2018.tar.gz
其他资源
- cBioPortal 网站:https://www.cbioportal.org/
- API Swagger UI:https://www.cbioportal.org/api/swagger-ui/index.html
- 文档:https://docs.cbioportal.org/
- GitHub:https://github.com/cBioPortal/cbioportal
- 数据中心:https://www.cbioportal.org/datasets
- 引用文献:Cerami E et al.(2012)Cancer Discovery。PMID: 22588877
- API 客户端:https://docs.cbioportal.org/web-api-and-clients/
脚本
scripts/query_cbioportal.py — 可运行的 cBioPortal REST API 辅助脚本(公开,无需密钥):
python scripts/query_cbioportal.py studies --filter tcga
python scripts/query_cbioportal.py profiles brca_tcga
python scripts/query_cbioportal.py mutations brca_tcga_mutations --genes 7157,672