unit_root/tools/adf.rs
1// Copyright (c) 2022. Sebastien Soudan
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http:www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Augmented Dickey-Fuller test
16use nalgebra::{DVector, RealField, Scalar};
17use num_traits::Float;
18
19use crate::distrib::Regression;
20use crate::prelude::tools::Report;
21use crate::regression::ols;
22use crate::{tools, Error};
23
24/// Augmented Dickey-Fuller test
25/// - Constant and no trend model
26/// - Fixed lag
27/// - y must have strictly more than n + 1 elements.
28pub fn adf_test<F: RealField + Scalar + Float>(
29 y: &DVector<F>,
30 lag: usize,
31 regression: Regression,
32) -> Result<Report<F>, Error> {
33 let (delta_y, x, size) = tools::prepare(y, lag, regression)?;
34
35 let (_betas, t_stats) = ols(&delta_y, &x)?;
36
37 Ok(Report {
38 test_statistic: t_stats[0],
39 size,
40 })
41}
42
43/// Comparison with statsmodels.tsa.stattools.adfuller use the following code:
44/// ```python
45/// import numpy as np
46/// import pandas as pd
47/// import statsmodels.tsa.stattools as ts
48/// pd.options.display.float_format = '{:.12g}'.format
49///
50/// def adf_test(timeseries, maxlag=None, regression="c", autolag="AIC"):
51/// print("Results of Dickey-Fuller Test:")
52/// dftest = ts.adfuller(timeseries, maxlag=maxlag, regression=regression, autolag=autolag)
53/// dfoutput = pd.Series(
54/// dftest[0:4],
55/// index=[
56/// "Test Statistic",
57/// "p-value",
58/// "#Lags Used",
59/// "Number of Observations Used",
60/// ],
61/// )
62/// for key, value in dftest[4].items():
63/// dfoutput["Critical Value (%s)" % key] = value
64/// print(dfoutput)
65///
66/// y = [-1.06714348, -1.14700339, 0.79204106, -0.05845247, -0.67476754,
67/// -0.10396661, 1.82059282, -0.51169443, 2.07712365, 1.85668086, 2.56363688]
68///
69/// adf_test(y, maxlag=2, regression='n')
70/// adf_test(y, maxlag=2, regression='c')
71/// adf_test(y, maxlag=2, regression='ct')
72/// ```
73///
74/// Note: this library does not support the `autolag` yet. Tests are using the lag from
75/// statsmodels.
76#[cfg(test)]
77mod tests {
78 use approx::assert_relative_eq;
79 use nalgebra::DVector;
80
81 use crate::distrib::Regression;
82 use crate::prelude::tools::{adf_test, dickeyfuller_test};
83
84 const Y: [f64; 11] = [
85 -1.06714348,
86 -1.14700339,
87 0.79204106,
88 -0.05845247,
89 -0.67476754,
90 -0.10396661,
91 1.82059282,
92 -0.51169443,
93 2.07712365,
94 1.85668086,
95 2.56363688,
96 ];
97
98 #[test]
99 fn test_t_statistics_n() {
100 let lag = 1;
101 let y = DVector::from_row_slice(&Y[..]);
102
103 let report = adf_test(&y, lag, Regression::NoConstantNoTrend).unwrap();
104 assert_eq!(report.size, 9);
105 assert_relative_eq!(report.test_statistic, -0.417100483298f64, epsilon = 1e-9);
106 // Test Statistic -0.417100483298
107 // p-value 0.529851882135
108 // #Lags Used 1
109 // Number of Observations Used 9
110 // Critical Value (1%) -2.85894
111 // Critical Value (5%) -1.96955775034
112 // Critical Value (10%) -1.58602219479
113 }
114
115 #[test]
116 fn test_t_statistics_c() {
117 let lag = 2;
118 let y = DVector::from_row_slice(&Y[..]);
119
120 let report = adf_test(&y, lag, Regression::Constant).unwrap();
121 assert_eq!(report.size, 8);
122 assert_relative_eq!(report.test_statistic, 0.486121422662f64, epsilon = 1e-9);
123 // Results of Dickey-Fuller Test:
124 // Test Statistic 0.486121422662
125 // p-value 0.984445107564
126 // #Lags Used 2
127 // Number of Observations Used 8
128 // Critical Value (1%) -4.66518632812
129 // Critical Value (5%) -3.367186875
130 // Critical Value (10%) -2.802960625
131 }
132
133 #[test]
134 fn test_t_statistics_ct() {
135 let lag = 0;
136 let y = DVector::from_row_slice(&Y[..]);
137
138 let report = adf_test(&y, lag, Regression::ConstantAndTrend).unwrap();
139 assert_eq!(report.size, 10);
140 assert_relative_eq!(report.test_statistic, -4.20337098854f64, epsilon = 1e-9);
141 // Results of Dickey-Fuller Test:
142 // Test Statistic -4.20337098854
143 // p-value 0.00442477220907
144 // #Lags Used 0
145 // Number of Observations Used 10
146 // Critical Value (1%) -5.282515
147 // Critical Value (5%) -3.985264
148 // Critical Value (10%) -3.44724
149 }
150
151 #[test]
152 fn test_adf_lag_0_is_dickeyfuller_test() {
153 let lag = 0;
154
155 let y = vec![1_f32, 3., 6., 10., 15., 21., 28., 36., 45., 55.];
156
157 let y = DVector::from(y);
158
159 let regression = Regression::Constant;
160
161 let report = adf_test(&y, lag, regression).unwrap();
162 let df_report = dickeyfuller_test(&y, regression).unwrap();
163
164 assert_eq!(report.test_statistic, df_report.test_statistic);
165 assert_eq!(report.size, df_report.size);
166 }
167}