Skip to content

Test the stationarity of a given Time-Series Dataset🔗

Introduction🔗

Summary

As stated by Robert Nau, Duke University:

A stationary time series is one whose statistical properties such as mean, variance, autocorrelation, etc. are all constant over time. Most statistical forecasting methods are based on the assumption that the time series can be rendered approximately stationary (i.e., "stationarized") through the use of mathematical transformations.


For more info, see: Introduction to ARIMA models.

Info

There are two primary libraries used to implement these tests, ensuring both breadth of coverage and numerical reliability:

library category algorithm short import script url
statsmodels Stationarity Augmented Dickey-Fuller ADF from statsmodels.tsa.stattools import adfuller https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html
Stationarity Kwiatkowski-Phillips-Schmidt-Shin KPSS from statsmodels.tsa.stattools import kpss https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html
Unit-Root Zivot-Andrews structural-break unit-root test ZA from statsmodels.tsa.stattools import zivot_andrews https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html
Stationarity Range unit-root test for stationarity RUR from statsmodels.tsa.stattools import range_unit_root_test https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html
arch Stationarity Phillips-Perron PP from arch.unitroot import PhillipsPerron https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.PhillipsPerron.html
Stationarity Elliott-Rothenberg-Stock (ERS) de-trended DF ERS from arch.unitroot import DFGLS https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html
Unit-Root Variance Ratio (VR) test VR from arch.unitroot import VarianceRatio https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html

For more info, see: Statsmodels TSA and Arch Unit Roots.

Source Library

The statsmodels and arch packages were chosen because they provide robust, industry-standard implementations of unit root and stationarity tests. Specifically, statsmodels offers the classic ADF and KPSS tests along with specialized ones like Zivot-Andrews and RUR, while arch provides high-performance implementations of Phillips-Perron, ERS, and Variance Ratio tests, ensuring a comprehensive suite of tools for detecting non-stationarity and random walks.

Source Module

All of the source code can be found within these modules:

Modules🔗

ts_stat_tests.stationarity.tests 🔗

Summary

This module contains convenience functions and tests for stationarity measures, allowing for easy access to different unit root and stationarity algorithms.

STATIONARITY_ITEM module-attribute 🔗

STATIONARITY_ITEM = Union[
    float,
    int,
    dict[str, float],
    NDArray[float64],
    ResultsStore,
]

Type alias for the items in the stationarity test result tuple.

STATIONARITY_RETURN_TYPE module-attribute 🔗

STATIONARITY_RETURN_TYPE = tuple[STATIONARITY_ITEM, ...]

Type alias for the return type of stationarity tests.

stationarity 🔗

stationarity(
    x: ArrayLike,
    algorithm: str = "adf",
    **kwargs: Union[float, int, str, bool, ArrayLike, None]
) -> STATIONARITY_RETURN_TYPE

Summary

Perform a stationarity test on the given data.

Details

This function is a convenience wrapper around multiple underlying algorithms:
- adf()
- kpss()
- pp()
- za()
- ers()
- vr()
- rur()

Parameters:

Name Type Description Default
x ArrayLike

The data to be checked.

required
algorithm str

Which stationarity algorithm to use.
- adf(): ["adf", "augmented_dickey_fuller"]
- kpss(): ["kpss", "kwiatkowski_phillips_schmidt_shin"]
- pp(): ["pp", "phillips_perron"]
- za(): ["za", "zivot_andrews"]
- ers(): ["ers", "elliott_rothenberg_stock"]
- vr(): ["vr", "variance_ratio"]
- rur(): ["rur", "range_unit_root"]
Defaults to "adf".

'adf'
kwargs Union[float, int, str, bool, ArrayLike, None]

Additional arguments to pass to the underlying algorithm.

{}

Raises:

Type Description
ValueError

When the given value for algorithm is not valid.

Returns:

Type Description
tuple[Union[float, int, dict, ResultsStore, None], ...]

The result of the stationarity test.

Credit

Calculations are performed by statsmodels, arch, and pmdarima.

Examples
Setup
1
2
3
>>> from ts_stat_tests.stationarity.tests import stationarity
>>> from ts_stat_tests.utils.data import data_normal
>>> normal = data_normal
Example 1: Augmented Dickey-Fuller
1
2
3
>>> result = stationarity(normal, algorithm="adf")
>>> print(f"ADF statistic: {result[0]:.4f}")
ADF statistic: -30.7838
Example 2: Kwiatkowski-Phillips-Schmidt-Shin
1
2
3
>>> result = stationarity(normal, algorithm="kpss")
>>> print(f"KPSS statistic: {result[0]:.4f}")
KPSS statistic: 0.0858
Example 3: Phillips-Perron
1
2
3
>>> result = stationarity(normal, algorithm="pp")
>>> print(f"PP statistic: {result[0]:.4f}")
PP statistic: -30.7758
Example 4: Zivot-Andrews
1
2
3
>>> result = stationarity(normal, algorithm="za")
>>> print(f"ZA statistic: {result[0]:.4f}")
ZA statistic: -30.8800
Example 5: Elliot-Rothenberg-Stock
1
2
3
>>> result = stationarity(normal, algorithm="ers")
>>> print(f"ERS statistic: {result[0]:.4f}")
ERS statistic: -30.1517
Example 6: Variance Ratio
1
2
3
>>> result = stationarity(normal, algorithm="vr")
>>> print(f"VR statistic: {result[0]:.4f}")
VR statistic: -12.8518
Example 7: Range Unit Root
1
2
3
>>> result = stationarity(normal, algorithm="rur")
>>> print(f"RUR statistic: {result[0]:.4f}")
RUR statistic: 0.3479
Example 8: Invalid algorithm
1
2
3
4
>>> stationarity(normal, algorithm="invalid")
Traceback (most recent call last):
    ...
ValueError: Invalid 'algorithm': invalid. Options: {'adf': ('adf', 'augmented_dickey_fuller'), 'kpss': ('kpss', 'kwiatkowski_phillips_schmidt_shin'), 'pp': ('pp', 'phillips_perron'), 'za': ('za', 'zivot_andrews'), 'ers': ('ers', 'elliott_rothenberg_stock'), 'vr': ('vr', 'variance_ratio'), 'rur': ('rur', 'range_unit_root')}
Source code in src/ts_stat_tests/stationarity/tests.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@typechecked
def stationarity(
    x: ArrayLike,
    algorithm: str = "adf",
    **kwargs: Union[float, int, str, bool, ArrayLike, None],
) -> STATIONARITY_RETURN_TYPE:
    """
    !!! note "Summary"
        Perform a stationarity test on the given data.

    ???+ abstract "Details"
        This function is a convenience wrapper around multiple underlying algorithms:<br>
        - [`adf()`][ts_stat_tests.stationarity.algorithms.adf]<br>
        - [`kpss()`][ts_stat_tests.stationarity.algorithms.kpss]<br>
        - [`pp()`][ts_stat_tests.stationarity.algorithms.pp]<br>
        - [`za()`][ts_stat_tests.stationarity.algorithms.za]<br>
        - [`ers()`][ts_stat_tests.stationarity.algorithms.ers]<br>
        - [`vr()`][ts_stat_tests.stationarity.algorithms.vr]<br>
        - [`rur()`][ts_stat_tests.stationarity.algorithms.rur]

    Params:
        x (ArrayLike):
            The data to be checked.
        algorithm (str):
            Which stationarity algorithm to use.<br>
            - `adf()`: `["adf", "augmented_dickey_fuller"]`<br>
            - `kpss()`: `["kpss", "kwiatkowski_phillips_schmidt_shin"]`<br>
            - `pp()`: `["pp", "phillips_perron"]`<br>
            - `za()`: `["za", "zivot_andrews"]`<br>
            - `ers()`: `["ers", "elliott_rothenberg_stock"]`<br>
            - `vr()`: `["vr", "variance_ratio"]`<br>
            - `rur()`: `["rur", "range_unit_root"]`<br>
            Defaults to `"adf"`.
        kwargs (Union[float, int, str, bool, ArrayLike, None]):
            Additional arguments to pass to the underlying algorithm.

    Raises:
        (ValueError):
            When the given value for `algorithm` is not valid.

    Returns:
        (tuple[Union[float, int, dict, ResultsStore, None], ...]):
            The result of the stationarity test.

    !!! success "Credit"
        Calculations are performed by `statsmodels`, `arch`, and `pmdarima`.

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.tests import stationarity
        >>> from ts_stat_tests.utils.data import data_normal
        >>> normal = data_normal

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Augmented Dickey-Fuller"}
        >>> result = stationarity(normal, algorithm="adf")
        >>> print(f"ADF statistic: {result[0]:.4f}")
        ADF statistic: -30.7838

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Kwiatkowski-Phillips-Schmidt-Shin"}
        >>> result = stationarity(normal, algorithm="kpss")
        >>> print(f"KPSS statistic: {result[0]:.4f}")
        KPSS statistic: 0.0858

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Phillips-Perron"}
        >>> result = stationarity(normal, algorithm="pp")
        >>> print(f"PP statistic: {result[0]:.4f}")
        PP statistic: -30.7758

        ```

        ```pycon {.py .python linenums="1" title="Example 4: Zivot-Andrews"}
        >>> result = stationarity(normal, algorithm="za")
        >>> print(f"ZA statistic: {result[0]:.4f}")
        ZA statistic: -30.8800

        ```

        ```pycon {.py .python linenums="1" title="Example 5: Elliot-Rothenberg-Stock"}
        >>> result = stationarity(normal, algorithm="ers")
        >>> print(f"ERS statistic: {result[0]:.4f}")
        ERS statistic: -30.1517

        ```

        ```pycon {.py .python linenums="1" title="Example 6: Variance Ratio"}
        >>> result = stationarity(normal, algorithm="vr")
        >>> print(f"VR statistic: {result[0]:.4f}")
        VR statistic: -12.8518

        ```

        ```pycon {.py .python linenums="1" title="Example 7: Range Unit Root"}
        >>> result = stationarity(normal, algorithm="rur")
        >>> print(f"RUR statistic: {result[0]:.4f}")
        RUR statistic: 0.3479

        ```

        ```pycon {.py .python linenums="1" title="Example 8: Invalid algorithm"}
        >>> stationarity(normal, algorithm="invalid")
        Traceback (most recent call last):
            ...
        ValueError: Invalid 'algorithm': invalid. Options: {'adf': ('adf', 'augmented_dickey_fuller'), 'kpss': ('kpss', 'kwiatkowski_phillips_schmidt_shin'), 'pp': ('pp', 'phillips_perron'), 'za': ('za', 'zivot_andrews'), 'ers': ('ers', 'elliott_rothenberg_stock'), 'vr': ('vr', 'variance_ratio'), 'rur': ('rur', 'range_unit_root')}

        ```
    """
    options: dict[str, tuple[str, ...]] = {
        "adf": ("adf", "augmented_dickey_fuller"),
        "kpss": ("kpss", "kwiatkowski_phillips_schmidt_shin"),
        "pp": ("pp", "phillips_perron"),
        "za": ("za", "zivot_andrews"),
        "ers": ("ers", "elliott_rothenberg_stock"),
        "vr": ("vr", "variance_ratio"),
        "rur": ("rur", "range_unit_root"),
    }

    # Internal helper to handle kwargs casting for ty
    def _call(
        func: Callable[..., STATIONARITY_RETURN_TYPE],
        **args: Union[float, int, str, bool, ArrayLike, None],
    ) -> STATIONARITY_RETURN_TYPE:
        """
        !!! note "Summary"
            Internal helper to call the test function.

        Params:
            func (Callable[..., STATIONARITY_RETURN_TYPE]):
                The function to call.
            args (Union[float, int, str, bool, ArrayLike, None]):
                The arguments to pass to the function.

        Returns:
            (STATIONARITY_RETURN_TYPE):
                The result of the function call.

        ???+ example "Examples"

            ```pycon {.py .python linenums="1" title="Setup"}
            >>> from ts_stat_tests.stationarity.tests import stationarity
            >>> from ts_stat_tests.utils.data import data_normal
            >>> normal = data_normal
            ```

            ```pycon {.py .python linenums="1" title="Example 1: ADF test via internal helper"}
            >>> result = stationarity(normal, algorithm="adf")
            >>> print(f"ADF statistic: {result[0]:.4f}")
            ADF statistic: -30.7838

            ```
        """
        return func(**args)

    if algorithm in options["adf"]:
        return _call(_adf, x=x, **kwargs)
    if algorithm in options["kpss"]:
        return _call(_kpss, x=x, **kwargs)
    if algorithm in options["pp"]:
        return _call(_pp, x=x, **kwargs)
    if algorithm in options["za"]:
        return _call(_za, x=x, **kwargs)
    if algorithm in options["ers"]:
        return _call(_ers, y=x, **kwargs)
    if algorithm in options["vr"]:
        return _call(_vr, y=x, **kwargs)
    if algorithm in options["rur"]:
        return _call(_rur, x=x, **kwargs)

    raise ValueError(
        generate_error_message(
            parameter_name="algorithm",
            value_parsed=algorithm,
            options=options,
        )
    )

is_stationary 🔗

is_stationary(
    x: ArrayLike,
    algorithm: str = "adf",
    alpha: float = 0.05,
    **kwargs: Union[float, int, str, bool, ArrayLike, None]
) -> dict[str, Union[str, bool, STATIONARITY_ITEM, None]]

Summary

Test whether a given data set is stationary or not.

Details

This function checks the results of a stationarity test against a significance level alpha.

Note that different tests have different null hypotheses: - For ADF, PP, ZA, ERS, VR, RUR: H0 is non-stationarity (unit root). Stationary if p-value < alpha. - For KPSS: H0 is stationarity. Stationary if p-value > alpha.

Parameters:

Name Type Description Default
x ArrayLike

The data to be checked.

required
algorithm str

Which stationarity algorithm to use. Defaults to "adf".

'adf'
alpha float

The significance level for the test. Defaults to 0.05.

0.05
kwargs Union[float, int, str, bool, ArrayLike, None]

Additional arguments to pass to the underlying algorithm.

{}

Returns:

Type Description
dict[str, Union[str, float, bool, None]]

A dictionary containing: - "result" (bool): Indicator if the series is stationary. - "statistic" (float): The test statistic. - "pvalue" (float): The p-value of the test. - "alpha" (float): The significance level used. - "algorithm" (str): The algorithm used.

