Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Query NCBI Gene Expression Omnibus (GEO) for expression datasets using Biopython Bio.Entrez. Use when finding microarray/RNA-seq datasets, downloading expression data, or linking GEO series to SRA runs.
.claude/skills/bio-geo-data/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 323% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 232% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 201% | 0% |
<!--
#
#
-->
Query and access Gene Expression Omnibus datasets using Biopython's Entrez module.
pythonfrom Bio import Entrez Entrez.email = 'your.email@example.com' # Required by NCBI Entrez.api_key = 'your_api_key' # Optional
| Database | db value | Description | |----------|----------|-------------| | GEO DataSets | gds | Curated datasets (GDS) | | GEO Profiles | geoprofiles | Individual gene profiles |
GEO Record Types: | Prefix | Type | Description | |--------|------|-------------| | GSE | Series | Complete study/experiment | | GSM | Sample | Individual sample | | GPL | Platform | Array/sequencing platform | | GDS | DataSet | Curated, normalized dataset |
pythonfrom Bio import Entrez Entrez.email = 'your.email@example.com' # Search curated datasets handle = Entrez.esearch(db='gds', term='breast cancer AND Homo sapiens[orgn]', retmax=10) record = Entrez.read(handle) handle.close() print(f"Found {record['Count']} datasets") print(f"IDs: {record['IdList']}")
python# Search GEO Series via gds database # Use entry_type filter handle = Entrez.esearch(db='gds', term='RNA-seq[title] AND human[orgn] AND gse[entry_type]', retmax=10) record = Entrez.read(handle) handle.close()
| Field | Description | Example | |-------|-------------|---------| | [orgn] | Organism | human[orgn] | | [title] | Dataset title | breast cancer[title] | | [description] | Description text | stem cell[description] | | [platform] | Platform GPL | GPL570[platform] | | [entry_type] | Record type | gse[entry_type], gds[entry_type] | | [gdstype] | Study type | expression profiling[gdstype] | | [pubmed] | PubMed ID | 35412348[pubmed] | | [pdat] | Publication date | 2024[pdat] |
python# Expression profiling by array term = 'expression profiling by array[gdstype] AND cancer' # RNA-seq expression term = 'expression profiling by high throughput sequencing[gdstype]' # ChIP-seq term = 'genome binding/occupancy profiling[gdstype]'
python# Fetch summary for GDS records handle = Entrez.esummary(db='gds', id='200024320') record = Entrez.read(handle) handle.close() summary = record[0] print(f"Accession: {summary['Accession']}") print(f"Title: {summary['title']}") print(f"Summary: {summary['summary'][:200]}...") print(f"Organism: {summary['taxon']}") print(f"Platform: {summary['GPL']}") print(f"Samples: {summary['n_samples']}")
pythonsummary['Accession'] # GSE/GDS accession summary['title'] # Dataset title summary['summary'] # Description summary['taxon'] # Organism summary['GPL'] # Platform ID summary['n_samples'] # Number of samples summary['FTPLink'] # FTP download link summary['PubMedIds'] # Associated publications summary['gdsType'] # Dataset type summary['ptechType'] # Platform technology
pythonfrom Bio import Entrez Entrez.email = 'your.email@example.com' def search_geo(term, entry_type='gse', max_results=20): full_term = f'{term} AND {entry_type}[entry_type]' handle = Entrez.esearch(db='gds', term=full_term, retmax=max_results) search = Entrez.read(handle) handle.close() if not search['IdList']: return [] handle = Entrez.esummary(db='gds', id=','.join(search['IdList'])) summaries = Entrez.read(handle) handle.close() results = [] for s in summaries: results.append({ 'accession': s['Accession'], 'title': s['title'], 'organism': s['taxon'], 'samples': s['n_samples'], 'platform': s['GPL'] }) return results datasets = search_geo('breast cancer RNA-seq AND human[orgn]') for ds in datasets: print(f"{ds['accession']}: {ds['title'][:60]}... ({ds['samples']} samples)")
pythondef find_rnaseq_datasets(organism, keywords, max_results=20): term = f'{keywords} AND {organism}[orgn] AND expression profiling by high throughput sequencing[gdstype] AND gse[entry_type]' handle = Entrez.esearch(db='gds', term=term, retmax=max_results) search = Entrez.read(handle) handle.close() if not search['IdList']: return [] handle = Entrez.esummary(db='gds', id=','.join(search['IdList'])) summaries = Entrez.read(handle) handle.close() return summaries datasets = find_rnaseq_datasets('Homo sapiens', 'COVID-19') for ds in datasets: print(f"{ds['Accession']}: {ds['n_samples']} samples - {ds['title'][:50]}...")
pythondef get_geo_ftp(gse_accession): '''Get FTP download link for a GSE''' handle = Entrez.esearch(db='gds', term=f'{gse_accession}[accn]') search = Entrez.read(handle) handle.close() if not search['IdList']: return None handle = Entrez.esummary(db='gds', id=search['IdList'][0]) summary = Entrez.read(handle)[0] handle.close() return summary.get('FTPLink') ftp_link = get_geo_ftp('GSE123456') print(f"Download from: {ftp_link}")
Many GEO RNA-seq datasets have associated SRA data.
pythondef geo_to_sra(gse_accession): '''Find SRA runs associated with a GEO series''' # Search GEO handle = Entrez.esearch(db='gds', term=f'{gse_accession}[accn]') search = Entrez.read(handle) handle.close() if not search['IdList']: return [] # Link to SRA handle = Entrez.elink(dbfrom='gds', db='sra', id=search['IdList'][0]) links = Entrez.read(handle) handle.close() if not links[0]['LinkSetDb']: return [] sra_ids = [link['Id'] for link in links[0]['LinkSetDb'][0]['Link']] # Get SRA accessions handle = Entrez.esummary(db='sra', id=','.join(sra_ids[:50])) summaries = Entrez.read(handle) handle.close() runs = [] for s in summaries: expxml = s.get('ExpXml', '') if 'SRR' in str(expxml) or 'SRX' in str(expxml): runs.append(s) return runs sra_data = geo_to_sra('GSE123456') print(f"Found {len(sra_data)} SRA records")
pythondef geo_from_pubmed(pmid): '''Find GEO datasets associated with a publication''' handle = Entrez.elink(dbfrom='pubmed', db='gds', id=pmid) links = Entrez.read(handle) handle.close() if not links[0]['LinkSetDb']: return [] gds_ids = [link['Id'] for link in links[0]['LinkSetDb'][0]['Link']] handle = Entrez.esummary(db='gds', id=','.join(gds_ids)) summaries = Entrez.read(handle) handle.close() return summaries datasets = geo_from_pubmed('35412348') for ds in datasets: print(f"{ds['Accession']}: {ds['title']}")
For full data parsing, use the GEOparse library:
python# pip install GEOparse import GEOparse # Download and parse GSE gse = GEOparse.get_GEO('GSE123456') # Access metadata print(f"Title: {gse.metadata['title'][0]}") print(f"Samples: {len(gse.gsms)}") # Get sample metadata for gsm_name, gsm in gse.gsms.items(): print(f"{gsm_name}: {gsm.metadata['title'][0]}") # Get expression table if gse.gpls: gpl_name = list(gse.gpls.keys())[0] expression_table = gse.pivot_samples('VALUE')
bash# Download entire GSE wget -r -np -nd ftp://ftp.ncbi.nlm.nih.gov/geo/series/GSE123nnn/GSE123456/ # Download specific file types wget ftp://ftp.ncbi.nlm.nih.gov/geo/series/GSE123nnn/GSE123456/suppl/*counts*.txt.gz
pythonimport gzip import urllib.request def download_series_matrix(gse): '''Download series matrix file''' gse_prefix = gse[:len(gse)-3] + 'nnn' url = f'https://ftp.ncbi.nlm.nih.gov/geo/series/{gse_prefix}/{gse}/matrix/{gse}_series_matrix.txt.gz' filename = f'{gse}_series_matrix.txt.gz' urllib.request.urlretrieve(url, filename) return filename
| Error | Cause | Solution | |-------|-------|----------| | Empty results | Wrong entry_type | Add gse[entry_type] or gds[entry_type] | | No FTPLink | Superseries or no data | Check if series has supplementary files | | No SRA link | Microarray data | SRA only for sequencing data |
Need GEO expression data?
├── Looking for curated datasets?
│ └── Search gds with [entry_type]=gds
├── Looking for any experiment?
│ └── Search gds with [entry_type]=gse
├── Want RNA-seq specifically?
│ └── Add 'expression profiling by high throughput sequencing[gdstype]'
├── Have a publication?
│ └── Link pubmed -> gds
├── Need raw sequencing data?
│ └── Link gds -> sra, then use sra-data skill
├── Need processed expression matrix?
│ └── Download series matrix or use GEOparse
└── Need full metadata?
└── Use GEOparse library<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 16,620 | 8,730 | -47% | 1 | 1 | 0% | 3,548 | 5,038 | +42% | 0 | 0 | — |
case-11 | pass→pass | 4,853 | 2,350 | -52% | 1 | 1 | 0% | 857 | 3,419 | +299% | 0 | 0 | — |
case-12 | pass→pass | 4,926 | 1,824 | -63% | 1 | 1 | 0% | 791 | 3,383 | +328% | 0 | 0 | — |
case-13 | fail→pass | 4,328 | 2,535 | -41% | 1 | 1 | 0% | 846 | 3,576 | +323% | 0 | 0 | — |
case-14 | pass→pass | 9,584 | 2,485 | -74% | 1 | 1 | 0% | 1,457 | 3,516 | +141% | 0 | 0 | — |
case-02 | fail→pass | 6,004 | 3,652 | -39% | 1 | 1 | 0% | 1,119 | 3,711 | +232% | 0 | 0 | — |
case-03 | fail→pass | 14,379 | 4,306 | -70% | 1 | 1 | 0% | 2,706 | 4,002 | +48% | 0 | 0 | — |
case-04 | fail→pass | 6,927 | 2,416 | -65% | 1 | 1 | 0% | 1,167 | 3,509 | +201% | 0 | 0 | — |
case-05 | pass→pass | 5,352 | 2,328 | -57% | 1 | 1 | 0% | 925 | 3,513 | +280% | 0 | 0 | — |
case-06 | pass→pass | 9,756 | 4,654 | -52% | 1 | 1 | 0% | 2,011 | 4,087 | +103% | 0 | 0 | — |
case-07 | pass→pass | 7,092 | 20,015 | +182% | 1 | 1 | 0% | 1,288 | 3,506 | +172% | 0 | 0 | — |
case-08 | pass→pass | 6,752 | 2,404 | -64% | 1 | 1 | 0% | 1,205 | 3,492 | +190% | 0 | 0 | — |
case-09 | pass→pass | 10,494 | 19,970 | +90% | 1 | 1 | 0% | 2,157 | 4,155 | +93% | 0 | 0 | — |
case-10 | pass→pass | 5,386 | 3,338 | -38% | 1 | 1 | 0% | 1,019 | 3,661 | +259% | 0 | 0 | — |
case-15 | pass→pass | 8,994 | 5,157 | -43% | 1 | 1 | 0% | 1,825 | 4,140 | +127% | 0 | 0 | — |
case-16 | pass→pass | 3,450 | 3,662 | +6% | 1 | 1 | 0% | 627 | 3,701 | +490% | 0 | 0 | — |
case-17 | pass→pass | 8,821 | 6,061 | -31% | 1 | 1 | 0% | 1,851 | 4,238 | +129% | 0 | 0 | — |
case-18 | pass→pass | 13,996 | 1,963 | -86% | 1 | 1 | 0% | 2,727 | 3,442 | +26% | 0 | 0 | — |
case-19 | fail→pass | 9,105 | 2,733 | -70% | 1 | 1 | 0% | 1,681 | 3,626 | +116% | 0 | 0 | — |
case-20 | pass→pass | 10,371 | 2,378 | -77% | 1 | 1 | 0% | 2,027 | 3,553 | +75% | 0 | 0 | — |
case-21 | pass→pass | 3,331 | 1,989 | -40% | 1 | 1 | 0% | 599 | 3,429 | +472% | 0 | 0 | — |
case-22 | pass→pass | 8,722 | 2,651 | -70% | 1 | 1 | 0% | 1,616 | 3,530 | +118% | 0 | 0 | — |
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. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/24/2026 | +55% |
Other measured skills in the registry, with their headline benchmark lift.