Module 3 Discussion

Author

Teddy Kelly

1 Part 1: Choosing 3 Time Series from Fred

I have selected to analyze the following three Fred Monthly time series:

  1. 10-Year Treasury Constant Maturity Minus 2-Year Treasury Constant Maturity (T10Y2YM)
  2. The US Unemployment Rate (UNRATE)
  3. The US CPI for All Urban Consumers (CPIAUCSL)

Clearing Environment and Setting up Fred API Key

library(fpp3)
library(fredr)

# Clear Environment
rm(list=ls())

# Specifying the Fred API key
fredr_set_key(Sys.getenv("fred_api_key"))

Loading the time series data

#T10Y2YM
treasury <- fredr(series_id = "T10Y2YM",
                  observation_start = as.Date("2000-01-01"),
                  observation_end = as.Date("2026-01-01"))
treasury <- treasury |>
  mutate(Month = yearmonth(date)) |>
  select(Month, value) |>
  as_tsibble(index = Month)

# Unemployment Rate
unrate <- fredr(series_id = "UNRATE",
                  observation_start = as.Date("2000-01-01"),
                  observation_end = as.Date("2025-12-01"))
unrate <- unrate |>
  mutate(Month = yearmonth(date)) |>
  select(Month, value) |>
  as_tsibble(index = Month)

# CPI
cpi <- fredr(series_id = "CPIAUCSL",
             observation_start = as.Date("2000-01-01"),
             observation_end = as.Date("2025-12-01"))
cpi <- cpi |>
  mutate(Month = yearmonth(date)) |>
  select(Month, value) |>
  as_tsibble(index = Month)

Plotting the initial Time series

treasury |> autoplot(value) +
  labs(title = "T10Y2YM Time Series",
       subtitle = "Monthly Data from 2000-2026",
       x = "Time (Months)",
       y = "Percent")

Stationarity

  • Mean: From looking at the graph, it’s not super clear whether the time series is stationary. It appears to be relatively mean stationary despite the significant dip in 2023, but overall, the mean does not appear to be changing over time.

  • Variance: Again, it’s difficult to tell whether the time series is variance stationary from looking at the graph. The peaks and troughs are relatively constant except towards the end of the series where the percentage value reaches its lowest point at around 2023.

  • Covariance: The time series does not appear to be covariance stationary because of the extended cycle observed from about 2008 to about 2015 which is much longer than the one seen from about 2001 to 2004 and 2020 to 2021. Therefore, this time series will likely require differencing to become stationary.

unrate |> autoplot(value) +
  labs(title = "US Unemployment Rate",
       subtitle = "Monthly Data from Jan 2000 - Dec 2025",
       x = "Time (Months)",
       y = "Unemployment Rate (Percent)")

Stationarity

  • Mean: The unemployment rate does not change much if we just look at the starting and ending values. However, the unemployment rate appears to be trending upward until 2010, and then it begins a gradual decline, except for the massive spike during Covid. Therefore, the time series may not be mean stationary since the average unemployment rate likely differs depending on the time.

  • Variance: The series is definitely not variance stationary since peaks change height throughout the series. For example the first peak of unemployment is relatively low around 2004, the second peak around 2010 is higher, and the spike during Covid is extremely high. Therefore, the time series is not stationary and will require differencing.

  • Covariance: The time series is likely not covariance stationary because the cycles differ in length. The spike in unemployment during Covid is very short compared to the other peaks.

cpi |> autoplot(value) +
  labs(title = "Consumer Price Index for All Urban Consumers",
       subtitle = "Monthly Data from Jan 2000 - Dec 2025",
       x = "Time (Months)",
       y = "Index")

Stationarity

  • Mean: The CPI time series is clearly not mean stationary since there is a visible upward trend over time. Therefore, without even considering whether the time series is variance or covariance stationary, we can conclude that it’s not stationary, and therefore requires differencing.

  • Variance/Covariance: It’s difficult to see whether or not the time series is variance or covariance stationary because there are not many visible cycles.

Before conducting the formal KPSS tests to determine stationarity, looking at the time series for unemployment rate and CPI, we can see that there is a missing observation for October 2025 in both time series. To address this, I have imputed those missing observations with the average between the values in September of 2025 and November of 2025.

This step will be important for performing decomposition because there cannot be any NA values for decomposition to work.

# Imputing Values for Unemployment rate
unrate$value[unrate$Month == yearmonth('2025 Oct')] <-
  (unrate$value[unrate$Month == yearmonth('2025 Sep')] + unrate$value[unrate$Month == yearmonth('2025 Nov')]) / 2

# Imputing values for CPI
cpi$value[cpi$Month == yearmonth('2025 Oct')] <-
  (cpi$value[cpi$Month == yearmonth('2025 Sep')] + cpi$value[cpi$Month == yearmonth('2025 Nov')]) /2 

1.1 Formal KPSS Tests

