Coverage for src / ts_stat_tests / heteroscedasticity / tests.py: 100%
27 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: Heteroscedasticity Tests #
4# Purpose: Implementation of heteroscedasticity test wrappers. #
5# #
6# ============================================================================ #
9# ---------------------------------------------------------------------------- #
10# #
11# Overview ####
12# #
13# ---------------------------------------------------------------------------- #
16# ---------------------------------------------------------------------------- #
17# Description ####
18# ---------------------------------------------------------------------------- #
21"""
22!!! note "Summary"
23 This module provides wrapper functions for various heteroscedasticity tests including:
24 - ARCH Test
25 - Breusch-Pagan Test
26 - Goldfeld-Quandt Test
27 - White's Test
28"""
31# ---------------------------------------------------------------------------- #
32# #
33# Setup ####
34# #
35# ---------------------------------------------------------------------------- #
38# ---------------------------------------------------------------------------- #
39# Imports ####
40# ---------------------------------------------------------------------------- #
43# ## Python StdLib Imports ----
44from typing import Any, Callable, Union
46# ## Python Third Party Imports ----
47from numpy.typing import ArrayLike
48from statsmodels.regression.linear_model import (
49 RegressionResults,
50 RegressionResultsWrapper,
51)
52from typeguard import typechecked
54# ## Local First Party Imports ----
55from ts_stat_tests.heteroscedasticity.algorithms import arch, bpl, gq, wlm
56from ts_stat_tests.utils.errors import generate_error_message
59# ---------------------------------------------------------------------------- #
60# Exports ####
61# ---------------------------------------------------------------------------- #
64__all__: list[str] = ["heteroscedasticity", "is_heteroscedastic"]
67# ---------------------------------------------------------------------------- #
68# #
69# Tests ####
70# #
71# ---------------------------------------------------------------------------- #
74@typechecked
75def heteroscedasticity(
76 res: Union[RegressionResults, RegressionResultsWrapper],
77 algorithm: str = "bp",
78 **kwargs: Union[float, int, str, bool, ArrayLike, None],
79) -> tuple[Any, ...]:
80 """
81 !!! note "Summary"
82 Perform a heteroscedasticity test on a fitted regression model.
84 ???+ abstract "Details"
85 This function is a convenience wrapper around four underlying algorithms:<br>
86 - [`arch()`][ts_stat_tests.heteroscedasticity.algorithms.arch]<br>
87 - [`bp()`][ts_stat_tests.heteroscedasticity.algorithms.bpl]<br>
88 - [`gq()`][ts_stat_tests.heteroscedasticity.algorithms.gq]<br>
89 - [`white()`][ts_stat_tests.heteroscedasticity.algorithms.wlm]
91 Params:
92 res (Union[RegressionResults, RegressionResultsWrapper]):
93 The fitted regression model to be checked.
94 algorithm (str):
95 Which heteroscedasticity algorithm to use.<br>
96 - `arch()`: `["arch", "engle"]`<br>
97 - `bp()`: `["bp", "breusch-pagan", "breusch-pagan-lagrange-multiplier"]`<br>
98 - `gq()`: `["gq", "goldfeld-quandt"]`<br>
99 - `white()`: `["white"]`<br>
100 Default: `"bp"`
101 kwargs (Union[float, int, str, bool, ArrayLike, None]):
102 Additional keyword arguments passed to the underlying test function.
104 Raises:
105 (ValueError):
106 When the given value for `algorithm` is not valid.
108 Returns:
109 (Union[tuple[float, float, float, float], tuple[float, float, str], ResultsStore]):
110 The results of the heteroscedasticity test. The return type depends on the chosen algorithm and `kwargs`.
112 !!! success "Credit"
113 Calculations are performed by `statsmodels`.
115 ???+ example "Examples"
117 ```pycon {.py .python linenums="1" title="Setup"}
118 >>> import statsmodels.api as sm
119 >>> from ts_stat_tests.heteroscedasticity.tests import heteroscedasticity
120 >>> from ts_stat_tests.utils.data import data_line, data_random
121 >>> X = sm.add_constant(data_line)
122 >>> y = 2 * data_line + data_random
123 >>> res = sm.OLS(y, X).fit()
125 ```
127 ```pycon {.py .python linenums="1" title="Example 1: Breusch-Pagan test"}
128 >>> result = heteroscedasticity(res, algorithm="bp")
129 >>> print(f"p-value: {result[1]:.4f}")
130 p-value: 0.2461
132 ```
134 ```pycon {.py .python linenums="1" title="Example 2: ARCH test"}
135 >>> lm, lmp, f, fp = heteroscedasticity(res, algorithm="arch")
136 >>> print(f"ARCH p-value: {lmp:.4f}")
137 ARCH p-value: 0.9124
139 ```
140 """
141 options: dict[str, tuple[str, ...]] = {
142 "arch": ("arch", "engle"),
143 "bp": ("bp", "breusch-pagan", "breusch-pagan-lagrange-multiplier"),
144 "gq": ("gq", "goldfeld-quandt"),
145 "white": ("white",),
146 }
148 # Internal helper to handle kwargs casting for ty
149 def _call(
150 func: Callable[..., Any],
151 **args: Any,
152 ) -> tuple[Any, ...]:
153 """
154 !!! note "Summary"
155 Internal helper to handle keyword arguments types.
157 Params:
158 func (Callable[..., Any]):
159 The function to call.
160 args (Any):
161 The keyword arguments to pass.
163 Returns:
164 (tuple[Any, ...]):
165 The function output.
167 ???+ example "Examples"
168 This is an internal function and is not intended to be called directly.
169 """
170 return func(**args)
172 if algorithm in options["arch"]:
173 return _call(arch, resid=res.resid, **kwargs)
175 if algorithm in options["bp"]:
176 return _call(bpl, resid=res.resid, exog_het=res.model.exog, **kwargs)
178 if algorithm in options["gq"]:
179 return _call(gq, y=res.model.endog, x=res.model.exog, **kwargs)
181 if algorithm in options["white"]:
182 return _call(wlm, resid=res.resid, exog_het=res.model.exog, **kwargs)
184 raise ValueError(
185 generate_error_message(
186 parameter_name="algorithm",
187 value_parsed=algorithm,
188 options=options,
189 )
190 )
193@typechecked
194def is_heteroscedastic(
195 res: Union[RegressionResults, RegressionResultsWrapper],
196 algorithm: str = "bp",
197 alpha: float = 0.05,
198 **kwargs: Union[float, int, str, bool, ArrayLike, None],
199) -> dict[str, Union[str, float, bool, None]]:
200 """
201 !!! note "Summary"
202 Test whether a given model's residuals exhibit `heteroscedasticity` or not.
204 ???+ abstract "Details"
205 This function checks the results of a heteroscedasticity test against a significance level `alpha`. The null hypothesis ($H_0$) for all supported tests is homoscedasticity (constant variance). If the p-value is less than `alpha`, the null hypothesis is rejected in favor of heteroscedasticity.
207 Params:
208 res (Union[RegressionResults, RegressionResultsWrapper]):
209 The fitted regression model to be checked.
210 algorithm (str):
211 Which heteroscedasticity algorithm to use. See [`heteroscedasticity()`][ts_stat_tests.heteroscedasticity.tests.heteroscedasticity] for options.
212 Default: `"bp"`
213 alpha (float):
214 The significance level for the test.
215 Default: `0.05`
216 kwargs (Union[float, int, str, bool, ArrayLike, None]):
217 Additional keyword arguments passed to the underlying test function.
219 Returns:
220 (dict[str, Union[str, float, bool, None]]):
221 A dictionary containing:
222 - `"result"` (bool): Indicator if the residuals are heteroscedastic (i.e., p-value < alpha).
223 - `"statistic"` (float): The test statistic.
224 - `"pvalue"` (float): The p-value of the test.
225 - `"alpha"` (float): The significance level used.
226 - `"algorithm"` (str): The algorithm used.
228 ???+ example "Examples"
230 ```pycon {.py .python linenums="1" title="Setup"}
231 >>> import statsmodels.api as sm
232 >>> from ts_stat_tests.heteroscedasticity.tests import is_heteroscedastic
233 >>> from ts_stat_tests.utils.data import data_line, data_random
234 >>> X = sm.add_constant(data_line)
235 >>> y = 2 * data_line + data_random
236 >>> res = sm.OLS(y, X).fit()
238 ```
240 ```pycon {.py .python linenums="1" title="Example 1: Check heteroscedasticity with Breusch-Pagan"}
241 >>> res_check = is_heteroscedastic(res, algorithm="bp")
242 >>> print(res_check["result"])
243 False
245 ```
246 """
247 raw_res = heteroscedasticity(res=res, algorithm=algorithm, **kwargs)
249 # All heteroscedasticity algorithms return a tuple
250 # (lm, lmpval, fval, fpval) or (fval, pval, ...)
251 stat = float(raw_res[0])
252 pvalue = float(raw_res[1])
254 return {
255 "algorithm": algorithm,
256 "statistic": float(stat),
257 "pvalue": float(pvalue),
258 "alpha": alpha,
259 "result": bool(pvalue < alpha),
260 }