Examples
Setup
1
2
3
>>> from ts_stat_tests.stationarity.tests import is_stationary
>>> from ts_stat_tests.utils.data import data_normal
>>> normal = data_normal
Example 1: ADF test on stationary data
1
2
3
4
5
>>> res = is_stationary(normal, algorithm="adf")
>>> res["result"]
True
>>> print(f"p-value: {res['pvalue']:.4f}")
p-value: 0.0000
Example 2: KPSS test on stationary data
1
2
3
4
5
>>> res = is_stationary(normal, algorithm="kpss")
>>> res["result"]
True
>>> print(f"p-value: {res['pvalue']:.4f}")
p-value: 0.1000
Example 3: RUR test
1
2
3
4
5
>>> res = is_stationary(normal, algorithm="rur")
>>> res["result"]
True
>>> print(f"p-value: {res['pvalue']:.2f}")
p-value: 0.01
Source code in src/ts_stat_tests/stationarity/tests.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
@typechecked
def is_stationary(
    x: ArrayLike,
    algorithm: str = "adf",
    alpha: float = 0.05,
    **kwargs: Union[float, int, str, bool, ArrayLike, None],
) -> dict[str, Union[str, bool, STATIONARITY_ITEM, None]]:
    """
    !!! note "Summary"
        Test whether a given data set is `stationary` or not.

    ???+ abstract "Details"
        This function checks the results of a stationarity test against a significance level `alpha`.

        Note that different tests have different null hypotheses:
        - For ADF, PP, ZA, ERS, VR, RUR: H0 is non-stationarity (unit root). Stationary if p-value < alpha.
        - For KPSS: H0 is stationarity. Stationary if p-value > alpha.

    Params:
        x (ArrayLike):
            The data to be checked.
        algorithm (str):
            Which stationarity algorithm to use. Defaults to `"adf"`.
        alpha (float, optional):
            The significance level for the test. Defaults to `0.05`.
        kwargs (Union[float, int, str, bool, ArrayLike, None]):
            Additional arguments to pass to the underlying algorithm.

    Returns:
        (dict[str, Union[str, float, bool, None]]):
            A dictionary containing:
            - `"result"` (bool): Indicator if the series is stationary.
            - `"statistic"` (float): The test statistic.
            - `"pvalue"` (float): The p-value of the test.
            - `"alpha"` (float): The significance level used.
            - `"algorithm"` (str): The algorithm used.

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.tests import is_stationary
        >>> from ts_stat_tests.utils.data import data_normal
        >>> normal = data_normal

        ```

        ```pycon {.py .python linenums="1" title="Example 1: ADF test on stationary data"}
        >>> res = is_stationary(normal, algorithm="adf")
        >>> res["result"]
        True
        >>> print(f"p-value: {res['pvalue']:.4f}")
        p-value: 0.0000

        ```

        ```pycon {.py .python linenums="1" title="Example 2: KPSS test on stationary data"}
        >>> res = is_stationary(normal, algorithm="kpss")
        >>> res["result"]
        True
        >>> print(f"p-value: {res['pvalue']:.4f}")
        p-value: 0.1000

        ```

        ```pycon {.py .python linenums="1" title="Example 3: RUR test"}
        >>> res = is_stationary(normal, algorithm="rur")
        >>> res["result"]
        True
        >>> print(f"p-value: {res['pvalue']:.2f}")
        p-value: 0.01

        ```
    """
    res: Any = stationarity(x=x, algorithm=algorithm, **kwargs)

    stat: Any
    pvalue: Any

    # stationarity() always returns a tuple
    res_tuple: Any = res
    stat = res_tuple[0]
    pvalue_or_bool = res_tuple[1]

    # Handle H0 logic
    stationary_h0 = (
        "kpss",
        "kwiatkowski_phillips_schmidt_shin",
    )

    is_stat: bool = False
    pvalue = None

    if isinstance(pvalue_or_bool, bool):
        is_stat = pvalue_or_bool
    elif isinstance(pvalue_or_bool, (int, float)):
        pvalue = pvalue_or_bool
        if algorithm in stationary_h0:
            is_stat = bool(pvalue > alpha)
        else:
            is_stat = bool(pvalue < alpha)

    # Define return dict explicitly to match return type hint
    ret: dict[str, Union[str, bool, STATIONARITY_ITEM, None]] = {
        "result": bool(is_stat),
        "statistic": float(stat) if isinstance(stat, (int, float)) else stat,
        "pvalue": float(pvalue) if pvalue is not None else None,
        "alpha": float(alpha),
        "algorithm": str(algorithm),
    }

    return ret

ts_stat_tests.stationarity.algorithms 🔗

Summary

Stationarity tests are statistical tests used to determine whether a time series is stationary or not. A stationary time series is one whose statistical properties, such as mean and variance, do not change over time. Stationarity is an important assumption in many time series forecasting models, as it allows for the use of techniques such as autoregression and moving averages.

There are several different types of stationarity tests, including the Augmented Dickey-Fuller (ADF) test, the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test, the Phillips-Perron (PP) test, the Elliott-Rothenberg-Stock (ERS) test, and the Variance Ratio (VR) test. Each of these tests has its own strengths and weaknesses, and the choice of which test to use will depend on the specific characteristics of the time series being analyzed.

Overall, stationarity tests are an important tool in time series analysis and forecasting, as they help identify whether a time series is stationary or non-stationary, which can have implications for the choice of forecasting models and methods.

For a really good article on ADF & KPSS tests, check: When A Time Series Only Quacks Like A Duck: Testing for Stationarity Before Running Forecast Models. With Python. And A Duckling Picture.

VALID_ADF_REGRESSION_OPTIONS module-attribute 🔗

VALID_ADF_REGRESSION_OPTIONS = Literal[
    "c", "ct", "ctt", "n"
]

VALID_ADF_AUTOLAG_OPTIONS module-attribute 🔗

VALID_ADF_AUTOLAG_OPTIONS = Literal['AIC', 'BIC', 't-stat']

VALID_KPSS_REGRESSION_OPTIONS module-attribute 🔗

VALID_KPSS_REGRESSION_OPTIONS = Literal['c', 'ct']

VALID_KPSS_NLAGS_OPTIONS module-attribute 🔗

VALID_KPSS_NLAGS_OPTIONS = Literal['auto', 'legacy']

VALID_ZA_REGRESSION_OPTIONS module-attribute 🔗

VALID_ZA_REGRESSION_OPTIONS = Literal['c', 't', 'ct']

VALID_ZA_AUTOLAG_OPTIONS module-attribute 🔗

VALID_ZA_AUTOLAG_OPTIONS = Literal['AIC', 'BIC', 't-stat']

VALID_PP_TREND_OPTIONS module-attribute 🔗

VALID_PP_TREND_OPTIONS = Literal['n', 'c', 'ct']

VALID_PP_TEST_TYPE_OPTIONS module-attribute 🔗

VALID_PP_TEST_TYPE_OPTIONS = Literal['rho', 'tau']

VALID_ERS_TREND_OPTIONS module-attribute 🔗

VALID_ERS_TREND_OPTIONS = Literal['c', 'ct']

VALID_ERS_METHOD_OPTIONS module-attribute 🔗

VALID_ERS_METHOD_OPTIONS = Literal['aic', 'bic', 't-stat']

VALID_VR_TREND_OPTIONS module-attribute 🔗

VALID_VR_TREND_OPTIONS = Literal['c', 'n']

adf 🔗

adf(
    x: ArrayLike,
    maxlag: Optional[int] = None,
    regression: VALID_ADF_REGRESSION_OPTIONS = "c",
    *,
    autolag: Optional[VALID_ADF_AUTOLAG_OPTIONS] = "AIC",
    store: Literal[True],
    regresults: bool = False
) -> tuple[float, float, dict, ResultsStore]
adf(
    x: ArrayLike,
    maxlag: Optional[int] = None,
    regression: VALID_ADF_REGRESSION_OPTIONS = "c",
    *,
    autolag: None,
    store: Literal[False] = False,
    regresults: bool = False
) -> tuple[float, float, int, int, dict]
adf(
    x: ArrayLike,
    maxlag: Optional[int] = None,
    regression: VALID_ADF_REGRESSION_OPTIONS = "c",
    *,
    autolag: VALID_ADF_AUTOLAG_OPTIONS = "AIC",
    store: Literal[False] = False,
    regresults: bool = False
) -> tuple[float, float, int, int, dict, float]
adf(
    x: ArrayLike,
    maxlag: Optional[int] = None,
    regression: VALID_ADF_REGRESSION_OPTIONS = "c",
    *,
    autolag: Optional[VALID_ADF_AUTOLAG_OPTIONS] = "AIC",
    store: bool = False,
    regresults: bool = False
) -> Union[
    tuple[float, float, dict, ResultsStore],
    tuple[float, float, int, int, dict],
    tuple[float, float, int, int, dict, float],
]

Summary

The Augmented Dickey-Fuller test can be used to test for a unit root in a univariate process in the presence of serial correlation.

Details

The Augmented Dickey-Fuller (ADF) test is a statistical test used to determine whether a time series is stationary or not. Stationarity refers to the property of a time series where the statistical properties, such as mean and variance, remain constant over time. Stationarity is important for time series forecasting as it allows for the use of many popular forecasting models, such as ARIMA.

The ADF test is an extension of the Dickey-Fuller test and involves regressing the first-difference of the time series on its lagged values, and then testing whether the coefficient of the lagged first-difference term is statistically significant. If it is, then the time series is considered non-stationary.

The null hypothesis of the ADF test is that the time series has a unit root, which means that it is non-stationary. The alternative hypothesis is that the time series is stationary. If the p-value of the test is less than a chosen significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary.

In practical terms, if a time series is found to be non-stationary by the ADF test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

Parameters:

Name Type Description Default
x ArrayLike

The data series to test.

required
maxlag Optional[int]

Maximum lag which is included in test, default value of \(12 \times (\frac{nobs}{100})^{\frac{1}{4}}\) is used when None. Default: None

None
regression VALID_ADF_REGRESSION_OPTIONS

Constant and trend order to include in regression.

  • "c": constant only (default).
  • "ct": constant and trend.
  • "ctt": constant, and linear and quadratic trend.
  • "n": no constant, no trend.

Default: "c"

'c'
autolag Optional[VALID_ADF_AUTOLAG_OPTIONS]

Method to use when automatically determining the lag length among the values \(0, 1, ..., maxlag\).

  • If "AIC" (default) or "BIC", then the number of lags is chosen to minimize the corresponding information criterion.
  • "t-stat" based choice of maxlag. Starts with maxlag and drops a lag until the t-statistic on the last lag length is significant using a 5%-sized test.
  • If None, then the number of included lags is set to maxlag.

Default: "AIC"

'AIC'
store bool

If True, then a result instance is returned additionally to the adf statistic. Default: False

False
regresults bool

If True, the full regression results are returned. Default: False

False

Returns:

Type Description
Union[tuple[float, float, dict, ResultsStore], tuple[float, float, int, int, dict], tuple[float, float, int, int, dict, float]]

Depending on parameters, returns a tuple containing: - adf (float): The test statistic. - pvalue (float): MacKinnon's approximate p-value. - uselag (int): The number of lags used. - nobs (int): The number of observations used. - critical_values (dict): Critical values at the 1%, 5%, and 10% levels. - icbest (float): The maximized information criterion (if autolag is not None). - resstore (Optional[ResultsStore]): Result instance (if store is True).

Examples
Setup
1
2
3
4
>>> from ts_stat_tests.stationarity.algorithms import adf
>>> from ts_stat_tests.utils.data import data_airline, data_normal
>>> normal = data_normal
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
>>> stat, pvalue, lags, nobs, crit, icbest = adf(x=normal)
>>> print(f"ADF statistic: {stat:.4f}")
ADF statistic: -30.7838
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
Example 2: Airline Passengers Data
1
2
3
>>> stat, pvalue, lags, nobs, crit, icbest = adf(x=airline)
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.9919
Example 3: Store Result Instance
1
2
3
>>> res = adf(x=airline, store=True)
>>> print(res)
(0.8153..., 0.9918..., {'1%': np.float64(-3.4816...), '5%': np.float64(-2.8840...), '10%': np.float64(-2.5787...)}, <statsmodels.stats.diagnostic.ResultsStore object at ...>)
Example 4: No Autolag
1
2
3
>>> stat, pvalue, lags, nobs, crit = adf(x=airline, autolag=None, maxlag=5)
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.7670
Calculation

The mathematical equation for the Augmented Dickey-Fuller (ADF) test for stationarity in time series forecasting is:

\[ \Delta y_t = \alpha + \beta y_{t-1} + \sum_{i=1}^p \delta_i \Delta y_{t-i} + \epsilon_t \]

where:

  • \(y_t\) is the value of the time series at time \(t\).
  • \(\Delta y_t\) is the first difference of \(y_t\), which is defined as \(\Delta y_t = y_t - y_{t-1}\).
  • \(\alpha\) is the constant term.
  • \(\beta\) is the coefficient on \(y_{t-1}\).
  • \(\delta_i\) are the coefficients on the lagged differences of \(y\).
  • \(\epsilon_t\) is the error term.

The ADF test involves testing the null hypothesis that \(\beta = 0\), or equivalently, that the time series has a unit root. If \(\beta\) is significantly different from \(0\), then the null hypothesis can be rejected and the time series is considered stationary.

Here are the detailed steps for how to calculate the ADF test:

  1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

  2. Calculate the first differences of the time series, which is simply the difference between each observation and the previous observation. This step is performed to transform the original data into a stationary process. The first difference of \(y_t\) is defined as \(\Delta y_t = y_t - y_{t-1}\).

  3. Estimate the parameters \(\alpha\), \(\beta\), and \(\delta_i\) using the least squares method. This involves regressing \(\Delta y_t\) on its lagged values, \(y_{t-1}\), and the lagged differences of \(y, \Delta y_{t-1}, \Delta y_{t-2}, \dots, \Delta y_{t-p}\), where \(p\) is the number of lags to include in the model. The estimated equation is:

    \[ \Delta y_t = \alpha + \beta y_{t-1} + \sum_{i=1}^p \delta_i \Delta y_{t-i} + \epsilon_t \]
  4. Calculate the test statistic, which is given by:

    \[ ADF = \frac {\beta-1}{SE(\beta)} \]
    • where \(SE(\beta)\) is the standard error of the coefficient on \(y_{t-1}\).

    The test statistic measures the number of standard errors by which \(\beta\) deviates from \(1\). If ADF is less than the critical values from the ADF distribution table, we can reject the null hypothesis and conclude that the time series is stationary.

  5. Compare the test statistic to the critical values in the ADF distribution table to determine the level of significance. The critical values depend on the sample size, the level of significance, and the number of lags in the model.

  6. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is stationary and can be used for forecasting. If the null hypothesis is not rejected, then the time series is non-stationary and requires further pre-processing before it can be used for forecasting.

Notes

The null hypothesis of the Augmented Dickey-Fuller is that there is a unit root, with the alternative that there is no unit root. If the p-value is above a critical size, then we cannot reject that there is a unit root.

The p-values are obtained through regression surface approximation from MacKinnon 1994, but using the updated 2010 tables. If the p-value is close to significant, then the critical values should be used to judge whether to reject the null.

The autolag option and maxlag for it are described in Greene.

Credit
References
  • Baum, C.F. (2004). ZANDREWS: Stata module to calculate Zivot-Andrews unit root test in presence of structural break," Statistical Software Components S437301, Boston College Department of Economics, revised 2015.
  • Schwert, G.W. (1989). Tests for unit roots: A Monte Carlo investigation. Journal of Business & Economic Statistics, 7: 147-159.
  • Zivot, E., and Andrews, D.W.K. (1992). Further evidence on the great crash, the oil-price shock, and the unit-root hypothesis. Journal of Business & Economic Studies, 10: 251-270.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
