logo Netzschleuder network catalog, repository, and centrifuge

History of pubmed_linqs

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: pubmed_linqs by Tiago de Paula Peixoto cdb81e9
diff --git a/entries/pubmed_linqs/__init__.py b/entries/pubmed_linqs/__init__.py
new file mode 100644
index 0000000..4caa127
--- /dev/null
+++ b/entries/pubmed_linqs/__init__.py
@@ -0,0 +1,124 @@
+#! /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/>.
+
+
+from .. import *
+
+
+title = "PubMed diabetes papers (LINQS, 2012)"
+description = """A directed citation network of scientific papers on diabetes from the PubMed database, from the "Pubmed-Diabetes" dataset distributed by the LINQS group (Namata et al.) as a benchmark for collective classification, also used (as one of the "Planetoid" datasets) in many works on graph neural networks. Each of the 19,717 papers is classified into one of three classes, after the Medical Subject Headings (MeSH) of the paper, and described by the TF-IDF weights of the 500 words of a vocabulary of word stems. The data also give, for each paper, the list of the word stems that appear in it (attribute "summary"), which is redundant with the TF-IDF weights and not included. See also the `cora_linqs` and `citeseer_linqs` entries.
+
+An edge from paper A to paper B means that A cites B. The data contain 3 self-loops and 11 pairs of papers that cite each other, which are kept.
+
+The network has the following properties:
+
+- `id`: the PubMed identifier of the paper.
+- `subject`: the class of the paper, one of `Diabetes Mellitus, Experimental`, `Diabetes Mellitus Type 1` and `Diabetes Mellitus Type 2`.
+- `words`: a vector of 500 values, where the i-th value is the TF-IDF weight of the i-th word of the vocabulary in the paper (0 if the word does not appear in it).
+- Graph property `vocabulary`: the word stems of the vocabulary, in the order of the values of `words`."""
+tags = ['Informational', 'Citation', 'Unweighted', 'Metadata', 'Text']
+url = ['https://linqs.org/datasets/#pubmed-diabetes',
+       'https://web.archive.org/web/20260307191008/https://linqs-data.soe.ucsc.edu/public/']
+citation = [('G. Namata, B. London, L. Getoor, B. Huang, "Query-driven Active Surveying for Collective Classification." International Workshop on Mining and Learning with Graphs (MLG) (2012)', 'https://linqs.org/assets/resources/namata-mlg12.pdf'),
+            ('P. Sen, G. Namata, M. Bilgic, L. Getoor, B. Gallagher, T. Eliassi-Rad, "Collective Classification in Network Data." AI Magazine 29(3), 93-106 (2008)', 'https://doi.org/10.1609/aimag.v29i3.2157')]
+icon_hash = None
+upstream_license = None
+# The LINQS host (https://linqs-data.soe.ucsc.edu/public/Pubmed-Diabetes.tgz)
+# was unreachable at the time of writing, so we use the Internet Archive's
+# copy
+files = [(('Pubmed-Diabetes.tgz:Pubmed-Diabetes/data/Pubmed-Diabetes.DIRECTED.cites.tab',
+           'Pubmed-Diabetes.tgz:Pubmed-Diabetes/data/Pubmed-Diabetes.NODE.paper.tab'),
+          None, None)]
+archived_urls = {
+    'Pubmed-Diabetes.tgz': 'https://web.archive.org/web/20260307191008id_/https://linqs-data.soe.ucsc.edu/public/Pubmed-Diabetes.tgz'}
+
+subjects = {"1": "Diabetes Mellitus, Experimental",
+            "2": "Diabetes Mellitus Type 1",
+            "3": "Diabetes Mellitus Type 2"}
+
+def fetch_upstream(force=False):
+    return fetch_archived_files(__name__.split(".")[-1], archived_urls, force,
+                                tries=4)
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+    global files
+    name = __name__.split(".")[-1]
+    for fnames, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        with ExitStack() as stack:
+            fs = [stack.enter_context(open_upstream_file(name, fn, "r")) for fn in fnames]
+
+            # Papers: a title line, a line with the attributes
+            # ("cat=1,2,3:label", "numeric:w-<stem>:0.0" for each word,
+            # "string:summary"), then one line per paper with its id and
+            # its non-zero attributes as "<name>=<value>"
+            next(fs[1])
+            header = next(fs[1]).rstrip("\n").split("\t")
+            vocab = [h.split(":")[1] for h in header if h.startswith("numeric:")]
+            widx = {w: i for i, w in enumerate(vocab)}
+            ids = []
+            labels = []
+            words = []
+            for line in fs[1]:
+                row = line.rstrip("\n").split("\t")
+                if not row[0]:
+                    continue
+                ids.append(int(row[0]))
+                x = numpy.zeros(len(vocab))
+                label = None
+                for a in row[1:]:
+                    k, val = a.split("=", 1)
+                    if k == "label":
+                        label = subjects[val]
+                    elif k in widx:
+                        x[widx[k]] = float(val)
+                    else:
+                        assert k == "summary", k
+                labels.append(label)
+                words.append(x)
+            idx = {i: v for v, i in enumerate(ids)}
+            assert len(idx) == len(ids)
+
+            g = Graph(directed=True)
+            g.add_vertex(len(ids))
+            g.vp.id = g.new_vp("int64_t", vals=ids)
+            g.vp.subject = g.new_vp("string", vals=labels)
+            g.vp.words = g.new_vp("vector<double>")
+            for v, x in enumerate(words):
+                g.vp.words[v] = x
+            g.gp.vocabulary = g.new_gp("vector<string>",
+                                       val=[w.removeprefix("w-") for w in vocab])
+
+            # Citations: "<id> paper:<citing> | paper:<cited>", after two
+            # header lines
+            next(fs[0])
+            next(fs[0])
+            edges = []
+            for line in fs[0]:
+                row = line.split()
+                if not row:
+                    continue
+                assert row[2] == "|"
+                edges.append((idx[int(row[1].removeprefix("paper:"))],
+                              idx[int(row[3].removeprefix("paper:"))]))
+            g.add_edge_list(edges)
+        yield alt, g