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}