In this paper we will be forecasting U.S. inflation over a 12 month period. We will start with the Stock and Watson (1999) equation for determining inflation. This model revolves around the conventional Philips curve using unemployment and price level. We will be using a training sample from January 1982 to December 2018 and measuring our forecast against actual data from January 2019 to the most recently available data. All data will be retrieved from the Federal Reserve Economic Database in St. Louis.
The full RMD code and project file is available through my GitHub website’s project page using the following link:
For this analysis we will be retrieving the data from the Federal
Reserve Economic Database in St. Louis (FRED). The data we will be using
are: Personal Consumption Expenditures price index, chained to 2012,
seasonally adjusted, measured in percent PCEPI
. We also
will be using monthly seasonally adjusted Unemployment Rate, measured in
percent UNRATE
. Non-seasonally adjusted one-year expected
inflation rate, measured in percent EXPINF1YR
. Seasonally
adjusted total capacity utilization per month, measured in percents
TCU
. Seasonally adjusted new privately-owned housing units
starts, measured in thousands of units per month HOUST
.
Variables <- c("PCEPI", "UNRATE", "EXPINF1YR", "TCU", "HOUST")
FRED_Inflation <- tq_get(Variables,get="economic.data",from= "1982-01-01") %>%
mutate(Date = yearmonth(date), value = price) %>%
select(-c(date, price)) %>%
as_tsibble(index = Date, key = symbol)
FRED_Inflation_wide <- FRED_Inflation %>%
pivot_wider(names_from = symbol, values_from = value) %>%
as_tsibble()
We will use this data to try and model inflation in the United States, based on the equations used to specify the Philips curve.
The Stock and Watson (1999) specification of the Philips curve is the following:
\[\begin{align*} \pi^{12}_t - \pi_{t-12} = \phi + \beta(B)\Delta\pi_{t-12} + \gamma(B)u_{t-12} + \varepsilon_{t} \end{align*}\]
The purpose of using PCEPI
is inherent, the change in
the PCEPI
is known for capturing inflation (or deflation)
across a wide range of consumer expenses. After transforming the data
PCEPI
will represent the level of inflation in the economy
according to the left hand side of the above equation. The left hand
side of the equation simply states the difference between the percentage
change of price level PCEPI
from a year ago to the current
observed time (\(\pi^{12}_t\)) minus
the percentage change of the 13th month lag to the 12th month lag (\(\pi_{t-12}\)), annualized.
There is a fundamental connection between the unemployment rate and
the amount of inflation in the economy. The Philips curve is a concave
slope demonstrating the relationship between the two variables. In the
equation above (\(u_t\)) represents the
annualized unemployment rate measured by UNRATE
. After a
lot of math, the equation above reduces and allows us to conclude the
one-year ahead steady-state inflation rate is equal to a constant (\(\phi\)) + the steady-state of inflation + a
polynomial effect (\(\gamma^{*}\)) from
past values of lagged unemployment (12 months) multiplied by the
steady-state of unemployment. A basic interpretation of the Philips
curve is at lower levels of unemployment, a deviation from the
steady-state of unemployment, signals that firms are increasing their
demand for labor. An increase demand for labor drives wages up and
companies off-set their increased costs by passing them on to consumers
through higher prices for their products. The increase in costs for the
same products is inflation.
However, the economy is more complex then this interpretation and
more variables and/or models are needed to further predict how inflation
will change in the future. For each variable we will be constructing a
model using the same Philips curve equation substituting (\(u_t\)) for each variable:
TCU
,HOUST
, and EXPINF1YR
.
The purpose for using the number of new housing starts,
HOUST
, is often associated with inflation primarily through
interest rates. If the Federal Reserve raises interest rates due to
inflation, then permits for new housing developments will decrease. With
less permits being issued new housing starts are going to decline. High
interest rates also lead to an increase in the cost of borrowing. This
increased costs of borrowing leads to less individuals willing to take
out a loan to buy a new house. With less borrowing occurring in the
market and less houses being developed housing prices will rise.
The variable coded TCU
is included due to the simple
theory that demonstrates how inflation occurs from the relationship
between the amount of money and the amount of goods in an economy. The
simple example is an economy with only 10 dollars and 10 bananas. From
the ratio of goods to money we can establish that one banana is worth
one dollar. If another $10 is introduced into the economy, the value of
the dollar is now worth half a banana only if the amount of bananas
available does not change, i.e. inflation. TCU
is the
amount of bananas we can produce this hypothetical economy we previously
mentioned. If the total amount of capital being used by corporations and
factories is approaching 100 percent, then the amount of goods that can
be produced is reaching its upper bound. When the money supply is
increasing and new goods are not being added to the economy inflation
will occur.
The data we gathered from FRED is not stationary data, non-stationary data makes effective time series analysis practically impossible. Non-stationary time series data will have a systematic changes that are unpredictable. Non-stationary data has a stochastic trend, also referred to as “a random walk with a drift”.. Time series data is comprised of three components: a trend/cycle component, a seasonal component, and a remainder component. Running time series forecasts on non-stationary data results in too much noise in the model due to trend and seasonality components. Leaving trends and seasonally components in the data reduces the ability to find signals in the models that provide actual information for our interpretation. Transforming the data will decompose the trend and seasonality components leaving the remainder. If we can produce a remainder that is comprised of white noise, we know our forecast is accurate.
In our data we have several variables with trends and seasonality. A tool we will use to generate stationary data from non-stationary data is differencing. Differencing is simply computing the differences between consecutive observations. Another technique we will be utilizing is converting level data to the natural logarithm of that same data. Using logs in tandem with differencing will transition our consecutive observations into month over month percent changes. This will help stabilize the variance of our time series, leading to a approximately normal distribution of our error terms. A quick scan of the raw data shows that no observations are equal to zero or have negative values which allows us to transform the data using logarithms. If our raw data had negative or zero values a power transformation would be needed.
We will be using the KPSS
unit root test to test for the
number of unit roots we have in each variable. Unit root tests will tell
us if our data is stationary or not. For variables that we find to have
a unit root, we will be transforming those variable accordingly
converting them to stationary data. There seems to be a single unit root
in all of our variables. We will then apply the rules discussed above
and transform each variable in the data accordingly.
FRED_Inflation_final <- FRED_Inflation_wide %>%
select(c(PCEPI, UNRATE, EXPINF1YR ,TCU, HOUST)) %>%
mutate(infl = 1200*log(PCEPI/lag(PCEPI))) %>%
mutate(dinfl = infl - lag(infl,1)) %>%
mutate(dinfl12 = 100*log(PCEPI/lag(PCEPI,12)) - lag(infl,12)) %>%
mutate(unrate = UNRATE - lag(UNRATE)) %>%
mutate(dinflexp = EXPINF1YR - lag(EXPINF1YR)) %>%
mutate(tcu = TCU - lag(TCU)) %>%
mutate(houst = 100*log(HOUST/lag(HOUST))) %>%
select(-c(PCEPI, UNRATE, EXPINF1YR ,TCU, HOUST)) %>%
drop_na()
train_data <- FRED_Inflation_final %>% filter_index(~ "2019-12")
test_data <- FRED_Inflation_final %>% filter_index("2020-01" ~ .)
Now that the data is stationary we can begin our analysis. We will be
using basic Time Series Linear Models from the FPP3
package.
The following specifications for our four models is listed in the code below:
fit <- train_data %>%
model(
mUMP = TSLM(dinfl12 ~ 1 +
lag(dinfl,12) + lag(dinfl,13) + lag(dinfl,14) +
lag(dinfl,15) + lag(dinfl,16) + lag(dinfl,17) +
lag(dinfl,18) + lag(dinfl,19) + lag(dinfl,20) +
lag(dinfl,21) + lag(dinfl,22) + lag(dinfl,23) +
lag(unrate,12) + lag(unrate,13) + lag(unrate,14) +
lag(unrate,15) + lag(unrate,16) + lag(unrate,17) +
lag(unrate,18) + lag(unrate,19) + lag(unrate,20) +
lag(unrate,21) + lag(unrate,22) + lag(unrate,23)
),
mTCU = TSLM(dinfl12 ~ 1 +
lag(dinfl,12) + lag(dinfl,13) + lag(dinfl,14) +
lag(dinfl,15) + lag(dinfl,16) + lag(dinfl,17) +
lag(dinfl,18) + lag(dinfl,19) + lag(dinfl,20) +
lag(dinfl,21) + lag(dinfl,22) + lag(dinfl,23) +
lag(tcu,12) + lag(tcu,13) + lag(tcu,14) +
lag(tcu,15) + lag(tcu,16) + lag(tcu,17) +
lag(tcu,18) + lag(tcu,19) + lag(tcu,20) +
lag(tcu,21) + lag(tcu,22) + lag(tcu,23)
),
mHOUST = TSLM(dinfl12 ~ 1 +
lag(dinfl,12) + lag(dinfl,13) + lag(dinfl,14) +
lag(dinfl,15) + lag(dinfl,16) + lag(dinfl,17) +
lag(dinfl,18) + lag(dinfl,19) + lag(dinfl,20) +
lag(dinfl,21) + lag(dinfl,22) + lag(dinfl,23) +
lag(houst,12) + lag(houst,13) + lag(houst,14) +
lag(houst,15) + lag(houst,16) + lag(houst,17) +
lag(houst,18) + lag(houst,19) + lag(houst,20) +
lag(houst,21) + lag(houst,22) + lag(houst,23)
),
mdinflexp = TSLM(dinfl12 ~ 1 +
lag(dinfl,12) + lag(dinfl,13) + lag(dinfl,14) +
lag(dinfl,15) + lag(dinfl,16) + lag(dinfl,17) +
lag(dinfl,18) + lag(dinfl,19) + lag(dinfl,20) +
lag(dinfl,21) + lag(dinfl,22) + lag(dinfl,23) +
lag(dinflexp,12) + lag(dinflexp,13) + lag(dinflexp,14) +
lag(dinflexp,15) + lag(dinflexp,16) + lag(dinflexp,17) +
lag(dinflexp,18) + lag(dinflexp,19) + lag(dinflexp,20) +
lag(dinflexp,21) + lag(dinflexp,22) + lag(dinflexp,23)
)
)
accuracy(fit) %>%
select(c(".model",".type" ,"MAPE")) %>%
kable(format = "html", table.attr = "style='width:30%;' ") %>%
kableExtra::kable_styling()
.model | .type | MAPE |
---|---|---|
mUMP | Training | 194.3407 |
mTCU | Training | 184.0923 |
mHOUST | Training | 204.2391 |
mdinflexp | Training | 214.9833 |
The table above is given by the accuracy
command. This
command returns a list of summary measures from the four forecasting
models we used. The measurement we are most interested in is the mean
absolute percentage error (MAPE). This displays the absolute value of
the average difference between our forecast values minus the actual
values for each time period, converted to a percentage. The model with
the lowest MAPE determines which model most accurately forecasted
inflation. Out of the four models we used so far, the TCU
variable model has predicted future inflation the most accurately.
Although our the TCU
model performed the best out of the
bunch, this does not allow us to conclude that this model is useful. By
observing the residual plot below generated from the
gg_tsrediduals
command, we can see our residuals are not
white noise. The Auto-correlation function plot (acf) shows that our
model has serious serial correlation in our error terms. A shock in the
first error term persist as a geometric decomposition up until the 7th
lagged period. Even after the 7th lagged period the model seems to over
correct and continue to have serial correlation until the 16th lagged
period. A correlated shock in the error term seems to affect our model
for about 16 months or about a year and a half after the shock occurred.
This is far from a good model to forecast future inflation.
fit %>% select(mTCU) %>% gg_tsresiduals()
The graph below shows each model forecast of inflation from 2020 to 2022 with a 95% confidence level surrounding each forecast:
fc_fit <- fit %>% forecast(new_data = test_data)
fc_fit %>% autoplot(filter(FRED_Inflation_final, year(Date) > 2016), level = c(95)) +
labs(caption = "Time Series data taken from FRED database", aes(color = "blue")) +
labs(title = "Time Series Linear Model Forecast") +
ylab("Inflation Rate (measured in percent)") +
xlab("Month")
Looking at the graph of our four forecasted models compared to the
actual data, visually we find that all of the models fail at certain
aspects of their forecasting ability. The TCU
model fails
to forecast a large spike in inflation our 2021 and over corrects by
over estimating inflation around August of 2021. However, it does
comparatively do the best job at sticking around the higher rates of
inflation after it’s over prediction. EXPINF1YR
and
HOUST
do a good job at keeping up with the large spike in
2021, however they fail to catch up to the higher levels of inflation
around August of 2021 and under predict inflation for the remainder of
the test data window. The unemployment model UMP
is all
over the place. From under predicting to over predicted back to under
predicting, the model does not seem to know how to deal with inflation.
This is not all the models fault the U.S. is currently experiencing
distorted levels of unemployment and the job market is radically
changing in unprecedented ways.
We observe that the models working independently from each other all fail to accurately forecast inflation from 2020 to 2022. But what if the models could work with each other. How accurately would the average of all four of these models forecast inflation? In the next section we will create an ensemble model which consists of the average of all four models in one.
The code below shows how will use the combo
function to
take the average of all four models and construct an ensemble model:
Ensemble <- fit %>% transmute(combo = (mUMP + mTCU + mHOUST + mdinflexp)/4)
fit_all <- fit %>%
mutate(combo = (mUMP + mTCU + mHOUST + mdinflexp)/4)
fc_fit_all <- fit_all %>% forecast(new_data = test_data)
fc_fit_all %>% autoplot(filter(FRED_Inflation_final, year(Date) > 2016), level = c(95)) +
labs(caption = "Time Series data taken from FRED database", aes(color = "blue")) +
labs(title = "Time Series Linear Model Forecast") +
ylab("Inflation Rate (measured in percent)") +
xlab("Month")
We can observe that this model, similar to the TCU
model, fails to forecast the large spike in 2021. However, unlike
TCU
it does not over correct and predict higher levels of
inflation than the data actual shows. Comparatively, this model performs
the best at keeping up with the high levels of inflation after the first
large initial spike in 2021.
We can now observe the accuracy measures of all five models:
accuracy(fit_all) %>%
select(c(".model",".type" ,"MAPE")) %>%
kable(format = "html", table.attr = "style='width:30%;' ") %>%
kableExtra::kable_styling()
.model | .type | MAPE |
---|---|---|
mUMP | Training | 194.3407 |
mTCU | Training | 184.0923 |
mHOUST | Training | 204.2391 |
mdinflexp | Training | 214.9833 |
combo | Training | 179.9584 |
It seems that while in sample the ensemble model out performed the other four models. The ensemble model has the lowest MAPE compared to the other four models. However, the model is still not perfect. For the true test we can observe how the models performed out of sample.
The following table displays the accuracy measures of the five models while out of sample:
accuracy(fc_fit_all, FRED_Inflation_final) %>%
select(c(".model",".type" ,"MAPE", "MAE")) %>%
kable(format = "html", table.attr = "style='width:30%;' ") %>%
kableExtra::kable_styling()
.model | .type | MAPE | MAE |
---|---|---|---|
combo | Test | 102.8146 | 1.547044 |
mdinflexp | Test | 121.1259 | 1.634609 |
mHOUST | Test | 136.2762 | 1.768383 |
mTCU | Test | 102.5024 | 1.631720 |
mUMP | Test | 172.5794 | 2.774931 |
At first glance it seems that the ensemble model did not perform the
best while out of sample. By a very slim margin, the TCU
model has a smaller MAPE compared to the ensemble model. This could be
due to the improvement of adding in EXPINF1YR
,
HOUST
, and UMP
for certain aspects not
outweighing the harm that the same models had. Another metric we can
observe to test the ensemble more fairly would be the mean absolute
error (MAE). Similar to the MAPE the MAE measures the difference between
predicted values and observed values, however it is not converted to a
percent. This means that when using MAPE larger differences in the
errors are weighted higher. Adding wildly inaccurate models like
UMP
could be why we see a larger MAPE score then predicted
in the ensemble model. When observing the MAE we find that the ensemble
has a the smallest MAE value by a wider margin then MAPE.
It seems that the ensemble model did preform the best in sample and out of sample. However, all five of these models still leave something to be desired. Forecasting inflation is very difficult and the models in this paper were not equip to handle such a complex task. A basic time series linear model might be able to forecast inflation for the next year, however our specification for this model was not sufficient. We will look to better specify our models and add other relevant variables that effect future levels of inflation.