Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Query NHGRI-EBI GWAS Catalog for SNP-trait associations. Search variants by rs ID, disease/trait, gene, retrieve p-values and summary statistics, for genetic epidemiology and polygenic risk scores.
.claude/skills/gwas-database/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-22 | ✓→✓ | = Same ✓ | — | — |
The GWAS Catalog is a comprehensive repository of published genome-wide association studies maintained by the National Human Genome Research Institute (NHGRI) and the European Bioinformatics Institute (EBI). The catalog contains curated SNP-trait associations from thousands of GWAS publications, including genetic variants, associated traits and diseases, p-values, effect sizes, and full summary statistics for many studies.
This skill should be used when queries involve:
The GWAS Catalog is organized around four core entities:
Key Identifiers:
GCST IDs (e.g., GCST001234)rs numbers (e.g., rs7903146) or variant_id formatThe web interface at https://www.ebi.ac.uk/gwas/ supports multiple search modes:
By Variant (rs ID):
rs7903146Returns all trait associations for this SNP.
By Disease/Trait:
type 2 diabetes
Parkinson disease
body mass indexReturns all associated genetic variants.
By Gene:
APOE
TCF7L2Returns variants in or near the gene region.
By Chromosomal Region:
10:114000000-115000000Returns variants in the specified genomic interval.
By Publication:
PMID:20581827
Author: McCarthy MI
GCST001234Returns study details and all reported associations.
The GWAS Catalog provides two REST APIs for programmatic access:
Base URLs:
https://www.ebi.ac.uk/gwas/rest/apihttps://www.ebi.ac.uk/gwas/summary-statistics/apiAPI Documentation:
Core Endpoints:
/studies/{accessionID}python import requests
# Get a specific study url = "https://www.ebi.ac.uk/gwas/rest/api/studies/GCST001795" response = requests.get(url, headers={"Content-Type": "application/json"}) study = response.json()
/associationspython # Find associations for a variant variant = "rs7903146" url = f"https://www.ebi.ac.uk/gwas/rest/api/singleNucleotidePolymorphisms/{variant}/associations" params = {"projection": "associationBySnp"} response = requests.get(url, params=params, headers={"Content-Type": "application/json"}) associations = response.json()
/singleNucleotidePolymorphisms/{rsID}python # Get variant details url = "https://www.ebi.ac.uk/gwas/rest/api/singleNucleotidePolymorphisms/rs7903146" response = requests.get(url, headers={"Content-Type": "application/json"}) variant_info = response.json()
/efoTraits/{efoID}python # Get trait information url = "https://www.ebi.ac.uk/gwas/rest/api/efoTraits/EFO_0001360" response = requests.get(url, headers={"Content-Type": "application/json"}) trait_info = response.json()
Example 1: Find all associations for a disease
pythonimport requests trait = "EFO_0001360" # Type 2 diabetes base_url = "https://www.ebi.ac.uk/gwas/rest/api" # Query associations for this trait url = f"{base_url}/efoTraits/{trait}/associations" response = requests.get(url, headers={"Content-Type": "application/json"}) associations = response.json() # Process results for assoc in associations.get('_embedded', {}).get('associations', []): variant = assoc.get('rsId') pvalue = assoc.get('pvalue') risk_allele = assoc.get('strongestAllele') print(f"{variant}: p={pvalue}, risk allele={risk_allele}")
Example 2: Get variant information and all trait associations
pythonimport requests variant = "rs7903146" base_url = "https://www.ebi.ac.uk/gwas/rest/api" # Get variant details url = f"{base_url}/singleNucleotidePolymorphisms/{variant}" response = requests.get(url, headers={"Content-Type": "application/json"}) variant_data = response.json() # Get all associations for this variant url = f"{base_url}/singleNucleotidePolymorphisms/{variant}/associations" params = {"projection": "associationBySnp"} response = requests.get(url, params=params, headers={"Content-Type": "application/json"}) associations = response.json() # Extract trait names and p-values for assoc in associations.get('_embedded', {}).get('associations', []): trait = assoc.get('efoTrait') pvalue = assoc.get('pvalue') print(f"Trait: {trait}, p-value: {pvalue}")
Example 3: Access summary statistics
pythonimport requests # Query summary statistics API base_url = "https://www.ebi.ac.uk/gwas/summary-statistics/api" # Find associations by trait with p-value threshold trait = "EFO_0001360" # Type 2 diabetes p_upper = "0.000000001" # p < 1e-9 url = f"{base_url}/traits/{trait}/associations" params = { "p_upper": p_upper, "size": 100 # Number of results } response = requests.get(url, params=params) results = response.json() # Process genome-wide significant hits for hit in results.get('_embedded', {}).get('associations', []): variant_id = hit.get('variant_id') chromosome = hit.get('chromosome') position = hit.get('base_pair_location') pvalue = hit.get('p_value') print(f"{chromosome}:{position} ({variant_id}): p={pvalue}")
Example 4: Query by chromosomal region
pythonimport requests # Find variants in a specific genomic region chromosome = "10" start_pos = 114000000 end_pos = 115000000 base_url = "https://www.ebi.ac.uk/gwas/rest/api" url = f"{base_url}/singleNucleotidePolymorphisms/search/findByChromBpLocationRange" params = { "chrom": chromosome, "bpStart": start_pos, "bpEnd": end_pos } response = requests.get(url, params=params, headers={"Content-Type": "application/json"}) variants_in_region = response.json()
The GWAS Catalog hosts full summary statistics for many studies, providing access to all tested variants (not just genome-wide significant hits).
Access Methods:
Summary Statistics API Features:
Example: Download summary statistics for a study
pythonimport requests import gzip # Get available summary statistics base_url = "https://www.ebi.ac.uk/gwas/summary-statistics/api" url = f"{base_url}/studies/GCST001234" response = requests.get(url) study_info = response.json() # Download link is provided in the response # Alternatively, use FTP: # ftp://ftp.ebi.ac.uk/pub/databases/gwas/summary_statistics/GCSTXXXXXX/
The GWAS Catalog provides links to external resources:
Genomic Databases:
Functional Resources:
Phenotype Resources:
Following Links in API Responses:
pythonimport requests # API responses include _links for related resources response = requests.get("https://www.ebi.ac.uk/gwas/rest/api/studies/GCST001234") study = response.json() # Follow link to associations associations_url = study['_links']['associations']['href'] associations_response = requests.get(associations_url)
python url = f"https://www.ebi.ac.uk/gwas/rest/api/efoTraits/{efo_id}/associations"
python url = f"https://www.ebi.ac.uk/gwas/rest/api/singleNucleotidePolymorphisms/{rs_id}"
python url = f"https://www.ebi.ac.uk/gwas/rest/api/singleNucleotidePolymorphisms/{rs_id}/associations"
python url = f"https://www.ebi.ac.uk/gwas/rest/api/singleNucleotidePolymorphisms/search/findByGene" params = {"geneName": gene_symbol}
bash # Via FTP wget ftp://ftp.ebi.ac.uk/pub/databases/gwas/summary_statistics/GCSTXXXXXX/harmonised/GCSTXXXXXX-harmonised.tsv.gz
python url = f"https://www.ebi.ac.uk/gwas/summary-statistics/api/chromosomes/{chrom}/associations" params = {"start": start_pos, "end": end_pos}
Key Fields in Association Records:
rsId: Variant identifier (rs number)strongestAllele: Risk allele for the associationpvalue: Association p-valuepvalueText: P-value as text (may include inequality)orPerCopyNum: Odds ratio or beta coefficientbetaNum: Effect size (for quantitative traits)betaUnit: Unit of measurement for betarange: Confidence intervalefoTrait: Associated trait namemappedLabel: EFO-mapped trait termStudy Metadata Fields:
accessionId: GCST study identifierpubmedId: PubMed IDauthor: First authorpublicationDate: Publication dateancestryInitial: Discovery population ancestryancestryReplication: Replication population ancestrysampleSize: Total sample sizePagination: Results are paginated (default 20 items per page). Navigate using:
size parameter: Number of results per pagepage parameter: Page number (0-indexed)_links in response: URLs for next/previous pagesComplete workflow for querying and analyzing GWAS data:
pythonimport requests import pandas as pd from time import sleep def query_gwas_catalog(trait_id, p_threshold=5e-8): """ Query GWAS Catalog for trait associations Args: trait_id: EFO trait identifier (e.g., 'EFO_0001360') p_threshold: P-value threshold for filtering Returns: pandas DataFrame with association results """ base_url = "https://www.ebi.ac.uk/gwas/rest/api" url = f"{base_url}/efoTraits/{trait_id}/associations" headers = {"Content-Type": "application/json"} results = [] page = 0 while True: params = {"page": page, "size": 100} response = requests.get(url, params=params, headers=headers) if response.status_code != 200: break data = response.json() associations = data.get('_embedded', {}).get('associations', []) if not associations: break for assoc in associations: pvalue = assoc.get('pvalue') if pvalue and float(pvalue) <= p_threshold: results.append({ 'variant': assoc.get('rsId'), 'pvalue': pvalue, 'risk_allele': assoc.get('strongestAllele'), 'or_beta': assoc.get('orPerCopyNum') or assoc.get('betaNum'), 'trait': assoc.get('efoTrait'), 'pubmed_id': assoc.get('pubmedId') }) page += 1 sleep(0.1) # Rate limiting return pd.DataFrame(results) # Example usage df = query_gwas_catalog('EFO_0001360') # Type 2 diabetes print(df.head()) print(f"\nTotal associations: {len(df)}") print(f"Unique variants: {df['variant'].nunique()}")
Comprehensive API documentation including:
Consult this reference when:
The GWAS Catalog team provides workshop materials:
When using GWAS Catalog data, cite:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.