Intro:

Congress has tasked the Federal Reserve with the responsibility of managing inflation and ensuring low levels of unemployment. These two objectives seem to be at odds with one another. This analysis will look at the the economic indicators from the past 25 years in an effort to answer the question: “Has the FED been able to fulfill the mandate given to it by Congress?”

I will be using Consumer Price Index and Unemployment that I will source from the Bureau of Labor Statistics Public API. I will also be working with Effective Federal Fund Rates data that I will source from the FRED API. The timeframe the data will reflect will be pertaining to the last 25 years (1998-2023).

For context, here are the definitions of the metrics I will be working with:

Consumer Price Index (CPI):

The Consumer Price Index (CPI) is a measure of the average change overtime in the prices paid by urban consumers for a market basket of consumer goods and services.

(Citation 1)

Unemployment:

The percentage of unemployed individuals in the total labor force.

  • People with jobs are employed.
  • People who are jobless, looking for a job, and available for work are unemployed.
  • The labor force is made up of the employed and the unemployed.
  • People who are neither employed nor unemployed are not in the labor force.

(Citation 2)

Effective Federal Funds Rate:

The federal funds rate is the interest rate at which depository institutions trade federal funds (balances held at Federal Reserve Banks) with each other overnight. When a depository institution has surplus balances in its reserve account, it lends to other banks in need of larger balances. In simpler terms, a bank with excess cash, which is often referred to as liquidity, will lend to another bank that needs to quickly raise liquidity. The rate that the borrowing institution pays to the lending institution is determined between the two banks; the weighted average rate for all of these types of negotiations is called the effective federal funds rate. The effective federal funds rate is essentially determined by the market but is influenced by the Federal Reserve through open market operations to reach the federal funds rate target.

(Citation 3)

Loading necessary packages

I will be using quite few packages for this analysis. A few packages: tidyverse and dplyr are just being used for general scripting processes. I will also be using ggplot2 as my visualization package. I will be using httr, jsonlite, fredr and glue to help me access and process data from the separate APIs. I want to note my use of yaml here as this allows me to hide my API keys in a file on my computer, that I can knit my document to RPubs.

library(httr)
library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.3.1
## Warning: package 'lubridate' was built under R version 4.3.1
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── 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(glue)
## Warning: package 'glue' was built under R version 4.3.1
library(jsonlite)
## 
## Attaching package: 'jsonlite'
## 
## The following object is masked from 'package:purrr':
## 
##     flatten
library(fredr)
library(yaml)
## Warning: package 'yaml' was built under R version 4.3.1
library(ggplot2)
library(dplyr)

General BLS API Stuff

As I mentioned earlier, the yaml package allows me to access my API keys in a yaml file so that I can knit my document without exposing my API keys.

config <- yaml::read_yaml("api_keys.yaml")

bls_api_key <- config$api_key

# API URL
api_url = "https://api.bls.gov/publicAPI/v2/timeseries/data/"

CPI 2004-2023

I want to note, I understand that the way I will work with Bureau of Labor Statistics API is not the prettiest and I was experiencing some trouble writing a general function to access data by the seriesid string, so in an effort to provide functionality with my code I chose to ignore how clunk the repetitive nature of my API requests look in my document. The code works and that meets my needs.

For context the API FAQ stated that it only supports with pulls associated with 20 years at a time, hence the splitting up of requests you will see below.

Here I am requesting the Consumer Price Index data from the years 2004-2023.

