GEO sample → experiment, BioSample and series¶
Needs: a GSM accession Feeds: experiment → run → files
Routes: 3, all from GEO's own per-sample record
Until these existed a GSM was a dead end. The graph could reach one from a series but not
leave it, so half the GEO entry points resolved to nothing.
Routes¶
All three read acc.cgi?acc=<GSM>&targ=self&form=text&view=brief, which returns GEO's SOFT
record for a single sample. One request each, and one request serves all three if the record
is parsed once.
| Route | Reads |
|---|---|
geo_sample->experiment:acc_cgi |
!Sample_relation = SRA: |
geo_sample->biosample:acc_cgi |
!Sample_relation = BioSample: |
geo_sample->geo_series:acc_cgi |
!Sample_series_id |
Do not use these to enumerate a series¶
The per-sample record is the wrong tool for listing a series' samples. acc.cgi is a web
endpoint rather than an API, view=brief still returns the submitter's full protocol prose
— tens of kilobytes per sample — and a series of 2,396 samples would mean 2,396 requests.
The SOFT family file returns every sample and every
relation in one request. These routes are for when a GSM is the input, not for
walking down from a series.
A sample can belong to several series¶
GSM7518069 is in both GSE236084 and GSE236087. Routing from a sample to "its" series
cannot assume there is one, which is why geo_sample->geo_series returns a list and the
tier-one corpus contains that accession specifically.
The series-level pathologies show up here too¶
The same records back both views, so a sample inherits whatever its series omits. On the tier-one corpus:
| Sample | From | experiment | biosample |
|---|---|---|---|
| GSM7518069 | GSE236084 (healthy) | resolves | resolves |
| GSM4005486 | GSE135325 | empty | resolves |
| GSM4274734 | GSE150508 | resolves — but the experiment carries no runs | resolves |
GSM4005486 is the useful one: GEO records its BioSample and no SRA relation, which is exactly what its series does, so the pathology is a property of the record rather than of the family-file parser.
GSM4274734 is the more dangerous shape. The route resolves, the answer looks ordinary, and the experiment it names has no data — which is only visible by confirming against a data-proving route.
Measured¶
Corpus: sample:1000@reprocessed-sample — a seeded draw of 1,000 from the
104,515 samples in the reprocessed table. 825 of those 1,000 are GEO samples;
the other 175 are INSDC samples these routes cannot accept, and the runner filters
them out rather than counting them as failures. Surveyed: 2026-09-15.
All three directions come from the same acc.cgi record, and all three behave
identically on it:
| Route | Resolved | Empty | Failed | Unique results |
|---|---|---|---|---|
geo_sample->experiment:acc_cgi |
822 | 0 | 3 | 822 |
geo_sample->biosample:acc_cgi |
822 | 0 | 3 | 822 |
geo_sample->geo_series:acc_cgi |
822 | 0 | 3 | 849 |
The same 3 failures in each case — one record, one fetch, one outcome.
849 series for 822 samples. 27 samples belong to more than one series, which is the multi-series membership case above measured rather than asserted: 3.3% of samples, common enough that code treating "its series" as singular is wrong for one sample in thirty.
Why zero empties is not the good news it looks like¶
Every one of the 822 named an experiment. That is not evidence against GEO omitting the SRA relation: the reprocessed table contains only samples the pipeline succeeded on, so a sample GEO records no relation for is largely excluded from this corpus by construction. The corpus can measure how reliable the route is for samples that are known to have worked; it cannot measure how often GEO leaves a sample unreachable, because those samples are not in it.
Measuring that needs a draw from all of GEO — geo-sample-<n> — which is what
the series-level census used.
Tier one (3 accessions) is described above, and is a coverage check rather than a prevalence estimate — see benchmarks on why the two cannot substitute for each other.