Global Visa Policies: Creating a Network on a Map with R

Global Visa Policies

Recently, I started a new position in a project that aims at developing a typology of border walls at a global scale. Against the backdrop of globalization literature of the 1990s, it has been shown that instead of world that is increasingly ‘borderless’ (Ohmae 1990) we actually observe more and more fortification (Hassner & Wittenberg 2015). While I will certainly write more about this project once it matures, this post features related data that I collected with colleagues at the University of Bremen: The Visa Network Data.

Of course, borders are not the only instrument that states employ to control flows of human mobility. Another instrument to filter mobility are visa policies. Arguably, it matters whether we can visit a country without applying for a visa in advance or whether we have to travel to consulates weeks before in order to retain a visa.

The global pattern of visa waivers between states can be thought of as a network. Between each of two countries, there could either be a visa restriction in place (coded 0) or it could be waived (coded 1). Reading up on the great blog post by Skye Bender-de Moll who explains how we can create a network-on-a-map got me motivated to reproduce his work with the visa network data.

The remaining post will describe my efforts to visualize the visa network data on a global map. Even though, most parts rely heavily on Skye’s work, some deviations were necessary.

Preparing the Visa Network Data

The Visa Network Data is hosted at the University of Bonn. It is freely available and described in some depth in an article published in the Journal of Ethnic and Migration Study by Mau et al. (2015). The following packages are used to create a network-on-a-map:

if (!require("pacman")) install.packages("pacman")

p_load(tidyverse, rworldmap, igraph, countrycode, statnet, rio, readxl)

We can access the excel file that contains the network of visa waivers in the following way:

visa <- import(url, format = "xls", which = 2, range = "C5:FN172", 
               col_types = c("text", rep("numeric", 167)), na = "/")

Before we have a look at the data, it is necessary to remove certain rows that are badly formatted in the xls-file.

# Delete unnecessary rows and columns
visa <- visa[-1, ]
visa <- visa[ ,-2]

# Self-ties are included as "NA", however, they should be coded as "0".
visa[] <- 0

A next preparation step is necessary because some suboptimal choices were made when the data collection for the visa network data started. Most importantly, instead of applying standardized country identifiers (i.e. ISO3), we used common names. To our avail the beautiful package countrycode (Vignette) transforms different formats of country identifiers. However, let’s first rename some unambiguously named countries.

# Rename the first column (country IDs) and unambiguous country names
visa <- visa %>%
  rename(Name = "Home country:", 
         "Central African Republic" = "Central African Rep.",
         "Comoro Islands" = "Comores Islands",
         "North Korea" = "Korea (Peoples Rep.)",
         "Swaziland" = "Swasiland",
         "Kyrgyzstan" = "Kyrgystan")

Now, transforming full country names into standardized ISO3 codes is a breeze.

iso3 <- countrycode(colnames(visa)[2:167], "", "iso3c")

Finally, it is possible to approach the network character of the data. (Social) network data can come in different representations such as matrix or edgelist. The visa network data is, by now, very close to a standard matrix representation. We can transform the data into an matrix object with ISO3 codes as row and column names.

# As a matrix object
visa.mat <- as.matrix(visa[,2:167])

# Use ISO3 codes as row and column names
rownames(visa.mat) <- iso3
colnames(visa.mat) <- iso3

Using R’s network capacities

To provide some substantive results in passing, it is easy to access the countries with the highest visa freedom using magrittr and some base R:

visa.mat %>%
  apply(MARGIN = 2, sum) %>%   # MARGIN = 1 sent visa waivers
  sort(decreasing = TRUE) %>%
  head(n = 10)
##  82  80  80  79  79  79  79  78  78  78

Citizens of Ireland can travel in 82 countries without applying for a visa in advance. Moreover, it is interesting to note that all countries in the top10 are part of the Schengen Area.

Now, we use the igraph package in order to access further network analysis capacities in R. In two steps, we transform the object visa.mat into an igraph object which is then represented as edgelist.

# Step 1: Create an igraph object
visa.graph <- igraph::graph.adjacency(visa.mat, mode = "directed",
                                                  diag = FALSE, add.colnames = TRUE)

# Step 2: Transform into an edgelist 
visa.edge <- igraph::get.edgelist(visa.graph, names = TRUE)

An edgelist just lists all relationships that exists in our network.

##      [,1] [,2]
## [1,]    2    5
## [2,]    2    7
## [3,]    2    8
## [4,]    2   13
## [5,]    2   17
## [6,]    2   19

