History of heterophilous_graphs
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: heterophilous_graphs 91a0faf
diff --git a/entries/heterophilous_graphs/__init__.py b/entries/heterophilous_graphs/__init__.py
new file mode 100644
index 0000000..117bbd6
--- /dev/null
+++ b/entries/heterophilous_graphs/__init__.py
@@ -0,0 +1,87 @@
+#! /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 = "Heterophilous node classification benchmarks (roman-empire, amazon-ratings, questions, minesweeper, 2023)"
+description = """Four of the networks assembled by Platonov et al. as benchmarks for node classification on graphs in which connected nodes tend to have different labels ("heterophily"), as distributed in their repository:
+
+- `roman_empire`: the words of the English Wikipedia article "Roman Empire" (from the dump of 1 March 2022), one of the longest articles of Wikipedia. Each node is one occurrence of a word in the text, and two words are connected if they follow each other in the text, or if they are connected in the dependency tree of their sentence (as parsed by spaCy). The network is thus a chain with syntactic shortcuts. Each word is labeled with its syntactic role (as given by spaCy): the 17 most frequent roles are separate classes, and all the others form an 18th class.
+- `amazon_ratings`: products of Amazon (books, music CDs, DVDs and VHS tapes), from the Amazon product co-purchasing metadata of SNAP (2006), connected if they are frequently bought together. The network is the largest connected component of the 5-core of the co-purchasing network. Each product is labeled with its average rating by reviewers, grouped into five classes.
+- `questions`: users of the question-answering website Yandex Q who are interested in the topic "medicine", connected if one of them answered a question of the other between September 2021 and August 2022. Each user is labeled by whether they remained active (their account was neither deleted nor blocked) at the end of that period.
+- `minesweeper`: a synthetic network, inspired by the game Minesweeper, and not derived from any real data: a regular 100 x 100 grid, in which each cell is connected to its (up to eight) neighbors, horizontally, vertically and diagonally. A random 20% of the cells are "mines", which is the label to predict. The node features of the benchmark give the number of mines among the neighbors of each cell, but only for a random half of the cells (for the other half it is marked as unknown), and they are kept as a property.
+
+The edges are undirected. For the three real networks, the data do not include the words, products and users themselves (the text of the words, the product identifiers and the user names are not given), and the labels are given only as class indices: their meaning (e.g. which syntactic role, or which rating range, a class stands for) is not given. The node features of these benchmarks (averages of fastText embeddings of the words, or of the descriptions of products and users, with 300 dimensions) are left out, since they are derived data that would make the network files much larger; the only exception is the indicator of the users of `questions` without a description, kept as a property. The train, validation and test splits of the benchmarks are also left out. The fifth network of the benchmark, `tolokers`, is the entry `tolokers`.
+
+The node indices are those of the source data. The networks have the following properties:
+
+- `label`: the class of the node, an index from 0 (the syntactic role for `roman_empire`, from 0 to 17, where 17 groups the less frequent roles; the rating class for `amazon_ratings`, from 0 to 4; for `questions`, 1 if the user was no longer active and 0 otherwise; for `minesweeper`, 1 if the cell is a mine and 0 otherwise).
+- `has_description` (`questions` only): whether the user has a profile description.
+- `row`, `column` (`minesweeper` only): the position of the cell in the grid, from 0 to 99.
+- `neighbor_mines` (`minesweeper` only): the number of mines among the neighbors of the cell, as given by the node features (from 0 to 4, or 5 for five or more), or -1 if it is unknown."""
+tags = ['Informational', 'Language', 'Economic', 'Commerce', 'Social', 'Online',
+ 'Unweighted', 'Metadata']
+url = 'https://github.com/yandex-research/heterophilous-graphs'
+citation = [('O. Platonov, D. Kuznedelev, M. Diskin, A. Babenko, L. Prokhorenkova, "A critical look at the evaluation of GNNs under heterophily: Are we really making progress?" International Conference on Learning Representations (ICLR) (2023)', 'https://doi.org/10.48550/arXiv.2302.11640')]
+icon_hash = None
+upstream_license = ('MIT', 'https://github.com/yandex-research/heterophilous-graphs/blob/a431395582e929d88271309716bea4fe24ce6318/LICENSE')
+upstream_prefix = 'https://raw.githubusercontent.com/yandex-research/heterophilous-graphs/a431395582e929d88271309716bea4fe24ce6318/data'
+files = [(f'{alt}.npz', alt, None)
+ for alt in ['roman_empire', 'amazon_ratings', 'questions', 'minesweeper']]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+@cache_network()
+@coerce_props()
+@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 = numpy.load(f)
+ labels = z["node_labels"]
+ edges = z["edges"]
+ # the first feature of "questions" flags the users without a
+ # description (whose other features are all zero)
+ no_desc = z["node_features"][:, 0] if alt == "questions" else None
+ # the features of "minesweeper" are one-hot: unknown, or 0, 1, 2,
+ # 3, 4, or 5 or more neighboring mines
+ mines = z["node_features"] if alt == "minesweeper" else None
+ g = Graph(directed=False)
+ g.add_vertex(len(labels))
+ g.vp.label = g.new_vp("int", vals=labels)
+ if no_desc is not None:
+ assert set(numpy.unique(no_desc)) <= {0, 1}
+ g.vp.has_description = g.new_vp("bool", vals=no_desc == 0)
+ if mines is not None:
+ # a 100 x 100 grid, numbered row by row
+ assert mines.shape == (100 * 100, 7) and (mines.sum(axis=1) == 1).all()
+ i = numpy.arange(g.num_vertices())
+ g.vp.row = g.new_vp("int", vals=i // 100)
+ g.vp.column = g.new_vp("int", vals=i % 100)
+ g.vp.neighbor_mines = g.new_vp("int", vals=mines.argmax(axis=1) - 1)
+ g.add_edge_list(edges)
+ yield alt, g