History of ogbn_products
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/ogbn_products/__init__.py b/entries/ogbn_products/__init__.py
index 41b5dc8..1c965d1 100644
--- a/entries/ogbn_products/__init__.py
+++ b/entries/ogbn_products/__init__.py
@@ -40,7 +40,7 @@ url = ['https://ogb.stanford.edu/docs/nodeprop/#ogbn-products',
'https://snap.stanford.edu/ogb/data/']
citation = [('W. Hu, M. Fey, M. Zitnik, Y. Dong, H. Ren, B. Liu, M. Catasta, J. Leskovec, "Open Graph Benchmark: Datasets for Machine Learning on Graphs." Advances in Neural Information Processing Systems 33, 22118-22133 (2020)', 'https://doi.org/10.48550/arXiv.2005.00687'),
('W.-L. Chiang, X. Liu, S. Si, Y. Li, S. Bengio, C.-J. Hsieh, "Cluster-GCN: An Efficient Algorithm for Training Deep and Large Graph Convolutional Networks." Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining, 257-266 (2019)', 'https://doi.org/10.1145/3292500.3330925')]
-icon_hash = None
+date_added = '2026-10-28'
upstream_license = ('Amazon license (academic research use; no resale or republication)',
'https://web.archive.org/web/20230609045700/http://s3.amazonaws.com/amazon-reviews-pds/LICENSE.txt')
upstream_prefix = 'https://snap.stanford.edu/ogb/data'
[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/ogbn_products/__init__.py b/entries/ogbn_products/__init__.py
index fac81df..41b5dc8 100644
--- a/entries/ogbn_products/__init__.py
+++ b/entries/ogbn_products/__init__.py
@@ -31,10 +31,10 @@ As noted by OGB, a few self-loops appear more than once; they are kept as given.
The network has the following properties:
-- `asin`: the Amazon Standard Identification Number of the product.
-- `category`: the top-level category of the product, e.g. `Books` or `Electronics` (as named in the mapping file of OGB, where one category has an empty name and another one is named `#508510`).
-- `split`: the set of the product in the benchmark's split by sales rank: `train` (the 8% best-selling products), `valid` (the next 2%), or `test` (the rest).
-- `features`: the 100-dimensional feature vector of the product (the reduced bag-of-words of its description)."""
+- (Node) `asin`: the Amazon Standard Identification Number of the product.
+- (Node) `category`: the top-level category of the product, e.g. `Books` or `Electronics` (as named in the mapping file of OGB, where one category has an empty name and another one is named `#508510`).
+- (Node) `split`: the set of the product in the benchmark's split by sales rank: `train` (the 8% best-selling products), `valid` (the next 2%), or `test` (the rest).
+- (Node) `features`: the 100-dimensional feature vector of the product (the reduced bag-of-words of its description)."""
tags = ['Economic', 'Commerce', 'Unweighted', 'Metadata']
url = ['https://ogb.stanford.edu/docs/nodeprop/#ogbn-products',
'https://snap.stanford.edu/ogb/data/']
[entries] New entry: ogbn_products 9354a41
diff --git a/entries/ogbn_products/__init__.py b/entries/ogbn_products/__init__.py
new file mode 100644
index 0000000..fac81df
--- /dev/null
+++ b/entries/ogbn_products/__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 html
+import pyarrow
+import pyarrow.csv
+
+from .. import *
+
+title = "Amazon product co-purchases (ogbn-products)"
+description = r"""An undirected network of products sold on Amazon, two products being connected if they were bought together, from the `ogbn-products` dataset of the Open Graph Benchmark (OGB), a benchmark for node classification (version 1, of May 2020). The network was built by W.-L. Chiang and colleagues for the evaluation of Cluster-GCN (where it is called "Amazon2M"), from the Amazon product data of the Extreme Classification Repository (originally collected by J. McAuley and colleagues from the Amazon website), and processed further by the OGB authors.
+
+Each product is labeled with one of 47 top-level categories of Amazon (the target of the classification task), and has the node features of the benchmark: a 100-dimensional vector obtained from the bag-of-words of its description, reduced by principal component analysis. The products are identified by their Amazon ASIN; their names and descriptions are not included. The benchmark split of the products into training, validation and test sets follows their sales rank (the top 8% are for training, the next 2% for validation and the rest for testing) and is kept as the property `split`.
+
+As noted by OGB, a few self-loops appear more than once; they are kept as given.
+
+The network has the following properties:
+
+- `asin`: the Amazon Standard Identification Number of the product.
+- `category`: the top-level category of the product, e.g. `Books` or `Electronics` (as named in the mapping file of OGB, where one category has an empty name and another one is named `#508510`).
+- `split`: the set of the product in the benchmark's split by sales rank: `train` (the 8% best-selling products), `valid` (the next 2%), or `test` (the rest).
+- `features`: the 100-dimensional feature vector of the product (the reduced bag-of-words of its description)."""
+tags = ['Economic', 'Commerce', 'Unweighted', 'Metadata']
+url = ['https://ogb.stanford.edu/docs/nodeprop/#ogbn-products',
+ 'https://snap.stanford.edu/ogb/data/']
+citation = [('W. Hu, M. Fey, M. Zitnik, Y. Dong, H. Ren, B. Liu, M. Catasta, J. Leskovec, "Open Graph Benchmark: Datasets for Machine Learning on Graphs." Advances in Neural Information Processing Systems 33, 22118-22133 (2020)', 'https://doi.org/10.48550/arXiv.2005.00687'),
+ ('W.-L. Chiang, X. Liu, S. Si, Y. Li, S. Bengio, C.-J. Hsieh, "Cluster-GCN: An Efficient Algorithm for Training Deep and Large Graph Convolutional Networks." Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining, 257-266 (2019)', 'https://doi.org/10.1145/3292500.3330925')]
+icon_hash = None
+upstream_license = ('Amazon license (academic research use; no resale or republication)',
+ 'https://web.archive.org/web/20230609045700/http://s3.amazonaws.com/amazon-reviews-pds/LICENSE.txt')
+upstream_prefix = 'https://snap.stanford.edu/ogb/data'
+ZIP = 'nodeproppred/products.zip:products'
+files = [((f'{ZIP}/raw/edge.csv.gz',
+ f'{ZIP}/raw/node-label.csv.gz',
+ f'{ZIP}/mapping/labelidx2productcategory.csv.gz',
+ f'{ZIP}/mapping/nodeidx2asin.csv.gz',
+ f'{ZIP}/split/sales_ranking/train.csv.gz',
+ f'{ZIP}/split/sales_ranking/valid.csv.gz',
+ f'{ZIP}/split/sales_ranking/test.csv.gz',
+ f'{ZIP}/raw/node-feat.csv.gz'),
+ None, None)]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+def read_csv_gz(name, fname, header=False, types=None):
+ "The table of the gzipped CSV member `fname`."
+ with open_upstream_file(name, fname, "rb") as f, gzip.open(f) as gf:
+ return pyarrow.csv.read_csv(
+ gf,
+ read_options=pyarrow.csv.ReadOptions(autogenerate_column_names=not header),
+ convert_options=pyarrow.csv.ConvertOptions(column_types=types or {}))
+
+def set_features(g, name, fname, n, dim=100):
+ """Store the node features of the gzipped CSV member `fname` (one line of
+ `dim` numbers per node) as the vector<double> vertex property `features`,
+ read in blocks."""
+ p = g.new_vp("vector<double>")
+ i = 0
+ with open_upstream_file(name, fname, "rb") as f, gzip.open(f) as gf:
+ reader = pyarrow.csv.open_csv(
+ gf,
+ read_options=pyarrow.csv.ReadOptions(autogenerate_column_names=True,
+ block_size=1 << 26),
+ convert_options=pyarrow.csv.ConvertOptions(
+ column_types={f"f{j}": pyarrow.float64() for j in range(dim)}))
+ for batch in reader:
+ assert batch.num_columns == dim and i + batch.num_rows <= n
+ x = numpy.column_stack([c.to_numpy() for c in batch.columns])
+ for row in x:
+ p[i] = row
+ i += 1
+ del x
+ assert i == n, (i, n)
+ g.vp.features = p
+
+@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
+ labels = read_csv_gz(name, fnames[1])["f0"].to_numpy()
+ N = len(labels)
+ # "label idx,product category"
+ cats = read_csv_gz(name, fnames[2], header=True)
+ cats = dict(zip(cats.column(0).to_pylist(),
+ [html.unescape(c or "") for c in cats.column(1).to_pylist()]))
+ # "node idx,asin"
+ asin = read_csv_gz(name, fnames[3], header=True,
+ types={"asin": pyarrow.string()})
+ assert asin.column(0).to_pylist() == list(range(N))
+ split = numpy.full(N, "", dtype=object)
+ for s, fname in zip(["train", "valid", "test"], fnames[4:7]):
+ split[read_csv_gz(name, fname)["f0"].to_numpy()] = s
+ assert (split != "").all()
+
+ g = Graph(directed=False)
+ g.add_vertex(N)
+ g.vp.asin = g.new_vp("string", vals=asin.column(1).to_pylist())
+ g.vp.category = g.new_vp("string", vals=[cats[l] for l in labels])
+ g.vp.split = g.new_vp("string", vals=split)
+ del asin, split
+ # Edges: "<product>,<product>"
+ edges = read_csv_gz(name, fnames[0])
+ g.add_edge_list(numpy.column_stack((edges["f0"].to_numpy(),
+ edges["f1"].to_numpy())))
+ del edges
+ set_features(g, name, fnames[7], N)
+ yield alt, g