Visualizing geopolitics with UN Data and Machine Learning

Sam Bhattacharyya
11 min readOct 31, 2023

--

TLDR: I trained a Machine Learning model which groups countries by how similar their voting patterns are in the UN General Assembly (see graph)

Image by Author

I put together an interactive graph 👉 here 👈 where you can select a country, and see which other countries have the most/least similar voting patterns.

Image by Author

Intro

There have been many Geo-politically significant events recently, such as the conflict in Gaza and the war in Ukraine. I see many opinions on these events, both in the news I consume and in social media. Of course I have my own opinions, but I will not share them.

Instead, as an Engineer, I like data. I'm also currently developing my Machine Learning skills. So when confronted with various news stories about these events, especially regarding how certain countries seem to fall in various "camps" on these issues, I wanted to see if I could use Machine Learning to identify "Voting blocks" or other patterns within UN General Assembly.

As you can tell from the visualizations above, I was successful. I specifically used a Machine Learning technique called "Embeddings" to generate those graphs. In the rest of this post, I'll walk through how I got those visualizations, show some code and provide some more interesting analysis at the end.

I've written this to be approachable for non-ML experts, so if you just want to see the code, here is the Google Colab Notebook.

The Data

Fortunately the United Nations keeps a digital library, with records for each vote in the UN General Assembly going back to the 1960s. You can conveniently search by year for every resolution with a vote.

Image by Author

For each UN General Assembly resolution, you can find meta-data (like the Resolution number, Vote summary, and most importantly for this analysis, the actual Vote data).

© United Nations, 2023, https://digitallibrary.un.org, downloaded on 10/31/2023

I collected UN General Assembly Resolution votes for all the resolutions since the year 2001 (~1800 resolutions).

I could have included data from years before 2001 AD, however (1) Geopolitical tendencies might change over time, and (2) Countries may leave or join the UN General Assembly over time. Given various events in the 1990s (the collapse of the Soviet Union and the Yugoslav wars), I chose 2001 as it provided enough data without handling for too many changes in the group of voting members .

I further cleaned up the data to account for countries which have changed their English names in the time period concerned (Turkey -> Türkiye, Swaziland -> Eswatini).

For each resolution, I recorded the vote for each one of the 196 UN voting members, as follows:

{
"AFGHANISTAN": "Y",
"ALBANIA": "Y",
"ALGERIA": "N",
"ANDORRA": "A",
"ANGOLA": "",
....
}

