History of amazon_coauthor_shchur
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: amazon_coauthor_shchur bd7bffb
diff --git a/entries/amazon_coauthor_shchur/__init__.py b/entries/amazon_coauthor_shchur/__init__.py
new file mode 100644
index 0000000..4e44e64
--- /dev/null
+++ b/entries/amazon_coauthor_shchur/__init__.py
@@ -0,0 +1,128 @@
+#! /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 pickle
+from numpy.lib import format as npy_format
+
+from .. import *
+
+
+title = "Amazon co-purchases and Microsoft Academic co-authorships (Shchur et al., 2018)"
+description = """Four networks introduced by Shchur et al. as benchmarks for node classification, as distributed in their repository:
+
+- `amazon_computers` and `amazon_photo`: products of the categories "Computers" and "Camera & Photo" of Amazon, from the Amazon product data of McAuley et al. (2015). A directed edge from product A to product B means that B is listed among the products frequently bought together with A (the relation is not symmetric in the data: about a third of the pairs are listed in both directions, and there is one self-loop in `amazon_photo`). Each product is labeled with its subcategory (e.g. "Laptops" or "Lenses").
+- `coauthor_cs` and `coauthor_physics`: authors of papers in computer science and in physics, from the Microsoft Academic Graph of the KDD Cup 2016 challenge. Two authors are connected by an undirected edge if they co-authored a paper. Each author is described by the keywords of their papers, and labeled with their most active field of study (for `coauthor_physics`, the journal of the Physical Review in which they publish most).
+
+The benchmarks use only the largest connected component of each network (treating the edges as undirected), but the files include all the nodes, which are kept here. The node features of the Amazon networks (bag-of-words encodings of the product reviews, without the words) are left out, as are the product identifiers, which are not given.
+
+The networks have the following properties:
+
+- `category` (Amazon networks): the subcategory of the product.
+- `mag_id` (co-authorship networks): the identifier of the author in the Microsoft Academic Graph (in hexadecimal, as given).
+- `field` (co-authorship networks): the most active field of study of the author (for `coauthor_physics`, a journal, e.g. `phys rev b`).
+- `keywords` (co-authorship networks): the keywords of the author's papers.
+- `keyword_counts` (co-authorship networks): the count given for each keyword in `keywords` (presumably the number of the author's papers with that keyword)."""
+tags = ['Economic', 'Commerce', 'Social', 'Collaboration', 'Unweighted', 'Metadata']
+url = 'https://github.com/shchur/gnn-benchmark'
+citation = [('O. Shchur, M. Mumme, A. Bojchevski, S. Günnemann, "Pitfalls of Graph Neural Network Evaluation." Relational Representation Learning Workshop (R2L 2018), NeurIPS 2018 (2018)', 'https://doi.org/10.48550/arXiv.1811.05868'),
+ ('J. McAuley, C. Targett, Q. Shi, A. van den Hengel, "Image-based recommendations on styles and substitutes." Proceedings of the 38th International ACM SIGIR Conference on Research and Development in Information Retrieval, 43-52 (2015)', 'https://doi.org/10.1145/2766462.2767755')]
+icon_hash = None
+upstream_license = ('MIT', 'https://github.com/shchur/gnn-benchmark/blob/1e72912a0810cdf27ae54fd589a3b43358a2b161/LICENSE')
+upstream_prefix = 'https://raw.githubusercontent.com/shchur/gnn-benchmark/1e72912a0810cdf27ae54fd589a3b43358a2b161/data/npz'
+files = [('amazon_electronics_computers.npz', 'amazon_computers', None),
+ ('amazon_electronics_photo.npz', 'amazon_photo', None),
+ ('ms_academic_cs.npz', 'coauthor_cs', None),
+ ('ms_academic_phy.npz', 'coauthor_physics', None)]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+class ArrayUnpickler(pickle.Unpickler):
+ "Unpickles only numpy arrays (of strings), and nothing else."
+ allowed = {("numpy.core.multiarray", "_reconstruct"),
+ ("numpy._core.multiarray", "_reconstruct"),
+ ("numpy", "ndarray"), ("numpy", "dtype"), ("_codecs", "encode")}
+ def find_class(self, module, name):
+ if (module, name) not in self.allowed:
+ raise pickle.UnpicklingError(f"forbidden: {module}.{name}")
+ return super().find_class(module, name)
+
+def load_npz(f):
+ """The arrays of the .npz file `f` (seekable), including arrays of objects
+ (strings), which are unpickled with ArrayUnpickler."""
+ arrays = {}
+ with zipfile.ZipFile(f) as zf:
+ for member in zf.namelist():
+ with zf.open(member) as fm:
+ version = npy_format.read_magic(fm)
+ if version == (1, 0):
+ shape, order, dtype = npy_format.read_array_header_1_0(fm)
+ else:
+ shape, order, dtype = npy_format.read_array_header_2_0(fm)
+ if dtype.hasobject:
+ a = ArrayUnpickler(fm).load()
+ else:
+ a = numpy.frombuffer(fm.read(), dtype=dtype)
+ a = a.reshape(shape, order="F" if order else "C")
+ arrays[member.removesuffix(".npy")] = a
+ return arrays
+
+@cache_network()
+@coerce_props(vforce={"mag_id": "string"})
+@annotate()
+def parse(alts=None):
+ global files
+ name = __name__.split(".")[-1]
+ for fname, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ with open_upstream_file(name, fname, "rb", seekable=True) as f:
+ z = load_npz(f)
+ N = int(z["adj_shape"][0])
+ indptr, indices = z["adj_indptr"], z["adj_indices"]
+ src = numpy.repeat(numpy.arange(N, dtype="int64"), numpy.diff(indptr))
+ assert (z["adj_data"] == 1).all()
+ classes = [str(c) for c in z["class_names"]]
+ labels = z["labels"]
+ assert len(labels) == N
+ if alt.startswith("amazon"):
+ g = Graph(directed=True)
+ g.add_vertex(N)
+ g.vp.category = g.new_vp("string", vals=[classes[l] for l in labels])
+ g.add_edge_list(numpy.column_stack([src, indices]))
+ else:
+ g = Graph(directed=False)
+ g.add_vertex(N)
+ g.vp.mag_id = g.new_vp("string", vals=[str(x) for x in z["node_names"]])
+ g.vp.field = g.new_vp("string", vals=[classes[l] for l in labels])
+ words = [str(w) for w in z["attr_names"]]
+ aptr, aidx, adata = z["attr_indptr"], z["attr_indices"], z["attr_data"]
+ g.vp.keywords = g.new_vp("vector<string>")
+ g.vp.keyword_counts = g.new_vp("vector<int>")
+ for v in range(N):
+ r = slice(aptr[v], aptr[v + 1])
+ g.vp.keywords[v] = [words[i] for i in aidx[r]]
+ g.vp.keyword_counts[v] = adata[r].astype("int")
+ # the adjacency matrix is symmetric: each edge once
+ m = src < indices
+ assert m.sum() * 2 == len(src)
+ g.add_edge_list(numpy.column_stack([src[m], indices[m]]))
+ yield alt, g