payload1 <- glue('{
"seriesid":["CUSR0000SA0"],
"startyear":"2004",
"endyear":"2023",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
  
response1 = POST(api_url, body=payload1, content_type('application/json'), encode = 'json')

x1 = content(response1, 'text') %>% jsonlite::fromJSON()
## No encoding supplied: defaulting to UTF-8.
CPI_2023_2004 = x1$Results$series$data[[1]] %>% as_tibble()

#CPI_2023_2004 = subset(CPI_2023_2004, select = -latest)

CPI 1998-2003

This code chunk is an API request for Consumer Price Index data from the years 1998-2003.

# Payload for 2003 - 1998
payload2 = glue('{
"seriesid":["CUSR0000SA0"],
"startyear":"1998",
"endyear":"2003",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")

# Response for 2003 - 1998
response2 = POST(api_url, body=payload2, content_type('application/json'), encode = 'json')

x2 = content(response2, 'text') %>% jsonlite::fromJSON()
## No encoding supplied: defaulting to UTF-8.
CPI_2003_1998 = x2$Results$series$data[[1]] %>% as_tibble()

Combinging BLS API CPI requests

Here I am combining the two API requests to form a singular data frame for the whole 25 year window (1998-2023).

CPI = rbind(CPI_2023_2004, CPI_2003_1998)

CPI = dplyr::select(CPI, -c('footnotes', 'periodName'))

CPI = CPI %>%
  rename('cpi_value' = 'value')

Unemployment 2004-2023

Here is an API request from the Bureau of Labor Statistics API from Unemployment data from the years 2004-2023.

payload1 <- glue('{
"seriesid":["LNU04000000"],
"startyear":"2004",
"endyear":"2023",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
  
response1 = POST(api_url, body=payload1, content_type('application/json'), encode = 'json')

x1 = content(response1, 'text') %>% jsonlite::fromJSON()
## No encoding supplied: defaulting to UTF-8.
U_2023_2004 = x1$Results$series$data[[1]] %>% as_tibble()

Unemployment 1998-2003

Here is an API request from the Bureau of Labor Statistics API from Unemployment data from the years 1998-2003.

payload1 <- glue('{
"seriesid":["LNU04000000"],
"startyear":"1998",
"endyear":"2003",
"registrationkey":"{{bls_api_key}}"
}', .open="{{", .close="}}")
  
response1 = POST(api_url, body=payload1, content_type('application/json'), encode = 'json')

x1 = content(response1, 'text') %>% jsonlite::fromJSON()
## No encoding supplied: defaulting to UTF-8.
U_2003_1998 = x1$Results$series$data[[1]] %>% as_tibble()

Combinging BLS API Unemployment requests

Here I am combining the two data frames for the Unemployment data so that I have one single data frame for the timeframe 1998-2023.

Unemployment = rbind(U_2023_2004, U_2003_1998)

Unemployment = dplyr::select(Unemployment, -c('footnotes', 'periodName'))

Unemployment <- Unemployment %>%
  rename('unemployment' = 'value')

FRED API Info

The FRED API did not seem to have the same 20 year restriction that the BLS API did and so I did not have to run individual API requests and then join the data. Here is my FRED API request which gives me the Effective Federal Fund Rates.

fred_api_key = config$fred_api_key

fredr_set_key(fred_api_key)

search_ls = fredr_series_search_text('Federal Funds Effective Rate')

#colnames(search_ls)

series_ls = fredr_series_observations(series_id = 'FEDFUNDS')

FRED = series_ls %>%
  dplyr::mutate(year = lubridate::year(date), 
                month = lubridate::month(date))

FRED = dplyr::select(FRED, -c('date', 'series_id', 'realtime_start', 'realtime_end'))

FRED = FRED %>% 
  rename('fund_rate'='value')

Preparing to join my data

Here I needed to adopt some standardization in my variables formatting and variable types so that I could join the data based on date data.

# cleaning period
clean_period = function(df) {
  df %>%
    mutate(period = ifelse(grepl('^M0', period),
                            as.numeric(gsub('M0', '', period)),
                            as.numeric(gsub('M', '', period))))
}

# changing period to number
CPI = clean_period(CPI)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `period = ifelse(...)`.
## Caused by warning in `ifelse()`:
## ! NAs introduced by coercion
Unemployment = clean_period(Unemployment)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `period = ifelse(...)`.
## Caused by warning in `ifelse()`:
## ! NAs introduced by coercion
# changing the name of period to join the data
CPI = CPI %>%
  rename('month'='period')

Unemployment = Unemployment %>%
  rename('month'='period')

# Changing FRED$year to character so that I can join the dataframes
FRED = FRED %>%
  mutate(year = as.character(year))

Joining my data

Here I join my Consumer Price Index data to my Unemployment data and then join that data to my Effective Federal Fund Rates data.

df = CPI %>% 
  left_join(Unemployment, by = c('year','month')) %>% 
  left_join(FRED, by = c('year','month')) %>%
  mutate( year = as.numeric(year))

Preparing data for visualization

Here I prep my data for visualization. In order to have a pretty x-axis that does not present problems, I create a date column. I then change unemployment to a numeric datatype. My final task in this code chunk creates two new columns (the second column depends on the creation of the first) color and major_event (the intent here is to color code the major events). I selected three major events that impacted the economy writ large (The Dot-com Bubble Burst, The 2008 Global Financial Crisis, and the ongoing COVID-19 Pandemic).

# Convert year and month columns to date format
df$date <- as.Date(paste(df$year, df$month, "01", sep = "-"))

# Assuming unemployment is a character type, convert it to numeric
df$unemployment <- as.numeric(df$unemployment)

df <- df %>%
  mutate(color = case_when(
    date >= as.Date('2000-03-01') & date <= as.Date('2002-11-01') ~ 'blue',
    date >= as.Date('2008-09-01') & date <= as.Date('2009-06-01') ~ 'orange',
    date >= as.Date('2020-01-01') & date <= as.Date('2023-12-01') ~ 'green',
    TRUE ~ NA_character_
  )) %>%
  mutate(
    'Major Event' = case_when(
      color == 'blue' ~ 'Dot-com Bubble Burst',
      color == 'orange' ~ 'Global Financial Crisis',
      color == 'green' ~ 'COVID-19 Pandemic',
      TRUE ~ NA_character_
    )
  )

Unemployment over time with major financial events overlayed

ggplot(df, aes(x = date, y = unemployment)) +
  geom_rect(aes(xmin = lag(date), xmax = date, ymin = -Inf, ymax = Inf, fill = `Major Event`), alpha = 0.2) +
  geom_line() +
  labs(x = "Year", y = "Unemployment Rate (%)") +
  theme_minimal() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 year") +
  scale_y_continuous(breaks = seq(0, 20, by = 1)) +
  scale_fill_manual(values = c("Dot-com Bubble Burst" = "cyan", "Global Financial Crisis" = "orange", "COVID-19 Pandemic" = "lightgreen"), 
                    name = "Major Event")
## Warning: Removed 1 rows containing missing values (`geom_rect()`).

Changes in metrics happen with real-life context and from this chart it seems clear that the steep increases in the unemployment percentage metric happen along side significant real world events that had an affect on the economy. I do want to note, as it is particularly salient in the visual, that the unemployment percentage metric experienced a super drastic increase and then subsequent decrease towards the beginning of the pandemic. This could have likely been the result of a swift response on behalf of the government in relation to employment.

Consumer Price Index over time with major financial events overlayed

df$cpi_value <- as.numeric(df$cpi_value)

ggplot(df, aes(x = date, y = cpi_value)) +
  geom_rect(data = df, aes(xmin = lag(date), xmax = date, ymin = -Inf, ymax = Inf, fill = `Major Event`), alpha = 0.2) +
  geom_line() +
  labs(x = "Year", y = "Consumer Price Index Value") +
  theme_minimal() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 year") +
  scale_y_continuous(breaks = seq(0, 400, by = 50)) +
  scale_fill_manual(values = c("Dot-com Bubble Burst" = "cyan", "Global Financial Crisis" = "orange", "COVID-19 Pandemic" = "lightgreen"), 
                    name = "Major Event")
## Warning: Removed 1 rows containing missing values (`geom_rect()`).

The overlaying of major events which impacted the economy also added necessary context to this graph of changing Consumer Price Index (CPI) values over time. It is initially very clear that the Consumer Price Index has been consistently increasing since 1998 (the beginning of the data’s time frame).

Another observation is the at the beginnings of both The Dot-com Bubble Burst and The COVID-19 Pandemic, there was a dip in the CPI value, indicating brief decrease in the cost of goods overall (implying a decrease in inflation). However, as I mentioned those periods were brief and then followed by periods by a resumed increase in inflation. When looking at the COVID-19 Pandemic period of the chart, the increase in inflation is visually a more significant increase, which suggests that the rate at which inflation increased was more drastic than compared to earlier years in this time frame (a common sentiment among the American public given numerical validation in this chart).

Effective Fed Fund Rate over time with major financial events overlayed

ggplot(df, aes(x = date, y = fund_rate, group = 1)) +
  geom_rect(data = df, aes(xmin = lag(date), xmax = date, ymin = -Inf, ymax = Inf, fill = `Major Event`), alpha = 0.2) +
  geom_line() +
  labs(x = "Year", y = "Fund Rate (%)") +
  theme_minimal() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 year") +
  scale_y_continuous(breaks = seq(0, 10, by = 1)) +
  scale_fill_manual(values = c("Dot-com Bubble Burst" = "cyan", "Global Financial Crisis" = "orange", "COVID-19 Pandemic" = "lightgreen"), 
                    name = "Major Event")
## Warning: Removed 1 rows containing missing values (`geom_rect()`).

The Major Events, do not immediately seem to provide context to the changes in the Effective Federal Fund Rate. It seems that there was a step decrease in the Fund Rate throughout the Dot-com Bubble Burst, followed by an increase. There seemed to be an increase in the Fund Rate before the Global Financial Crisis, but the around the time of the crisis the rate seemed to flat line for a bit. Then there was a period of increase before the COVID-19 Pandemic, followed by a steep decrease a little before the Pandemic formally began continuing on into the pandemic. However, at our current stage into the Pandemic we seem to be riding and increase in Fund Rates (although there could be the beginnings of a flat line, based on a small observable pattern of recent).

While, the Major Events don’t provide as much context as they did for CPI and Unemployment, looking at the metrics simultaneously will likely illuminate important takeaways with Effective Federal Fund Rate data. It’s important to note that the FED raises rate after reviewing the CPI and other data.

Unemployment Effective Fed Fund Rate over time with major financial events overlayed

# Convert 'unemployment' and 'fund_rate' to numeric if necessary
df$unemployment <- as.numeric(df$unemployment)
df$fund_rate <- as.numeric(df$fund_rate)

# Determine the ratio of maximum values between unemployment and fund_rate
ratio <- max(df$fund_rate) / max(df$unemployment)

ggplot(df, aes(x = date)) +
  geom_rect(data = df, aes(xmin = lag(date), xmax = date, ymin = -Inf, ymax = Inf, fill = `Major Event`), alpha = 0.2, show.legend = TRUE) +
  geom_line(aes(y = unemployment, color = "Unemployment Rate"), linetype = "solid", size = 1.5) +
  geom_line(aes(y = fund_rate / ratio, color = "Effective Federal Funds Rate"), linetype = "solid", size = 1.5) +
  labs(x = "Year", y = "Unemployment Rate") +
  theme_minimal() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 year") +
  scale_y_continuous(name = "Unemployment Rate", breaks = seq(0, 20, by = 1), 
                     sec.axis = sec_axis(~.*ratio, breaks = seq(0, 8, by = 1), name = "Effective Federal Funds Rate")) +
  scale_color_manual(values = c("blue", "black"), name = "Measure", 
                     labels = c("Effective Federal Funds Rate","Unemployment Rate"), guide = guide_legend(na.rm = TRUE)) +
  scale_fill_manual(values = c("Dot-com Bubble Burst" = "cyan", "Global Financial Crisis" = "orange", "COVID-19 Pandemic" = "lightgreen"), 
                    name = "Major Event") +
  theme(legend.position = "right")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Removed 1 rows containing missing values (`geom_rect()`).

When looking at Effective Federal Fund Rates and Unemployment on the same chart, I believe that it is fair to make the general statement that the two seem to have inverse relationship (when unemployment experiences an increase the effective federal fund rate seems to decrease and vice versa).

ggplot(df, aes(x = fund_rate, y = unemployment)) +
  geom_point() +
  labs(x = "Federal Funds Rate", y = "Unemployment Rate") +
  ggtitle("Scatterplot of Federal Funds Rate vs. Unemployment Rate") + 
  geom_smooth(method = "lm")
## `geom_smooth()` using formula = 'y ~ x'

If it was not immediately clear, here is a scatter plot with fund rate values on the x-axis and unemployment values on the y-axis. There seems to be a slight downward slope (that inverse relationship I mentioned earlier.)

summary(lm(unemployment ~ fund_rate,df))
## 
## Call:
## lm(formula = unemployment ~ fund_rate, data = df)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.2959 -1.2109 -0.0110  0.7333  7.6508 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  6.77660    0.12544   54.02   <2e-16 ***
## fund_rate   -0.54746    0.04303  -12.72   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.579 on 310 degrees of freedom
## Multiple R-squared:  0.3431, Adjusted R-squared:  0.3409 
## F-statistic: 161.9 on 1 and 310 DF,  p-value: < 2.2e-16

Here is the result of a very basic linear model I ran using fund_rate as the explanatory variable for unemployment. If you look at the fund_rate coefficient, there does seem to be an inverse relationship present (a coefficient of -0.54746, indicating that for every one percent decrease in unemployment there is an increase of 0.54746 in the effective federal fund rate) While this model can most definitely be improved (work could be done with the points that have a small fed rate as that may be messing with the model given that the fed fund rate cannot go below zero percent, but unemployment has more room to increase), it is worth noting that p-value and t-value here both lend themselves to the interpretation of statistical significance. I do acknowledge the R-Squared indicates that much of the variability cannot be explaned by the model and again, with further model development, I think this relationship could likely become clearer.

Consumer Price Index vs Unemployment over time with major financial events overlayed

# Determine the ratio of maximum values between cpi_value and unemployment
ratio <- max(df$cpi_value) / max(df$unemployment)

ggplot(df, aes(x = date)) +
  geom_rect(data = df, aes(xmin = lag(date), xmax = date, ymin = -Inf, ymax = Inf, fill = `Major Event`), alpha = 0.2, show.legend = TRUE) +
  geom_line(aes(y = unemployment * ratio, color = "Unemployment Rate"), linetype = "solid", size = 1.5) +
  geom_line(aes(y = cpi_value, color = "CPI Value"), linetype = "solid", size = 1.5) +
  labs(x = "Year", y = "CPI Value") +
  theme_minimal() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 year") +
  scale_y_continuous(name = "CPI Value", breaks = seq(0, max(df$cpi_value), by = 50), 
                     sec.axis = sec_axis(~./ratio, breaks = seq(0, max(df$unemployment), by = 1), name = "Unemployment Rate")) +
  scale_color_manual(values = c("red", "black"), name = "Measure", labels = c("CPI Value", "Unemployment Rate")) +
  scale_fill_manual(values = c("Dot-com Bubble Burst" = "cyan", "Global Financial Crisis" = "orange", "COVID-19 Pandemic" = "lightgreen"), 
                    name = "Major Event") +
  theme(legend.position = "right")
## Warning: Removed 1 rows containing missing values (`geom_rect()`).

While there seemed to be an albeit subtle relationship present between Unemployment rates and Effective Federal Fund rates, there does not seem to any overall trend between Unemployment and The Consumer Price Index. It generally seems that despite what unemployment is Inflation either steadily or drastically (see the beginning of the COVID-19 Pandemic) increases.

Consumer Price Index vs Effective Federal Fund Rate over time with major financial events overlayed

# Determine the ratio of maximum values between cpi_value and unemployment
ratio <- max(df$cpi_value) / max(df$fund_rate)

ggplot(df, aes(x = date)) +
  geom_rect(data = df, aes(xmin = lag(date), xmax = date, ymin = -Inf, ymax = Inf, fill = `Major Event`), alpha = 0.2, show.legend = TRUE) +
  geom_line(aes(y = fund_rate * ratio, color = "Effective Federal Fund Rate"), linetype = "solid", size = 1.5) +
  geom_line(aes(y = cpi_value, color = "CPI Value"), linetype = "solid", size = 1.5) +
  labs(x = "Year", y = "CPI Value") +
  theme_minimal() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 year") +
  scale_y_continuous(name = "CPI Value", breaks = seq(0, max(df$cpi_value), by = 50), 
                     sec.axis = sec_axis(~./ratio, breaks = seq(0, max(df$unemployment), by = 1), name = "Effective Federal Fund Rate")) +
  scale_color_manual(values = c("red", "blue"), name = "Measure", labels = c("CPI Value", "Effective Federal Fund Rate"), na.value = "transparent") +
  scale_fill_manual(values = c("Dot-com Bubble Burst" = "cyan", "Global Financial Crisis" = "orange", "COVID-19 Pandemic" = "lightgreen"), 
                    name = "Major Event") +
  theme(legend.position = "right")
## Warning: Removed 1 rows containing missing values (`geom_rect()`).

There are two points on the chart that I highlighted on the graph of Consumer Price Index values (the beginning of the COVID-19 Pandemic and the beginning of the Global Financial Crisis) where there was a brief decrease in CPI values which coincided with the beginning of Major Events that impacted the economy. Based on this graph, it seems that for a period leading up to the Global Financial Crisis and the COVID-19 Pandemic there was a rapid dropping of rates, which coincided with brief periods of decrease in CPI values; however, after that brief the CPI began it’s steady state of increase yet again.

Summary:

It is important to note that the FED raises rate after reviewing the CPI and other data, and based on the chart showing Effective Federal Fund Rate data vs CPI data I am not entirely sure what salient features of the CPI data help the FED make decisions about the interest rate. Again, this graph just shows The Consumer Price Index steadily increasing with major fluctuations in the Effective Federal Fund Rates. It does not seem that the changes in the Federal Fund rate have much impact on unemployment.

The initial question I posed was: “Has the FED been able to fulfill the mandate given to it by Congress?” As a reminder, that mandate was to manage inflation and ensure low levels of unemployment.

Based on this data, it does not seem like the FED has much of an impact on inflation. Truthfully, it seems that inflation is just a constant (based on the data and personal experience). Though, I will note that it does seem the Effective Federal Fund Rates and Unemployment display some sort of more meaningful relationship.

Further analysis could look at the derivative associated with the Consumer Price Index values, to see if the change in CPI values over time was impacted by the Changes in Effective Federal Fund Rates; however, given the my time constraints, I cannot conduct that analysis.