I have decided to use the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) to determine if the time series I am analyzing are stationary or require further differencing. For the KPSS test, the null hypothesis is that the data are stationary, and if the p-value is below \(\alpha=0.05\), then we can reject the null hypothesis and conclude that the data are not stationary (Hyndman 2025).

# T10Y2YM
treasury_kpss <- treasury |> features(value, unitroot_kpss)
# Unemployment Rate
unrate_kpss <- unrate |> features(value, unitroot_kpss)
#CPI
cpi_kpss <- cpi |> features(value, unitroot_kpss)

# Summary Table
kpss_df <- data.frame(
c("Treasury", "Unemployment Rate", "CPI"),
 c(treasury_kpss[[1]], unrate_kpss[[1]], cpi_kpss[[1]]),
 c(treasury_kpss[[2]], unrate_kpss[[2]], cpi_kpss[[2]])
)

colnames(kpss_df) <- c("Time Series", "KPSS Statistic", "KPSS p-value")
kableExtra::kable(kpss_df, digits = 2)
Time Series KPSS Statistic KPSS p-value
Treasury 1.24 0.01
Unemployment Rate 0.82 0.01
CPI 4.93 0.01
  • We can see that the p-values for all three of the time series are 0.01 which is less than \(\alpha=0.05\), meaning that we can reject the null hypothesis of stationarity. Therefore, these time series are not stationary and if we want to apply forecasting methods that require stationarity, we will have to difference them before making forecasts.

2 Part 2: ACF and PACF Plots

I will now generate the ACF and PACF graphs for each of the three time series to determine the values for \(q\) and \(p\) for the auto-regressive and moving average processes. However, since none of the time series data were stationary according to the KPSS test, I will first have to difference them for the ACF and PACF graphs to tell us anything meaningful.

2.1 Differencing the Data

To difference the data, I have used the difference() command and added a column to each of the time series containing the differenced values. The differenced values are calculated by taking the actual value in the current period and subtracting from it the actual value in the previous period. Hence, the first observation will not have a differenced value for any of the time series since the value before the first observation is unobserved.

library(kableExtra)

# Treasury Data Difference
treasury <- treasury |>
  mutate(diff_value = difference(value))

# Unemployment Rate Difference
unrate <- unrate |> mutate(diff_value = difference(value))

# CPI Difference
cpi <- cpi |> mutate(diff_value = difference(value))


# Using KPAA Test to determine if the differenced data is stationary
kpss_diff <- data.frame(
  c("Treasury Differenced", "Unemployment Rate Differenced", "CPI Differenced"),
  c((treasury |> features(diff_value, unitroot_kpss))[[2]],
    (unrate |> features(diff_value, unitroot_kpss))[[2]],
    (cpi |> features(diff_value, unitroot_kpss))[[2]])
)
colnames(kpss_diff) <- c("Time Series", "KPSS p-value")

kable(kpss_diff, digits = 2)
Time Series KPSS p-value
Treasury Differenced 0.10
Unemployment Rate Differenced 0.10
CPI Differenced 0.01
  • According to the KPSS test, the treasury and unemployment rate time series are now stationary since their p-values are 0.10 which are greater than \(\alpha=0.05\), meaning we fail to reject the null hypothesis of stationarity.

  • However, the p-value for the CPI time-series is 0.01 < 0.05, meaning we can reject the null hypothesis, and conclude that the time series still is not stationary. Therefore, I will have to compute the 2nd difference to make the time series stationary and ready for applying the ACF and PACF graphs.

# Computing the 2nd difference for the CPI time series
cpi <- cpi |> mutate(diff2_value = difference(diff_value))
cpi |> features(diff2_value, unitroot_kpss)
# A tibble: 1 × 2
  kpss_stat kpss_pvalue
      <dbl>       <dbl>
1    0.0132         0.1
  • This confirms that the 2nd difference of the CPI is indeed stationary and we will use that for the ACF and PACF plots.

2.2 ACF and PACF Graphs

Treasury

  • First, I will plot the ACF and PACF graphs for the T10Y2YM time series and determine which process (AR, MA, or ARMA) is the most appropriate.

  • I have loaded in the patchwork library to display the ACF and PACF graphs side by side for direct comparison

library(patchwork)
# T10Y2YM ACF and PACF Graphs
treas_acf <- treasury |> ACF(diff_value) |> autoplot() +
  labs(title = "T10Y2YM Differenced ACF Graph", x = "Lag", y = "ACF")
treas_pacf <- treasury |> PACF(diff_value) |> autoplot() +
  labs(title = "T10Y2YM Difference PACF Graph", x = "Lag", y = "PACF")

# Unemployment Rate ACF and PACF Graphs
unrate_acf <- unrate |> ACF(diff_value) |> autoplot() +
  labs(title = "Unrate Differenced ACF Graph", x = "Lag", y = "ACF")

unrate_pacf <- unrate |> PACF(diff_value) |> autoplot() +
  labs(title = "Unrate Differenced PACF Graph", x = "Lag", y = "ACF")

