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?”
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).
# 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.
# Plot the annual CPI data
ggplot(cpi_annual, aes(x = year, y = cpi)) +
geom_line(color = "black", linewidth = 0.8, na.rm = TRUE) +
geom_point(color ="black", size = 2, na.rm = TRUE) +
labs(
x = "Year",
y = "CPI Value",
title = "Consumer Price Index (CPI) Over the Last 25 Years (2000–2025)"
) +
theme_minimal()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
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 with black color
ggplot(average_per_year, aes(x = year, y = avg_unemployment)) +
geom_point(color = "black", size = 2) + # dotted points
geom_line(color = "black", linewidth = 0.8) + # straight connecting line
labs(
x = "Year",
y = "Unemployment Rate",
title = "Unemployment Rate Over the Last 25 Years"
) +
theme_minimal()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
# 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
library(ggplot2)
# 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")
# Faceted plot
ggplot(combined_long, aes(x = year, y = value, color = indicator)) +
geom_line(linewidth = 1) +
geom_point(size = 1.8) +
facet_wrap(~ indicator, ncol = 1, scales = "free_y") +
scale_color_manual(values = c("fed_funds" = "blue", "inf_rate" = "red"),
labels = c("Fed Funds Rate", "Inflation Rate")) +
labs(
title = "Federal Funds Rate and Inflation (2000–2025)",
x = "Year",
y = "Rate (%)",
color = "Indicator"
) +
theme_minimal() +
theme(
legend.position = "bottom",
plot.title = element_text(size = 14, face = "bold")
)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?
While there is some evidence to suggest the answer is yes, it is more complicated than that because of the mixed results in relation to the mandate.
Inflation targeting from 2000-2019 stayed within the range. However, 2021-2022 forced the FED to increase rates because of the credibility challenges surrounding the aggressive inflation.
Employment stability after downturns has been achieved, particularly after 2010 and in the 2020 period. Sustaining the two sides of the mandate has been more complicated because of the shocks to inflation (global supply chain issues, recovery from the pandemic).
The past 25 years reflect the Fed being more successful with the stability of employment. There is the CPI, which confirms the instability of fully controlling inflation.
The Fed Funds Rate clearly indicates the extent of FED intervention when there is risk of destabilizing inflation.
The ability to fulfill the mandate is effective under normal conditions, but there are scenarios of major external shocks where it’s not the case.