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[is.na(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], "country.name.en", "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)
## IRL DNK SWE FIN DEU ITA LUX BEL NLD NOR
## 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.
head(visa.edge)
## [,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(
from.no = visa.edge[,1],
to.no = 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$from.no, lookup$no),]$country
visa.edge$to <- lookup[match(visa.edge$to.no, lookup$no),]$country
Let see if our interpretation of the edgelist above was correct.
head(visa.edge)
## # A tibble: 6 x 4
## from.no to.no 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
.
data("countriesLow")
We can now easily plot a world map with a one-liner:
plot(countriesLow)
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$region.to <- countries[match(visa.edge$to, countries$ISO3),]$REGION
The edgelist is now spatial.
head(visa.edge)
## # A tibble: 6 x 6
## from.no to.no from to region.from region.to
## <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$region.to == "Africa", ]
Now, we turn our network into a network object provided the network
package which is part of the statnet suite.
visa.net <- 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(visa.net) <- countrycoords$name
# Set vertex attributes
# Here: Longitudes and latitudes
set.vertex.attribute(visa.net, attrname = "lon", countrycoords$lon)
set.vertex.attribute(visa.net, 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
plot(countries)
# (2) Plot the network on top (using the network package)
plot.network(visa.net,
new = FALSE, # Plot on top of the world map
coord = cbind(visa.net%v%"lon", visa.net%v%"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.
Bibliography
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.