Coverage for src / ts_stat_tests / seasonality / tests.py: 100%
59 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-01 09:48 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-01 09:48 +0000
1# ============================================================================ #
2# #
3# Title: Seasonality Tests #
4# Purpose: Tests for seasonality detection algorithms. #
5# #
6# ============================================================================ #
9# ---------------------------------------------------------------------------- #
10# #
11# Overview ####
12# #
13# ---------------------------------------------------------------------------- #
16# ---------------------------------------------------------------------------- #
17# Description ####
18# ---------------------------------------------------------------------------- #
21"""
22!!! note "Summary"
23 This module contains functions to assess the seasonality of time series data.
25 The implemented algorithms include:
27 - QS Test
28 - OCSB Test
29 - CH Test
30 - Seasonal Strength
31 - Trend Strength
32 - Spikiness
34 Each function is designed to analyze a univariate time series and return relevant statistics or indicators of seasonality. This module provides both a dispatcher for flexible algorithm selection and a boolean check for easy integration into pipelines.
35"""
38# ---------------------------------------------------------------------------- #
39# #
40# Setup ####
41# #
42# ---------------------------------------------------------------------------- #
45# ---------------------------------------------------------------------------- #
46# Imports ####
47# ---------------------------------------------------------------------------- #
50# ## Python StdLib Imports ----
51from typing import Any, Callable, Optional, Union
53# ## Python Third Party Imports ----
54from numpy.typing import ArrayLike
55from pmdarima.arima import ARIMA
56from typeguard import typechecked
58# ## Local First Party Imports ----
59from ts_stat_tests.seasonality.algorithms import (
60 ch as _ch,
61 ocsb as _ocsb,
62 qs as _qs,
63 seasonal_strength as _seasonal_strength,
64 spikiness as _spikiness,
65 trend_strength as _trend_strength,
66)
67from ts_stat_tests.utils.errors import generate_error_message
70# ---------------------------------------------------------------------------- #
71# Exports ####
72# ---------------------------------------------------------------------------- #
75__all__: list[str] = ["seasonality", "is_seasonal"]
78# ---------------------------------------------------------------------------- #
79# #
80# Tests ####
81# #
82# ---------------------------------------------------------------------------- #
85@typechecked
86def seasonality(
87 x: ArrayLike,
88 algorithm: str = "qs",
89 **kwargs: Union[float, int, str, bool, ArrayLike, None],
90) -> Union[float, int, tuple[Union[float, int, ARIMA, None], ...]]:
91 """
92 !!! note "Summary"
93 Dispatcher for seasonality algorithms. This function provides a unified interface to call various seasonality tests.
95 ???+ abstract "Details"
97 The `seasonality` function acts as a centralized dispatcher for the various seasonality algorithms implemented in the `algorithms.seasonality` module. It allows users to easily switch between different tests by specifying the `algorithm` name.
99 The supported algorithms include:
101 - `"qs"`: The QS (Quenouille-Sarle) test for seasonality.
102 - `"ocsb"`: The Osborn-Chui-Smith-Birchenhall test for seasonal differencing.
103 - `"ch"`: The Canova-Hansen test for seasonal stability.
104 - `"seasonal_strength"` (or `"ss"`): The STL-based seasonal strength measure.
105 - `"trend_strength"` (or `"ts"`): The STL-based trend strength measure.
106 - `"spikiness"`: The STL-based spikiness measure.
108 Params:
109 x (ArrayLike):
110 The data to be checked.
111 algorithm (str, optional):
112 Which seasonality algorithm to use.<br>
113 Default: `"qs"`
114 kwargs (Union[float, int, str, bool, ArrayLike, None]):
115 Additional arguments to pass to the underlying algorithm.
117 Returns:
118 (Union[float, int, tuple[Union[float, int, object, None], ...]]):
119 The result of the seasonality test. The return type depends on the chosen algorithm:
120 - `"qs"` returns a tuple `(statistic, pvalue)`.
121 - `"ocsb"` and `"ch"` return an integer (0 or 1).
122 - `"seasonal_strength"`, `"trend_strength"`, and `"spikiness"` return a float.
124 ???+ example "Examples"
126 ```pycon {.py .python linenums="1" title="Basic usage"}
127 >>> from ts_stat_tests.utils.data import load_airline
128 >>> from ts_stat_tests.seasonality.tests import seasonality
129 >>> data = load_airline().values
130 >>> # Using the default QS test
131 >>> seasonality(x=data, freq=12)
132 (194.469289..., 5.909223...-43)
133 >>> # Using seasonal strength
134 >>> seasonality(x=data, algorithm="ss", m=12)
135 0.778721...
137 ```
138 """
139 options: dict[str, tuple[str, ...]] = {
140 "qs": ("qs",),
141 "ocsb": ("ocsb",),
142 "ch": ("ch",),
143 "seasonal_strength": ("seasonal_strength", "ss"),
144 "trend_strength": ("trend_strength", "ts"),
145 "spikiness": ("spikiness",),
146 }
148 # Internal helper to handle kwargs casting for ty
149 def _call(
150 func: Callable[..., Any],
151 **args: Any,
152 ) -> Any:
153 """
154 !!! note "Summary"
155 Internal helper to call the test function.
157 Params:
158 func (Callable[..., Any]):
159 The function to call.
160 args (Any):
161 The arguments to pass.
163 Returns:
164 (Any):
165 The result.
167 ???+ example "Examples"
169 ```python
170 # Internal helper.
171 ```
172 """
173 return func(**args)
175 if algorithm in options["qs"]:
176 return _call(_qs, x=x, **kwargs)
177 if algorithm in options["ocsb"]:
178 return _call(_ocsb, x=x, **kwargs)
179 if algorithm in options["ch"]:
180 return _call(_ch, x=x, **kwargs)
181 if algorithm in options["seasonal_strength"]:
182 return _call(_seasonal_strength, x=x, **kwargs)
183 if algorithm in options["trend_strength"]:
184 return _call(_trend_strength, x=x, **kwargs)
185 if algorithm in options["spikiness"]:
186 return _call(_spikiness, x=x, **kwargs)
188 raise ValueError(
189 generate_error_message(
190 parameter_name="algorithm",
191 value_parsed=algorithm,
192 options={k: str(v) for k, v in options.items()},
193 )
194 )
197@typechecked
198def is_seasonal(
199 x: ArrayLike,
200 algorithm: str = "qs",
201 alpha: float = 0.05,
202 **kwargs: Union[float, int, str, bool, ArrayLike, None],
203) -> dict[str, Union[str, float, bool, None]]:
204 """
205 !!! note "Summary"
206 Boolean check for seasonality. This function wraps the `seasonality` dispatcher and returns a standardized dictionary indicating whether the series is seasonal based on a significance level or threshold.
208 ???+ abstract "Details"
210 The `is_seasonal` function interprets the results of the underlying seasonality tests to provide a boolean `"result"`.
212 - For `"qs"`, the test is considered seasonal if the p-value is less than `alpha`.
213 - For `"ocsb"` and `"ch"`, the test is considered seasonal if the returned integer is 1.
214 - For `"seasonal_strength"`, the test is considered seasonal if the strength is greater than 0.64 (a common threshold in literature).
215 - For others, it checks if the statistic is greater than 0.
217 Params:
218 x (ArrayLike):
219 The data to be checked.
220 algorithm (str, optional):
221 Which seasonality algorithm to use.<br>
222 Default: `"qs"`
223 alpha (float, optional):
224 The significance level for the test (used by `"qs"`).<br>
225 Default: `0.05`
226 kwargs (Union[float, int, str, bool, ArrayLike, None]):
227 Additional arguments to pass to the underlying algorithm.
229 Returns:
230 (dict[str, Union[str, float, bool, None]]):
231 A dictionary containing:
233 - `"result"` (bool): Indicator if the series is seasonal.
234 - `"statistic"` (float): The test statistic (or strength).
235 - `"pvalue"` (float, optional): The p-value of the test (if available).
236 - `"alpha"` (float): The significance level used.
237 - `"algorithm"` (str): The algorithm used.
239 ???+ example "Examples"
241 ```pycon {.py .python linenums="1" title="Standard check"}
242 >>> from ts_stat_tests.utils.data import load_airline
243 >>> from ts_stat_tests.seasonality.tests import is_seasonal
244 >>> data = load_airline().values
245 >>> res = is_seasonal(x=data, algorithm="qs", freq=12)
246 >>> res["result"]
247 True
248 >>> res["algorithm"]
249 'qs'
251 ```
252 """
253 res: Any = seasonality(x=x, algorithm=algorithm, **kwargs)
255 is_sea: bool = False
256 stat: float = 0.0
257 pval: Optional[float] = None
259 if algorithm in ("qs",):
260 if isinstance(res, (tuple, list)):
261 v0: Any = res[0]
262 v1: Any = res[1]
263 stat = float(v0) if isinstance(v0, (int, float)) else 0.0
264 pval = float(v1) if isinstance(v1, (int, float)) else 1.0
265 is_sea = bool(pval < alpha)
266 elif algorithm in ("ocsb", "ch"):
267 if isinstance(res, (tuple, list)):
268 v0: Any = res[0]
269 stat = float(v0) if isinstance(v0, (int, float)) else 0.0
270 else:
271 v_any: Any = res
272 stat = float(v_any) if isinstance(res, (int, float)) else 0.0
273 is_sea = bool(stat == 1)
274 elif algorithm in ("seasonal_strength", "ss"):
275 if isinstance(res, (tuple, list)):
276 v0: Any = res[0]
277 stat = float(v0) if isinstance(v0, (int, float)) else 0.0
278 else:
279 v_any: Any = res
280 stat = float(v_any) if isinstance(res, (int, float)) else 0.0
281 # Default threshold of 0.64 is often used for seasonal strength
282 is_sea = bool(stat > 0.64)
283 else:
284 if isinstance(res, (tuple, list)):
285 v0: Any = res[0]
286 stat = float(v0) if isinstance(v0, (int, float)) else 0.0
287 else:
288 v_any: Any = res
289 stat = float(v_any) if isinstance(res, (int, float)) else 0.0
290 is_sea = bool(stat > 0)
292 return {
293 "result": is_sea,
294 "statistic": stat,
295 "pvalue": pval,
296 "alpha": alpha,
297 "algorithm": algorithm,
298 }