Introduction to macroeconomics

NOTE: The present notebook is coded with quarto, in the R programming language. The white margin to the right will be used to display code output. To access World Bank data using python, we recommend this short colab notebook.

This document relies heavily on the {tidyverse} ecosystem of packages. We load the tidyverse below as a prerequisite for the rest of the notebook - along with a few other important libraries.

To install packages, use the install.packages() function as below (commented on purpose).

# install.packages("tidyverse")

Once they are installed, we can call/load/activate them in the current session.

library(tidyverse)  # Package for data wrangling
library(readxl)     # Package to import MS Excel files
library(latex2exp)  # Package for LaTeX expressions
library(quantmod)   # Package for stock data extraction
library(plotly)     # For interactive plots
library(ggsci)      # For addition color palettes
library(WDI)        # World Bank data!

\(\rightarrow\) Don’t forget that code flows sequentially. A random chunk may not work if the previous ones have have not been executed.

1 Topics in macroeconomics

Macroeconomics is a vast discipline. There are many valuable textbooks on the matter, but we take for instance the recent and excellent Advanced Macroeconomics - An Easy Guide - CSV2021 henceforth (initials of authors). It will guide us all along the way. Its content is divided into the following sections:

  • Growth Theory

  • Growth theory preliminaries

  • The neoclassical growth model

  • An application: The small open economy

  • Endogenous growth models I: Escaping diminishing returns (with human capital)

  • Endogenous growth models II: Technological change

  • Proximate and fundamental causes of growth

  • Overlapping Generations Models

  • Overlapping generations models

  • An application: Pension systems and transitions

  • Unified growth theory

  • Consumption and Investment

  • Consumption

  • Consumption under uncertainty and macro finance

  • Investment

  • Short Term Fluctuations

  • Real business cycles

  • (New) Keynesian theories of fluctuations: A primer

  • Unemployment

  • Monetary and Fiscal Policy

  • Fiscal policy I: Public debt and the effectiveness of fiscal policy

  • Fiscal policy II: The long-run determinants of fiscal policy

  • Monetary policy: An introduction

  • Rules vs Discretion

  • Recent debates in monetary policy

  • New developments in monetary and fiscal policy

In the present course, we will cover those in green. For the sake of consistency, we will often keep the same notations as CSV2021.

We also recommend the older monographs Economic Growth by Barro and Sala-ì-Martin and Advanced Macroeconomics by Romer. Those who want to push further can have a look at the PhD Macro Book.

Also, before we start, a brief warning (to be discussed in class):

2 Empirical overview

The aim of any model is to explain or even predict salient empirical facts. In order to test the validity of models, we need data.

There are many sources from which we can obtain macroeconomic data and many are national or international institutions. Some are more practical and user-friendly than others, especially via their API services.

List of resources

We mention for instance:

Several of these institutions use the SDMX standard.

I recommend to have a look at the econdataverse, and perhaps the IMF’s World Economic Outlook R package in particular.

Below, we will use the first one (World Bank) because we feel it is the most user-friendly to access directly and does not require any authentication.

We list the indicators (from the World Bank) we’ll study below:

The data starts in 1960 and for simplicity, we focus on two countries, France and the United States. To add other countries, you need to add their names as is it specified in the data - see example below with Spain, Italy, Poland, Slovenia and Venezuela. You can also specify their ISO alpha-2 codes.

The chunk below fetches the data. It can take up to a few minutes to run.

wb_raw = WDI(indicator = c("gdp_per_capita" = "NY.GDP.PCAP.KD",
                           "population" = "SP.POP.TOTL",
                           "gdp" = "NY.GDP.MKTP.CD",
                           "gdp_growth" = "NY.GDP.MKTP.KD.ZG",
                           "inflation" = "FP.CPI.TOTL.ZG",
                           "debt" = "GC.DOD.TOTL.GD.ZS",
                           "unemployment" = "SL.UEM.TOTL.ZS"), 
             #country = c('FR','US'), # here you can specify some countries
             extra = TRUE,
             start = 1960, 
             end = 2025) |>
  mutate(across(everything(), as.vector)) |>
  select(-status, -lending, -iso2c, -iso3c)