@typechecked
def adf(
    x: ArrayLike,
    maxlag: Optional[int] = None,
    regression: VALID_ADF_REGRESSION_OPTIONS = "c",
    *,
    autolag: Optional[VALID_ADF_AUTOLAG_OPTIONS] = "AIC",
    store: bool = False,
    regresults: bool = False,
) -> Union[
    tuple[float, float, dict, ResultsStore],
    tuple[float, float, int, int, dict],
    tuple[float, float, int, int, dict, float],
]:
    r"""
    !!! note "Summary"
        The Augmented Dickey-Fuller test can be used to test for a unit root in a univariate process in the presence of serial correlation.

    ???+ abstract "Details"

        The Augmented Dickey-Fuller (ADF) test is a statistical test used to determine whether a time series is stationary or not. Stationarity refers to the property of a time series where the statistical properties, such as mean and variance, remain constant over time. Stationarity is important for time series forecasting as it allows for the use of many popular forecasting models, such as ARIMA.

        The ADF test is an extension of the Dickey-Fuller test and involves regressing the first-difference of the time series on its lagged values, and then testing whether the coefficient of the lagged first-difference term is statistically significant. If it is, then the time series is considered non-stationary.

        The null hypothesis of the ADF test is that the time series has a unit root, which means that it is non-stationary. The alternative hypothesis is that the time series is stationary. If the p-value of the test is less than a chosen significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary.

        In practical terms, if a time series is found to be non-stationary by the ADF test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

    Params:
        x (ArrayLike):
            The data series to test.
        maxlag (Optional[int]):
            Maximum lag which is included in test, default value of $12 \times (\frac{nobs}{100})^{\frac{1}{4}}$ is used when `None`.
            Default: `None`
        regression (VALID_ADF_REGRESSION_OPTIONS):
            Constant and trend order to include in regression.

            - `"c"`: constant only (default).
            - `"ct"`: constant and trend.
            - `"ctt"`: constant, and linear and quadratic trend.
            - `"n"`: no constant, no trend.

            Default: `"c"`
        autolag (Optional[VALID_ADF_AUTOLAG_OPTIONS]):
            Method to use when automatically determining the lag length among the values $0, 1, ..., maxlag$.

            - If `"AIC"` (default) or `"BIC"`, then the number of lags is chosen to minimize the corresponding information criterion.
            - `"t-stat"` based choice of `maxlag`. Starts with `maxlag` and drops a lag until the t-statistic on the last lag length is significant using a 5%-sized test.
            - If `None`, then the number of included lags is set to `maxlag`.

            Default: `"AIC"`
        store (bool):
            If `True`, then a result instance is returned additionally to the `adf` statistic.
            Default: `False`
        regresults (bool):
            If `True`, the full regression results are returned.
            Default: `False`

    Returns:
        (Union[tuple[float, float, dict, ResultsStore], tuple[float, float, int, int, dict], tuple[float, float, int, int, dict, float]]):
            Depending on parameters, returns a tuple containing:
            - `adf` (float): The test statistic.
            - `pvalue` (float): MacKinnon's approximate p-value.
            - `uselag` (int): The number of lags used.
            - `nobs` (int): The number of observations used.
            - `critical_values` (dict): Critical values at the 1%, 5%, and 10% levels.
            - `icbest` (float): The maximized information criterion (if `autolag` is not `None`).
            - `resstore` (Optional[ResultsStore]): Result instance (if `store` is `True`).

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.algorithms import adf
        >>> from ts_stat_tests.utils.data import data_airline, data_normal
        >>> normal = data_normal
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, lags, nobs, crit, icbest = adf(x=normal)
        >>> print(f"ADF statistic: {stat:.4f}")
        ADF statistic: -30.7838
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Airline Passengers Data"}
        >>> stat, pvalue, lags, nobs, crit, icbest = adf(x=airline)
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.9919

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Store Result Instance"}
        >>> res = adf(x=airline, store=True)
        >>> print(res)
        (0.8153..., 0.9918..., {'1%': np.float64(-3.4816...), '5%': np.float64(-2.8840...), '10%': np.float64(-2.5787...)}, <statsmodels.stats.diagnostic.ResultsStore object at ...>)

        ```

        ```pycon {.py .python linenums="1" title="Example 4: No Autolag"}
        >>> stat, pvalue, lags, nobs, crit = adf(x=airline, autolag=None, maxlag=5)
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.7670

        ```

    ??? equation "Calculation"

        The mathematical equation for the Augmented Dickey-Fuller (ADF) test for stationarity in time series forecasting is:

        $$
        \Delta y_t = \alpha + \beta y_{t-1} + \sum_{i=1}^p \delta_i \Delta y_{t-i} + \epsilon_t
        $$

        where:

        - $y_t$ is the value of the time series at time $t$.
        - $\Delta y_t$ is the first difference of $y_t$, which is defined as $\Delta y_t = y_t - y_{t-1}$.
        - $\alpha$ is the constant term.
        - $\beta$ is the coefficient on $y_{t-1}$.
        - $\delta_i$ are the coefficients on the lagged differences of $y$.
        - $\epsilon_t$ is the error term.

        The ADF test involves testing the null hypothesis that $\beta = 0$, or equivalently, that the time series has a unit root. If $\beta$ is significantly different from $0$, then the null hypothesis can be rejected and the time series is considered stationary.

        Here are the detailed steps for how to calculate the ADF test:

        1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

        1. Calculate the first differences of the time series, which is simply the difference between each observation and the previous observation. This step is performed to transform the original data into a stationary process. The first difference of $y_t$ is defined as $\Delta y_t = y_t - y_{t-1}$.

        1. Estimate the parameters $\alpha$, $\beta$, and $\delta_i$ using the least squares method. This involves regressing $\Delta y_t$ on its lagged values, $y_{t-1}$, and the lagged differences of $y, \Delta y_{t-1}, \Delta y_{t-2}, \dots, \Delta y_{t-p}$, where $p$ is the number of lags to include in the model. The estimated equation is:

            $$
            \Delta y_t = \alpha + \beta y_{t-1} + \sum_{i=1}^p \delta_i \Delta y_{t-i} + \epsilon_t
            $$

        1. Calculate the test statistic, which is given by:

            $$
            ADF = \frac {\beta-1}{SE(\beta)}
            $$

            - where $SE(\beta)$ is the standard error of the coefficient on $y_{t-1}$.

            The test statistic measures the number of standard errors by which $\beta$ deviates from $1$. If ADF is less than the critical values from the ADF distribution table, we can reject the null hypothesis and conclude that the time series is stationary.

        1. Compare the test statistic to the critical values in the ADF distribution table to determine the level of significance. The critical values depend on the sample size, the level of significance, and the number of lags in the model.

        1. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is stationary and can be used for forecasting. If the null hypothesis is not rejected, then the time series is non-stationary and requires further pre-processing before it can be used for forecasting.

    ??? note "Notes"
        The null hypothesis of the Augmented Dickey-Fuller is that there is a unit root, with the alternative that there is no unit root. If the p-value is above a critical size, then we cannot reject that there is a unit root.

        The p-values are obtained through regression surface approximation from MacKinnon 1994, but using the updated 2010 tables. If the p-value is close to significant, then the critical values should be used to judge whether to reject the null.

        The `autolag` option and `maxlag` for it are described in Greene.

    ??? success "Credit"
        - All credit goes to the [`statsmodels`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html) library.

    ??? question "References"
        - Baum, C.F. (2004). ZANDREWS: Stata module to calculate Zivot-Andrews unit root test in presence of structural break," Statistical Software Components S437301, Boston College Department of Economics, revised 2015.
        - Schwert, G.W. (1989). Tests for unit roots: A Monte Carlo investigation. Journal of Business & Economic Statistics, 7: 147-159.
        - Zivot, E., and Andrews, D.W.K. (1992). Further evidence on the great crash, the oil-price shock, and the unit-root hypothesis. Journal of Business & Economic Studies, 10: 251-270.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    res: Any = _adfuller(  # Using `Any` to avoid ty issues with statsmodels stubs
        x=x,
        maxlag=maxlag,
        regression=regression,
        autolag=autolag,  # type: ignore[arg-type] # statsmodels stubs are often missing `None`
        store=store,
        regresults=regresults,
    )

    if store:
        # returns (stat, pval, crit, store)
        return float(res[0]), float(res[1]), dict(res[2]), res[3]

    if autolag is None:
        # returns (stat, pval, lags, nobs, crit)
        return (
            float(res[0]),
            float(res[1]),
            int(res[2]),
            int(res[3]),
            dict(res[4]),
        )

    # returns (stat, pval, lags, nobs, crit, icbest)
    return (
        float(res[0]),
        float(res[1]),
        int(res[2]),
        int(res[3]),
        dict(res[4]),
        float(res[5]),
    )

kpss 🔗

kpss(
    x: ArrayLike,
    regression: VALID_KPSS_REGRESSION_OPTIONS = "c",
    nlags: Optional[
        Union[VALID_KPSS_NLAGS_OPTIONS, int]
    ] = None,
    *,
    store: Literal[True]
) -> tuple[float, float, int, dict, ResultsStore]
kpss(
    x: ArrayLike,
    regression: VALID_KPSS_REGRESSION_OPTIONS = "c",
    nlags: Optional[
        Union[VALID_KPSS_NLAGS_OPTIONS, int]
    ] = None,
    *,
    store: Literal[False] = False
) -> tuple[float, float, int, dict]
kpss(
    x: ArrayLike,
    regression: VALID_KPSS_REGRESSION_OPTIONS = "c",
    nlags: Optional[
        Union[VALID_KPSS_NLAGS_OPTIONS, int]
    ] = None,
    *,
    store: bool = False
) -> Union[
    tuple[float, float, int, dict, ResultsStore],
    tuple[float, float, int, dict],
]

Summary

Computes the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test for the null hypothesis that x is level or trend stationary.

Details

The Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test is another statistical test used to determine whether a time series is stationary or not. The KPSS test is the opposite of the Augmented Dickey-Fuller (ADF) test, which tests for the presence of a unit root in the time series.

The KPSS test involves regressing the time series on a constant and a time trend. The null hypothesis of the test is that the time series is stationary. The alternative hypothesis is that the time series has a unit root, which means that it is non-stationary.

The test statistic is calculated by taking the sum of the squared residuals of the regression. If the test statistic is greater than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is non-stationary. If the test statistic is less than the critical value, then we fail to reject the null hypothesis and conclude that the time series is stationary.

In practical terms, if a time series is found to be non-stationary by the KPSS test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

Overall, the ADF and KPSS tests are both important tools in time series analysis and forecasting, as they help identify whether a time series is stationary or non-stationary, which can have implications for the choice of forecasting models and methods.

Parameters:

Name Type Description Default
x ArrayLike

The data series to test.

required
regression VALID_KPSS_REGRESSION_OPTIONS

The null hypothesis for the KPSS test.

  • "c": The data is stationary around a constant (default).
  • "ct": The data is stationary around a trend.

Defaults to "c".

'c'
nlags Optional[Union[VALID_KPSS_NLAGS_OPTIONS, int]]

Indicates the number of lags to be used.

  • If "auto" (default), lags is calculated using the data-dependent method of Hobijn et al. (1998). See also Andrews (1991), Newey & West (1994), and Schwert (1989).
  • If set to "legacy", uses \(int(12 \\times (\\frac{n}{100})^{\\frac{1}{4}})\), as outlined in Schwert (1989).

Defaults to None.

None
store bool

If True, then a result instance is returned additionally to the KPSS statistic.
Defaults to False.

False

Returns:

Type Description
Union[tuple[float, float, int, dict, ResultsStore], tuple[float, float, int, dict]]

Returns a tuple containing: - stat (float): The KPSS test statistic. - pvalue (float): The p-value of the test. - lags (int): The truncation lag parameter. - crit (dict): The critical values at 10%, 5%, 2.5%, and 1%. - resstore (Optional[ResultsStore]): Result instance (if store is True).

Examples
Setup
1
2
3
4
>>> from ts_stat_tests.stationarity.algorithms import kpss
>>> from ts_stat_tests.utils.data import data_airline, data_normal
>>> normal = data_normal
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = kpss(x=normal)
>>> print(f"KPSS statistic: {stat:.4f}")
KPSS statistic: 0.0858
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.1000
Example 2: Airline Passengers Data
1
2
3
>>> stat, pvalue, lags, crit = kpss(x=airline)
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0100
Calculation

The mathematical equation for the KPSS test for stationarity in time series forecasting is:

\[ y_t = \mu_t + \epsilon_t \]

where:

  • \(y_t\) is the value of the time series at time \(t\).
  • \(\mu_t\) is the trend component of the time series.
  • \(\epsilon_t\) is the error term.

The KPSS test involves testing the null hypothesis that the time series is trend stationary, which means that the trend component of the time series is stationary over time. If the null hypothesis is rejected, then the time series is non-stationary and requires further pre-processing before it can be used for forecasting.

Here are the detailed steps for how to calculate the KPSS test:

  1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

  2. Divide your time series data into multiple overlapping windows of equal size. The length of each window depends on the length of your time series and the level of detail you want to capture.

  3. Calculate the trend component \(\mu_t\) for each window using a trend estimation method. There are several methods for estimating the trend component, such as the Hodrick-Prescott filter, the Christiano-Fitzgerald filter, or simple linear regression. The choice of method depends on the characteristics of your data and the level of accuracy you want to achieve.

  4. Calculate the residual series \(\epsilon_t\) by subtracting the trend component from the original time series:

    \[ \epsilon_t = y_t - \mu_t \]
  5. Estimate the variance of the residual series using a suitable estimator, such as the Newey-West estimator or the Bartlett kernel estimator. This step is necessary to correct for any serial correlation in the residual series.

  6. Calculate the test statistic, which is given by:

    \[ KPSS = T \times \sum_{t=1}^T \frac {S_t^2} {\sigma^2} \]

    where:

    • \(T\) is the number of observations in the time series.
    • \(S_t\) is the cumulative sum of the residual series up to time \(t\), i.e., \(S_t = \sum_{i=1}^t \epsilon_i\).
    • \(\sigma^2\) is the estimated variance of the residual series.

    The test statistic measures the strength of the trend component relative to the residual series. If KPSS is greater than the critical values from the KPSS distribution table, we can reject the null hypothesis and conclude that the time series is non-stationary.

  7. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary and requires further pre-processing before it can be used for forecasting. If the null hypothesis is not rejected, then the time series is trend stationary and can be used for forecasting.

Notes

To estimate \(\sigma^2\) the Newey-West estimator is used. If lags is "legacy", the truncation lag parameter is set to \(int(12 \times (\frac{n}{100})^{\frac{1}{4}})\), as outlined in Schwert (1989). The p-values are interpolated from Table 1 of Kwiatkowski et al. (1992). If the computed statistic is outside the table of critical values, then a warning message is generated.

Missing values are not handled.

Credit
References
  • Andrews, D.W.K. (1991). Heteroskedasticity and autocorrelation consistent covariance matrix estimation. Econometrica, 59: 817-858.
  • Hobijn, B., Frances, B.H., & Ooms, M. (2004). Generalizations of the KPSS-test for stationarity. Statistica Neerlandica, 52: 483-502.
  • Kwiatkowski, D., Phillips, P.C.B., Schmidt, P., & Shin, Y. (1992). Testing the null hypothesis of stationarity against the alternative of a unit root. Journal of Econometrics, 54: 159-178.
  • Newey, W.K., & West, K.D. (1994). Automatic lag selection in covariance matrix estimation. Review of Economic Studies, 61: 631-653.
  • Schwert, G. W. (1989). Tests for unit roots: A Monte Carlo investigation. Journal of Business and Economic Statistics, 7 (2): 147-159.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
@typechecked
def kpss(
    x: ArrayLike,
    regression: VALID_KPSS_REGRESSION_OPTIONS = "c",
    nlags: Optional[Union[VALID_KPSS_NLAGS_OPTIONS, int]] = None,
    *,
    store: bool = False,
) -> Union[
    tuple[float, float, int, dict, ResultsStore],
    tuple[float, float, int, dict],
]:
    r"""
    !!! note "Summary"
        Computes the Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test for the null hypothesis that `x` is level or trend stationary.

    ???+ abstract "Details"

        The Kwiatkowski-Phillips-Schmidt-Shin (KPSS) test is another statistical test used to determine whether a time series is stationary or not. The KPSS test is the opposite of the Augmented Dickey-Fuller (ADF) test, which tests for the presence of a unit root in the time series.

        The KPSS test involves regressing the time series on a constant and a time trend. The null hypothesis of the test is that the time series is stationary. The alternative hypothesis is that the time series has a unit root, which means that it is non-stationary.

        The test statistic is calculated by taking the sum of the squared residuals of the regression. If the test statistic is greater than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is non-stationary. If the test statistic is less than the critical value, then we fail to reject the null hypothesis and conclude that the time series is stationary.

        In practical terms, if a time series is found to be non-stationary by the KPSS test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

        Overall, the ADF and KPSS tests are both important tools in time series analysis and forecasting, as they help identify whether a time series is stationary or non-stationary, which can have implications for the choice of forecasting models and methods.

    Params:
        x (ArrayLike):
            The data series to test.
        regression (VALID_KPSS_REGRESSION_OPTIONS, optional):
            The null hypothesis for the KPSS test.

            - `"c"`: The data is stationary around a constant (default).
            - `"ct"`: The data is stationary around a trend.

            Defaults to `"c"`.
        nlags (Optional[Union[VALID_KPSS_NLAGS_OPTIONS, int]], optional):
            Indicates the number of lags to be used.

            - If `"auto"` (default), `lags` is calculated using the data-dependent method of Hobijn et al. (1998). See also Andrews (1991), Newey & West (1994), and Schwert (1989).
            - If set to `"legacy"`, uses $int(12 \\times (\\frac{n}{100})^{\\frac{1}{4}})$, as outlined in Schwert (1989).

            Defaults to `None`.
        store (bool, optional):
            If `True`, then a result instance is returned additionally to the KPSS statistic.<br>
            Defaults to `False`.

    Returns:
        (Union[tuple[float, float, int, dict, ResultsStore], tuple[float, float, int, dict]]):
            Returns a tuple containing:
            - `stat` (float): The KPSS test statistic.
            - `pvalue` (float): The p-value of the test.
            - `lags` (int): The truncation lag parameter.
            - `crit` (dict): The critical values at 10%, 5%, 2.5%, and 1%.
            - `resstore` (Optional[ResultsStore]): Result instance (if `store` is `True`).

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.algorithms import kpss
        >>> from ts_stat_tests.utils.data import data_airline, data_normal
        >>> normal = data_normal
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, lags, crit = kpss(x=normal)
        >>> print(f"KPSS statistic: {stat:.4f}")
        KPSS statistic: 0.0858
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.1000

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Airline Passengers Data"}
        >>> stat, pvalue, lags, crit = kpss(x=airline)
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0100

        ```

    ??? equation "Calculation"

        The mathematical equation for the KPSS test for stationarity in time series forecasting is:

        $$
        y_t = \mu_t + \epsilon_t
        $$

        where:

        - $y_t$ is the value of the time series at time $t$.
        - $\mu_t$ is the trend component of the time series.
        - $\epsilon_t$ is the error term.

        The KPSS test involves testing the null hypothesis that the time series is trend stationary, which means that the trend component of the time series is stationary over time. If the null hypothesis is rejected, then the time series is non-stationary and requires further pre-processing before it can be used for forecasting.

        Here are the detailed steps for how to calculate the KPSS test:

        1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

        1. Divide your time series data into multiple overlapping windows of equal size. The length of each window depends on the length of your time series and the level of detail you want to capture.

        1. Calculate the trend component $\mu_t$ for each window using a trend estimation method. There are several methods for estimating the trend component, such as the Hodrick-Prescott filter, the Christiano-Fitzgerald filter, or simple linear regression. The choice of method depends on the characteristics of your data and the level of accuracy you want to achieve.

        1. Calculate the residual series $\epsilon_t$ by subtracting the trend component from the original time series:

            $$
            \epsilon_t = y_t - \mu_t
            $$

        1. Estimate the variance of the residual series using a suitable estimator, such as the Newey-West estimator or the Bartlett kernel estimator. This step is necessary to correct for any serial correlation in the residual series.

        1. Calculate the test statistic, which is given by:

            $$
            KPSS = T \times \sum_{t=1}^T \frac {S_t^2} {\sigma^2}
            $$

            where:

            - $T$ is the number of observations in the time series.
            - $S_t$ is the cumulative sum of the residual series up to time $t$, i.e., $S_t = \sum_{i=1}^t \epsilon_i$.
            - $\sigma^2$ is the estimated variance of the residual series.

            The test statistic measures the strength of the trend component relative to the residual series. If KPSS is greater than the critical values from the KPSS distribution table, we can reject the null hypothesis and conclude that the time series is non-stationary.

        1. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary and requires further pre-processing before it can be used for forecasting. If the null hypothesis is not rejected, then the time series is trend stationary and can be used for forecasting.

    ??? note "Notes"
        To estimate $\sigma^2$ the Newey-West estimator is used. If `lags` is `"legacy"`, the truncation lag parameter is set to $int(12 \times (\frac{n}{100})^{\frac{1}{4}})$, as outlined in Schwert (1989). The p-values are interpolated from Table 1 of Kwiatkowski et al. (1992). If the computed statistic is outside the table of critical values, then a warning message is generated.

        Missing values are not handled.

    ??? success "Credit"
        - All credit goes to the [`statsmodels`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html) library.

    ??? question "References"
        - Andrews, D.W.K. (1991). Heteroskedasticity and autocorrelation consistent covariance matrix estimation. Econometrica, 59: 817-858.
        - Hobijn, B., Frances, B.H., & Ooms, M. (2004). Generalizations of the KPSS-test for stationarity. Statistica Neerlandica, 52: 483-502.
        - Kwiatkowski, D., Phillips, P.C.B., Schmidt, P., & Shin, Y. (1992). Testing the null hypothesis of stationarity against the alternative of a unit root. Journal of Econometrics, 54: 159-178.
        - Newey, W.K., & West, K.D. (1994). Automatic lag selection in covariance matrix estimation. Review of Economic Studies, 61: 631-653.
        - Schwert, G. W. (1989). Tests for unit roots: A Monte Carlo investigation. Journal of Business and Economic Statistics, 7 (2): 147-159.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    _nlags: Union[VALID_KPSS_NLAGS_OPTIONS, int] = nlags if nlags is not None else "auto"
    return _kpss(x=x, regression=regression, nlags=_nlags, store=store)

rur 🔗

rur(
    x: ArrayLike, *, store: Literal[True]
) -> tuple[float, float, dict, ResultsStore]
rur(
    x: ArrayLike, *, store: Literal[False] = False
) -> tuple[float, float, dict]
rur(x: ArrayLike, *, store: bool = False) -> Union[
    tuple[float, float, dict, ResultsStore],
    tuple[float, float, dict],
]

Summary

Computes the Range Unit-Root (RUR) test for the null hypothesis that x is stationary.

Details

The Range Unit-Root (RUR) test is a statistical test used to determine whether a time series is stationary or not. It is based on the range of the time series and does not require any knowledge of the underlying stochastic process.

The RUR test involves dividing the time series into non-overlapping windows of a fixed size and calculating the range of each window. Then, the range of the entire time series is calculated. If the time series is stationary, the range of the entire time series should be proportional to the square root of the window size. If the time series is non-stationary, the range of the entire time series will grow with the window size.

The null hypothesis of the RUR test is that the time series is non-stationary (unit root). The alternative hypothesis is that the time series is stationary. If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

In practical terms, if a time series is found to be non-stationary by the RUR test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

The RUR test is a simple and computationally efficient test for stationarity, but it may not be as powerful as other unit root tests in detecting non-stationarity in some cases. It is important to use multiple tests to determine the stationarity of a time series, as no single test is perfect in all situations.

Parameters:

Name Type Description Default
x ArrayLike

The data series to test.

required
store bool

If True, then a result instance is returned additionally to the RUR statistic.
Defaults to False.

False

Returns:

Type Description
Union[tuple[float, float, dict, ResultsStore], tuple[float, float, dict]]

Returns a tuple containing: - stat (float): The RUR test statistic. - pvalue (float): The p-value of the test. - crit (dict): The critical values at 10%, 5%, 2.5%, and 1%. - resstore (Optional[ResultsStore]): Result instance (if store is True).

Examples
Setup
1
2
3
4
5
6
>>> from ts_stat_tests.utils.data import data_airline, data_normal, data_trend, data_sine
>>> from ts_stat_tests.stationarity.algorithms import rur
>>> normal = data_normal
>>> trend = data_trend
>>> seasonal = data_sine
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
>>> stat, pvalue, crit = rur(x=normal)
>>> print(f"RUR statistic: {stat:.4f}")
RUR statistic: 0.3479
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0100
Example 2: Trend-Stationary Series
1
2
3
4
5
>>> stat, pvalue, crit = rur(x=trend)
>>> print(f"RUR statistic: {stat:.4f}")
RUR statistic: 31.5912
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.9500
Example 3: Seasonal Series
1
2
3
4
5
>>> stat, pvalue, crit = rur(x=seasonal)
>>> print(f"RUR statistic: {stat:.4f}")
RUR statistic: 0.9129
>>> print(f"p-value: {pvalue:.04f}")
p-value: 0.0100
Example 4: Real-World Time Series
1
2
3
4
5
>>> stat, pvalue, crit = rur(x=airline)
>>> print(f"RUR statistic: {stat:.4f}")
RUR statistic: 2.3333
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.9000
Calculation

The mathematical equation for the RUR test is:

\[ y_t = \rho y_{t-1} + \epsilon_t \]

where:

  • \(y_t\) is the value of the time series at time \(t\).
  • \(\rho\) is the parameter of the unit root process.
  • \(y_{t-1}\) is the value of the time series at time \(t-1\).
  • \(\epsilon_t\) is a stationary error term with mean zero and constant variance.

The null hypothesis of the RUR test is that the time series is stationary, and the alternative hypothesis is that the time series is non-stationary with a unit root.

Here are the detailed steps for how to calculate the RUR test:

  1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

  2. Estimate the parameter \(\rho\) using the ordinary least squares method. This involves regressing \(y_t\) on \(y_{t-1}\). The estimated equation is:

    \[ y_t = \alpha + \rho y_{t-1} + \epsilon_t \]

    where:

    • \(\alpha\) is the intercept.
    • \(\epsilon_t\) is the error term.
  3. Calculate the range of the time series, which is the difference between the maximum and minimum values of the time series:

    \[ R = \max(y_t) - \min(y_t) \]
  4. Calculate the expected range of the time series under the null hypothesis of stationarity, which is given by:

    \[ E(R) = \frac {T - 1} {2 \sqrt{T}} \]

    where:

    • \(T\) is the sample size.
  5. Calculate the test statistic, which is given by:

    \[ RUR = \frac {R - E(R)} {E(R)} \]
  6. Compare the test statistic to the critical values in the RUR distribution table to determine the level of significance. The critical values depend on the sample size and the level of significance.

  7. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary with a unit root. If the null hypothesis is not rejected, then the time series is stationary.

In practice, the RUR test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of parameters and calculation of the test statistic.

Notes

The p-values are interpolated from Table 1 of Aparicio et al. (2006). If the computed statistic is outside the table of critical values, then a warning message is generated.

Missing values are not handled.

Credit

References
  • Aparicio, F., Escribano A., Sipols, A.E. (2006). Range Unit-Root (RUR) tests: robust against nonlinearities, error distributions, structural breaks and outliers. Journal of Time Series Analysis, 27 (4): 545-576.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
@typechecked
def rur(x: ArrayLike, *, store: bool = False) -> Union[
    tuple[float, float, dict, ResultsStore],
    tuple[float, float, dict],
]:
    r"""
    !!! note "Summary"
        Computes the Range Unit-Root (RUR) test for the null hypothesis that x is stationary.

    ???+ abstract "Details"

        The Range Unit-Root (RUR) test is a statistical test used to determine whether a time series is stationary or not. It is based on the range of the time series and does not require any knowledge of the underlying stochastic process.

        The RUR test involves dividing the time series into non-overlapping windows of a fixed size and calculating the range of each window. Then, the range of the entire time series is calculated. If the time series is stationary, the range of the entire time series should be proportional to the square root of the window size. If the time series is non-stationary, the range of the entire time series will grow with the window size.

        The null hypothesis of the RUR test is that the time series is non-stationary (unit root). The alternative hypothesis is that the time series is stationary. If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

        In practical terms, if a time series is found to be non-stationary by the RUR test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

        The RUR test is a simple and computationally efficient test for stationarity, but it may not be as powerful as other unit root tests in detecting non-stationarity in some cases. It is important to use multiple tests to determine the stationarity of a time series, as no single test is perfect in all situations.

    Params:
        x (ArrayLike):
            The data series to test.
        store (bool, optional):
            If `True`, then a result instance is returned additionally to the RUR statistic.<br>
            Defaults to `False`.

    Returns:
        (Union[tuple[float, float, dict, ResultsStore], tuple[float, float, dict]]):
            Returns a tuple containing:
            - `stat` (float): The RUR test statistic.
            - `pvalue` (float): The p-value of the test.
            - `crit` (dict): The critical values at 10%, 5%, 2.5%, and 1%.
            - `resstore` (Optional[ResultsStore]): Result instance (if `store` is `True`).

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.utils.data import data_airline, data_normal, data_trend, data_sine
        >>> from ts_stat_tests.stationarity.algorithms import rur
        >>> normal = data_normal
        >>> trend = data_trend
        >>> seasonal = data_sine
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, crit = rur(x=normal)
        >>> print(f"RUR statistic: {stat:.4f}")
        RUR statistic: 0.3479
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0100

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Trend-Stationary Series"}
        >>> stat, pvalue, crit = rur(x=trend)
        >>> print(f"RUR statistic: {stat:.4f}")
        RUR statistic: 31.5912
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.9500

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Seasonal Series"}
        >>> stat, pvalue, crit = rur(x=seasonal)
        >>> print(f"RUR statistic: {stat:.4f}")
        RUR statistic: 0.9129
        >>> print(f"p-value: {pvalue:.04f}")
        p-value: 0.0100

        ```

        ```pycon {.py .python linenums="1" title="Example 4: Real-World Time Series"}
        >>> stat, pvalue, crit = rur(x=airline)
        >>> print(f"RUR statistic: {stat:.4f}")
        RUR statistic: 2.3333
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.9000

        ```

    ??? equation "Calculation"

        The mathematical equation for the RUR test is:

        $$
        y_t = \rho y_{t-1} + \epsilon_t
        $$

        where:

        - $y_t$ is the value of the time series at time $t$.
        - $\rho$ is the parameter of the unit root process.
        - $y_{t-1}$ is the value of the time series at time $t-1$.
        - $\epsilon_t$ is a stationary error term with mean zero and constant variance.

        The null hypothesis of the RUR test is that the time series is stationary, and the alternative hypothesis is that the time series is non-stationary with a unit root.

        Here are the detailed steps for how to calculate the RUR test:

        1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

        1. Estimate the parameter $\rho$ using the ordinary least squares method. This involves regressing $y_t$ on $y_{t-1}$. The estimated equation is:

            $$
            y_t = \alpha + \rho y_{t-1} + \epsilon_t
            $$

            where:

            - $\alpha$ is the intercept.
            - $\epsilon_t$ is the error term.

        1. Calculate the range of the time series, which is the difference between the maximum and minimum values of the time series:

            $$
            R = \max(y_t) - \min(y_t)
            $$

        1. Calculate the expected range of the time series under the null hypothesis of stationarity, which is given by:

            $$
            E(R) = \frac {T - 1} {2 \sqrt{T}}
            $$

            where:

            - $T$ is the sample size.

        1. Calculate the test statistic, which is given by:

            $$
            RUR = \frac {R - E(R)} {E(R)}
            $$

        1. Compare the test statistic to the critical values in the RUR distribution table to determine the level of significance. The critical values depend on the sample size and the level of significance.

        1. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary with a unit root. If the null hypothesis is not rejected, then the time series is stationary.

        In practice, the RUR test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of parameters and calculation of the test statistic.

    ??? note "Notes"
        The p-values are interpolated from Table 1 of Aparicio et al. (2006). If the computed statistic is outside the table of critical values, then a warning message is generated.

        Missing values are not handled.

    !!! success "Credit"
        - All credit goes to the [`statsmodels`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html) library.

    ??? question "References"
        - Aparicio, F., Escribano A., Sipols, A.E. (2006). Range Unit-Root (RUR) tests: robust against nonlinearities, error distributions, structural breaks and outliers. Journal of Time Series Analysis, 27 (4): 545-576.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    return _rur(x=x, store=store)

