Who absorbs the World’s surplus?

economy
python
An analysis of global imbalance in the current account.
Published

Mar 14, 2026

Keywords

exorbitant privilege

Summary

Global trade imbalances reveal structural asymmetries in the international financial system, with the United States playing a unique role due to the dominance of the U.S. dollar. While some economies accumulate surpluses, the U.S. consistently runs large trade deficits. This persistent imbalance is not simply a weakness but a reflection of the exorbitant privilege` of the dollar as the world’s primary reserve currency.

Code
# Libraries
# =====================================================================
import os
import requests
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.ticker as mticker
from matplotlib import font_manager

# Data Extraction (Countries)
# =====================================================================
# Extract JSON and bring data to a dataframe
url = 'https://raw.githubusercontent.com/guillemmaya92/world_map/main/Dim_Country.json'
response = requests.get(url)
data = response.json()
df = pd.DataFrame(data)
df = pd.DataFrame.from_dict(data, orient='index').reset_index()
df_countries = df.rename(columns={'index': 'ISO3'})

# Data Extraction (IMF)
# =====================================================================
#Parameter
parameters = ['BCA', 'NGDPD']

# Create an empty list
records = []

# Iterar sobre cada parámetro
for parameter in parameters:
    # Request URL
    url = f"https://www.imf.org/external/datamapper/api/v1/{parameter}"
    response = requests.get(url)
    data = response.json()
    values = data.get('values', {})

    # Iterate over each country and year
    for country, years in values.get(parameter, {}).items():
        for year, value in years.items():
            records.append({
                'Parameter': parameter,
                'ISO3': country,
                'Year': int(year),
                'Value': float(value)
            })
    
# Create dataframe
df_imf = pd.DataFrame(records)

# Data Manipulation
# =====================================================================
# Pivot Parameter to columns and filter nulls
df = df_imf.pivot(index=['ISO3', 'Year'], columns='Parameter', values='Value').reset_index()
df = df.dropna(subset=['BCA'], how='any')

# Merge queries
df = df.merge(df_countries, how='left', left_on='ISO3', right_on='ISO3')
df = df[['ISO3', 'Country', 'Year', 'BCA', 'NGDPD', 'Analytical', 'Region', 'Cod_Currency']]
df = df[df['Region'].notna()]

# Custom region
conditions = [
    df['ISO3'] == 'USA',
    df['ISO3'] == 'GBR',
    df['ISO3'].isin(['CHN', 'TWN', 'HKG', 'MAC']),
    df['ISO3'] == 'JPN',
    df['Cod_Currency'] == 'EUR',
    df['BCA'] >= 0,
    df['BCA'] < 0
]
result = ['USA', 'UK', 'Greater China', 'Japan', 'Eurozone', 'Other Surplus', 'Other Deficit']

df['Region'] = np.select(conditions, result)

# Groupping region and year
df = df.groupby(["Region", "Year"], as_index=False)[["BCA", "NGDPD"]].sum()

# Add total GDP
df['NGDPD'] = df.groupby('Year')['NGDPD'].transform('sum')
df['Ratio'] = df['BCA'] / df['NGDPD'] * 100

# Pivot Regions
df = df.pivot_table(index="Year", columns="Region", values="Ratio", aggfunc="sum")

# Reorder columns
df = df[["USA", "UK", "Eurozone", "Greater China", "Japan", "Other Surplus", "Other Deficit"]]

# Filter period
df = df.loc[df.index <= 2029]

# Values
usa_percent = df.loc[2029, 'USA'] / (df.loc[2029, 'Other Deficit'] + df.loc[2029, 'USA'] + df.loc[2029, 'UK'])
uk_percent = df.loc[2029, 'UK'] / (df.loc[2029, 'Other Deficit'] + df.loc[2029, 'USA'] + df.loc[2029, 'UK'])
eur_percent = df.loc[2029, 'Eurozone'] / (df.loc[2029, 'Other Surplus'] + df.loc[2029, 'Eurozone'] + df.loc[2029, 'Greater China'] + df.loc[2029, 'Japan'])
chn_percent = df.loc[2029, 'Greater China'] / (df.loc[2029, 'Other Surplus'] + df.loc[2029, 'Eurozone'] + df.loc[2029, 'Greater China'] + df.loc[2029, 'Japan'])
jpn_percent = df.loc[2029, 'Japan'] / (df.loc[2029, 'Other Surplus'] + df.loc[2029, 'Eurozone'] + df.loc[2029, 'Greater China'] + df.loc[2029, 'Japan'])

print(df)

# Data Visualization
# =====================================================================
# Font and style
plt.rcParams.update({'font.family': 'sans-serif', 'font.sans-serif': ['Franklin Gothic'], 'font.size': 9})
sns.set(style="white", palette="muted")

# Palette
palette = ["#C00000", "#E75527", "#002D64", "#157FFF", "#90bee0", "#E8F1F8", "#FFE1E1"]

# Create figure
fig, ax = plt.subplots(figsize=(10, 6))

# Crear figure and plot
ax = df.plot(kind="bar", stacked=True, width=0.9, color=palette, legend=False, ax=ax)

# Title
fig.add_artist(plt.Line2D([0.11, 0.11], [0.91, 1], linewidth=6, color='#203764', solid_capstyle='butt')) 
plt.text(0, 1.12, f'Who Absorbs the World’s Surplus?', fontsize=16, fontweight='bold', ha='left', transform=plt.gca().transAxes)
plt.text(0, 1.08, f'Global imbalance in the current account', fontsize=11, color='#262626', ha='left', transform=plt.gca().transAxes)
plt.text(0, 1.045, f'(as percent of global GDP)', fontsize=8, color='#262626', ha='left', transform=plt.gca().transAxes)

# Adjust ticks and grid
plt.ylim(-3, 3)
ax.set_xticks(range(0, 50, 5))  # Ajustar el rango con len(df)+1
ax.set_xticklabels(df.index[::len(df) // 10], fontsize=9, rotation=0)
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, pos: f'{int(x):,}'.replace(",", ".")))
plt.gca().set_xlabel('')
plt.yticks(fontsize=9, color='#282828')
plt.grid(axis='x', linestyle='--', color='gray', linewidth=0.5, alpha=0.3)

# Custom legend values
handles = [
    mpatches.Patch(color=palette[0], label="USA", linewidth=2),
    mpatches.Patch(color=palette[1], label="UK", linewidth=2),
    mpatches.Patch(color=palette[2], label="Eurozone", linewidth=2),
    mpatches.Patch(color=palette[3], label="Greater China (大中华)", linewidth=2),
    mpatches.Patch(color=palette[4], label="Japan (日本)", linewidth=2),
    mpatches.Patch(color=palette[5], label="Other Surplus", linewidth=2),
    mpatches.Patch(color=palette[6], label="Other Deficit", linewidth=2)
]

# Legend
legend = plt.legend(
    handles=handles,
    loc='lower center', #center
    bbox_to_anchor=(0.5, -0.12),
    ncol=8,
    fontsize=8,
    frameon=False,
    handlelength=0.5,
    handleheight=0.5,
    borderpad=0.2,
    columnspacing=0.4
)
# legend.set_bbox_to_anchor((60, 0), transform=ax.transData)

# Change Font (accept chinese characters)
prop = font_manager.FontProperties(fname='C:\\Windows\\Fonts\\msyh.ttc')
for text in legend.get_texts():
    text.set_fontproperties(prop)
    text.set_fontsize(8)

# Add Data Source
plt.text(0, -0.15, 'Data Source:', 
    transform=plt.gca().transAxes, 
    fontsize=8,
    fontweight='bold',
    color='gray')
space = " " * 23
plt.text(0, -0.15, space + 'IMF World Economic Outlook Database, 2024', 
    transform=plt.gca().transAxes, 
    fontsize=8,
    color='gray')

# Add text
plt.text(50, -0.35, f"← {usa_percent:.0%}", fontsize=7, ha='left', va='bottom')
plt.text(50, -0.6, f"← {uk_percent:.0%}", fontsize=7, ha='left', va='bottom')
plt.text(50, 0.15, f"← {eur_percent:.0%}", fontsize=7, ha='left', va='bottom')
plt.text(50, 0.55, f"← {chn_percent:.0%}", fontsize=7, ha='left', va='bottom')
plt.text(50, 0.8, f"← {jpn_percent:.0%}", fontsize=7, ha='left', va='bottom')

plt.text(50, 2, f"World\nsurplus", fontsize=7, fontweight = 'bold', ha='left', va='top')
plt.text(50, -1.8, f"World\ndeficit", fontsize=7, fontweight = 'bold', ha='left', va='bottom')

# Forecast
plt.text(47, 3.1, f'Forecast', fontsize=7, fontweight='bold', color='gray', ha='center')
ax.axvspan(44.5, 49.5, color='gray', alpha=0.15, edgecolor='none')

# Remove spines
for spine in plt.gca().spines.values():
    spine.set_visible(False)

# Save it...
download_folder = os.path.join(os.path.expanduser("~"), "Downloads")
filename = os.path.join(download_folder, f"FIG_IMF_Global_Surplus.png")
plt.savefig(filename, dpi=300, bbox_inches='tight')

# Show :)
plt.show()

Back to top