wb_data <- wb_raw |> filter(country %in% c("France", "United States")) |> arrange(country, year)
wb_eu <- wb_raw |> filter(country %in% c("Spain", "Italy", "Poland", "Slovenia", "Venezuela, RB")) |> arrange(country, year)

Again, for pythonistas, the equivalent kind of request can be found in this colab notebook.

wb_eu <- wb_eu |> mutate(gdp_per_capita = if_else(is.na(gdp_per_capita), gdp/population, gdp_per_capita))

Below, we provide a snapshot of the data.

head(wb_data |> select(-region))
country year lastupdated gdp_per_capita population gdp gdp_growth inflation debt unemployment capital longitude latitude income
France 1960 2025-07-01 10733.30 47412964 61756878990 NA 4.139936 NA NA Paris 2.35097 48.8566 High income
France 1961 2025-07-01 11133.15 47905982 66806595975 4.803832 2.400461 NA NA Paris 2.35097 48.8566 High income
France 1962 2025-07-01 11779.29 48389516 74888595286 6.871699 5.331280 NA NA Paris 2.35097 48.8566 High income
France 1963 2025-07-01 12384.54 48877567 83957182115 6.198635 4.999153 NA NA Paris 2.35097 48.8566 High income
France 1964 2025-07-01 13040.57 49401492 93079224076 6.425865 3.211192 NA NA Paris 2.35097 48.8566 High income
France 1965 2025-07-01 13537.05 49877725 100522559916 4.807918 2.703105 NA NA Paris 2.35097 48.8566 High income

As usual, there are some missing points (NAs). Several causes can explain missing data. Either the country did not compute (or want to compute/disclose) particular indicators, or the database simply does not include it. Sometimes, it may be necessary to resort to several sources and join them.

2.1 Gross domestic product

2.1.1 Definitions

Surprisingly, there are several ways to evaluate the amount of goods and services produced within a country in a given year. We provide the definition used by the World Bank:

GDP is the sum of gross value added by all resident producers in the economy plus any product taxes and minus any subsidies not included in the value of the products. It is calculated without making deductions for depreciation of fabricated assets or for depletion and degradation of natural resources”.

As we will see later, there are several ways to compute the GDP, depending on the focus (production, income, expenditure).

The international standard for measuring GDP is contained in the System of National Accounts, 1993, compiled by the International Monetary Fund, the European Commission, the Organization for Economic Cooperation and Development, the United Nations, and the World Bank.

The baseline values reported for GDP are measured in the prices of a particular year. But current price series are influenced by the effects of inflation (see below in Section 2.2)… hence it is possible to correct this - in which case the series becomes “constant” in prices of a given year (e.g., 2010).

2.1.2 Examples

Let’s visualize the time-series of GDP per capita, this is usually the proxy for how rich the average citizen is - and is used to compared wealth (or well-being, loosely speaking) of citizens across the world. We can of course point to other indicators, like happiness, though they are out of the scope of the present course. The French statistical administration even introduced the idea of “subjective GDP” (see also this report).

Below, code snippets are shown in the body of the text, while visual output is presented in the right margin.

g <- wb_data |>
  mutate(gdp_per_capita = round(gdp_per_capita)) |>
  ggplot(aes(x = year, y = gdp_per_capita, color = country)) +
  geom_line() + theme_classic() +
  theme(axis.title = element_blank(),
        title = element_text(face = "bold"),
        legend.title = element_text(face = "bold")) +
  ggtitle("GDP per capita") +
  scale_color_manual(values = c("#22D4A3", "#000000"))
ggplotly(g) |>
  layout(legend = list(orientation = "h", x = -0.2, y =-0.2))

It seems the divide between the US and France has slowly but steadily accelerated since 1990. Investments, R&D spending (innovation & technology), as well as higher energy costs are potential explanations. Other reasons: a higher number of hours worked in the US, and the relative strength of the dollar (which fluctuates). For raw GDP, population trends are also important.

In Eastern Europe, GDP per capita has been catching up that of countries of Southern Europe since ~2010.