za 🔗

za(
    x: ArrayLike,
    trim: float = 0.15,
    maxlag: Optional[int] = None,
    regression: VALID_ZA_REGRESSION_OPTIONS = "c",
    autolag: Optional[VALID_ZA_AUTOLAG_OPTIONS] = "AIC",
) -> tuple[float, float, dict, int, int]

Summary

The Zivot-Andrews (ZA) test tests for a unit root in a univariate process in the presence of serial correlation and a single structural break.

Details

The Zivot-Andrews (ZA) test is a statistical test used to determine whether a time series is stationary or not in the presence of structural breaks. Structural breaks refer to significant changes in the underlying stochastic process of the time series, which can cause non-stationarity.

The ZA test involves running a regression of the time series on a constant and a linear time trend, and testing whether the residuals of the regression are stationary or not. The null hypothesis of the test is that the time series is stationary with a single break point, while the alternative hypothesis is that the time series is non-stationary with a single break point.

The test statistic is calculated by first estimating the break point using a likelihood ratio test. Then, the test statistic is calculated based on the estimated break point and the residuals of the regression. If the test statistic is greater than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is non-stationary with a structural break. If the test statistic is less than the critical value, then we fail to reject the null hypothesis and conclude that the time series is stationary with a structural break.

In practical terms, if a time series is found to be non-stationary with a structural break by the ZA test, one can apply methods to account for the structural break, such as including dummy variables in the regression or using time series models that allow for structural breaks.

