History of gwas_catalog
The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.
[entries] Add the date when each entry was added, and remove icon_hash beff7c9
The dates are those of the commits that first added the entries' files (2020-08-11 for the entries of the initial commit); renamed entries keep the date of their first name. Changed: none
diff --git a/entries/gwas_catalog/__init__.py b/entries/gwas_catalog/__init__.py
index e62e548..292753a 100644
--- a/entries/gwas_catalog/__init__.py
+++ b/entries/gwas_catalog/__init__.py
@@ -79,7 +79,7 @@ tags = ['Biological', 'Genetic', 'Unweighted', 'Bipartite', 'Multigraph', 'Times
url = ['https://www.ebi.ac.uk/gwas/', 'https://ftp.ebi.ac.uk/pub/databases/gwas/releases/']
citation = [('M. Cerezo, E. Sollis, Y. Ji, E. Lewis, A. Abid, K. O. Bircan, P. Hall, J. Hayhurst, S. John, A. Mosaku, et al., "The NHGRI-EBI GWAS Catalog: standards for reusability, sustainability and diversity." Nucleic Acids Research 53, D998-D1005 (2025)',
'https://doi.org/10.1093/nar/gkae1070')]
-icon_hash = None
+date_added = '2026-10-31'
upstream_license = ('Terms of Use for EMBL-EBI Services', 'https://www.ebi.ac.uk/about/terms-of-use')
upstream_prefix = 'https://ftp.ebi.ac.uk/pub/databases/gwas/releases'
files = [((assoc_file(alt, d), f"{d}/gwas-efo-trait-mappings.tsv"), alt, None)
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) bc3dbbd
The lists of properties at the end of the descriptions marked edge properties with "Edge" but left node properties unmarked, or grouped them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property was often unclear. Every item now starts with (Node), (Edge) or (Graph), followed by the property names, a colon and the explanation, one item per property or group of properties; qualifications (kinds of nodes, networks) follow the names in parentheses. Properties that were only named got a short explanation, and a few items that named the wrong kind of property were corrected.
diff --git a/entries/gwas_catalog/__init__.py b/entries/gwas_catalog/__init__.py
index f200e83..e62e548 100644
--- a/entries/gwas_catalog/__init__.py
+++ b/entries/gwas_catalog/__init__.py
@@ -58,23 +58,23 @@ There is one network for each year from 2016 to 2026, from the last release of t
The networks have the following properties:
-- `is_variant`: whether the node is a variant (otherwise it is a trait).
-- `is_mapped`: whether the node is a trait mapped to an ontology term (false for the traits of the associations that are not mapped to any term, which are named by the reported trait, and for variants).
-- `name`: the identifier of the variant (e.g. `rs4950928`), or the name of the trait term (from the mappings of the reported traits of the same release; the name given in the associations if the term is not there), or the reported trait of the unmapped traits.
-- `trait_uri`: the URI of the trait term (e.g. `http://www.ebi.ac.uk/efo/EFO_0000676`; empty for variants and unmapped traits).
-- `parent_trait`: the category of the trait (e.g. `Cancer`, `Cardiovascular disease`, from the mappings of the reported traits; empty for variants).
-- `chr_id`, `chr_pos`, `region`: the chromosome, the position on the chromosome (in the genome assembly of the release) and the cytogenetic region of the variant (empty for traits, and for variants found only in associations of several variants).
-- `mapped_gene`: the genes mapped to the variant by the catalog (genes containing the variant, or the nearest upstream and downstream genes).
-- `context`: the functional class of the variant (e.g. `intron_variant`).
-- Edge `study`: the accession of the study (e.g. `GCST000177`).
-- Edge `pubmed_id`: the PubMed identifier of the publication of the study.
-- Edge `date_added`: the date at which the association was added to the catalog (as Unix time).
-- Edge `reported_trait`: the trait as reported in the study.
-- Edge `snps`: all the variants of the association, as given by the catalog.
-- Edge `risk_allele`: the strongest variant and risk allele (e.g. `rs4950928-G`; `?` if the allele is unknown).
-- Edge `risk_allele_frequency`: the frequency of the risk allele in the controls (NaN if not given).
-- Edge `p_value`: the reported $p$-value of the association; `p_value_mlog`: $-\log_{10}$ of the $p$-value; `p_value_text`: additional information about the $p$-value (e.g. the subgroup).
-- Edge `or_beta`: the reported odds ratio, or the beta coefficient (NaN if not given); `ci_text`: the 95% confidence interval, and the unit of the beta coefficient."""
+- (Node) `is_variant`: whether the node is a variant (otherwise it is a trait).
+- (Node) `is_mapped`: whether the node is a trait mapped to an ontology term (false for the traits of the associations that are not mapped to any term, which are named by the reported trait, and for variants).
+- (Node) `name`: the identifier of the variant (e.g. `rs4950928`), or the name of the trait term (from the mappings of the reported traits of the same release; the name given in the associations if the term is not there), or the reported trait of the unmapped traits.
+- (Node) `trait_uri`: the URI of the trait term (e.g. `http://www.ebi.ac.uk/efo/EFO_0000676`; empty for variants and unmapped traits).
+- (Node) `parent_trait`: the category of the trait (e.g. `Cancer`, `Cardiovascular disease`, from the mappings of the reported traits; empty for variants).
+- (Node) `chr_id`, `chr_pos`, `region`: the chromosome, the position on the chromosome (in the genome assembly of the release) and the cytogenetic region of the variant (empty for traits, and for variants found only in associations of several variants).
+- (Node) `mapped_gene`: the genes mapped to the variant by the catalog (genes containing the variant, or the nearest upstream and downstream genes).
+- (Node) `context`: the functional class of the variant (e.g. `intron_variant`).
+- (Edge) `study`: the accession of the study (e.g. `GCST000177`).
+- (Edge) `pubmed_id`: the PubMed identifier of the publication of the study.
+- (Edge) `date_added`: the date at which the association was added to the catalog (as Unix time).
+- (Edge) `reported_trait`: the trait as reported in the study.
+- (Edge) `snps`: all the variants of the association, as given by the catalog.
+- (Edge) `risk_allele`: the strongest variant and risk allele (e.g. `rs4950928-G`; `?` if the allele is unknown).
+- (Edge) `risk_allele_frequency`: the frequency of the risk allele in the controls (NaN if not given).
+- (Edge) `p_value`: the reported $p$-value of the association; `p_value_mlog`: $-\log_{10}$ of the $p$-value; `p_value_text`: additional information about the $p$-value (e.g. the subgroup).
+- (Edge) `or_beta`: the reported odds ratio, or the beta coefficient (NaN if not given); `ci_text`: the 95% confidence interval, and the unit of the beta coefficient."""
tags = ['Biological', 'Genetic', 'Unweighted', 'Bipartite', 'Multigraph', 'Timestamps', 'Metadata']
url = ['https://www.ebi.ac.uk/gwas/', 'https://ftp.ebi.ac.uk/pub/databases/gwas/releases/']
citation = [('M. Cerezo, E. Sollis, Y. Ji, E. Lewis, A. Abid, K. O. Bircan, P. Hall, J. Hayhurst, S. John, A. Mosaku, et al., "The NHGRI-EBI GWAS Catalog: standards for reusability, sustainability and diversity." Nucleic Acids Research 53, D998-D1005 (2025)',
[entries] New entry: gwas_catalog 8493b97
diff --git a/entries/gwas_catalog/__init__.py b/entries/gwas_catalog/__init__.py
new file mode 100644
index 0000000..f200e83
--- /dev/null
+++ b/entries/gwas_catalog/__init__.py
@@ -0,0 +1,233 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import csv
+import datetime as dt
+import math
+import re
+import sys
+from collections import defaultdict
+
+from .. import *
+
+# The last release of each year in the release archive (releases since
+# March 2016), with the associations annotated with ontology terms (a zip
+# file since 2025) and the mappings of the reported traits to ontology terms
+RELEASES = {
+ "2016": "2016/12/13",
+ "2017": "2017/12/13",
+ "2018": "2018/12/27",
+ "2019": "2019/12/16",
+ "2020": "2020/12/16",
+ "2021": "2021/12/22",
+ "2022": "2022/12/22",
+ "2023": "2023/12/21",
+ "2024": "2024/12/19",
+ "2025": "2025/12/23",
+ "2026": "2026/10/22",
+}
+ZIPPED = {"2025", "2026"}
+
+def assoc_file(alt, d):
+ if alt in ZIPPED:
+ return (f"{d}/gwas-catalog-associations_ontology-annotated-full.zip:"
+ "gwas-catalog-download-associations-alt-full.tsv")
+ return f"{d}/gwas-catalog-associations_ontology-annotated.tsv"
+
+title = "GWAS Catalog variant-trait associations (2016-2026)"
+description = r"""Bipartite networks of genetic variants and the traits they are associated with, from the NHGRI-EBI Catalog of human genome-wide association studies (GWAS Catalog), which curates the associations reported in the published GWAS (with $p < 10^{-5}$) together with the details of the studies.
+
+Nodes are variants (mostly single-nucleotide polymorphisms, identified by their dbSNP rsID, or by their position or another name when they have none) and traits (diseases, measurements and other phenotypes, as terms of the Experimental Factor Ontology, EFO, or of ontologies it imports, such as MONDO, HP, Orphanet or GO). There is an edge between a variant and a trait for each association reported in a study, so that a variant and a trait can be connected by several edges (e.g. associations found in several studies, or for several reported traits that are mapped to the same term). An association can involve several variants (haplotypes, written with `;`, and interactions between variants, written with ` x `) and several traits (a reported trait mapped to several terms): it then gives an edge between each of its variants and each of its traits. Associations that are not mapped to any trait term (up to 2% of the associations of a release) are kept: their trait is a node named by the trait as reported in the study (one node per distinct reported trait, without a term), marked by `is_mapped` false.
+
+There is one network for each year from 2016 to 2026, from the last release of the year in the release archive of the catalog (which starts in March 2016): 2016-12-13, 2017-12-13, 2018-12-27, 2019-12-16, 2020-12-16, 2021-12-22, 2022-12-22, 2023-12-21, 2024-12-19, 2025-12-23 and 2026-10-22 (the latest one).
+
+The networks have the following properties:
+
+- `is_variant`: whether the node is a variant (otherwise it is a trait).
+- `is_mapped`: whether the node is a trait mapped to an ontology term (false for the traits of the associations that are not mapped to any term, which are named by the reported trait, and for variants).
+- `name`: the identifier of the variant (e.g. `rs4950928`), or the name of the trait term (from the mappings of the reported traits of the same release; the name given in the associations if the term is not there), or the reported trait of the unmapped traits.
+- `trait_uri`: the URI of the trait term (e.g. `http://www.ebi.ac.uk/efo/EFO_0000676`; empty for variants and unmapped traits).
+- `parent_trait`: the category of the trait (e.g. `Cancer`, `Cardiovascular disease`, from the mappings of the reported traits; empty for variants).
+- `chr_id`, `chr_pos`, `region`: the chromosome, the position on the chromosome (in the genome assembly of the release) and the cytogenetic region of the variant (empty for traits, and for variants found only in associations of several variants).
+- `mapped_gene`: the genes mapped to the variant by the catalog (genes containing the variant, or the nearest upstream and downstream genes).
+- `context`: the functional class of the variant (e.g. `intron_variant`).
+- Edge `study`: the accession of the study (e.g. `GCST000177`).
+- Edge `pubmed_id`: the PubMed identifier of the publication of the study.
+- Edge `date_added`: the date at which the association was added to the catalog (as Unix time).
+- Edge `reported_trait`: the trait as reported in the study.
+- Edge `snps`: all the variants of the association, as given by the catalog.
+- Edge `risk_allele`: the strongest variant and risk allele (e.g. `rs4950928-G`; `?` if the allele is unknown).
+- Edge `risk_allele_frequency`: the frequency of the risk allele in the controls (NaN if not given).
+- Edge `p_value`: the reported $p$-value of the association; `p_value_mlog`: $-\log_{10}$ of the $p$-value; `p_value_text`: additional information about the $p$-value (e.g. the subgroup).
+- Edge `or_beta`: the reported odds ratio, or the beta coefficient (NaN if not given); `ci_text`: the 95% confidence interval, and the unit of the beta coefficient."""
+tags = ['Biological', 'Genetic', 'Unweighted', 'Bipartite', 'Multigraph', 'Timestamps', 'Metadata']
+url = ['https://www.ebi.ac.uk/gwas/', 'https://ftp.ebi.ac.uk/pub/databases/gwas/releases/']
+citation = [('M. Cerezo, E. Sollis, Y. Ji, E. Lewis, A. Abid, K. O. Bircan, P. Hall, J. Hayhurst, S. John, A. Mosaku, et al., "The NHGRI-EBI GWAS Catalog: standards for reusability, sustainability and diversity." Nucleic Acids Research 53, D998-D1005 (2025)',
+ 'https://doi.org/10.1093/nar/gkae1070')]
+icon_hash = None
+upstream_license = ('Terms of Use for EMBL-EBI Services', 'https://www.ebi.ac.uk/about/terms-of-use')
+upstream_prefix = 'https://ftp.ebi.ac.uk/pub/databases/gwas/releases'
+files = [((assoc_file(alt, d), f"{d}/gwas-efo-trait-mappings.tsv"), alt, None)
+ for alt, d in RELEASES.items()]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+_check_rows = None
+_verbose = False
+
+def to_float(x):
+ try:
+ return float(x)
+ except ValueError:
+ return math.nan
+
+def to_time(x):
+ try:
+ return int(dt.datetime.strptime(x, "%Y-%m-%d").replace(
+ tzinfo=dt.timezone.utc).timestamp())
+ except ValueError:
+ return -1
+
+def split_snps(s):
+ "The variants of an association (haplotypes with ';', interactions with ' x ')."
+ return [x for x in (t.strip() for t in re.split(r";| x |,", s)) if x]
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ csv.field_size_limit(sys.maxsize)
+ for (afile, mfile), alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+
+ # names and categories of the trait terms
+ tname, tparent = {}, {}
+ with open_upstream_file(name, mfile, "r") as f:
+ reader = csv.reader(f, delimiter="\t", quoting=csv.QUOTE_NONE)
+ h = next(reader)
+ c = {x: i for i, x in enumerate(h)}
+ for row in reader:
+ if len(row) != len(h):
+ continue
+ u = row[c["EFO URI"]].strip()
+ tname.setdefault(u, row[c["EFO term"]].strip())
+ tparent.setdefault(u, row[c["Parent term"]].strip())
+
+ vidx = {}
+ vvals = defaultdict(list)
+ VKEYS = ["name", "trait_uri", "parent_trait", "chr_id", "chr_pos",
+ "region", "mapped_gene", "context"]
+ def add_vertex(key, is_v, is_mapped=False, **props):
+ v = vidx.get((is_v, key))
+ if v is None:
+ v = vidx[(is_v, key)] = len(vidx)
+ vvals["is_variant"].append(is_v)
+ vvals["is_mapped"].append(is_mapped)
+ for k in VKEYS:
+ vvals[k].append(props.get(k, ""))
+ elif is_v and props.get("chr_id") and not vvals["chr_id"][v]:
+ for k in VKEYS[3:]:
+ vvals[k][v] = props.get(k, "")
+ return v
+
+ edges = []
+ evals = defaultdict(list)
+ strings = {}
+ ECOLS = {"STUDY ACCESSION": "study", "PUBMEDID": "pubmed_id",
+ "DISEASE/TRAIT": "reported_trait", "SNPS": "snps",
+ "STRONGEST SNP-RISK ALLELE": "risk_allele",
+ "P-VALUE (TEXT)": "p_value_text", "95% CI (TEXT)": "ci_text"}
+ nrows = unmapped = 0
+ with open_upstream_file(name, afile, "r") as f:
+ reader = csv.reader(f, delimiter="\t", quoting=csv.QUOTE_NONE)
+ h = next(reader)
+ c = {x: i for i, x in enumerate(h)}
+ for i, row in enumerate(reader):
+ if _check_rows is not None and i >= _check_rows:
+ break
+ if len(row) != len(h):
+ raise ValueError(f"{alt}: malformed row {i}: {row[:3]}")
+ nrows += 1
+ uris = [u.strip() for u in row[c["MAPPED_TRAIT_URI"]].split(",")
+ if u.strip()]
+ tnames = [t.strip() for t in row[c["MAPPED_TRAIT"]].split(",")]
+ if len(tnames) != len(uris):
+ tnames = [""] * len(uris)
+ snps = split_snps(row[c["SNPS"]])
+ single = len(snps) == 1
+ vs = [add_vertex(s, True, name=s,
+ chr_id=row[c["CHR_ID"]] if single else "",
+ chr_pos=row[c["CHR_POS"]] if single else "",
+ region=row[c["REGION"]] if single else "",
+ mapped_gene=row[c["MAPPED_GENE"]] if single else "",
+ context=row[c["CONTEXT"]] if single else "")
+ for s in snps]
+ ts = [add_vertex(u, False, is_mapped=True, name=tname.get(u) or tn,
+ trait_uri=u, parent_trait=tparent.get(u, ""))
+ for u, tn in zip(uris, tnames)]
+ if not uris:
+ # an association not mapped to any trait term: its trait
+ # is a node named by the reported trait (keyed apart
+ # from the terms)
+ unmapped += 1
+ rt = row[c["DISEASE/TRAIT"]].strip()
+ ts = [add_vertex(("reported", rt), False, name=rt)]
+ ev = {p: row[c[col]] for col, p in ECOLS.items()}
+ ev = {p: strings.setdefault(x, x) for p, x in ev.items()}
+ pv = to_float(row[c["P-VALUE"]])
+ pm = to_float(row[c["PVALUE_MLOG"]])
+ orb = to_float(row[c["OR or BETA"]])
+ raf = to_float(row[c["RISK ALLELE FREQUENCY"]])
+ da = to_time(row[c["DATE ADDED TO CATALOG"]])
+ for v in vs:
+ for t in ts:
+ edges.append((v, t))
+ for p, x in ev.items():
+ evals[p].append(x)
+ evals["p_value"].append(pv)
+ evals["p_value_mlog"].append(pm)
+ evals["or_beta"].append(orb)
+ evals["risk_allele_frequency"].append(raf)
+ evals["date_added"].append(da)
+ if _verbose:
+ print(alt, "rows:", nrows, "unmapped:", unmapped)
+
+ g = Graph(directed=False)
+ g.add_vertex(len(vidx))
+ g.add_edge_list(numpy.array(edges, dtype="int64").reshape(-1, 2))
+ del edges
+ g.vp.is_variant = g.new_vp("bool", vals=vvals["is_variant"])
+ g.vp.is_mapped = g.new_vp("bool", vals=vvals["is_mapped"])
+ for k in VKEYS:
+ g.vp[k] = g.new_vp("string", vals=vvals[k])
+ for p in ["study", "pubmed_id"]:
+ g.ep[p] = g.new_ep("string", vals=evals.pop(p))
+ g.ep.date_added = g.new_ep("int64_t", vals=evals.pop("date_added"))
+ for p in ["reported_trait", "snps", "risk_allele"]:
+ g.ep[p] = g.new_ep("string", vals=evals.pop(p))
+ g.ep.risk_allele_frequency = g.new_ep("double", vals=evals.pop("risk_allele_frequency"))
+ g.ep.p_value = g.new_ep("double", vals=evals.pop("p_value"))
+ g.ep.p_value_mlog = g.new_ep("double", vals=evals.pop("p_value_mlog"))
+ g.ep.p_value_text = g.new_ep("string", vals=evals.pop("p_value_text"))
+ g.ep.or_beta = g.new_ep("double", vals=evals.pop("or_beta"))
+ g.ep.ci_text = g.new_ep("string", vals=evals.pop("ci_text"))
+ yield alt, g