A series' BioProject holds experiments that are not the series'¶
Archive: NCBI GEO / SRA, read through ENA
Affected route: gse->experiment:soft_bioproject_ena
Status: measured; worked around in relations; declared on the route
Measured: from the 2026-09-15 census exports; re-checked live 2026-10-23
The number¶
The route takes the BioProject a series' SOFT file names and asks ENA for every experiment
in it. A project is not a series. It can also hold runs submitted to SRA directly under the
same project, or other series' runs. Over the census of 12,540 series that both this route
and soft_family resolve, 77 series get experiments the SOFT file does not name:
5,115 in all.
| Series | Extra experiments |
|---|---|
| GSE290975 | 2,362 |
| GSE175521 | 1,726 |
| GSE234261 | 232 |
| GSE98600 | 76 |
Reproducer¶
curl -s 'https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE234261&targ=self&form=text&view=brief' \
| grep -E 'BioProject|Series_sample_id' | sort | uniq -c | sort -rn | head -2
# 100 !Series_sample_id ... one BioProject: PRJNA980643
curl -s 'https://www.ebi.ac.uk/ena/portal/api/filereport?accession=PRJNA980643&result=read_run&fields=sample_alias&format=tsv' \
| tail -n +2 | cut -c1-3 | sort | uniq -c
# 100 GSM, and 232 runs whose aliases are T1-..., T6-...: submitted to SRA under the project, not in the series
Checked on 2026-10-23.
What it is not¶
- Not the umbrella project. That defect is a species-wide project holding thousands of unrelated series. This is a study's own project holding a little more than its series.
- Not wrong data. The extra experiments are real. They are not the series'.
Workaround¶
relations.py keeps only the project's runs whose experiment the series' SOFT file names. It
falls back to the whole project only when that filter would leave nothing: GSE150508's SOFT
names a stale experiment with no runs. The route itself answers by project, and --explain names this page
beside its values.
Affected accessions¶
python3 - <<'PY'
import csv, gzip, sys; csv.field_size_limit(sys.maxsize)
load = lambda n: {r["accession"]: set(filter(None, r["results"].split(";"))) for r in
csv.DictReader(gzip.open(f"data/surveys/reprocessed-gse/{n}.csv.gz", "rt")) if r["outcome"] == "resolved"}
a, b = load("gse-to-experiment_soft_bioproject_ena"), load("gse-to-experiment_soft_family")
print(sorted(g for g in set(a) & set(b) if a[g] - b[g]))
PY
Regression test¶
tests/pathologies/test_experiments_outside_the_series_bioproject.py asserts that
PRJNA980643 still holds runs whose sample alias is not one of GSE234261's GSMs.