wb_eu |>
  filter(year > 1990) |>
  mutate(gdp_per_capita = round(gdp_per_capita)) |>
  ggplot(aes(x = year, y = gdp_per_capita, color = country)) +
  geom_line() + theme_classic() +
  theme(axis.title = element_blank(),
        title = element_text(face = "bold"),
        legend.position = "bottom",
        legend.title = element_text(face = "bold")) +
  ggtitle("GDP per capita") + 
  guides(color = guide_legend(nrow = 2, byrow = T)) + 
  scale_color_observable()

But what about GDP growth?… \[\Delta GDP_t=\frac{GDP_t-GDP_{t-1}}{GDP_{t-1}}.\] (from one period - year, or quarter - to the other).

wb_data |>
  mutate(gdp_growth = round(gdp_growth, 2)) |>
  ggplot(aes(x = year, y = gdp_growth, fill = country)) +
  geom_col(position = "dodge") + theme_classic() +
  theme(axis.title = element_blank(),
        legend.position = c(0.5,0.2),
        title = element_text(face = "bold"),
        legend.title = element_text(face = "bold")) +
  ggtitle("GDP growth (%)") +
  scale_fill_manual(values = c("#22D4A3", "#000000"))

In the recent period (1980 onwards), US figures are consistently above those of France. The US economy (as measured by GDP) has thus increased faster than France’s. This could already be seen in the preceding plot.

2.1.3 The cross-section of countries

What kind of differences exist between countries in terms of GDP/capita?

wb_raw |>
  filter(year == 2023, income != "Aggregates") |>
  mutate(gdp_per_capita = round(gdp_per_capita)) |>
  ggplot(aes(y = reorder(country, gdp_per_capita), x = gdp_per_capita)) + geom_col() +
  theme(axis.text.y = element_blank(), 
        axis.title.y = element_blank(), 
        axis.ticks.y = element_blank()) + 
  xlab("GDP per capita") + 
  annotate("text", label = "this is Monaco", y = "Monaco", x = 149000, size = 3.3, vjust = 1.2) + 
  annotate("text", label = "this is Afghanistan", y = "Afghanistan", x = 29500, size = 3.3, vjust = 0.1)

In 2023, the discrepancy is between Monaco (220k USD) and Afghanistan (0.4k USD), so a gap between 1 and 500 roughly!

What are the rich countries?

wb_raw |>
  filter(year == 2023, income != "Aggregates", gdp_per_capita > 50000) |>
  mutate(gdp_per_capita = round(gdp_per_capita)) |>
  ggplot(aes(y = reorder(country, gdp_per_capita), x = gdp_per_capita)) + geom_col(fill = "#2299FF") +
  theme_classic() + 
  theme(axis.title.y = element_blank()) + xlab("GDP per capita") 

Most of them are “small” (at least in terms of population, the US being the most notable exception).

A brief look at human development index (from the United Nations) that embeds other criteria:

We fetch data from the open-numbers repository on Github (managed by gapminder).

url <- "https://raw.githubusercontent.com/open-numbers/ddf--gapminder--systema_globalis/master/countries-etc-datapoints/ddf--datapoints--hdi_human_development_index--by--geo--time.csv"
hdi <- read_csv(url)
colnames(hdi) <- c("country", "year", "hdi")

hdi |>
  filter(country %in% c("fra", "usa")) |>
  ggplot(aes(x = year, y = hdi, color = country)) +
  geom_line() + theme_classic() +
  theme(axis.title = element_blank(),
        title = element_text(face = "bold"),
        legend.position = c(0.8,0.25),
        legend.title = element_text(face = "bold")) +
  ggtitle("Human Development Index") +
  scale_color_manual(values = c("#22D4A3", "#000000"))

Money does not buy everything it seems… and France is catching up, despite lower GDP per capita in the recent period.

Across countries, this gives:

hdi |> 
  filter(year == 2023) |>
  arrange(desc(hdi)) |>
  head(10)
country year hdi
isl 2023 0.972
che 2023 0.970
nor 2023 0.970
dnk 2023 0.962
deu 2023 0.959
swe 2023 0.959
aus 2023 0.958
hkg 2023 0.955
nld 2023 0.955
bel 2023 0.951

2.2 Inflation

2.2.1 Definition

