logo Netzschleuder network catalog, repository, and centrifuge

History of polecat_events

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/polecat_events/__init__.py b/entries/polecat_events/__init__.py
index 86b3313..77771d7 100644
--- a/entries/polecat_events/__init__.py
+++ b/entries/polecat_events/__init__.py
@@ -51,7 +51,7 @@ citation = [('G. I. Scarborough, B. E. Bagozzi, A. Beger, J. Berrie, A. Halterma
              'https://doi.org/10.7910/DVN/AJGVIT'),
             ('A. Halterman, P. A. Schrodt, A. Beger, B. E. Bagozzi, G. I. Scarborough, "Creating Custom Event Data Without Dictionaries: A Bag-of-Tricks." arXiv:2304.01331 (2023)',
              'https://doi.org/10.48550/arXiv.2304.01331')]
-icon_hash = None
+date_added = '2026-10-27'
 upstream_license = ("CC0 1.0", "http://creativecommons.org/publicdomain/zero/1.0")
 # Harvard Dataverse file ids of ngecEvents.DV.<year>.txt, version 62.0 of
 # doi:10.7910/DVN/AJGVIT
[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/polecat_events/__init__.py b/entries/polecat_events/__init__.py
index 113fc7e..86b3313 100644
--- a/entries/polecat_events/__init__.py
+++ b/entries/polecat_events/__init__.py
@@ -27,24 +27,24 @@ Each event lists its actors and recipients (possibly several of each). An actor
 
 The network has the following properties:
 
-- `name`: the name of the actor, or its country and sector for unnamed actors.
-- `named`: whether the actor has a name in the data (otherwise it is defined by its country and sector).
-- `country`: the country of the actor (the most frequent one in its events; e.g. `Intergovernmental Organization` for international organizations; `None` if not known).
-- `cow_code`: the Correlates of War code of the actor's country (the most frequent one; -1 if not known).
-- `sector`: the primary sector of the actor (the most frequent one), as a PLOVER sector code (e.g. `GOV` government, `MIL` military, `COP` police, `JUD` judiciary, `LEG` legislature, `PTY` political party, `REB` rebels, `CVL` civilians, `JRN` journalists, `BUS` business; `None` if not known).
-- `wikipedia_id`: the title of the Wikipedia page of the actor, as resolved by the coder (the most frequent one; `None` if not known).
-- Edge `event_id`: the identifier of the event.
-- Edge `time`: the date of the event, in Unix time.
-- Edge `event_type`: the PLOVER type of the event: `AGREE`, `CONSULT`, `SUPPORT`, `CONCEDE`, `COOPERATE`, `AID`, `RETREAT`, `REQUEST`, `ACCUSE`, `REJECT`, `THREATEN`, `PROTEST`, `SANCTION`, `MOBILIZE`, `COERCE` or `ASSAULT`.
-- Edge `event_mode`: the PLOVER mode of the event, a subtype (e.g. `arrest`, `demo`, `ceasefire`; empty if none).
-- Edge `intensity`: the intensity of the event, from -10 (most hostile) to 10 (most cooperative).
-- Edge `quad_code`: `VERBAL COOPERATION`, `MATERIAL COOPERATION`, `VERBAL CONFLICT` or `MATERIAL CONFLICT`.
-- Edge `contexts`: the contexts of the event (e.g. `military`, `election`, `health`), separated by `|` (`None` if none).
-- Edge `placename`: the name of the location of the event (`None` if not known).
-- Edge `location_country`: the ISO 3166-1 alpha-3 code of the country of the location (`None` if not known).
-- Edge `latitude`, `longitude`: the coordinates of the location (NaN if not known).
-- Edge `source`: the news outlet of the story.
-- Edge `language`: the language of the story."""
+- (Node) `name`: the name of the actor, or its country and sector for unnamed actors.
+- (Node) `named`: whether the actor has a name in the data (otherwise it is defined by its country and sector).
+- (Node) `country`: the country of the actor (the most frequent one in its events; e.g. `Intergovernmental Organization` for international organizations; `None` if not known).
+- (Node) `cow_code`: the Correlates of War code of the actor's country (the most frequent one; -1 if not known).
+- (Node) `sector`: the primary sector of the actor (the most frequent one), as a PLOVER sector code (e.g. `GOV` government, `MIL` military, `COP` police, `JUD` judiciary, `LEG` legislature, `PTY` political party, `REB` rebels, `CVL` civilians, `JRN` journalists, `BUS` business; `None` if not known).
+- (Node) `wikipedia_id`: the title of the Wikipedia page of the actor, as resolved by the coder (the most frequent one; `None` if not known).
+- (Edge) `event_id`: the identifier of the event.
+- (Edge) `time`: the date of the event, in Unix time.
+- (Edge) `event_type`: the PLOVER type of the event: `AGREE`, `CONSULT`, `SUPPORT`, `CONCEDE`, `COOPERATE`, `AID`, `RETREAT`, `REQUEST`, `ACCUSE`, `REJECT`, `THREATEN`, `PROTEST`, `SANCTION`, `MOBILIZE`, `COERCE` or `ASSAULT`.
+- (Edge) `event_mode`: the PLOVER mode of the event, a subtype (e.g. `arrest`, `demo`, `ceasefire`; empty if none).
+- (Edge) `intensity`: the intensity of the event, from -10 (most hostile) to 10 (most cooperative).
+- (Edge) `quad_code`: `VERBAL COOPERATION`, `MATERIAL COOPERATION`, `VERBAL CONFLICT` or `MATERIAL CONFLICT`.
+- (Edge) `contexts`: the contexts of the event (e.g. `military`, `election`, `health`), separated by `|` (`None` if none).
+- (Edge) `placename`: the name of the location of the event (`None` if not known).
+- (Edge) `location_country`: the ISO 3166-1 alpha-3 code of the country of the location (`None` if not known).
+- (Edge) `latitude`, `longitude`: the coordinates of the location (NaN if not known).
+- (Edge) `source`: the news outlet of the story.
+- (Edge) `language`: the language of the story."""
 tags = ['Social', 'Political', 'Conflict', 'Unweighted', 'Multigraph', 'Timestamps', 'Metadata']
 url = ['https://doi.org/10.7910/DVN/AJGVIT', 'https://doi.org/10.7910/DVN/LMFPIP']
 citation = [('G. I. Scarborough, B. E. Bagozzi, A. Beger, J. Berrie, A. Halterman, P. A. Schrodt, J. Spivey, "POLECAT Weekly Data." Harvard Dataverse, V62 (2024)',
[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/polecat_events/__init__.py b/entries/polecat_events/__init__.py
index 4775541..113fc7e 100644
--- a/entries/polecat_events/__init__.py
+++ b/entries/polecat_events/__init__.py
@@ -96,7 +96,6 @@ def most_common(c):
 @coerce_props(vforce={"name": "string"}, eforce={"event_mode": "string"})
 @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:
[entries] New entry: polecat_events by Tiago de Paula Peixoto 60bc84f
diff --git a/entries/polecat_events/__init__.py b/entries/polecat_events/__init__.py
new file mode 100644
index 0000000..4775541
--- /dev/null
+++ b/entries/polecat_events/__init__.py
@@ -0,0 +1,185 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# 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
+# 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 collections
+import pandas
+
+title = "Political events between actors (POLECAT, 2018-2024)"
+description = r"""A temporal network of political events between socio-political actors worldwide (governments, politicians, parties, the military, the police, armed groups, journalists, citizens, ...), from January 2018 to June 2024, from the POLitical Event Classification, Attributes, and Types (POLECAT) dataset, the successor of the Integrated Crisis Early Warning System (ICEWS) event data. POLECAT codes events automatically, with a machine-learning coder (NGEC) based on the PLOVER ontology of event types, from news stories in seven languages (English, Russian, Spanish, French, Portuguese, Arabic and Chinese) from over a thousand sources. Each directed edge from A to B is an event in which actor A acts upon actor B (e.g. A accuses, threatens, aids or assaults B), with its date, type and other attributes.
+
+Each event lists its actors and recipients (possibly several of each). An actor with a name in the data (the name resolved by the coder, usually that of a person or organization, e.g. `Narendra Modi`, `NATO`) is a node identified by that name. Actors without a name, which are described only by their country and their sector (e.g. the Ukrainian military, the Indian police), are nodes identified by the country and the sector, e.g. `Ukraine MIL` or `India COP`; actors with neither name, country nor sector are left out. An event with several actors or recipients becomes one edge for each pair of actor and recipient. Events without any identifiable actor or recipient (about a third of the 2.2 million events) are not included. Of the 9 event identifiers that appear twice in the data, only the first occurrence is kept. The data are the yearly files of version 62 of the "POLECAT Weekly Data" dataset (the file of 2024 ends in June 2024). The raw names and titles of the actors as they appear in the stories, the people, organizations and locations mentioned in the stories, and the finer geographic information (city, district, province, GeoNames identifiers) are not included.
+
+The network has the following properties:
+
+- `name`: the name of the actor, or its country and sector for unnamed actors.
+- `named`: whether the actor has a name in the data (otherwise it is defined by its country and sector).
+- `country`: the country of the actor (the most frequent one in its events; e.g. `Intergovernmental Organization` for international organizations; `None` if not known).
+- `cow_code`: the Correlates of War code of the actor's country (the most frequent one; -1 if not known).
+- `sector`: the primary sector of the actor (the most frequent one), as a PLOVER sector code (e.g. `GOV` government, `MIL` military, `COP` police, `JUD` judiciary, `LEG` legislature, `PTY` political party, `REB` rebels, `CVL` civilians, `JRN` journalists, `BUS` business; `None` if not known).
+- `wikipedia_id`: the title of the Wikipedia page of the actor, as resolved by the coder (the most frequent one; `None` if not known).
+- Edge `event_id`: the identifier of the event.
+- Edge `time`: the date of the event, in Unix time.
+- Edge `event_type`: the PLOVER type of the event: `AGREE`, `CONSULT`, `SUPPORT`, `CONCEDE`, `COOPERATE`, `AID`, `RETREAT`, `REQUEST`, `ACCUSE`, `REJECT`, `THREATEN`, `PROTEST`, `SANCTION`, `MOBILIZE`, `COERCE` or `ASSAULT`.
+- Edge `event_mode`: the PLOVER mode of the event, a subtype (e.g. `arrest`, `demo`, `ceasefire`; empty if none).
+- Edge `intensity`: the intensity of the event, from -10 (most hostile) to 10 (most cooperative).
+- Edge `quad_code`: `VERBAL COOPERATION`, `MATERIAL COOPERATION`, `VERBAL CONFLICT` or `MATERIAL CONFLICT`.
+- Edge `contexts`: the contexts of the event (e.g. `military`, `election`, `health`), separated by `|` (`None` if none).
+- Edge `placename`: the name of the location of the event (`None` if not known).
+- Edge `location_country`: the ISO 3166-1 alpha-3 code of the country of the location (`None` if not known).
+- Edge `latitude`, `longitude`: the coordinates of the location (NaN if not known).
+- Edge `source`: the news outlet of the story.
+- Edge `language`: the language of the story."""
+tags = ['Social', 'Political', 'Conflict', 'Unweighted', 'Multigraph', 'Timestamps', 'Metadata']
+url = ['https://doi.org/10.7910/DVN/AJGVIT', 'https://doi.org/10.7910/DVN/LMFPIP']
+citation = [('G. I. Scarborough, B. E. Bagozzi, A. Beger, J. Berrie, A. Halterman, P. A. Schrodt, J. Spivey, "POLECAT Weekly Data." Harvard Dataverse, V62 (2024)',
+             'https://doi.org/10.7910/DVN/AJGVIT'),
+            ('A. Halterman, P. A. Schrodt, A. Beger, B. E. Bagozzi, G. I. Scarborough, "Creating Custom Event Data Without Dictionaries: A Bag-of-Tricks." arXiv:2304.01331 (2023)',
+             'https://doi.org/10.48550/arXiv.2304.01331')]
+icon_hash = None
+upstream_license = ("CC0 1.0", "http://creativecommons.org/publicdomain/zero/1.0")
+# Harvard Dataverse file ids of ngecEvents.DV.<year>.txt, version 62.0 of
+# doi:10.7910/DVN/AJGVIT
+upstream_prefix = 'https://dataverse.harvard.edu/api/access/datafile'
+FILE_IDS = {2018: '10454249', 2019: '10423692', 2020: '10423691', 2021: '10399130',
+            2022: '10399128', 2023: '10399126', 2024: '10399129'}
+files = [(tuple(FILE_IDS.values()), None, None)]
+
+def fetch_upstream(force=False):
+    return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+                                force)
+
+COLS = ["Event ID", "Event Date", "Event Type", "Event Mode", "Event Intensity",
+        "Quad Code", "Contexts", "Placename", "Country", "Latitude", "Longitude",
+        "Source", "Language"]
+SIDE_COLS = ["Name", "Country", "COW", "Primary {} Sector", "Wikipedia {} ID"]
+
+def side_cols(side):
+    return [f"{side} {c}" if "{}" not in c else c.format(side) for c in SIDE_COLS]
+
+def split_actors(row):
+    "The actors of one side of an event, as (name, country, cow, sector, wiki) tuples."
+    fields = [x.split("; ") for x in row]
+    n = len(fields[0])
+    # a few events have lists of different lengths; they are padded or cut
+    fields = [(f + ["None"] * n)[:n] for f in fields]
+    return list(zip(*fields))
+
+def actor_key(a):
+    name, country, cow, sector, wiki = a
+    if name != "None":
+        return name, True
+    parts = [x for x in (country, sector) if x != "None"]
+    if not parts:
+        return None, False
+    return " ".join(parts), False
+
+def most_common(c):
+    return c.most_common(1)[0][0]
+
+@cache_network()
+@coerce_props(vforce={"name": "string"}, eforce={"event_mode": "string"})
+@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
+        acols, rcols = side_cols("Actor"), side_cols("Recipient")
+        vidx = {}
+        named = []
+        attrs = []                  # per actor: Counters of country, cow, sector, wiki
+        def get_v(a):
+            key, is_named = actor_key(a)
+            if key is None:
+                return None
+            v = vidx.get(key)
+            if v is None:
+                v = vidx[key] = len(vidx)
+                named.append(is_named)
+                attrs.append([collections.Counter() for i in range(4)])
+            for c, x in zip(attrs[v], a[1:]):
+                c[x] += 1
+            return v
+        edges = []
+        eattrs = {c: [] for c in COLS}
+        seen = set()
+        for fid in fnames:
+            with open_upstream_file(name, fid, "rb") as f:
+                d = pandas.read_csv(f, sep="\t", dtype=str, keep_default_na=False,
+                                    quoting=csv.QUOTE_NONE,
+                                    usecols=COLS + acols + rcols)
+            d = d[~d["Event ID"].isin(seen)]
+            d = d.drop_duplicates("Event ID")
+            seen.update(d["Event ID"])
+            ecols = d[COLS].to_numpy()
+            for erow, arow, rrow in zip(ecols, d[acols].to_numpy(),
+                                        d[rcols].to_numpy()):
+                if arow[0] == "None" and arow[1] == "None" and arow[3] == "None":
+                    continue
+                if rrow[0] == "None" and rrow[1] == "None" and rrow[3] == "None":
+                    continue
+                us = [get_v(a) for a in split_actors(arow)]
+                ws = [get_v(a) for a in split_actors(rrow)]
+                for u in us:
+                    if u is None:
+                        continue
+                    for w in ws:
+                        if w is None:
+                            continue
+                        edges.append((u, w))
+                        for c, x in zip(COLS, erow):
+                            eattrs[c].append(x)
+            del d, ecols
+        g = Graph(directed=True)
+        g.add_vertex(len(vidx))
+        g.vp.name = g.new_vp("string", vals=list(vidx.keys()))
+        g.vp.named = g.new_vp("bool", vals=named)
+        g.vp.country = g.new_vp("string", vals=[most_common(a[0]) for a in attrs])
+        def cow(a):
+            for x, c in a[1].most_common():
+                if x.strip().lstrip("-").isdigit():
+                    return int(x)
+            return -1
+        g.vp.cow_code = g.new_vp("int", vals=[cow(a) for a in attrs])
+        g.vp.sector = g.new_vp("string", vals=[most_common(a[2]) for a in attrs])
+        g.vp.wikipedia_id = g.new_vp("string", vals=[most_common(a[3]) for a in attrs])
+        del attrs
+        g.add_edge_list(edges)
+        del edges
+        def fnum(x):
+            try:
+                return float(x)
+            except ValueError:
+                return numpy.nan
+        g.ep.event_id = g.new_ep("string", vals=eattrs.pop("Event ID"))
+        g.ep.time = g.new_ep("int64_t", vals=pandas.to_datetime(pandas.Series(eattrs.pop("Event Date"))).astype("int64") // 10**9)
+        g.ep.event_type = g.new_ep("string", vals=eattrs.pop("Event Type"))
+        g.ep.event_mode = g.new_ep("string", vals=["" if x in ("None", "NA") else x
+                                                   for x in eattrs.pop("Event Mode")])
+        g.ep.intensity = g.new_ep("double", vals=[fnum(x) for x in eattrs.pop("Event Intensity")])
+        g.ep.quad_code = g.new_ep("string", vals=eattrs.pop("Quad Code"))
+        g.ep.contexts = g.new_ep("string", vals=[x.strip() for x in eattrs.pop("Contexts")])
+        g.ep.placename = g.new_ep("string", vals=eattrs.pop("Placename"))
+        g.ep.location_country = g.new_ep("string", vals=eattrs.pop("Country"))
+        g.ep.latitude = g.new_ep("double", vals=[fnum(x) for x in eattrs.pop("Latitude")])
+        g.ep.longitude = g.new_ep("double", vals=[fnum(x) for x in eattrs.pop("Longitude")])
+        g.ep.source = g.new_ep("string", vals=eattrs.pop("Source"))
+        g.ep.language = g.new_ep("string", vals=eattrs.pop("Language"))
+        yield alt, g