logo Netzschleuder network catalog, repository, and centrifuge

History of t_social

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 by Tiago de Paula Peixoto 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/t_social/__init__.py b/entries/t_social/__init__.py
index 5b16957..b9c171c 100644
--- a/entries/t_social/__init__.py
+++ b/entries/t_social/__init__.py
@@ -42,7 +42,7 @@ citation = [('J. Tang, J. Li, Z. Gao, J. Li, "Rethinking Graph Neural Networks f
              'https://arxiv.org/abs/2306.12251')]
 # No license is stated (the repositories have none)
 upstream_license = None
-icon_hash = None
+date_added = '2026-10-31'
 
 # The datasets of GADBench, as a zip archive on Google Drive (file id); the
 # networks are DGL graph files
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) by Tiago de Paula Peixoto 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/t_social/__init__.py b/entries/t_social/__init__.py
index f2df212..5b16957 100644
--- a/entries/t_social/__init__.py
+++ b/entries/t_social/__init__.py
@@ -31,7 +31,8 @@ The networks are read from the DGL graph files of GADBench, which store each edg
 
 The networks have the following properties:
 
-- Nodes: `feature`, the 10 features of the account (anonymized, as given); `label`, whether the account is annotated as anomalous (1) or not (0)."""
+- (Node) `feature`: the 10 features of the account (anonymized, as given).
+- (Node) `label`: whether the account is annotated as anomalous (1) or not (0)."""
 tags = ['Social', 'Economic', 'Online', 'Unweighted', 'Metadata']
 url = ['https://github.com/squareRoot3/Rethinking-Anomaly-Detection',
        'https://github.com/squareRoot3/GADBench']
[entries] t_social: find the edge arrays of the DGL files by their shape by Tiago de Paula Peixoto 72aa49f
The source and target arrays were recognized by the absence of a name
before them in the file, which failed when the bytes before one of them
looked like a name. They are now the first two integer arrays of the
same length other than the node data; if they are not found, or refer
to unknown nodes, the error lists the arrays of the file.
diff --git a/entries/t_social/__init__.py b/entries/t_social/__init__.py
index 1f47a4d..f2df212 100644
--- a/entries/t_social/__init__.py
+++ b/entries/t_social/__init__.py
@@ -107,15 +107,27 @@ def parse(alts=None):
     for fname, alt, fmt in files:
         if alts is not None and alt not in alts:
             continue
+        # The source and target arrays are the first two integer arrays of
+        # the same length, other than the node data (the names found before
+        # the arrays are not reliable for them)
         coo = []
         ndata = {}
+        seen = []
         with open_upstream_file(name, fname, "rb") as f:
             for key, a in read_ndarrays(f):
-                if a.ndim == 1 and len(a) > 2 and key is None and len(coo) < 2:
-                    coo.append(a)          # the source and target arrays
-                elif key in ("feature", "label"):
+                seen.append((key, a.dtype.name, a.shape))
+                if key in ("feature", "label"):
                     ndata[key] = a
+                elif (len(coo) < 2 and a.ndim == 1 and len(a) > 2 and
+                      a.dtype.kind in "iu"):
+                    if coo and len(coo[0]) != len(a):
+                        coo = []
+                    coo.append(a)
+        if len(coo) != 2 or set(ndata) != {"feature", "label"}:
+            raise ValueError(f"unexpected arrays in {fname}: {seen}")
         src, tgt = coo
+        if min(src.min(), tgt.min()) < 0 or max(src.max(), tgt.max()) >= len(ndata["label"]):
+            raise ValueError(f"edges of unknown nodes in {fname}: {seen}")
         # one edge per pair (each is stored in both directions), without the
         # self-loops of the nodes
         sel = src < tgt
[entries] Remove unneeded code from the entries by Tiago de Paula Peixoto 2c69f19
Unneeded "global files" statements, unused imports and variables,
f-strings without placeholders, duplicate keys in foodweb_little_rock
(keeping the order and the values used), and the second definition of
get_lines() in twitter_higgs. Nothing changes in the networks or
descriptions.

Changed: none
diff --git a/entries/t_social/__init__.py b/entries/t_social/__init__.py
index 7d7e642..1f47a4d 100644
--- a/entries/t_social/__init__.py
+++ b/entries/t_social/__init__.py
@@ -103,7 +103,6 @@ def read_ndarrays(f):
 @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:
[entries] New entry: t_social by Tiago de Paula Peixoto bb06423
diff --git a/entries/t_social/__init__.py b/entries/t_social/__init__.py
new file mode 100644
index 0000000..7d7e642
--- /dev/null
+++ b/entries/t_social/__init__.py
@@ -0,0 +1,131 @@
+#! /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 *
+import struct
+
+title = "T-Social and T-Finance anomaly detection networks"
+description = """Two anonymized networks of the accounts of an online platform with expert annotations of anomalous accounts, released by Tang, Li, Gao and Li with their graph anomaly detection method BWGNN, and part of the GADBench benchmark:
+
+- `t_social`: a social network, where two accounts are connected if they maintained a friend relationship for more than three months (5,781,065 accounts, 73,105,508 friendships).
+- `t_finance`: a transaction network, where two accounts are connected if they have transaction records (39,357 accounts, 21,222,543 edges).
+
+The nodes of both networks have the same kinds of features (10 values related to the registration days, logging activities and interaction frequency of the account) and labels: human experts annotated accounts as anomalous if they fall into categories such as fraud, money laundering and online gambling (3.0% of the accounts in `t_social`, 4.6% in `t_finance`). The platform is not named by the authors.
+
+The networks are read from the DGL graph files of GADBench, which store each edge in both directions, and a self-loop for each node; the undirected networks here have one edge per pair, without these self-loops. The random splits of the benchmark (train, validation and test masks) are left out, and so is the edge data `count` of `t_finance`, which is 1 for all edges.
+
+The networks have the following properties:
+
+- Nodes: `feature`, the 10 features of the account (anonymized, as given); `label`, whether the account is annotated as anomalous (1) or not (0)."""
+tags = ['Social', 'Economic', 'Online', 'Unweighted', 'Metadata']
+url = ['https://github.com/squareRoot3/Rethinking-Anomaly-Detection',
+       'https://github.com/squareRoot3/GADBench']
+citation = [('J. Tang, J. Li, Z. Gao, J. Li, "Rethinking Graph Neural Networks for Anomaly Detection." Proceedings of the 39th International Conference on Machine Learning (ICML), PMLR 162, 21076-21089 (2022)',
+             'https://proceedings.mlr.press/v162/tang22b.html'),
+            ('J. Tang, F. Hua, Z. Gao, P. Zhao, J. Li, "GADBench: Revisiting and Benchmarking Supervised Graph Anomaly Detection." Advances in Neural Information Processing Systems 36 (2023)',
+             'https://arxiv.org/abs/2306.12251')]
+# No license is stated (the repositories have none)
+upstream_license = None
+icon_hash = None
+
+# The datasets of GADBench, as a zip archive on Google Drive (file id); the
+# networks are DGL graph files
+upstream_prefix = 'gdrive://'
+ZIP = '1txzXrzwBBAOEATXmfKzMUUKaXh6PJeR1'
+files = [(f'{ZIP}:zip:datasets/tsocial', 't_social', None),
+         (f'{ZIP}:zip:datasets/tfinance', 't_finance', None)]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+NDARRAY_MAGIC = struct.pack("<Q", 0xDD5E40F096B4A13F)
+DTYPES = {(0, 8): "int8", (0, 16): "int16", (0, 32): "int32", (0, 64): "int64",
+          (1, 8): "uint8", (1, 16): "uint16", (1, 32): "uint32",
+          (1, 64): "uint64", (2, 32): "float32", (2, 64): "float64"}
+
+def read_ndarrays(f):
+    """Iterate over the arrays (NDArrays) of a DGL graph file read as a stream
+    `f`, as pairs (name, array), where the name is the last identifier
+    written before the array (the key of node or edge data; None for the
+    structure arrays). The arrays are found by the magic number of their
+    headers; the rest of the format is skipped."""
+    buf = b""
+    while True:
+        i = buf.find(NDARRAY_MAGIC)
+        if i < 0:
+            chunk = f.read(1 << 16)
+            if not chunk:
+                return
+            buf = buf[-7:] + chunk if len(buf) > 7 else buf + chunk
+            continue
+        while len(buf) < i + 40 + 8 * 8 + 8:
+            chunk = f.read(1 << 16)
+            if not chunk:
+                break
+            buf += chunk
+        names = re.findall(rb"[A-Za-z_][A-Za-z_0-9]{2,}", buf[:i])
+        name = names[-1].decode() if names else None
+        ndim = struct.unpack_from("<I", buf, i + 24)[0]
+        code, bits, lanes = struct.unpack_from("<BBH", buf, i + 28)
+        shape = struct.unpack_from(f"<{ndim}q", buf, i + 32)
+        nbytes = struct.unpack_from("<q", buf, i + 32 + 8 * ndim)[0]
+        start = i + 40 + 8 * ndim
+        data = bytearray(nbytes)
+        rest = buf[start:start + nbytes]
+        data[:len(rest)] = rest
+        view = memoryview(data)[len(rest):]
+        while len(view) > 0:
+            n = f.readinto(view)
+            if not n:
+                raise EOFError("truncated DGL file")
+            view = view[n:]
+        buf = buf[start + nbytes:]
+        a = numpy.frombuffer(data, dtype=DTYPES[(code, bits)]).reshape(shape)
+        yield name, a
+
+@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
+        coo = []
+        ndata = {}
+        with open_upstream_file(name, fname, "rb") as f:
+            for key, a in read_ndarrays(f):
+                if a.ndim == 1 and len(a) > 2 and key is None and len(coo) < 2:
+                    coo.append(a)          # the source and target arrays
+                elif key in ("feature", "label"):
+                    ndata[key] = a
+        src, tgt = coo
+        # one edge per pair (each is stored in both directions), without the
+        # self-loops of the nodes
+        sel = src < tgt
+        g = Graph(directed=False)
+        g.add_vertex(len(ndata["label"]))
+        g.vp.feature = g.new_vp("vector<double>")
+        g.vp.feature.set_2d_array(ndata["feature"].astype("float64").T)
+        g.vp.label = g.new_vp("int16_t", vals=ndata["label"])
+        e = numpy.column_stack((src[sel], tgt[sel]))
+        del src, tgt, coo, sel
+        g.add_edge_list(e)
+        yield alt, g