# CPI ACF and PACF Graphs
cpi_acf <- cpi |> ACF(diff2_value) |> autoplot() +
  labs(title = "CPI Differenced ACF Graph", x = "Lag", y = "ACF")

cpi_pacf <- cpi |> PACF(diff2_value) |> autoplot() +
  labs(title = "CPI Differenced PACF Graph", x = "Lag", y = "ACF")

# Graphing all of the ACF and PACF graphs together using patchwork
(treas_acf | treas_pacf) / (unrate_acf | unrate_pacf) / (cpi_acf | cpi_pacf)

Analysis

  • T10Y2YM:

    • ACF Graph: There is no clear cutoff, but instead a gradual decline in the ACF values after lag 1. This rules out a moving average process, and whether the appropriate process is an auto-regressive or ARMA depends on the PACF graph.

    • PACF Graph: Again, there is no clear cutoff and the PACF values gradually decline after lag 1, suggesting that an ARMA process would be the most appropriate given what we found for the ACF plot.

    • Specifically, an ARMA(1,1) would probably make the most sense because the first lags clearly have the highest autocorrelation with the present values in both graphs, and then start to gradually decline afterward.

  • Unemployment Rate:

    • ACF Graph: It appears that none of the lags have any significant ACF values, but instead, all of them remain either within or right at the dashed blue line.
    • PACF Graph: Similarly, none of the lags have any significant PACF values.
    • Since none of the lags have any significant ACF or PACF values, this implies white noise which is equivalent to an ARMA(0,0) process.
  • CPI:

    • ACF Graph: It appears that lags 1 and 2 have significant ACF values (especially lag 2) and then the ACF value drops significantly for the remaining lags. This would suggest a process of MA(2).

    • PACF Graph: There is a gradual decay in the PACF value after the 2nd lag, suggesting that this is a moving average process.

    • Hence, the AFC and PACF graphs provide evidence that supports an MA(2) process.

3 Part 3: Decomposition Methods

I will apply the STL decomposition method on all three of the original time series graphs to understand how the trend-cycle, seasonality, and remainder components contribute to the overall time series.

I have decided to use the default windows of window = 21 and window = 11 for the trend and seasonal components respectively.

# T10Y2YM Time Series
treas_decomp <- treasury |> 
  model(STL(value ~ trend() + season())) |>
  components() |> autoplot() + labs(title = "T10Y2YM STL Decomposition", x = "Time (Months)")

# Unemployment RAte Time Series
unrate_decomp <- unrate |>
  model(STL(value ~ trend() + season())) |>
  components() |> autoplot() + labs(title = "Unemployment STL Decomposition", x = "Time (Months)")

# CPi Time Series
cpi_decomp <- cpi |> 
  model(STL(value ~ trend() + season())) |>
  components() |> autoplot() + labs(title = "CPI STL Decomposition", x = "Time (Months)")

Plotting the Treasury Decomposition

treas_decomp

  • Trend: The trend component appears to remain relatively constant over time.

  • Seasonal: The seasonal component clearly shows that the variance changes depending on time with the variance being very small between 2010 and 2020 compared to the other time periods. This confirms what we found previously when running the KPSS test that the time series is not stationary.

  • Remainder: The remainder component appears to be fairly random and does not show any clear patterns throughout the time series.

Unemployment Rate Decomposition

unrate_decomp

  • Trend: There appears to be an initial upward trend in the unemployment rate until about 2010, and then the unemployment rate begins to decrease slightly despite the significant spike in 2020. The trend component also shows that unemployment was greater during the Great Recession than during Covid, whereas the original time series clearly shows that unemployment during Covid reached much higher levels. This suggests that much of the unemployment during Covid is captured in the seasonality and remainder component.

  • Seasonal: The seasonal component clearly displays high levels of seasonality towards the end of the series compared to very low levels at the beginning. This demonstrates that the variance changes over time, and therefore, the unemployment time series is not stationary, confirming what we found with the KPSS test.

  • Remainder: The remainder component is not random and increases greatly during 2020. Also the length of the cycles seen in the remainder component are much longer towards the end of the series, indicating that the time series is not covariance stationary.

CPI Decomposition

cpi_decomp

  • Trend: There is a visible upward trend in the CPI time series, suggesting that the is changing over time. Therefore, this time series is not stationary because it is not mean stationary.

  • Seasonal: Seasonality changes over time with higher levels of seasonality at the beginning and lower levels towards the end. This implies that the time series is not variance stationary, and it is also not covariance stationary since cycle length appears to be much shorter in years towards the end.

  • Remainder: The remainder component appears to be fairly random and doesn’t show any clear trends. However, there are some spikes that the remainder picks up around the Great Recession and Covid.

References

Hyndman, Rob. 2025. “Fpp3: Data for "Forecasting: Principles and Practice" (3rd Edition).” https://doi.org/10.32614/CRAN.package.fpp3.