Global Debt Networks

R
Visualization
Explore external debt relationships with interactive networks in R
Author

Christoph Scheuch

Published

November 28, 2024

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.

Note

Due to a bug in scales::cut_short_scale(), I installed the development version of scales from GitHub.

library(tidyverse)
library(wbids)
library(visNetwork)
library(scales)

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.

geographies <- ids_list_geographies() |> 
  filter(geography_type != "Region")

external_debt_raw <- geographies$geography_id |> 
  map_df(\(x) ids_get(x, "DT.DOD.DPPG.CD", "all", 2022, 2022))

counterparts <- ids_list_counterparts()

external_debt <- external_debt_raw |> 
  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.

selected_geographies <- c("Nigeria", "Cameroon")

external_debt_sub <- external_debt |>
  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:

format_label <- function(id) {
  label <- str_wrap(id, width = 20)
  label
}

format_debt <- function(x, decimals = 2) {
  debt <- sapply(x, function(value) {
    if (is.na(value)) {
      return(NA_character_)
    }
    if (abs(value) < 1e7) {
      formatted_value <- sprintf(paste0("%.", decimals, "f"), value / 1e6)
      return(paste0(formatted_value, "M"))
    } else {
      formatted_value <- sprintf(paste0("%.", decimals, "f"), value / 1e9)
      return(paste0(formatted_value, "B"))
    }
  })
  debt
}

format_title <- function(id, value_from, value_to) {
  title <- case_when(
    value_from > 0 & value_to > 0 ~ str_c(
      id, 
      "<br>Received: ", format_debt(value_from), 
      "<br>Provided: ", format_debt(value_to)
    ),
    value_from > 0 ~ str_c(
      id, 
      "<br>Received: ", format_debt(value_from)
    ),
    value_to > 0 ~ str_c(
      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:

create_nodes <- function(external_debt_sub) {
  
  total_debt <- sum(external_debt_sub$value)
  
  nodes <- external_debt_sub |> 
    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(
        color == "Other" ~ "#C46231",
        color == "Country" ~ "#3193C4",
        color == "Global MDBs" ~ "#AB31C4",
        color == "Bondholders" ~ "#4AC431"
      )
    )
  nodes
}

nodes <- create_nodes(external_debt_sub)
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:

create_edges <- function(external_debt_sub) {
  edges <- external_debt_sub |> 
    select(from, to) |> 
    mutate(
      shadow = TRUE, 
      color = "grey",
      smooth = TRUE
    )
  edges
}

edges <- create_edges(external_debt_sub)
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.

visualize_network <- function(external_debt_sub) {
  nodes <- create_nodes(external_debt_sub)
  edges <- create_edges(external_debt_sub)
  
  visNetwork(
    nodes, edges, width = "100%", height = "600px"
  ) |> 
    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).

selected_counterparts <- c("Austria", "Germany, Fed. Rep. of")

external_debt_sub <- external_debt |>
  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!