ADF and Phillips-Perron Tests for Stationarity using lists

code
rtip
lists
timeseries
lapply
Author

Steven P. Sanderson II, MPH

Published

January 23, 2023

Introduction

A time series is a set of data points collected at regular intervals of time. Sometimes, the data points in a time series change over time in a predictable way. This is called a stationary time series. Other times, the data points change in an unpredictable way. This is called a non-stationary time series.

Imagine you are playing a game of catch with a friend. If you throw the ball back and forth at the same speed and distance, that’s like a stationary time series. But if you keep throwing the ball harder and farther, that’s like a non-stationary time series.

There are two tests that we can use to see if a time series is stationary or non-stationary. The first test is called the ADF test, which stands for Augmented Dickey-Fuller test. The second test is called the Phillips-Perron test.

The ADF test looks at the data points and checks to see if the average value of the data points is the same over time. If the average value is the same, then the time series is stationary. If the average value is not the same, then the time series is non-stationary.

The Phillips-Perron test is similar to the ADF test, but it is a bit more advanced. It checks to see if the data points are changing in a predictable way. If the data points are changing in a predictable way, then the time series is stationary. If the data points are changing in an unpredictable way, then the time series is non-stationary.

So, in short, The ADF test checks if the mean of the time series is constant over time and Phillips-Perron test checks if the variance of the time series is constant over time.

Now, you can use these two tests to see if the time series you are studying is stationary or non-stationary, just like how you can use the game of catch to see if your throws are the same or different.

Function

To perform these test we can use two libraries, one is the {tseries} library for the adf.test() and the other is the {aTSA} for the pp.test()

Let’s see some examples.

Examples

Let’s first make our time series obejcts and place them in a list.

library(tseries)
library(aTSA)

# create time series objects
ts1 <- ts(rnorm(100), start = c(1990,1), frequency = 12)
ts2 <- ts(rnorm(100), start = c(1995,1), frequency = 12)
ts3 <- ts(rnorm(100), start = c(2000,1), frequency = 12)

# create list of time series
ts_list <- list(ts1, ts2, ts3)

Now let’s make our functions.

# function to test for stationarity
adf_is_stationary <- function(x) {
  adf.test(x)$p.value > 0.05
}

pp_is_stationary <- function(x) {
  pp_df <- pp.test(x) |> as.data.frame() 
  pp_df$p.value > 0.05
}

Time to use lapply()!

# apply function to each time series in list
lapply(ts_list, adf_is_stationary)
Augmented Dickey-Fuller Test 
alternative: stationary 
 
Type 1: no drift no trend 
     lag    ADF p.value
[1,]   0 -10.97    0.01
[2,]   1  -7.70    0.01
[3,]   2  -5.23    0.01
[4,]   3  -4.05    0.01
[5,]   4  -4.03    0.01
Type 2: with drift no trend 
     lag    ADF p.value
[1,]   0 -11.48    0.01
[2,]   1  -8.32    0.01
[3,]   2  -5.81    0.01
[4,]   3  -4.59    0.01
[5,]   4  -4.63    0.01
Type 3: with drift and trend 
     lag    ADF p.value
[1,]   0 -11.42    0.01
[2,]   1  -8.28    0.01
[3,]   2  -5.77    0.01
[4,]   3  -4.56    0.01
[5,]   4  -4.59    0.01
---- 
Note: in fact, p.value = 0.01 means p.value <= 0.01 
Augmented Dickey-Fuller Test 
alternative: stationary 
 
Type 1: no drift no trend 
     lag    ADF p.value
[1,]   0 -10.60    0.01
[2,]   1  -7.88    0.01
[3,]   2  -5.96    0.01
[4,]   3  -5.26    0.01
[5,]   4  -4.90    0.01
Type 2: with drift no trend 
     lag    ADF p.value
[1,]   0 -10.64    0.01
[2,]   1  -7.98    0.01
[3,]   2  -6.08    0.01
[4,]   3  -5.41    0.01
[5,]   4  -5.08    0.01
Type 3: with drift and trend 
     lag    ADF p.value
[1,]   0 -10.58    0.01
[2,]   1  -7.94    0.01
[3,]   2  -6.06    0.01
[4,]   3  -5.39    0.01
[5,]   4  -5.07    0.01
---- 
Note: in fact, p.value = 0.01 means p.value <= 0.01 
Augmented Dickey-Fuller Test 
alternative: stationary 
 
Type 1: no drift no trend 
     lag   ADF p.value
[1,]   0 -9.19    0.01
[2,]   1 -6.65    0.01
[3,]   2 -5.41    0.01
[4,]   3 -5.33    0.01
[5,]   4 -4.77    0.01
Type 2: with drift no trend 
     lag   ADF p.value
[1,]   0 -9.14    0.01
[2,]   1 -6.62    0.01
[3,]   2 -5.39    0.01
[4,]   3 -5.30    0.01
[5,]   4 -4.75    0.01
Type 3: with drift and trend 
     lag   ADF p.value
[1,]   0 -9.11    0.01
[2,]   1 -6.59    0.01
[3,]   2 -5.36    0.01
[4,]   3 -5.29    0.01
[5,]   4 -4.73    0.01
---- 
Note: in fact, p.value = 0.01 means p.value <= 0.01 
[[1]]
logical(0)

[[2]]
logical(0)

[[3]]
logical(0)
lapply(ts_list, pp_is_stationary)
Phillips-Perron Unit Root Test 
alternative: stationary 
 
Type 1: no drift no trend 
 lag Z_rho p.value
   3  -111    0.01
----- 
 Type 2: with drift no trend 
 lag Z_rho p.value
   3  -110    0.01
----- 
 Type 3: with drift and trend 
 lag Z_rho p.value
   3  -110    0.01
--------------- 
Note: p-value = 0.01 means p.value <= 0.01 
Phillips-Perron Unit Root Test 
alternative: stationary 
 
Type 1: no drift no trend 
 lag Z_rho p.value
   3  -101    0.01
----- 
 Type 2: with drift no trend 
 lag Z_rho p.value
   3  -101    0.01
----- 
 Type 3: with drift and trend 
 lag Z_rho p.value
   3  -101    0.01
--------------- 
Note: p-value = 0.01 means p.value <= 0.01 
Phillips-Perron Unit Root Test 
alternative: stationary 
 
Type 1: no drift no trend 
 lag Z_rho p.value
   3 -92.9    0.01
----- 
 Type 2: with drift no trend 
 lag Z_rho p.value
   3 -92.9    0.01
----- 
 Type 3: with drift and trend 
 lag Z_rho p.value
   3   -93    0.01
--------------- 
Note: p-value = 0.01 means p.value <= 0.01 
[[1]]
[1] FALSE FALSE FALSE

[[2]]
[1] FALSE FALSE FALSE

[[3]]
[1] FALSE FALSE FALSE

Voila!