United Nations Voting Correlations

David Robinson

2022-09-12

Here we’ll examine an example application of the widyr package, particularly the pairwise_cor and pairwise_dist functions. We’ll use the data on United Nations General Assembly voting from the unvotes package:

library(dplyr)
library(unvotes)

un_votes
## # A tibble: 869,937 × 4
##     rcid country            country_code vote 
##    <dbl> <chr>              <chr>        <fct>
##  1     3 United States      US           yes  
##  2     3 Canada             CA           no   
##  3     3 Cuba               CU           yes  
##  4     3 Haiti              HT           yes  
##  5     3 Dominican Republic DO           yes  
##  6     3 Mexico             MX           yes  
##  7     3 Guatemala          GT           yes  
##  8     3 Honduras           HN           yes  
##  9     3 El Salvador        SV           yes  
## 10     3 Nicaragua          NI           yes  
## # … with 869,927 more rows

This dataset has one row for each country for each roll call vote. We’re interested in finding pairs of countries that tended to vote similarly.

Pairwise correlations

Notice that the vote column is a factor, with levels (in order) “yes”, “abstain”, and “no”:

levels(un_votes$vote)
## [1] "yes"     "abstain" "no"

We may then be interested in obtaining a measure of country-to-country agreement for each vote, using the pairwise_cor function.

library(widyr)

cors <- un_votes %>%
  mutate(vote = as.numeric(vote)) %>%
  pairwise_cor(country, rcid, vote, use = "pairwise.complete.obs", sort = TRUE)

cors
## # A tibble: 39,800 × 3
##    item1     item2     correlation
##    <chr>     <chr>           <dbl>
##  1 Slovakia  Czechia         0.989
##  2 Czechia   Slovakia        0.989
##  3 Lithuania Germany         0.978
##  4 Germany   Lithuania       0.978
##  5 Lithuania Estonia         0.975
##  6 Estonia   Lithuania       0.975
##  7 Lithuania Latvia          0.973
##  8 Latvia    Lithuania       0.973
##  9 Slovakia  Slovenia        0.972
## 10 Slovenia  Slovakia        0.972
## # … with 39,790 more rows

We could, for example, find the countries that the US is most and least in agreement with:

US_cors <- cors %>%
  filter(item1 == "United States")

# Most in agreement
US_cors
## # A tibble: 199 × 3
##    item1         item2          correlation
##    <chr>         <chr>                <dbl>
##  1 United States United Kingdom       0.575
##  2 United States Canada               0.570
##  3 United States Israel               0.546
##  4 United States Australia            0.514
##  5 United States Netherlands          0.513
##  6 United States Luxembourg           0.504
##  7 United States Belgium              0.498
##  8 United States Italy                0.470
##  9 United States Japan                0.462
## 10 United States France               0.459
## # … with 189 more rows
# Least in agreement
US_cors %>%
  arrange(correlation)
## # A tibble: 199 × 3
##    item1         item2          correlation
##    <chr>         <chr>                <dbl>
##  1 United States Belarus             -0.331
##  2 United States Cuba                -0.313
##  3 United States Czechoslovakia      -0.275
##  4 United States Egypt               -0.262
##  5 United States Russia              -0.261
##  6 United States Syria               -0.261
##  7 United States India               -0.241
##  8 United States Afghanistan         -0.191
##  9 United States Iraq                -0.189
## 10 United States Indonesia           -0.188
## # … with 189 more rows

This can be particularly useful when visualized on a map.

if (require("maps", quietly = TRUE) &&
    require("fuzzyjoin", quietly = TRUE) &&
    require("countrycode", quietly = TRUE) &&
    require("ggplot2", quietly = TRUE)) {
  world_data <- map_data("world") %>%
    regex_full_join(iso3166, by = c("region" = "mapname")) %>%
    filter(region != "Antarctica")
  
  US_cors %>%
    mutate(a2 = countrycode(item2, "country.name", "iso2c")) %>%
    full_join(world_data, by = "a2") %>%
    ggplot(aes(long, lat, group = group, fill = correlation)) +
    geom_polygon(color = "gray", size = .1) +
    scale_fill_gradient2() +
    coord_quickmap() +
    theme_void() +
    labs(title = "Correlation of each country's UN votes with the United States",
         subtitle = "Blue indicates agreement, red indicates disagreement",
         fill = "Correlation w/ US")
}

Visualizing clusters in a network

Another useful kind of visualization is a network plot, which can be created with Thomas Pedersen’s ggraph package. We can filter for pairs of countries with correlations above a particular threshold.

if (require("ggraph", quietly = TRUE) &&
    require("igraph", quietly = TRUE) &&
    require("countrycode", quietly = TRUE)) {
  cors_filtered <- cors %>%
    filter(correlation > .6)
  
  continents <- tibble(country = unique(un_votes$country)) %>%
    filter(country %in% cors_filtered$item1 |
             country %in% cors_filtered$item2) %>%
    mutate(continent = countrycode(country, "country.name", "continent"))
  
  set.seed(2017)
  
  cors_filtered %>%
    graph_from_data_frame(vertices = continents) %>%
    ggraph() +
    geom_edge_link(aes(edge_alpha = correlation)) +
    geom_node_point(aes(color = continent), size = 3) +
    geom_node_text(aes(label = name), check_overlap = TRUE, vjust = 1, hjust = 1) +
    theme_void() +
    labs(title = "Network of countries with correlated United Nations votes")
}

Choosing the threshold for filtering correlations (or other measures of similarity) typically requires some trial and error. Setting too high a threshold will make a graph too sparse, while too low a threshold will make a graph too crowded.