logo Netzschleuder network catalog, repository, and centrifuge

History of eventernote

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: eventernote by Tiago de Paula Peixoto 84a9dd0
diff --git a/entries/eventernote/__init__.py b/entries/eventernote/__init__.py
new file mode 100644
index 0000000..cf60d0f
--- /dev/null
+++ b/entries/eventernote/__init__.py
@@ -0,0 +1,105 @@
+#! /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 calendar
+
+from .. import *
+
+
+title = "Japanese idol and voice actor events (Eventernote, 1980-2024)"
+description = """A bipartite network of Japanese idols, voice actors and other artists, and the events they took part in (concerts, release events, stage greetings, broadcasts, etc.), from Eventernote, a crowdsourced Japanese website that lists such events, as collected for XGI-data. Events span from July 1980 to April 2024; an event of several days is split into one event per day. An artist node is either an individual artist or a group of artists (e.g. an idol group); individuals and their groups are distinct nodes, and which artists belong to which groups is not given. The data list 5,381 artists without any event, which are isolated nodes.
+
+XGI-data also distributes a bipartite network of the artists and the places of their events; it is not included as a separate network, since it follows from this one (the places are given as properties of the events).
+
+The network has the following properties:
+
+- `is_event`: whether the node is an event (otherwise an artist).
+- `eventernote_id`: the identifier of the artist or event on Eventernote (e.g. `https://www.eventernote.com/actors/<id>` or `https://www.eventernote.com/events/<id>`).
+- Artists: `name`, the name of the artist or group; `kana`, the reading of the name in kana; `sex`, `female`, `male`, or `other` (non-binary, not specified or not applicable, e.g. for groups).
+- Events: `name`, the name of the event; `date`, the date of the event (YYYY-MM-DD); `time`, the same date in Unix time (midnight UTC); `place_id`, the Eventernote identifier of the place of the event (`https://www.eventernote.com/places/<id>`; -1 if unknown); `place_name`, the name of the place; `prefecture`, the prefecture of the place, as its ISO 3166-2:JP code (1 for Hokkaido to 47 for Okinawa; 0 if the prefecture of a known place is not given, 90 for places outside Japan, and -1 for events without a known place)."""
+tags = ['Social', 'Offline', 'Affiliation', 'Unweighted', 'Timestamps', 'Metadata']
+url = ['https://www.eventernote.com/',
+       'https://zenodo.org/records/21917912',
+       'https://zenodo.org/records/21917851',
+       'https://github.com/xgi-org/xgi-data']
+citation = [('N. W. Landry, M. Lucas, I. Iacopini, G. Petri, A. Schwarze, A. Patania, L. Torres, "XGI: A Python package for higher-order interaction networks." Journal of Open Source Software 8(85), 5162 (2023)', 'https://doi.org/10.21105/joss.05162')]
+icon_hash = None
+upstream_license = ('CC BY-SA 4.0', 'https://creativecommons.org/licenses/by-sa/4.0/')
+upstream_prefix = 'https://zenodo.org/records'
+files = [(('21917912/files/eventernote-events.json',
+           '21917851/files/eventernote-places.json'), None, None)]
+
+sexes = {1: "female", 2: "male", 3: "other"}
+
+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 fnames, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        # HIF (hypergraph interchange format) of XGI
+        with open_upstream_file(name, fnames[1], "r") as f:
+            places = {int(e["edge"]): e["attrs"] for e in json.load(f)["edges"]}
+        with open_upstream_file(name, fnames[0], "r") as f:
+            d = json.load(f)
+        artists = sorted(d["nodes"], key=lambda n: int(n["node"]))
+        events = sorted(d["edges"], key=lambda e: int(e["edge"]))
+        aindex = {n["node"]: i for i, n in enumerate(artists)}
+        N = len(artists)
+        eindex = {e["edge"]: N + i for i, e in enumerate(events)}
+        g = Graph(directed=False)
+        g.add_vertex(N + len(events))
+        g.vp.is_event = g.new_vp("bool")
+        g.vp.is_event.a[N:] = True
+        g.vp.eventernote_id = g.new_vp("int64_t",
+                                       vals=[int(n["node"]) for n in artists] +
+                                            [int(e["edge"]) for e in events])
+        g.vp.name = g.new_vp("string", vals=[n["attrs"]["name"] for n in artists] +
+                                            [e["attrs"]["event_name"] for e in events])
+        g.vp.kana = g.new_vp("string", vals=[n["attrs"]["kana"] for n in artists] +
+                                            [""] * len(events))
+        g.vp.sex = g.new_vp("string", vals=[sexes[n["attrs"]["sex"]] for n in artists] +
+                                           [""] * len(events))
+        dates = [e["attrs"]["event_date"] for e in events]
+        g.vp.date = g.new_vp("string", vals=[""] * N + dates)
+        g.vp.time = g.new_vp("int64_t")
+        g.vp.time.a[N:] = [calendar.timegm(time.strptime(x, "%Y-%m-%d")) for x in dates]
+        pids = []
+        for e in events:
+            p = e["attrs"].get("place_id")
+            pids.append(-1 if p is None or p != p else int(p))
+        g.vp.place_id = g.new_vp("int", val=-1)
+        g.vp.place_id.a[N:] = pids
+        g.vp.place_name = g.new_vp("string", vals=[""] * N +
+                                   [places[p]["place_name"] if p in places else ""
+                                    for p in pids])
+        g.vp.prefecture = g.new_vp("int", val=-1)
+        g.vp.prefecture.a[N:] = [int(places[p]["prefecture"]) if p in places else -1
+                                 for p in pids]
+        g.add_edge_list(sorted(set((aindex[x["node"]], eindex[x["edge"]])
+                                   for x in d["incidences"]), key=lambda e: e[::-1]))
+        yield alt, g