Instruction
Story - 2 : Can the FED Control Inflation and Maintain Full
Employment
The Federal Reserve’s mandate from Congress is to control inflation
and to maintain low unemployment. These seem to be contradictory
objectives.
For this story you will need to source the following
data for the last 25 years;
The Consumer Price Index (CPI) (Bureau of Labor Statistics)
The FED Funds Rate (FRED) (Federal Reserve Board)
Unemployment Rate (Bureau of Labor Statistics)
Your Data Visualizations should be designed to answer the question “Has the FED been able to fulfill the mandate given to it by Congress?”
The Federal Reserve is tasked with a dual mandate: price stability (low inflation) and maximum employment. These goals often pull in opposite directions. In this story, we examine whether the Fed has successfully balanced these goals over the last 25 years.
To ensure accuracy and timeliness, I accessed all datasets directly through their official APIs.
The Unemployment Rate was retrieved from the BLS API using the bls_api() function.
The Consumer Price Index (CPI) was pulled from the BLS API as well.
The Federal Funds Rate came from the FRED API using the fredr package.
Using API calls rather than manual downloads ensures reproducibility and access to the most up-to-date information available.
API Configuration and
Data Access
To keep API credentials secure and portable, I stored them in a local .Renviron file. This allows R to load keys at startup without exposing them in the code.
BLS_KEY was used for BLS data (CPI and unemployment).
FRED_KEY was used for FRED data (Fed Funds Rate and Inflation).
This setup keeps the workflow reproducible while maintaining security.
BLS_KEY=07b439349cae4e88ae91a8ff0f78xxxx
FRED_KEY=367eba0ab43d0131322198973bdbxxxx
When R starts, these environment variables are automatically loaded. I retrieved them in my script using:
bls_api_key <- Sys.getenv(“BLS_KEY”)
fred_key <-
Sys.getenv(“FRED_KEY”)
library(httr)
library(tidyverse)
library(glue)
library(openintro)
library(ggplot2)
library(dplyr)
library(jsonlite)
library(blscrapeR)
library(fredr)
library(lubridate) Clean & Preprocess
(25 Years of Annual Data)
Both CPI and unemployment data from the BLS were provided in monthly frequency, while Fed Funds and inflation data from FRED were daily or monthly. To ensure comparability across all sources, I aggregated them into annual averages covering 2000–2025 (25 years).
Inflation reflects how quickly the cost of living rises over
time.
By tracking CPI, we can see whether the Fed has managed to keep prices
stable.
# Build the request body for CPI data (2015–2025)
payload <- glue('{
"seriesid":["CUUR0000SA0"],
"startyear":"2015",
"endyear":"2025",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
# Send POST request to the BLS API for 2015–2025 data
response <- POST(bls_url,
body = payload,
content_type("application/json"),
encode = "json")
# Parse the JSON response into an R object
x <- content(response, "text") %>% jsonlite::fromJSON()
# Extract CPI data and convert to tibble
CPI_2 <- x$Results$series$data[[1]] %>% as_tibble()
# Build the request body for CPI data (2000–2015)
payload <- glue('{
"seriesid":["CUUR0000SA0"],
"startyear":"2000",
"endyear":"2015",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
# Send POST request to the BLS API for 2000–2015 data
response <- POST(bls_url,
body = payload,
content_type("application/json"),
encode = "json")
# Parse the second response
x <- content(response, "text") %>% jsonlite::fromJSON()
# Extract CPI data and convert to tibble
CPI_1 <- x$Results$series$data[[1]] %>% as_tibble()
# Combine two datasets
CPI_Final <- bind_rows(CPI_1, CPI_2) %>%
distinct()
# Ensure 'year' column is numeric
CPI_Final$year <- as.numeric(CPI_Final$year)
# Ensure 'value' column (CPI index) is numeric
CPI_Final$value <- as.numeric(CPI_Final$value)
# Calculate annual average CPI values by year
cpi_annual <- CPI_Final %>%
group_by(year) %>%
summarise(cpi = mean(value, na.rm = TRUE)) %>%
ungroup()
# Select the 10 most recent years
top_10_cpi <- cpi_annual %>%
arrange(desc(year)) %>%
head(10)
# Display the 10 most recent annual CPI values
top_10_cpi# A tibble: 10 × 2
year cpi
<dbl> <dbl>
1 2025 321.
2 2024 314.
3 2023 305.
4 2022 293.
5 2021 271.
6 2020 259.
7 2019 256.
8 2018 251.
9 2017 245.
10 2016 240.
# CPI Graph
ggplot(cpi_annual, aes(x = year, y = cpi)) +
# Shaded crisis zones
annotate("rect", xmin = 2008, xmax = 2010, ymin = -Inf, ymax = Inf,
fill = "red", alpha = 0.1) +
annotate("rect", xmin = 2020, xmax = 2020.9, ymin = -Inf, ymax = Inf,
fill = "orange", alpha = 0.15) +
annotate("rect", xmin = 2022, xmax = 2023, ymin = -Inf, ymax = Inf,
fill = "red", alpha = 0.15) +
# Line + points
geom_line(color = "#FF9999", linewidth = 1) +
geom_point(color = "#FF9999", size = 2) +
# Labels for key crises
annotate("text", x = 2009, y = max(cpi_annual$cpi) - 20,
label = "2008 Financial Crisis", color = "red", size = 3.5, fontface="bold") +
annotate("text", x = 2020, y = max(cpi_annual$cpi) - 15,
label = "COVID Shock", color = "orange", size = 3.5, fontface="bold") +
annotate("text", x = 2022.5, y = max(cpi_annual$cpi) - 10,
label = "Post-COVID Inflation Surge", color = "red", size = 3.5, fontface="bold") +
labs(
title = "Fed Struggles to Control Inflation",
subtitle = "CPI keeps climbing despite policy shifts, with sharp surges in crises",
x = "Year",
y = "CPI Index"
) +
theme_minimal(base_size = 13)The Consumer Price Index (CPI) measures the average change in prices for goods and services.
Over the last 25 years, the CPI shows a steady upward trend, reflecting the long-term increase in the cost of living across categories such as housing, food, healthcare, and transportation. Inflationary pressures have been consistent, with sharper increases in certain periods
Unemployment Rate
The unemployment rate is the share of the labor force
actively seeking work but unable to find a job. Unlike the CPI, it does
not trend upward, instead it fluctuates around the “natural rate” of
unemployment—the level consistent with stable inflation.
# Unemployment
payload <- glue('{
"seriesid":["LNS14000000"],
"startyear":"2015",
"endyear":"2025",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
response <- POST(bls_url,
body = payload,
content_type("application/json"),
encode = "json")
x <- content(response, "text") %>% jsonlite::fromJSON()
x$Results$series$data[[1]] %>% as_tibble() -> Unemploy_2
payload <- glue('{
"seriesid":["LNS14000000"],
"startyear":"2000",
"endyear":"2015",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
response <- POST(bls_url,
body = payload,
content_type("application/json"),
encode = "json")
x <- content(response, "text") %>% jsonlite::fromJSON()
x$Results$series$data[[1]] %>% as_tibble() -> Unemploy_1
Unemploy_Final <- bind_rows(Unemploy_1, Unemploy_2) %>%
distinct()
# str(Unemploy_Final)
# Convert year to numeric
Unemploy_Final <- Unemploy_Final %>%
mutate(year = as.numeric(year))
Unemploy_Final <- Unemploy_Final %>%
mutate(value = as.numeric(value))
# Assuming 'Unemploy_Final' has a 'year' and 'value' column
average_per_year <- Unemploy_Final %>%
group_by(year) %>%
summarise(avg_unemployment = mean(value, na.rm = TRUE))
# View the result
head(average_per_year)# A tibble: 6 × 2
year avg_unemployment
<dbl> <dbl>
1 2000 3.97
2 2001 4.74
3 2002 5.78
4 2003 5.99
5 2004 5.54
6 2005 5.08
# Plotting
ggplot(average_per_year, aes(x = year, y = avg_unemployment)) +
# Shaded zones
annotate("rect", xmin = 2008, xmax = 2010, ymin = -Inf, ymax = Inf,
fill = "red", alpha = 0.1) +
annotate("rect", xmin = 2020, xmax = 2020.9, ymin = -Inf, ymax = Inf,
fill = "orange", alpha = 0.15) +
# Line + points
geom_line(color = "darkgreen", linewidth = 1) +
geom_point(color = "darkgreen", size = 2) +
# Labels
annotate("text", x = 2009, y = max(average_per_year$avg_unemployment),
label = "Great Recession Spike", color = "red", size = 3.5, fontface="bold") +
annotate("text", x = 2020.3, y = max(average_per_year$avg_unemployment)-1,
label = "COVID Job Loss", color = "orange", size = 3.5, fontface="bold") +
annotate("text", x = 2023, y = min(average_per_year$avg_unemployment)+0.5,
label = "Recovery to Historic Lows", color = "darkgreen", size = 3.5, fontface="bold") +
labs(
title = "Fed Succeeds in Stabilizing Employment",
subtitle = "Unemployment spikes during crises but consistently recovers to low levels",
x = "Year",
y = "Unemployment Rate (%)"
) +
theme_minimal(base_size = 13)The unemployment rate reveals the proportion of the labor force actively seeking work but unable to find employment.
Across the 25-year period, unemployment shows several important episodes:
2008–2010 recession → unemployment spiked sharply.
2010s recovery → unemployment gradually declined.
Pandemic disruption (2020) → a brief surge in unemployment followed by rapid recovery.
2021–2025 → unemployment has remained at historically low levels.
Overall, unemployment trends suggest that the labor market has largely stabilized in the post-recession years.
Federal Funds Rate &
Inflation
The federal funds rate is the Fed’s primary
tool to influence inflation.
Comparing rate changes with CPI reveals how policy actions shape price
stability.
# FED Funds Rate (FRED)
fred_key <-"367eba0ab43d0131322198973bdb735b"
# FED Funds Rate (FRED)
fredr_set_key(fred_key)
fed_data <- fredr(
series_id = "FEDFUNDS",
observation_start = as.Date("2000-01-01"),
observation_end = as.Date("2025-12-31")
) %>%
mutate(year = year(date)) %>%
select(year, date, fed_funds = value)
# Extract the last 25 years of data
last_25_years <- fed_data %>%
filter(year >= (year(Sys.Date()) - 25))
# Remove duplicate years
last_25_years <- last_25_years %>%
distinct(year, .keep_all = TRUE)
# Convert year to numeric
FED_avg <- last_25_years %>%
mutate(year = as.numeric(year))
FED_avg <- last_25_years %>%
mutate(fed_funds = as.numeric(fed_funds))
# Assuming 'FED_avg' has a 'year' and 'fed_funds' column
average_each_year <- FED_avg %>%
group_by(year) %>%
summarise(avg_fed_funds = mean(fed_funds, na.rm = TRUE))
# Check FED Funds Rate
head(average_each_year)# A tibble: 6 × 2
year avg_fed_funds
<dbl> <dbl>
1 2000 5.45
2 2001 5.98
3 2002 1.73
4 2003 1.24
5 2004 1
6 2005 2.28
Inflation_data <- fredr(
series_id = "FPCPITOTLZGUSA",
observation_start = as.Date("2000-01-01"),
observation_end = as.Date("2025-12-31")
) %>%
mutate(year = year(date)) %>%
select(year, date, inf_rate = value)
# Extract the last 25 years of data
last_25_years <- Inflation_data %>%
filter(year >= (year(Sys.Date()) - 25))
# Remove duplicate years
last_25_years <- last_25_years %>%
distinct(year, .keep_all = TRUE)
# Convert year to numeric
inflation_avg <- last_25_years %>%
mutate(year = as.numeric(year))
inflation_avg <- last_25_years %>%
mutate(inf_rate = as.numeric(inf_rate))
# Assuming 'inflation_avg' has a 'year' and 'fed_funds' column
average_each_year <- inflation_avg %>%
group_by(year) %>%
summarise(avg_inflation_rate = mean(inf_rate, na.rm = TRUE))
# Check inflation
head(average_each_year)# A tibble: 6 × 2
year avg_inflation_rate
<dbl> <dbl>
1 2000 3.38
2 2001 2.83
3 2002 1.59
4 2003 2.27
5 2004 2.68
6 2005 3.39
# Merge datasets by year-month
combined_data <- full_join(
FED_avg,
inflation_avg,
by = "year"
)
# Check combine data
head(combined_data)# A tibble: 6 × 5
year date.x fed_funds date.y inf_rate
<dbl> <date> <dbl> <date> <dbl>
1 2000 2000-01-01 5.45 2000-01-01 3.38
2 2001 2001-01-01 5.98 2001-01-01 2.83
3 2002 2002-01-01 1.73 2002-01-01 1.59
4 2003 2003-01-01 1.24 2003-01-01 2.27
5 2004 2004-01-01 1 2004-01-01 2.68
6 2005 2005-01-01 2.28 2005-01-01 3.39
# data for faceting
combined_long <- combined_data %>%
select(year, fed_funds, inf_rate) %>%
pivot_longer(cols = c(fed_funds, inf_rate),
names_to = "indicator",
values_to = "value")
ggplot(combined_long, aes(x = year, y = value, color = indicator)) +
# Shaded zones
annotate("rect", xmin = 2008, xmax = 2010, ymin = -Inf, ymax = Inf,
fill = "red", alpha = 0.1) +
annotate("rect", xmin = 2020, xmax = 2020.9, ymin = -Inf, ymax = Inf,
fill = "orange", alpha = 0.15) +
annotate("rect", xmin = 2022, xmax = 2023, ymin = -Inf, ymax = Inf,
fill = "red", alpha = 0.15) +
# Line + points
geom_line(linewidth = 1) +
geom_point(size = 2) +
scale_color_manual(values = c("fed_funds" = "blue", "inf_rate" = "#FF9999"),
labels = c("Fed Funds Rate", "Inflation Rate")) +
# Labels
annotate("text", x = 2009, y = max(combined_long$value, na.rm = TRUE)-1,
label = "Zero Rates\n(2008–2015)", color = "blue", size = 3.5, fontface="bold") +
annotate("text", x = 2023, y = max(combined_long$value, na.rm = TRUE)-1,
label = "High Inflation Despite Rate Hikes", color = "red", size = 3.5, fontface="bold") +
labs(
title = "Fed’s Policy Tool vs Inflation Reality",
subtitle = "Rate cuts in crises support jobs, but inflation often escapes control",
x = "Year",
y = "Rate (%)",
color = ""
) +
theme_minimal(base_size = 13) +
theme(legend.position = "bottom")The Fed Funds Rate represents the central bank’s primary monetary policy tool, while inflation measures changes in overall price levels.
From 2000 to 2025, we observe:
Early 2000s: Higher Fed rates (around 6%) before cuts during the dot-com crash.
2008–2015: Ultra-low rates (near zero) to combat the financial crisis and stimulate recovery.
2016–2019: Gradual increases as the economy stabilized.
2020 pandemic: Sharp rate cuts again near zero.
2022–2025: Aggressive hikes above 5% to combat post-COVID inflation.
Inflation closely mirrored these cycles: generally low and stable pre-2020, but sharply elevated in the early 2020s before showing signs of moderation.
Has the FED been able to
fulfill the mandate given to it by Congress?
Mandate of the Federal Reserve
The Fed’s dual mandate is to
maintain price stability (inflation near 2%) and promote maximum
sustainable employment (unemployment around 3–5%). Since 2000, it has
cut rates in recessions and raised them during inflationary periods.
While not perfect, unemployment has mostly stayed near its target, and
inflation has averaged close to 2%.
Consumer Price Index (CPI)
CPI tracks changes in the cost
of goods and services and signals inflation trends. When CPI rises, the
Fed raises rates to curb spending, when it falls or growth slows, rates
are lowered to stimulate demand. This creates a negative correlation
between CPI and interest rates.
Unemployment Rate Low unemployment boosts spending but can fuel inflation, prompting the Fed to raise rates. Rising unemployment typically leads the Fed to cut rates to spur growth and job creation. An unemployment rate of 3 to 5% is often considered healthy for the economy.
Inflation Rate
When inflation rises, the Fed raises rates
to cool demand. when it falls, rates are lowered to encourage borrowing
and growth. The goal is to keep inflation anchored near the 2%
target.
library(tibble)
library(ggplot2)
scorecard <- tibble(
Mandate = c("Employment", "Inflation"),
Status = c("✔ Stable", "✖ Unstable"),
Color = c("lightgreen", "#FF9999")
)
ggplot(scorecard, aes(x = Mandate, y = 1, fill = Mandate)) +
geom_col(width = 0.6, show.legend = FALSE) +
geom_text(aes(label = Status), vjust = -0.5, size = 6, fontface = "bold") +
scale_fill_manual(values = setNames(scorecard$Color, scorecard$Mandate)) +
ylim(0, 1.2) +
labs(
title = "Fed's Dual Mandate: Final Scorecard",
subtitle = "✔ Employment stabilized | ✖ Inflation less controlled",
x = "",
y = ""
) +
theme_minimal(base_size = 14) +
theme(
axis.text = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank()
)Over the past 25 years, the Federal Reserve has been more successful in maintaining stable employment than in fully controlling inflation. The CPI shows that inflation has consistently trended upward, confirming the challenges of keeping prices fully stable, while unemployment has generally remained near its natural rate, rising sharply only during major recessions such as the Dot-Com bust (2001), the financial crisis (2008), and the COVID-19 pandemic. In each of these episodes, the Federal Funds Rate reveals the Fed’s interventions—cutting rates during downturns to support employment and raising them when inflationary pressures built up. Taken together, the evidence suggests that the Fed has generally achieved its dual mandate of price stability and maximum employment. While it cannot completely resolve the tension between low unemployment and stable prices, its policy actions have consistently guided the economy toward both objectives, responding effectively to cyclical shifts and external shocks.