library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 4.0.0 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(lubridate)
library(fredr)
## Warning: package 'fredr' was built under R version 4.5.2
library(scales)
##
## Attaching package: 'scales'
##
## The following object is masked from 'package:purrr':
##
## discard
##
## The following object is masked from 'package:readr':
##
## col_factor
The Federal Reserve has a simple but demanding mandate from Congress: - Keep prices stable - Maintain maximum employment
In practice, these goals often pull in opposite directions. Raising interest rates to fight inflation can slow the economy and cost jobs. Cutting rates to support employment can risk higher inflation.
This data story asks: Has the Federal Reserve been able to fulfill its dual mandate over the past 25 years?
To explore this question, I look at three monthly series from 1999 to the present:
Consumer Price Index (CPI) - from the Bureau of Labor Statistics (BLS), accessed via the Federal Reserve Bank of St. Louis FRED API.
Unemployment Rate - BLS series, also accessed via FRED.
Effective Federal Funds Rate - the Fed’s main policy interest rate, from FRED.
I first examine each objective separately (inflation and unemployment), then overlay the Fed’s policy rate to see how the central bank reacts to changing economic conditions.
fredr_set_key("5b17c40f404da59e1ed890efd6eaf575")
start_date <- as.Date("1999-01-01")
end_date <- Sys.Date()
# CPI
cpi_raw <- fredr(
series_id = "CPIAUCSL",
observation_start = start_date,
observation_end = end_date
)
# Unemployument rate
unemp_raw <- fredr(
series_id = "UNRATE",
observation_start = start_date,
observation_end = end_date
)
# Fed funds rate
fedfunds_raw <- fredr(
series_id = "FEDFUNDS",
observation_start = start_date,
observation_end = end_date
)
head(cpi_raw)
head(unemp_raw)
head(fedfunds_raw)
I convert CPI into a year over year inflation rate and then merge all three series into a single monthly data frame.
cpi <- cpi_raw %>%
select(date, cpi_index = value) %>%
arrange(date) %>%
mutate(
inflation_yoy = (cpi_index / lag(cpi_index, 12) - 1)* 100) %>%
filter(!is.na(inflation_yoy))
# clean unemployment
unemp <- unemp_raw %>%
select(date, unemployment_rate = value)
# clean fed funds rate
fedfunds <- fedfunds_raw %>%
select(date, fed_funds_rate = value)
# merge by date
macro <- cpi %>%
inner_join(unemp, by = "date") %>%
inner_join(fedfunds, by = "date") %>%
filter(date >= start_date, date <= end_date)
head(macro)
summary(macro)
## date cpi_index inflation_yoy unemployment_rate
## Min. :2000-01-01 Min. :169.3 Min. :-1.959 Min. : 3.400
## 1st Qu.:2006-06-01 1st Qu.:201.8 1st Qu.: 1.641 1st Qu.: 4.200
## Median :2012-11-01 Median :231.2 Median : 2.334 Median : 5.000
## Mean :2012-10-31 Mean :232.6 Mean : 2.583 Mean : 5.652
## 3rd Qu.:2019-04-01 3rd Qu.:255.2 3rd Qu.: 3.322 3rd Qu.: 6.300
## Max. :2025-09-01 Max. :324.4 Max. : 8.999 Max. :14.800
## fed_funds_rate
## Min. :0.050
## 1st Qu.:0.150
## Median :1.260
## Mean :1.989
## 3rd Qu.:3.970
## Max. :6.540
The Fed’s long-run inflation goal is roughly 2% per year. The chart below shows year-over-year CPI inflation since 1999, with a light band around the 2% target.
ggplot(macro, aes(x = date, y = inflation_yoy)) +
geom_hline(yintercept = 2, linetype = "dashed", color = "grey40") +
geom_ribbon(aes(ymin = 1.5, ymax = 2.5),
fill = "grey90", alpha = 0.5) +
geom_line(color = "firebrick") +
labs(
title = "Year over Year CPI Inflation (1999-Present)",
x = "Year",
y = "Inflation Rate (%, year over year)",
caption = "Source: BLS CPI (via FRED)"
) +
scale_y_continuous(labels = percent_format(scale = 1)) +
theme_minimal()
For much of the early 2000s and the decade after the Great Recession, inflation hovered near the 2% target band. There were temporary dips during the 2008–2009 financial crisis and in the early COVID period, followed by a sharp spike in 2021–2022 when inflation surged well above 5%.
Overall, the Fed stayed reasonably close to its price-stability goal for long stretches of time, but the post-pandemic period stands out as a clear miss where inflation persistently ran above target.
Economists often view an unemployment rate around 4%–5% as compatible with “maximum employment.” The next chart shows how the job market has evolved over the same period.
ggplot(macro, aes(x = date, y = unemployment_rate)) +
geom_hline(yintercept = 4, linetype = "dashed", color = "grey40") +
geom_line(color = "steelblue") +
labs(
title = "U.S. Unemployment Rate (1999-Present)",
x = "Year",
y = "Unemployment rate(%)",
caption = "Source: BLS Unemployment Rate (via FRED)"
) +
theme_minimal()
The unemployment rate tells a different part of the story. It was low and fairly stable in the late 1990s and mid-2000s, then spiked above 10% during the Great Recession. After a long recovery, unemployment fell to historically low levels before the COVID shock pushed it abruptly higher again in 2020.
In both major downturns, the Fed eventually helped bring unemployment back down, but only after long adjustment periods. Over the full 25 years, the labor market spends more time in “healthy” territory than in crisis, yet there are clear episodes when the mandate of maximum employment was not met.
To see how the Fed responds to changing conditions, I standardize each series (inflation, unemployment, and the Fed funds rate) so we can compare their ups and downs on the same scale.
macro_std <- macro %>%
mutate(
inflation_z = as.numeric(scale(inflation_yoy)),
unemployment_z = as.numeric(scale(unemployment_rate)),
fedfunds_z = as.numeric(scale(fed_funds_rate))
) %>%
select(date, inflation_z, unemployment_z, fedfunds_z) %>%
pivot_longer(
cols = -date,
names_to = "series",
values_to = "z"
) %>%
mutate(
series = recode(series,
inflation_z = "Inflation(YoY)",
unemployment_z = "Unemployment Rate",
fedfunds_z = "Fed Funds Rate")
)
ggplot(macro_std, aes(x = date, y = z, color = series)) +
geom_line() +
labs(
title = "Standardized Inflation, Unemployment, and Fed Funds Rate",
subtitle = "Each line shows deviations from its own 25 years average",
x = "Year",
y = "Standardized Value (z-score)",
color = "Series",
caption = "Sources: BLS, Federal Reserve"
) +
theme_minimal()
This plot highlights how the Fed tends to move interest rates in response to inflation and unemployment.
The timing shows the usual sequence: inflation picks up, the Fed raises rates, and higher borrowing costs eventually cool the economy and push unemployment higher with a lag. The Fed is clearly reacting to the data, but it cannot avoid all pain—there are periods when either unemployment or inflation is far from the desired range.
Finally, I look directly at the relationship between inflation and unemployment over time. Each point is a month, colored by year.
macro %>%
mutate(year = year(date)) %>% # color by year to show evolution
ggplot(aes(x = inflation_yoy, y = unemployment_rate, color = year)) +
geom_point(alpha = 0.7) +
scale_color_viridis_c(option = "plasma") +
labs(
title = "Inflation vs Unemployment (Monthly, 1999–Present)",
x = "Inflation rate (%, year over year)",
y = "Unemployment rate (%)",
color = "Year"
) +
theme_minimal()
If the Fed could perfectly balance its mandate, we might expect most points to cluster around low inflation and low unemployment. Instead, the scatter shows that the economy moves through many different combinations:
There is no single, stable trade-off curve. Instead, the Fed is constantly trying to steer the economy back toward the sweet spot of low, stable inflation and low unemployment, but global shocks and structural changes frequently push it away.
So, has the Fed fulfilled the mandate given to it by Congress?
On inflation, the Fed kept price growth close to 2% for much of the 2000s and 2010s, but the post-COVID surge shows that price stability is fragile. The recent overshoot is a clear miss on the price-stability part of the mandate.
In terms of policy actions, the Fed funds rate clearly responds to inflation and unemployment. Rates are cut when unemployment spikes and raised when inflation becomes a concern. However, these changes work with a lag and cannot fully offset powerful shocks such as financial crises or pandemics.
Overall, the evidence suggests that the Fed has only partially achieved its dual mandate. It has generally maintained low inflation and low unemployment over long stretches of time, but there are significant periods—most notably the early-2000s downturn, the Great Recession, and the post-COVID inflation surge—when either inflation or unemployment (and sometimes both) have been far from the desired range.