Overall, the ZA test is a useful tool in time series analysis and forecasting when there is a suspicion of structural breaks in the data. However, it is important to note that the test may not detect multiple break points or breaks that are not well-separated in time.

Parameters:

Name Type Description Default
x ArrayLike

The data series to test.

required
trim float

The percentage of series at begin/end to exclude. Default: 0.15

0.15
maxlag Optional[int]

The maximum lag which is included in test. Default: None

None
regression VALID_ZA_REGRESSION_OPTIONS

Constant and trend order to include in regression.

  • "c": constant only (default).
  • "t": trend only.
  • "ct": constant and trend.

Default: "c"

'c'
autolag Optional[VALID_ZA_AUTOLAG_OPTIONS]

The method to select the lag length.

  • If None, then maxlag lags are used.
  • If "AIC" (default) or "BIC", then the number of lags is chosen.

Default: "AIC"

'AIC'

Returns:

Type Description
tuple[float, float, dict, int, int]

Returns a tuple containing: - zastat (float): The test statistic. - pvalue (float): The p-value. - cvdict (dict): Critical values at the \(1\%\), \(5\%\), and \(10\%\) levels. - baselag (int): Lags used for period regressions. - pbidx (int): Break period index.

Examples
Setup
1
2
3
4
5
>>> from ts_stat_tests.utils.data import data_airline, data_normal, data_noise
>>> from ts_stat_tests.stationarity.algorithms import za
>>> normal = data_normal
>>> noise = data_noise
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
>>> stat, pvalue, crit, lags, break_idx = za(x=normal)
>>> print(f"ZA statistic: {stat:.4f}")
ZA statistic: -30.8800
>>> print(f"p-value: {pvalue:.4e}")
p-value: 1.0000e-05
Example 2: Noisy Series
1
2
3
4
5
>>> stat, pvalue, crit, lags, break_idx = za(x=noise)
>>> print(f"ZA statistic: {stat:.4f}")
ZA statistic: -32.4316
>>> print(f"p-value: {pvalue:.4e}")
p-value: 1.0000e-05
Example 3: Real-World Time Series
1
2
3
4
5
>>> stat, pvalue, crit, lags, break_idx = za(x=airline)
>>> print(f"ZA statistic: {stat:.4f}")
ZA statistic: -3.6508
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.5808
Calculation

The mathematical equation for the Zivot-Andrews test is:

\[ y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 D_t + \delta_2 t D_t + \epsilon_t \]

where:

  • \(y_t\) is the value of the time series at time \(t\).
  • \(\alpha\) is the intercept.
  • \(\beta\) is the slope coefficient of the time trend.
  • \(\gamma\) is the coefficient of the lagged dependent variable.
  • \(D_t\) is a dummy variable that takes a value of 1 after the suspected structural break point, and 0 otherwise.
  • \(\delta_1\) and \(\delta_2\) are the coefficients of the dummy variable and the interaction term of the dummy variable and time trend, respectively.
  • \(\epsilon_t\) is a stationary error term with mean zero and constant variance.

The null hypothesis of the Zivot-Andrews test is that the time series is non-stationary, and the alternative hypothesis is that the time series is stationary with a single structural break.

Here are the detailed steps for how to calculate the Zivot-Andrews test:

  1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

  2. Estimate the parameters of the model using the least squares method. This involves regressing \(y_t\) on \(t\), \(y_{t-1}\), \(D_t\), and \(t D_t\). The estimated equation is:

    \[ y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 D_t + \delta_2 t D_t + \epsilon_t \]
  3. Perform a unit root test on the residuals to check for stationarity. The most commonly used unit root tests for this purpose are the Augmented Dickey-Fuller (ADF) test and the Phillips-Perron (PP) test.

  4. Calculate the test statistic, which is based on the largest root of the following equation:

    \[ \Delta y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 D_t + \delta_2 t D_t + \epsilon_t \]

    where:

    • \(\Delta\) is the first difference operator.
  5. Determine the critical values of the test statistic from the Zivot-Andrews distribution table. The critical values depend on the sample size, the level of significance, and the number of lagged dependent variables in the model.

  6. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is stationary with a structural break. If the null hypothesis is not rejected, then the time series is non-stationary and may require further processing to make it stationary.

In practice, the Zivot-Andrews test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of parameters and calculation of the test statistic.

Notes

H0 = unit root with a single structural break

Algorithm follows Baum (2004/2015) approximation to original Zivot-Andrews method. Rather than performing an autolag regression at each candidate break period (as per the original paper), a single autolag regression is run up-front on the base model (constant + trend with no dummies) to determine the best lag length. This lag length is then used for all subsequent break-period regressions. This results in significant run time reduction but also slightly more pessimistic test statistics than the original Zivot-Andrews method, although no attempt has been made to characterize the size/power trade-off.

Credit
References
  • Baum, C.F. (2004). ZANDREWS: Stata module to calculate Zivot-Andrews unit root test in presence of structural break," Statistical Software Components S437301, Boston College Department of Economics, revised 2015.
  • Schwert, G.W. (1989). Tests for unit roots: A Monte Carlo investigation. Journal of Business & Economic Statistics, 7: 147-159.
  • Zivot, E., and Andrews, D.W.K. (1992). Further evidence on the great crash, the oil-price shock, and the unit-root hypothesis. Journal of Business & Economic Studies, 10: 251-270.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
