library(tidyverse)
library(wbids)
library(visNetwork)
library(scales)
Global Debt Networks
In this blog post, I visualize external debt using interactive networks with R. Understanding external debt flows between countries and creditors is a crucial aspect in analyzing global economic relationships. Using tools such as the recently released wbids
package to download data from the World Bank International Debt statistics and visNetwork
for visualization, we can uncover interesting insights.
This post relies on the following packages.
Due to a bug in scales::cut_short_scale()
, I installed the development version of scales
from GitHub.
Data Preparation
The wbids
package provides easy access to download debtor-creditor relationships for all available countries. Here, we pull external debt data (IDS series “DT.DOD.DPPG.CD”) for 2022 using ids_get()
and enrich it with geographical and counterpart details. Note that we drop “World” and “Region” counterparts because we rather want to look at their individual components.
<- ids_list_geographies() |>
geographies filter(geography_type != "Region")
<- geographies$geography_id |>
external_debt_raw map_df(\(x) ids_get(x, "DT.DOD.DPPG.CD", "all", 2022, 2022))
<- ids_list_counterparts()
counterparts
<- external_debt_raw |>
external_debt filter(value > 0) |>
left_join(geographies, join_by(geography_id)) |>
left_join(counterparts, join_by(counterpart_id)) |>
filter(!counterpart_name %in% c("World", "Region")) |>
select(from = geography_name, to = counterpart_name, value, counterpart_type) |>
mutate(to = str_squish(to))
Debtor-Centric View
For now, let’s narrow our focus to specific debtor countries. This step allows us to examine the relationships from a debtor’s perspective, understanding how much debt they owe and to whom. In the following, we focus on Nigeria and its neighbor Cameroon.
<- c("Nigeria", "Cameroon")
selected_geographies
<- external_debt |>
external_debt_sub filter(from %in% selected_geographies)
visNetwork
requires two data frames: nodes
and edges
, each with corresponding properties. To enhance the visualization, we create helper functions for formatting node titles and labels:
<- function(id) {
format_label <- str_wrap(id, width = 20)
label
label
}
<- function(x, decimals = 2) {
format_debt <- sapply(x, function(value) {
debt if (is.na(value)) {
return(NA_character_)
}if (abs(value) < 1e7) {
<- sprintf(paste0("%.", decimals, "f"), value / 1e6)
formatted_value return(paste0(formatted_value, "M"))
else {
} <- sprintf(paste0("%.", decimals, "f"), value / 1e9)
formatted_value return(paste0(formatted_value, "B"))
}
})
debt
}
<- function(id, value_from, value_to) {
format_title <- case_when(
title > 0 & value_to > 0 ~ str_c(
value_from
id, "<br>Received: ", format_debt(value_from),
"<br>Provided: ", format_debt(value_to)
),> 0 ~ str_c(
value_from
id, "<br>Received: ", format_debt(value_from)
),> 0 ~ str_c(
value_to
id, "<br>Provided: ", format_debt(value_to)
),TRUE ~ NA_character_
)
title }
We now construct the nodes and edges for the network. Nodes represent entities (countries or institutions), and edges represent debt relationships. The data looks like this:
<- function(external_debt_sub) {
create_nodes
<- sum(external_debt_sub$value)
total_debt
<- external_debt_sub |>
nodes group_by(id = from, color = "Country") |>
summarize(value_from = sum(value),
.groups = "drop") |>
bind_rows(
|>
external_debt_sub group_by(id = to, color = counterpart_type) |>
summarize(value_to = sum(value),
.groups = "drop")
|>
) group_by(id, color) |>
summarize(across(c(value_from, value_to), \(x) sum(x, na.rm = TRUE)),
.groups = "drop") |>
mutate(
title = format_title(id, value_from, value_to),
label = format_label(id),
value = coalesce(value_from, 0) + coalesce(value_to, 0),
size = value / total_debt,
color = case_when(
== "Other" ~ "#C46231",
color == "Country" ~ "#3193C4",
color == "Global MDBs" ~ "#AB31C4",
color == "Bondholders" ~ "#4AC431"
color
)
)
nodes
}
<- create_nodes(external_debt_sub)
nodes nodes
# A tibble: 33 × 8
id color value_from value_to title label value size
<chr> <chr> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
1 African Dev. Bank #C46… 0 4.18e 9 Afri… "Afr… 4.18e 9 8.07e-2
2 African Export-Import … #C46… 0 7.60e 7 Afri… "Afr… 7.60e 7 1.47e-3
3 Arab Bank for Economic… #C46… 0 7.23e 7 Arab… "Ara… 7.23e 7 1.39e-3
4 Austria #319… 0 8.78e 6 Aust… "Aus… 8.78e 6 1.69e-4
5 Belgium #319… 0 5.69e 7 Belg… "Bel… 5.69e 7 1.10e-3
6 Bondholders #4AC… 0 1.73e10 Bond… "Bon… 1.73e10 3.33e-1
7 Cameroon #319… 1.18e10 0 Came… "Cam… 1.18e10 2.28e-1
8 Central Bank of West A… #C46… 0 2.28e 7 Cent… "Cen… 2.28e 7 4.40e-4
9 China #319… 0 8.08e 9 Chin… "Chi… 8.08e 9 1.56e-1
10 Dev. Bank of the Centr… #C46… 0 3.80e 6 Dev.… "Dev… 3.80e 6 7.33e-5
# ℹ 23 more rows
Edges add the connective tissue to the network, showing who owes whom. Here is how the example data looks like:
<- function(external_debt_sub) {
create_edges <- external_debt_sub |>
edges select(from, to) |>
mutate(
shadow = TRUE,
color = "grey",
smooth = TRUE
)
edges
}
<- create_edges(external_debt_sub)
edges edges
# A tibble: 45 × 5
from to shadow color smooth
<chr> <chr> <lgl> <chr> <lgl>
1 Cameroon World Bank-IDA TRUE grey TRUE
2 Cameroon World Bank-IBRD TRUE grey TRUE
3 Cameroon United States TRUE grey TRUE
4 Cameroon United Kingdom TRUE grey TRUE
5 Cameroon United Arab Emirates TRUE grey TRUE
6 Cameroon Turkiye TRUE grey TRUE
7 Cameroon Switzerland TRUE grey TRUE
8 Cameroon Spain TRUE grey TRUE
9 Cameroon Saudi Arabia TRUE grey TRUE
10 Cameroon OPEC Fund for International Dev. TRUE grey TRUE
# ℹ 35 more rows
The visNetwork
library brings everything together, producing an interactive network. This visualization provides a debtor-centric perspective, illustrating how selected countries distribute their debt obligations among creditors.
<- function(external_debt_sub) {
visualize_network <- create_nodes(external_debt_sub)
nodes <- create_edges(external_debt_sub)
edges
visNetwork(
width = "100%", height = "600px"
nodes, edges, |>
) visNodes(shape = "dot")
}
visualize_network(external_debt_sub)
Visualizing the network from a debtor’s perspective sheds light on the diversity of funding sources for countries like Nigeria and Cameroon. While both nations share some creditors, the wider spread of Cameroon’s creditor network could indicate stronger diversification in funding sources—a potentially advantageous position for economic resilience.
Creditor-Centric View
If you wondered why we wrapped everything into functions, then here is the resolution: we can use the same function to examine the network from creditors’ perspective. For example, let’s focus on Austria (my home country) and Germany (where I currently live).
<- c("Austria", "Germany, Fed. Rep. of")
selected_counterparts
<- external_debt |>
external_debt_sub filter(to %in% selected_counterparts)
visualize_network(external_debt_sub)
This approach reveals which countries owe these creditors and in what amounts. For me, it is interesting to see that Austria shares a lof of counterparts with Germany (which is not surprising), but that Germany provides credit to many more counterparts around the World. Germany’s broader network of counterparts underscores its role as a significant lender globally, while Austria’s overlapping but smaller network highlights the nuanced dynamics of regional lending patterns.
Putting Everyhing into an App
Do you want to quickly look at different countries, counterparts, or time periods? The code above actually constitutes the buildings blocks of a shiny app that allows you to explore the data interactively - check out the Debt Network Visualizer!
Concluding Remarks
By combining wbids
for data retrieval, tidyverse
for manipulation, and visNetwork
for visualization, you can quickly uncover intricate patterns in global debt relationships. Try adapting this workflow to your own analysis!