logo Netzschleuder network catalog, repository, and centrifuge

History of qa_user

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/qa_user/__init__.py b/entries/qa_user/__init__.py
index 56bb36b..25afada 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -43,7 +43,7 @@ url = ['https://snap.stanford.edu/data/sx-stackoverflow.html',
        'https://archive.org/details/stackexchange']
 citation = [('A. Paranjape, A. R. Benson, & J. Leskovec, "Motifs in Temporal Networks". In Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM ’17). Association for Computing Machinery, New York, NY, USA, 601–610. (2017)', 'https://doi.org/10.1145/3018661.3018731'),
             ('Stack Exchange, Inc., "Stack Exchange Data Dump - 2024-04-02."', 'https://archive.org/details/stackexchange')]
-icon_hash = '586eac70a411221d0e7c5bda'
+date_added = '2020-08-11'
 upstream_license = ("Creative Commons Attribution-ShareAlike 4.0 (CC BY-SA 4.0)",
                     "https://creativecommons.org/licenses/by-sa/4.0/")
 upstream_prefix = 'https://snap.stanford.edu/data'
[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/qa_user/__init__.py b/entries/qa_user/__init__.py
index 1b01506..56bb36b 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -33,8 +33,8 @@ The networks named with `2024`, as in `mathoverflow_2024_a2q`, were constructed
 
 The networks have the following properties:
 
-- `vid`: the user ID from the source data.
-- `timestamp`: the time of the interaction (Unix time, in seconds)."""
+- (Node) `vid`: the user ID from the source data.
+- (Edge) `timestamp`: the time of the interaction (Unix time, in seconds)."""
 tags = ['Social', 'Online', 'Unweighted', 'Timestamps']
 url = ['https://snap.stanford.edu/data/sx-stackoverflow.html',
        'https://snap.stanford.edu/data/sx-mathoverflow.html',
[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/qa_user/__init__.py b/entries/qa_user/__init__.py
index adcf904..1b01506 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -215,7 +215,6 @@ def dump_interactions(name, posts, comments):
 #@coerce_props()
 @annotate()
 def parse(alts=None):
-    global files
     name = __name__.split(".")[-1]
     dump = None                 # the interactions of the last site of the dump
     for fname, alt, fmt in files:
[entries] qa_user: add the networks of the Stack Exchange dump of April 2024 by Tiago de Paula Peixoto 1fa10cf
The entry had only the networks of 2016 (SNAP). The same networks are
built from the last open Stack Exchange data dump (April 2024, on
archive.org), for the same four sites, with the same construction; the
networks of 2016 are unchanged. The upstream license, not given before,
is set to the CC BY-SA 4.0 of the Stack Exchange data.

Changed: upstream qa_user
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index 32f23f6..adcf904 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -16,9 +16,11 @@
 # 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 subprocess
+
 from .. import *
 
-title = "User interactions on Q&A websites (2016)"
+title = "User interactions on Q&A websites (2016, 2024)"
 description = """Temporal networks of interactions among users of four question-and-answer websites of the Stack Exchange network: Stack Overflow (programming), Math Overflow (research mathematics), Super User (computer use) and Ask Ubuntu (the Ubuntu operating system). The networks were constructed by A. Paranjape, A. R. Benson and J. Leskovec from the public Stack Exchange data dump, and span from the creation of each site (2008 or 2009) until March 2016. Nodes are users, and a directed edge $(i,j)$ with timestamp $t$ indicates that user $i$ responded to a post of user $j$ at time $t$, in one of three ways:
 
 - `a2q`: user $i$ answered user $j$'s question;
@@ -27,6 +29,8 @@ description = """Temporal networks of interactions among users of four question-
 
 and `all` is the union of the three types. Each network is named after the site and the type of interaction, as in `mathoverflow_a2q`. Each interaction is a separate edge, so pairs of users can be connected by many parallel edges, and the networks contain self-loops, e.g. when users comment on their own posts.
 
+The networks named with `2024`, as in `mathoverflow_2024_a2q`, were constructed in the same way from the Stack Exchange data dump of April 2024 (the last one published on the Internet Archive), and span from the creation of each site until early April 2024. They contain the questions, answers and comments of the dump whose authors (and the authors of the posts they respond to) are given; the posts and comments of deleted user accounts, which the dump gives without author, are left out, as are those of the automated "Community" account (user id -1), which the 2016 networks do not contain either. In the `all` networks, the interactions are sorted by time.
+
 The networks have the following properties:
 
 - `vid`: the user ID from the source data.
@@ -35,11 +39,26 @@ tags = ['Social', 'Online', 'Unweighted', 'Timestamps']
 url = ['https://snap.stanford.edu/data/sx-stackoverflow.html',
        'https://snap.stanford.edu/data/sx-mathoverflow.html',
        'https://snap.stanford.edu/data/sx-superuser.html',
-       'https://snap.stanford.edu/data/sx-askubuntu.html']
-citation = [('A. Paranjape, A. R. Benson, & J. Leskovec, "Motifs in Temporal Networks". In Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM ’17). Association for Computing Machinery, New York, NY, USA, 601–610. (2017)', 'https://doi.org/10.1145/3018661.3018731')]
+       'https://snap.stanford.edu/data/sx-askubuntu.html',
+       'https://archive.org/details/stackexchange']
+citation = [('A. Paranjape, A. R. Benson, & J. Leskovec, "Motifs in Temporal Networks". In Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM ’17). Association for Computing Machinery, New York, NY, USA, 601–610. (2017)', 'https://doi.org/10.1145/3018661.3018731'),
+            ('Stack Exchange, Inc., "Stack Exchange Data Dump - 2024-04-02."', 'https://archive.org/details/stackexchange')]
 icon_hash = '586eac70a411221d0e7c5bda'
-upstream_license = None
+upstream_license = ("Creative Commons Attribution-ShareAlike 4.0 (CC BY-SA 4.0)",
+                    "https://creativecommons.org/licenses/by-sa/4.0/")
 upstream_prefix = 'https://snap.stanford.edu/data'
+# The Stack Exchange data dump of April 2024: 7z archives of XML files, one
+# per site, except Stack Overflow, which has one archive per file
+dump_prefix = 'https://archive.org/download/stackexchange'
+dump_sites = [('stackoverflow', 'stackoverflow.com-Posts.7z:Posts.xml',
+               'stackoverflow.com-Comments.7z:Comments.xml'),
+              ('mathoverflow', 'mathoverflow.net.7z:Posts.xml',
+               'mathoverflow.net.7z:Comments.xml'),
+              ('superuser', 'superuser.com.7z:Posts.xml',
+               'superuser.com.7z:Comments.xml'),
+              ('askubuntu', 'askubuntu.com.7z:Posts.xml',
+               'askubuntu.com.7z:Comments.xml')]
+interaction_kinds = ['all', 'a2q', 'c2q', 'c2a']
 files = [('sx-stackoverflow.txt.gz', 'stackoverflow_all', None),
          ('sx-stackoverflow-a2q.txt.gz', 'stackoverflow_a2q', None),
          ('sx-stackoverflow-c2q.txt.gz', 'stackoverflow_c2q', None),
@@ -56,10 +75,141 @@ files = [('sx-stackoverflow.txt.gz', 'stackoverflow_all', None),
          ('sx-askubuntu-a2q.txt.gz', 'askubuntu_a2q', None),
          ('sx-askubuntu-c2q.txt.gz', 'askubuntu_c2q', None),
          ('sx-askubuntu-c2a.txt.gz', 'askubuntu_c2a', None)]
+dump_files = [((posts, comments), f'{site}_2024_{kind}', None)
+              for site, posts, comments in dump_sites
+              for kind in interaction_kinds]
+files += dump_files
 
 def fetch_upstream(force=False):
-    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
-                                force)
+    name = __name__.split(".")[-1]
+    fetch_upstream_files(name, upstream_prefix,
+                         [f for f in files if f not in dump_files], force)
+    # The 7z archives are stored as they are; their existence is checked
+    # without extracting them (check=False)
+    retry_download(fetch_upstream_files, name, dump_prefix, dump_files,
+                   force, check=False)
+
+@contextmanager
+def open_dump_member(name, fname):
+    """Stream a member "<archive>:<file>" of a 7z archive of the dump,
+    decompressed by the 7z command line tool (the members are too large to
+    be extracted to temporary files)."""
+    archive, member = fname.split(":")
+    path = f"{_dump_dir or f'cache/upstream/{name}'}/{archive}"
+    if not os.path.exists(path):
+        raise FileNotFoundError(path)
+    with subprocess.Popen(["7z", "e", "-so", path, member],
+                          stdout=subprocess.PIPE,
+                          stderr=subprocess.DEVNULL) as p:
+        yield p.stdout
+        p.stdout.close()
+    if p.returncode not in (0, None, -13):    # -13: closed early (SIGPIPE)
+        raise RuntimeError(f"7z failed with code {p.returncode} for {fname}")
+
+def _attrs(line, names):
+    """The values of the given attributes of a row of a dump XML file
+    (rows are on separate lines; quotes inside values are escaped), as bytes,
+    or None if missing."""
+    vals = []
+    for n in names:
+        i = line.find(b' ' + n + b'="')
+        if i < 0:
+            vals.append(None)
+            continue
+        i += len(n) + 3
+        vals.append(line[i:line.index(b'"', i)])
+    return vals
+
+def _unix_time(ts):
+    "Unix time (in seconds) of the dump's dates, e.g. 2008-07-31T21:42:52.667"
+    return numpy.array([t[:19].decode() for t in ts],
+                       dtype="datetime64[s]").astype("int64")
+
+# For checks only: if set, only (about) this many rows are read from each
+# file of the dump, and the archives are read from the directory _dump_dir
+_check_rows = None
+_dump_dir = None
+
+def dump_interactions(name, posts, comments):
+    """The interactions of a site of the dump, as a dict mapping each kind
+    to an array of (source user, target user, time) rows, sorted by time."""
+    # Posts: owner (-1 if missing), type, and parent of answers, by id
+    # (collected in chunks of numpy arrays, to save memory)
+    chunks, rows = [], []
+    def flush():
+        if rows:
+            ids, ts = zip(*rows)
+            chunks.append((numpy.array(ids, dtype="int64"), _unix_time(ts)))
+            rows.clear()
+    with open_dump_member(name, posts) as f:
+        for i, line in enumerate(f):
+            if _check_rows is not None and i >= _check_rows:
+                break
+            if not line.lstrip().startswith(b"<row "):
+                continue
+            x, t, par, own, c = _attrs(line, [b"Id", b"PostTypeId", b"ParentId",
+                                              b"OwnerUserId", b"CreationDate"])
+            if t not in (b"1", b"2"):        # only questions and answers
+                continue
+            rows.append(((int(x), int(t), int(par) if par is not None else -1,
+                          int(own) if own is not None else -1), c))
+            if len(rows) >= 1 << 20:
+                flush()
+    flush()
+    a = numpy.concatenate([c[0] for c in chunks]).reshape(-1, 4) if chunks else numpy.zeros((0, 4), dtype="int64")
+    ptime = numpy.concatenate([c[1] for c in chunks]) if chunks else numpy.zeros(0, dtype="int64")
+    del chunks
+    pid, ptype, parent, owner = a.T
+    N = pid.max() + 1 if len(pid) else 1
+    owner_of = numpy.full(N, -1, dtype="int64")
+    owner_of[pid] = owner
+    type_of = numpy.zeros(N, dtype="int8")
+    type_of[pid] = ptype
+
+    def lookup(a, ids):
+        out = numpy.full(len(ids), -1, dtype=a.dtype)
+        ok = (ids >= 0) & (ids < N)
+        out[ok] = a[ids[ok]]
+        return out
+
+    inter = {}
+    # a2q: the owner of an answer responded to the owner of its question
+    ans = ptype == 2
+    e = numpy.column_stack([owner[ans], lookup(owner_of, parent[ans]),
+                            ptime[ans]])
+    inter["a2q"] = e
+    del pid, ptype, parent, owner, ptime
+
+    # comments: the commenter responded to the owner of the post
+    chunks = []
+    with open_dump_member(name, comments) as f:
+        for i, line in enumerate(f):
+            if _check_rows is not None and i >= _check_rows:
+                break
+            if not line.lstrip().startswith(b"<row "):
+                continue
+            p, u, c = _attrs(line, [b"PostId", b"UserId", b"CreationDate"])
+            rows.append(((int(p), int(u) if u is not None else -1), c))
+            if len(rows) >= 1 << 20:
+                flush()
+    flush()
+    a = numpy.concatenate([c[0] for c in chunks]).reshape(-1, 2) if chunks else numpy.zeros((0, 2), dtype="int64")
+    ctime = numpy.concatenate([c[1] for c in chunks]) if chunks else numpy.zeros(0, dtype="int64")
+    del chunks
+    cpost, cuser = a.T
+    ctype = lookup(type_of, cpost)
+    cowner = lookup(owner_of, cpost)
+    for kind, t in [("c2q", 1), ("c2a", 2)]:
+        m = ctype == t
+        inter[kind] = numpy.column_stack([cuser[m], cowner[m], ctime[m]])
+    del cpost, cuser, ctime, ctype, cowner
+    for kind, e in inter.items():
+        # only the interactions whose users are both given
+        e = e[(e[:, 0] >= 0) & (e[:, 1] >= 0)]
+        inter[kind] = e[numpy.argsort(e[:, 2], kind="stable")]
+    e = numpy.concatenate([inter[k] for k in ["a2q", "c2q", "c2a"]])
+    inter["all"] = e[numpy.argsort(e[:, 2], kind="stable")]
+    return inter
 
 @cache_network()
 #@coerce_props()
@@ -67,9 +217,21 @@ def fetch_upstream(force=False):
 def parse(alts=None):
     global files
     name = __name__.split(".")[-1]
+    dump = None                 # the interactions of the last site of the dump
     for fname, alt, fmt in files:
         if alts is not None and alt not in alts:
             continue
+        if "_2024_" in alt:
+            if dump is None or dump[0] != fname:
+                dump = None
+                dump = (fname, dump_interactions(name, *fname))
+            e = dump[1][alt.split("_")[-1]]
+            g = Graph()
+            g.ep.timestamp = g.new_ep("int64_t")
+            vid = g.add_edge_list(e, hashed=True, eprops=[g.ep.timestamp])
+            g.vp.vid = vid.copy("int")
+            yield alt, g
+            continue
         with open_upstream_file(name, fname, "r") as f:
             g = Graph()
             g.ep.timestamp = time = g.new_ep("int64_t")
[entries] Fix parse bugs and improve the metadata of existing entries by Tiago de Paula Peixoto c996a0e
From a review of the dataset descriptions against the upstream sources
and papers, and from user reports:

- parse fixes (wrong edge direction or directedness, shifted or
  misnamed properties, merged node identities, duplicated or missing
  edges and nodes, wrong types), e.g. arxiv_citation (the HepTh dates
  were a copy of the HepPh ones; now from the KDD Cup 2003 data),
  eu_procurements, foursquare_global, hiv_transmission, genetic_multiplex,
  gnutella (adds the 05 snapshot), roadnet, macaque_neural, python_dependency
- descriptions rewritten from the upstream sources, documenting every
  property
- corrected titles, citations and DOIs, licences (ustream_license typo,
  the data's own terms), tags and ICON ids
- url lists for entries with several sources or dataset pages
- SocioPatterns entries moved to the site's new data location, with the
  daily networks of sp_infectious and sp_primary_school
- the Michael Fire entries marked as orphans (served from the mirror)
- redundant type forcings removed after the graph-tool coercion fixes
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index 081599e..32f23f6 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -19,13 +19,26 @@
 from .. import *
 
 title = "User interactions on Q&A websites (2016)"
-description = """Networks of interactions among users from four online Q&A sites: Stack Overflow, Math Overflow, Super User, and Ask Ubuntu. A directed edge (i,j)  indicates a user i responded to user j's post. Edges are timestamped. For each Q&A site, four differently defined networks are provided, based on the definition of an edge: (i) a user answered a question, (ii) a user commented on a question, (iii) a user commented on an answer, and (iv) the union of these three types.[^icon]
-[^icon]: Description obtained from the [ICON](https://icon.colorado.edu) project."""
+description = """Temporal networks of interactions among users of four question-and-answer websites of the Stack Exchange network: Stack Overflow (programming), Math Overflow (research mathematics), Super User (computer use) and Ask Ubuntu (the Ubuntu operating system). The networks were constructed by A. Paranjape, A. R. Benson and J. Leskovec from the public Stack Exchange data dump, and span from the creation of each site (2008 or 2009) until March 2016. Nodes are users, and a directed edge $(i,j)$ with timestamp $t$ indicates that user $i$ responded to a post of user $j$ at time $t$, in one of three ways:
+
+- `a2q`: user $i$ answered user $j$'s question;
+- `c2q`: user $i$ commented on user $j$'s question;
+- `c2a`: user $i$ commented on user $j$'s answer;
+
+and `all` is the union of the three types. Each network is named after the site and the type of interaction, as in `mathoverflow_a2q`. Each interaction is a separate edge, so pairs of users can be connected by many parallel edges, and the networks contain self-loops, e.g. when users comment on their own posts.
+
+The networks have the following properties:
+
+- `vid`: the user ID from the source data.
+- `timestamp`: the time of the interaction (Unix time, in seconds)."""
 tags = ['Social', 'Online', 'Unweighted', 'Timestamps']
-url = 'http://snap.stanford.edu/data/sx-stackoverflow.html'
+url = ['https://snap.stanford.edu/data/sx-stackoverflow.html',
+       'https://snap.stanford.edu/data/sx-mathoverflow.html',
+       'https://snap.stanford.edu/data/sx-superuser.html',
+       'https://snap.stanford.edu/data/sx-askubuntu.html']
 citation = [('A. Paranjape, A. R. Benson, & J. Leskovec, "Motifs in Temporal Networks". In Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM ’17). Association for Computing Machinery, New York, NY, USA, 601–610. (2017)', 'https://doi.org/10.1145/3018661.3018731')]
 icon_hash = '586eac70a411221d0e7c5bda'
-ustream_license = None
+upstream_license = None
 upstream_prefix = 'https://snap.stanford.edu/data'
 files = [('sx-stackoverflow.txt.gz', 'stackoverflow_all', None),
          ('sx-stackoverflow-a2q.txt.gz', 'stackoverflow_a2q', None),
Update copyright information by Tiago de Paula Peixoto 73a4f0d
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index 1dbc592..081599e 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -1,7 +1,7 @@
 #! /usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2020-2024 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2020-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
Update copyright information by Tiago de Paula Peixoto 3d288ea
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index 8e88b74..1dbc592 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -1,7 +1,7 @@
 #! /usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2020-2023 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2020-2024 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
Update copyright information by Tiago de Paula Peixoto 8d005ae
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index 0c49be1..8e88b74 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -1,7 +1,7 @@
 #! /usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2021 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2020-2023 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
Update copyright info by Tiago de Paula Peixoto db2efcc
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index a01dd9b..0c49be1 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -1,7 +1,7 @@
 #! /usr/bin/env python
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2020 Tiago de Paula Peixoto <tiago@skewed.de>
+# Copyright (C) 2021 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
Change license from GPLv3 to AGPLv3 by Tiago de Paula Peixoto d754be5
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index d1b24bf..a01dd9b 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -3,18 +3,18 @@
 #
 # Copyright (C) 2020 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 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 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 General Public License for more details.
+# 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 General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# 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 *
 
Add explicit attribution to descriptions obtained from ICON by Tiago de Paula Peixoto 1645314
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
index f843a4d..d1b24bf 100644
--- a/entries/qa_user/__init__.py
+++ b/entries/qa_user/__init__.py
@@ -19,7 +19,8 @@
 from .. import *
 
 title = "User interactions on Q&A websites (2016)"
-description = "Networks of interactions among users from four online Q&A sites: Stack Overflow, Math Overflow, Super User, and Ask Ubuntu. A directed edge (i,j)  indicates a user i responded to user j's post. Edges are timestamped. For each Q&A site, four differently defined networks are provided, based on the definition of an edge: (i) a user answered a question, (ii) a user commented on a question, (iii) a user commented on an answer, and (iv) the union of these three types."
+description = """Networks of interactions among users from four online Q&A sites: Stack Overflow, Math Overflow, Super User, and Ask Ubuntu. A directed edge (i,j)  indicates a user i responded to user j's post. Edges are timestamped. For each Q&A site, four differently defined networks are provided, based on the definition of an edge: (i) a user answered a question, (ii) a user commented on a question, (iii) a user commented on an answer, and (iv) the union of these three types.[^icon]
+[^icon]: Description obtained from the [ICON](https://icon.colorado.edu) project."""
 tags = ['Social', 'Online', 'Unweighted', 'Timestamps']
 url = 'http://snap.stanford.edu/data/sx-stackoverflow.html'
 citation = [('A. Paranjape, A. R. Benson, & J. Leskovec, "Motifs in Temporal Networks". In Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM ’17). Association for Computing Machinery, New York, NY, USA, 601–610. (2017)', 'https://doi.org/10.1145/3018661.3018731')]
Initial commit by Tiago de Paula Peixoto cc85872
diff --git a/entries/qa_user/__init__.py b/entries/qa_user/__init__.py
new file mode 100644
index 0000000..f843a4d
--- /dev/null
+++ b/entries/qa_user/__init__.py
@@ -0,0 +1,68 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from .. import *
+
+title = "User interactions on Q&A websites (2016)"
+description = "Networks of interactions among users from four online Q&A sites: Stack Overflow, Math Overflow, Super User, and Ask Ubuntu. A directed edge (i,j)  indicates a user i responded to user j's post. Edges are timestamped. For each Q&A site, four differently defined networks are provided, based on the definition of an edge: (i) a user answered a question, (ii) a user commented on a question, (iii) a user commented on an answer, and (iv) the union of these three types."
+tags = ['Social', 'Online', 'Unweighted', 'Timestamps']
+url = 'http://snap.stanford.edu/data/sx-stackoverflow.html'
+citation = [('A. Paranjape, A. R. Benson, & J. Leskovec, "Motifs in Temporal Networks". In Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM ’17). Association for Computing Machinery, New York, NY, USA, 601–610. (2017)', 'https://doi.org/10.1145/3018661.3018731')]
+icon_hash = '586eac70a411221d0e7c5bda'
+ustream_license = None
+upstream_prefix = 'https://snap.stanford.edu/data'
+files = [('sx-stackoverflow.txt.gz', 'stackoverflow_all', None),
+         ('sx-stackoverflow-a2q.txt.gz', 'stackoverflow_a2q', None),
+         ('sx-stackoverflow-c2q.txt.gz', 'stackoverflow_c2q', None),
+         ('sx-stackoverflow-c2a.txt.gz', 'stackoverflow_c2a', None),
+         ('sx-mathoverflow.txt.gz', 'mathoverflow_all', None),
+         ('sx-mathoverflow-a2q.txt.gz', 'mathoverflow_a2q', None),
+         ('sx-mathoverflow-c2q.txt.gz', 'mathoverflow_c2q', None),
+         ('sx-mathoverflow-c2a.txt.gz', 'mathoverflow_c2a', None),
+         ('sx-superuser.txt.gz', 'superuser_all', None),
+         ('sx-superuser-a2q.txt.gz', 'superuser_a2q', None),
+         ('sx-superuser-c2q.txt.gz', 'superuser_c2q', None),
+         ('sx-superuser-c2a.txt.gz', 'superuser_c2a', None),
+         ('sx-askubuntu.txt.gz', 'askubuntu_all', None),
+         ('sx-askubuntu-a2q.txt.gz', 'askubuntu_a2q', None),
+         ('sx-askubuntu-c2q.txt.gz', 'askubuntu_c2q', None),
+         ('sx-askubuntu-c2a.txt.gz', 'askubuntu_c2a', None)]
+
+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, "r") as f:
+            g = Graph()
+            g.ep.timestamp = time = g.new_ep("int64_t")
+            def get_lines():
+                for line in f:
+                    vals = [int(x) for x in line.split()]
+                    yield vals
+            vid = g.add_edge_list(get_lines(), hashed=True, eprops=[time])
+            g.vp.vid = vid.copy("int")
+        yield alt, g