@typechecked
def za(
    x: ArrayLike,
    trim: float = 0.15,
    maxlag: Optional[int] = None,
    regression: VALID_ZA_REGRESSION_OPTIONS = "c",
    autolag: Optional[VALID_ZA_AUTOLAG_OPTIONS] = "AIC",
) -> tuple[float, float, dict, int, int]:
    r"""
    !!! note "Summary"
        The Zivot-Andrews (ZA) test tests for a unit root in a univariate process in the presence of serial correlation and a single structural break.

    ???+ abstract "Details"
        The Zivot-Andrews (ZA) test is a statistical test used to determine whether a time series is stationary or not in the presence of structural breaks. Structural breaks refer to significant changes in the underlying stochastic process of the time series, which can cause non-stationarity.

        The ZA test involves running a regression of the time series on a constant and a linear time trend, and testing whether the residuals of the regression are stationary or not. The null hypothesis of the test is that the time series is stationary with a single break point, while the alternative hypothesis is that the time series is non-stationary with a single break point.

        The test statistic is calculated by first estimating the break point using a likelihood ratio test. Then, the test statistic is calculated based on the estimated break point and the residuals of the regression. If the test statistic is greater than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is non-stationary with a structural break. If the test statistic is less than the critical value, then we fail to reject the null hypothesis and conclude that the time series is stationary with a structural break.

        In practical terms, if a time series is found to be non-stationary with a structural break by the ZA test, one can apply methods to account for the structural break, such as including dummy variables in the regression or using time series models that allow for structural breaks.

        Overall, the ZA test is a useful tool in time series analysis and forecasting when there is a suspicion of structural breaks in the data. However, it is important to note that the test may not detect multiple break points or breaks that are not well-separated in time.

    Params:
        x (ArrayLike):
            The data series to test.
        trim (float):
            The percentage of series at begin/end to exclude.
            Default: `0.15`
        maxlag (Optional[int]):
            The maximum lag which is included in test.
            Default: `None`
        regression (VALID_ZA_REGRESSION_OPTIONS):
            Constant and trend order to include in regression.

            - `"c"`: constant only (default).
            - `"t"`: trend only.
            - `"ct"`: constant and trend.

            Default: `"c"`
        autolag (Optional[VALID_ZA_AUTOLAG_OPTIONS]):
            The method to select the lag length.

            - If `None`, then `maxlag` lags are used.
            - If `"AIC"` (default) or `"BIC"`, then the number of lags is chosen.

            Default: `"AIC"`

    Returns:
        (tuple[float, float, dict, int, int]):
            Returns a tuple containing:
            - `zastat` (float): The test statistic.
            - `pvalue` (float): The p-value.
            - `cvdict` (dict): Critical values at the $1\%$, $5\%$, and $10\%$ levels.
            - `baselag` (int): Lags used for period regressions.
            - `pbidx` (int): Break period index.

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.utils.data import data_airline, data_normal, data_noise
        >>> from ts_stat_tests.stationarity.algorithms import za
        >>> normal = data_normal
        >>> noise = data_noise
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, crit, lags, break_idx = za(x=normal)
        >>> print(f"ZA statistic: {stat:.4f}")
        ZA statistic: -30.8800
        >>> print(f"p-value: {pvalue:.4e}")
        p-value: 1.0000e-05

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Noisy Series"}
        >>> stat, pvalue, crit, lags, break_idx = za(x=noise)
        >>> print(f"ZA statistic: {stat:.4f}")
        ZA statistic: -32.4316
        >>> print(f"p-value: {pvalue:.4e}")
        p-value: 1.0000e-05

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Real-World Time Series"}
        >>> stat, pvalue, crit, lags, break_idx = za(x=airline)
        >>> print(f"ZA statistic: {stat:.4f}")
        ZA statistic: -3.6508
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.5808

        ```

    ??? equation "Calculation"

        The mathematical equation for the Zivot-Andrews test is:

        $$
        y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 D_t + \delta_2 t D_t + \epsilon_t
        $$

        where:

        - $y_t$ is the value of the time series at time $t$.
        - $\alpha$ is the intercept.
        - $\beta$ is the slope coefficient of the time trend.
        - $\gamma$ is the coefficient of the lagged dependent variable.
        - $D_t$ is a dummy variable that takes a value of 1 after the suspected structural break point, and 0 otherwise.
        - $\delta_1$ and $\delta_2$ are the coefficients of the dummy variable and the interaction term of the dummy variable and time trend, respectively.
        - $\epsilon_t$ is a stationary error term with mean zero and constant variance.

        The null hypothesis of the Zivot-Andrews test is that the time series is non-stationary, and the alternative hypothesis is that the time series is stationary with a single structural break.

        Here are the detailed steps for how to calculate the Zivot-Andrews test:

        1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

        1. Estimate the parameters of the model using the least squares method. This involves regressing $y_t$ on $t$, $y_{t-1}$, $D_t$, and $t D_t$. The estimated equation is:

            $$
            y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 D_t + \delta_2 t D_t + \epsilon_t
            $$

        1. Perform a unit root test on the residuals to check for stationarity. The most commonly used unit root tests for this purpose are the Augmented Dickey-Fuller (ADF) test and the Phillips-Perron (PP) test.

        1. Calculate the test statistic, which is based on the largest root of the following equation:

            $$
            \Delta y_t = \alpha + \beta t + \gamma y_{t-1} + \delta_1 D_t + \delta_2 t D_t + \epsilon_t
            $$

            where:

            - $\Delta$ is the first difference operator.

        1. Determine the critical values of the test statistic from the Zivot-Andrews distribution table. The critical values depend on the sample size, the level of significance, and the number of lagged dependent variables in the model.

        1. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is stationary with a structural break. If the null hypothesis is not rejected, then the time series is non-stationary and may require further processing to make it stationary.

        In practice, the Zivot-Andrews test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of parameters and calculation of the test statistic.

    ??? note "Notes"
        H0 = unit root with a single structural break

        Algorithm follows Baum (2004/2015) approximation to original Zivot-Andrews method. Rather than performing an autolag regression at each candidate break period (as per the original paper), a single autolag regression is run up-front on the base model (constant + trend with no dummies) to determine the best lag length. This lag length is then used for all subsequent break-period regressions. This results in significant run time reduction but also slightly more pessimistic test statistics than the original Zivot-Andrews method, although no attempt has been made to characterize the size/power trade-off.

    ??? success "Credit"
        - All credit goes to the [`statsmodels`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html) library.

    ??? question "References"
        - Baum, C.F. (2004). ZANDREWS: Stata module to calculate Zivot-Andrews unit root test in presence of structural break," Statistical Software Components S437301, Boston College Department of Economics, revised 2015.
        - Schwert, G.W. (1989). Tests for unit roots: A Monte Carlo investigation. Journal of Business & Economic Statistics, 7: 147-159.
        - Zivot, E., and Andrews, D.W.K. (1992). Further evidence on the great crash, the oil-price shock, and the unit-root hypothesis. Journal of Business & Economic Studies, 10: 251-270.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    res: Any = _za(
        x=x,
        trim=trim,
        maxlag=maxlag,
        regression=regression,
        autolag=autolag,  # type: ignore[arg-type] # statsmodels stubs are often missing None
    )
    return (
        float(res[0]),
        float(res[1]),
        dict(res[2]),
        int(res[3]),
        int(res[4]),
    )

pp 🔗

pp(
    x: ArrayLike,
    lags: Optional[int] = None,
    trend: VALID_PP_TREND_OPTIONS = "c",
    test_type: VALID_PP_TEST_TYPE_OPTIONS = "tau",
) -> tuple[float, float, int, dict]

Summary

Conduct a Phillips-Perron (PP) test for stationarity.

In statistics, the Phillips-Perron test (named after Peter C. B. Phillips and Pierre Perron) is a unit root test. It is used in time series analysis to test the null hypothesis that a time series is integrated of order \(1\). It builds on the Dickey-Fuller test of the null hypothesis \(p=0\).

Details

The Phillips-Perron (PP) test is a statistical test used to determine whether a time series is stationary or not. It is similar to the Augmented Dickey-Fuller (ADF) test, but it has some advantages, especially in the presence of autocorrelation and heteroscedasticity.

The PP test involves regressing the time series on a constant and a linear time trend, and testing whether the residuals of the regression are stationary or not. The null hypothesis of the test is that the time series is non-stationary, while the alternative hypothesis is that the time series is stationary.

The test statistic is calculated by taking the sum of the squared residuals of the regression, which is adjusted for autocorrelation and heteroscedasticity. The PP test also accounts for the bias in the standard errors of the test statistic, which can lead to incorrect inference in small samples.

If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

In practical terms, if a time series is found to be non-stationary by the PP test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

Overall, the PP test is a powerful and robust test for stationarity, and it is widely used in time series analysis and forecasting. However, it is important to use multiple tests and diagnostic tools to determine the stationarity of a time series, as no single test is perfect in all situations.

Parameters:

Name Type Description Default
x ArrayLike

The data series to test.

required
lags Optional[int]

The number of lags to use in the Newey-West estimator of the variance. If omitted or None, the lag length is selected automatically.
Defaults to None.

None
trend VALID_PP_TREND_OPTIONS

The trend component to include in the test.

  • "n": No constant, no trend.
  • "c": Include a constant (default).
  • "ct": Include a constant and linear time trend.

Defaults to "c".

'c'
test_type VALID_PP_TEST_TYPE_OPTIONS

The type of test statistic to compute:

  • "tau": The t-statistic based on the augmented regression (default).
  • "rho": The normalized autocorrelation coefficient (also known as the \(Z(\\alpha)\) test).

Defaults to "tau".

'tau'

Returns:

Type Description
tuple[float, float, int, dict]

Returns a tuple containing: - stat (float): The test statistic. - pvalue (float): The p-value for the test statistic. - lags (int): The number of lags used in the test. - crit (dict): The critical values at 1%, 5%, and 10%.

Examples
Setup
1
2
3
4
5
6
>>> from ts_stat_tests.stationarity.algorithms import pp
>>> from ts_stat_tests.utils.data import data_airline, data_normal, data_trend, data_sine
>>> normal = data_normal
>>> trend = data_trend
>>> seasonal = data_sine
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = pp(x=normal)
>>> print(f"PP statistic: {stat:.4f}")
PP statistic: -30.7758
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
Example 2: Trend-Stationary Series
1
2
3
>>> stat, pvalue, lags, crit = pp(x=trend, trend="ct")
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
Example 3: Seasonal Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = pp(x=seasonal)
>>> print(f"PP statistic: {stat:.4f}")
PP statistic: -8.0571
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
Example 4: Real-World Time Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = pp(x=airline)
>>> print(f"PP statistic: {stat:.4f}")
PP statistic: -1.3511
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.6055
Example 5: PP test with excessive lags (coverage check)
1
2
3
4
5
6
>>> from ts_stat_tests.stationarity.algorithms import pp
>>> from ts_stat_tests.utils.data import data_normal
>>> # data_normal has 1000 observations. Force lags = 1000 to trigger adjustment.
>>> res = pp(data_normal, lags=1000)
>>> print(f"stat: {res[0]:.4f}, lags: {res[2]}")
stat: -43.6895, lags: 998
Calculation

The Phillips-Perron (PP) test is a commonly used test for stationarity in time series forecasting. The mathematical equation for the PP test is:

\[ y_t = \delta + \pi t + \rho y_{t-1} + \epsilon_t \]

where:

  • \(y_t\) is the value of the time series at time \(t\).
  • \(\delta\) is a constant term.
  • \(\pi\) is a coefficient that captures the trend in the data.
  • \(\rho\) is a coefficient that captures the autocorrelation in the data.
  • \(y_{t-1}\) is the lagged value of the time series at time \(t-1\).
  • \(\epsilon_t\) is a stationary error term with mean zero and constant variance.

The PP test is based on the idea that if the time series is stationary, then the coefficient \(\rho\) should be equal to zero. Therefore, the null hypothesis of the PP test is that the time series is stationary, and the alternative hypothesis is that the time series is non-stationary with a non-zero value of \(\rho\).

Here are the detailed steps for how to calculate the PP test:

  1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

  2. Estimate the regression model by regressing \(y_t\) on a constant, a linear trend, and the lagged value of \(y_{t-1}\). The regression equation is:

    \[ y_t = \delta + \pi t + \rho y_{t-1} + \epsilon_t \]
  3. Calculate the test statistic, which is based on the following equation:

    \[ z = \left( T^{-\frac{1}{2}} \right) \times \left( \sum_{t=1}^T \left( y_t - \delta - \pi t - \rho y_{t-1} \right) - \left( \frac{1}{T} \right) \times \sum_{t=1}^T \sum_{s=1}^T K \left( \frac{s-t}{h} \right) (y_s - \delta - \pi s - \rho y_{s-1}) \right) \]

    where:

    • \(T\) is the sample size.
    • \(K(\dots)\) is the kernel function, which determines the weight of each observation in the smoothed series. The choice of the kernel function depends on the degree of serial correlation in the data. Typically, a Gaussian kernel or a Bartlett kernel is used.
    • \(h\) is the bandwidth parameter, which controls the degree of smoothing of the series. The optimal value of \(h\) depends on the sample size and the noise level of the data.
  4. Determine the critical values of the test statistic from the PP distribution table. The critical values depend on the sample size and the level of significance.

  5. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary with a non-zero value of \(\rho\). If the null hypothesis is not rejected, then the time series is stationary.

In practice, the PP test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of the regression model and calculation of the test statistic.

Notes

This test is generally used indirectly via the pmdarima.arima.ndiffs() function, which computes the differencing term, d.

The R code allows for two types of tests: 'Z(alpha)' and 'Z(t_alpha)'. Since sklearn does not allow extraction of std errors from the linear model fit, t_alpha is much more difficult to achieve, so we do not allow that variant.

Credit

  • All credit goes to the arch library.
References
  • Phillips, P. C. B.; Perron, P. (1988). Testing for a Unit Root in Time Series Regression. Biometrika. 75 (2): 335-346.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
@typechecked
def pp(
    x: ArrayLike,
    lags: Optional[int] = None,
    trend: VALID_PP_TREND_OPTIONS = "c",
    test_type: VALID_PP_TEST_TYPE_OPTIONS = "tau",
) -> tuple[float, float, int, dict]:
    r"""
    !!! note "Summary"
        Conduct a Phillips-Perron (PP) test for stationarity.

        In statistics, the Phillips-Perron test (named after Peter C. B. Phillips and Pierre Perron) is a unit root test. It is used in time series analysis to test the null hypothesis that a time series is integrated of order $1$. It builds on the Dickey-Fuller test of the null hypothesis $p=0$.

    ???+ abstract "Details"

        The Phillips-Perron (PP) test is a statistical test used to determine whether a time series is stationary or not. It is similar to the Augmented Dickey-Fuller (ADF) test, but it has some advantages, especially in the presence of autocorrelation and heteroscedasticity.

        The PP test involves regressing the time series on a constant and a linear time trend, and testing whether the residuals of the regression are stationary or not. The null hypothesis of the test is that the time series is non-stationary, while the alternative hypothesis is that the time series is stationary.

        The test statistic is calculated by taking the sum of the squared residuals of the regression, which is adjusted for autocorrelation and heteroscedasticity. The PP test also accounts for the bias in the standard errors of the test statistic, which can lead to incorrect inference in small samples.

        If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

        In practical terms, if a time series is found to be non-stationary by the PP test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

        Overall, the PP test is a powerful and robust test for stationarity, and it is widely used in time series analysis and forecasting. However, it is important to use multiple tests and diagnostic tools to determine the stationarity of a time series, as no single test is perfect in all situations.

    Params:
        x (ArrayLike):
            The data series to test.
        lags (Optional[int], optional):
            The number of lags to use in the Newey-West estimator of the variance. If omitted or `None`, the lag length is selected automatically.<br>
            Defaults to `None`.
        trend (VALID_PP_TREND_OPTIONS, optional):
            The trend component to include in the test.

            - `"n"`: No constant, no trend.
            - `"c"`: Include a constant (default).
            - `"ct"`: Include a constant and linear time trend.

            Defaults to `"c"`.
        test_type (VALID_PP_TEST_TYPE_OPTIONS, optional):
            The type of test statistic to compute:

            - `"tau"`: The t-statistic based on the augmented regression (default).
            - `"rho"`: The normalized autocorrelation coefficient (also known as the $Z(\\alpha)$ test).

            Defaults to `"tau"`.

    Returns:
        (tuple[float, float, int, dict]):
            Returns a tuple containing:
            - `stat` (float): The test statistic.
            - `pvalue` (float): The p-value for the test statistic.
            - `lags` (int): The number of lags used in the test.
            - `crit` (dict): The critical values at 1%, 5%, and 10%.

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.algorithms import pp
        >>> from ts_stat_tests.utils.data import data_airline, data_normal, data_trend, data_sine
        >>> normal = data_normal
        >>> trend = data_trend
        >>> seasonal = data_sine
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, lags, crit = pp(x=normal)
        >>> print(f"PP statistic: {stat:.4f}")
        PP statistic: -30.7758
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Trend-Stationary Series"}
        >>> stat, pvalue, lags, crit = pp(x=trend, trend="ct")
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Seasonal Series"}
        >>> stat, pvalue, lags, crit = pp(x=seasonal)
        >>> print(f"PP statistic: {stat:.4f}")
        PP statistic: -8.0571
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000

        ```

        ```pycon {.py .python linenums="1" title="Example 4: Real-World Time Series"}
        >>> stat, pvalue, lags, crit = pp(x=airline)
        >>> print(f"PP statistic: {stat:.4f}")
        PP statistic: -1.3511
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.6055

        ```

        ```pycon {.py .python linenums="1" title="Example 5: PP test with excessive lags (coverage check)"}
        >>> from ts_stat_tests.stationarity.algorithms import pp
        >>> from ts_stat_tests.utils.data import data_normal
        >>> # data_normal has 1000 observations. Force lags = 1000 to trigger adjustment.
        >>> res = pp(data_normal, lags=1000)
        >>> print(f"stat: {res[0]:.4f}, lags: {res[2]}")
        stat: -43.6895, lags: 998

        ```

    ??? equation "Calculation"

        The Phillips-Perron (PP) test is a commonly used test for stationarity in time series forecasting. The mathematical equation for the PP test is:

        $$
        y_t = \delta + \pi t + \rho y_{t-1} + \epsilon_t
        $$

        where:

        - $y_t$ is the value of the time series at time $t$.
        - $\delta$ is a constant term.
        - $\pi$ is a coefficient that captures the trend in the data.
        - $\rho$ is a coefficient that captures the autocorrelation in the data.
        - $y_{t-1}$ is the lagged value of the time series at time $t-1$.
        - $\epsilon_t$ is a stationary error term with mean zero and constant variance.

        The PP test is based on the idea that if the time series is stationary, then the coefficient $\rho$ should be equal to zero. Therefore, the null hypothesis of the PP test is that the time series is stationary, and the alternative hypothesis is that the time series is non-stationary with a non-zero value of $\rho$.

        Here are the detailed steps for how to calculate the PP test:

        1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

        1. Estimate the regression model by regressing $y_t$ on a constant, a linear trend, and the lagged value of $y_{t-1}$. The regression equation is:

            $$
            y_t = \delta + \pi t + \rho y_{t-1} + \epsilon_t
            $$

        1. Calculate the test statistic, which is based on the following equation:

            $$
            z = \left( T^{-\frac{1}{2}} \right) \times \left( \sum_{t=1}^T \left( y_t - \delta - \pi t - \rho y_{t-1} \right) - \left( \frac{1}{T} \right) \times \sum_{t=1}^T \sum_{s=1}^T K \left( \frac{s-t}{h} \right) (y_s - \delta - \pi s - \rho y_{s-1}) \right)
            $$

            where:

            - $T$ is the sample size.
            - $K(\dots)$ is the kernel function, which determines the weight of each observation in the smoothed series. The choice of the kernel function depends on the degree of serial correlation in the data. Typically, a Gaussian kernel or a Bartlett kernel is used.
            - $h$ is the bandwidth parameter, which controls the degree of smoothing of the series. The optimal value of $h$ depends on the sample size and the noise level of the data.

        1. Determine the critical values of the test statistic from the PP distribution table. The critical values depend on the sample size and the level of significance.

        1. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary with a non-zero value of $\rho$. If the null hypothesis is not rejected, then the time series is stationary.

        In practice, the PP test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of the regression model and calculation of the test statistic.

    ??? note "Notes"
        This test is generally used indirectly via the [`pmdarima.arima.ndiffs()`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.ndiffs.html) function, which computes the differencing term, `d`.

        The R code allows for two types of tests: `'Z(alpha)'` and `'Z(t_alpha)'`. Since sklearn does not allow extraction of std errors from the linear model fit, `t_alpha` is much more difficult to achieve, so we do not allow that variant.

    !!! success "Credit"
        - All credit goes to the [`arch`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.PhillipsPerron.html) library.

    ??? question "References"
        - Phillips, P. C. B.; Perron, P. (1988). Testing for a Unit Root in Time Series Regression. Biometrika. 75 (2): 335-346.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    _x = np.asarray(x)
    nobs = _x.shape[0]
    _lags = lags
    if _lags is None:
        _lags = int(np.ceil(12.0 * np.power(nobs / 100.0, 1 / 4.0)))

    # arch PP test requires lags < nobs-1
    if _lags >= nobs - 1:
        _lags = max(0, nobs - 2)

    res = _pp(y=_x, lags=_lags, trend=trend, test_type=test_type)
    return (float(res.stat), float(res.pvalue), int(res.lags), dict(res.critical_values))

ers 🔗

ers(
    y: ArrayLike,
    lags: Optional[int] = None,
    trend: VALID_ERS_TREND_OPTIONS = "c",
    max_lags: Optional[int] = None,
    method: VALID_ERS_METHOD_OPTIONS = "aic",
    low_memory: Optional[bool] = None,
) -> tuple[float, float, int, dict]

Summary

Elliott, Rothenberg and Stock's GLS detrended Dickey-Fuller.

Details

The Elliott-Rothenberg-Stock (ERS) test is a statistical test used to determine whether a time series is stationary or not. It is a robust test that is able to handle a wide range of non-stationary processes, including ones with structural breaks, heteroscedasticity, and autocorrelation.

The ERS test involves fitting a local-to-zero regression of the time series on a constant and a linear time trend, using a kernel function to weight the observations. The test statistic is then calculated based on the sum of the squared residuals of the local-to-zero regression, which is adjusted for the bandwidth of the kernel function and for the correlation of the residuals.

If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

In practical terms, if a time series is found to be non-stationary by the ERS test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

Overall, the ERS test is a powerful and flexible test for stationarity, and it is widely used in time series analysis and forecasting. However, it is important to use multiple tests and diagnostic tools to determine the stationarity of a time series, as no single test is perfect in all situations.

Parameters:

Name Type Description Default
y ArrayLike

The data to test for a unit root.

required
lags Optional[int]

The number of lags to use in the ADF regression. If omitted or None, method is used to automatically select the lag length with no more than max_lags are included.
Defaults to None.

None
trend VALID_ERS_TREND_OPTIONS

The trend component to include in the test

  • "c": Include a constant (Default)
  • "ct": Include a constant and linear time trend

Defaults to "c".

'c'
max_lags Optional[int]

The maximum number of lags to use when selecting lag length. When using automatic lag length selection, the lag is selected using OLS detrending rather than GLS detrending.
Defaults to None.

None
method VALID_ERS_METHOD_OPTIONS

The method to use when selecting the lag length

  • "AIC": Select the minimum of the Akaike IC
  • "BIC": Select the minimum of the Schwarz/Bayesian IC
  • "t-stat": Select the minimum of the Schwarz/Bayesian IC

Defaults to "aic".

'aic'
low_memory Optional[bool]

Flag indicating whether to use the low-memory algorithm for lag-length selection. Defaults to None.

None

Returns:

Type Description
tuple[float, float, int, dict]

Returns a tuple containing: - stat (float): The test statistic for a unit root. - pvalue (float): The p-value for the test statistic. - lags (int): The number of lags used in the test. - crit (dict): The critical values for the test statistic at the 1%, 5%, and 10% levels.

Examples
Setup
1
2
3
4
5
>>> from ts_stat_tests.stationarity.algorithms import ers
>>> from ts_stat_tests.utils.data import data_airline, data_normal, data_noise
>>> normal = data_normal
>>> noise = data_noise
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = ers(y=normal)
>>> print(f"ERS statistic: {stat:.4f}")
ERS statistic: -30.1517
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
Example 2: Noisy Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = ers(y=noise)
>>> print(f"ERS statistic: {stat:.4f}")
ERS statistic: -12.6897
>>> print(f"p-value: {pvalue:.4e}")
p-value: 1.0956e-21
Example 3: Real-World Time Series
1
2
3
4
5
>>> stat, pvalue, lags, crit = ers(y=airline)
>>> print(f"ERS statistic: {stat:.4f}")
ERS statistic: 0.9918
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.9232
Calculation

The mathematical equation for the ERS test is:

\[ y_t = \mu_t + \epsilon_t \]

where:

  • \(y_t\) is the value of the time series at time \(t\).
  • \(\mu_t\) is a time-varying mean function.
  • \(\epsilon_t\) is a stationary error term with mean zero and constant variance.

The ERS test is based on the idea that if the time series is stationary, then the mean function should be a constant over time. Therefore, the null hypothesis of the ERS test is that the time series is non-stationary (unit root), and the alternative hypothesis is that the time series is stationary.

Here are the detailed steps for how to calculate the ERS test:

  1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

  2. Estimate the time-varying mean function using a local polynomial regression method. The choice of the polynomial degree depends on the complexity of the mean function and the sample size. Typically, a quadratic or cubic polynomial is used. The estimated mean function is denoted as \(\mu_t\).

  3. Calculate the test statistic, which is based on the following equation:

    \[ z = \left( \frac {T-1} {( \frac {1} {12\pi^2 \times \Delta^2} )} \right) ^{\frac{1}{2}} \times \left( \sum_{t=1}^T \frac {(y_t - \mu_t)^2} {T-1} \right) \]

    where:

    • \(T\) is the sample size
    • \(\Delta\) is the bandwidth parameter, which controls the degree of smoothing of the mean function. The optimal value of \(\Delta\) depends on the sample size and the noise level of the data.
    • \(\pi\) is the constant pi.
  4. Determine the critical values of the test statistic from the ERS distribution table. The critical values depend on the sample size and the level of significance.

  5. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary with a time-varying mean function. If the null hypothesis is not rejected, then the time series is stationary.

In practice, the ERS test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of the time-varying mean function and calculation of the test statistic.

Notes

The null hypothesis of the Dickey-Fuller GLS is that there is a unit root, with the alternative that there is no unit root. If the p-value is above a critical size, then the null cannot be rejected and the series appears to be a unit root.

DFGLS differs from the ADF test in that an initial GLS detrending step is used before a trend-less ADF regression is run.

Critical values and p-values when trend is "c" are identical to the ADF. When trend is set to "ct", they are from Elliott, Rothenberg, and Stock (1996).

Credit

  • All credit goes to the arch library.
References
  • Elliott, G. R., T. J. Rothenberg, and J. H. Stock. 1996. Efficient bootstrap for an autoregressive unit root. Econometrica 64: 813-836.
  • Perron, P., & Qu, Z. (2007). A simple modification to improve the finite sample properties of Ng and Perron’s unit root tests. Economics letters, 94(1), 12-19.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
@typechecked
def ers(
    y: ArrayLike,
    lags: Optional[int] = None,
    trend: VALID_ERS_TREND_OPTIONS = "c",
    max_lags: Optional[int] = None,
    method: VALID_ERS_METHOD_OPTIONS = "aic",
    low_memory: Optional[bool] = None,
) -> tuple[float, float, int, dict]:
    r"""
    !!! note "Summary"
        Elliott, Rothenberg and Stock's GLS detrended Dickey-Fuller.

    ???+ abstract "Details"

        The Elliott-Rothenberg-Stock (ERS) test is a statistical test used to determine whether a time series is stationary or not. It is a robust test that is able to handle a wide range of non-stationary processes, including ones with structural breaks, heteroscedasticity, and autocorrelation.

        The ERS test involves fitting a local-to-zero regression of the time series on a constant and a linear time trend, using a kernel function to weight the observations. The test statistic is then calculated based on the sum of the squared residuals of the local-to-zero regression, which is adjusted for the bandwidth of the kernel function and for the correlation of the residuals.

        If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

        In practical terms, if a time series is found to be non-stationary by the ERS test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

        Overall, the ERS test is a powerful and flexible test for stationarity, and it is widely used in time series analysis and forecasting. However, it is important to use multiple tests and diagnostic tools to determine the stationarity of a time series, as no single test is perfect in all situations.

    Params:
        y (ArrayLike):
            The data to test for a unit root.
        lags (Optional[int], optional):
            The number of lags to use in the ADF regression. If omitted or `None`, method is used to automatically select the lag length with no more than `max_lags` are included.<br>
            Defaults to `None`.
        trend (VALID_ERS_TREND_OPTIONS, optional):
            The trend component to include in the test

            - `"c"`: Include a constant (Default)
            - `"ct"`: Include a constant and linear time trend

            Defaults to `"c"`.
        max_lags (Optional[int], optional):
            The maximum number of lags to use when selecting lag length. When using automatic lag length selection, the lag is selected using OLS detrending rather than GLS detrending.<br>
            Defaults to `None`.
        method (VALID_ERS_METHOD_OPTIONS, optional):
            The method to use when selecting the lag length

            - `"AIC"`: Select the minimum of the Akaike IC
            - `"BIC"`: Select the minimum of the Schwarz/Bayesian IC
            - `"t-stat"`: Select the minimum of the Schwarz/Bayesian IC

            Defaults to `"aic"`.
        low_memory (Optional[bool], optional):
            Flag indicating whether to use the low-memory algorithm for lag-length selection.
            Defaults to `None`.

    Returns:
        (tuple[float, float, int, dict]):
            Returns a tuple containing:
            - `stat` (float): The test statistic for a unit root.
            - `pvalue` (float): The p-value for the test statistic.
            - `lags` (int): The number of lags used in the test.
            - `crit` (dict): The critical values for the test statistic at the 1%, 5%, and 10% levels.

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.algorithms import ers
        >>> from ts_stat_tests.utils.data import data_airline, data_normal, data_noise
        >>> normal = data_normal
        >>> noise = data_noise
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, lags, crit = ers(y=normal)
        >>> print(f"ERS statistic: {stat:.4f}")
        ERS statistic: -30.1517
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Noisy Series"}
        >>> stat, pvalue, lags, crit = ers(y=noise)
        >>> print(f"ERS statistic: {stat:.4f}")
        ERS statistic: -12.6897
        >>> print(f"p-value: {pvalue:.4e}")
        p-value: 1.0956e-21

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Real-World Time Series"}
        >>> stat, pvalue, lags, crit = ers(y=airline)
        >>> print(f"ERS statistic: {stat:.4f}")
        ERS statistic: 0.9918
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.9232

        ```

    ??? equation "Calculation"

        The mathematical equation for the ERS test is:

        $$
        y_t = \mu_t + \epsilon_t
        $$

        where:

        - $y_t$ is the value of the time series at time $t$.
        - $\mu_t$ is a time-varying mean function.
        - $\epsilon_t$ is a stationary error term with mean zero and constant variance.

        The ERS test is based on the idea that if the time series is stationary, then the mean function should be a constant over time. Therefore, the null hypothesis of the ERS test is that the time series is non-stationary (unit root), and the alternative hypothesis is that the time series is stationary.

        Here are the detailed steps for how to calculate the ERS test:

        1. Collect your time series data and plot it to visually check for any trends, seasonal patterns, or other patterns that could make the data non-stationary. If you detect any such patterns, you will need to pre-process your data (e.g., detrending, deseasonalizing, etc.) to remove these effects.

        1. Estimate the time-varying mean function using a local polynomial regression method. The choice of the polynomial degree depends on the complexity of the mean function and the sample size. Typically, a quadratic or cubic polynomial is used. The estimated mean function is denoted as $\mu_t$.

        1. Calculate the test statistic, which is based on the following equation:

            $$
            z = \left( \frac {T-1} {( \frac {1} {12\pi^2 \times \Delta^2} )} \right) ^{\frac{1}{2}} \times \left( \sum_{t=1}^T \frac {(y_t - \mu_t)^2} {T-1} \right)
            $$

            where:

            - $T$ is the sample size
            - $\Delta$ is the bandwidth parameter, which controls the degree of smoothing of the mean function. The optimal value of $\Delta$ depends on the sample size and the noise level of the data.
            - $\pi$ is the constant pi.

        1. Determine the critical values of the test statistic from the ERS distribution table. The critical values depend on the sample size and the level of significance.

        1. Finally, interpret the results and draw conclusions about the stationarity of the time series. If the null hypothesis is rejected, then the time series is non-stationary with a time-varying mean function. If the null hypothesis is not rejected, then the time series is stationary.

        In practice, the ERS test is often conducted using software packages such as R, Python, or MATLAB, which automate the estimation of the time-varying mean function and calculation of the test statistic.

    ??? note "Notes"
        The null hypothesis of the Dickey-Fuller GLS is that there is a unit root, with the alternative that there is no unit root. If the p-value is above a critical size, then the null cannot be rejected and the series appears to be a unit root.

        DFGLS differs from the ADF test in that an initial GLS detrending step is used before a trend-less ADF regression is run.

        Critical values and p-values when trend is `"c"` are identical to the ADF. When trend is set to `"ct"`, they are from Elliott, Rothenberg, and Stock (1996).

    !!! success "Credit"
        - All credit goes to the [`arch`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html) library.

    ??? question "References"
        - Elliott, G. R., T. J. Rothenberg, and J. H. Stock. 1996. Efficient bootstrap for an autoregressive unit root. Econometrica 64: 813-836.
        - Perron, P., & Qu, Z. (2007). A simple modification to improve the finite sample properties of Ng and Perron’s unit root tests. Economics letters, 94(1), 12-19.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    res = _ers(
        y=np.asarray(y),
        lags=lags,
        trend=trend,
        max_lags=max_lags,
        method=method,
        low_memory=low_memory,
    )
    return (float(res.stat), float(res.pvalue), int(res.lags), dict(res.critical_values))

