Predicting Post-Pandemic Consumption Behaviors Given Potential Paths of Monetary Policy, Part 1 -- Economic Theories, Structural Breaks and Stationarity

It’s official on Nov.3rd that the Federal Reserve is winding down its aggressive pandemic-era asset purchasing, or as what we call “tapering”. The announcement actually didn’t come out as a surprise since Fed officials had been laying the groundwork for policy tightening as the economy gradually recovered from the Covid19 slump. Furthermore, due to the lately surging inflation readings, we are seeing traders start to bet on faster Fed hikes. So it seems interesting to get some ideas of how the post-pandemic consumption behaviors of the American households would be, and quantify the potential impact of policy shifting on these behaviors using two classic time series models - the Vector Autoregression (VAR) and the Vector Error Correction Model (VECM). Tribute to Christopher Sims who first promoted the use of VAR in empirical macroeconomics.

We will build the models following the steps below,

And before we get onto that, I will introduce some of the economic theories behind predicting real consumption.

Economic Intuitions Behind

Let’s first recap some of the main determinants of private consumption,

  • Disposable Income (in the current period) (+)
  • Expectations (consumer confidence indexes, employment growth) (+)
  • Wealth (stock market performance, housing prices) (+)
  • Uncertainty (precautionary saving) (-)
  • Availability of Credit (+)
  • Real Interest Rate (?)
        - Substitution effect (-)
        - Income effect (+)

You might remember that perhaps the simplest theories of consumption, Keynesian theories of consumption, would link private consumption to disposable income in the current period, and of course positively. So as disposable income goes up, private consumption is expected to go up as well. More sophisticated, forward looking models would give more prominence or link consumption not to disposable income today but to expectations about the evolution of disposable income in the future. A number of indicators that can be used to gauge these expectations, for example, consumer confidence index, employment growth or my personally preferred - yield spreads. These can be used as a proxy for expectations for future income.

Another variable thought to be important by economists in terms of explaining the behavior of private consumption is wealth. In that sense, for this wealth channel, the performance of the stock market or even the behavior of housing prices, would have an impact on private consumption.

Another important factor is uncertainty, which would actually have a negative association with private consumption because it would create incentives for what the economists called precautionary saving. So agents will try to create a buffer to be able to fall back on in case a bad shock hits.

The availability of credit is also thought to be an important determinant with, obviously, a positive relationship.

And finally, the real interest rate is also thought to be one of the important determinants of private consumption.
The effect of real interest on consumption can be a little bit ambiguous. Perhaps the most straightforward or direct effect
would be what economists call a substitution effect where, as interest rates go up, the incentives to consume are reduced. So agents will have, on the contrary, incentives to save more. And therefore, an increase in rates will be linked to a decrease in private consumption. But on the other hand, it is also possible that if consumers are net savers the increase in interest rates
might generate some additional income– what economists call an income effect–and this might actually lead to an increase in private consumption.

Getting Data

Based on the economic intuitions, I grabbed the following well-defined and organized economic data from the Federal Reserve Economic Data of the St. Louis Fed. The FRED allows modelers to write programs and build applications that retrieve data through an API provided. There is a list of packages/libraries that one can turn to, depending on one’s programming language. Since I have to model everything in Python as it’s compatible with the github blog, I use the library fredapi to request the data. However, in practice, I would probably go for EViews as most economists prefer and do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np
from fredapi import Fred
import matplotlib.pyplot as plt
from datetime import datetime
%matplotlib inline

import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.stattools import kpss
from statsmodels.tsa.api import VAR
from statsmodels.tsa.vector_ar import vecm
from statsmodels.tsa import filters

pd.options.display.max_colwidth = 50
pd.options.display.max_rows = 400
1
fred = Fred(api_key=your_key_string)

The period I study is from 1962Q1 to 2021Q2, which ensures that the economic data that I am interested in all has a value.

