unit_root/tools/
mod.rs

1use std::fmt::Debug;
2
3use nalgebra::{DMatrix, DVector, RealField, Scalar};
4use num_traits::Float;
5
6use crate::distrib::Regression;
7use crate::Error;
8
9// Copyright (c) 2022. Sebastien Soudan
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15//     http:www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22pub(crate) mod adf;
23pub(crate) mod dickeyfuller;
24
25/// Test report
26#[derive(Debug, Clone)]
27pub struct Report<F: Debug + Clone> {
28    /// The test statistic
29    pub test_statistic: F,
30    /// The size of the sample
31    pub size: usize,
32}
33
34/// Returns Delta(y) = y - y.shift(1) and a matrix made of:
35/// - a column of y.shift(1)
36/// - n columns of Delta(y).shift(n)
37pub(crate) fn prepare<F: RealField + Scalar + Float>(
38    y: &DVector<F>,
39    n: usize,
40    regression: Regression,
41) -> Result<(DVector<F>, DMatrix<F>, usize), Error> {
42    let y_len = y.len();
43
44    if y_len <= n + 1 {
45        return Err(Error::NotEnoughSamples);
46    }
47
48    // remove last element to build y[t-1]
49    // it's length is y_len - 1
50    let y_t_1_full = y.clone().remove_row(y_len - 1);
51
52    // build Delta[y[t]] = y[t] - y[t-1] by removing the first element of y
53    // and subtracting y[t-1].
54    // The length of Delta[y[t]] is y_len - 1.
55    let delta_y = y.clone().remove_row(0) - &y_t_1_full;
56
57    // we want to return as first element the last y_len - 1 -n elements of Delta[y[t]].
58    // we have to remove the first n elements of delta_y.
59    let delta_y_output = delta_y.clone().remove_rows(0, n);
60
61    // Now for the second element of the tuple, we want to build a matrix of size (y_len
62    // - 1 - n) x (n + 1)
63
64    // Create the empty matrix
65    let mut x = DMatrix::zeros(y_len - n - 1, n + 1);
66
67    // - The first column is a column of y[t-1]
68    let y_t_1 = y_t_1_full.remove_rows(0, n);
69    x.column_mut(0).copy_from(&y_t_1);
70
71    // - The next n columns are shifted elements of Delta[y[t]] (by removing the last element)
72    if n > 0 {
73        let mut delta_y_shifted = delta_y.clone().remove_row(delta_y.len() - 1);
74
75        for i in 0..n {
76            let col = delta_y_shifted.clone().remove_rows(0, n - i - 1);
77            x.column_mut(i + 1).copy_from(&col);
78            let delta_y_shifted_len = delta_y_shifted.len();
79            delta_y_shifted = delta_y_shifted.remove_row(delta_y_shifted_len - 1);
80        }
81    }
82
83    if regression != Regression::NoConstantNoTrend {
84        // constant trend column
85        let constant = F::from(1.0).ok_or(Error::ConversionFailed)?;
86        let a = vec![constant; x.nrows()];
87        x.extend(a)
88    }
89
90    if regression == Regression::ConstantAndTrend {
91        // time trend column
92        let tt: Result<Vec<F>, crate::Error> = (1..x.nrows() + 1)
93            .map(|i| F::from(i as f64).ok_or(Error::ConversionFailed))
94            .collect();
95        match tt {
96            Ok(tt) => x.extend(tt),
97            Err(_) => return Err(Error::ConversionFailed),
98        };
99    }
100
101    Ok((delta_y_output.into_owned(), x, y_len - n - 1))
102}
103
104#[cfg(test)]
105mod tests {
106    use nalgebra::{DMatrix, Matrix, Vector};
107
108    use crate::distrib::Regression;
109
110    #[test]
111    fn test_prepare_constant() {
112        // Given
113        let sz = 10;
114        let n = 2;
115
116        let y = vec![1., 3., 6., 10., 15., 21., 28., 36., 45., 55.];
117        assert_eq!(y.len(), sz);
118
119        let row_count = sz - n - 1;
120        let column_count = n + 2;
121
122        let expected = DMatrix::from_row_slice(
123            row_count,
124            column_count,
125            &[
126                // y[t-1], Delta[y[t]].shift(1), Delta[y[t]].shift(2), constant
127                6., 3., 2., 1., //  row 0
128                10., 4., 3., 1., // row 1
129                15., 5., 4., 1., // row 2
130                21., 6., 5., 1., // row 3
131                28., 7., 6., 1., // row 4
132                36., 8., 7., 1., // row 5
133                45., 9., 8., 1., //  row 6
134            ],
135        );
136
137        let y = Matrix::from(y);
138
139        let regression = Regression::Constant;
140
141        // When
142        let (delta_y, x, sz_) = super::prepare(&y, n, regression).unwrap();
143
144        // Expected
145        assert_eq!(sz_, sz - n - 1);
146
147        // Delta[y[t]] = y[t] - y[t-1]
148        assert_eq!(delta_y, Vector::from(vec![4., 5., 6., 7., 8., 9., 10.]));
149
150        assert_eq!(
151            x.shape(),
152            (row_count, column_count),
153            "The shape of x was not as expected"
154        );
155        assert_eq!(
156            x, expected,
157            "The output matrix x did not match the expected result"
158        );
159    }
160
161    #[test]
162    fn test_prepare_constant_and_trend() {
163        // Given
164        let sz = 10;
165        let n = 2;
166
167        let y = vec![1., 3., 6., 10., 15., 21., 28., 36., 45., 55.];
168        assert_eq!(y.len(), sz);
169
170        let row_count = sz - n - 1;
171        let column_count = n + 3;
172
173        let expected = DMatrix::from_row_slice(
174            row_count,
175            column_count,
176            &[
177                // y[t-1], Delta[y[t]].shift(1), Delta[y[t]].shift(2), constant, time trend
178                6., 3., 2., 1., 1., //  row 0
179                10., 4., 3., 1., 2., // row 1
180                15., 5., 4., 1., 3., // row 2
181                21., 6., 5., 1., 4., // row 3
182                28., 7., 6., 1., 5., // row 4
183                36., 8., 7., 1., 6., // row 5
184                45., 9., 8., 1., 7., //  row 6
185            ],
186        );
187
188        let y = Matrix::from(y);
189
190        let regression = Regression::ConstantAndTrend;
191
192        // When
193        let (delta_y, x, sz_) = super::prepare(&y, n, regression).unwrap();
194
195        // Expected
196        assert_eq!(sz_, sz - n - 1);
197
198        // Delta[y[t]] = y[t] - y[t-1]
199        assert_eq!(delta_y, Vector::from(vec![4., 5., 6., 7., 8., 9., 10.]));
200
201        assert_eq!(
202            x.shape(),
203            (row_count, column_count),
204            "The shape of x was not as expected"
205        );
206        assert_eq!(
207            x, expected,
208            "The output matrix x did not match the expected result"
209        );
210    }
211
212    #[test]
213    fn test_prepare_no_constant_no_trend() {
214        // Given
215        let sz = 10;
216        let n = 2;
217
218        let y = vec![1., 3., 6., 10., 15., 21., 28., 36., 45., 55.];
219        assert_eq!(y.len(), sz);
220
221        let row_count = sz - n - 1;
222        let column_count = n + 1;
223
224        let expected = DMatrix::from_row_slice(
225            row_count,
226            column_count,
227            &[
228                // y[t-1], Delta[y[t]].shift(1), Delta[y[t]].shift(2)
229                6., 3., 2., //  row 0
230                10., 4., 3., // row 1
231                15., 5., 4., // row 2
232                21., 6., 5., // row 3
233                28., 7., 6., // row 4
234                36., 8., 7., // row 5
235                45., 9., 8., //  row 6
236            ],
237        );
238
239        let y = Matrix::from(y);
240        let regression = Regression::NoConstantNoTrend;
241
242        // When
243        let (delta_y, x, sz_) = super::prepare(&y, n, regression).unwrap();
244
245        // Expected
246        assert_eq!(sz_, sz - n - 1);
247
248        // Delta[y[t]] = y[t] - y[t-1]
249        assert_eq!(delta_y, Vector::from(vec![4., 5., 6., 7., 8., 9., 10.]));
250        assert_eq!(
251            x.shape(),
252            (row_count, column_count),
253            "The shape of x was not as expected"
254        );
255        assert_eq!(
256            x, expected,
257            "The output matrix x did not match the expected result"
258        );
259    }
260
261    #[test]
262    fn test_prepare_minimum_size_00() {
263        let n = 0;
264
265        let y: Vec<f32> = vec![];
266
267        let y = Matrix::from(y);
268
269        let res = super::prepare(&y, n, Regression::Constant);
270        assert!(res.is_err());
271    }
272
273    #[test]
274    fn test_prepare_minimum_size_0() {
275        let n = 0;
276
277        let y = vec![1.];
278
279        let y = Matrix::from(y);
280
281        let res = super::prepare(&y, n, Regression::Constant);
282        assert!(res.is_err());
283    }
284
285    #[test]
286    fn test_prepare_minimum_size_1() {
287        let n = 1;
288
289        let y = vec![1.];
290
291        let y = Matrix::from(y);
292
293        let res = super::prepare(&y, n, Regression::Constant);
294        assert!(res.is_err());
295    }
296
297    #[test]
298    fn test_prepare_minimum_size_2() {
299        let n = 1;
300
301        let y = vec![1., 3.];
302
303        let y = Matrix::from(y);
304
305        let res = super::prepare(&y, n, Regression::Constant);
306        assert!(res.is_err());
307    }
308
309    #[test]
310    fn test_prepare_minimum_size_3() {
311        let n = 2;
312
313        let y = vec![1., 3.];
314
315        let y = Matrix::from(y);
316
317        let res = super::prepare(&y, n, Regression::Constant);
318        assert!(res.is_err());
319    }
320}