vr 🔗

vr(
    y: ArrayLike,
    lags: int = 2,
    trend: VALID_VR_TREND_OPTIONS = "c",
    debiased: bool = True,
    robust: bool = True,
    overlap: bool = True,
) -> tuple[float, float, float]

Summary

Variance Ratio test of a random walk.

Details

The Variance Ratio (VR) test is a statistical test used to determine whether a time series is stationary or not based on the presence of long-term dependence in the series. It is a non-parametric test that can be used to test for the presence of a unit root or a trend in the series.

The VR test involves calculating the ratio of the variance of the differences of the logarithms of the time series over different time intervals. The variance of the differences of the logarithms is a measure of the volatility of the series, and the ratio of the variances over different intervals is a measure of the long-term dependence in the series.

If the series is stationary, then the variance ratio will be close to one for all intervals. If the series is non-stationary, then the variance ratio will tend to increase as the length of the interval increases, reflecting the presence of long-term dependence in the series.

The VR test involves comparing the observed variance ratio to the distribution of variance ratios expected under the null hypothesis of a random walk (non-stationary). If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

In practical terms, if a time series is found to be non-stationary by the VR test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

Overall, the VR test is a useful and relatively simple test for stationarity that can be applied to a wide range of time series. However, it is important to use multiple tests and diagnostic tools to confirm the stationarity of a time series, as no single test is perfect in all situations.

Parameters:

Name Type Description Default
y ArrayLike

The data to test for a random walk.

required
lags int

The number of periods to used in the multi-period variance, which is the numerator of the test statistic. Must be at least 2.
Defaults to 2.

2
trend VALID_VR_TREND_OPTIONS

"c" allows for a non-zero drift in the random walk, while "n" requires that the increments to y are mean 0.
Defaults to "c".

'c'
debiased bool

Indicates whether to use a debiased version of the test. Only applicable if overlap is True.
Defaults to True.

True
robust bool

Indicates whether to use heteroskedasticity robust inference.
Defaults to True.

True
overlap bool

Indicates whether to use all overlapping blocks. If False, the number of observations in \(y-1\) must be an exact multiple of lags. If this condition is not satisfied, some values at the end of y will be discarded.
Defaults to True.