Now, let us move on to another important topic: the evolution of prices.

It is critical to maintain inflation to relatively low levels (e.g., 2%). Hyperinflation has devastating effects for a country (one notable case is Germany in the 1920s - but Argentina nowadays is another example). This is so important that in Europe, the primary mandate (objective) of the European Central Bank (ECB) is to control inflation, 2% being the usual target.
In the US, the tone is the same:

In conducting monetary policy, we will remain highly focused on fostering as strong a labor market as possible for the benefit of all Americans. And we will steadfastly seek to achieve a 2 percent inflation rate over time.

Federal Reserve Board (Fed) Chair Jerome Powell in his Aug. 27, 2020 speech.

But how is inflation computed? The calculation rests on the notion of a representative basket that encompasses the goods and services consumed by the average citizen of a country. The price of items (bread, apples, microwave oven, cars, cinema ticket, etc.) is recorded from various sources (across supermarket chains, but also from online stores). Then, the basket price is computed as a weighted average of all of its elements (this is sometimes referred to the Consumer Price Index - CPI). This is usually done every month, hence data is quite granular. Inflation is then the increase of the CPI compared to its value 12 months earlier.

2.2.2 Illustration

The horizontal grey line marks the 2% target (upper bound). It was quite well met between 1990 and 2020.

wb_data |>
  ggplot(aes(x = year, y = inflation, fill = country)) +
  geom_col(position = "dodge") + theme_classic() +
  theme(axis.title = element_blank(),
        legend.position = c(0.7,0.7),
        title = element_text(face = "bold"),
        legend.title = element_text(face = "bold")) +
  ggtitle("Inflation (%)") + geom_hline(yintercept = 2, color = "grey") + 
  annotate("text", label = "2% target", x = 2015, y = 4, color = "grey") + 
  scale_fill_manual(values = c("#22D4A3", "#000000"))

The values are annualized and they are in percents.
There appears to be a change. Before 1985, France had (much) higher inflation, but since then, it is the opposite.
We clearly see the post-2022 surge in high inflation.
From these series, we can reconstruct the CPI (usually, it’s done the other way around).

wb_data |>
  group_by(country) |>
  mutate(CPI = cumprod(1+inflation/100)) |>
  ggplot(aes(x = year, y = CPI, color = country)) + geom_line() +
  ggtitle("Customer Price index") +
  scale_color_manual(values = c("#22D4A3", "#000000")) + theme_classic() +
  theme(axis.title = element_blank(),
        legend.position = c(0.8,0.2),
        title = element_text(face = "bold"),
        legend.title = element_text(face = "bold")) 

Prices have risen slightly faster in France, compared to the US - during the 1970-1990 decades. After that, trends are more parallel.

2.3 Unemployment

2.3.1 Definitions

For unemployment, there are some subtleties. Indeed, the World Bank proposes two estimates:

The French INSEE follows the ILO guidelines for instance. Someone is considered unemployed if the following criteria are verified. This person

  • is aged 15 years or over;
  • is without work;
  • has actively taken steps to seek work over the last four weeks or has found a job that begins in less than 3 months;
  • is available for work in the next two weeks