Here we see that Albania (country no. 2) allows citizens of Argentina (5) and Australia (7) to travel to Albania without applying for a visa in advance.

Next, I replace the numbers with ISO3 codes as this makes deciphering the edgelist much easier.

# Step 1: Transform the edgelist into a dataframe
visa.edge <- tibble( = visa.edge[,1], = visa.edge[,2])

# Step 2: Create a lookup table for matching
lookup <- tibble(
  country = colnames(visa.mat),
  no = 1:166)

# Step 3: Replace numbers with ISO3 codes
visa.edge$from <- lookup[match(visa.edge$, lookup$no),]$country
visa.edge$to <- lookup[match(visa.edge$, lookup$no),]$country

Let see if our interpretation of the edgelist above was correct.

## # A tibble: 6 x 4
## from  to   
##     <dbl> <dbl> <chr> <chr>
## 1      2.    5. ALB   ARG  
## 2      2.    7. ALB   AUS  
## 3      2.    8. ALB   AUT  
## 4      2.   13. ALB   BEL  
## 5      2.   17. ALB   BIH  
## 6      2.   19. ALB   BRA

Indeed, we can confirm our interpretation.

Creating a world map

Next, we use a package that entails a shapefile for the world. There are several. Here, I use the high resolution world map of the packages rworldmap and rworldxtra.


We can now easily plot a world map with a one-liner:


I remove Greenland and Antarctica to make the map a little bit leaner:

countries <- countriesLow[countriesLow@data$ISO3 != c("GRL", "ATA"),] # Greenland

Making a network spatial

In a last step, I add longitudes and latitudes to the network in order to make the data “spatial”.

# Step 1: Create a dataframe with iso3 codes and respective lon/lat
countrycoords <- tibble(
  name = iso3,           
  lon = countries@data[match(iso3, countries@data$ISO3),]$LON,
  lat = countries@data[match(iso3, countries@data$ISO3),]$LAT

# Step 2: Add them to the edgelist
visa.edge$region.from <- countries[match(visa.edge$from, countries$ISO3),]$REGION
visa.edge$ <- countries[match(visa.edge$to, countries$ISO3),]$REGION

The edgelist is now spatial.

## # A tibble: 6 x 6
## from  to    region.from    
##     <dbl> <dbl> <chr> <chr> <fct>       <fct>        
## 1      2.    5. ALB   ARG   Europe      South America
## 2      2.    7. ALB   AUS   Europe      Australia    
## 3      2.    8. ALB   AUT   Europe      Europe       
## 4      2.   13. ALB   BEL   Europe      Europe       
## 5      2.   17. ALB   BIH   Europe      Europe       
## 6      2.   19. ALB   BRA   Europe      South America

Plotting the network on a map

The final step involves the visualization of both the visa network and the world map. A final issue is the size of our network. There are just too many visa waivers to plot them all. In the end, our plot would display nothing more than a huge hairball. Hence, I reduce the network to only those visa waivers received by African countries.

visa.edge <- visa.edge[visa.edge$ == "Africa", ]

Now, we turn our network into a network object provided the network package which is part of the statnet suite. <- network(visa.edge,
                    matrix.type = 'edgelist',
                    directed = TRUE)

The network packages comes with functions that can be used to set network.vertex.names and to set.vertex.attributes.

# Set vertex labels
network.vertex.names( <- countrycoords$name

# Set vertex attributes
# Here: Longitudes and latitudes
set.vertex.attribute(, attrname = "lon", countrycoords$lon)
set.vertex.attribute(, attrname = "lat", countrycoords$lat)

Finally, we arrived at point where we can visualize a network-on-a-map. This is involves two steps. First, we plot the world map and, second, we put our network on top of it.

# (1) Plot the world map

# (2) Plot the network on top (using the network package),
             new = FALSE, # Plot on top of the world map
             coord = cbind("lon","lat"), # Lon/Lat
             edge.col = '#AA555555',
             vertex.cex = 0.5,
             vertex.col = '#AA555555',
             vertex.border = "white",
             jitter = FALSE)

Et voilà, we reproduced Skye’s network-on-a-map using our visa network data.


Mau, S., Gülzau, F., Laube, L. & Zaun, N. (2015): The Global Mobility Divide: How Visa Policies Have Evolved over Time, Journal of Ethnic and Migration Studies, 41:8, 1192-1213.

Hassner, R.E. & Wittberg, J. (2015): Barriers to Entry: Who Builds Fortified Boundaries and Why?, International Security, 40:1, 157-190.

Ohmae, K. (1990): The Borderless World, New York: Harper Collins.