History of un_votes
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: un_votes f9c6a11
diff --git a/entries/un_votes/__init__.py b/entries/un_votes/__init__.py
new file mode 100644
index 0000000..d81ac04
--- /dev/null
+++ b/entries/un_votes/__init__.py
@@ -0,0 +1,226 @@
+#! /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 pandas
+
+title = "UN General Assembly roll-call votes (1946-2024)"
+description = r"""Bipartite networks of the recorded votes of the member states of the United Nations General Assembly (UNGA), from two independent compilations. In both, the nodes are the member states and the decisions taken by recorded vote (roll calls), and a member state is connected to a decision if it voted in favor, against, or abstained; the edge is annotated with the vote (a signed network). Member states that did not vote (absent, not voting, or not members at the time) are not connected to the decision.
+
+- `unga_dm`: the UNGA-DM database of J. Fjelstul, S. Hug and C. Kilby (version 2.0), which compiles all resolution-related decisions of the General Assembly from its meeting records (UN Official Document System and UN Digital Library) and from cleaned versions of the ICPSR roll-call data for the first sessions. It covers the roll calls between January 1946 and September 2023, in regular, special and emergency special sessions: votes on the final passage of draft resolutions (including failed ones), on amendments, on motions, and separate votes on parts of draft resolutions.
+- `voeten`: the raw roll-call data of E. Voeten, A. Strezhnev and M. Bailey ("United Nations General Assembly Voting Data", Harvard Dataverse, version 33 of June 2024, the last version that contains the raw votes; later versions only have the derived ideal points and agreement scores). It covers sessions 1 to 78 (January 1946 to June 2024). It was assembled from the ICPSR roll-call data, the data of E. Gartzke and D.-J. Jo, and UN documents, and differs from UNGA-DM in several ways: committee votes of sessions 27-29 and unanimous votes of sessions 40-51 were removed, votes on amendments, paragraphs and failed resolutions are incomplete before the 1990s, decisions are identified by roll-call ids with resolution numbers that are not always correct before 1985, member states are identified by their Correlates of War code only (e.g. Yugoslavia, Serbia and Montenegro and Serbia share the code 345), and the decisions are annotated with issue codes.
+
+The `unga_dm` network uses the extract of all recorded votes by member states (`votes_extr.csv`) of the database. Decisions taken without a vote or by a non-recorded vote, for which there are no votes per member state, are not included, and neither are the database's additional votes not related to resolutions (e.g. elections). Votes are as amended by member states after the vote (through changes notified to the Secretariat); the original votes are kept as an edge property.
+
+The `voeten` network uses the file `UNVotes-1.RData`, whose last session (78) has errors: 88 votes without a member state (a state that could not be identified) are left out, as are 130 of the 261 rows of session 78 of Yemen, Zambia and Zimbabwe, which carry the resolution number, date and vote totals of another roll call than the one they are assigned to (these rows are scrambled, so their votes cannot be attributed reliably); the few remaining duplicated votes (the same member state, roll call and vote) are merged. The vote totals given for each roll call (`num_yes`, `num_no`, `num_abstain`) are kept as in the data and do not always agree with the votes of the network. The undocumented variable `ident` is not included.
+
+The networks have the following properties:
+
+- `is_member_state`: whether the node is a member state (otherwise it is a decision).
+- `cow_code` (member states): the Correlates of War (COW) code of the member state (-1 for decisions).
+- Edge `vote`: the vote of the member state: $+1$ in favor, $-1$ against, $0$ abstaining.
+
+Properties of `unga_dm`:
+
+- `name`: the common name of the member state (unique per Correlates of War code), or the identifier of the decision: the resolution number (or the number of the draft resolution or other document, if it was not adopted) followed by `-FP` for the final passage, `-A####` for an amendment, `-M####` for a motion and `-S####` for a separate vote.
+- `seat_name` (member states): the current official name of the member state's seat.
+- `resolution` (decisions): the number of the resolution (`rejected` if the draft resolution was not adopted).
+- `draft_resolution` (decisions): the number of the draft resolution.
+- `session` (decisions): the session (`RS-###` for regular sessions, `SS-###` for special sessions, `ESS-###` for emergency special sessions).
+- `meeting_record` (decisions): the meeting record of the meeting in which the decision was taken.
+- `topic` (decisions): `draft resolution`, `amendment`, `motion` or `separate vote`.
+- `decision_mode` (decisions): `recorded vote`, or `non-recorded vote` for the few decisions with a non-recorded vote in the meeting record but votes per member state in the data.
+- `important` (decisions): whether the United States designated the vote as important: `important`, `not important`, or `not available` (before the 38th regular session).
+- `requested_by` (decisions): the member states that requested the recorded vote (`not available` if unknown or not applicable).
+- `time` (decisions): the date of the meeting, in Unix time.
+- Edge `original_vote`: the vote originally recorded (`in favor`, `against`, `abstaining`, `not voting` or `not voting (suspended)`).
+- Edge `vote_was_amended`: whether the member state changed its vote after the vote.
+
+Properties of `voeten`:
+
+- `name`: the most frequent name of the member state in the data, or the resolution number of the decision (not unique: several roll calls can concern the same resolution).
+- `country_code` (member states): the three-letter code of the member state.
+- `rcid` (decisions): the roll-call id (matching the ICPSR ids until 1985; ids above 9000 are emergency special sessions not in the ICPSR data).
+- `resid` (decisions): the sequential id of the roll call within its session (session number followed by three digits).
+- `session` (decisions): the number of the session (sessions run from September into the following year; emergency special sessions are assigned to the regular session of the time).
+- `resolution` (decisions): the resolution number.
+- `time` (decisions): the date of the vote, in Unix time.
+- `amendment`, `paragraph` (decisions): whether the vote was on an amendment, or on a paragraph rather than an entire resolution.
+- `short_description`, `long_description` (decisions): the descriptions of the roll call.
+- `important` (decisions): whether the vote was identified as important by the US State Department report "Voting Practices in the United Nations": `important`, `not important`, or `not available`.
+- `middle_east`, `nuclear`, `disarmament`, `human_rights`, `colonialism`, `economic_development` (decisions): the issue codes of the data (votes relating to the Palestinian conflict, nuclear weapons and material, arms control and disarmament, human rights, colonialism, and economic development), assigned by searches of the descriptions.
+- `num_yes`, `num_no`, `num_abstain` (decisions): the numbers of votes in favor, against and abstaining, as given in the data (-1 where missing)."""
+tags = ['Social', 'Political', 'Offline', 'Signed', 'Unweighted', 'Timestamps', 'Metadata']
+url = ['https://unvotes.unige.ch/', 'https://doi.org/10.7910/DVN/LEJUQZ']
+citation = [('J. Fjelstul, S. Hug, C. Kilby, "Decision-making in the United Nations General Assembly: A comprehensive database of resolution-related decisions." The Review of International Organizations 21, 447-464 (2026)',
+ 'https://doi.org/10.1007/s11558-024-09580-1'),
+ ('E. Voeten, "Data and Analyses of Voting in the UN General Assembly." In B. Reinalda (ed.), Routledge Handbook of International Organization, Routledge (2013)',
+ 'https://doi.org/10.2139/ssrn.2111149'),
+ ('M. A. Bailey, A. Strezhnev, E. Voeten, "Estimating Dynamic State Preferences from United Nations Voting Data." Journal of Conflict Resolution 61(2), 430-456 (2017)',
+ 'https://doi.org/10.1177/0022002715595700'),
+ ('E. Voeten, A. Strezhnev, M. Bailey, "United Nations General Assembly Voting Data." Harvard Dataverse, version 33 (2024)',
+ 'https://doi.org/10.7910/DVN/LEJUQZ')]
+icon_hash = None
+# The UNGA-DM database states no license; Voeten et al.'s data are CC0
+upstream_license = ("CC0 1.0 Universal (voeten); none stated (unga_dm)",
+ "https://creativecommons.org/publicdomain/zero/1.0/")
+# Version 2.0 of the UNGA-DM database (the path is that of the uploaded file),
+# and the raw votes of Voeten et al. (Harvard Dataverse, version 33 of the
+# dataset, the last one with the raw votes, as file id: UNVotes-1.RData)
+upstream_prefix = {'unga_dm': 'https://unvotes.unige.ch/application/files/4917/3287/3542',
+ 'voeten': 'https://dataverse.harvard.edu/api/access/datafile'}
+files = [('votes_extr.csv', 'unga_dm', None),
+ ('10295576', 'voeten', None)]
+
+VOTES = {"in favor": 1, "against": -1, "abstaining": 0}
+
+def fetch_upstream(force=False):
+ for f in files:
+ fetch_upstream_files(__name__.split(".")[-1], upstream_prefix[f[1]],
+ [f], force)
+
+@cache_network()
+@coerce_props(vforce={"name": "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
+ if alt == "voeten":
+ with open_upstream_file(name, fnames, "rb") as f:
+ g = parse_voeten(f)
+ yield alt, g
+ continue
+ with open_upstream_file(name, fnames, "rb") as f:
+ d = pandas.read_csv(f, keep_default_na=False, na_values=[],
+ dtype=str)
+ d = d[d.amended_vote.isin(VOTES.keys())]
+ states = d.drop_duplicates("member_state_id")
+ states = states.assign(cow=states.member_state_id.astype(int)).sort_values("cow")
+ decisions = d.drop_duplicates("decision_id")
+ decisions = decisions.assign(t=pandas.to_datetime(decisions.meeting_date)).sort_values(["t", "decision_id"], kind="stable")
+ N = len(states)
+ sidx = {s: i for i, s in enumerate(states.member_state_id)}
+ didx = {s: N + i for i, s in enumerate(decisions.decision_id)}
+ g = Graph(directed=False)
+ g.add_vertex(N + len(decisions))
+ g.vp.is_member_state = g.new_vp("bool")
+ g.vp.is_member_state.a[:N] = True
+ g.vp.name = g.new_vp("string", vals=list(states.member_state) +
+ list(decisions.decision_id))
+ g.vp.cow_code = g.new_vp("int", vals=list(states.cow) + [-1] * len(decisions))
+ g.vp.seat_name = g.new_vp("string", vals=list(states.current_seat_name) +
+ [""] * len(decisions))
+ for p, col in [("resolution", "resolution_id"),
+ ("draft_resolution", "draft_resolution_id"),
+ ("session", "session_id"),
+ ("meeting_record", "meeting_record_id"),
+ ("topic", "decision_topic"),
+ ("decision_mode", "decision_mode"),
+ ("important", "important"),
+ ("requested_by", "request_by")]:
+ g.vp[p] = g.new_vp("string", vals=[""] * N + list(decisions[col]))
+ g.vp.time = g.new_vp("int64_t", vals=[0] * N +
+ list(decisions.t.astype("int64") // 10**9))
+ g.ep.vote = g.new_ep("int")
+ g.ep.original_vote = g.new_ep("string")
+ g.ep.vote_was_amended = g.new_ep("bool")
+ g.add_edge_list(numpy.column_stack((d.member_state_id.map(sidx).to_numpy(),
+ d.decision_id.map(didx).to_numpy())))
+ g.ep.vote.a = d.amended_vote.map(VOTES).to_numpy()
+ for e, x in zip(g.edges(), d.original_vote):
+ g.ep.original_vote[e] = x
+ g.ep.vote_was_amended.a = d.vote_was_amended.astype(int).to_numpy()
+ yield alt, g
+
+# Codes of the votes in Voeten's data: 1 yes, 2 abstain, 3 no (8 absent, 9
+# not a member)
+VOETEN_VOTES = {1: 1, 3: -1, 2: 0}
+VOETEN_ISSUES = [("me", "middle_east"), ("nu", "nuclear"),
+ ("di", "disarmament"), ("hr", "human_rights"),
+ ("co", "colonialism"), ("ec", "economic_development")]
+
+def parse_voeten(f):
+ d = read_rdata(f)["completeVotes"]
+ val, attrs = d
+ d = pandas.DataFrame({k: (c[0] if isinstance(c, tuple) else c)
+ for k, c in zip(attrs["names"], val)})
+ # The session 78 rows of Yemen, Zambia and Zimbabwe are scrambled: many
+ # carry the resolution, date and totals of another roll call than their
+ # rcid; keep only those that agree with the other rows of their roll call
+ key = ["unres", "date", "yes", "no", "abstain"]
+ ref = d[d.ccode.notna() & ~d.Country.isin(["YEM", "ZMB", "ZWE"])]
+ ref = ref.drop_duplicates("rcid").set_index("rcid")[key]
+ s78 = (d.session == 78) & d.Country.isin(["YEM", "ZMB", "ZWE"])
+ r = ref.loc[d.rcid[s78]]
+ agree = numpy.ones(len(d), dtype=bool)
+ agree[s78.to_numpy()] = numpy.all([(d[k][s78].to_numpy() == r[k].to_numpy()) |
+ (d[k][s78].isna().to_numpy() &
+ r[k].isna().to_numpy())
+ for k in key], axis=0)
+ # Rows without a country (all in session 78) cannot be attributed
+ d = d[agree & d.ccode.notna()]
+ names = d[d.Countryname.notna()].groupby("ccode").Countryname.agg(lambda x: x.value_counts().index[0])
+ codes = d[d.Country.str.len() == 3].groupby("ccode").Country.agg(lambda x: x.value_counts().index[0])
+ # Rows of non-voting member states (absent, or not members) are dropped
+ # before the duplicates, since e.g. Yugoslavia, Serbia and Montenegro, and
+ # Serbia share the code 345 and have several rows per roll call
+ d = d[d.vote.isin(VOETEN_VOTES.keys())]
+ assert not d.duplicated(["rcid", "ccode", "vote"], keep=False).ne(
+ d.duplicated(["rcid", "ccode"], keep=False)).any()
+ d = d.drop_duplicates(["rcid", "ccode"])
+ states = numpy.sort(d.ccode.unique())
+ decisions = d.drop_duplicates("rcid").sort_values(["date", "rcid"], kind="stable")
+ N = len(states)
+ sidx = {s: i for i, s in enumerate(states)}
+ didx = {s: N + i for i, s in enumerate(decisions.rcid)}
+ M = len(decisions)
+ g = Graph(directed=False)
+ g.add_vertex(N + M)
+ g.vp.is_member_state = g.new_vp("bool")
+ g.vp.is_member_state.a[:N] = True
+ g.vp.name = g.new_vp("string", vals=[names.get(c, "") for c in states] +
+ list(decisions.unres))
+ g.vp.cow_code = g.new_vp("int", vals=list(states.astype(int)) + [-1] * M)
+ g.vp.country_code = g.new_vp("string", vals=[codes.get(c, "") for c in states] +
+ [""] * M)
+ g.vp.rcid = g.new_vp("int", vals=[-1] * N + list(decisions.rcid.astype(int)))
+ g.vp.resid = g.new_vp("int", vals=[-1] * N + list(decisions.resid.astype(int)))
+ g.vp.session = g.new_vp("int", vals=[-1] * N + list(decisions.session.astype(int)))
+ g.vp.resolution = g.new_vp("string", vals=[""] * N + list(decisions.unres))
+ g.vp.time = g.new_vp("int64_t", vals=[0] * N +
+ list(decisions.date.astype("int64") * 86400))
+ for p, col in [("amendment", "amend"), ("paragraph", "para")]:
+ g.vp[p] = g.new_vp("bool", vals=[False] * N + list(decisions[col] == 1))
+ for p, col in [("short_description", "short"), ("long_description", "descr")]:
+ g.vp[p] = g.new_vp("string", vals=[""] * N +
+ [x if isinstance(x, str) else "" for x in decisions[col]])
+ g.vp.important = g.new_vp("string", vals=[""] * N +
+ [{1: "important", 0: "not important"}.get(x, "not available")
+ for x in decisions.importantvote])
+ for col, p in VOETEN_ISSUES:
+ g.vp[p] = g.new_vp("bool", vals=[False] * N + list(decisions[col] == 1))
+ for p, col in [("num_yes", "yes"), ("num_no", "no"), ("num_abstain", "abstain")]:
+ g.vp[p] = g.new_vp("int", vals=[-1] * N +
+ [int(x) if x == x else -1 for x in decisions[col]])
+ g.add_edge_list(numpy.column_stack((d.ccode.map(sidx).to_numpy(),
+ d.rcid.map(didx).to_numpy())))
+ g.ep.vote = g.new_ep("int", vals=d.vote.astype(int).map(VOETEN_VOTES).to_numpy())
+ return g