True

Returns:

Type Description
tuple[float, float, float]

Returns a tuple containing: - stat (float): The test statistic for a unit root. - pvalue (float): The p-value for the test statistic. - vr (float): The ratio of the long block lags-period variance.

Examples
Setup
1
2
3
4
5
6
>>> from ts_stat_tests.stationarity.algorithms import vr
>>> from ts_stat_tests.utils.data import data_airline, data_normal, data_noise, data_sine
>>> normal = data_normal
>>> noise = data_noise
>>> seasonal = data_sine
>>> airline = data_airline.values
Example 1: Stationary Series
1
2
3
4
5
6
7
>>> stat, pvalue, variance_ratio = vr(y=normal)
>>> print(f"VR statistic: {stat:.4f}")
VR statistic: -12.8518
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
>>> print(f"Variance ratio: {variance_ratio:.4f}")
Variance ratio: 0.5202
Example 2: Noisy Series
1
2
3
4
5
6
7
>>> stat, pvalue, variance_ratio = vr(y=noise)
>>> print(f"VR statistic: {stat:.4f}")
VR statistic: -11.5007
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
>>> print(f"Variance ratio: {variance_ratio:.4f}")
Variance ratio: 0.5094
Example 3: Seasonal Series
1
2
3
4
5
6
7
>>> stat, pvalue, variance_ratio = vr(y=seasonal)
>>> print(f"VR statistic: {stat:.4f}")
VR statistic: 44.7019
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0000
>>> print(f"Variance ratio: {variance_ratio:.4f}")
Variance ratio: 1.9980
Example 4: Real-World Time Series
1
2
3
4
5
6
7
>>> stat, pvalue, variance_ratio = vr(y=airline)
>>> print(f"VR statistic: {stat:.4f}")
VR statistic: 3.1511
>>> print(f"p-value: {pvalue:.4f}")
p-value: 0.0016
>>> print(f"Variance ratio: {variance_ratio:.4f}")
Variance ratio: 1.3163
Calculation

The Variance Ratio (VR) test is a statistical test for stationarity in time series forecasting that is based on the idea that if the time series is stationary, then the variance of the returns should be constant over time. The mathematical equation for the VR test is:

\[ VR(k) = \frac {\sigma^2(k)} {k\sigma^2(1)} \]

where:

  • \(VR(k)\) is the variance ratio for the time series over \(k\) periods.
  • \(\sigma^2(k)\) is the variance of the returns over \(k\) periods.
  • \(\sigma^2(1)\) is the variance of the returns over \(1\) period.

The VR test involves comparing the variance ratio to a critical value, which is derived from the null distribution of the variance ratio under the assumption of a random walk with drift.

Here are the detailed steps for how to calculate the VR test:

  1. Collect your time series data and compute the log returns, which are defined as:

    \[ r_t = \log(y_t) - \log(y_{t-1}) \]

    where:

    • \(y_t\) is the value of the time series at time \(t\).
  2. Compute the variance of the returns over \(k\) periods, which is defined as:

    \[ \sigma^2(k) = \left( \frac {1} {n-k} \right) \times \sum_{t=k+1}^n (r_t - \mu_k)^2 \]

    where:

    • \(n\) is the sample size.
    • \(\mu_k\) is the mean of the returns over \(k\) periods, which is defined as:

      \(\mu_k = \left( \frac{1} {n-k} \right) \times \sum_{t=k+1}^n r_t\)

  3. Compute the variance of the returns over \(1\) period, which is defined as:

    \[ \sigma^2(1) = \left( \frac{1} {n-1} \right) \times \sum_{t=2}^n (r_t - \mu_1)^2 \]

    where:

    • \(\mu_1\) is the mean of the returns over \(1\) period, which is defined as:

      \(\mu_1 = \left( \frac{1} {n-1} \right) \times \sum_{t=2}^n r_t\)

  4. Compute the variance ratio for each value of \(k\), which is defined as:

    \[ VR(k) = \frac {\sigma^2(k)} {k\sigma^2(1)} \]
  5. Determine the critical values of the variance ratio from the null distribution table of the VR test, which depend on the sample size, the level of significance, and the lag length \(k\).

  6. Finally, compare the variance ratio to the critical value. If the variance ratio is greater than the critical value, then the null hypothesis of a random walk with drift is rejected, and the time series is considered stationary. If the variance ratio is less than or equal to the critical value, then the null hypothesis cannot be rejected, and the time series is considered non-stationary.

In practice, the VR test is often conducted using software packages such as R, Python, or MATLAB, which automate the calculation of the variance ratio and the determination of the critical value.

Notes

The null hypothesis of a VR is that the process is a random walk, possibly plus drift. Rejection of the null with a positive test statistic indicates the presence of positive serial correlation in the time series.

Credit

  • All credit goes to the arch library.
References
  • Campbell, John Y., Lo, Andrew W. and MacKinlay, A. Craig. (1997) The Econometrics of Financial Markets. Princeton, NJ: Princeton University Press.
See Also
Source code in src/ts_stat_tests/stationarity/algorithms.py
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
@typechecked
def vr(
    y: ArrayLike,
    lags: int = 2,
    trend: VALID_VR_TREND_OPTIONS = "c",
    debiased: bool = True,
    robust: bool = True,
    overlap: bool = True,
) -> tuple[float, float, float]:
    r"""
    !!! note "Summary"
        Variance Ratio test of a random walk.

    ???+ abstract "Details"

        The Variance Ratio (VR) test is a statistical test used to determine whether a time series is stationary or not based on the presence of long-term dependence in the series. It is a non-parametric test that can be used to test for the presence of a unit root or a trend in the series.

        The VR test involves calculating the ratio of the variance of the differences of the logarithms of the time series over different time intervals. The variance of the differences of the logarithms is a measure of the volatility of the series, and the ratio of the variances over different intervals is a measure of the long-term dependence in the series.

        If the series is stationary, then the variance ratio will be close to one for all intervals. If the series is non-stationary, then the variance ratio will tend to increase as the length of the interval increases, reflecting the presence of long-term dependence in the series.

        The VR test involves comparing the observed variance ratio to the distribution of variance ratios expected under the null hypothesis of a random walk (non-stationary). If the test statistic is less than a critical value at a given significance level, typically 0.05, then we reject the null hypothesis and conclude that the time series is stationary. If the test statistic is greater than the critical value, then we fail to reject the null hypothesis and conclude that the time series is non-stationary.

        In practical terms, if a time series is found to be non-stationary by the VR test, one can apply differencing to the time series until it becomes stationary. This involves taking the difference between consecutive observations and potentially repeating this process until the time series is stationary.

        Overall, the VR test is a useful and relatively simple test for stationarity that can be applied to a wide range of time series. However, it is important to use multiple tests and diagnostic tools to confirm the stationarity of a time series, as no single test is perfect in all situations.

    Params:
        y (ArrayLike):
            The data to test for a random walk.
        lags (int):
            The number of periods to used in the multi-period variance, which is the numerator of the test statistic. Must be at least 2.<br>
            Defaults to `2`.
        trend (VALID_VR_TREND_OPTIONS, optional):
            `"c"` allows for a non-zero drift in the random walk, while `"n"` requires that the increments to `y` are mean `0`.<br>
            Defaults to `"c"`.
        debiased (bool, optional):
            Indicates whether to use a debiased version of the test. Only applicable if `overlap` is `True`.<br>
            Defaults to `True`.
        robust (bool, optional):
            Indicates whether to use heteroskedasticity robust inference.<br>
            Defaults to `True`.
        overlap (bool, optional):
            Indicates whether to use all overlapping blocks. If `False`, the number of observations in $y-1$ must be an exact multiple of `lags`. If this condition is not satisfied, some values at the end of `y` will be discarded.<br>
            Defaults to `True`.

    Returns:
        (tuple[float, float, float]):
            Returns a tuple containing:
            - `stat` (float): The test statistic for a unit root.
            - `pvalue` (float): The p-value for the test statistic.
            - `vr` (float): The ratio of the long block lags-period variance.

    ???+ example "Examples"

        ```pycon {.py .python linenums="1" title="Setup"}
        >>> from ts_stat_tests.stationarity.algorithms import vr
        >>> from ts_stat_tests.utils.data import data_airline, data_normal, data_noise, data_sine
        >>> normal = data_normal
        >>> noise = data_noise
        >>> seasonal = data_sine
        >>> airline = data_airline.values

        ```

        ```pycon {.py .python linenums="1" title="Example 1: Stationary Series"}
        >>> stat, pvalue, variance_ratio = vr(y=normal)
        >>> print(f"VR statistic: {stat:.4f}")
        VR statistic: -12.8518
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000
        >>> print(f"Variance ratio: {variance_ratio:.4f}")
        Variance ratio: 0.5202

        ```

        ```pycon {.py .python linenums="1" title="Example 2: Noisy Series"}
        >>> stat, pvalue, variance_ratio = vr(y=noise)
        >>> print(f"VR statistic: {stat:.4f}")
        VR statistic: -11.5007
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000
        >>> print(f"Variance ratio: {variance_ratio:.4f}")
        Variance ratio: 0.5094

        ```

        ```pycon {.py .python linenums="1" title="Example 3: Seasonal Series"}
        >>> stat, pvalue, variance_ratio = vr(y=seasonal)
        >>> print(f"VR statistic: {stat:.4f}")
        VR statistic: 44.7019
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0000
        >>> print(f"Variance ratio: {variance_ratio:.4f}")
        Variance ratio: 1.9980

        ```

        ```pycon {.py .python linenums="1" title="Example 4: Real-World Time Series"}
        >>> stat, pvalue, variance_ratio = vr(y=airline)
        >>> print(f"VR statistic: {stat:.4f}")
        VR statistic: 3.1511
        >>> print(f"p-value: {pvalue:.4f}")
        p-value: 0.0016
        >>> print(f"Variance ratio: {variance_ratio:.4f}")
        Variance ratio: 1.3163

        ```

    ??? equation "Calculation"

        The Variance Ratio (VR) test is a statistical test for stationarity in time series forecasting that is based on the idea that if the time series is stationary, then the variance of the returns should be constant over time. The mathematical equation for the VR test is:

        $$
        VR(k) = \frac {\sigma^2(k)} {k\sigma^2(1)}
        $$

        where:

        - $VR(k)$ is the variance ratio for the time series over $k$ periods.
        - $\sigma^2(k)$ is the variance of the returns over $k$ periods.
        - $\sigma^2(1)$ is the variance of the returns over $1$ period.

        The VR test involves comparing the variance ratio to a critical value, which is derived from the null distribution of the variance ratio under the assumption of a random walk with drift.

        Here are the detailed steps for how to calculate the VR test:

        1. Collect your time series data and compute the log returns, which are defined as:

            $$
            r_t = \log(y_t) - \log(y_{t-1})
            $$

            where:

            - $y_t$ is the value of the time series at time $t$.

        1. Compute the variance of the returns over $k$ periods, which is defined as:

            $$
            \sigma^2(k) = \left( \frac {1} {n-k} \right) \times \sum_{t=k+1}^n (r_t - \mu_k)^2
            $$

            where:

            - $n$ is the sample size.
            - $\mu_k$ is the mean of the returns over $k$ periods, which is defined as:

                $\mu_k = \left( \frac{1} {n-k} \right) \times \sum_{t=k+1}^n r_t$

        1. Compute the variance of the returns over $1$ period, which is defined as:

            $$
            \sigma^2(1) = \left( \frac{1} {n-1} \right) \times \sum_{t=2}^n (r_t - \mu_1)^2
            $$

            where:

            - $\mu_1$ is the mean of the returns over $1$ period, which is defined as:

                $\mu_1 = \left( \frac{1} {n-1} \right) \times \sum_{t=2}^n r_t$

        1. Compute the variance ratio for each value of $k$, which is defined as:

            $$
            VR(k) = \frac {\sigma^2(k)} {k\sigma^2(1)}
            $$

        1. Determine the critical values of the variance ratio from the null distribution table of the VR test, which depend on the sample size, the level of significance, and the lag length $k$.

        1. Finally, compare the variance ratio to the critical value. If the variance ratio is greater than the critical value, then the null hypothesis of a random walk with drift is rejected, and the time series is considered stationary. If the variance ratio is less than or equal to the critical value, then the null hypothesis cannot be rejected, and the time series is considered non-stationary.

        In practice, the VR test is often conducted using software packages such as R, Python, or MATLAB, which automate the calculation of the variance ratio and the determination of the critical value.

    ??? note "Notes"
        The null hypothesis of a VR is that the process is a random walk, possibly plus drift. Rejection of the null with a positive test statistic indicates the presence of positive serial correlation in the time series.

    !!! success "Credit"
        - All credit goes to the [`arch`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html) library.

    ??? question "References"
        - Campbell, John Y., Lo, Andrew W. and MacKinlay, A. Craig. (1997) The Econometrics of Financial Markets. Princeton, NJ: Princeton University Press.

    ??? tip "See Also"
        - [`statsmodels.tsa.stattools.adfuller`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html): Augmented Dickey-Fuller unit root test.
        - [`statsmodels.tsa.stattools.kpss`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.kpss.html): Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`statsmodels.tsa.stattools.range_unit_root_test`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.range_unit_root_test.html): Range Unit-Root test.
        - [`statsmodels.tsa.stattools.zivot_andrews`](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.zivot_andrews.html): Zivot-Andrews structural break test.
        - [`pmdarima.arima.PPTest`](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.PPTest.html): Phillips-Perron unit root test.
        - [`arch.unitroot.DFGLS`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.DFGLS.html): Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller.
        - [`arch.unitroot.VarianceRatio`](https://arch.readthedocs.io/en/latest/unitroot/generated/arch.unitroot.VarianceRatio.html): Variance Ratio test of a random walk.
        - [`ts_stat_tests.stationarity.algorithms.adf`][ts_stat_tests.stationarity.algorithms.adf]: Augmented Dickey-Fuller unit root test.
        - [`ts_stat_tests.stationarity.algorithms.kpss`][ts_stat_tests.stationarity.algorithms.kpss]: Kwiatkowski-Phillips-Schmidt-Shin stationarity test.
        - [`ts_stat_tests.stationarity.algorithms.rur`][ts_stat_tests.stationarity.algorithms.rur]: Range Unit-Root test of stationarity.
        - [`ts_stat_tests.stationarity.algorithms.za`][ts_stat_tests.stationarity.algorithms.za]: Zivot-Andrews structural break unit root test.
        - [`ts_stat_tests.stationarity.algorithms.pp`][ts_stat_tests.stationarity.algorithms.pp]: Phillips-Perron unit root test.
        - [`ts_stat_tests.stationarity.algorithms.ers`][ts_stat_tests.stationarity.algorithms.ers]: Elliot, Rothenberg and Stock's GLS-detrended Dickey-Fuller test.
        - [`ts_stat_tests.stationarity.algorithms.vr`][ts_stat_tests.stationarity.algorithms.vr]: Variance Ratio test of a random walk.
    """
    res = _vr(
        y=np.asarray(y),
        lags=lags,
        trend=trend,
        debiased=debiased,
        robust=robust,
        overlap=overlap,
    )
    return float(res.stat), float(res.pvalue), float(res.vr)