1
2
startDate = '1962-01-01'
endDate = '2021-04-01'
1
2
3
4
def get_series_and_print_info(fred, series_id, startDate, endDate, name):
    series = fred.get_series(series_id, observation_start=startDate, observation_end=endDate, frequency='q')
    print(name+': '+fred.get_series_info(series_id).title)
    return series
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
data = {}
data['saving_rate'] = get_series_and_print_info(fred, 'a072rc1q156sbea',startDate, endDate, 'saving_rate')
data['ns'] = get_series_and_print_info(fred, 'w986rc1q027sbea',startDate, endDate, 'ns')
data['nnw'] = get_series_and_print_info(fred, 'TNWBSHNO',startDate, endDate, 'nnw')
data['nl'] = get_series_and_print_info(fred, 'TCMILBSHNO',startDate, endDate, 'nl')
data['na'] = get_series_and_print_info(fred, 'TABSHNO',startDate, endDate, 'na')
data['nfa'] = get_series_and_print_info(fred, 'TFAABSHNO',startDate, endDate, 'nfa')
data['interest_payments'] = get_series_and_print_info(fred, 'b069rc1q027sbea',startDate, endDate, 'interest_payments')
data['cpi'] = get_series_and_print_info(fred, 'cpiaucsl',startDate, endDate, 'cpi')
data['c_deflator'] = get_series_and_print_info(fred, 'dpcerd3q086sbea',startDate, endDate, 'c_deflator')/100
data['dff'] = get_series_and_print_info(fred, 'DFF',startDate, endDate, 'dff')
data['yield_3m'] = get_series_and_print_info(fred, 'dgs3',startDate, endDate, 'yield_3m')
data['yield_10y'] = get_series_and_print_info(fred, 'irltlt01usq156n',startDate, endDate, 'yield_10y')
data['prime_rate'] = get_series_and_print_info(fred, 'dprime',startDate, endDate, 'prime_rate')
data['m2'] = get_series_and_print_info(fred, 'M2REAL',startDate, endDate, 'm2')
data['ndy'] = get_series_and_print_info(fred, 'dpi',startDate, endDate, 'ndy')
data['rdy'] = get_series_and_print_info(fred, 'dpic96',startDate, endDate, 'rdy')
data['npiy'] = get_series_and_print_info(fred, 'b703rc1q027sbea',startDate, endDate, 'npiy')
data['ny'] = get_series_and_print_info(fred, 'pincome',startDate, endDate, 'ny')
data['ry'] = get_series_and_print_info(fred, 'rpi',startDate, endDate, 'ry')
data['nc'] = get_series_and_print_info(fred, 'pcec',startDate, endDate, 'nc')
data['rc'] = get_series_and_print_info(fred, 'pcecc96',startDate, endDate, 'rc')
data['nc_services'] = get_series_and_print_info(fred, 'pcesv',startDate, endDate, 'nc_services')
data['nc_durable_goods'] = get_series_and_print_info(fred, 'pcdg',startDate, endDate, 'nc_durable_goods')
data['unemp'] = get_series_and_print_info(fred, 'unrate',startDate, endDate, 'unemp')
data['house_prices'] = get_series_and_print_info(fred, 'ussthpi',startDate, endDate, 'house_prices')
data['mortgage_30y'] = get_series_and_print_info(fred, 'MORTGAGE30US',startDate, endDate, 'mortgage_30y')
data['consumer_confidence'] = get_series_and_print_info(fred, 'CSCICP03USM665S',startDate, endDate, 'consumer_confidence')
data['gov_debt'] = get_series_and_print_info(fred, 'fygfdpun',startDate, endDate, 'gov_debt')
data['gov_transfers'] = get_series_and_print_info(fred, 'w211rc1q027sbea',startDate, endDate, 'gov_transfers')

    saving_rate: Personal saving as a percentage of disposable personal income
    ns: Net private saving: Households and institutions
    nnw: Households and Nonprofit Organizations; Net Worth, Level
    nl: Households and Nonprofit Organizations; Debt Securities and Loans; Liability, Level
    na: Households and Nonprofit Organizations; Total Assets, Level
    nfa: Households and Nonprofit Organizations; Total Financial Assets, Level
    interest_payments: Personal interest payments
    cpi: Consumer Price Index for All Urban Consumers: All Items in U.S. City Average
    c_deflator: Personal consumption expenditures (implicit price deflator)
    dff: Federal Funds Effective Rate
    yield_3m: Market Yield on U.S. Treasury Securities at 3-Year Constant Maturity
    yield_10y: Long-Term Government Bond Yields: 10-year: Main (Including Benchmark) for the United States
    prime_rate: Bank Prime Loan Rate
    m2: Real M2 Money Stock
    ndy: Disposable Personal Income
    rdy: Real Disposable Personal Income
    npiy: Personal income receipts on assets: Personal dividend income
    ny: Personal Income
    ry: Real Personal Income
    nc: Personal Consumption Expenditures
    rc: Real Personal Consumption Expenditures
    nc_services: Personal Consumption Expenditures: Services
    nc_durable_goods: Personal Consumption Expenditures: Durable Goods
    unemp: Unemployment Rate
    house_prices: All-Transactions House Price Index for the United States
    mortgage_30y: 30-Year Fixed Rate Mortgage Average in the United States
    consumer_confidence: Consumer Opinion Surveys: Confidence Indicators: Composite Indicators: OECD Indicator for the United States
    gov_debt: Federal Debt Held by the Public
    gov_transfers: Personal current transfer payments