Employment starts when a person has done at least one hour’s paid work (including short contracts or partial work). The unemployment rate is: \[U = \frac{\# \text{unemployed}}{\# \text{employed} + \#\text{unemployed}}\]

The denominator is the size of the active workforce (who are in fact potentially active). Inactive people are for instance children or retired citizens. Estimates are produced thanks to Labour Force Surveys of 20 questions - conducted every week (!) in France (sample size ~100k+). This is done continuously, with rolling “reference weeks”.

For comparison purposes, we stick to the ILO definition, but in this case, the data starts in 1991 only.

2.3.2 Examples

g <- wb_data |>
  na.omit() |>
  ggplot(aes(x = year, y = unemployment, color = country)) + geom_line() +
  theme_classic() + 
  theme(axis.title = element_blank(),
        title = element_text(face = "bold"),
        legend.title = element_text(face = "bold")) + 
  ggtitle("Unemployment (%)") +
  scale_color_manual(values = c("#22D4A3", "#000000"))
ggplotly(g) |>
  layout(legend = list(orientation = "h", x = -0.2, y =-0.2))

There are pronounced fluctuations of the unemployment rate. Especially for the US, for which the job market is more “fluid”: jobs are more easily created and destroyed there, compared to France where it’s harder to lay off - and hence employers are more careful when hiring. The average value is again to the advantage of the US… (it is cutomary to consider that lower unemployment is better, but precarious jobs are also more widespread in the US).

2.4 Debt

2.4.1 Definition

Central governments use debt instruments (e.g., bonds) to access financial markets and capital. This is usually because their budgets is in deficit. Note that some data providers sometimes also provide a field called General Government Debt: in this case, local debts from cities, regions or states is also included in the totals. To keep numbers to reasonable levels, and to compare countries, the total debt levels are scaled by GDP.

Let’s have a look at a sample from the French debt:

Short-term debt usually refers to payments that will be due in one year or less.
Medium-term is between 1 and 5-10 years and long-term is beyond 5-10 years.
“Encours démembré” = strips, i.e., zero-coupon bonds.

2.4.2 Snapshot of data

wb_data |>
  filter(year > 1985) |>
  ggplot(aes(x = year, y = debt, fill = country)) +
  geom_hline(yintercept = 100, color = "grey") +
  geom_col(position = "dodge") + theme_classic() +
  theme(axis.title = element_blank(),
        legend.position = c(0.3, 0.7),
        title = element_text(face = "bold"),
        legend.title = element_text(face = "bold")) +
  ggtitle("Debt (% of GDP)") + 
  scale_fill_manual(values = c("#22D4A3", "#000000"))

We see the data is more recent here.
A natural threshold is 100%: when total debt is equal to national output.
The trend is clearly upward, which raises the question of the sustainability of ever growing debt. For now, it still holds, but for how long? \(\rightarrow\) see the European debt sustainability monitor.

3 Bottom line

Data, data, data, it’s all about data!

Nevertheless, it is far from easy to explain each one of this concepts (variables) alone (and the corresponding series). But the joint (and possibly causal) relationships across countries and between notions are very hard to rationalize!

Standard models try to explain sustained growth, but often fail to do so. This is what we will see/cover in the next sessions.

In short: we have seen the data (empirical salient facts), we will then cover (some) (imperfect) theory.

4 Fetching data outside the World Bank

For the FRED, you need an API key. But the rest is easy, thanks to the {fredr} package.
IMF really is a mess, not user-friendly at all.

# The example from the IMF website does not even work.
# https://data.imf.org/en/Resource-Pages/IMF-API
# library(rsdmx)
# flowref <- 'IMF.STA,CPI'
# filter <- 'USA...IX.M'
# df <- as.data.frame(readSDMX(providerId = 'IMF_DATA',
#                              resource = 'data',
#                              flowRef = flowref,
#                              key = filter))

With the {imfweo} package: World Economic Outlook data.
Which series are available?

library(imfweo)
weo_get_series() |> head(8)
series_id series_name units
BCA Current account balance U.S. dollars
BCA_NGDPD Current account balance Percent of GDP
BF Financial account balance U.S. dollars
BFD Direct investment, net U.S. dollars
BFF Financial derivatives, net U.S. dollars
BFO Other investment, net U.S. dollars
BFP Portfolio investment, net U.S. dollars
BFRA Change in reserves U.S. dollars

Fetching some data for particular countries.

weo_get(
  entities = c("USA", "GBR", "DEU"),
  start_year = 2000,
  end_year = 2025,
  year = 2025,
  release = "Spring"
) |> head(8)
entity_name entity_id series_name units series_id year value
Germany DEU Current account balance U.S. dollars BCA 2000 -29.993
Germany DEU Current account balance U.S. dollars BCA 2001 -3.411
Germany DEU Current account balance U.S. dollars BCA 2002 41.056
Germany DEU Current account balance U.S. dollars BCA 2003 37.997
Germany DEU Current account balance U.S. dollars BCA 2004 128.708
Germany DEU Current account balance U.S. dollars BCA 2005 134.648
Germany DEU Current account balance U.S. dollars BCA 2006 176.991
Germany DEU Current account balance U.S. dollars BCA 2007 232.471