unit_root/tools/
dickeyfuller.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
15use nalgebra::{RealField, Scalar};
16use num_traits::Float;
17
18use crate::distrib::Regression;
19use crate::prelude::nalgebra::DVector;
20use crate::prelude::tools::Report;
21use crate::regression::ols;
22use crate::tools::prepare;
23use crate::Error;
24
25/// Returns the t-statistic of the Dickey-Fuller test
26/// and the size of the sample.
27///
28/// The null hypothesis is that the series is non-stationary.
29///
30/// # Details
31///
32/// Critical values for can obtained from
33/// `unit_root::prelude::distrib::dickeyfuller::get_critical_value`.
34///
35/// - If $t_{stat} < \mathrm{t_{\mathrm{crit}}(\alpha)}$ then reject $H_0$ at
36/// $alpha$ significance level - and thus conclude that the series is stationary.
37/// - If $t_{stat} > \mathrm{t_{\mathrm{crit}}(\alpha)}$ then fail to reject $H_0$ at
38/// $alpha$ significance level - and thus conclude we cannot reject the hypothesis that
39/// the series is not stationary.
40///
41/// # Examples:
42///
43/// ```rust
44/// use unit_root::prelude::distrib::{AlphaLevel, Regression};
45/// use unit_root::prelude::nalgebra::DVector;
46/// use unit_root::prelude::*;
47///
48/// let y = DVector::from_row_slice(&[
49///     -0.89642362,
50///     0.3222552,
51///     -1.96581989,
52///     -1.10012936,
53///     -1.3682928,
54///     1.17239875,
55///     2.19561259,
56///     2.54295031,
57///     2.05530587,
58///     1.13212955,
59///     -0.42968979,
60/// ]);
61///
62/// let regression = Regression::Constant;
63///
64/// let report = tools::dickeyfuller_test(&y, regression).unwrap();
65///
66/// let critical_value =
67///     distrib::dickeyfuller::get_critical_value(regression, report.size, AlphaLevel::OnePercent)
68///         .unwrap();
69/// assert_eq!(report.size, 10);
70///
71/// let t_stat = report.test_statistic;
72/// println!("t-statistic: {}", t_stat);
73/// assert!((t_stat - -1.472691f64).abs() < 1e-6);
74/// assert!(t_stat > critical_value);
75/// ```
76pub fn dickeyfuller_test<F: Float + Scalar + RealField>(
77    series: &DVector<F>,
78    regression: Regression,
79) -> Result<Report<F>, Error> {
80    let (delta_y, y_t_1, size) = prepare(series, 0, regression)?;
81
82    let (_betas, t_stats) = ols(&delta_y, &y_t_1)?;
83
84    Ok(Report {
85        test_statistic: t_stats[0],
86        size,
87    })
88}
89
90/// Comparison with statsmodels.tsa.stattools.adfuller use the following code - see
91/// [`tools::adf_test::test`] for the definition of the function:
92/// ```python
93/// adf_test(y, maxlag=0, regression='n')
94/// adf_test(y, maxlag=0, regression='c')
95/// adf_test(y, maxlag=0, regression='ct')
96/// ```
97/// Note: maxlag is set to 0.
98#[cfg(test)]
99mod tests {
100    use approx::assert_relative_eq;
101    use rand::prelude::*;
102    use rand_chacha::ChaCha8Rng;
103
104    use super::*;
105    use crate::distrib::dickeyfuller::constant_no_trend_critical_value;
106    use crate::distrib::AlphaLevel;
107    use crate::utils::gen_ar_1;
108
109    const Y: [f64; 11] = [
110        -1.06714348,
111        -1.14700339,
112        0.79204106,
113        -0.05845247,
114        -0.67476754,
115        -0.10396661,
116        1.82059282,
117        -0.51169443,
118        2.07712365,
119        1.85668086,
120        2.56363688,
121    ];
122
123    #[test]
124    fn test_t_statistics_n() {
125        let y = DVector::from_row_slice(&Y[..]);
126
127        let report = dickeyfuller_test(&y, Regression::NoConstantNoTrend).unwrap();
128        assert_eq!(report.size, 10);
129        assert_relative_eq!(report.test_statistic, -1.5140129055f64, epsilon = 1e-9);
130        // Results of Dickey-Fuller Test:
131        // Test Statistic                 -1.5140129055
132        // p-value                       0.121977783883
133        // #Lags Used                                 0
134        // Number of Observations Used               10
135        // Critical Value (1%)                 -2.82559
136        // Critical Value (5%)                -1.970287
137        // Critical Value (10%)               -1.592036
138    }
139
140    #[test]
141    fn test_t_statistics_c() {
142        let y = DVector::from_row_slice(&Y[..]);
143
144        let report = dickeyfuller_test(&y, Regression::Constant).unwrap();
145        assert_eq!(report.size, 10);
146        assert_relative_eq!(report.test_statistic, -1.83288396527f64, epsilon = 1e-9);
147        // Results of Dickey-Fuller Test:
148        // Test Statistic                -1.83288396527
149        // p-value                       0.364262207135
150        // #Lags Used                                 0
151        // Number of Observations Used               10
152        // Critical Value (1%)                -4.331573
153        // Critical Value (5%)                 -3.23295
154        // Critical Value (10%)                 -2.7487
155    }
156
157    #[test]
158    fn test_t_statistics_ct() {
159        let y = DVector::from_row_slice(&Y[..]);
160
161        let report = dickeyfuller_test(&y, Regression::ConstantAndTrend).unwrap();
162        assert_eq!(report.size, 10);
163        assert_relative_eq!(report.test_statistic, -4.20337098854f64, epsilon = 1e-9);
164        // Results of Dickey-Fuller Test:
165        // Test Statistic                  -4.20337098854
166        // p-value                       0.00442477220907
167        // #Lags Used                                   0
168        // Number of Observations Used                 10
169        // Critical Value (1%)                  -5.282515
170        // Critical Value (5%)                  -3.985264
171        // Critical Value (10%)                  -3.44724
172    }
173
174    #[test]
175    fn test_dickeyfuller_no_unit_root_f32() {
176        let n = 100;
177
178        let mut rng = ChaCha8Rng::seed_from_u64(42);
179
180        let delta: f32 = 0.5;
181        let y = gen_ar_1(&mut rng, n, 0.0, delta, 1.0);
182
183        let report = dickeyfuller_test(&y, Regression::Constant).unwrap();
184
185        let critical_value =
186            match constant_no_trend_critical_value(report.size, AlphaLevel::OnePercent) {
187                Ok(v) => v,
188                Err(_) => f32::MIN,
189            };
190
191        let t_stat = report.test_statistic;
192        assert!(t_stat < critical_value);
193    }
194
195    #[test]
196    fn test_dickeyfuller_with_unit_root_f32() {
197        let n = 100;
198
199        let mut rng = ChaCha8Rng::seed_from_u64(42);
200
201        let delta: f32 = 1.0;
202        let y = gen_ar_1(&mut rng, n, 0.0, delta, 1.0);
203
204        let report = dickeyfuller_test(&y, Regression::Constant).unwrap();
205
206        let critical_value =
207            match constant_no_trend_critical_value(report.size, AlphaLevel::OnePercent) {
208                Ok(v) => v,
209                Err(_) => f32::MAX,
210            };
211
212        let t_stat = report.test_statistic;
213        assert!(t_stat > critical_value);
214    }
215
216    #[test]
217    fn test_dickeyfuller_no_unit_root_f64() {
218        let n = 100;
219
220        let mut rng = ChaCha8Rng::seed_from_u64(42);
221
222        let delta: f64 = 0.5;
223        let y = gen_ar_1(&mut rng, n, 0.0, delta, 1.0);
224
225        let report = dickeyfuller_test(&y, Regression::Constant).unwrap();
226
227        let critical_value =
228            match constant_no_trend_critical_value(report.size, AlphaLevel::OnePercent) {
229                Ok(v) => v,
230                Err(_) => f64::MIN,
231            };
232
233        let t_stat = report.test_statistic;
234        assert!(t_stat < critical_value);
235    }
236
237    #[test]
238    fn test_dickeyfuller_with_unit_root_f64() {
239        let n = 100;
240
241        let mut rng = ChaCha8Rng::seed_from_u64(42);
242
243        let delta: f64 = 1.0;
244        let y = gen_ar_1(&mut rng, n, 0.0, delta, 1.0);
245
246        let report = dickeyfuller_test(&y, Regression::Constant).unwrap();
247
248        let critical_value =
249            match constant_no_trend_critical_value(report.size, AlphaLevel::OnePercent) {
250                Ok(v) => v,
251                Err(_) => f64::MAX,
252            };
253
254        let t_stat = report.test_statistic;
255        assert!(t_stat > critical_value);
256    }
257
258    #[test]
259    fn no_enough_data() {
260        let y = DVector::from_row_slice(&[1.0]);
261        let report = dickeyfuller_test(&y, Regression::Constant);
262        assert!(report.is_err());
263    }
264
265    #[test]
266    fn test_constant_no_trend_test() {
267        let y = vec![1_f32, 3., 6., 10., 15., 21., 28., 36., 45., 55.];
268
269        let y = DVector::from(y);
270
271        let report = dickeyfuller_test(&y, Regression::Constant).unwrap();
272
273        assert_eq!(report.size, 9);
274    }
275}