1
df = pd.DataFrame(data)

Since we are interested in predicting the real consumption, which means eliminating the effect of prices, nominal variables including consumption, disposable income and net wealth need to be converted into real terms. Here they are deflated by the price deflator for consumption (c_deflator).

1
2
3
4
df['rnw'] = df['nnw']/df['c_deflator']
df['rl'] = df['nl']/df['c_deflator']
df['ra'] = df['na']/df['c_deflator']
df['rfa'] = df['nfa']/df['c_deflator']

Data Visualization and Preliminary Analysis (Some Fun Ideas)

Structural Breaks

To get a feel for the data, let’s plot log(rc/rdy) against log(rnw/rdy). Also regress log(rc/rdy) on a constant and log(rnw/rdy) and then examine the fitted residuals, looking for evidence of any structural changes in the relationship between consumption, disposable income and net wealth.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fig, ax1 = plt.subplots()
ax1.set_xlabel('Year')
ax1.set_ylabel('rdy rl')
ax1.plot(df['rc'], color='tab:grey',label='real consumption')
ax1.plot(df['rdy'], color='tab:blue',label='real disposable income')
ax1.plot(df['rl'], color='tab:green',label='real liability')
ax1.legend(loc='upper left')
ax2 = ax1.twinx()  
ax2.set_ylabel('rnw ra rfa')
ax2.plot(df['rnw'], color='tab:red',label='real net worth')
ax2.plot(df['ra'], color='tab:orange',label='real asset')
ax2.plot(df['rfa'], color='tab:purple',label='real fincl asset')
ax2.legend(loc='lower right')
fig.tight_layout()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data1 = df['rc']/df['rdy']
data2 = df['rc']/df['rnw']
data3 = df['rc']/df['rl']
data4 = df['rc']/df['ra']
data5 = df['rc']/df['rfa']
fig, ax1 = plt.subplots()
ax1.set_xlabel('Year')
ax1.set_ylabel('rdy')
ax1.plot(data1, color='tab:blue',label='rc/rdy')
ax1.legend(loc='upper left')
ax2 = ax1.twinx()  
ax2.set_ylabel('rnw ra rfa')
ax2.plot(data2, color='tab:red',label='rc/rnw')
ax2.plot(data4, color='tab:orange',label='rc/ra')
ax2.plot(data5, color='tab:purple',label='rc/rfa')
ax2.legend(loc='upper right')
fig.tight_layout()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data1 = np.log(df['rc']/df['rdy'])
data2 = np.log(df['rnw']/df['rdy'])
fig, ax1 = plt.subplots()
color = 'tab:green'
ax1.set_xlabel('Year')
ax1.set_ylabel('rc/rdy', color=color)
ax1.plot(data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()  
color = 'tab:blue'
ax2.set_ylabel('rnw/rdy', color=color)
ax2.plot(data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout()
ax1.axvline(x=datetime(1975, 10, 1), color='k')
ax1.axvline(x=datetime(2008, 7, 1), color='k')
plt.show()

Are there any structural breaks in the relationship before 1975Q4 and after 2008Q3? If so, we have to create dummies to allow for them. Let’s run a regression to further confirm it.

1
2
3
4
5
Y = np.log(df['rc']/df['rdy'])
X = np.log(df['rnw']/df['rdy'])
X = sm.add_constant(X)
model = sm.OLS(Y,X)
res = model.fit()
1
2
3
4
ax = res.resid[:-8].plot(style='.',grid=True)
ax.xaxis.grid(True, linestyle='--', linewidth=0.25)
ax.axvline(x=datetime(1975, 10, 1), color='k')
ax.axvline(x=datetime(2008, 7, 1), color='k')

Both the time plot and the regression of log(rc/rdy) on a constant and log(rnw/rdy) suggests that there were structural breaks around 1975Q4 and 2008Q3. Notice the consistently negative residuals till 1975Q4 and the wider gap between log(rc/rdy) and log(rnw/rdy) before 1976 compared to the gap after 1976. Another one is that household net worth and assets have increased at an accelerating rate after the 2008 global financial crisis (increasing rnw/rdy ratio). While at the same time, we don’t see a rising trend of consumption compared to income even though households have accumulated higher real net wealth benefited from the booming asset prices post-crisis, which is a quite different behavior compared to pre-crisis.

These are strong suggestive evidence that structural breaks occurred around 1975Q4 and 2008Q3. But why? What are the drivers?

Inequality and the Kondratiev theory

Let’s now take a look at the inequality data, which will give us better clues to our questions.

Data is downloaded from the World Inequality Database WID. Buckets I selected are top 10%, middle 40% and the bottom 50% of the US population.

1
2
3
4
income_inequality = pd.read_csv('income_inequality.csv',index_col=0)
wealth_inequality = pd.read_csv('wealth_inequality.csv',index_col=0)
pretax_income = pd.read_csv('pretax_income.csv',index_col=0)
net_wealth = pd.read_csv('net_wealth.csv',index_col=0)

As we can tell from the following graphs, income inequality in the US has risen dramatically since the mid 1970s, see how the income of the top 10% grows whereas that of the middle and bottom classes remains nearly flat. This issue has drawn heightened attention in recent years (including me :D).

In the past decade, economic observers have also become increasingly worried about “secular stagnation” - or a chronic shortfall of aggregate demand, fearing that this shortfall will constrain American economic growth in coming years. These two phenomena—rising inequality and chronic weakness of demand - are related. Specifically, since the marginal propensity to consume (MPC) is much lower at the higher wealth quintiles (for low-wealth households, the MPC is 10 times larger than it is for wealthy households, see this paper elaborating it), rising inequality transfers income from high MPC households in the bottom and middle of the income distribution to lower MPC households at the top. All else equal, this redistribution away from low- to high-saving households reduces consumption spending, which drags on demand growth.

1
pretax_income.iloc[:,1:].plot(title='Pre-tax Income')
1
net_wealth.iloc[:,1:].plot(title='Net Wealth')

    <matplotlib.axes._subplots.AxesSubplot at 0x23c76c39e48>

Due to the differences in income growth, we see a larger and larger wealth gap between groups.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
data1 = wealth_inequality['sw_p99p100']
data2 = wealth_inequality['sw_p90p100']
data3 = wealth_inequality['sw_p50p90']
data4 = wealth_inequality['sw_p0p50']

fig, ax1 = plt.subplots()
color = 'tab:blue'
ax1.set_xlabel('Year')
ax1.set_ylabel('Rich', color=color)
ax1.plot(data1, color='royalblue',label='Share of p99p100')
ax1.plot(data2, color='dodgerblue',label='Share of p90p100')
ax1.tick_params(axis='y', labelcolor=color)
ax1.legend(loc ='center left')
ax1.set_title('Wealth Inequality')
ax2 = ax1.twinx()  
color = 'tab:green'
ax2.set_ylabel('Poor', color=color)
ax2.plot(data3, color='limegreen',label='Share of p50p90')
ax2.plot(data4, color='lightgreen',label='Share of p0p50')
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout()
ax2.legend(loc ='center right')
plt.show()

Based on the theory, we can assume that there is a negative correlation between income inequality and consumption. Here I created an inequality indicator using the difference in shares of wealth of the top 10% and the bottom 50%, so that we can further look into the change of inequality over the last half century. The long-term trend is estimated using the Hodrick–Prescott filter, which can smooth the data by getting rid of the short-term fluctuations (shorter cycles).

1
2
3
4
5
wealth_inequality.index = pd.to_datetime(wealth_inequality.index, format='%Y')
# create a variable nw_inequlity for the wealth gap between top 10% and bottom 50%
wealth_inequality['nw_inequality'] = wealth_inequality['sw_p90p100']-wealth_inequality['sw_p50p90']
# apply the Hodrick-Prescott filter to smooth the inequality data and estimate the long-term trend
wealth_inequality['f_nw_inequality'] = pd.DataFrame(filters.hp_filter.hpfilter(wealth_inequality['nw_inequality'],lamb=6.25)[1])
1
2
3
4
5
f, (ax1, ax2) = plt.subplots(1,2,figsize=(15,4))
ax1.plot(wealth_inequality['nw_inequality'])
ax2.plot(wealth_inequality['f_nw_inequality'])
ax1.set_title('nw inequality')
ax2.set_title('filtered nw inequality (trend)')

Seeing the long-term inequity trend, it’s no wonder why we have identified two structural breaks.

Actually up to now, if you are familiar with the theory of Kondratieff cycles, this probably starts to look quite interesting to you. Kondratiev waves (also called super-cycles, great surges, long waves, K-waves or the long economic cycle) are hypothesized cycle-like phenomena in the modern world economy. The phenomenon is closely connected with the technology life cycle. It is stated that the period of a wave ranges from forty to sixty years, the cycles consist of alternating intervals of high sectoral growth and intervals of relatively slow growth.

Though there is a lack of agreement about both the cause of the waves and the start and end years of particular waves, inequity appears to be the most obvious driver, and yet some researchers have presented a technological and credit cycle explanation as well. However, among several modern timing versions of the cycles based on any of the causes, the consensus is that the start of our current cycle - Wave of the Information and Telecommunications Revolution falls into the range of early 1970s to mid 1980s, which happens to be the first break point we identified.

Every wave of innovations lasts approximately until the profits from the new innovation or sector fall to the level of other, older, more traditional sectors. It is a situation when the new technology, which originally increased a capacity to utilize new sources from nature, reached its limits and it is not possible to overcome this limit without an application of another new technology. For the end of an application phase of any wave there is typically an economic crisis and economic stagnation. The financial crisis of 2007–2008 is viewed by some as a result of the coming end of the Wave of the Information and Telecommunications Revolution, our second break point.

So after all, I create two dummy variables below to represent the turning points of the cycle.

1
2
df['sb_1975_4'] = np.where(df.index < '1976-10-1',0,1)
df['sb_2008_3'] = np.where(df.index < '2008-07-1',0,1)

Long-run Model Specification

Now let’s put aside the fun ideas and start model development with specifying the long-run predictive equation for real consumption.

Consider the following long-run model for U.S. real consumption:

\begin{align}
\log rc_t &= \beta_0 + \beta_1 \log rdy_t + \beta_1 \log rnw_t + \epsilon_t
\end{align}

It’s helpful to express our model in logarithmic form, so that the parameter estimates are elasticities.

1
2
3
4
df['ln_rc'] = np.log(df['rc'])
df['ln_rdy'] = np.log(df['rdy'])
df['ln_rnw'] = np.log(df['rnw'])
df['const'] = 1

Sample and Forecast Period

The sample period for model specification i.e., specifying the preferred model for forecasting purposes can be 1962:1 to 2016:4 (and no later than 2007:4). Remember we have to stick with the train data from now on for the model development. The test set will be preserved for testing the predictive power of the model.

1
2
3
cut = 220
train,test = df[:cut], df[cut:]
train.shape,test.shape

    ((220, 39), (18, 39))

1
train.tail()

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
saving_ratensnnwnlnanfainterest_paymentscpic_deflatordff...rnwrlrarfasb_1975_4sb_2008_3ln_rcln_rdyln_rnwconst
2015-10-017.41026.97289638.83214191.141104202.90072619.083271.856237.8371.032860.16...86787.01082413739.655907100887.72921870308.737873119.3929249.50597811.3712121
2016-01-017.51046.17290792.56214181.444105350.15773464.821266.871237.6891.033400.36...87858.10141313723.092704101945.18773071090.401587119.4002319.51352511.3834781
2016-04-016.9969.63592170.18414313.771106862.97874353.858271.273239.5901.039890.37...88634.55173113764.697228102763.73270271501.656906119.4053019.51188511.3922771
2016-07-016.8961.21893991.42514497.803108869.33175700.352274.959240.6071.043790.39...90048.21372113889.578363104301.94866872524.503971119.4111429.51670511.4081011
2016-10-016.8974.32094743.72614601.767109726.27276056.719277.958242.1351.048720.45...90342.25150713923.418072104628.75886872523.379930119.4159789.52200211.4113611

5 rows × 39 columns

Check for Stationary

I’m going to pre-test the data to determine whether if it has a unit root, that is, whether it’s stationary or non-stationary.

1. What is stationarity?

A stationary process is a stochastic process whose probability distribution does not change over time. For our work in time series analysis, we mostly care about stationarity in its weak form - covariance stationary.

Covariance stationary:

  • Constant mean:
    \begin{align}
    &E(y_t) = E(y_{t+1}) = \mu
    \end{align}
  • Constant variance:
    \begin{align}
    &Var(y_t) = Var(y_{t+1}) = \sigma^2
    \end{align}
  • Covariance depends on time that has elapsed between observations, not on reference period:
    \begin{align}
    &Cov(y_t,y_{t+j}) = Cov(y_s,y_{s+j}) = \gamma_j
    \end{align}

To get an intuition of stationarity, one can imagine a frictionless pendulum. It swings back and forth in an oscillatory motion, yet the amplitude and frequency remain constant. Although the pendulum is moving, the process is stationary as its “statistics” are constant (frequency and amplitude). However, if a force were to be applied to the pendulum, either the frequency or amplitude would change, thus making the process non-stationary.

2. Why is stationarity important?

Stationarity is one important assumption underlying in time series analysis as if the series is non-stationary, the shocks do not die out, the impact of disturbances become more influential over time and so gives a poor forecast accuracy.

Consequences of non-stationarity

  • The effect of a shock will diminish as time elapses
  • Statistical consequences
         - Non-normal distribution of test statistics
         - Bias in autoregressive coefficients; we might mistakenly estimate an AR(1), deficient forecast
         - Usual confidence intervals for coefficients not valid, poor forecast ability

3. How do we determine whether a time series is nonstationary?

As always, visual inspection is one of the most useful ways to identify non stationary series. It looks that all of the curves obviously have a trend, which is definitely against the definition of stationarity.

1
train[['ln_rc','ln_rdy','ln_rnw']].plot()

    <matplotlib.axes._subplots.AxesSubplot at 0x23c0007c248>

Then we can turn to statistical tests. The first would be Augmented Dickey-Fuller (ADF) Test, which tests for non-stationarity.

Recall AR(1) model:
\begin{align}
y_t =  by{t_1} + \epsilon_t
\end{align}

A special case is the random walk when $b = 1$. However, for stationarity, it requires $b<1$.

If generalizing to AR(p), it means that roots of the polynomial below must all be >1 in abs value,
\begin{align}
1 - b_1z -b_2z^2-b_3z^3-…-b_pz^p
\end{align}

If one of the roots = 1, then it is said to have a unit root i.e., non-stationary.

The ADF Test can test for the coefficient on $y_{t-1}$. Basically, it regresses y on its lag testing for significance of coefficient. I won’t go into great mathematical details, if anyone’s interested, check out this wiki page.

However, ADF has been found to have low power in certain circumstances:

  • stationary processes with near-unit roots. For example, it has difficulty distinguishing between b = 1 and b = 0.95, especially with small samples.
  • Trend stationary processes.

So alternative tests have been designed, which are Phillips–Perron (PP) Test and Kwiatkowski–Phillips–Schmidt–Shin (KPSS)Test. I usually prefer to use all three of them together, but here I would focus on ADF and KPSS as they have been implemented in statsmodels.

A few notes:
 - ADF and PP are called unit root tests; the null hypothesis is that $y_t$ has a unit root; is I(1) or higher.
 - KPSS, on the other hand, is a stationarity test; the null hypothesis is that $y_t$ is I(0).
 - Correct specification is key. Intercept and trend should be included when appropriate as critical values for the t-statistics will vary depending on whether they are included.
 - Structural breaks can complicate matters further. That’s why we start this blog with detecting structural breaks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def augmented_dickey_fuller_test(time_series):
    result = adfuller(time_series.values)
    print('ADF Statistic: %f' % result[0])
    print('ADF p-value: %f' % result[1])
    if result[1]<0.05:
        print('stationary - null hypothesis of a unit root can be rejected at a 5% significance level')
    else:
        print('non-stationary - null hypothesis of a unit root cannot be rejected at a 5% significance level')

def kpss_test(timeseries):
    result = kpss(timeseries, regression="c")
    print('KPSS Statistic: %f' % result[0])
    print('KPSS p-value: %f' % result[1])
    if result[1]>=0.05:
        print('stationary - null hypothesis of stationary cannot be rejected at a 5% significance level')
    else:
        print('non-stationary - null hypothesis of stationary can be rejected at a 5% significance level')
1
2
augmented_dickey_fuller_test(train['ln_rc'])
kpss_test(train['ln_rc'])

    ADF Statistic: -1.949803
    ADF p-value: 0.309007
    non-stationary - null hypothesis of a unit root cannot be rejected at a 5% significance level
    KPSS Statistic: 2.297233
    KPSS p-value: 0.010000
    non-stationary - null hypothesis of stationary can be rejected at a 5% significance level

1
2
augmented_dickey_fuller_test(train['ln_rdy'])
kpss_test(train['ln_rdy'])

    ADF Statistic: -2.970069
    ADF p-value: 0.037786
    stationary - null hypothesis of a unit root can be rejected at a 5% significance level
    KPSS Statistic: 2.292161
    KPSS p-value: 0.010000
    non-stationary - null hypothesis of stationary can be rejected at a 5% significance level

1
2
augmented_dickey_fuller_test(train['ln_rnw'])
kpss_test(train['ln_rnw'])

    ADF Statistic: -0.266563
    ADF p-value: 0.930108
    non-stationary - null hypothesis of a unit root cannot be rejected at a 5% significance level
    KPSS Statistic: 2.300497
    KPSS p-value: 0.010000
    non-stationary - null hypothesis of stationary can be rejected at a 5% significance level

None of the variables can be considered stationary with the two tests. We can therefore ask whether the variables form a cointegrated system with a given number of “common trends”. Intuitively, I would argue that there exists one cointegration equation among the three variables as in the long run, one’s amount consumed and wealth accumulated should be equal to the income that one earns.

We will discuss how to test for cointegration relationships and how to estimate them using a vector error correction model in the next blog.

Predicting Post-Pandemic Consumption Behaviors Given Potential Paths of Monetary Policy, Part 2 -- Cointegration, VAR/VECM and Forecast Project Covid Admissions Based on Vaccine Progress, Part 3 -- Mixed-effect models and Bayesian Inference
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×