Where each vote is either "Y" (Yes), "N" (No), "A" (Abstain) or "" (Didn't vote). I formatted the dataset, and made it available, open source, on Huggingface for anyone else who wants to analyze the data.

Image by Author

The Model

I wanted to see if we can find which countries tend to vote together. While there are a number of data-science techniques I could have used, I decided to use Embeddings, which are commonly used in Natural Language Processing (which I am also learning).

In Natural Language Processing, you typically create "embeddings" for each word in the dictionary, where an embedding is just a list of numbers.

Image from Hariam Gautam

By converting words to sets of numbers, we can then train a Machine Learning model to adjust the numbers in each embedding to meet some criteria, such as learning the relationship between different words.

A common example in Natural Language processing is using embeddings to learn word analogies, such as Woman — Man + King ≈ Queen.

Image from Hariam Gautam

In those examples, this literally means that if you subtract the embedding for man from the embedding from woman, and then add that to the embedding for king, the result is mathematically close to the embedding for Queen

That works because those embeddings were trained to work like that [1]. Embeddings start out as random sets of numbers, but then we can use Machine Learning algorithms and large datasets to gradually adjust the values of embeddings to maximize some mathematical formula which we choose in order to achieve some learning objective.

Going back to countries and the United Nations, instead of creating embeddings for each word in the dictionary, we are going to create an embedding for each voting country in the UN General Assembly.

I used Pytorch for this project, and in PyTorch it is incredibly simple to create an embedding model like this. You can do so as below:

import torch

model = torch.nn.Embedding(num_embeddings=196, embedding_dim=32)

This will initialize 196 embeddings, which are each a list of 32 random numbers, where 32 (the "dimensionality" of embedding) is chosen somewhat arbitrarily. Embeddings with more dimensions can capture more complex relationships, and so nowadays you can turn entire paragraphs into an embeddings of ~1024 dimensions in order to compare paragraphs (and pick out differences like topics discussed), but for our use case of comparing 196 countries on their voting patterns, 32 seems more than enough.

We just created the model, and so the numbers are assigned at random. If we want the embeddings to show the relationship between countries, we need to train them.

Training

Let's take the votes of 4 random countries in a particular UN resolution, which could be as follows (Yes, No, Abstain, Not voted)

 {
"MEXICO": "Y",
"SINGAPORE": "N",
"KENYA": "A",
"FINLAND": "",
}

We mathematically represent Yes as 1, No as -1, and Abstentions and Not voting as 0. We then turn this particular vote into an array (a list of numbers).

Image by Author

What we want to know is whether one country voted the same way (e.g. both Yes or both No) as another country, or if one voted the opposite of the other (one Yes, one No), or if there was no relation (either one of the countries abstained or didn't vote).

To do this, we multiply each country's vote value (-1, 0, 1) with every other country's vote number to fill out a matrix (a table).

Image by Author

If two countries voted Yes, multiplying their vote values (1 * 1) is equal to 1. If two countries voted No, multiplying their vote values (-1 * -1) is equal to 1.

If one country voted Yes, and one country voted No, multiplying their vote values (1 * -1) is equal to -1.

If either of a pair of countries abstained or didn't vote, multiplying their vote value (0 *1) or (0 * -1) or (0 *0) is equal to 0.

For each UN General Assembly vote, therefore, we can construct a matrix as follows, indicating whether any two pairs of voted the same or voted differently.

Image by Author

Now remember those embeddings we created earlier? Each country is associated with a random set of numbers.

Image by Author

We can also multiply two embeddings using dot product multiplication. You can therefore multiply Mexico by Singapore, which would be multiplying their embedding vectors as so:

Image by Author

We can now multiply every country embedding by every other country embedding to obtain a matrix as follows

Image by Author

We treat that matrix as our model prediction, for whether or not the two countries voted the same in a given UN Resolution.

Image by Author

We can now formulate the machine learning task as follows: Can we adjust the embedding vectors for each country, until we minimize the difference between the predictions (multiplication of country embeddings) and the observations (the actual vote correlations as discussed above).

Fortunately we can do this pretty easily with a machine learning framework like Pytorch.

(Pseudo) Pytorch implementation

I'm simplifying the code a bit, so this is pseudo code, but the training algorithm is something like this:

from datasets import load_dataset
import torch

dataset = load_dataset("sam-bha/un-general-assembly-votes-2000-2023")

model = torch.nn.Embedding(num_embeddings=196, embedding_dim=32)

loss_function = torch.nn.MSELoss()

optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

for epoch in range(40): #40 Epochs

for countries, votes in data_set:

embeddings = model(countries)
prediction = torch.bmm(embeddings, embeddings.permute(0, 2,1)) # Dot product multiplication for batches

loss = loss_function(prediction, votes)

optimizer.step()
optimizer.zero_grad()

Again, the above code is for illustration purposes and won't work if you try to run it. You can find the real code here.

After training the model, you can the trained country embedding vectors now have new values which we use to measure relationships between countries.

Analyzing the relationship between countries

Now that we have trained embedding vectors, we can see how well they work by multiplying two embedding vectors together and seeing how aligned the model thinks the two countries are (on a scale from -1 to 1)


def get_embedding(country):
idx = dataset.idx_by_country[country]
return model(torch.tensor(idx))

def compare_country(countryA, countryB):

embed_a = get_embedding(countryA)
embed_b = get_embedding(countryB)

return embed_a @ embed_b.T

Running this for a specific example

compare_country('UNITED KINGDOM', 'FRANCE')
>> 0.92

Returns 0.92, which is pretty high, and indicates that those two countries tend to vote similarily in the UN General Assembly.

If we try two countries with different geopolitical interests

compare_country('RUSSIAN FEDERATION', 'UNITED STATES')
>> -0.52

We get -0.52, which also makes sense, and indicates that those two countries tend to vote opposite one another in the UN General Assembly.

If you want compare individual countries yourself, I created a quick web tool here.

Image by Author

If we want to visualize how countries' voting patterns compare with each other (as in the intro graphic), we can't just graph our embedding vectors because they have 32 dimensions, and we can at most graph 3 dimensions in a 3d plot. We therefore Principal Component Analysis to reduce the dimensionality of our embedding vectors from 32 to just 3.

Principal Component Analysis works by "rotating the axes" of a graph to identify a new coordinate system (a linear transformation), and does so in a way such that the first few dimensions account for most of the variation among the dataset.

It's possibly easier to visualize this for a 2d example. In 2 dimensions, if you have a dataset like the one below, you'll see that most of those values follow a line.

Image by Raghavan

Principal Component Analysis will identify this line (PCA 1st dimension), which accounts for most of the variation in the dataset.

You could "simplify" the dataset by throwing away the less important PCA 2nd Dimension, and just plot all of the data-points along a line (wherever it falls on PCA 1st dimension), thereby reducing a 2 dimensional dataset to 1 dimension. This throws away some data, but does so in a way that doesn't change the relative position of data points within the dataset that much.

In our case, we use Principal Component analysis on our embeddings, and just throw away the 29 least important dimensions, leaving us with embedding vectors of 3 dimensions.

import numpy as np
from sklearn.decomposition import PCA

# First, convert from pytorch to numpy
country_embeddings = np.array([model(torch.tensor(i)).cpu().numpy() for i in range(196)])

# Reduce the dataset to the 3 most import dimensions
three_dims = PCA(random_state=0).fit_transform(country_embeddings)[:,:3]

With 3 dimensions, we can now actually graph our data onto a 3d graph, which finally brings us back to the intro graphic at the beginning of the post (interactive version here).

Image by Author

No one defined what these axes mean explicitly — these features are entirely learned from the UN voting dataset, but it does seem interesting that you can kind of tease out some explicit meaning from these axes.

For example, many developed countries seem to have positive values on the x axis, while developing countries tend to have negative values for the x axis.

You can also can also clearly see voting blocks, including what seems to be a "developed country" voting block, including many EU countries, as well as countries like Australia and Japan.

You can also clearly see a developing country voting block, though with a spectrum with countries like Brazil on one end (lower on the Z axis), and countries like China, North Korea, Russia and India on the other (higher on the Z axis).

Does geopolitics inform voting strategy at the UN?

It does seem like there are voting blocks and some sense of tendencies or alignment within the UN General assembly.

Using our newly obtained country embeddings (which measure how similarly countries tend to vote), I wanted to see if there were any examples of resolutions where geopolitical alignments may have informed a country's voting strategy.

To do this, I looked for examples of resolutions where a large majority of UN members voted one way (Yes or No), and a small minority of UN members voted the other way.

We can define a metric for this as (Majority Vote) / (Minority Vote).

Next, we define compute how aligned the countries in the minority were, by calculating a group cohesiveness score, which is basically the average similarity (dot product) between any two members in the minority.

I then ranked all the UN resolutions which maximized the following metric.

minority_group_cohesiveness * majority_vote_count/minority_vote_count

Filtering out for resolutions where only 1 or 2 countries voted in the minority (not interesting), we get the following list of UN Resolutions where the minority group were highly geopolitically aligned.

Aligned Minority Votes

Here is a sample of the top results for the above query.

855223 — United action with renewed determination towards the total elimination of nuclear weapons

  • China, North Korea, Russia, Syria

613302 — Necessity of ending the economic, commercial and financial embargo imposed by the United States of America against Cuba

  • Israel, United States, Marshall Islands, Palau

719134 — Restoration of the rights of membership of Libya in the Human Rights Council

  • Bolivia, Ecuador, Nicaragua, Venezuela

Unexpected alliances

On the contrary, I wanted to see if there were votes where the minority included highly unaligned members, as calculated by

majority_vote_count/minority_vote_count * (1 / minority_group_cohesiveness)

644185 — Renewed determination towards the total elimination of nuclear weapons

  • United States, North Korea, Israel, India

694928 — General Assembly resolution supporting the immediate ceasefire according to Security Council resolution 1860

  • Israel, Nauru, United States, Venezuela

855219 — Nuclear-weapon free southern hemisphere and adjacent areas

  • France, United Kingdom, Russian Federation, United States

Of course you can find more examples (or do your own analysis) in the Google Colab Notebook.

Conclusion

If you made it this far, thank you for reading to the end (I know this was long). I tried my best to avoid expressing any opinions on geopolitics or recent events.

It was an interesting project and been useful personally for learning purposes. If you think there are better techniques or approaches for this Machine Learning project, please let me know in a comment or feel free to reach out.

Here is my github and my LinkedIn

If you want to dig into the data yourself, again here is the dataset and Google Colab notebook. If you find anything interesting as well, feel free to comment and /or let me know.

--

--

Sam Bhattacharyya

Entrepreneur, Full Stack Developer and general student of the world