History of stitch_chemicals
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] New entry: stitch_chemicals 9aab542
diff --git a/entries/stitch_chemicals/__init__.py b/entries/stitch_chemicals/__init__.py
new file mode 100644
index 0000000..4753c5d
--- /dev/null
+++ b/entries/stitch_chemicals/__init__.py
@@ -0,0 +1,170 @@
+#! /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 pyarrow
+import pyarrow.csv
+import pyarrow.compute as pc
+
+from .. import *
+
+ORGANISMS = {"9606": "Homo sapiens", "10090": "Mus musculus",
+ "4932": "Saccharomyces cerevisiae", "511145": "Escherichia coli K-12 MG1655",
+ "7227": "Drosophila melanogaster", "6239": "Caenorhabditis elegans",
+ "3702": "Arabidopsis thaliana"}
+
+title = "STITCH chemical-chemical and protein-chemical associations (2015)"
+description = r"""Networks of known and predicted associations between chemicals, and between chemicals and proteins, from STITCH ("search tool for interactions of chemicals"), version 5.0, the companion database of STRING (see the entries `string_all` and `string_ppi`). The chemicals are those of PubChem; STITCH merges the salt forms of a compound, and distinguishes the stereo-specific compounds (identifiers `CIDs...`) from the "flat" compounds whose stereoisomers are merged (`CIDm...`); the suffix is the PubChem compound identifier (CID). The proteins are those of STRING 10.
+
+STITCH combines evidence from several channels, each of which gives a confidence score, into a combined confidence score: experiments (e.g. binding affinities from ChEMBL, PDSP Ki, PDB), curated databases (e.g. DrugBank, KEGG, GLIDA, Matador, CTD), co-mention in the scientific literature (text mining), and, for protein-chemical links, predictions (e.g. from co-occurrence in pathways), and, for chemical-chemical links, the structural similarity of the chemicals. Scores are multiplied by 1000 (the lowest combined score in the files is 150).
+
+- `chemical_chemical`: an undirected network of chemicals, with an edge between two associated chemicals (both orientations of each pair are listed upstream; each pair is an edge once). A few chemicals (34) are associated with themselves, as self-loops.
+- `protein_chemical_<taxid>`: a bipartite network of the chemicals and the proteins of an organism, with an edge between a chemical and a protein associated with it. STITCH has such networks for 2,031 organisms (31 GB of files); only those of the seven model organisms of the entry `string_ppi` are included, named after their NCBI taxonomy identifiers: `9606` (human), `10090` (mouse), `4932` (yeast *Saccharomyces cerevisiae*), `511145` (*Escherichia coli* K-12 MG1655), `7227` (fruit fly *Drosophila melanogaster*), `6239` (*Caenorhabditis elegans*) and `3702` (*Arabidopsis thaliana*).
+
+Left out are the associations transferred between organisms by orthology (a separate file of STITCH), and the modes of action (activation, inhibition, binding, ...), which are only available for a subset of the associations.
+
+The networks have the following properties:
+
+- `stitch_id`: the identifier of the node in STITCH: `CIDm<CID>` or `CIDs<CID>` (with the CID padded with zeros to 8 digits) for chemicals, `<taxid>.<protein id>` (e.g. `9606.ENSP00000257254`) for proteins.
+- `name`: the name of the chemical (empty for proteins).
+- `molecular_weight`: the molecular weight of the chemical (NaN for proteins).
+- `smiles`: the structure of the chemical, as a SMILES string (empty for proteins).
+- `is_chemical` (`protein_chemical_<taxid>` networks): whether the node is a chemical (otherwise it is a protein).
+- Edge `combined_score`: the combined confidence score of the association (from 150 to 999).
+- Edge `experimental`, `database`, `textmining`: the scores of the corresponding channels (0 if there is no evidence of that kind).
+- Edge `similarity` (`chemical_chemical`): the score of the structural similarity of the chemicals.
+- Edge `prediction` (`protein_chemical_<taxid>`): the score of the predictions.
+- `taxon_id`, `organism` (graph, `protein_chemical_<taxid>`): the NCBI taxonomy identifier and the name of the organism."""
+tags = ['Biological', 'Chemical', 'Drug interactions', 'Weighted', 'Metadata']
+url = 'http://stitch-db.org/cgi/download.pl'
+citation = [('D. Szklarczyk, A. Santos, C. von Mering, L. J. Jensen, P. Bork, M. Kuhn, "STITCH 5: augmenting protein-chemical interaction networks with tissue and affinity data." Nucleic Acids Research 44, D380-D384 (2016)',
+ 'https://doi.org/10.1093/nar/gkv1277')]
+icon_hash = None
+# The files with the combined scores are CC BY 4.0; those with the scores of
+# the channels (".detailed"), which also contain the combined scores, are CC
+# BY-NC-SA 4.0
+upstream_license = ("CC BY-NC-SA 4.0 (detailed scores; the combined scores and the chemicals are CC BY 4.0)",
+ "http://creativecommons.org/licenses/by-nc-sa/4.0/")
+# STITCH 5.0 (2015) has not been updated since. The https URLs fail for
+# scripts; the http ones work.
+upstream_prefix = 'http://stitch-db.org/download'
+CHEMICALS = "chemicals.v5.0.tsv.gz"
+files = [(("chemical_chemical.links.detailed.v5.0.tsv.gz", CHEMICALS),
+ "chemical_chemical", None)] + \
+ [((f"protein_chemical.links.detailed.v5.0/{t}.protein_chemical.links.detailed.v5.0.tsv.gz",
+ CHEMICALS), f"protein_chemical_{t}", None) for t in ORGANISMS]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+def read_links(f, ids, scores):
+ "The table of links of the detailed file `f`."
+ return pyarrow.csv.read_csv(
+ f,
+ read_options=pyarrow.csv.ReadOptions(block_size=1 << 26),
+ parse_options=pyarrow.csv.ParseOptions(delimiter="\t", quote_char=False),
+ convert_options=pyarrow.csv.ConvertOptions(
+ column_types=dict({k: pyarrow.string() for k in ids},
+ **{k: pyarrow.int16() for k in scores})))
+
+def set_chemical_props(g, name, fname, ids):
+ """Set the name, molecular weight and SMILES of the chemicals whose STITCH
+ ids are `ids` (a dict of id -> vertex index), streamed from the chemicals
+ file (68 million chemicals)."""
+ names = [""] * g.num_vertices()
+ smiles = [""] * g.num_vertices()
+ mw = numpy.full(g.num_vertices(), numpy.nan)
+ idset = pyarrow.array(list(ids.keys()))
+ with open_upstream_file(name, fname, "rb") as f:
+ reader = pyarrow.csv.open_csv(
+ f,
+ read_options=pyarrow.csv.ReadOptions(block_size=1 << 26),
+ parse_options=pyarrow.csv.ParseOptions(delimiter="\t", quote_char=False),
+ convert_options=pyarrow.csv.ConvertOptions(
+ column_types={"chemical": pyarrow.string(), "name": pyarrow.string(),
+ "molecular_weight": pyarrow.float64(),
+ "SMILES_string": pyarrow.string()}))
+ for batch in reader:
+ keep = pc.is_in(batch.column("chemical"), value_set=idset)
+ if not pc.any(keep).as_py():
+ continue
+ b = batch.filter(keep)
+ for c, n, w, s in zip(b.column("chemical").to_pylist(),
+ b.column("name").to_pylist(),
+ b.column("molecular_weight").to_pylist(),
+ b.column("SMILES_string").to_pylist()):
+ v = ids[c]
+ names[v] = n or ""
+ smiles[v] = s or ""
+ mw[v] = numpy.nan if w is None else w
+ g.vp.name = g.new_vp("string", vals=names)
+ g.vp.molecular_weight = g.new_vp("double", vals=mw)
+ g.vp.smiles = g.new_vp("string", vals=smiles)
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ for fnames, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ if alt == "chemical_chemical":
+ idc = ["chemical1", "chemical2"]
+ scores = ["similarity", "experimental", "database", "textmining",
+ "combined_score"]
+ else:
+ idc = ["chemical", "protein"]
+ scores = ["experimental", "prediction", "database", "textmining",
+ "combined_score"]
+ with open_upstream_file(name, fnames[0], "rb") as f:
+ t = read_links(f, idc, scores)
+ if alt == "chemical_chemical":
+ # Both orientations of each pair are listed, with the same
+ # scores: keep one (and the few self-loops, listed once)
+ n = len(t)
+ t = t.filter(pc.less_equal(t.column(idc[0]), t.column(idc[1])))
+ loops = pc.sum(pc.equal(t.column(idc[0]), t.column(idc[1]))).as_py()
+ assert 2 * len(t) - loops == n, (n, len(t), loops)
+ ids = pc.unique(pyarrow.chunked_array(t.column(idc[0]).chunks +
+ t.column(idc[1]).chunks))
+ else:
+ ids = pyarrow.concat_arrays([pc.unique(t.column(idc[0])),
+ pc.unique(t.column(idc[1]))])
+ ids = pc.take(ids, pc.sort_indices(ids))
+ s = pc.index_in(t.column(idc[0]), value_set=ids).to_numpy()
+ u = pc.index_in(t.column(idc[1]), value_set=ids).to_numpy()
+
+ g = Graph(directed=False)
+ g.add_vertex(len(ids))
+ g.add_edge_list(numpy.column_stack([s, u]))
+ del s, u
+ for k in scores:
+ g.ep[k] = g.new_ep("int16_t", vals=t.column(k).to_numpy())
+ del t
+ ids = ids.to_pylist()
+ g.vp.stitch_id = g.new_vp("string", vals=ids)
+ is_chem = numpy.array([x.startswith("CID") for x in ids])
+ if alt != "chemical_chemical":
+ g.vp.is_chemical = g.new_vp("bool", vals=is_chem)
+ taxid = alt.split("_")[-1]
+ g.gp.taxon_id = g.new_gp("int", val=int(taxid))
+ g.gp.organism = g.new_gp("string", val=ORGANISMS[taxid])
+ set_chemical_props(g, name, fnames[1],
+ {x: v for v, x in enumerate(ids) if is_chem[v]})
+ yield alt, g